897 lines
32 KiB
C++
897 lines
32 KiB
C++
/*
|
||
* Copyright (c) 2016-present The ZLMediaKit project authors. All Rights Reserved.
|
||
*
|
||
* This file is part of ZLMediaKit(https://github.com/ZLMediaKit/ZLMediaKit).
|
||
*
|
||
* Use of this source code is governed by MIT-like license that can be found in the
|
||
* LICENSE file in the root of the source tree. All contributing project authors
|
||
* may be found in the AUTHORS file in the root of the source tree.
|
||
*/
|
||
|
||
#include <algorithm>
|
||
#include "RtmpProtocol.h"
|
||
#include "Rtmp/utils.h"
|
||
#include "RtmpMediaSource.h"
|
||
#include "Util/util.h"
|
||
|
||
using namespace std;
|
||
using namespace toolkit;
|
||
|
||
#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
|
||
|
||
#ifdef ENABLE_OPENSSL
|
||
#include "Util/SSLBox.h"
|
||
#include <openssl/hmac.h>
|
||
#include <openssl/opensslv.h>
|
||
|
||
static string openssl_HMACsha256(const void *key, size_t key_len, const void *data, size_t data_len){
|
||
std::shared_ptr<char> out(new char[32], [](char *ptr) { delete[] ptr; });
|
||
unsigned int out_len;
|
||
|
||
#if defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER > 0x10100000L)
|
||
// openssl 1.1.0新增api,老版本api作废 [AUTO-TRANSLATED:4c7b59a8]
|
||
// OpenSSL 1.1.0 added new APIs, old APIs are deprecated
|
||
HMAC_CTX *ctx = HMAC_CTX_new();
|
||
HMAC_CTX_reset(ctx);
|
||
HMAC_Init_ex(ctx, key, (int)key_len, EVP_sha256(), NULL);
|
||
HMAC_Update(ctx, (unsigned char*)data, data_len);
|
||
HMAC_Final(ctx, (unsigned char *)out.get(), &out_len);
|
||
HMAC_CTX_reset(ctx);
|
||
HMAC_CTX_free(ctx);
|
||
#else
|
||
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.get(), &out_len);
|
||
HMAC_CTX_cleanup(&ctx);
|
||
#endif //defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER > 0x10100000L)
|
||
return string(out.get(),out_len);
|
||
}
|
||
#endif //ENABLE_OPENSSL
|
||
|
||
namespace mediakit {
|
||
|
||
RtmpProtocol::RtmpProtocol() {
|
||
_packet_pool.setSize(64);
|
||
_next_step_func = [this](const char *data, size_t len) {
|
||
return handle_C0C1(data, len);
|
||
};
|
||
}
|
||
|
||
RtmpProtocol::~RtmpProtocol() {
|
||
reset();
|
||
}
|
||
|
||
void RtmpProtocol::reset() {
|
||
////////////ChunkSize////////////
|
||
_chunk_size_in = DEFAULT_CHUNK_LEN;
|
||
_chunk_size_out = DEFAULT_CHUNK_LEN;
|
||
////////////Acknowledgement////////////
|
||
_bytes_sent = 0;
|
||
_bytes_sent_last = 0;
|
||
_windows_size = 0;
|
||
///////////PeerBandwidth///////////
|
||
_bandwidth = 2500000;
|
||
_band_limit_type = 2;
|
||
////////////Chunk////////////
|
||
_map_chunk_data.clear();
|
||
_now_stream_index = 0;
|
||
_now_chunk_id = 0;
|
||
//////////Invoke Request//////////
|
||
_send_req_id = 0;
|
||
//////////Rtmp parser//////////
|
||
HttpRequestSplitter::reset();
|
||
_stream_index = STREAM_CONTROL;
|
||
_next_step_func = [this](const char *data, size_t len) {
|
||
return handle_C0C1(data, len);
|
||
};
|
||
}
|
||
|
||
void RtmpProtocol::sendAcknowledgement(uint32_t size) {
|
||
size = htonl(size);
|
||
std::string acknowledgement((char *) &size, 4);
|
||
sendRequest(MSG_ACK, acknowledgement);
|
||
}
|
||
|
||
void RtmpProtocol::sendAcknowledgementSize(uint32_t size) {
|
||
size = htonl(size);
|
||
std::string set_windowSize((char *) &size, 4);
|
||
sendRequest(MSG_WIN_SIZE, set_windowSize);
|
||
}
|
||
|
||
void RtmpProtocol::sendPeerBandwidth(uint32_t size) {
|
||
size = htonl(size);
|
||
std::string set_peerBandwidth((char *) &size, 4);
|
||
set_peerBandwidth.push_back((char) 0x02);
|
||
sendRequest(MSG_SET_PEER_BW, set_peerBandwidth);
|
||
}
|
||
|
||
void RtmpProtocol::sendChunkSize(uint32_t size) {
|
||
uint32_t len = htonl(size);
|
||
std::string set_chunk((char *) &len, 4);
|
||
sendRequest(MSG_SET_CHUNK, set_chunk);
|
||
_chunk_size_out = size;
|
||
}
|
||
|
||
void RtmpProtocol::sendPingRequest(uint32_t stamp) {
|
||
sendUserControl(CONTROL_PING_REQUEST, stamp);
|
||
}
|
||
|
||
void RtmpProtocol::sendPingResponse(uint32_t time_stamp) {
|
||
sendUserControl(CONTROL_PING_RESPONSE, time_stamp);
|
||
}
|
||
|
||
void RtmpProtocol::sendSetBufferLength(uint32_t stream_index, uint32_t len) {
|
||
std::string control;
|
||
stream_index = htonl(stream_index);
|
||
control.append((char *) &stream_index, 4);
|
||
|
||
len = htonl(len);
|
||
control.append((char *) &len, 4);
|
||
sendUserControl(CONTROL_SETBUFFER, control);
|
||
}
|
||
|
||
void RtmpProtocol::sendUserControl(uint16_t event_type, uint32_t event_data) {
|
||
std::string control;
|
||
event_type = htons(event_type);
|
||
control.append((char *) &event_type, 2);
|
||
|
||
event_data = htonl(event_data);
|
||
control.append((char *) &event_data, 4);
|
||
sendRequest(MSG_USER_CONTROL, control);
|
||
}
|
||
|
||
void RtmpProtocol::sendUserControl(uint16_t event_type, const string &event_data) {
|
||
std::string control;
|
||
event_type = htons(event_type);
|
||
control.append((char *) &event_type, 2);
|
||
control.append(event_data);
|
||
sendRequest(MSG_USER_CONTROL, control);
|
||
}
|
||
|
||
void RtmpProtocol::sendResponse(int type, const string &str) {
|
||
if(!_data_started && (type == MSG_DATA)){
|
||
_data_started = true;
|
||
}
|
||
sendRtmp(type, _now_stream_index, str, 0, _data_started ? CHUNK_CLIENT_REQUEST_AFTER : CHUNK_CLIENT_REQUEST_BEFORE);
|
||
}
|
||
|
||
void RtmpProtocol::sendInvoke(const string &cmd, const AMFValue &val) {
|
||
AMFEncoder enc;
|
||
enc << cmd << ++_send_req_id << val;
|
||
sendRequest(MSG_CMD, enc.data());
|
||
}
|
||
|
||
void RtmpProtocol::sendRequest(int cmd, const string& str) {
|
||
if (cmd <= MSG_SET_PEER_BW) {
|
||
// 若 cmd 属于 Protocol Control Messages ,则应使用 chunk id 2 发送 [AUTO-TRANSLATED:3f17e21f]
|
||
// If cmd belongs to Protocol Control Messages, it should be sent using chunk id 2
|
||
sendRtmp(cmd, _stream_index, str, 0, CHUNK_NETWORK);
|
||
} else {
|
||
// 否则使用 chunk id 发送(任意值3-128,参见 obs 及 ffmpeg 选取 3) [AUTO-TRANSLATED:65f8d861]
|
||
// Otherwise, use chunk id to send (any value 3-128, see obs and ffmpeg select 3)
|
||
sendRtmp(cmd, _stream_index, str, 0, CHUNK_SYSTEM);
|
||
}
|
||
}
|
||
|
||
class BufferPartial : public Buffer {
|
||
public:
|
||
BufferPartial(const Buffer::Ptr &buffer, size_t offset, size_t size) {
|
||
_buffer = buffer;
|
||
_data = buffer->data() + offset;
|
||
_size = size;
|
||
}
|
||
|
||
char *data() const override {
|
||
return _data;
|
||
}
|
||
|
||
size_t size() const override{
|
||
return _size;
|
||
}
|
||
|
||
private:
|
||
char *_data;
|
||
size_t _size;
|
||
Buffer::Ptr _buffer;
|
||
};
|
||
|
||
void RtmpProtocol::sendRtmp(uint8_t type, uint32_t stream_index, const std::string &buffer, uint32_t stamp, int chunk_id) {
|
||
sendRtmp(type, stream_index, std::make_shared<BufferString>(buffer), stamp, chunk_id);
|
||
}
|
||
|
||
void RtmpProtocol::sendRtmp(uint8_t type, uint32_t stream_index, const Buffer::Ptr &buf, uint32_t stamp, int chunk_id){
|
||
if (chunk_id < 2 || chunk_id > 63) {
|
||
auto strErr = StrPrinter << "不支持发送该类型的块流 ID:" << chunk_id << endl;
|
||
throw std::runtime_error(strErr);
|
||
}
|
||
// 是否有扩展时间戳 [AUTO-TRANSLATED:85bae69f]
|
||
// Does it have an extended timestamp
|
||
bool ext_stamp = stamp >= 0xFFFFFF;
|
||
|
||
// rtmp头 [AUTO-TRANSLATED:915b278c]
|
||
// RTMP header
|
||
BufferRaw::Ptr buffer_header = obtainBuffer();
|
||
buffer_header->setCapacity(sizeof(RtmpHeader));
|
||
buffer_header->setSize(sizeof(RtmpHeader));
|
||
// 对rtmp头赋值,如果使用整形赋值,在arm android上可能由于数据对齐导致总线错误的问题 [AUTO-TRANSLATED:90c79d70]
|
||
// Assign values to the RTMP header. If using integer assignment, it may cause bus errors on ARM Android due to data alignment issues
|
||
RtmpHeader *header = (RtmpHeader *) buffer_header->data();
|
||
header->fmt = 0;
|
||
header->chunk_id = chunk_id;
|
||
header->type_id = type;
|
||
set_be24(header->time_stamp, ext_stamp ? 0xFFFFFF : stamp);
|
||
set_be24(header->body_size, (uint32_t)buf->size());
|
||
set_le32(header->stream_index, stream_index);
|
||
// 发送rtmp头 [AUTO-TRANSLATED:3c038cd5]
|
||
// Send RTMP header
|
||
onSendRawData(std::move(buffer_header));
|
||
|
||
// 扩展时间戳字段 [AUTO-TRANSLATED:6f79a475]
|
||
// Extended timestamp field
|
||
BufferRaw::Ptr buffer_ext_stamp;
|
||
if (ext_stamp) {
|
||
// 生成扩展时间戳 [AUTO-TRANSLATED:cd22977a]
|
||
// Generate extended timestamp
|
||
buffer_ext_stamp = obtainBuffer();
|
||
buffer_ext_stamp->setCapacity(4);
|
||
buffer_ext_stamp->setSize(4);
|
||
set_be32(buffer_ext_stamp->data(), stamp);
|
||
}
|
||
|
||
// 生成一个字节的flag,标明是什么chunkId [AUTO-TRANSLATED:fbdbf476]
|
||
// Generate a one-byte flag to indicate what chunkId it is
|
||
BufferRaw::Ptr buffer_flags = obtainBuffer();
|
||
buffer_flags->setCapacity(1);
|
||
buffer_flags->setSize(1);
|
||
header = (RtmpHeader *) buffer_flags->data();
|
||
header->fmt = 3;
|
||
header->chunk_id = chunk_id;
|
||
|
||
size_t offset = 0;
|
||
size_t totalSize = sizeof(RtmpHeader);
|
||
while (offset < buf->size()) {
|
||
if (offset) {
|
||
onSendRawData(buffer_flags);
|
||
totalSize += 1;
|
||
}
|
||
if (ext_stamp) {
|
||
// 扩展时间戳 [AUTO-TRANSLATED:f263b9bf]
|
||
// Extended timestamp
|
||
onSendRawData(buffer_ext_stamp);
|
||
totalSize += 4;
|
||
}
|
||
size_t chunk = min(_chunk_size_out, buf->size() - offset);
|
||
onSendRawData(std::make_shared<BufferPartial>(buf, offset, chunk));
|
||
totalSize += chunk;
|
||
offset += chunk;
|
||
}
|
||
_bytes_sent += (uint32_t)totalSize;
|
||
if (_windows_size > 0 && _bytes_sent - _bytes_sent_last >= _windows_size) {
|
||
_bytes_sent_last = _bytes_sent;
|
||
sendAcknowledgement(_bytes_sent);
|
||
}
|
||
}
|
||
|
||
void RtmpProtocol::onParseRtmp(const char *data, size_t size) {
|
||
input(data, size);
|
||
}
|
||
|
||
const char *RtmpProtocol::onSearchPacketTail(const char *data,size_t len){
|
||
// 移动拷贝提高性能 [AUTO-TRANSLATED:1e6fce8d]
|
||
// Move copy to improve performance
|
||
auto next_step_func(std::move(_next_step_func));
|
||
// 执行下一步 [AUTO-TRANSLATED:4199a3e3]
|
||
// Execute the next step
|
||
auto ret = next_step_func(data, len);
|
||
if (!_next_step_func) {
|
||
// 为设置下一步,恢复之 [AUTO-TRANSLATED:b5494ef3]
|
||
// Set the next step, restore it
|
||
next_step_func.swap(_next_step_func);
|
||
}
|
||
return ret;
|
||
}
|
||
|
||
////for client////
|
||
void RtmpProtocol::startClientSession(const function<void()> &func, bool complex) {
|
||
// 发送 C0C1 [AUTO-TRANSLATED:5da06136]
|
||
// Send C0C1
|
||
char handshake_head = HANDSHAKE_PLAINTEXT;
|
||
onSendRawData(obtainBuffer(&handshake_head, 1));
|
||
RtmpHandshake c1(0);
|
||
if (complex) {
|
||
c1.create_complex_c0c1();
|
||
}
|
||
onSendRawData(obtainBuffer((char *) (&c1), sizeof(c1)));
|
||
_next_step_func = [this, func](const char *data, size_t len) {
|
||
// 等待 S0+S1+S2 [AUTO-TRANSLATED:3f95dd6d]
|
||
// Wait for S0+S1+S2
|
||
return handle_S0S1S2(data, len, func);
|
||
};
|
||
}
|
||
|
||
const char* RtmpProtocol::handle_S0S1S2(const char *data, size_t len, const function<void()> &func) {
|
||
if (len < 1 + 2 * C1_HANDSHARK_SIZE) {
|
||
// 数据不够 [AUTO-TRANSLATED:72802244]
|
||
// Not enough data
|
||
return nullptr;
|
||
}
|
||
if (data[0] != HANDSHAKE_PLAINTEXT) {
|
||
throw std::runtime_error("only plaintext[0x03] handshake supported");
|
||
}
|
||
// 发送 C2 [AUTO-TRANSLATED:e51c339e]
|
||
// Send C2
|
||
const char *pcC2 = data + 1;
|
||
onSendRawData(obtainBuffer(pcC2, C1_HANDSHARK_SIZE));
|
||
// 握手结束 [AUTO-TRANSLATED:9df763ff]
|
||
// Handshake finished
|
||
_next_step_func = [this](const char *data, size_t len) {
|
||
// 握手结束并且开始进入解析命令模式 [AUTO-TRANSLATED:3b4e0886]
|
||
// Handshake finished and start entering command parsing mode
|
||
return handle_rtmp(data, len);
|
||
};
|
||
func();
|
||
return data + 1 + 2 * C1_HANDSHARK_SIZE;
|
||
}
|
||
|
||
////for server ////
|
||
const char * RtmpProtocol::handle_C0C1(const char *data, size_t len) {
|
||
if (len < 1 + C1_HANDSHARK_SIZE) {
|
||
//need more data!
|
||
return nullptr;
|
||
}
|
||
if (data[0] != HANDSHAKE_PLAINTEXT) {
|
||
throw std::runtime_error("only plaintext[0x03] handshake supported");
|
||
}
|
||
if (memcmp(data + 5, "\x00\x00\x00\x00", 4) == 0) {
|
||
//simple handsharke
|
||
handle_C1_simple(data);
|
||
} else {
|
||
#ifdef ENABLE_OPENSSL
|
||
//complex handsharke
|
||
handle_C1_complex(data);
|
||
#else
|
||
WarnL << "未打开ENABLE_OPENSSL宏,复杂握手采用简单方式处理,flash播放器可能无法播放!";
|
||
handle_C1_simple(data);
|
||
#endif//ENABLE_OPENSSL
|
||
}
|
||
return data + 1 + C1_HANDSHARK_SIZE;
|
||
}
|
||
|
||
void RtmpProtocol::handle_C1_simple(const char *data){
|
||
// 发送S0 [AUTO-TRANSLATED:79eca118]
|
||
// Send S0
|
||
char handshake_head = HANDSHAKE_PLAINTEXT;
|
||
onSendRawData(obtainBuffer(&handshake_head, 1));
|
||
// 发送S1 [AUTO-TRANSLATED:a86847cf]
|
||
// Send S1
|
||
RtmpHandshake s1(0);
|
||
onSendRawData(obtainBuffer((char *) &s1, C1_HANDSHARK_SIZE));
|
||
// 发送S2 [AUTO-TRANSLATED:dbcf960a]
|
||
// Send S2
|
||
onSendRawData(obtainBuffer(data + 1, C1_HANDSHARK_SIZE));
|
||
// 等待C2 [AUTO-TRANSLATED:b9900831]
|
||
// Wait for C2
|
||
_next_step_func = [this](const char *data, size_t len) {
|
||
// 握手结束并且开始进入解析命令模式 [AUTO-TRANSLATED:3b4e0886]
|
||
// Handshake finished and start entering command parsing mode
|
||
return handle_C2(data, len);
|
||
};
|
||
}
|
||
|
||
#ifdef ENABLE_OPENSSL
|
||
void RtmpProtocol::handle_C1_complex(const char *data){
|
||
// 参考自:http://blog.csdn.net/win_lin/article/details/13006803 [AUTO-TRANSLATED:97a05bd3]
|
||
// Refer to: http://blog.csdn.net/win_lin/article/details/13006803
|
||
//skip c0,time,version
|
||
const char *c1_start = 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 &) {
|
||
// 貌似flash从来都不用schema1 [AUTO-TRANSLATED:2c6d140f]
|
||
// It seems that flash never uses 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 &) {
|
||
// WarnL << "try rtmp complex schema1 failed:" << ex.what();
|
||
handle_C1_simple(data);
|
||
}
|
||
}
|
||
}
|
||
|
||
#if !defined(u_int8_t)
|
||
#define u_int8_t unsigned char
|
||
#endif // !defined(u_int8_t)
|
||
|
||
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 mismatched");
|
||
} 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
|
||
*764bytes digest structure
|
||
offset: 4bytes
|
||
random-data: (offset)bytes
|
||
digest-data: 32bytes
|
||
random-data: (764-4-offset-32)bytes
|
||
|
||
* [AUTO-TRANSLATED:99455c22]
|
||
*/
|
||
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
|
||
*764bytes key structure
|
||
random-data: (offset)bytes
|
||
key-data: 128bytes
|
||
random-data: (764-offset-128-4)bytes
|
||
offset: 4bytes
|
||
|
||
* [AUTO-TRANSLATED:25b8d7c7]
|
||
*/
|
||
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 [AUTO-TRANSLATED:8a372a12]
|
||
// S1S2 calculation refers to: https://github.com/hitYangfei/golang/blob/master/rtmpserver.go
|
||
// 发送S0 [AUTO-TRANSLATED:79eca118]
|
||
// Send S0
|
||
char handshake_head = HANDSHAKE_PLAINTEXT;
|
||
onSendRawData(obtainBuffer(&handshake_head, 1));
|
||
//S1
|
||
RtmpHandshake s1(0);
|
||
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(obtainBuffer((char *) &s1, sizeof(s1)));
|
||
|
||
//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(obtainBuffer((char *) &s2, sizeof(s2)));
|
||
// 等待C2 [AUTO-TRANSLATED:b9900831]
|
||
// Wait for C2
|
||
_next_step_func = [this](const char *data, size_t len) {
|
||
return handle_C2(data, len);
|
||
};
|
||
}
|
||
#endif //ENABLE_OPENSSL
|
||
|
||
// 发送复杂握手c0c1 [AUTO-TRANSLATED:f6f646dc]
|
||
// Send complex handshake c0c1
|
||
void RtmpHandshake::create_complex_c0c1() {
|
||
#ifdef ENABLE_OPENSSL
|
||
memcpy(zero, "\x80\x00\x07\x02", 4);
|
||
// digest随机偏移长度 [AUTO-TRANSLATED:758606f0]
|
||
// Digest random offset length
|
||
auto offset_value = rand() % (C1_SCHEMA_SIZE - C1_OFFSET_SIZE - C1_DIGEST_SIZE);
|
||
// 设置digest偏移长度 [AUTO-TRANSLATED:1a4caf92]
|
||
// Set digest offset length
|
||
auto offset_ptr = random + C1_SCHEMA_SIZE;
|
||
offset_ptr[0] = offset_ptr[1] = offset_ptr[2] = offset_value / 4;
|
||
offset_ptr[3] = offset_value - 3 * (offset_value / 4);
|
||
// 去除digest后的剩余随机数据 [AUTO-TRANSLATED:133cc74e]
|
||
// Remove the remaining random data after digest
|
||
string str((char *) this, sizeof(*this));
|
||
str.erase(8 + C1_SCHEMA_SIZE + C1_OFFSET_SIZE + offset_value, C1_DIGEST_SIZE);
|
||
// 获取摘要 [AUTO-TRANSLATED:2a7aab55]
|
||
// Get digest
|
||
auto digest_value = openssl_HMACsha256(FPKey, C1_FPKEY_SIZE, str.data(), str.size());
|
||
// 插入摘要 [AUTO-TRANSLATED:3f12641b]
|
||
// Insert digest
|
||
memcpy(random + C1_SCHEMA_SIZE + C1_OFFSET_SIZE + offset_value, digest_value.data(), digest_value.size());
|
||
#endif
|
||
}
|
||
|
||
const char* RtmpProtocol::handle_C2(const char *data, size_t len) {
|
||
if (len < C1_HANDSHARK_SIZE) {
|
||
//need more data!
|
||
return nullptr;
|
||
}
|
||
_next_step_func = [this](const char *data, size_t len) {
|
||
return handle_rtmp(data, len);
|
||
};
|
||
|
||
// 握手结束,进入命令模式 [AUTO-TRANSLATED:2b22d604]
|
||
// Handshake ends, enter command mode
|
||
return handle_rtmp(data + C1_HANDSHARK_SIZE, len - C1_HANDSHARK_SIZE);
|
||
}
|
||
|
||
static constexpr size_t HEADER_LENGTH[] = {12, 8, 4, 1};
|
||
|
||
const char* RtmpProtocol::handle_rtmp(const char *data, size_t len) {
|
||
auto ptr = data;
|
||
while (len) {
|
||
size_t offset = 0;
|
||
auto header = (RtmpHeader *) ptr;
|
||
auto header_len = HEADER_LENGTH[header->fmt];
|
||
_now_chunk_id = header->chunk_id;
|
||
switch (_now_chunk_id) {
|
||
case 0: {
|
||
// 0 值表示二字节形式,并且 ID 范围 64 - 319 [AUTO-TRANSLATED:46207a57]
|
||
// 0 value represents two-byte form, and ID range is 64 - 319
|
||
// (第二个字节 + 64)。 [AUTO-TRANSLATED:ab640c90]
|
||
// (second byte + 64).
|
||
if (len < 2) {
|
||
//need more data
|
||
return ptr;
|
||
}
|
||
_now_chunk_id = 64 + (uint8_t) (ptr[1]);
|
||
offset = 1;
|
||
break;
|
||
}
|
||
|
||
case 1: {
|
||
// 1 值表示三字节形式,并且 ID 范围为 64 - 65599 [AUTO-TRANSLATED:e7f24d54]
|
||
// 1 value represents three-byte form, and ID range is 64 - 65599
|
||
// ((第三个字节) * 256 + 第二个字节 + 64)。 [AUTO-TRANSLATED:6e631032]
|
||
// ((third byte) * 256 + second byte + 64).
|
||
if (len < 3) {
|
||
//need more data
|
||
return ptr;
|
||
}
|
||
_now_chunk_id = 64 + ((uint8_t) (ptr[2]) << 8) + (uint8_t) (ptr[1]);
|
||
offset = 2;
|
||
break;
|
||
}
|
||
|
||
// 带有 2 值的块流 ID 被保留,用于下层协议控制消息和命令。 [AUTO-TRANSLATED:b0987fdb]
|
||
// Block stream IDs with a value of 2 are reserved for lower-level protocol control messages and commands.
|
||
default : break;
|
||
}
|
||
|
||
if (len < header_len + offset) {
|
||
//need more data
|
||
return ptr;
|
||
}
|
||
header = (RtmpHeader *) (ptr + offset);
|
||
auto &pr = _map_chunk_data[_now_chunk_id];
|
||
auto &now_packet = pr.first;
|
||
auto &last_packet = pr.second;
|
||
if (!now_packet) {
|
||
now_packet = RtmpPacket::create();
|
||
if (last_packet) {
|
||
// 恢复chunk上下文 [AUTO-TRANSLATED:84bf0621]
|
||
// Restore chunk context
|
||
*now_packet = *last_packet;
|
||
}
|
||
// 绝对时间戳标记复位 [AUTO-TRANSLATED:a0d50bad]
|
||
// Absolute timestamp marker reset
|
||
now_packet->is_abs_stamp = false;
|
||
}
|
||
auto &chunk_data = *now_packet;
|
||
chunk_data.chunk_id = _now_chunk_id;
|
||
switch (header_len) {
|
||
case 12:
|
||
chunk_data.is_abs_stamp = true;
|
||
chunk_data.stream_index = load_le32(header->stream_index);
|
||
case 8:
|
||
chunk_data.body_size = load_be24(header->body_size);
|
||
chunk_data.type_id = header->type_id;
|
||
case 4:
|
||
chunk_data.ts_field = load_be24(header->time_stamp);
|
||
}
|
||
|
||
auto time_stamp = chunk_data.ts_field;
|
||
if (chunk_data.ts_field == 0xFFFFFF) {
|
||
if (len < header_len + offset + 4) {
|
||
//need more data
|
||
return ptr;
|
||
}
|
||
time_stamp = load_be32(ptr + offset + header_len);
|
||
offset += 4;
|
||
}
|
||
|
||
if (chunk_data.body_size < chunk_data.buffer.size()) {
|
||
throw std::runtime_error("非法的bodySize");
|
||
}
|
||
|
||
auto more = min(_chunk_size_in, (size_t) (chunk_data.body_size - chunk_data.buffer.size()));
|
||
if (len < header_len + offset + more) {
|
||
//need more data
|
||
return ptr;
|
||
}
|
||
if (more) {
|
||
chunk_data.buffer.append(ptr + header_len + offset, more);
|
||
}
|
||
ptr += header_len + offset + more;
|
||
len -= header_len + offset + more;
|
||
if (chunk_data.buffer.size() == chunk_data.body_size) {
|
||
//frame is ready
|
||
_now_stream_index = chunk_data.stream_index;
|
||
chunk_data.time_stamp = time_stamp + (chunk_data.is_abs_stamp ? 0 : chunk_data.time_stamp);
|
||
// 保存chunk上下文 [AUTO-TRANSLATED:4ed4fbb0]
|
||
// Save chunk context
|
||
last_packet = now_packet;
|
||
if (chunk_data.body_size) {
|
||
handle_chunk(std::move(now_packet));
|
||
} else {
|
||
now_packet = nullptr;
|
||
}
|
||
}
|
||
}
|
||
return ptr;
|
||
}
|
||
|
||
void RtmpProtocol::handle_chunk(RtmpPacket::Ptr packet) {
|
||
auto &chunk_data = *packet;
|
||
switch (chunk_data.type_id) {
|
||
case MSG_ACK: {
|
||
if (chunk_data.buffer.size() < 4) {
|
||
throw std::runtime_error("MSG_ACK: Not enough data");
|
||
}
|
||
//auto bytePeerRecv = load_be32(&chunk_data.buffer[0]);
|
||
//TraceL << "MSG_ACK:" << bytePeerRecv;
|
||
break;
|
||
}
|
||
|
||
case MSG_SET_CHUNK: {
|
||
if (chunk_data.buffer.size() < 4) {
|
||
throw std::runtime_error("MSG_SET_CHUNK :Not enough data");
|
||
}
|
||
_chunk_size_in = load_be32(&chunk_data.buffer[0]);
|
||
TraceL << "MSG_SET_CHUNK:" << _chunk_size_in;
|
||
break;
|
||
}
|
||
|
||
case MSG_USER_CONTROL: {
|
||
//user control message
|
||
if (chunk_data.buffer.size() < 2) {
|
||
throw std::runtime_error("MSG_USER_CONTROL: Not enough data.");
|
||
}
|
||
uint16_t event_type = load_be16(&chunk_data.buffer[0]);
|
||
chunk_data.buffer.erase(0, 2);
|
||
switch (event_type) {
|
||
case CONTROL_PING_REQUEST: {
|
||
if (chunk_data.buffer.size() < 4) {
|
||
throw std::runtime_error("CONTROL_PING_REQUEST: Not enough data.");
|
||
}
|
||
uint32_t timeStamp = load_be32(&chunk_data.buffer[0]);
|
||
//TraceL << "CONTROL_PING_REQUEST:" << time_stamp;
|
||
sendUserControl(CONTROL_PING_RESPONSE, timeStamp);
|
||
break;
|
||
}
|
||
|
||
case CONTROL_PING_RESPONSE: {
|
||
if (chunk_data.buffer.size() < 4) {
|
||
throw std::runtime_error("CONTROL_PING_RESPONSE: Not enough data.");
|
||
}
|
||
//uint32_t time_stamp = load_be32(&chunk_data.buffer[0]);
|
||
//TraceL << "CONTROL_PING_RESPONSE:" << time_stamp;
|
||
break;
|
||
}
|
||
|
||
case CONTROL_STREAM_BEGIN: {
|
||
// 开始播放 [AUTO-TRANSLATED:1d82b293]
|
||
// Start playback
|
||
if (chunk_data.buffer.size() < 4) {
|
||
WarnL << "CONTROL_STREAM_BEGIN: Not enough data:" << chunk_data.buffer.size();
|
||
break;
|
||
}
|
||
uint32_t stream_index = load_be32(&chunk_data.buffer[0]);
|
||
onStreamBegin(stream_index);
|
||
TraceL << "CONTROL_STREAM_BEGIN:" << stream_index;
|
||
break;
|
||
}
|
||
|
||
case CONTROL_STREAM_EOF: {
|
||
// 暂停 [AUTO-TRANSLATED:73a3f145]
|
||
// Pause
|
||
if (chunk_data.buffer.size() < 4) {
|
||
throw std::runtime_error("CONTROL_STREAM_EOF: Not enough data.");
|
||
}
|
||
uint32_t stream_index = load_be32(&chunk_data.buffer[0]);
|
||
onStreamEof(stream_index);
|
||
TraceL << "CONTROL_STREAM_EOF:" << stream_index;
|
||
break;
|
||
}
|
||
|
||
case CONTROL_STREAM_DRY: {
|
||
// 停止播放 [AUTO-TRANSLATED:1292c1b5]
|
||
// Stop playback
|
||
if (chunk_data.buffer.size() < 4) {
|
||
throw std::runtime_error("CONTROL_STREAM_DRY: Not enough data.");
|
||
}
|
||
uint32_t stream_index = load_be32(&chunk_data.buffer[0]);
|
||
onStreamDry(stream_index);
|
||
TraceL << "CONTROL_STREAM_DRY:" << stream_index;
|
||
break;
|
||
}
|
||
|
||
default: /*WarnL << "unhandled user control:" << event_type; */ break;
|
||
}
|
||
break;
|
||
}
|
||
|
||
case MSG_WIN_SIZE: {
|
||
// 如果窗口太小,会导致发送sendAcknowledgement时无限递归:https://github.com/ZLMediaKit/ZLMediaKit/issues/1839 [AUTO-TRANSLATED:05267962]
|
||
// If the window is too small, it will cause infinite recursion when sending sendAcknowledgement: https://github.com/ZLMediaKit/ZLMediaKit/issues/1839
|
||
// 窗口太大,也可能导致fms服务器认为播放器心跳超时 [AUTO-TRANSLATED:30147e88]
|
||
// If the window is too large, it may also cause the fms server to consider the player heartbeat timeout
|
||
_windows_size = min(max(load_be32(&chunk_data.buffer[0]), 32 * 1024U), 1280 * 1024U);
|
||
TraceL << "MSG_WIN_SIZE:" << _windows_size;
|
||
break;
|
||
}
|
||
|
||
case MSG_SET_PEER_BW: {
|
||
_bandwidth = load_be32(&chunk_data.buffer[0]);
|
||
_band_limit_type = chunk_data.buffer[4];
|
||
TraceL << "MSG_SET_PEER_BW:" << _bandwidth << " " << (int)_band_limit_type;
|
||
break;
|
||
}
|
||
|
||
case MSG_AGGREGATE: {
|
||
auto ptr = (uint8_t *) chunk_data.buffer.data();
|
||
auto ptr_tail = ptr + chunk_data.buffer.size();
|
||
uint32_t latest_ts, timestamp;
|
||
timestamp = chunk_data.time_stamp;
|
||
bool first_message = true;
|
||
while (ptr + 8 + 3 < ptr_tail) {
|
||
auto type = *ptr;
|
||
ptr += 1;
|
||
auto size = load_be24(ptr);
|
||
ptr += 3;
|
||
auto ts = load_be24(ptr);
|
||
ptr += 3;
|
||
ts |= (*ptr << 24);
|
||
ptr += 1;
|
||
ptr += 3;
|
||
// 参考FFmpeg多拷贝了4个字节 [AUTO-TRANSLATED:d8aae4ae]
|
||
// Reference FFmpeg copied 4 more bytes
|
||
size += 4;
|
||
if (ptr + size > ptr_tail) {
|
||
break;
|
||
}
|
||
if (!first_message) {
|
||
timestamp += ts - latest_ts;
|
||
}
|
||
first_message = false;
|
||
latest_ts = ts;
|
||
auto sub_packet_ptr = RtmpPacket::create();
|
||
auto &sub_packet = *sub_packet_ptr;
|
||
sub_packet.buffer.assign((char *)ptr, size);
|
||
sub_packet.type_id = type;
|
||
sub_packet.body_size = size;
|
||
sub_packet.time_stamp = timestamp;
|
||
sub_packet.stream_index = chunk_data.stream_index;
|
||
sub_packet.chunk_id = chunk_data.chunk_id;
|
||
handle_chunk(std::move(sub_packet_ptr));
|
||
ptr += size;
|
||
}
|
||
break;
|
||
}
|
||
|
||
default: {
|
||
_bytes_recv += packet->size();
|
||
if (_windows_size > 0 && _bytes_recv - _bytes_recv_last >= _windows_size) {
|
||
_bytes_recv_last = _bytes_recv;
|
||
sendAcknowledgement(_bytes_recv);
|
||
}
|
||
onRtmpChunk(std::move(packet));
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
BufferRaw::Ptr RtmpProtocol::obtainBuffer(const void *data, size_t len) {
|
||
auto buffer = _packet_pool.obtain2();
|
||
if (data && len) {
|
||
buffer->assign((const char *) data, len);
|
||
}
|
||
return buffer;
|
||
}
|
||
|
||
} /* namespace mediakit */
|