
- Fixed segfault when attempting to initialseek on disconnected streams - Fix 100% CPU bug in controller's stats code - WebRTC UDP bind socket improvements - Several segfault fixes - Increased packet reordering buffer size from 30 to 150 packets - Tweaks to default output/buffer behaviour for incoming pushes - Added message for load balancer checks - Fixed HLS content type - Stats fixes - Exit reason fixes - Fixed socket IP address detection - Fixed non-string arguments for stream settings - Added caching for getConnectedBinHost() - Added WebRTC playback rate control - Added/completed VP8/VP9 support to WebRTC/RTSP - Added live seek option to WebRTC - Fixed seek to exactly newest timestamp - Fixed HLS input # Conflicts: # lib/defines.h # src/input/input.cpp
542 lines
19 KiB
C++
542 lines
19 KiB
C++
/// \file rtmpchunks.cpp
|
|
/// Holds all code for the RTMPStream namespace.
|
|
|
|
#include "auth.h"
|
|
#include "defines.h"
|
|
#include "flv_tag.h"
|
|
#include "rtmpchunks.h"
|
|
#include "timing.h"
|
|
|
|
std::string RTMPStream::handshake_in; ///< Input for the handshake.
|
|
std::string RTMPStream::handshake_out; ///< Output for the handshake.
|
|
|
|
size_t RTMPStream::chunk_rec_max = 128;
|
|
size_t RTMPStream::chunk_snd_max = 128;
|
|
size_t RTMPStream::rec_window_size = 2500000;
|
|
size_t RTMPStream::snd_window_size = 2500000;
|
|
size_t RTMPStream::rec_window_at = 0;
|
|
size_t RTMPStream::snd_window_at = 0;
|
|
size_t RTMPStream::rec_cnt = 0;
|
|
size_t RTMPStream::snd_cnt = 0;
|
|
|
|
timeval RTMPStream::lastrec;
|
|
|
|
/// Holds the last sent chunk for every msg_id.
|
|
std::map<unsigned int, RTMPStream::Chunk> RTMPStream::lastsend;
|
|
/// Holds the last received chunk for every msg_id.
|
|
std::map<unsigned int, RTMPStream::Chunk> RTMPStream::lastrecv;
|
|
|
|
#define P1024 \
|
|
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404" \
|
|
"DD" \
|
|
"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7" \
|
|
"ED" \
|
|
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF"
|
|
|
|
char 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
|
|
|
|
char 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;
|
|
}
|
|
}
|
|
|
|
bool ValidateClientScheme(uint8_t *pBuffer, uint8_t scheme){
|
|
uint32_t clientDigestOffset = GetDigestOffset(pBuffer, scheme);
|
|
uint8_t pTempBuffer[1536 - 32];
|
|
memcpy(pTempBuffer, pBuffer, clientDigestOffset);
|
|
memcpy(pTempBuffer + clientDigestOffset, pBuffer + clientDigestOffset + 32, 1536 - clientDigestOffset - 32);
|
|
char pTempHash[32];
|
|
Secure::hmac_sha256bin((char *)pTempBuffer, 1536 - 32, genuineFPKey, 30, pTempHash);
|
|
bool result = (memcmp(pBuffer + clientDigestOffset, pTempHash, 32) == 0);
|
|
MEDIUM_MSG("Client scheme validation %hhi %s", scheme, result ? "success" : "failed");
|
|
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();
|
|
bool allow_short = lastsend.count(cs_id);
|
|
RTMPStream::Chunk prev = lastsend[cs_id];
|
|
unsigned int tmpi;
|
|
unsigned char chtype = 0x00;
|
|
if (allow_short && (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 == prev.ts_delta){
|
|
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;
|
|
}
|
|
ts_delta = tmpi;
|
|
if (tmpi >= 0x00ffffff){
|
|
ntime = tmpi;
|
|
tmpi = 0x00ffffff;
|
|
}
|
|
ts_header = tmpi;
|
|
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));
|
|
}
|
|
}
|
|
}else{
|
|
ts_header = prev.ts_header;
|
|
if (ts_header == 0xffffff){ntime = timestamp;}
|
|
}
|
|
// support for 0x00ffffff timestamps
|
|
if (ntime){
|
|
output += (unsigned char)((ntime >> 24) & 0xff);
|
|
output += (unsigned char)((ntime >> 16) & 0xff);
|
|
output += (unsigned char)((ntime >> 8) & 0xff);
|
|
output += (unsigned char)(ntime & 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);
|
|
}
|
|
}
|
|
if (ntime){
|
|
output += (unsigned char)((ntime >> 24) & 0xff);
|
|
output += (unsigned char)((ntime >> 16) & 0xff);
|
|
output += (unsigned char)((ntime >> 8) & 0xff);
|
|
output += (unsigned char)(ntime & 0xff);
|
|
}
|
|
}
|
|
}
|
|
lastsend[cs_id] = *this;
|
|
RTMPStream::snd_cnt += output.size();
|
|
return output;
|
|
}// SendChunk
|
|
|
|
/// Default constructor, creates an empty chunk with all values initialized to zero.
|
|
RTMPStream::Chunk::Chunk(){
|
|
headertype = 0;
|
|
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 = 0;
|
|
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;
|
|
// Commented bit is more efficient and correct according to RTMP spec.
|
|
// Simply passing "4" is the only thing that actually plays correctly, though.
|
|
// Adobe, if you're ever reading this... wtf? Seriously.
|
|
ch.cs_id = 4; //((unsigned char)tag.data[0]);
|
|
ch.timestamp = tag.tagTime();
|
|
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));
|
|
ch.len = ch.data.size();
|
|
ch.real_len = ch.len;
|
|
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::getMS();
|
|
ch.msg_type_id = type;
|
|
ch.msg_stream_id = 0;
|
|
ch.len_left = 0;
|
|
if (type == 6){
|
|
ch.len = 5;
|
|
ch.real_len = 5;
|
|
ch.data.resize(5);
|
|
((char *)ch.data.data())[4] = 0;
|
|
}else{
|
|
ch.len = 4;
|
|
ch.real_len = 4;
|
|
ch.data.resize(4);
|
|
}
|
|
*(int *)((char *)ch.data.data()) = 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::getMS();
|
|
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::getMS();
|
|
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::getMS();
|
|
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 Socket::Buffer into the current chunk.
|
|
/// Tries to read a whole chunk, removing data from the Buffer as it reads.
|
|
/// If a single packet contains a partial chunk, it will remove the packet 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 buffer The input 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;
|
|
}
|
|
|
|
bool allow_short = lastrecv.count(cs_id);
|
|
RTMPStream::Chunk prev = lastrecv[cs_id];
|
|
|
|
// process the rest of the header, for each chunk type
|
|
headertype = chunktype & 0xC0;
|
|
|
|
DONTEVEN_MSG("Parsing RTMP chunk header (%#.2hhX) at offset %#zx", chunktype, RTMPStream::rec_cnt);
|
|
|
|
switch (headertype){
|
|
case 0x00:
|
|
if (!buffer.available(i + 11)){
|
|
DONTEVEN_MSG("Cannot read whole header");
|
|
return false;
|
|
}// can't read whole header
|
|
indata = buffer.copy(i + 11);
|
|
timestamp = indata[i++] * 256 * 256;
|
|
timestamp += indata[i++] * 256;
|
|
timestamp += indata[i++];
|
|
ts_delta = timestamp;
|
|
ts_header = timestamp;
|
|
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)){
|
|
DONTEVEN_MSG("Cannot read whole header");
|
|
return false;
|
|
}// can't read whole header
|
|
indata = buffer.copy(i + 7);
|
|
if (!allow_short){WARN_MSG("Warning: Header type 0x40 with no valid previous chunk!");}
|
|
timestamp = indata[i++] * 256 * 256;
|
|
timestamp += indata[i++] * 256;
|
|
timestamp += indata[i++];
|
|
ts_header = timestamp;
|
|
if (timestamp != 0x00ffffff){
|
|
ts_delta = timestamp;
|
|
timestamp = prev.timestamp + ts_delta;
|
|
}
|
|
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)){
|
|
DONTEVEN_MSG("Cannot read whole header");
|
|
return false;
|
|
}// can't read whole header
|
|
indata = buffer.copy(i + 3);
|
|
if (!allow_short){WARN_MSG("Warning: Header type 0x80 with no valid previous chunk!");}
|
|
timestamp = indata[i++] * 256 * 256;
|
|
timestamp += indata[i++] * 256;
|
|
timestamp += indata[i++];
|
|
ts_header = timestamp;
|
|
if (timestamp != 0x00ffffff){
|
|
ts_delta = timestamp;
|
|
timestamp = prev.timestamp + ts_delta;
|
|
}
|
|
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 (!allow_short){WARN_MSG("Warning: Header type 0xC0 with no valid previous chunk!");}
|
|
timestamp = prev.timestamp + prev.ts_delta;
|
|
ts_header = prev.ts_header;
|
|
ts_delta = prev.ts_delta;
|
|
len = prev.len;
|
|
len_left = prev.len_left;
|
|
if (len_left > 0){timestamp = prev.timestamp;}
|
|
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;
|
|
}
|
|
|
|
DONTEVEN_MSG("Parsing RTMP chunk result: len_left=%d, real_len=%d", len_left, real_len);
|
|
|
|
// read extended timestamp, if necessary
|
|
if (ts_header == 0x00ffffff){
|
|
if (!buffer.available(i + 4)){
|
|
DONTEVEN_MSG("Cannot read timestamp");
|
|
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++];
|
|
ts_delta = timestamp;
|
|
DONTEVEN_MSG("Extended timestamp: %u", timestamp);
|
|
}
|
|
|
|
// read data if length > 0, and allocate it
|
|
if (real_len > 0){
|
|
if (!buffer.available(i + real_len)){
|
|
DONTEVEN_MSG("Cannot read all data yet");
|
|
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
|
|
if (handshake_in.size() < 1537){
|
|
FAIL_MSG("Handshake wasn't filled properly (%lu/1537) - aborting!", handshake_in.size());
|
|
return false;
|
|
}
|
|
Version = RTMPStream::handshake_in[0];
|
|
uint8_t *Client = (uint8_t *)RTMPStream::handshake_in.data() + 1;
|
|
RTMPStream::handshake_out.resize(3073);
|
|
uint8_t *Server = (uint8_t *)RTMPStream::handshake_out.data() + 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] = FILLER_DATA[i % sizeof(FILLER_DATA)];
|
|
}//"random" data
|
|
|
|
bool encrypted = (Version == 6);
|
|
HIGH_MSG("Handshake version is %hhi", Version);
|
|
uint8_t _validationScheme = 5;
|
|
if (ValidateClientScheme(Client, 0)) _validationScheme = 0;
|
|
if (ValidateClientScheme(Client, 1)) _validationScheme = 1;
|
|
|
|
HIGH_MSG("Handshake type is %hhi, encryption is %s", _validationScheme, encrypted ? "on" : "off");
|
|
uint32_t serverDigestOffset = GetDigestOffset(Server, _validationScheme);
|
|
uint32_t keyChallengeIndex = GetDigestOffset(Client, _validationScheme);
|
|
|
|
// FIRST 1536 bytes for server response
|
|
char pTempBuffer[1504];
|
|
memcpy(pTempBuffer, Server, serverDigestOffset);
|
|
memcpy(pTempBuffer + serverDigestOffset, Server + serverDigestOffset + 32, 1504 - serverDigestOffset);
|
|
Secure::hmac_sha256bin(pTempBuffer, 1504, genuineFMSKey, 36, (char *)Server + serverDigestOffset);
|
|
|
|
// SECOND 1536 bytes for server response
|
|
if (_validationScheme == 5 && Version == 3){
|
|
// copy exactly from client
|
|
memcpy(Server + 1536, Client, 1536);
|
|
}else{
|
|
char pTempHash[32];
|
|
Secure::hmac_sha256bin((char *)Client + keyChallengeIndex, 32, genuineFMSKey, 68, pTempHash);
|
|
Secure::hmac_sha256bin((char *)Server + 1536, 1536 - 32, pTempHash, 32, (char *)Server + 1536 * 2 - 32);
|
|
}
|
|
|
|
Server[-1] = Version;
|
|
RTMPStream::snd_cnt += 3073;
|
|
return true;
|
|
}
|