2017-04-01 16:35:56 +08:00
|
|
|
|
/*
|
|
|
|
|
* RtmpProtocol.cpp
|
|
|
|
|
*
|
|
|
|
|
* Created on: 2017年2月7日
|
|
|
|
|
* Author: xzl
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "RtmpProtocol.h"
|
2017-04-25 11:35:41 +08:00
|
|
|
|
#include "Rtsp/Rtsp.h"
|
|
|
|
|
#include "Rtmp/utils.h"
|
2017-04-01 16:35:56 +08:00
|
|
|
|
#include "Util/util.h"
|
|
|
|
|
#include "Util/onceToken.h"
|
2017-04-25 11:35:41 +08:00
|
|
|
|
#include "Thread/ThreadPool.h"
|
2017-04-01 16:35:56 +08:00
|
|
|
|
using namespace ZL::Util;
|
|
|
|
|
|
2017-05-16 11:45:36 +08:00
|
|
|
|
#ifdef ENABLE_OPENSSL
|
|
|
|
|
#include <openssl/hmac.h>
|
|
|
|
|
static string openssl_HMACsha256(const void *key,unsigned int key_len,
|
|
|
|
|
const void *data,unsigned int data_len){
|
|
|
|
|
char out[48];
|
|
|
|
|
unsigned int out_len;
|
|
|
|
|
HMAC_CTX ctx;
|
|
|
|
|
HMAC_CTX_init(&ctx);
|
|
|
|
|
HMAC_Init_ex(&ctx, key, key_len, EVP_sha256(), NULL);
|
|
|
|
|
HMAC_Update(&ctx, (unsigned char*)data, data_len);
|
|
|
|
|
HMAC_Final(&ctx, (unsigned char *)out, &out_len);
|
|
|
|
|
HMAC_CTX_cleanup(&ctx);
|
|
|
|
|
return string(out,out_len);
|
|
|
|
|
}
|
|
|
|
|
#endif //ENABLE_OPENSSL
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define C1_DIGEST_SIZE 32
|
|
|
|
|
#define C1_KEY_SIZE 128
|
|
|
|
|
#define C1_SCHEMA_SIZE 764
|
|
|
|
|
#define C1_HANDSHARK_SIZE (RANDOM_LEN + 8)
|
|
|
|
|
#define C1_FPKEY_SIZE 30
|
|
|
|
|
#define S1_FMS_KEY_SIZE 36
|
|
|
|
|
#define S2_FMS_KEY_SIZE 68
|
|
|
|
|
#define C1_OFFSET_SIZE 4
|
|
|
|
|
|
2017-04-01 16:35:56 +08:00
|
|
|
|
namespace ZL {
|
|
|
|
|
namespace Rtmp {
|
|
|
|
|
|
|
|
|
|
RtmpProtocol::RtmpProtocol() {
|
|
|
|
|
m_nextHandle = [this](){
|
|
|
|
|
handle_C0C1();
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
RtmpProtocol::~RtmpProtocol() {
|
|
|
|
|
clear();
|
|
|
|
|
}
|
|
|
|
|
void RtmpProtocol::clear() {
|
|
|
|
|
////////////ChunkSize////////////
|
|
|
|
|
m_iChunkLenIn = DEFAULT_CHUNK_LEN;
|
|
|
|
|
m_iChunkLenOut = DEFAULT_CHUNK_LEN;
|
|
|
|
|
////////////Acknowledgement////////////
|
|
|
|
|
m_ui32ByteSent = 0;
|
|
|
|
|
m_ui32LastSent = 0;
|
|
|
|
|
m_ui32WinSize = 0;
|
|
|
|
|
///////////PeerBandwidth///////////
|
|
|
|
|
m_ui32Bandwidth = 2500000;
|
|
|
|
|
m_ui8LimitType = 2;
|
|
|
|
|
////////////Chunk////////////
|
|
|
|
|
m_mapChunkData.clear();
|
|
|
|
|
m_iNowStreamID = 0;
|
|
|
|
|
m_iNowChunkID = 0;
|
|
|
|
|
//////////Invoke Request//////////
|
|
|
|
|
m_iReqID = 0;
|
|
|
|
|
//////////Rtmp parser//////////
|
|
|
|
|
m_strRcvBuf.clear();
|
|
|
|
|
m_ui32StreamId = STREAM_CONTROL;
|
|
|
|
|
m_nextHandle = [this]() {
|
|
|
|
|
handle_C0C1();
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RtmpProtocol::sendAcknowledgement(uint32_t ui32Size) {
|
|
|
|
|
std::string control;
|
|
|
|
|
uint32_t stream = htonl(ui32Size);
|
|
|
|
|
control.append((char *) &stream, 4);
|
|
|
|
|
sendRequest(MSG_ACK, control);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RtmpProtocol::sendAcknowledgementSize(uint32_t ui32Size) {
|
|
|
|
|
uint32_t windowSize = htonl(ui32Size);
|
|
|
|
|
std::string set_windowSize((char *) &windowSize, 4);
|
|
|
|
|
sendRequest(MSG_WIN_SIZE, set_windowSize);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RtmpProtocol::sendPeerBandwidth(uint32_t ui32Size) {
|
|
|
|
|
uint32_t peerBandwidth = htonl(ui32Size);
|
|
|
|
|
std::string set_peerBandwidth((char *) &peerBandwidth, 4);
|
|
|
|
|
set_peerBandwidth.push_back((char) 0x02);
|
|
|
|
|
sendRequest(MSG_SET_PEER_BW, set_peerBandwidth);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RtmpProtocol::sendChunkSize(uint32_t ui32Size) {
|
|
|
|
|
uint32_t len = htonl(ui32Size);
|
|
|
|
|
std::string set_chunk((char *) &len, 4);
|
|
|
|
|
sendRequest(MSG_SET_CHUNK, set_chunk);
|
|
|
|
|
m_iChunkLenOut = ui32Size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RtmpProtocol::sendPingRequest(uint32_t ui32TimeStamp) {
|
|
|
|
|
sendUserControl(CONTROL_PING_REQUEST, ui32TimeStamp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RtmpProtocol::sendPingResponse(uint32_t ui32TimeStamp) {
|
|
|
|
|
sendUserControl(CONTROL_PING_RESPONSE, ui32TimeStamp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RtmpProtocol::sendSetBufferLength(uint32_t ui32StreamId,
|
|
|
|
|
uint32_t ui32Length) {
|
|
|
|
|
std::string control;
|
|
|
|
|
ui32StreamId = htonl(ui32StreamId);
|
|
|
|
|
control.append((char *) &ui32StreamId, 4);
|
|
|
|
|
ui32Length = htonl(ui32Length);
|
|
|
|
|
control.append((char *) &ui32Length, 4);
|
|
|
|
|
sendUserControl(CONTROL_SETBUFFER, control);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RtmpProtocol::sendUserControl(uint16_t ui16EventType,
|
|
|
|
|
uint32_t ui32EventData) {
|
|
|
|
|
std::string control;
|
|
|
|
|
uint16_t type = htons(ui16EventType);
|
|
|
|
|
control.append((char *) &type, 2);
|
|
|
|
|
uint32_t stream = htonl(ui32EventData);
|
|
|
|
|
control.append((char *) &stream, 4);
|
|
|
|
|
sendRequest(MSG_USER_CONTROL, control);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RtmpProtocol::sendUserControl(uint16_t ui16EventType,
|
|
|
|
|
const string& strEventData) {
|
|
|
|
|
std::string control;
|
|
|
|
|
uint16_t type = htons(ui16EventType);
|
|
|
|
|
control.append((char *) &type, 2);
|
|
|
|
|
control.append(strEventData);
|
|
|
|
|
sendRequest(MSG_USER_CONTROL, control);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RtmpProtocol::sendResponse(int iType, const string& str) {
|
2017-05-13 17:25:31 +08:00
|
|
|
|
if(!m_bDataStarted && (iType == MSG_DATA)){
|
|
|
|
|
m_bDataStarted = true;
|
|
|
|
|
}
|
|
|
|
|
sendRtmp(iType, m_iNowStreamID, str, 0, m_bDataStarted ? CHUNK_CLIENT_REQUEST_AFTER : CHUNK_CLIENT_REQUEST_BEFORE);
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RtmpProtocol::sendInvoke(const string& strCmd, const AMFValue& val) {
|
|
|
|
|
AMFEncoder enc;
|
|
|
|
|
enc << strCmd << ++m_iReqID << val;
|
|
|
|
|
sendRequest(MSG_CMD, enc.data());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RtmpProtocol::sendRequest(int iCmd, const string& str) {
|
|
|
|
|
sendRtmp(iCmd, m_ui32StreamId, str, 0, CHUNK_SERVER_REQUEST);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RtmpProtocol::sendRtmp(uint8_t ui8Type, uint32_t ui32StreamId,
|
|
|
|
|
const std::string& strBuf, uint32_t ui32TimeStamp, int iChunkId) {
|
|
|
|
|
if (iChunkId < 2 || iChunkId > 63) {
|
|
|
|
|
auto strErr = StrPrinter << "不支持发送该类型的块流 ID:" << iChunkId << endl;
|
|
|
|
|
throw std::runtime_error(strErr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool bExtStamp = ui32TimeStamp >= 0xFFFFFF;
|
|
|
|
|
RtmpHeader header;
|
|
|
|
|
header.flags = (iChunkId & 0x3f) | (0 << 6);
|
|
|
|
|
header.typeId = ui8Type;
|
|
|
|
|
set_be24(header.timeStamp, bExtStamp ? 0xFFFFFF : ui32TimeStamp);
|
|
|
|
|
set_be24(header.bodySize, strBuf.size());
|
|
|
|
|
set_le32(header.streamId, ui32StreamId);
|
|
|
|
|
std::string strSend;
|
2017-05-16 11:45:36 +08:00
|
|
|
|
strSend.append((char *) &header, sizeof(header));
|
2017-04-01 16:35:56 +08:00
|
|
|
|
char acExtStamp[4];
|
|
|
|
|
if (bExtStamp) {
|
|
|
|
|
//扩展时间戳
|
|
|
|
|
set_be32(acExtStamp, ui32TimeStamp);
|
|
|
|
|
}
|
|
|
|
|
size_t pos = 0;
|
|
|
|
|
while (pos < strBuf.size()) {
|
|
|
|
|
if (pos) {
|
|
|
|
|
uint8_t flags = (iChunkId & 0x3f) | (3 << 6);
|
|
|
|
|
strSend += char(flags);
|
|
|
|
|
}
|
|
|
|
|
if (bExtStamp) {
|
|
|
|
|
//扩展时间戳
|
|
|
|
|
strSend.append(acExtStamp, 4);
|
|
|
|
|
}
|
|
|
|
|
size_t chunk = min(m_iChunkLenOut, strBuf.size() - pos);
|
|
|
|
|
strSend.append(strBuf, pos, chunk);
|
|
|
|
|
pos += chunk;
|
|
|
|
|
}
|
|
|
|
|
onSendRawData(strSend.data(),strSend.size());
|
|
|
|
|
m_ui32ByteSent += strSend.size();
|
|
|
|
|
if (m_ui32WinSize > 0 && m_ui32ByteSent - m_ui32LastSent >= m_ui32WinSize) {
|
|
|
|
|
m_ui32LastSent = m_ui32ByteSent;
|
|
|
|
|
sendAcknowledgement(m_ui32ByteSent);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RtmpProtocol::onParseRtmp(const char *pcRawData, int iSize) {
|
|
|
|
|
m_strRcvBuf.append(pcRawData, iSize);
|
|
|
|
|
auto cb = m_nextHandle;
|
|
|
|
|
cb();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
////for client////
|
|
|
|
|
void RtmpProtocol::startClientSession(const function<void()> &callBack) {
|
|
|
|
|
//发送 C0C1
|
|
|
|
|
char handshake_head = HANDSHAKE_PLAINTEXT;
|
|
|
|
|
onSendRawData(&handshake_head, 1);
|
2017-05-17 11:52:59 +08:00
|
|
|
|
RtmpHandshake c1(0);
|
2017-05-16 11:45:36 +08:00
|
|
|
|
onSendRawData((char *) (&c1), sizeof(c1));
|
2017-04-01 16:35:56 +08:00
|
|
|
|
m_nextHandle = [this,callBack]() {
|
|
|
|
|
//等待 S0+S1+S2
|
|
|
|
|
handle_S0S1S2(callBack);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
void RtmpProtocol::handle_S0S1S2(const function<void()> &callBack) {
|
2017-05-16 11:45:36 +08:00
|
|
|
|
if (m_strRcvBuf.size() < 1 + 2 * C1_HANDSHARK_SIZE) {
|
2017-04-01 16:35:56 +08:00
|
|
|
|
//数据不够
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (m_strRcvBuf[0] != HANDSHAKE_PLAINTEXT) {
|
|
|
|
|
throw std::runtime_error("only plaintext[0x03] handshake supported");
|
|
|
|
|
}
|
|
|
|
|
//发送 C2
|
|
|
|
|
const char *pcC2 = m_strRcvBuf.data() + 1;
|
2017-05-16 11:45:36 +08:00
|
|
|
|
onSendRawData(pcC2, C1_HANDSHARK_SIZE);
|
|
|
|
|
m_strRcvBuf.erase(0, 1 + 2 * C1_HANDSHARK_SIZE);
|
2017-04-01 16:35:56 +08:00
|
|
|
|
//握手结束
|
|
|
|
|
m_nextHandle = [this]() {
|
|
|
|
|
//握手结束并且开始进入解析命令模式
|
|
|
|
|
handle_rtmp();
|
|
|
|
|
};
|
|
|
|
|
callBack();
|
|
|
|
|
}
|
|
|
|
|
////for server ////
|
|
|
|
|
void RtmpProtocol::handle_C0C1() {
|
2017-05-16 11:45:36 +08:00
|
|
|
|
if (m_strRcvBuf.size() < 1 + C1_HANDSHARK_SIZE) {
|
2017-04-01 16:35:56 +08:00
|
|
|
|
//need more data!
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (m_strRcvBuf[0] != HANDSHAKE_PLAINTEXT) {
|
|
|
|
|
throw std::runtime_error("only plaintext[0x03] handshake supported");
|
|
|
|
|
}
|
2017-05-16 11:45:36 +08:00
|
|
|
|
if(memcmp(m_strRcvBuf.c_str() + 5,"\x00\x00\x00\x00",4) ==0 ){
|
|
|
|
|
//simple handsharke
|
|
|
|
|
handle_C1_simple();
|
|
|
|
|
}else{
|
|
|
|
|
#ifdef ENABLE_OPENSSL
|
|
|
|
|
//complex handsharke
|
|
|
|
|
handle_C1_complex();
|
|
|
|
|
#else
|
|
|
|
|
WarnL << "未打开ENABLE_OPENSSL宏,复杂握手采用简单方式处理!";
|
|
|
|
|
handle_C1_simple();
|
|
|
|
|
#endif//ENABLE_OPENSSL
|
|
|
|
|
}
|
|
|
|
|
m_strRcvBuf.erase(0, 1 + C1_HANDSHARK_SIZE);
|
|
|
|
|
}
|
|
|
|
|
void RtmpProtocol::handle_C1_simple(){
|
2017-05-13 17:25:31 +08:00
|
|
|
|
//发送S0
|
2017-05-16 11:45:36 +08:00
|
|
|
|
char handshake_head = HANDSHAKE_PLAINTEXT;
|
2017-04-01 16:35:56 +08:00
|
|
|
|
onSendRawData(&handshake_head, 1);
|
2017-05-13 17:25:31 +08:00
|
|
|
|
//发送S1
|
2017-05-16 11:45:36 +08:00
|
|
|
|
RtmpHandshake s1(0);
|
|
|
|
|
onSendRawData((char *) &s1, C1_HANDSHARK_SIZE);
|
2017-05-13 17:25:31 +08:00
|
|
|
|
//发送S2
|
2017-05-16 11:45:36 +08:00
|
|
|
|
onSendRawData(m_strRcvBuf.c_str() + 1, C1_HANDSHARK_SIZE);
|
2017-04-01 16:35:56 +08:00
|
|
|
|
//等待C2
|
|
|
|
|
m_nextHandle = [this]() {
|
|
|
|
|
handle_C2();
|
|
|
|
|
};
|
|
|
|
|
}
|
2017-05-16 11:51:18 +08:00
|
|
|
|
#ifdef ENABLE_OPENSSL
|
2017-05-16 11:45:36 +08:00
|
|
|
|
void RtmpProtocol::handle_C1_complex(){
|
|
|
|
|
//参考自:http://blog.csdn.net/win_lin/article/details/13006803
|
|
|
|
|
//skip c0,time,version
|
|
|
|
|
const char *c1_start = m_strRcvBuf.data() + 1;
|
|
|
|
|
const char *schema_start = c1_start + 8;
|
|
|
|
|
char *digest_start;
|
|
|
|
|
try{
|
|
|
|
|
/* c1s1 schema0
|
|
|
|
|
time: 4bytes
|
|
|
|
|
version: 4bytes
|
|
|
|
|
key: 764bytes
|
|
|
|
|
digest: 764bytes
|
|
|
|
|
*/
|
|
|
|
|
auto digest = get_C1_digest((uint8_t *)schema_start + C1_SCHEMA_SIZE,&digest_start);
|
|
|
|
|
string c1_joined(c1_start,C1_HANDSHARK_SIZE);
|
|
|
|
|
c1_joined.erase(digest_start - c1_start , C1_DIGEST_SIZE );
|
|
|
|
|
check_C1_Digest(digest,c1_joined);
|
|
|
|
|
|
|
|
|
|
send_complex_S0S1S2(0,digest);
|
|
|
|
|
InfoL << "schema0";
|
|
|
|
|
}catch(std::exception &ex){
|
|
|
|
|
//貌似flash从来都不用schema1
|
|
|
|
|
WarnL << "try rtmp complex schema0 failed:" << ex.what();
|
|
|
|
|
try{
|
|
|
|
|
/* c1s1 schema1
|
|
|
|
|
time: 4bytes
|
|
|
|
|
version: 4bytes
|
|
|
|
|
digest: 764bytes
|
|
|
|
|
key: 764bytes
|
|
|
|
|
*/
|
|
|
|
|
auto digest = get_C1_digest((uint8_t *)schema_start,&digest_start);
|
|
|
|
|
string c1_joined(c1_start,C1_HANDSHARK_SIZE);
|
|
|
|
|
c1_joined.erase(digest_start - c1_start , C1_DIGEST_SIZE );
|
|
|
|
|
check_C1_Digest(digest,c1_joined);
|
|
|
|
|
|
|
|
|
|
send_complex_S0S1S2(1,digest);
|
|
|
|
|
InfoL << "schema1";
|
|
|
|
|
}catch(std::exception &ex){
|
|
|
|
|
WarnL << "try rtmp complex schema1 failed:" << ex.what();
|
|
|
|
|
handle_C1_simple();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static u_int8_t FMSKey[] = {
|
|
|
|
|
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, 0x65, 0x72,
|
|
|
|
|
0x20, 0x30, 0x30, 0x31, // Genuine Adobe Flash Media Server 001
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
static u_int8_t FPKey[] = {
|
|
|
|
|
0x47, 0x65, 0x6E, 0x75, 0x69, 0x6E, 0x65, 0x20,
|
|
|
|
|
0x41, 0x64, 0x6F, 0x62, 0x65, 0x20, 0x46, 0x6C,
|
|
|
|
|
0x61, 0x73, 0x68, 0x20, 0x50, 0x6C, 0x61, 0x79,
|
|
|
|
|
0x65, 0x72, 0x20, 0x30, 0x30, 0x31, // Genuine Adobe Flash Player 001
|
|
|
|
|
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
|
|
|
|
|
void RtmpProtocol::check_C1_Digest(const string &digest,const string &data){
|
|
|
|
|
auto sha256 = openssl_HMACsha256(FPKey,C1_FPKEY_SIZE,data.data(),data.size());
|
|
|
|
|
if(sha256 != digest){
|
|
|
|
|
throw std::runtime_error("digest不匹配");
|
|
|
|
|
}else{
|
|
|
|
|
InfoL << "check rtmp complex handshark success!";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
string RtmpProtocol::get_C1_digest(const uint8_t *ptr,char **digestPos){
|
|
|
|
|
/* 764bytes digest结构
|
|
|
|
|
offset: 4bytes
|
|
|
|
|
random-data: (offset)bytes
|
|
|
|
|
digest-data: 32bytes
|
|
|
|
|
random-data: (764-4-offset-32)bytes
|
|
|
|
|
*/
|
|
|
|
|
int offset = 0;
|
|
|
|
|
for(int i=0;i<C1_OFFSET_SIZE;++i){
|
|
|
|
|
offset += ptr[i];
|
|
|
|
|
}
|
|
|
|
|
offset %= (C1_SCHEMA_SIZE - C1_DIGEST_SIZE - C1_OFFSET_SIZE);
|
|
|
|
|
*digestPos = (char *)ptr + C1_OFFSET_SIZE + offset;
|
|
|
|
|
string digest(*digestPos,C1_DIGEST_SIZE);
|
|
|
|
|
//DebugL << "digest offset:" << offset << ",digest:" << hexdump(digest.data(),digest.size());
|
|
|
|
|
return digest;
|
|
|
|
|
}
|
|
|
|
|
string RtmpProtocol::get_C1_key(const uint8_t *ptr){
|
|
|
|
|
/* 764bytes key结构
|
|
|
|
|
random-data: (offset)bytes
|
|
|
|
|
key-data: 128bytes
|
|
|
|
|
random-data: (764-offset-128-4)bytes
|
|
|
|
|
offset: 4bytes
|
|
|
|
|
*/
|
|
|
|
|
int offset = 0;
|
|
|
|
|
for(int i = C1_SCHEMA_SIZE - C1_OFFSET_SIZE;i< C1_SCHEMA_SIZE;++i){
|
|
|
|
|
offset += ptr[i];
|
|
|
|
|
}
|
|
|
|
|
offset %= (C1_SCHEMA_SIZE - C1_KEY_SIZE - C1_OFFSET_SIZE);
|
|
|
|
|
string key((char *)ptr + offset,C1_KEY_SIZE);
|
|
|
|
|
//DebugL << "key offset:" << offset << ",key:" << hexdump(key.data(),key.size());
|
|
|
|
|
return key;
|
|
|
|
|
}
|
|
|
|
|
void RtmpProtocol::send_complex_S0S1S2(int schemeType,const string &digest){
|
|
|
|
|
//S1S2计算参考自:https://github.com/hitYangfei/golang/blob/master/rtmpserver.go
|
|
|
|
|
//发送S0
|
|
|
|
|
char handshake_head = HANDSHAKE_PLAINTEXT;
|
|
|
|
|
onSendRawData(&handshake_head, 1);
|
|
|
|
|
//S1
|
2017-05-17 11:52:59 +08:00
|
|
|
|
RtmpHandshake s1(0);
|
2017-05-16 11:45:36 +08:00
|
|
|
|
memcpy(s1.zero,"\x04\x05\x00\x01",4);
|
|
|
|
|
char *digestPos;
|
|
|
|
|
if(schemeType == 0){
|
|
|
|
|
/* c1s1 schema0
|
|
|
|
|
time: 4bytes
|
|
|
|
|
version: 4bytes
|
|
|
|
|
key: 764bytes
|
|
|
|
|
digest: 764bytes
|
|
|
|
|
*/
|
|
|
|
|
get_C1_digest(s1.random + C1_SCHEMA_SIZE,&digestPos);
|
|
|
|
|
}else{
|
|
|
|
|
/* c1s1 schema1
|
|
|
|
|
time: 4bytes
|
|
|
|
|
version: 4bytes
|
|
|
|
|
digest: 764bytes
|
|
|
|
|
key: 764bytes
|
|
|
|
|
*/
|
|
|
|
|
get_C1_digest(s1.random,&digestPos);
|
|
|
|
|
}
|
|
|
|
|
char *s1_start = (char *)&s1;
|
|
|
|
|
string s1_joined(s1_start,sizeof(s1));
|
|
|
|
|
s1_joined.erase(digestPos - s1_start,C1_DIGEST_SIZE);
|
|
|
|
|
string s1_digest = openssl_HMACsha256(FMSKey,S1_FMS_KEY_SIZE,s1_joined.data(),s1_joined.size());
|
|
|
|
|
memcpy(digestPos,s1_digest.data(),s1_digest.size());
|
|
|
|
|
onSendRawData((char *) &s1, sizeof(s1));
|
2017-04-01 16:35:56 +08:00
|
|
|
|
|
2017-05-16 11:45:36 +08:00
|
|
|
|
//S2
|
|
|
|
|
string s2_key = openssl_HMACsha256(FMSKey,S2_FMS_KEY_SIZE,digest.data(),digest.size());
|
|
|
|
|
RtmpHandshake s2(0);
|
|
|
|
|
s2.random_generate((char *)&s2,8);
|
|
|
|
|
string s2_digest = openssl_HMACsha256(s2_key.data(),s2_key.size(),&s2,sizeof(s2) - C1_DIGEST_SIZE);
|
|
|
|
|
memcpy((char *)&s2 + C1_HANDSHARK_SIZE - C1_DIGEST_SIZE,s2_digest.data(),C1_DIGEST_SIZE);
|
|
|
|
|
onSendRawData((char *)&s2, sizeof(s2));
|
|
|
|
|
//等待C2
|
|
|
|
|
m_nextHandle = [this]() {
|
|
|
|
|
handle_C2();
|
|
|
|
|
};
|
|
|
|
|
}
|
2017-05-16 11:51:18 +08:00
|
|
|
|
#endif //ENABLE_OPENSSL
|
2017-04-01 16:35:56 +08:00
|
|
|
|
void RtmpProtocol::handle_C2() {
|
2017-05-16 11:45:36 +08:00
|
|
|
|
if (m_strRcvBuf.size() < C1_HANDSHARK_SIZE) {
|
2017-04-01 16:35:56 +08:00
|
|
|
|
//need more data!
|
|
|
|
|
return;
|
|
|
|
|
}
|
2017-05-16 11:45:36 +08:00
|
|
|
|
m_strRcvBuf.erase(0, C1_HANDSHARK_SIZE);
|
2017-04-01 16:35:56 +08:00
|
|
|
|
//握手结束,进入命令模式
|
|
|
|
|
if (!m_strRcvBuf.empty()) {
|
|
|
|
|
handle_rtmp();
|
|
|
|
|
}
|
|
|
|
|
m_nextHandle = [this]() {
|
|
|
|
|
handle_rtmp();
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RtmpProtocol::handle_rtmp() {
|
|
|
|
|
while (!m_strRcvBuf.empty()) {
|
|
|
|
|
uint8_t flags = m_strRcvBuf[0];
|
|
|
|
|
int iOffset = 0;
|
|
|
|
|
static const size_t HEADER_LENGTH[] = { 12, 8, 4, 1 };
|
|
|
|
|
size_t iHeaderLen = HEADER_LENGTH[flags >> 6];
|
|
|
|
|
m_iNowChunkID = flags & 0x3f;
|
|
|
|
|
switch (m_iNowChunkID) {
|
|
|
|
|
case 0: {
|
|
|
|
|
//0 值表示二字节形式,并且 ID 范围 64 - 319
|
|
|
|
|
//(第二个字节 + 64)。
|
|
|
|
|
if (m_strRcvBuf.size() < 2) {
|
|
|
|
|
//need more data
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
m_iNowChunkID = 64 + (uint8_t) (m_strRcvBuf[1]);
|
|
|
|
|
iOffset = 1;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 1: {
|
|
|
|
|
//1 值表示三字节形式,并且 ID 范围为 64 - 65599
|
|
|
|
|
//((第三个字节) * 256 + 第二个字节 + 64)。
|
|
|
|
|
if (m_strRcvBuf.size() < 3) {
|
|
|
|
|
//need more data
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
m_iNowChunkID = 64 + ((uint8_t) (m_strRcvBuf[2]) << 8) + (uint8_t) (m_strRcvBuf[1]);
|
|
|
|
|
iOffset = 2;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
//带有 2 值的块流 ID 被保留,用于下层协议控制消息和命令。
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_strRcvBuf.size() < iHeaderLen + iOffset) {
|
|
|
|
|
//need more data
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
RtmpHeader &header = *((RtmpHeader *) (m_strRcvBuf.data() + iOffset));
|
|
|
|
|
auto &chunkData = m_mapChunkData[m_iNowChunkID];
|
|
|
|
|
chunkData.chunkId = m_iNowChunkID;
|
|
|
|
|
switch (iHeaderLen) {
|
|
|
|
|
case 12:
|
|
|
|
|
chunkData.streamId = load_le32(header.streamId);
|
|
|
|
|
case 8:
|
|
|
|
|
chunkData.bodySize = load_be24(header.bodySize);
|
|
|
|
|
chunkData.typeId = header.typeId;
|
|
|
|
|
case 4:
|
2017-05-27 09:42:08 +08:00
|
|
|
|
chunkData.deltaStamp = load_be24(header.timeStamp);
|
2017-04-01 16:35:56 +08:00
|
|
|
|
}
|
2017-05-27 09:42:08 +08:00
|
|
|
|
|
|
|
|
|
if (chunkData.deltaStamp == 0xFFFFFF) {
|
2017-04-01 16:35:56 +08:00
|
|
|
|
if (m_strRcvBuf.size() < iHeaderLen + iOffset + 4) {
|
|
|
|
|
//need more data
|
|
|
|
|
return;
|
|
|
|
|
}
|
2017-05-27 09:42:08 +08:00
|
|
|
|
chunkData.deltaStamp = load_be32( m_strRcvBuf.data() + iOffset + iHeaderLen);
|
2017-04-01 16:35:56 +08:00
|
|
|
|
iOffset += 4;
|
|
|
|
|
}
|
2017-05-27 09:42:08 +08:00
|
|
|
|
|
|
|
|
|
if (chunkData.bodySize < chunkData.strBuf.size()) {
|
2017-04-01 16:35:56 +08:00
|
|
|
|
throw std::runtime_error("非法的bodySize");
|
|
|
|
|
}
|
2017-05-27 09:42:08 +08:00
|
|
|
|
|
2017-04-01 16:35:56 +08:00
|
|
|
|
auto iMore = min(m_iChunkLenIn, chunkData.bodySize - chunkData.strBuf.size());
|
|
|
|
|
if (m_strRcvBuf.size() < iHeaderLen + iOffset + iMore) {
|
|
|
|
|
//need more data
|
|
|
|
|
return;
|
|
|
|
|
}
|
2017-05-27 09:42:08 +08:00
|
|
|
|
|
|
|
|
|
chunkData.strBuf.append(m_strRcvBuf, iHeaderLen + iOffset, iMore);
|
2017-04-01 16:35:56 +08:00
|
|
|
|
m_strRcvBuf.erase(0, iHeaderLen + iOffset + iMore);
|
2017-05-27 09:42:08 +08:00
|
|
|
|
|
2017-04-01 16:35:56 +08:00
|
|
|
|
if (chunkData.strBuf.size() == chunkData.bodySize) {
|
2017-05-27 09:42:08 +08:00
|
|
|
|
//frame is ready
|
|
|
|
|
chunkData.timeStamp = chunkData.deltaStamp + (iHeaderLen == 12 ? 0 : chunkData.timeStamp);
|
2017-04-01 16:35:56 +08:00
|
|
|
|
m_iNowStreamID = chunkData.streamId;
|
2017-05-18 09:10:22 +08:00
|
|
|
|
if(chunkData.bodySize){
|
|
|
|
|
handle_rtmpChunk(chunkData);
|
|
|
|
|
}
|
2017-04-01 16:35:56 +08:00
|
|
|
|
chunkData.strBuf.clear();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RtmpProtocol::handle_rtmpChunk(RtmpPacket& chunkData) {
|
|
|
|
|
switch (chunkData.typeId) {
|
|
|
|
|
case MSG_ACK: {
|
|
|
|
|
if (chunkData.strBuf.size() < 4) {
|
|
|
|
|
throw std::runtime_error("MSG_ACK: Not enough data");
|
|
|
|
|
}
|
|
|
|
|
//auto bytePeerRecv = load_be32(&chunkData.strBuf[0]);
|
|
|
|
|
//TraceL << "MSG_ACK:" << bytePeerRecv;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case MSG_SET_CHUNK: {
|
|
|
|
|
if (chunkData.strBuf.size() < 4) {
|
|
|
|
|
throw std::runtime_error("MSG_SET_CHUNK :Not enough data");
|
|
|
|
|
}
|
|
|
|
|
m_iChunkLenIn = load_be32(&chunkData.strBuf[0]);
|
|
|
|
|
TraceL << "MSG_SET_CHUNK:" << m_iChunkLenIn;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case MSG_USER_CONTROL: {
|
|
|
|
|
//user control message
|
|
|
|
|
if (chunkData.strBuf.size() < 2) {
|
|
|
|
|
throw std::runtime_error("MSG_USER_CONTROL: Not enough data.");
|
|
|
|
|
}
|
|
|
|
|
uint16_t event_type = load_be16(&chunkData.strBuf[0]);
|
|
|
|
|
chunkData.strBuf.erase(0, 2);
|
|
|
|
|
switch (event_type) {
|
|
|
|
|
case CONTROL_PING_REQUEST: {
|
|
|
|
|
if (chunkData.strBuf.size() < 4) {
|
|
|
|
|
throw std::runtime_error("CONTROL_PING_REQUEST: Not enough data.");
|
|
|
|
|
}
|
|
|
|
|
uint32_t timeStamp = load_be32(&chunkData.strBuf[0]);
|
|
|
|
|
//TraceL << "CONTROL_PING_REQUEST:" << timeStamp;
|
|
|
|
|
sendUserControl(CONTROL_PING_RESPONSE, timeStamp);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case CONTROL_PING_RESPONSE: {
|
|
|
|
|
if (chunkData.strBuf.size() < 4) {
|
|
|
|
|
throw std::runtime_error("CONTROL_PING_RESPONSE: Not enough data.");
|
|
|
|
|
}
|
|
|
|
|
//uint32_t timeStamp = load_be32(&chunkData.strBuf[0]);
|
|
|
|
|
//TraceL << "CONTROL_PING_RESPONSE:" << timeStamp;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case CONTROL_STREAM_BEGIN: {
|
|
|
|
|
//开始播放
|
|
|
|
|
if (chunkData.strBuf.size() < 4) {
|
|
|
|
|
throw std::runtime_error("CONTROL_STREAM_BEGIN: Not enough data.");
|
|
|
|
|
}
|
|
|
|
|
uint32_t stramId = load_be32(&chunkData.strBuf[0]);
|
|
|
|
|
onStreamBegin(stramId);
|
|
|
|
|
TraceL << "CONTROL_STREAM_BEGIN:" << stramId;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case CONTROL_STREAM_EOF: {
|
|
|
|
|
//暂停
|
|
|
|
|
if (chunkData.strBuf.size() < 4) {
|
|
|
|
|
throw std::runtime_error("CONTROL_STREAM_EOF: Not enough data.");
|
|
|
|
|
}
|
|
|
|
|
uint32_t stramId = load_be32(&chunkData.strBuf[0]);
|
|
|
|
|
onStreamEof(stramId);
|
|
|
|
|
TraceL << "CONTROL_STREAM_EOF:" << stramId;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case CONTROL_STREAM_DRY: {
|
|
|
|
|
//停止播放
|
|
|
|
|
if (chunkData.strBuf.size() < 4) {
|
|
|
|
|
throw std::runtime_error("CONTROL_STREAM_DRY: Not enough data.");
|
|
|
|
|
}
|
|
|
|
|
uint32_t stramId = load_be32(&chunkData.strBuf[0]);
|
|
|
|
|
onStreamDry(stramId);
|
|
|
|
|
TraceL << "CONTROL_STREAM_DRY:" << stramId;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
//WarnL << "unhandled user control:" << event_type;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case MSG_WIN_SIZE: {
|
|
|
|
|
m_ui32WinSize = load_be32(&chunkData.strBuf[0]);
|
|
|
|
|
TraceL << "MSG_WIN_SIZE:" << m_ui32WinSize;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case MSG_SET_PEER_BW: {
|
|
|
|
|
m_ui32Bandwidth = load_be32(&chunkData.strBuf[0]);
|
|
|
|
|
m_ui8LimitType = chunkData.strBuf[4];
|
|
|
|
|
TraceL << "MSG_SET_PEER_BW:" << m_ui32WinSize;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case MSG_AGGREGATE:
|
|
|
|
|
throw std::runtime_error("streaming FLV not supported");
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
onRtmpChunk(chunkData);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} /* namespace Rtmp */
|
|
|
|
|
} /* namespace ZL */
|