4678 lines
140 KiB
C++
4678 lines
140 KiB
C++
//
|
|
// Copyright (c) 2013-2025 The SRS Authors
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
//
|
|
#include <srs_utest_app6.hpp>
|
|
|
|
using namespace std;
|
|
|
|
#include <srs_app_config.hpp>
|
|
#include <srs_app_rtc_conn.hpp>
|
|
#include <srs_app_rtc_dtls.hpp>
|
|
#include <srs_app_rtc_network.hpp>
|
|
#include <srs_app_rtmp_source.hpp>
|
|
#include <srs_app_srt_source.hpp>
|
|
#include <srs_kernel_error.hpp>
|
|
#include <srs_protocol_sdp.hpp>
|
|
#include <srs_utest_app2.hpp>
|
|
|
|
// Mock DTLS implementation
|
|
MockDtls::MockDtls()
|
|
{
|
|
initialize_error_ = srs_success;
|
|
start_active_handshake_error_ = srs_success;
|
|
on_dtls_error_ = srs_success;
|
|
get_srtp_key_error_ = srs_success;
|
|
|
|
reset();
|
|
}
|
|
|
|
MockDtls::~MockDtls()
|
|
{
|
|
srs_freep(initialize_error_);
|
|
srs_freep(start_active_handshake_error_);
|
|
srs_freep(on_dtls_error_);
|
|
srs_freep(get_srtp_key_error_);
|
|
}
|
|
|
|
void MockDtls::reset()
|
|
{
|
|
srs_freep(initialize_error_);
|
|
srs_freep(start_active_handshake_error_);
|
|
srs_freep(on_dtls_error_);
|
|
srs_freep(get_srtp_key_error_);
|
|
|
|
initialize_error_ = srs_success;
|
|
start_active_handshake_error_ = srs_success;
|
|
on_dtls_error_ = srs_success;
|
|
get_srtp_key_error_ = srs_success;
|
|
|
|
last_role_ = "";
|
|
last_version_ = "";
|
|
recv_key_ = "";
|
|
send_key_ = "";
|
|
|
|
initialize_count_ = 0;
|
|
start_active_handshake_count_ = 0;
|
|
on_dtls_count_ = 0;
|
|
get_srtp_key_count_ = 0;
|
|
}
|
|
|
|
srs_error_t MockDtls::initialize(std::string role, std::string version)
|
|
{
|
|
initialize_count_++;
|
|
last_role_ = role;
|
|
last_version_ = version;
|
|
return srs_error_copy(initialize_error_);
|
|
}
|
|
|
|
srs_error_t MockDtls::start_active_handshake()
|
|
{
|
|
start_active_handshake_count_++;
|
|
return srs_error_copy(start_active_handshake_error_);
|
|
}
|
|
|
|
srs_error_t MockDtls::on_dtls(char *data, int nb_data)
|
|
{
|
|
on_dtls_count_++;
|
|
return srs_error_copy(on_dtls_error_);
|
|
}
|
|
|
|
srs_error_t MockDtls::get_srtp_key(std::string &recv_key, std::string &send_key)
|
|
{
|
|
get_srtp_key_count_++;
|
|
if (get_srtp_key_error_ != srs_success) {
|
|
return srs_error_copy(get_srtp_key_error_);
|
|
}
|
|
recv_key = recv_key_;
|
|
send_key = send_key_;
|
|
return srs_success;
|
|
}
|
|
|
|
void MockDtls::set_initialize_error(srs_error_t err)
|
|
{
|
|
srs_freep(initialize_error_);
|
|
initialize_error_ = srs_error_copy(err);
|
|
}
|
|
|
|
void MockDtls::set_start_active_handshake_error(srs_error_t err)
|
|
{
|
|
srs_freep(start_active_handshake_error_);
|
|
start_active_handshake_error_ = srs_error_copy(err);
|
|
}
|
|
|
|
void MockDtls::set_on_dtls_error(srs_error_t err)
|
|
{
|
|
srs_freep(on_dtls_error_);
|
|
on_dtls_error_ = srs_error_copy(err);
|
|
}
|
|
|
|
void MockDtls::set_get_srtp_key_error(srs_error_t err)
|
|
{
|
|
srs_freep(get_srtp_key_error_);
|
|
get_srtp_key_error_ = srs_error_copy(err);
|
|
}
|
|
|
|
void MockDtls::set_srtp_keys(const std::string &recv_key, const std::string &send_key)
|
|
{
|
|
recv_key_ = recv_key;
|
|
send_key_ = send_key;
|
|
}
|
|
|
|
// Mock RTC Network implementation
|
|
MockRtcNetwork::MockRtcNetwork()
|
|
{
|
|
on_dtls_handshake_done_error_ = srs_success;
|
|
on_dtls_alert_error_ = srs_success;
|
|
protect_rtp_error_ = srs_success;
|
|
protect_rtcp_error_ = srs_success;
|
|
write_error_ = srs_success;
|
|
|
|
reset();
|
|
}
|
|
|
|
MockRtcNetwork::~MockRtcNetwork()
|
|
{
|
|
srs_freep(on_dtls_handshake_done_error_);
|
|
srs_freep(on_dtls_alert_error_);
|
|
srs_freep(protect_rtp_error_);
|
|
srs_freep(protect_rtcp_error_);
|
|
srs_freep(write_error_);
|
|
}
|
|
|
|
void MockRtcNetwork::reset()
|
|
{
|
|
srs_freep(on_dtls_handshake_done_error_);
|
|
srs_freep(on_dtls_alert_error_);
|
|
srs_freep(protect_rtp_error_);
|
|
srs_freep(protect_rtcp_error_);
|
|
srs_freep(write_error_);
|
|
|
|
on_dtls_handshake_done_error_ = srs_success;
|
|
on_dtls_alert_error_ = srs_success;
|
|
protect_rtp_error_ = srs_success;
|
|
protect_rtcp_error_ = srs_success;
|
|
write_error_ = srs_success;
|
|
|
|
last_alert_type_ = "";
|
|
last_alert_desc_ = "";
|
|
|
|
on_dtls_handshake_done_count_ = 0;
|
|
on_dtls_alert_count_ = 0;
|
|
protect_rtp_count_ = 0;
|
|
protect_rtcp_count_ = 0;
|
|
write_count_ = 0;
|
|
is_established_ = true;
|
|
}
|
|
|
|
srs_error_t MockRtcNetwork::initialize(SrsSessionConfig *cfg, bool dtls, bool srtp)
|
|
{
|
|
return srs_success;
|
|
}
|
|
|
|
void MockRtcNetwork::set_state(SrsRtcNetworkState state)
|
|
{
|
|
}
|
|
|
|
srs_error_t MockRtcNetwork::on_dtls_handshake_done()
|
|
{
|
|
on_dtls_handshake_done_count_++;
|
|
return srs_error_copy(on_dtls_handshake_done_error_);
|
|
}
|
|
|
|
srs_error_t MockRtcNetwork::on_dtls_alert(std::string type, std::string desc)
|
|
{
|
|
on_dtls_alert_count_++;
|
|
last_alert_type_ = type;
|
|
last_alert_desc_ = desc;
|
|
return srs_error_copy(on_dtls_alert_error_);
|
|
}
|
|
|
|
srs_error_t MockRtcNetwork::on_dtls(char *data, int nb_data)
|
|
{
|
|
return srs_success;
|
|
}
|
|
|
|
srs_error_t MockRtcNetwork::protect_rtp(void *packet, int *nb_cipher)
|
|
{
|
|
protect_rtp_count_++;
|
|
return srs_error_copy(protect_rtp_error_);
|
|
}
|
|
|
|
srs_error_t MockRtcNetwork::protect_rtcp(void *packet, int *nb_cipher)
|
|
{
|
|
protect_rtcp_count_++;
|
|
return srs_error_copy(protect_rtcp_error_);
|
|
}
|
|
|
|
srs_error_t MockRtcNetwork::on_stun(SrsStunPacket *r, char *data, int nb_data)
|
|
{
|
|
return srs_success;
|
|
}
|
|
|
|
srs_error_t MockRtcNetwork::on_rtp(char *data, int nb_data)
|
|
{
|
|
return srs_success;
|
|
}
|
|
|
|
srs_error_t MockRtcNetwork::on_rtcp(char *data, int nb_data)
|
|
{
|
|
return srs_success;
|
|
}
|
|
|
|
bool MockRtcNetwork::is_establelished()
|
|
{
|
|
return is_established_;
|
|
}
|
|
|
|
srs_error_t MockRtcNetwork::write(void *buf, size_t size, ssize_t *nwrite)
|
|
{
|
|
write_count_++;
|
|
if (nwrite) {
|
|
*nwrite = (ssize_t)size;
|
|
}
|
|
return srs_error_copy(write_error_);
|
|
}
|
|
|
|
void MockRtcNetwork::set_on_dtls_handshake_done_error(srs_error_t err)
|
|
{
|
|
srs_freep(on_dtls_handshake_done_error_);
|
|
on_dtls_handshake_done_error_ = srs_error_copy(err);
|
|
}
|
|
|
|
void MockRtcNetwork::set_on_dtls_alert_error(srs_error_t err)
|
|
{
|
|
srs_freep(on_dtls_alert_error_);
|
|
on_dtls_alert_error_ = srs_error_copy(err);
|
|
}
|
|
|
|
void MockRtcNetwork::set_protect_rtp_error(srs_error_t err)
|
|
{
|
|
srs_freep(protect_rtp_error_);
|
|
protect_rtp_error_ = srs_error_copy(err);
|
|
}
|
|
|
|
void MockRtcNetwork::set_protect_rtcp_error(srs_error_t err)
|
|
{
|
|
srs_freep(protect_rtcp_error_);
|
|
protect_rtcp_error_ = srs_error_copy(err);
|
|
}
|
|
|
|
void MockRtcNetwork::set_write_error(srs_error_t err)
|
|
{
|
|
srs_freep(write_error_);
|
|
write_error_ = srs_error_copy(err);
|
|
}
|
|
|
|
void MockRtcNetwork::set_established(bool established)
|
|
{
|
|
is_established_ = established;
|
|
}
|
|
|
|
// Mock SRTP implementation
|
|
MockSrtp::MockSrtp()
|
|
{
|
|
initialize_error_ = srs_success;
|
|
protect_rtp_error_ = srs_success;
|
|
protect_rtcp_error_ = srs_success;
|
|
unprotect_rtp_error_ = srs_success;
|
|
unprotect_rtcp_error_ = srs_success;
|
|
|
|
reset();
|
|
}
|
|
|
|
MockSrtp::~MockSrtp()
|
|
{
|
|
srs_freep(initialize_error_);
|
|
srs_freep(protect_rtp_error_);
|
|
srs_freep(protect_rtcp_error_);
|
|
srs_freep(unprotect_rtp_error_);
|
|
srs_freep(unprotect_rtcp_error_);
|
|
}
|
|
|
|
void MockSrtp::reset()
|
|
{
|
|
srs_freep(initialize_error_);
|
|
srs_freep(protect_rtp_error_);
|
|
srs_freep(protect_rtcp_error_);
|
|
srs_freep(unprotect_rtp_error_);
|
|
srs_freep(unprotect_rtcp_error_);
|
|
|
|
initialize_error_ = srs_success;
|
|
protect_rtp_error_ = srs_success;
|
|
protect_rtcp_error_ = srs_success;
|
|
unprotect_rtp_error_ = srs_success;
|
|
unprotect_rtcp_error_ = srs_success;
|
|
|
|
last_recv_key_ = "";
|
|
last_send_key_ = "";
|
|
|
|
initialize_count_ = 0;
|
|
protect_rtp_count_ = 0;
|
|
protect_rtcp_count_ = 0;
|
|
unprotect_rtp_count_ = 0;
|
|
unprotect_rtcp_count_ = 0;
|
|
}
|
|
|
|
srs_error_t MockSrtp::initialize(std::string recv_key, std::string send_key)
|
|
{
|
|
initialize_count_++;
|
|
last_recv_key_ = recv_key;
|
|
last_send_key_ = send_key;
|
|
return srs_error_copy(initialize_error_);
|
|
}
|
|
|
|
srs_error_t MockSrtp::protect_rtp(void *packet, int *nb_cipher)
|
|
{
|
|
protect_rtp_count_++;
|
|
return srs_error_copy(protect_rtp_error_);
|
|
}
|
|
|
|
srs_error_t MockSrtp::protect_rtcp(void *packet, int *nb_cipher)
|
|
{
|
|
protect_rtcp_count_++;
|
|
return srs_error_copy(protect_rtcp_error_);
|
|
}
|
|
|
|
srs_error_t MockSrtp::unprotect_rtp(void *packet, int *nb_plaintext)
|
|
{
|
|
unprotect_rtp_count_++;
|
|
return srs_error_copy(unprotect_rtp_error_);
|
|
}
|
|
|
|
srs_error_t MockSrtp::unprotect_rtcp(void *packet, int *nb_plaintext)
|
|
{
|
|
unprotect_rtcp_count_++;
|
|
return srs_error_copy(unprotect_rtcp_error_);
|
|
}
|
|
|
|
void MockSrtp::set_initialize_error(srs_error_t err)
|
|
{
|
|
srs_freep(initialize_error_);
|
|
initialize_error_ = srs_error_copy(err);
|
|
}
|
|
|
|
void MockSrtp::set_protect_rtp_error(srs_error_t err)
|
|
{
|
|
srs_freep(protect_rtp_error_);
|
|
protect_rtp_error_ = srs_error_copy(err);
|
|
}
|
|
|
|
void MockSrtp::set_protect_rtcp_error(srs_error_t err)
|
|
{
|
|
srs_freep(protect_rtcp_error_);
|
|
protect_rtcp_error_ = srs_error_copy(err);
|
|
}
|
|
|
|
void MockSrtp::set_unprotect_rtp_error(srs_error_t err)
|
|
{
|
|
srs_freep(unprotect_rtp_error_);
|
|
unprotect_rtp_error_ = srs_error_copy(err);
|
|
}
|
|
|
|
void MockSrtp::set_unprotect_rtcp_error(srs_error_t err)
|
|
{
|
|
srs_freep(unprotect_rtcp_error_);
|
|
unprotect_rtcp_error_ = srs_error_copy(err);
|
|
}
|
|
|
|
// Custom SrsSecurityTransport for testing that allows injecting mock DTLS and SRTP
|
|
class TestableSecurityTransport : public SrsSecurityTransport
|
|
{
|
|
public:
|
|
TestableSecurityTransport(ISrsRtcNetwork *s, MockDtls *mock_dtls, MockSrtp *mock_srtp = NULL) : SrsSecurityTransport(s)
|
|
{
|
|
// Replace the real DTLS with our mock
|
|
srs_freep(dtls_);
|
|
dtls_ = mock_dtls;
|
|
|
|
// Replace the real SRTP with our mock if provided
|
|
if (mock_srtp) {
|
|
srs_freep(srtp_);
|
|
srtp_ = mock_srtp;
|
|
}
|
|
}
|
|
|
|
virtual ~TestableSecurityTransport()
|
|
{
|
|
// Don't free the mock DTLS and SRTP, they're managed by the test
|
|
dtls_ = NULL;
|
|
srtp_ = NULL;
|
|
}
|
|
};
|
|
|
|
VOID TEST(SecurityTransportTest, InitializeSuccess)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls));
|
|
|
|
SrsUniquePtr<SrsSessionConfig> cfg(new SrsSessionConfig());
|
|
cfg->dtls_role_ = "active";
|
|
cfg->dtls_version_ = "1.2";
|
|
|
|
HELPER_EXPECT_SUCCESS(transport->initialize(cfg.get()));
|
|
|
|
// Verify DTLS initialize was called with correct parameters
|
|
EXPECT_EQ(1, mock_dtls.initialize_count_);
|
|
EXPECT_STREQ("active", mock_dtls.last_role_.c_str());
|
|
EXPECT_STREQ("1.2", mock_dtls.last_version_.c_str());
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, InitializeFailure)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls));
|
|
|
|
// Set up mock to return error
|
|
srs_error_t mock_error = srs_error_new(ERROR_RTC_DTLS, "mock dtls initialize error");
|
|
mock_dtls.set_initialize_error(mock_error);
|
|
srs_freep(mock_error);
|
|
|
|
SrsUniquePtr<SrsSessionConfig> cfg(new SrsSessionConfig());
|
|
cfg->dtls_role_ = "passive";
|
|
cfg->dtls_version_ = "auto";
|
|
|
|
HELPER_EXPECT_FAILED(transport->initialize(cfg.get()));
|
|
|
|
// Verify DTLS initialize was called with correct parameters
|
|
EXPECT_EQ(1, mock_dtls.initialize_count_);
|
|
EXPECT_STREQ("passive", mock_dtls.last_role_.c_str());
|
|
EXPECT_STREQ("auto", mock_dtls.last_version_.c_str());
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, StartActiveHandshakeSuccess)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls));
|
|
|
|
HELPER_EXPECT_SUCCESS(transport->start_active_handshake());
|
|
|
|
// Verify DTLS start_active_handshake was called
|
|
EXPECT_EQ(1, mock_dtls.start_active_handshake_count_);
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, StartActiveHandshakeFailure)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls));
|
|
|
|
// Set up mock to return error
|
|
srs_error_t mock_error = srs_error_new(ERROR_RTC_DTLS, "mock dtls handshake error");
|
|
mock_dtls.set_start_active_handshake_error(mock_error);
|
|
srs_freep(mock_error);
|
|
|
|
HELPER_EXPECT_FAILED(transport->start_active_handshake());
|
|
|
|
// Verify DTLS start_active_handshake was called
|
|
EXPECT_EQ(1, mock_dtls.start_active_handshake_count_);
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, InitializeWithDifferentRoles)
|
|
{
|
|
srs_error_t err;
|
|
|
|
// Test with active role
|
|
if (true) {
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls));
|
|
|
|
SrsUniquePtr<SrsSessionConfig> cfg(new SrsSessionConfig());
|
|
cfg->dtls_role_ = "active";
|
|
cfg->dtls_version_ = "1.2";
|
|
|
|
HELPER_EXPECT_SUCCESS(transport->initialize(cfg.get()));
|
|
EXPECT_STREQ("active", mock_dtls.last_role_.c_str());
|
|
EXPECT_STREQ("1.2", mock_dtls.last_version_.c_str());
|
|
}
|
|
|
|
// Test with passive role
|
|
if (true) {
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls));
|
|
|
|
SrsUniquePtr<SrsSessionConfig> cfg(new SrsSessionConfig());
|
|
cfg->dtls_role_ = "passive";
|
|
cfg->dtls_version_ = "auto";
|
|
|
|
HELPER_EXPECT_SUCCESS(transport->initialize(cfg.get()));
|
|
EXPECT_STREQ("passive", mock_dtls.last_role_.c_str());
|
|
EXPECT_STREQ("auto", mock_dtls.last_version_.c_str());
|
|
}
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, InitializeWithEmptyRoleAndVersion)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls));
|
|
|
|
SrsUniquePtr<SrsSessionConfig> cfg(new SrsSessionConfig());
|
|
cfg->dtls_role_ = "";
|
|
cfg->dtls_version_ = "";
|
|
|
|
HELPER_EXPECT_SUCCESS(transport->initialize(cfg.get()));
|
|
|
|
// Verify DTLS initialize was called with empty parameters
|
|
EXPECT_EQ(1, mock_dtls.initialize_count_);
|
|
EXPECT_STREQ("", mock_dtls.last_role_.c_str());
|
|
EXPECT_STREQ("", mock_dtls.last_version_.c_str());
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, MultipleInitializeCalls)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls));
|
|
|
|
SrsUniquePtr<SrsSessionConfig> cfg(new SrsSessionConfig());
|
|
cfg->dtls_role_ = "active";
|
|
cfg->dtls_version_ = "1.2";
|
|
|
|
// First initialize call
|
|
HELPER_EXPECT_SUCCESS(transport->initialize(cfg.get()));
|
|
EXPECT_EQ(1, mock_dtls.initialize_count_);
|
|
|
|
// Second initialize call
|
|
cfg->dtls_role_ = "passive";
|
|
cfg->dtls_version_ = "auto";
|
|
HELPER_EXPECT_SUCCESS(transport->initialize(cfg.get()));
|
|
EXPECT_EQ(2, mock_dtls.initialize_count_);
|
|
EXPECT_STREQ("passive", mock_dtls.last_role_.c_str());
|
|
EXPECT_STREQ("auto", mock_dtls.last_version_.c_str());
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, MultipleHandshakeCalls)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls));
|
|
|
|
// First handshake call
|
|
HELPER_EXPECT_SUCCESS(transport->start_active_handshake());
|
|
EXPECT_EQ(1, mock_dtls.start_active_handshake_count_);
|
|
|
|
// Second handshake call
|
|
HELPER_EXPECT_SUCCESS(transport->start_active_handshake());
|
|
EXPECT_EQ(2, mock_dtls.start_active_handshake_count_);
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, InitializeAndHandshakeSequence)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls));
|
|
|
|
// Initialize first
|
|
SrsUniquePtr<SrsSessionConfig> cfg(new SrsSessionConfig());
|
|
cfg->dtls_role_ = "active";
|
|
cfg->dtls_version_ = "1.2";
|
|
|
|
HELPER_EXPECT_SUCCESS(transport->initialize(cfg.get()));
|
|
EXPECT_EQ(1, mock_dtls.initialize_count_);
|
|
EXPECT_EQ(0, mock_dtls.start_active_handshake_count_);
|
|
|
|
// Then start handshake
|
|
HELPER_EXPECT_SUCCESS(transport->start_active_handshake());
|
|
EXPECT_EQ(1, mock_dtls.initialize_count_);
|
|
EXPECT_EQ(1, mock_dtls.start_active_handshake_count_);
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, HandshakeWithoutInitialize)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls));
|
|
|
|
// Try handshake without initialize - should still work as DTLS handles it
|
|
HELPER_EXPECT_SUCCESS(transport->start_active_handshake());
|
|
EXPECT_EQ(0, mock_dtls.initialize_count_);
|
|
EXPECT_EQ(1, mock_dtls.start_active_handshake_count_);
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, WriteDtlsDataSuccess)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls));
|
|
|
|
char test_data[] = "test dtls data";
|
|
int data_size = strlen(test_data);
|
|
|
|
HELPER_EXPECT_SUCCESS(transport->write_dtls_data(test_data, data_size));
|
|
|
|
// Verify network write was called
|
|
EXPECT_EQ(1, mock_network.write_count_);
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, WriteDtlsDataZeroSize)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls));
|
|
|
|
char test_data[] = "test dtls data";
|
|
|
|
// Write with zero size should return success without calling network write
|
|
HELPER_EXPECT_SUCCESS(transport->write_dtls_data(test_data, 0));
|
|
|
|
// Verify network write was NOT called
|
|
EXPECT_EQ(0, mock_network.write_count_);
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, WriteDtlsDataNetworkError)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls));
|
|
|
|
// Set up mock to return error
|
|
srs_error_t mock_error = srs_error_new(ERROR_SOCKET_WRITE, "mock network write error");
|
|
mock_network.set_write_error(mock_error);
|
|
srs_freep(mock_error);
|
|
|
|
char test_data[] = "test dtls data";
|
|
int data_size = strlen(test_data);
|
|
|
|
HELPER_EXPECT_FAILED(transport->write_dtls_data(test_data, data_size));
|
|
|
|
// Verify network write was called
|
|
EXPECT_EQ(1, mock_network.write_count_);
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, OnDtlsSuccess)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls));
|
|
|
|
char test_data[] = "dtls handshake data";
|
|
int data_size = strlen(test_data);
|
|
|
|
HELPER_EXPECT_SUCCESS(transport->on_dtls(test_data, data_size));
|
|
|
|
// Verify DTLS on_dtls was called
|
|
EXPECT_EQ(1, mock_dtls.on_dtls_count_);
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, OnDtlsError)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls));
|
|
|
|
// Set up mock to return error
|
|
srs_error_t mock_error = srs_error_new(ERROR_RTC_DTLS, "mock dtls processing error");
|
|
mock_dtls.set_on_dtls_error(mock_error);
|
|
srs_freep(mock_error);
|
|
|
|
char test_data[] = "dtls handshake data";
|
|
int data_size = strlen(test_data);
|
|
|
|
HELPER_EXPECT_FAILED(transport->on_dtls(test_data, data_size));
|
|
|
|
// Verify DTLS on_dtls was called
|
|
EXPECT_EQ(1, mock_dtls.on_dtls_count_);
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, OnDtlsAlertSuccess)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls));
|
|
|
|
std::string alert_type = "warning";
|
|
std::string alert_desc = "close_notify";
|
|
|
|
HELPER_EXPECT_SUCCESS(transport->on_dtls_alert(alert_type, alert_desc));
|
|
|
|
// Verify network on_dtls_alert was called with correct parameters
|
|
EXPECT_EQ(1, mock_network.on_dtls_alert_count_);
|
|
EXPECT_STREQ("warning", mock_network.last_alert_type_.c_str());
|
|
EXPECT_STREQ("close_notify", mock_network.last_alert_desc_.c_str());
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, OnDtlsAlertError)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls));
|
|
|
|
// Set up mock to return error
|
|
srs_error_t mock_error = srs_error_new(ERROR_RTC_DTLS, "mock dtls alert error");
|
|
mock_network.set_on_dtls_alert_error(mock_error);
|
|
srs_freep(mock_error);
|
|
|
|
std::string alert_type = "fatal";
|
|
std::string alert_desc = "handshake_failure";
|
|
|
|
HELPER_EXPECT_FAILED(transport->on_dtls_alert(alert_type, alert_desc));
|
|
|
|
// Verify network on_dtls_alert was called with correct parameters
|
|
EXPECT_EQ(1, mock_network.on_dtls_alert_count_);
|
|
EXPECT_STREQ("fatal", mock_network.last_alert_type_.c_str());
|
|
EXPECT_STREQ("handshake_failure", mock_network.last_alert_desc_.c_str());
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, OnDtlsHandshakeDoneSuccess)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls));
|
|
|
|
// Set up mock DTLS to return valid SRTP keys (30 bytes each: 16 key + 14 salt)
|
|
std::string recv_key(30, 'r'); // 30 bytes of 'r'
|
|
std::string send_key(30, 's'); // 30 bytes of 's'
|
|
mock_dtls.set_srtp_keys(recv_key, send_key);
|
|
|
|
HELPER_EXPECT_SUCCESS(transport->on_dtls_handshake_done());
|
|
|
|
// Verify DTLS get_srtp_key was called
|
|
EXPECT_EQ(1, mock_dtls.get_srtp_key_count_);
|
|
// Verify network on_dtls_handshake_done was called
|
|
EXPECT_EQ(1, mock_network.on_dtls_handshake_done_count_);
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, OnDtlsHandshakeDoneAlreadyDone)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls));
|
|
|
|
// Set up mock DTLS to return valid SRTP keys (30 bytes each: 16 key + 14 salt)
|
|
std::string recv_key(30, 'r'); // 30 bytes of 'r'
|
|
std::string send_key(30, 's'); // 30 bytes of 's'
|
|
mock_dtls.set_srtp_keys(recv_key, send_key);
|
|
|
|
// First call should succeed
|
|
HELPER_EXPECT_SUCCESS(transport->on_dtls_handshake_done());
|
|
EXPECT_EQ(1, mock_dtls.get_srtp_key_count_);
|
|
EXPECT_EQ(1, mock_network.on_dtls_handshake_done_count_);
|
|
|
|
// Second call should return success immediately without calling DTLS/network
|
|
HELPER_EXPECT_SUCCESS(transport->on_dtls_handshake_done());
|
|
EXPECT_EQ(1, mock_dtls.get_srtp_key_count_); // Should not increase
|
|
EXPECT_EQ(1, mock_network.on_dtls_handshake_done_count_); // Should not increase
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, OnDtlsHandshakeDoneSrtpInitError)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls));
|
|
|
|
// Set up mock DTLS to return error when getting SRTP keys
|
|
srs_error_t mock_error = srs_error_new(ERROR_RTC_SRTP_INIT, "mock srtp key error");
|
|
mock_dtls.set_get_srtp_key_error(mock_error);
|
|
srs_freep(mock_error);
|
|
|
|
HELPER_EXPECT_FAILED(transport->on_dtls_handshake_done());
|
|
|
|
// Verify DTLS get_srtp_key was called
|
|
EXPECT_EQ(1, mock_dtls.get_srtp_key_count_);
|
|
// Verify network on_dtls_handshake_done was NOT called due to SRTP init failure
|
|
EXPECT_EQ(0, mock_network.on_dtls_handshake_done_count_);
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, OnDtlsHandshakeDoneNetworkError)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls));
|
|
|
|
// Set up mock DTLS to return valid SRTP keys (30 bytes each: 16 key + 14 salt)
|
|
std::string recv_key(30, 'r'); // 30 bytes of 'r'
|
|
std::string send_key(30, 's'); // 30 bytes of 's'
|
|
mock_dtls.set_srtp_keys(recv_key, send_key);
|
|
|
|
// Set up mock network to return error
|
|
srs_error_t mock_error = srs_error_new(ERROR_RTC_DTLS, "mock network handshake done error");
|
|
mock_network.set_on_dtls_handshake_done_error(mock_error);
|
|
srs_freep(mock_error);
|
|
|
|
HELPER_EXPECT_FAILED(transport->on_dtls_handshake_done());
|
|
|
|
// Verify DTLS get_srtp_key was called
|
|
EXPECT_EQ(1, mock_dtls.get_srtp_key_count_);
|
|
// Verify network on_dtls_handshake_done was called
|
|
EXPECT_EQ(1, mock_network.on_dtls_handshake_done_count_);
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, OnDtlsApplicationDataSuccess)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls));
|
|
|
|
char test_data[] = "application data";
|
|
int data_size = strlen(test_data);
|
|
|
|
HELPER_EXPECT_SUCCESS(transport->on_dtls_application_data(test_data, data_size));
|
|
|
|
// This method currently just returns success without doing anything
|
|
// TODO: When SCTP protocol support is added, this test should verify SCTP processing
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, SrtpInitializeSuccess)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
MockSrtp mock_srtp;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls, &mock_srtp));
|
|
|
|
// Set up mock DTLS to return valid SRTP keys (30 bytes each: 16 key + 14 salt)
|
|
std::string recv_key(30, 'r'); // 30 bytes of 'r'
|
|
std::string send_key(30, 's'); // 30 bytes of 's'
|
|
mock_dtls.set_srtp_keys(recv_key, send_key);
|
|
|
|
HELPER_EXPECT_SUCCESS(transport->srtp_initialize());
|
|
|
|
// Verify DTLS get_srtp_key was called
|
|
EXPECT_EQ(1, mock_dtls.get_srtp_key_count_);
|
|
// Verify SRTP initialize was called with correct keys
|
|
EXPECT_EQ(1, mock_srtp.initialize_count_);
|
|
EXPECT_STREQ(recv_key.c_str(), mock_srtp.last_recv_key_.c_str());
|
|
EXPECT_STREQ(send_key.c_str(), mock_srtp.last_send_key_.c_str());
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, SrtpInitializeDtlsKeyError)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
MockSrtp mock_srtp;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls, &mock_srtp));
|
|
|
|
// Set up mock DTLS to return error when getting SRTP keys
|
|
srs_error_t mock_error = srs_error_new(ERROR_RTC_SRTP_INIT, "mock dtls get srtp key error");
|
|
mock_dtls.set_get_srtp_key_error(mock_error);
|
|
srs_freep(mock_error);
|
|
|
|
HELPER_EXPECT_FAILED(transport->srtp_initialize());
|
|
|
|
// Verify DTLS get_srtp_key was called
|
|
EXPECT_EQ(1, mock_dtls.get_srtp_key_count_);
|
|
// Verify SRTP initialize was NOT called due to DTLS error
|
|
EXPECT_EQ(0, mock_srtp.initialize_count_);
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, SrtpInitializeSrtpError)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
MockSrtp mock_srtp;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls, &mock_srtp));
|
|
|
|
// Set up mock DTLS to return valid SRTP keys
|
|
std::string recv_key(30, 'r');
|
|
std::string send_key(30, 's');
|
|
mock_dtls.set_srtp_keys(recv_key, send_key);
|
|
|
|
// Set up mock SRTP to return error
|
|
srs_error_t mock_error = srs_error_new(ERROR_RTC_SRTP_INIT, "mock srtp initialize error");
|
|
mock_srtp.set_initialize_error(mock_error);
|
|
srs_freep(mock_error);
|
|
|
|
HELPER_EXPECT_FAILED(transport->srtp_initialize());
|
|
|
|
// Verify DTLS get_srtp_key was called
|
|
EXPECT_EQ(1, mock_dtls.get_srtp_key_count_);
|
|
// Verify SRTP initialize was called
|
|
EXPECT_EQ(1, mock_srtp.initialize_count_);
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, ProtectRtpSuccess)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
MockSrtp mock_srtp;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls, &mock_srtp));
|
|
|
|
char test_packet[100] = {0};
|
|
int nb_cipher = sizeof(test_packet);
|
|
|
|
HELPER_EXPECT_SUCCESS(transport->protect_rtp(test_packet, &nb_cipher));
|
|
|
|
// Verify SRTP protect_rtp was called
|
|
EXPECT_EQ(1, mock_srtp.protect_rtp_count_);
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, ProtectRtpError)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
MockSrtp mock_srtp;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls, &mock_srtp));
|
|
|
|
// Set up mock SRTP to return error
|
|
srs_error_t mock_error = srs_error_new(ERROR_RTC_SRTP_PROTECT, "mock srtp protect rtp error");
|
|
mock_srtp.set_protect_rtp_error(mock_error);
|
|
srs_freep(mock_error);
|
|
|
|
char test_packet[100] = {0};
|
|
int nb_cipher = sizeof(test_packet);
|
|
|
|
HELPER_EXPECT_FAILED(transport->protect_rtp(test_packet, &nb_cipher));
|
|
|
|
// Verify SRTP protect_rtp was called
|
|
EXPECT_EQ(1, mock_srtp.protect_rtp_count_);
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, ProtectRtcpSuccess)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
MockSrtp mock_srtp;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls, &mock_srtp));
|
|
|
|
char test_packet[100] = {0};
|
|
int nb_cipher = sizeof(test_packet);
|
|
|
|
HELPER_EXPECT_SUCCESS(transport->protect_rtcp(test_packet, &nb_cipher));
|
|
|
|
// Verify SRTP protect_rtcp was called
|
|
EXPECT_EQ(1, mock_srtp.protect_rtcp_count_);
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, ProtectRtcpError)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
MockSrtp mock_srtp;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls, &mock_srtp));
|
|
|
|
// Set up mock SRTP to return error
|
|
srs_error_t mock_error = srs_error_new(ERROR_RTC_SRTP_PROTECT, "mock srtp protect rtcp error");
|
|
mock_srtp.set_protect_rtcp_error(mock_error);
|
|
srs_freep(mock_error);
|
|
|
|
char test_packet[100] = {0};
|
|
int nb_cipher = sizeof(test_packet);
|
|
|
|
HELPER_EXPECT_FAILED(transport->protect_rtcp(test_packet, &nb_cipher));
|
|
|
|
// Verify SRTP protect_rtcp was called
|
|
EXPECT_EQ(1, mock_srtp.protect_rtcp_count_);
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, UnprotectRtpSuccess)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
MockSrtp mock_srtp;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls, &mock_srtp));
|
|
|
|
char test_packet[100] = {0};
|
|
int nb_plaintext = sizeof(test_packet);
|
|
|
|
HELPER_EXPECT_SUCCESS(transport->unprotect_rtp(test_packet, &nb_plaintext));
|
|
|
|
// Verify SRTP unprotect_rtp was called
|
|
EXPECT_EQ(1, mock_srtp.unprotect_rtp_count_);
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, UnprotectRtpError)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
MockSrtp mock_srtp;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls, &mock_srtp));
|
|
|
|
// Set up mock SRTP to return error
|
|
srs_error_t mock_error = srs_error_new(ERROR_RTC_SRTP_UNPROTECT, "mock srtp unprotect rtp error");
|
|
mock_srtp.set_unprotect_rtp_error(mock_error);
|
|
srs_freep(mock_error);
|
|
|
|
char test_packet[100] = {0};
|
|
int nb_plaintext = sizeof(test_packet);
|
|
|
|
HELPER_EXPECT_FAILED(transport->unprotect_rtp(test_packet, &nb_plaintext));
|
|
|
|
// Verify SRTP unprotect_rtp was called
|
|
EXPECT_EQ(1, mock_srtp.unprotect_rtp_count_);
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, UnprotectRtcpSuccess)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
MockSrtp mock_srtp;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls, &mock_srtp));
|
|
|
|
char test_packet[100] = {0};
|
|
int nb_plaintext = sizeof(test_packet);
|
|
|
|
HELPER_EXPECT_SUCCESS(transport->unprotect_rtcp(test_packet, &nb_plaintext));
|
|
|
|
// Verify SRTP unprotect_rtcp was called
|
|
EXPECT_EQ(1, mock_srtp.unprotect_rtcp_count_);
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, UnprotectRtcpError)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
MockSrtp mock_srtp;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls, &mock_srtp));
|
|
|
|
// Set up mock SRTP to return error
|
|
srs_error_t mock_error = srs_error_new(ERROR_RTC_SRTP_UNPROTECT, "mock srtp unprotect rtcp error");
|
|
mock_srtp.set_unprotect_rtcp_error(mock_error);
|
|
srs_freep(mock_error);
|
|
|
|
char test_packet[100] = {0};
|
|
int nb_plaintext = sizeof(test_packet);
|
|
|
|
HELPER_EXPECT_FAILED(transport->unprotect_rtcp(test_packet, &nb_plaintext));
|
|
|
|
// Verify SRTP unprotect_rtcp was called
|
|
EXPECT_EQ(1, mock_srtp.unprotect_rtcp_count_);
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, ProtectUnprotectWithNullPointers)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
MockSrtp mock_srtp;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls, &mock_srtp));
|
|
|
|
int nb_cipher = 100;
|
|
int nb_plaintext = 100;
|
|
|
|
// Test with NULL packet pointer - should still call SRTP methods
|
|
HELPER_EXPECT_SUCCESS(transport->protect_rtp(NULL, &nb_cipher));
|
|
EXPECT_EQ(1, mock_srtp.protect_rtp_count_);
|
|
|
|
HELPER_EXPECT_SUCCESS(transport->protect_rtcp(NULL, &nb_cipher));
|
|
EXPECT_EQ(1, mock_srtp.protect_rtcp_count_);
|
|
|
|
HELPER_EXPECT_SUCCESS(transport->unprotect_rtp(NULL, &nb_plaintext));
|
|
EXPECT_EQ(1, mock_srtp.unprotect_rtp_count_);
|
|
|
|
HELPER_EXPECT_SUCCESS(transport->unprotect_rtcp(NULL, &nb_plaintext));
|
|
EXPECT_EQ(1, mock_srtp.unprotect_rtcp_count_);
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, MultipleProtectUnprotectCalls)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
MockSrtp mock_srtp;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls, &mock_srtp));
|
|
|
|
char test_packet[100] = {0};
|
|
int nb_cipher = sizeof(test_packet);
|
|
int nb_plaintext = sizeof(test_packet);
|
|
|
|
// Multiple protect_rtp calls
|
|
HELPER_EXPECT_SUCCESS(transport->protect_rtp(test_packet, &nb_cipher));
|
|
HELPER_EXPECT_SUCCESS(transport->protect_rtp(test_packet, &nb_cipher));
|
|
EXPECT_EQ(2, mock_srtp.protect_rtp_count_);
|
|
|
|
// Multiple protect_rtcp calls
|
|
HELPER_EXPECT_SUCCESS(transport->protect_rtcp(test_packet, &nb_cipher));
|
|
HELPER_EXPECT_SUCCESS(transport->protect_rtcp(test_packet, &nb_cipher));
|
|
EXPECT_EQ(2, mock_srtp.protect_rtcp_count_);
|
|
|
|
// Multiple unprotect_rtp calls
|
|
HELPER_EXPECT_SUCCESS(transport->unprotect_rtp(test_packet, &nb_plaintext));
|
|
HELPER_EXPECT_SUCCESS(transport->unprotect_rtp(test_packet, &nb_plaintext));
|
|
EXPECT_EQ(2, mock_srtp.unprotect_rtp_count_);
|
|
|
|
// Multiple unprotect_rtcp calls
|
|
HELPER_EXPECT_SUCCESS(transport->unprotect_rtcp(test_packet, &nb_plaintext));
|
|
HELPER_EXPECT_SUCCESS(transport->unprotect_rtcp(test_packet, &nb_plaintext));
|
|
EXPECT_EQ(2, mock_srtp.unprotect_rtcp_count_);
|
|
}
|
|
|
|
VOID TEST(SecurityTransportTest, SrtpInitializeWithEmptyKeys)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
MockDtls mock_dtls;
|
|
MockSrtp mock_srtp;
|
|
SrsUniquePtr<TestableSecurityTransport> transport(new TestableSecurityTransport(&mock_network, &mock_dtls, &mock_srtp));
|
|
|
|
// Set up mock DTLS to return empty SRTP keys
|
|
std::string recv_key = "";
|
|
std::string send_key = "";
|
|
mock_dtls.set_srtp_keys(recv_key, send_key);
|
|
|
|
HELPER_EXPECT_SUCCESS(transport->srtp_initialize());
|
|
|
|
// Verify DTLS get_srtp_key was called
|
|
EXPECT_EQ(1, mock_dtls.get_srtp_key_count_);
|
|
// Verify SRTP initialize was called with empty keys
|
|
EXPECT_EQ(1, mock_srtp.initialize_count_);
|
|
EXPECT_STREQ("", mock_srtp.last_recv_key_.c_str());
|
|
EXPECT_STREQ("", mock_srtp.last_send_key_.c_str());
|
|
}
|
|
|
|
// Tests for SrsSemiSecurityTransport
|
|
VOID TEST(SemiSecurityTransportTest, ConstructorAndDestructor)
|
|
{
|
|
MockRtcNetwork mock_network;
|
|
SrsSemiSecurityTransport *transport = new SrsSemiSecurityTransport(&mock_network);
|
|
|
|
// Constructor should succeed
|
|
EXPECT_TRUE(transport != NULL);
|
|
|
|
// Destructor should not crash
|
|
srs_freep(transport);
|
|
}
|
|
|
|
VOID TEST(SemiSecurityTransportTest, ProtectRtpAlwaysSuccess)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
SrsUniquePtr<SrsSemiSecurityTransport> transport(new SrsSemiSecurityTransport(&mock_network));
|
|
|
|
char test_packet[100] = {0};
|
|
int nb_cipher = sizeof(test_packet);
|
|
|
|
// protect_rtp should always return success without doing anything
|
|
HELPER_EXPECT_SUCCESS(transport->protect_rtp(test_packet, &nb_cipher));
|
|
|
|
// Test with NULL packet
|
|
HELPER_EXPECT_SUCCESS(transport->protect_rtp(NULL, &nb_cipher));
|
|
|
|
// Test with NULL size pointer
|
|
HELPER_EXPECT_SUCCESS(transport->protect_rtp(test_packet, NULL));
|
|
|
|
// Test with both NULL
|
|
HELPER_EXPECT_SUCCESS(transport->protect_rtp(NULL, NULL));
|
|
}
|
|
|
|
VOID TEST(SemiSecurityTransportTest, ProtectRtcpAlwaysSuccess)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
SrsUniquePtr<SrsSemiSecurityTransport> transport(new SrsSemiSecurityTransport(&mock_network));
|
|
|
|
char test_packet[100] = {0};
|
|
int nb_cipher = sizeof(test_packet);
|
|
|
|
// protect_rtcp should always return success without doing anything
|
|
HELPER_EXPECT_SUCCESS(transport->protect_rtcp(test_packet, &nb_cipher));
|
|
|
|
// Test with NULL packet
|
|
HELPER_EXPECT_SUCCESS(transport->protect_rtcp(NULL, &nb_cipher));
|
|
|
|
// Test with NULL size pointer
|
|
HELPER_EXPECT_SUCCESS(transport->protect_rtcp(test_packet, NULL));
|
|
|
|
// Test with both NULL
|
|
HELPER_EXPECT_SUCCESS(transport->protect_rtcp(NULL, NULL));
|
|
}
|
|
|
|
VOID TEST(SemiSecurityTransportTest, UnprotectRtpAlwaysSuccess)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
SrsUniquePtr<SrsSemiSecurityTransport> transport(new SrsSemiSecurityTransport(&mock_network));
|
|
|
|
char test_packet[100] = {0};
|
|
int nb_plaintext = sizeof(test_packet);
|
|
|
|
// unprotect_rtp should always return success without doing anything
|
|
HELPER_EXPECT_SUCCESS(transport->unprotect_rtp(test_packet, &nb_plaintext));
|
|
|
|
// Test with NULL packet
|
|
HELPER_EXPECT_SUCCESS(transport->unprotect_rtp(NULL, &nb_plaintext));
|
|
|
|
// Test with NULL size pointer
|
|
HELPER_EXPECT_SUCCESS(transport->unprotect_rtp(test_packet, NULL));
|
|
|
|
// Test with both NULL
|
|
HELPER_EXPECT_SUCCESS(transport->unprotect_rtp(NULL, NULL));
|
|
}
|
|
|
|
VOID TEST(SemiSecurityTransportTest, UnprotectRtcpAlwaysSuccess)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
SrsUniquePtr<SrsSemiSecurityTransport> transport(new SrsSemiSecurityTransport(&mock_network));
|
|
|
|
char test_packet[100] = {0};
|
|
int nb_plaintext = sizeof(test_packet);
|
|
|
|
// unprotect_rtcp should always return success without doing anything
|
|
HELPER_EXPECT_SUCCESS(transport->unprotect_rtcp(test_packet, &nb_plaintext));
|
|
|
|
// Test with NULL packet
|
|
HELPER_EXPECT_SUCCESS(transport->unprotect_rtcp(NULL, &nb_plaintext));
|
|
|
|
// Test with NULL size pointer
|
|
HELPER_EXPECT_SUCCESS(transport->unprotect_rtcp(test_packet, NULL));
|
|
|
|
// Test with both NULL
|
|
HELPER_EXPECT_SUCCESS(transport->unprotect_rtcp(NULL, NULL));
|
|
}
|
|
|
|
VOID TEST(SemiSecurityTransportTest, MultipleProtectUnprotectCalls)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
SrsUniquePtr<SrsSemiSecurityTransport> transport(new SrsSemiSecurityTransport(&mock_network));
|
|
|
|
char test_packet[100] = {0};
|
|
int nb_cipher = sizeof(test_packet);
|
|
int nb_plaintext = sizeof(test_packet);
|
|
|
|
// Multiple calls should all succeed
|
|
for (int i = 0; i < 5; i++) {
|
|
HELPER_EXPECT_SUCCESS(transport->protect_rtp(test_packet, &nb_cipher));
|
|
HELPER_EXPECT_SUCCESS(transport->protect_rtcp(test_packet, &nb_cipher));
|
|
HELPER_EXPECT_SUCCESS(transport->unprotect_rtp(test_packet, &nb_plaintext));
|
|
HELPER_EXPECT_SUCCESS(transport->unprotect_rtcp(test_packet, &nb_plaintext));
|
|
}
|
|
}
|
|
|
|
VOID TEST(SemiSecurityTransportTest, InheritedMethodsFromSecurityTransport)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
SrsUniquePtr<SrsSemiSecurityTransport> transport(new SrsSemiSecurityTransport(&mock_network));
|
|
|
|
// Test inherited initialize method
|
|
SrsUniquePtr<SrsSessionConfig> cfg(new SrsSessionConfig());
|
|
cfg->dtls_role_ = "active";
|
|
cfg->dtls_version_ = "1.2";
|
|
|
|
// This should work since it inherits from SrsSecurityTransport
|
|
// Note: This will use real DTLS, so we expect it to work
|
|
HELPER_EXPECT_SUCCESS(transport->initialize(cfg.get()));
|
|
}
|
|
|
|
// Tests for SrsPlaintextTransport
|
|
VOID TEST(PlaintextTransportTest, ConstructorAndDestructor)
|
|
{
|
|
MockRtcNetwork mock_network;
|
|
SrsPlaintextTransport *transport = new SrsPlaintextTransport(&mock_network);
|
|
|
|
// Constructor should succeed
|
|
EXPECT_TRUE(transport != NULL);
|
|
|
|
// Destructor should not crash
|
|
srs_freep(transport);
|
|
}
|
|
|
|
VOID TEST(PlaintextTransportTest, InitializeAlwaysSuccess)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
SrsUniquePtr<SrsPlaintextTransport> transport(new SrsPlaintextTransport(&mock_network));
|
|
|
|
// initialize should always return success without doing anything
|
|
SrsUniquePtr<SrsSessionConfig> cfg(new SrsSessionConfig());
|
|
cfg->dtls_role_ = "active";
|
|
cfg->dtls_version_ = "1.2";
|
|
|
|
HELPER_EXPECT_SUCCESS(transport->initialize(cfg.get()));
|
|
|
|
// Test with different config values
|
|
cfg->dtls_role_ = "passive";
|
|
cfg->dtls_version_ = "auto";
|
|
HELPER_EXPECT_SUCCESS(transport->initialize(cfg.get()));
|
|
|
|
// Test with empty config values
|
|
cfg->dtls_role_ = "";
|
|
cfg->dtls_version_ = "";
|
|
HELPER_EXPECT_SUCCESS(transport->initialize(cfg.get()));
|
|
|
|
// Test with NULL config
|
|
HELPER_EXPECT_SUCCESS(transport->initialize(NULL));
|
|
}
|
|
|
|
VOID TEST(PlaintextTransportTest, StartActiveHandshakeCallsNetworkHandshakeDone)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
SrsUniquePtr<SrsPlaintextTransport> transport(new SrsPlaintextTransport(&mock_network));
|
|
|
|
// start_active_handshake should call network->on_dtls_handshake_done()
|
|
HELPER_EXPECT_SUCCESS(transport->start_active_handshake());
|
|
|
|
// Verify network on_dtls_handshake_done was called
|
|
EXPECT_EQ(1, mock_network.on_dtls_handshake_done_count_);
|
|
}
|
|
|
|
VOID TEST(PlaintextTransportTest, StartActiveHandshakeNetworkError)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
SrsUniquePtr<SrsPlaintextTransport> transport(new SrsPlaintextTransport(&mock_network));
|
|
|
|
// Set up mock network to return error
|
|
srs_error_t mock_error = srs_error_new(ERROR_RTC_DTLS, "mock network handshake done error");
|
|
mock_network.set_on_dtls_handshake_done_error(mock_error);
|
|
srs_freep(mock_error);
|
|
|
|
// start_active_handshake should propagate the network error
|
|
HELPER_EXPECT_FAILED(transport->start_active_handshake());
|
|
|
|
// Verify network on_dtls_handshake_done was called
|
|
EXPECT_EQ(1, mock_network.on_dtls_handshake_done_count_);
|
|
}
|
|
|
|
VOID TEST(PlaintextTransportTest, OnDtlsAlwaysSuccess)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
SrsUniquePtr<SrsPlaintextTransport> transport(new SrsPlaintextTransport(&mock_network));
|
|
|
|
char test_data[] = "dtls handshake data";
|
|
int data_size = strlen(test_data);
|
|
|
|
// on_dtls should always return success without doing anything
|
|
HELPER_EXPECT_SUCCESS(transport->on_dtls(test_data, data_size));
|
|
|
|
// Test with NULL data
|
|
HELPER_EXPECT_SUCCESS(transport->on_dtls(NULL, data_size));
|
|
|
|
// Test with zero size
|
|
HELPER_EXPECT_SUCCESS(transport->on_dtls(test_data, 0));
|
|
|
|
// Test with both NULL and zero
|
|
HELPER_EXPECT_SUCCESS(transport->on_dtls(NULL, 0));
|
|
}
|
|
|
|
VOID TEST(PlaintextTransportTest, OnDtlsAlertAlwaysSuccess)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
SrsUniquePtr<SrsPlaintextTransport> transport(new SrsPlaintextTransport(&mock_network));
|
|
|
|
// on_dtls_alert should always return success without doing anything
|
|
HELPER_EXPECT_SUCCESS(transport->on_dtls_alert("warning", "close_notify"));
|
|
HELPER_EXPECT_SUCCESS(transport->on_dtls_alert("fatal", "handshake_failure"));
|
|
HELPER_EXPECT_SUCCESS(transport->on_dtls_alert("", ""));
|
|
}
|
|
|
|
VOID TEST(PlaintextTransportTest, OnDtlsHandshakeDoneCallsNetwork)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
SrsUniquePtr<SrsPlaintextTransport> transport(new SrsPlaintextTransport(&mock_network));
|
|
|
|
// on_dtls_handshake_done should call network->on_dtls_handshake_done()
|
|
HELPER_EXPECT_SUCCESS(transport->on_dtls_handshake_done());
|
|
|
|
// Verify network on_dtls_handshake_done was called
|
|
EXPECT_EQ(1, mock_network.on_dtls_handshake_done_count_);
|
|
}
|
|
|
|
VOID TEST(PlaintextTransportTest, OnDtlsHandshakeDoneNetworkError)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
SrsUniquePtr<SrsPlaintextTransport> transport(new SrsPlaintextTransport(&mock_network));
|
|
|
|
// Set up mock network to return error
|
|
srs_error_t mock_error = srs_error_new(ERROR_RTC_DTLS, "mock network handshake done error");
|
|
mock_network.set_on_dtls_handshake_done_error(mock_error);
|
|
srs_freep(mock_error);
|
|
|
|
// on_dtls_handshake_done should propagate the network error
|
|
HELPER_EXPECT_FAILED(transport->on_dtls_handshake_done());
|
|
|
|
// Verify network on_dtls_handshake_done was called
|
|
EXPECT_EQ(1, mock_network.on_dtls_handshake_done_count_);
|
|
}
|
|
|
|
VOID TEST(PlaintextTransportTest, OnDtlsApplicationDataAlwaysSuccess)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
SrsUniquePtr<SrsPlaintextTransport> transport(new SrsPlaintextTransport(&mock_network));
|
|
|
|
char test_data[] = "application data";
|
|
int data_size = strlen(test_data);
|
|
|
|
// on_dtls_application_data should always return success without doing anything
|
|
HELPER_EXPECT_SUCCESS(transport->on_dtls_application_data(test_data, data_size));
|
|
|
|
// Test with NULL data
|
|
HELPER_EXPECT_SUCCESS(transport->on_dtls_application_data(NULL, data_size));
|
|
|
|
// Test with zero size
|
|
HELPER_EXPECT_SUCCESS(transport->on_dtls_application_data(test_data, 0));
|
|
|
|
// Test with both NULL and zero
|
|
HELPER_EXPECT_SUCCESS(transport->on_dtls_application_data(NULL, 0));
|
|
}
|
|
|
|
VOID TEST(PlaintextTransportTest, WriteDtlsDataAlwaysSuccess)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
SrsUniquePtr<SrsPlaintextTransport> transport(new SrsPlaintextTransport(&mock_network));
|
|
|
|
char test_data[] = "dtls data to write";
|
|
int data_size = strlen(test_data);
|
|
|
|
// write_dtls_data should always return success without doing anything
|
|
HELPER_EXPECT_SUCCESS(transport->write_dtls_data(test_data, data_size));
|
|
|
|
// Test with NULL data
|
|
HELPER_EXPECT_SUCCESS(transport->write_dtls_data(NULL, data_size));
|
|
|
|
// Test with zero size
|
|
HELPER_EXPECT_SUCCESS(transport->write_dtls_data(test_data, 0));
|
|
|
|
// Test with both NULL and zero
|
|
HELPER_EXPECT_SUCCESS(transport->write_dtls_data(NULL, 0));
|
|
}
|
|
|
|
VOID TEST(PlaintextTransportTest, ProtectRtpAlwaysSuccess)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
SrsUniquePtr<SrsPlaintextTransport> transport(new SrsPlaintextTransport(&mock_network));
|
|
|
|
char test_packet[100] = {0};
|
|
int nb_cipher = sizeof(test_packet);
|
|
|
|
// protect_rtp should always return success without doing anything
|
|
HELPER_EXPECT_SUCCESS(transport->protect_rtp(test_packet, &nb_cipher));
|
|
|
|
// Test with NULL packet
|
|
HELPER_EXPECT_SUCCESS(transport->protect_rtp(NULL, &nb_cipher));
|
|
|
|
// Test with NULL size pointer
|
|
HELPER_EXPECT_SUCCESS(transport->protect_rtp(test_packet, NULL));
|
|
|
|
// Test with both NULL
|
|
HELPER_EXPECT_SUCCESS(transport->protect_rtp(NULL, NULL));
|
|
}
|
|
|
|
VOID TEST(PlaintextTransportTest, ProtectRtcpAlwaysSuccess)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
SrsUniquePtr<SrsPlaintextTransport> transport(new SrsPlaintextTransport(&mock_network));
|
|
|
|
char test_packet[100] = {0};
|
|
int nb_cipher = sizeof(test_packet);
|
|
|
|
// protect_rtcp should always return success without doing anything
|
|
HELPER_EXPECT_SUCCESS(transport->protect_rtcp(test_packet, &nb_cipher));
|
|
|
|
// Test with NULL packet
|
|
HELPER_EXPECT_SUCCESS(transport->protect_rtcp(NULL, &nb_cipher));
|
|
|
|
// Test with NULL size pointer
|
|
HELPER_EXPECT_SUCCESS(transport->protect_rtcp(test_packet, NULL));
|
|
|
|
// Test with both NULL
|
|
HELPER_EXPECT_SUCCESS(transport->protect_rtcp(NULL, NULL));
|
|
}
|
|
|
|
VOID TEST(PlaintextTransportTest, UnprotectRtpAlwaysSuccess)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
SrsUniquePtr<SrsPlaintextTransport> transport(new SrsPlaintextTransport(&mock_network));
|
|
|
|
char test_packet[100] = {0};
|
|
int nb_plaintext = sizeof(test_packet);
|
|
|
|
// unprotect_rtp should always return success without doing anything
|
|
HELPER_EXPECT_SUCCESS(transport->unprotect_rtp(test_packet, &nb_plaintext));
|
|
|
|
// Test with NULL packet
|
|
HELPER_EXPECT_SUCCESS(transport->unprotect_rtp(NULL, &nb_plaintext));
|
|
|
|
// Test with NULL size pointer
|
|
HELPER_EXPECT_SUCCESS(transport->unprotect_rtp(test_packet, NULL));
|
|
|
|
// Test with both NULL
|
|
HELPER_EXPECT_SUCCESS(transport->unprotect_rtp(NULL, NULL));
|
|
}
|
|
|
|
VOID TEST(PlaintextTransportTest, UnprotectRtcpAlwaysSuccess)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
SrsUniquePtr<SrsPlaintextTransport> transport(new SrsPlaintextTransport(&mock_network));
|
|
|
|
char test_packet[100] = {0};
|
|
int nb_plaintext = sizeof(test_packet);
|
|
|
|
// unprotect_rtcp should always return success without doing anything
|
|
HELPER_EXPECT_SUCCESS(transport->unprotect_rtcp(test_packet, &nb_plaintext));
|
|
|
|
// Test with NULL packet
|
|
HELPER_EXPECT_SUCCESS(transport->unprotect_rtcp(NULL, &nb_plaintext));
|
|
|
|
// Test with NULL size pointer
|
|
HELPER_EXPECT_SUCCESS(transport->unprotect_rtcp(test_packet, NULL));
|
|
|
|
// Test with both NULL
|
|
HELPER_EXPECT_SUCCESS(transport->unprotect_rtcp(NULL, NULL));
|
|
}
|
|
|
|
VOID TEST(PlaintextTransportTest, MultipleProtectUnprotectCalls)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
SrsUniquePtr<SrsPlaintextTransport> transport(new SrsPlaintextTransport(&mock_network));
|
|
|
|
char test_packet[100] = {0};
|
|
int nb_cipher = sizeof(test_packet);
|
|
int nb_plaintext = sizeof(test_packet);
|
|
|
|
// Multiple calls should all succeed
|
|
for (int i = 0; i < 5; i++) {
|
|
HELPER_EXPECT_SUCCESS(transport->protect_rtp(test_packet, &nb_cipher));
|
|
HELPER_EXPECT_SUCCESS(transport->protect_rtcp(test_packet, &nb_cipher));
|
|
HELPER_EXPECT_SUCCESS(transport->unprotect_rtp(test_packet, &nb_plaintext));
|
|
HELPER_EXPECT_SUCCESS(transport->unprotect_rtcp(test_packet, &nb_plaintext));
|
|
}
|
|
}
|
|
|
|
VOID TEST(RtcPublishStreamTest, SendRtcpXrRrtr)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcAsyncTaskExecutor mock_exec;
|
|
MockRtcExpire mock_expire;
|
|
MockRtcPacketReceiver mock_receiver;
|
|
SrsUniquePtr<SrsRtcPublishStream> publish_stream(new SrsRtcPublishStream(&mock_exec, &mock_expire, &mock_receiver, SrsContextId()));
|
|
|
|
// Create audio track
|
|
SrsRtcTrackDescription *audio_desc = new SrsRtcTrackDescription();
|
|
audio_desc->type_ = "audio";
|
|
audio_desc->id_ = "audio_track_1";
|
|
audio_desc->ssrc_ = 12345;
|
|
audio_desc->is_active_ = true;
|
|
SrsRtcAudioRecvTrack *audio_track = new SrsRtcAudioRecvTrack(&mock_receiver, audio_desc);
|
|
publish_stream->audio_tracks_.push_back(audio_track);
|
|
|
|
// Create video track
|
|
SrsRtcTrackDescription *video_desc = new SrsRtcTrackDescription();
|
|
video_desc->type_ = "video";
|
|
video_desc->id_ = "video_track_1";
|
|
video_desc->ssrc_ = 67890;
|
|
video_desc->is_active_ = true;
|
|
SrsRtcVideoRecvTrack *video_track = new SrsRtcVideoRecvTrack(&mock_receiver, video_desc);
|
|
publish_stream->video_tracks_.push_back(video_track);
|
|
|
|
// Test successful case
|
|
HELPER_EXPECT_SUCCESS(publish_stream->send_rtcp_xr_rrtr());
|
|
|
|
// Verify that send_rtcp_xr_rrtr was called for both tracks (2 times total)
|
|
EXPECT_EQ(2, mock_receiver.send_rtcp_xr_rrtr_count_);
|
|
}
|
|
|
|
// Mock RTC expire implementation
|
|
MockRtcExpire::MockRtcExpire()
|
|
{
|
|
expired_ = false;
|
|
}
|
|
|
|
MockRtcExpire::~MockRtcExpire()
|
|
{
|
|
}
|
|
|
|
void MockRtcExpire::expire()
|
|
{
|
|
expired_ = true;
|
|
}
|
|
|
|
void MockRtcExpire::reset()
|
|
{
|
|
expired_ = false;
|
|
}
|
|
|
|
VOID TEST(RtcPublishStreamTest, SendRtcpRrSuccess)
|
|
{
|
|
srs_error_t err;
|
|
|
|
// Create mock objects
|
|
MockRtcAsyncTaskExecutor mock_exec;
|
|
MockRtcExpire mock_expire;
|
|
MockRtcPacketReceiver mock_receiver;
|
|
SrsContextId cid;
|
|
|
|
// Create publish stream
|
|
SrsUniquePtr<SrsRtcPublishStream> publish_stream(new SrsRtcPublishStream(&mock_exec, &mock_expire, &mock_receiver, cid));
|
|
|
|
// Create mock track descriptions
|
|
SrsUniquePtr<SrsRtcTrackDescription> video_track_desc(new SrsRtcTrackDescription());
|
|
video_track_desc->type_ = "video";
|
|
video_track_desc->id_ = "video_track_1";
|
|
video_track_desc->ssrc_ = 12345;
|
|
|
|
SrsUniquePtr<SrsRtcTrackDescription> audio_track_desc(new SrsRtcTrackDescription());
|
|
audio_track_desc->type_ = "audio";
|
|
audio_track_desc->id_ = "audio_track_1";
|
|
audio_track_desc->ssrc_ = 67890;
|
|
|
|
// Create video and audio recv tracks
|
|
SrsRtcVideoRecvTrack *video_track = new SrsRtcVideoRecvTrack(&mock_receiver, video_track_desc.get());
|
|
SrsRtcAudioRecvTrack *audio_track = new SrsRtcAudioRecvTrack(&mock_receiver, audio_track_desc.get());
|
|
|
|
// Add tracks to publish stream (using private member access)
|
|
// The publish stream will take ownership and free them in destructor
|
|
publish_stream->video_tracks_.push_back(video_track);
|
|
publish_stream->audio_tracks_.push_back(audio_track);
|
|
|
|
// Reset receiver count before test
|
|
mock_receiver.reset();
|
|
|
|
// Test send_rtcp_rr - should succeed when all tracks succeed
|
|
HELPER_EXPECT_SUCCESS(publish_stream->send_rtcp_rr());
|
|
|
|
// Verify that send_rtcp_rr was called on both tracks (2 calls total)
|
|
EXPECT_EQ(2, mock_receiver.send_rtcp_rr_count_);
|
|
}
|
|
|
|
VOID TEST(RtcPublishStreamTest, SendRtcpRrVideoTrackError)
|
|
{
|
|
srs_error_t err;
|
|
|
|
// Create mock objects
|
|
MockRtcAsyncTaskExecutor mock_exec;
|
|
MockRtcExpire mock_expire;
|
|
MockRtcPacketReceiver mock_receiver;
|
|
SrsContextId cid;
|
|
|
|
// Create publish stream
|
|
SrsUniquePtr<SrsRtcPublishStream> publish_stream(new SrsRtcPublishStream(&mock_exec, &mock_expire, &mock_receiver, cid));
|
|
|
|
// Create mock track descriptions
|
|
SrsUniquePtr<SrsRtcTrackDescription> video_track_desc(new SrsRtcTrackDescription());
|
|
video_track_desc->type_ = "video";
|
|
video_track_desc->id_ = "video_track_1";
|
|
video_track_desc->ssrc_ = 12345;
|
|
|
|
// Create video recv track
|
|
SrsRtcVideoRecvTrack *video_track = new SrsRtcVideoRecvTrack(&mock_receiver, video_track_desc.get());
|
|
|
|
// Add track to publish stream (only video track to simplify)
|
|
publish_stream->video_tracks_.push_back(video_track);
|
|
|
|
// Reset receiver count before test
|
|
mock_receiver.reset();
|
|
|
|
// Set receiver to return error after reset
|
|
srs_error_t mock_error = srs_error_new(ERROR_RTC_RTCP, "mock rtcp rr error");
|
|
mock_receiver.set_send_rtcp_rr_error(mock_error);
|
|
|
|
// Test send_rtcp_rr - should fail when receiver returns error
|
|
HELPER_EXPECT_FAILED(publish_stream->send_rtcp_rr());
|
|
|
|
// Verify that send_rtcp_rr was called once
|
|
EXPECT_EQ(1, mock_receiver.send_rtcp_rr_count_);
|
|
}
|
|
|
|
VOID TEST(RtcPublishStreamTest, SendRtcpRrNoTracks)
|
|
{
|
|
srs_error_t err;
|
|
|
|
// Create mock objects
|
|
MockRtcAsyncTaskExecutor mock_exec;
|
|
MockRtcExpire mock_expire;
|
|
MockRtcPacketReceiver mock_receiver;
|
|
SrsContextId cid;
|
|
|
|
// Create publish stream
|
|
SrsUniquePtr<SrsRtcPublishStream> publish_stream(new SrsRtcPublishStream(&mock_exec, &mock_expire, &mock_receiver, cid));
|
|
|
|
// Test send_rtcp_rr with no tracks - should succeed (empty loops)
|
|
HELPER_EXPECT_SUCCESS(publish_stream->send_rtcp_rr());
|
|
}
|
|
|
|
VOID TEST(RtcPublishStreamTest, SendRtcpRrMultipleTracks)
|
|
{
|
|
srs_error_t err;
|
|
|
|
// Create mock objects
|
|
MockRtcAsyncTaskExecutor mock_exec;
|
|
MockRtcExpire mock_expire;
|
|
MockRtcPacketReceiver mock_receiver;
|
|
SrsContextId cid;
|
|
|
|
// Create publish stream
|
|
SrsUniquePtr<SrsRtcPublishStream> publish_stream(new SrsRtcPublishStream(&mock_exec, &mock_expire, &mock_receiver, cid));
|
|
|
|
// Create multiple video track descriptions
|
|
SrsUniquePtr<SrsRtcTrackDescription> video_track_desc1(new SrsRtcTrackDescription());
|
|
video_track_desc1->type_ = "video";
|
|
video_track_desc1->id_ = "video_track_1";
|
|
video_track_desc1->ssrc_ = 12345;
|
|
|
|
SrsUniquePtr<SrsRtcTrackDescription> video_track_desc2(new SrsRtcTrackDescription());
|
|
video_track_desc2->type_ = "video";
|
|
video_track_desc2->id_ = "video_track_2";
|
|
video_track_desc2->ssrc_ = 12346;
|
|
|
|
// Create multiple audio track descriptions
|
|
SrsUniquePtr<SrsRtcTrackDescription> audio_track_desc1(new SrsRtcTrackDescription());
|
|
audio_track_desc1->type_ = "audio";
|
|
audio_track_desc1->id_ = "audio_track_1";
|
|
audio_track_desc1->ssrc_ = 67890;
|
|
|
|
SrsUniquePtr<SrsRtcTrackDescription> audio_track_desc2(new SrsRtcTrackDescription());
|
|
audio_track_desc2->type_ = "audio";
|
|
audio_track_desc2->id_ = "audio_track_2";
|
|
audio_track_desc2->ssrc_ = 67891;
|
|
|
|
// Create tracks
|
|
SrsRtcVideoRecvTrack *video_track1 = new SrsRtcVideoRecvTrack(&mock_receiver, video_track_desc1.get());
|
|
SrsRtcVideoRecvTrack *video_track2 = new SrsRtcVideoRecvTrack(&mock_receiver, video_track_desc2.get());
|
|
SrsRtcAudioRecvTrack *audio_track1 = new SrsRtcAudioRecvTrack(&mock_receiver, audio_track_desc1.get());
|
|
SrsRtcAudioRecvTrack *audio_track2 = new SrsRtcAudioRecvTrack(&mock_receiver, audio_track_desc2.get());
|
|
|
|
// Add tracks to publish stream
|
|
publish_stream->video_tracks_.push_back(video_track1);
|
|
publish_stream->video_tracks_.push_back(video_track2);
|
|
publish_stream->audio_tracks_.push_back(audio_track1);
|
|
publish_stream->audio_tracks_.push_back(audio_track2);
|
|
|
|
// Reset receiver count before test
|
|
mock_receiver.reset();
|
|
|
|
// Test send_rtcp_rr - should succeed when all tracks succeed
|
|
HELPER_EXPECT_SUCCESS(publish_stream->send_rtcp_rr());
|
|
|
|
// Verify that send_rtcp_rr was called on all tracks (4 calls total)
|
|
EXPECT_EQ(4, mock_receiver.send_rtcp_rr_count_);
|
|
}
|
|
|
|
VOID TEST(PlaintextTransportTest, MultipleHandshakeCalls)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
SrsUniquePtr<SrsPlaintextTransport> transport(new SrsPlaintextTransport(&mock_network));
|
|
|
|
// Multiple handshake calls should all succeed and call network each time
|
|
HELPER_EXPECT_SUCCESS(transport->start_active_handshake());
|
|
EXPECT_EQ(1, mock_network.on_dtls_handshake_done_count_);
|
|
|
|
HELPER_EXPECT_SUCCESS(transport->start_active_handshake());
|
|
EXPECT_EQ(2, mock_network.on_dtls_handshake_done_count_);
|
|
|
|
HELPER_EXPECT_SUCCESS(transport->on_dtls_handshake_done());
|
|
EXPECT_EQ(3, mock_network.on_dtls_handshake_done_count_);
|
|
}
|
|
|
|
VOID TEST(PlaintextTransportTest, InitializeAndHandshakeSequence)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
SrsUniquePtr<SrsPlaintextTransport> transport(new SrsPlaintextTransport(&mock_network));
|
|
|
|
// Initialize first
|
|
SrsUniquePtr<SrsSessionConfig> cfg(new SrsSessionConfig());
|
|
cfg->dtls_role_ = "active";
|
|
cfg->dtls_version_ = "1.2";
|
|
|
|
HELPER_EXPECT_SUCCESS(transport->initialize(cfg.get()));
|
|
EXPECT_EQ(0, mock_network.on_dtls_handshake_done_count_);
|
|
|
|
// Then start handshake
|
|
HELPER_EXPECT_SUCCESS(transport->start_active_handshake());
|
|
EXPECT_EQ(1, mock_network.on_dtls_handshake_done_count_);
|
|
}
|
|
|
|
VOID TEST(PlaintextTransportTest, HandshakeWithoutInitialize)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcNetwork mock_network;
|
|
SrsUniquePtr<SrsPlaintextTransport> transport(new SrsPlaintextTransport(&mock_network));
|
|
|
|
// Handshake without initialize should still work
|
|
HELPER_EXPECT_SUCCESS(transport->start_active_handshake());
|
|
EXPECT_EQ(1, mock_network.on_dtls_handshake_done_count_);
|
|
}
|
|
|
|
// Mock PLI Worker Handler implementation
|
|
MockRtcPliWorkerHandler::MockRtcPliWorkerHandler()
|
|
{
|
|
do_request_keyframe_error_ = srs_success;
|
|
do_request_keyframe_count_ = 0;
|
|
}
|
|
|
|
MockRtcPliWorkerHandler::~MockRtcPliWorkerHandler()
|
|
{
|
|
srs_freep(do_request_keyframe_error_);
|
|
}
|
|
|
|
srs_error_t MockRtcPliWorkerHandler::do_request_keyframe(uint32_t ssrc, SrsContextId cid)
|
|
{
|
|
do_request_keyframe_count_++;
|
|
keyframe_requests_.push_back(std::make_pair(ssrc, cid));
|
|
|
|
if (do_request_keyframe_error_ != srs_success) {
|
|
return srs_error_copy(do_request_keyframe_error_);
|
|
}
|
|
|
|
return srs_success;
|
|
}
|
|
|
|
void MockRtcPliWorkerHandler::reset()
|
|
{
|
|
srs_freep(do_request_keyframe_error_);
|
|
do_request_keyframe_error_ = srs_success;
|
|
do_request_keyframe_count_ = 0;
|
|
keyframe_requests_.clear();
|
|
}
|
|
|
|
void MockRtcPliWorkerHandler::set_do_request_keyframe_error(srs_error_t err)
|
|
{
|
|
srs_freep(do_request_keyframe_error_);
|
|
do_request_keyframe_error_ = srs_error_copy(err);
|
|
}
|
|
|
|
bool MockRtcPliWorkerHandler::has_keyframe_request(uint32_t ssrc, const SrsContextId &cid)
|
|
{
|
|
for (size_t i = 0; i < keyframe_requests_.size(); i++) {
|
|
if (keyframe_requests_[i].first == ssrc) {
|
|
// Compare the string values of the context IDs
|
|
std::string req_cid_str = keyframe_requests_[i].second.c_str();
|
|
std::string target_cid_str = cid.c_str();
|
|
if (req_cid_str == target_cid_str) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int MockRtcPliWorkerHandler::get_keyframe_request_count()
|
|
{
|
|
return do_request_keyframe_count_;
|
|
}
|
|
|
|
MockRtcPliWorker::MockRtcPliWorker(ISrsRtcPliWorkerHandler *h) : SrsRtcPliWorker(h)
|
|
{
|
|
request_keyframe_count_ = 0;
|
|
}
|
|
|
|
MockRtcPliWorker::~MockRtcPliWorker()
|
|
{
|
|
}
|
|
|
|
void MockRtcPliWorker::request_keyframe(uint32_t ssrc, SrsContextId cid)
|
|
{
|
|
request_keyframe_count_++;
|
|
keyframe_requests_.push_back(std::make_pair(ssrc, cid));
|
|
// Don't call the parent implementation to avoid actual PLI processing
|
|
}
|
|
|
|
void MockRtcPliWorker::reset()
|
|
{
|
|
request_keyframe_count_ = 0;
|
|
keyframe_requests_.clear();
|
|
}
|
|
|
|
bool MockRtcPliWorker::has_keyframe_request(uint32_t ssrc, const SrsContextId &cid)
|
|
{
|
|
for (std::vector<std::pair<uint32_t, SrsContextId> >::iterator it = keyframe_requests_.begin(); it != keyframe_requests_.end(); ++it) {
|
|
if (it->first == ssrc && it->second.compare(cid) == 0) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int MockRtcPliWorker::get_keyframe_request_count()
|
|
{
|
|
return request_keyframe_count_;
|
|
}
|
|
|
|
VOID TEST(RtcPliWorkerTest, BasicFunctionality)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcPliWorkerHandler mock_handler;
|
|
SrsUniquePtr<SrsRtcPliWorker> worker(new SrsRtcPliWorker(&mock_handler));
|
|
|
|
// Test starting the worker
|
|
HELPER_EXPECT_SUCCESS(worker->start());
|
|
|
|
// Request multiple keyframes with different SSRCs
|
|
uint32_t ssrc1 = 12345;
|
|
uint32_t ssrc2 = 67890;
|
|
|
|
SrsContextId cid1;
|
|
SrsContextId cid2;
|
|
cid1.set_value("test-cid-001");
|
|
cid2.set_value("test-cid-002");
|
|
|
|
worker->request_keyframe(ssrc1, cid1);
|
|
worker->request_keyframe(ssrc2, cid2);
|
|
|
|
// Give the worker time to process requests
|
|
srs_usleep(1 * SRS_UTIME_MILLISECONDS);
|
|
|
|
// Verify both handlers were called
|
|
EXPECT_EQ(2, mock_handler.get_keyframe_request_count());
|
|
EXPECT_TRUE(mock_handler.has_keyframe_request(ssrc1, cid1));
|
|
EXPECT_TRUE(mock_handler.has_keyframe_request(ssrc2, cid2));
|
|
}
|
|
|
|
VOID TEST(RtcPliWorkerTest, ErrorHandling)
|
|
{
|
|
srs_error_t err;
|
|
|
|
MockRtcPliWorkerHandler mock_handler;
|
|
SrsUniquePtr<SrsRtcPliWorker> worker(new SrsRtcPliWorker(&mock_handler));
|
|
|
|
// Test starting the worker
|
|
HELPER_EXPECT_SUCCESS(worker->start());
|
|
|
|
// Set up the mock handler to return an error
|
|
srs_error_t test_error = srs_error_new(ERROR_RTC_RTCP, "test PLI error");
|
|
mock_handler.set_do_request_keyframe_error(test_error);
|
|
srs_freep(test_error);
|
|
|
|
// Request a keyframe that should trigger the error
|
|
uint32_t ssrc = 12345;
|
|
SrsContextId cid;
|
|
cid.set_value("test-cid-error");
|
|
|
|
worker->request_keyframe(ssrc, cid);
|
|
|
|
// Give the worker time to process the request and handle the error
|
|
srs_usleep(1 * SRS_UTIME_MILLISECONDS);
|
|
|
|
// Verify the handler was called despite the error
|
|
EXPECT_EQ(1, mock_handler.get_keyframe_request_count());
|
|
EXPECT_TRUE(mock_handler.has_keyframe_request(ssrc, cid));
|
|
|
|
// Test that the worker continues to work after handling the error
|
|
mock_handler.reset();
|
|
|
|
// Request another keyframe with no error
|
|
uint32_t ssrc2 = 67890;
|
|
SrsContextId cid2;
|
|
cid2.set_value("test-cid-success");
|
|
|
|
worker->request_keyframe(ssrc2, cid2);
|
|
|
|
// Give the worker time to process the successful request
|
|
srs_usleep(1 * SRS_UTIME_MILLISECONDS);
|
|
|
|
// Verify the worker recovered and processed the successful request
|
|
EXPECT_EQ(1, mock_handler.get_keyframe_request_count());
|
|
EXPECT_TRUE(mock_handler.has_keyframe_request(ssrc2, cid2));
|
|
}
|
|
|
|
// Mock HTTP hooks implementation
|
|
MockHttpHooks::MockHttpHooks()
|
|
{
|
|
on_stop_count_ = 0;
|
|
on_unpublish_count_ = 0;
|
|
}
|
|
|
|
MockHttpHooks::~MockHttpHooks()
|
|
{
|
|
clear_calls();
|
|
}
|
|
|
|
srs_error_t MockHttpHooks::on_connect(std::string url, ISrsRequest *req)
|
|
{
|
|
return srs_success;
|
|
}
|
|
|
|
void MockHttpHooks::on_close(std::string url, ISrsRequest *req, int64_t send_bytes, int64_t recv_bytes)
|
|
{
|
|
}
|
|
|
|
srs_error_t MockHttpHooks::on_publish(std::string url, ISrsRequest *req)
|
|
{
|
|
return srs_success;
|
|
}
|
|
|
|
void MockHttpHooks::on_unpublish(std::string url, ISrsRequest *req)
|
|
{
|
|
on_unpublish_count_++;
|
|
on_unpublish_calls_.push_back(std::make_pair(url, req));
|
|
}
|
|
|
|
srs_error_t MockHttpHooks::on_play(std::string url, ISrsRequest *req)
|
|
{
|
|
return srs_success;
|
|
}
|
|
|
|
void MockHttpHooks::on_stop(std::string url, ISrsRequest *req)
|
|
{
|
|
on_stop_count_++;
|
|
on_stop_calls_.push_back(std::make_pair(url, req));
|
|
}
|
|
|
|
srs_error_t MockHttpHooks::on_dvr(SrsContextId cid, std::string url, ISrsRequest *req, std::string file)
|
|
{
|
|
return srs_success;
|
|
}
|
|
|
|
srs_error_t MockHttpHooks::on_hls(SrsContextId cid, std::string url, ISrsRequest *req, std::string file, std::string ts_url,
|
|
std::string m3u8, std::string m3u8_url, int sn, srs_utime_t duration)
|
|
{
|
|
return srs_success;
|
|
}
|
|
|
|
srs_error_t MockHttpHooks::on_hls_notify(SrsContextId cid, std::string url, ISrsRequest *req, std::string ts_url, int nb_notify)
|
|
{
|
|
return srs_success;
|
|
}
|
|
|
|
srs_error_t MockHttpHooks::discover_co_workers(std::string url, std::string &host, int &port)
|
|
{
|
|
return srs_success;
|
|
}
|
|
|
|
srs_error_t MockHttpHooks::on_forward_backend(std::string url, ISrsRequest *req, std::vector<std::string> &rtmp_urls)
|
|
{
|
|
return srs_success;
|
|
}
|
|
|
|
void MockHttpHooks::clear_calls()
|
|
{
|
|
on_stop_calls_.clear();
|
|
on_stop_count_ = 0;
|
|
on_unpublish_calls_.clear();
|
|
on_unpublish_count_ = 0;
|
|
}
|
|
|
|
// Mock context implementation
|
|
MockContext::MockContext()
|
|
{
|
|
current_id_.set_value("mock-context-id");
|
|
get_id_result_.set_value("mock-get-id");
|
|
}
|
|
|
|
MockContext::~MockContext()
|
|
{
|
|
}
|
|
|
|
SrsContextId MockContext::generate_id()
|
|
{
|
|
SrsContextId id;
|
|
return id.set_value("generated-id");
|
|
}
|
|
|
|
const SrsContextId &MockContext::get_id()
|
|
{
|
|
return get_id_result_;
|
|
}
|
|
|
|
const SrsContextId &MockContext::set_id(const SrsContextId &v)
|
|
{
|
|
current_id_ = v;
|
|
return current_id_;
|
|
}
|
|
|
|
void MockContext::set_current_id(const SrsContextId &id)
|
|
{
|
|
current_id_ = id;
|
|
get_id_result_ = id;
|
|
}
|
|
|
|
// Mock app config implementation
|
|
MockAppConfig::MockAppConfig()
|
|
{
|
|
http_hooks_enabled_ = true;
|
|
on_stop_directive_ = NULL;
|
|
on_unpublish_directive_ = NULL;
|
|
rtc_nack_enabled_ = true;
|
|
rtc_nack_no_copy_ = false;
|
|
rtc_drop_for_pt_ = 0;
|
|
rtc_twcc_enabled_ = true;
|
|
srt_enabled_ = false;
|
|
rtc_to_rtmp_ = false;
|
|
dash_dispose_ = 0;
|
|
dash_enabled_ = false;
|
|
}
|
|
|
|
MockAppConfig::~MockAppConfig()
|
|
{
|
|
clear_on_stop_directive();
|
|
clear_on_unpublish_directive();
|
|
}
|
|
|
|
srs_utime_t MockAppConfig::get_pithy_print()
|
|
{
|
|
return 10 * SRS_UTIME_SECONDS;
|
|
}
|
|
|
|
std::string MockAppConfig::get_default_app_name()
|
|
{
|
|
return "live";
|
|
}
|
|
|
|
void MockAppConfig::subscribe(ISrsReloadHandler *handler)
|
|
{
|
|
// Do nothing in mock
|
|
}
|
|
|
|
void MockAppConfig::unsubscribe(ISrsReloadHandler *handler)
|
|
{
|
|
// Do nothing in mock
|
|
}
|
|
|
|
bool MockAppConfig::get_vhost_http_hooks_enabled(std::string vhost)
|
|
{
|
|
return http_hooks_enabled_;
|
|
}
|
|
|
|
SrsConfDirective *MockAppConfig::get_vhost_on_stop(std::string vhost)
|
|
{
|
|
return on_stop_directive_;
|
|
}
|
|
|
|
SrsConfDirective *MockAppConfig::get_vhost_on_unpublish(std::string vhost)
|
|
{
|
|
return on_unpublish_directive_;
|
|
}
|
|
|
|
SrsConfDirective *MockAppConfig::get_vhost_on_dvr(std::string vhost)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
bool MockAppConfig::get_rtc_nack_enabled(std::string vhost)
|
|
{
|
|
return rtc_nack_enabled_;
|
|
}
|
|
|
|
bool MockAppConfig::get_rtc_nack_no_copy(std::string vhost)
|
|
{
|
|
return rtc_nack_no_copy_;
|
|
}
|
|
|
|
bool MockAppConfig::get_realtime_enabled(std::string vhost, bool is_rtc)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
int MockAppConfig::get_mw_msgs(std::string vhost, bool is_realtime, bool is_rtc)
|
|
{
|
|
return 8;
|
|
}
|
|
|
|
int MockAppConfig::get_rtc_drop_for_pt(std::string vhost)
|
|
{
|
|
return rtc_drop_for_pt_;
|
|
}
|
|
|
|
bool MockAppConfig::get_rtc_twcc_enabled(std::string vhost)
|
|
{
|
|
return rtc_twcc_enabled_;
|
|
}
|
|
|
|
bool MockAppConfig::get_srt_enabled()
|
|
{
|
|
return srt_enabled_;
|
|
}
|
|
|
|
bool MockAppConfig::get_srt_enabled(std::string vhost)
|
|
{
|
|
return srt_enabled_;
|
|
}
|
|
|
|
bool MockAppConfig::get_rtc_to_rtmp(std::string vhost)
|
|
{
|
|
return rtc_to_rtmp_;
|
|
}
|
|
|
|
srs_utime_t MockAppConfig::get_rtc_stun_timeout(std::string vhost)
|
|
{
|
|
return 30 * SRS_UTIME_SECONDS; // Default 30 seconds timeout
|
|
}
|
|
|
|
bool MockAppConfig::get_rtc_stun_strict_check(std::string vhost)
|
|
{
|
|
return false; // Default to non-strict mode
|
|
}
|
|
|
|
std::string MockAppConfig::get_rtc_dtls_role(std::string vhost)
|
|
{
|
|
return "passive"; // Default DTLS role
|
|
}
|
|
|
|
std::string MockAppConfig::get_rtc_dtls_version(std::string vhost)
|
|
{
|
|
return "auto"; // Default DTLS version
|
|
}
|
|
|
|
SrsConfDirective *MockAppConfig::get_vhost_on_hls(std::string vhost)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
SrsConfDirective *MockAppConfig::get_vhost_on_hls_notify(std::string vhost)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
bool MockAppConfig::get_hls_enabled(std::string vhost)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool MockAppConfig::get_hls_enabled(SrsConfDirective *vhost)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool MockAppConfig::get_hls_use_fmp4(std::string vhost)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
std::string MockAppConfig::get_hls_entry_prefix(std::string vhost)
|
|
{
|
|
return "";
|
|
}
|
|
|
|
std::string MockAppConfig::get_hls_path(std::string vhost)
|
|
{
|
|
return "./objs/nginx/html";
|
|
}
|
|
|
|
std::string MockAppConfig::get_hls_m3u8_file(std::string vhost)
|
|
{
|
|
return "[app]/[stream].m3u8";
|
|
}
|
|
|
|
std::string MockAppConfig::get_hls_ts_file(std::string vhost)
|
|
{
|
|
return "[app]/[stream]-[seq].ts";
|
|
}
|
|
|
|
std::string MockAppConfig::get_hls_fmp4_file(std::string vhost)
|
|
{
|
|
return "[app]/[stream]-[seq].m4s";
|
|
}
|
|
|
|
std::string MockAppConfig::get_hls_init_file(std::string vhost)
|
|
{
|
|
return "[app]/[stream]/init.mp4";
|
|
}
|
|
|
|
bool MockAppConfig::get_hls_ts_floor(std::string vhost)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
srs_utime_t MockAppConfig::get_hls_fragment(std::string vhost)
|
|
{
|
|
return 10 * SRS_UTIME_SECONDS;
|
|
}
|
|
|
|
double MockAppConfig::get_hls_td_ratio(std::string vhost)
|
|
{
|
|
return 1.5;
|
|
}
|
|
|
|
double MockAppConfig::get_hls_aof_ratio(std::string vhost)
|
|
{
|
|
return 2.0;
|
|
}
|
|
|
|
srs_utime_t MockAppConfig::get_hls_window(std::string vhost)
|
|
{
|
|
return 60 * SRS_UTIME_SECONDS;
|
|
}
|
|
|
|
std::string MockAppConfig::get_hls_on_error(std::string vhost)
|
|
{
|
|
return "continue";
|
|
}
|
|
|
|
bool MockAppConfig::get_hls_cleanup(std::string vhost)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
srs_utime_t MockAppConfig::get_hls_dispose(std::string vhost)
|
|
{
|
|
return 120 * SRS_UTIME_SECONDS;
|
|
}
|
|
|
|
bool MockAppConfig::get_hls_wait_keyframe(std::string vhost)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool MockAppConfig::get_hls_keys(std::string vhost)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int MockAppConfig::get_hls_fragments_per_key(std::string vhost)
|
|
{
|
|
return 5;
|
|
}
|
|
|
|
std::string MockAppConfig::get_hls_key_file(std::string vhost)
|
|
{
|
|
return "[app]/[stream]-[seq].key";
|
|
}
|
|
|
|
std::string MockAppConfig::get_hls_key_file_path(std::string vhost)
|
|
{
|
|
return "./objs/nginx/html";
|
|
}
|
|
|
|
std::string MockAppConfig::get_hls_key_url(std::string vhost)
|
|
{
|
|
return "";
|
|
}
|
|
|
|
int MockAppConfig::get_vhost_hls_nb_notify(std::string vhost)
|
|
{
|
|
return 64;
|
|
}
|
|
|
|
bool MockAppConfig::get_vhost_hls_dts_directly(std::string vhost)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool MockAppConfig::get_hls_ctx_enabled(std::string vhost)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool MockAppConfig::get_hls_ts_ctx_enabled(std::string vhost)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool MockAppConfig::get_hls_recover(std::string vhost)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool MockAppConfig::get_forward_enabled(std::string vhost)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
SrsConfDirective *MockAppConfig::get_forwards(std::string vhost)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
srs_utime_t MockAppConfig::get_queue_length(std::string vhost)
|
|
{
|
|
return 30 * SRS_UTIME_SECONDS;
|
|
}
|
|
|
|
SrsConfDirective *MockAppConfig::get_forward_backend(std::string vhost)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
bool MockAppConfig::get_atc(std::string vhost)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int MockAppConfig::get_time_jitter(std::string vhost)
|
|
{
|
|
return SrsRtmpJitterAlgorithmFULL;
|
|
}
|
|
|
|
bool MockAppConfig::get_mix_correct(std::string vhost)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool MockAppConfig::try_annexb_first(std::string vhost)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool MockAppConfig::get_vhost_is_edge(std::string vhost)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool MockAppConfig::get_atc_auto(std::string vhost)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool MockAppConfig::get_reduce_sequence_header(std::string vhost)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool MockAppConfig::get_parse_sps(std::string vhost)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void MockAppConfig::set_http_hooks_enabled(bool enabled)
|
|
{
|
|
http_hooks_enabled_ = enabled;
|
|
}
|
|
|
|
void MockAppConfig::set_on_stop_urls(const std::vector<std::string> &urls)
|
|
{
|
|
clear_on_stop_directive();
|
|
if (!urls.empty()) {
|
|
on_stop_directive_ = new SrsConfDirective();
|
|
on_stop_directive_->name_ = "on_stop";
|
|
on_stop_directive_->args_ = urls;
|
|
}
|
|
}
|
|
|
|
void MockAppConfig::clear_on_stop_directive()
|
|
{
|
|
srs_freep(on_stop_directive_);
|
|
}
|
|
|
|
void MockAppConfig::set_on_unpublish_urls(const std::vector<std::string> &urls)
|
|
{
|
|
clear_on_unpublish_directive();
|
|
if (!urls.empty()) {
|
|
on_unpublish_directive_ = new SrsConfDirective();
|
|
on_unpublish_directive_->name_ = "on_unpublish";
|
|
on_unpublish_directive_->args_ = urls;
|
|
}
|
|
}
|
|
|
|
void MockAppConfig::clear_on_unpublish_directive()
|
|
{
|
|
srs_freep(on_unpublish_directive_);
|
|
}
|
|
|
|
void MockAppConfig::set_rtc_nack_enabled(bool enabled)
|
|
{
|
|
rtc_nack_enabled_ = enabled;
|
|
}
|
|
|
|
void MockAppConfig::set_rtc_nack_no_copy(bool no_copy)
|
|
{
|
|
rtc_nack_no_copy_ = no_copy;
|
|
}
|
|
|
|
void MockAppConfig::set_rtc_drop_for_pt(int pt)
|
|
{
|
|
rtc_drop_for_pt_ = pt;
|
|
}
|
|
|
|
void MockAppConfig::set_rtc_twcc_enabled(bool enabled)
|
|
{
|
|
rtc_twcc_enabled_ = enabled;
|
|
}
|
|
|
|
void MockAppConfig::set_srt_enabled(bool enabled)
|
|
{
|
|
srt_enabled_ = enabled;
|
|
}
|
|
|
|
void MockAppConfig::set_rtc_to_rtmp(bool enabled)
|
|
{
|
|
rtc_to_rtmp_ = enabled;
|
|
}
|
|
|
|
// Mock request implementation
|
|
MockRtcAsyncCallRequest::MockRtcAsyncCallRequest(std::string vhost, std::string app, std::string stream)
|
|
{
|
|
vhost_ = vhost;
|
|
app_ = app;
|
|
stream_ = stream;
|
|
}
|
|
|
|
MockRtcAsyncCallRequest::~MockRtcAsyncCallRequest()
|
|
{
|
|
}
|
|
|
|
ISrsRequest *MockRtcAsyncCallRequest::copy()
|
|
{
|
|
MockRtcAsyncCallRequest *cp = new MockRtcAsyncCallRequest(vhost_, app_, stream_);
|
|
return cp;
|
|
}
|
|
|
|
std::string MockRtcAsyncCallRequest::get_stream_url()
|
|
{
|
|
return "rtmp://" + vhost_ + "/" + app_ + "/" + stream_;
|
|
}
|
|
|
|
void MockRtcAsyncCallRequest::update_auth(ISrsRequest *req)
|
|
{
|
|
}
|
|
|
|
void MockRtcAsyncCallRequest::strip()
|
|
{
|
|
}
|
|
|
|
ISrsRequest *MockRtcAsyncCallRequest::as_http()
|
|
{
|
|
return this;
|
|
}
|
|
|
|
// Mock RTC source manager implementation
|
|
MockRtcSourceManager::MockRtcSourceManager()
|
|
{
|
|
initialize_error_ = srs_success;
|
|
fetch_or_create_error_ = srs_success;
|
|
initialize_count_ = 0;
|
|
fetch_or_create_count_ = 0;
|
|
mock_source_ = SrsSharedPtr<SrsRtcSource>(new SrsRtcSource());
|
|
}
|
|
|
|
MockRtcSourceManager::~MockRtcSourceManager()
|
|
{
|
|
srs_freep(initialize_error_);
|
|
srs_freep(fetch_or_create_error_);
|
|
}
|
|
|
|
srs_error_t MockRtcSourceManager::initialize()
|
|
{
|
|
initialize_count_++;
|
|
return srs_error_copy(initialize_error_);
|
|
}
|
|
|
|
srs_error_t MockRtcSourceManager::fetch_or_create(ISrsRequest *r, SrsSharedPtr<SrsRtcSource> &pps)
|
|
{
|
|
fetch_or_create_count_++;
|
|
if (fetch_or_create_error_ != srs_success) {
|
|
return srs_error_copy(fetch_or_create_error_);
|
|
}
|
|
pps = mock_source_;
|
|
return srs_success;
|
|
}
|
|
|
|
SrsSharedPtr<SrsRtcSource> MockRtcSourceManager::fetch(ISrsRequest *r)
|
|
{
|
|
return mock_source_;
|
|
}
|
|
|
|
void MockRtcSourceManager::set_initialize_error(srs_error_t err)
|
|
{
|
|
srs_freep(initialize_error_);
|
|
initialize_error_ = srs_error_copy(err);
|
|
}
|
|
|
|
void MockRtcSourceManager::set_fetch_or_create_error(srs_error_t err)
|
|
{
|
|
srs_freep(fetch_or_create_error_);
|
|
fetch_or_create_error_ = srs_error_copy(err);
|
|
}
|
|
|
|
void MockRtcSourceManager::reset()
|
|
{
|
|
srs_freep(initialize_error_);
|
|
srs_freep(fetch_or_create_error_);
|
|
initialize_error_ = srs_success;
|
|
fetch_or_create_error_ = srs_success;
|
|
initialize_count_ = 0;
|
|
fetch_or_create_count_ = 0;
|
|
}
|
|
|
|
// Mock statistic implementation
|
|
MockRtcStatistic::MockRtcStatistic()
|
|
{
|
|
on_client_error_ = srs_success;
|
|
on_client_count_ = 0;
|
|
on_disconnect_count_ = 0;
|
|
last_client_id_ = "";
|
|
last_client_req_ = NULL;
|
|
last_client_conn_ = NULL;
|
|
last_client_type_ = SrsRtmpConnUnknown;
|
|
}
|
|
|
|
MockRtcStatistic::~MockRtcStatistic()
|
|
{
|
|
srs_freep(on_client_error_);
|
|
}
|
|
|
|
void MockRtcStatistic::on_disconnect(std::string id, srs_error_t err)
|
|
{
|
|
on_disconnect_count_++;
|
|
}
|
|
|
|
srs_error_t MockRtcStatistic::on_client(std::string id, ISrsRequest *req, ISrsExpire *conn, SrsRtmpConnType type)
|
|
{
|
|
on_client_count_++;
|
|
last_client_id_ = id;
|
|
last_client_req_ = req;
|
|
last_client_conn_ = conn;
|
|
last_client_type_ = type;
|
|
return srs_error_copy(on_client_error_);
|
|
}
|
|
|
|
void MockRtcStatistic::set_on_client_error(srs_error_t err)
|
|
{
|
|
srs_freep(on_client_error_);
|
|
on_client_error_ = srs_error_copy(err);
|
|
}
|
|
|
|
void MockRtcStatistic::reset()
|
|
{
|
|
srs_freep(on_client_error_);
|
|
on_client_error_ = srs_success;
|
|
on_client_count_ = 0;
|
|
on_disconnect_count_ = 0;
|
|
last_client_id_ = "";
|
|
last_client_req_ = NULL;
|
|
last_client_conn_ = NULL;
|
|
last_client_type_ = SrsRtmpConnUnknown;
|
|
}
|
|
|
|
srs_error_t MockRtcStatistic::on_video_info(ISrsRequest *req, SrsVideoCodecId vcodec, int avc_profile, int avc_level, int width, int height)
|
|
{
|
|
return srs_success;
|
|
}
|
|
|
|
srs_error_t MockRtcStatistic::on_audio_info(ISrsRequest *req, SrsAudioCodecId acodec, SrsAudioSampleRate asample_rate, SrsAudioChannels asound_type, SrsAacObjectType aac_object)
|
|
{
|
|
return srs_success;
|
|
}
|
|
|
|
void MockRtcStatistic::on_stream_publish(ISrsRequest *req, std::string publisher_id)
|
|
{
|
|
// Do nothing in mock
|
|
}
|
|
|
|
void MockRtcStatistic::on_stream_close(ISrsRequest *req)
|
|
{
|
|
// Do nothing in mock
|
|
}
|
|
|
|
void MockRtcStatistic::kbps_add_delta(std::string id, ISrsKbpsDelta *delta)
|
|
{
|
|
// Do nothing in mock
|
|
}
|
|
|
|
void MockRtcStatistic::kbps_sample()
|
|
{
|
|
// Do nothing in mock
|
|
}
|
|
|
|
srs_error_t MockRtcStatistic::on_video_frames(ISrsRequest *req, int nb_frames)
|
|
{
|
|
return srs_success;
|
|
}
|
|
|
|
std::string MockRtcStatistic::server_id()
|
|
{
|
|
return "mock_server_id";
|
|
}
|
|
|
|
std::string MockRtcStatistic::service_id()
|
|
{
|
|
return "mock_service_id";
|
|
}
|
|
|
|
std::string MockRtcStatistic::service_pid()
|
|
{
|
|
return "mock_pid";
|
|
}
|
|
|
|
SrsStatisticVhost *MockRtcStatistic::find_vhost_by_id(std::string vid)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
SrsStatisticStream *MockRtcStatistic::find_stream(std::string sid)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
SrsStatisticStream *MockRtcStatistic::find_stream_by_url(std::string url)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
SrsStatisticClient *MockRtcStatistic::find_client(std::string client_id)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
srs_error_t MockRtcStatistic::dumps_vhosts(SrsJsonArray *arr)
|
|
{
|
|
return srs_success;
|
|
}
|
|
|
|
srs_error_t MockRtcStatistic::dumps_streams(SrsJsonArray *arr, int start, int count)
|
|
{
|
|
return srs_success;
|
|
}
|
|
|
|
srs_error_t MockRtcStatistic::dumps_clients(SrsJsonArray *arr, int start, int count)
|
|
{
|
|
return srs_success;
|
|
}
|
|
|
|
srs_error_t MockRtcStatistic::dumps_metrics(int64_t &send_bytes, int64_t &recv_bytes, int64_t &nstreams, int64_t &nclients, int64_t &total_nclients, int64_t &nerrs)
|
|
{
|
|
send_bytes = 0;
|
|
recv_bytes = 0;
|
|
nstreams = 0;
|
|
nclients = 0;
|
|
total_nclients = 0;
|
|
nerrs = 0;
|
|
return srs_success;
|
|
}
|
|
|
|
// Unit tests for SrsRtcAsyncCallOnStop::call()
|
|
VOID TEST(RtcAsyncCallOnStopTest, CallWithHttpHooksDisabled)
|
|
{
|
|
srs_error_t err;
|
|
|
|
// Create mock objects
|
|
MockAppConfig mock_config;
|
|
MockHttpHooks mock_hooks;
|
|
MockContext mock_context;
|
|
MockRtcAsyncCallRequest mock_request("test.vhost", "live", "stream1");
|
|
|
|
// Disable HTTP hooks
|
|
mock_config.set_http_hooks_enabled(false);
|
|
|
|
// Create SrsRtcAsyncCallOnStop with mocked dependencies
|
|
SrsContextId cid;
|
|
cid.set_value("test-context-id");
|
|
|
|
SrsUniquePtr<SrsRtcAsyncCallOnStop> async_call(new SrsRtcAsyncCallOnStop(cid, &mock_request));
|
|
|
|
// Replace the dependencies with mocks (private members are accessible due to #define private public)
|
|
async_call->hooks_ = &mock_hooks;
|
|
async_call->context_ = &mock_context;
|
|
async_call->config_ = &mock_config;
|
|
|
|
// Call should succeed but not invoke hooks since they're disabled
|
|
HELPER_EXPECT_SUCCESS(async_call->call());
|
|
|
|
// Verify no hooks were called
|
|
EXPECT_EQ(0, mock_hooks.on_stop_count_);
|
|
EXPECT_EQ(0, (int)mock_hooks.on_stop_calls_.size());
|
|
}
|
|
|
|
VOID TEST(RtcAsyncCallOnStopTest, CallWithNoOnStopConfiguration)
|
|
{
|
|
srs_error_t err;
|
|
|
|
// Create mock objects
|
|
MockAppConfig mock_config;
|
|
MockHttpHooks mock_hooks;
|
|
MockContext mock_context;
|
|
MockRtcAsyncCallRequest mock_request("test.vhost", "live", "stream1");
|
|
|
|
// Enable HTTP hooks but don't configure on_stop URLs
|
|
mock_config.set_http_hooks_enabled(true);
|
|
// on_stop_directive_ remains NULL
|
|
|
|
// Create SrsRtcAsyncCallOnStop with mocked dependencies
|
|
SrsContextId cid;
|
|
cid.set_value("test-context-id");
|
|
|
|
SrsUniquePtr<SrsRtcAsyncCallOnStop> async_call(new SrsRtcAsyncCallOnStop(cid, &mock_request));
|
|
|
|
// Replace the dependencies with mocks
|
|
async_call->hooks_ = &mock_hooks;
|
|
async_call->context_ = &mock_context;
|
|
async_call->config_ = &mock_config;
|
|
|
|
// Call should succeed but not invoke hooks since no URLs are configured
|
|
HELPER_EXPECT_SUCCESS(async_call->call());
|
|
|
|
// Verify no hooks were called
|
|
EXPECT_EQ(0, mock_hooks.on_stop_count_);
|
|
EXPECT_EQ(0, (int)mock_hooks.on_stop_calls_.size());
|
|
}
|
|
|
|
VOID TEST(RtcAsyncCallOnStopTest, CallWithSingleOnStopUrl)
|
|
{
|
|
srs_error_t err;
|
|
|
|
// Create mock objects
|
|
MockAppConfig mock_config;
|
|
MockHttpHooks mock_hooks;
|
|
MockContext mock_context;
|
|
MockRtcAsyncCallRequest mock_request("test.vhost", "live", "stream1");
|
|
|
|
// Enable HTTP hooks and configure single on_stop URL
|
|
mock_config.set_http_hooks_enabled(true);
|
|
std::vector<std::string> urls;
|
|
urls.push_back("http://callback.server.com/on_stop");
|
|
mock_config.set_on_stop_urls(urls);
|
|
|
|
// Create SrsRtcAsyncCallOnStop with mocked dependencies
|
|
SrsContextId cid;
|
|
cid.set_value("test-context-id");
|
|
|
|
SrsUniquePtr<SrsRtcAsyncCallOnStop> async_call(new SrsRtcAsyncCallOnStop(cid, &mock_request));
|
|
|
|
// Replace the dependencies with mocks
|
|
async_call->hooks_ = &mock_hooks;
|
|
async_call->context_ = &mock_context;
|
|
async_call->config_ = &mock_config;
|
|
|
|
// Call should succeed and invoke hooks
|
|
HELPER_EXPECT_SUCCESS(async_call->call());
|
|
|
|
// Verify hooks were called once
|
|
EXPECT_EQ(1, mock_hooks.on_stop_count_);
|
|
EXPECT_EQ(1, (int)mock_hooks.on_stop_calls_.size());
|
|
EXPECT_STREQ("http://callback.server.com/on_stop", mock_hooks.on_stop_calls_[0].first.c_str());
|
|
// Note: The request pointer will be different because SrsRtcAsyncCallOnStop creates a copy
|
|
EXPECT_TRUE(mock_hooks.on_stop_calls_[0].second != NULL);
|
|
}
|
|
|
|
VOID TEST(RtcAsyncCallOnStopTest, CallWithMultipleOnStopUrls)
|
|
{
|
|
srs_error_t err;
|
|
|
|
// Create mock objects
|
|
MockAppConfig mock_config;
|
|
MockHttpHooks mock_hooks;
|
|
MockContext mock_context;
|
|
MockRtcAsyncCallRequest mock_request("test.vhost", "live", "stream1");
|
|
|
|
// Enable HTTP hooks and configure multiple on_stop URLs
|
|
mock_config.set_http_hooks_enabled(true);
|
|
std::vector<std::string> urls;
|
|
urls.push_back("http://callback1.server.com/on_stop");
|
|
urls.push_back("http://callback2.server.com/on_stop");
|
|
urls.push_back("http://callback3.server.com/on_stop");
|
|
mock_config.set_on_stop_urls(urls);
|
|
|
|
// Create SrsRtcAsyncCallOnStop with mocked dependencies
|
|
SrsContextId cid;
|
|
cid.set_value("test-context-id");
|
|
|
|
SrsUniquePtr<SrsRtcAsyncCallOnStop> async_call(new SrsRtcAsyncCallOnStop(cid, &mock_request));
|
|
|
|
// Replace the dependencies with mocks
|
|
async_call->hooks_ = &mock_hooks;
|
|
async_call->context_ = &mock_context;
|
|
async_call->config_ = &mock_config;
|
|
|
|
// Call should succeed and invoke all hooks
|
|
HELPER_EXPECT_SUCCESS(async_call->call());
|
|
|
|
// Verify all hooks were called
|
|
EXPECT_EQ(3, mock_hooks.on_stop_count_);
|
|
EXPECT_EQ(3, (int)mock_hooks.on_stop_calls_.size());
|
|
EXPECT_STREQ("http://callback1.server.com/on_stop", mock_hooks.on_stop_calls_[0].first.c_str());
|
|
EXPECT_STREQ("http://callback2.server.com/on_stop", mock_hooks.on_stop_calls_[1].first.c_str());
|
|
EXPECT_STREQ("http://callback3.server.com/on_stop", mock_hooks.on_stop_calls_[2].first.c_str());
|
|
|
|
// All calls should use the same request object (but different from original due to copy)
|
|
for (int i = 0; i < 3; i++) {
|
|
// Note: The request pointer will be different because SrsRtcAsyncCallOnStop creates a copy
|
|
EXPECT_TRUE(mock_hooks.on_stop_calls_[i].second != NULL);
|
|
}
|
|
}
|
|
|
|
VOID TEST(RtcAsyncCallOnStopTest, CallWithContextSwitching)
|
|
{
|
|
srs_error_t err;
|
|
|
|
// Create mock objects
|
|
MockAppConfig mock_config;
|
|
MockHttpHooks mock_hooks;
|
|
MockContext mock_context;
|
|
MockRtcAsyncCallRequest mock_request("test.vhost", "live", "stream1");
|
|
|
|
// Enable HTTP hooks and configure on_stop URL
|
|
mock_config.set_http_hooks_enabled(true);
|
|
std::vector<std::string> urls;
|
|
urls.push_back("http://callback.server.com/on_stop");
|
|
mock_config.set_on_stop_urls(urls);
|
|
|
|
// Set up context IDs
|
|
SrsContextId original_cid;
|
|
original_cid.set_value("original-context-id");
|
|
SrsContextId new_cid;
|
|
new_cid.set_value("new-context-id");
|
|
|
|
// Set the mock context to return the original context ID initially
|
|
mock_context.set_current_id(original_cid);
|
|
|
|
// Create SrsRtcAsyncCallOnStop with the new context ID
|
|
SrsUniquePtr<SrsRtcAsyncCallOnStop> async_call(new SrsRtcAsyncCallOnStop(new_cid, &mock_request));
|
|
|
|
// Replace the dependencies with mocks
|
|
async_call->hooks_ = &mock_hooks;
|
|
async_call->context_ = &mock_context;
|
|
async_call->config_ = &mock_config;
|
|
|
|
// Call should succeed and perform context switching
|
|
HELPER_EXPECT_SUCCESS(async_call->call());
|
|
|
|
// Verify hooks were called
|
|
EXPECT_EQ(1, mock_hooks.on_stop_count_);
|
|
EXPECT_EQ(1, (int)mock_hooks.on_stop_calls_.size());
|
|
|
|
// Verify context was switched to the new context ID
|
|
EXPECT_EQ(0, mock_context.current_id_.compare(new_cid));
|
|
}
|
|
|
|
VOID TEST(RtcPlayStreamTest, InitializeSuccess)
|
|
{
|
|
srs_error_t err;
|
|
|
|
// Create mock objects
|
|
MockAppConfig mock_config;
|
|
MockRtcSourceManager mock_rtc_sources;
|
|
MockRtcStatistic mock_stat;
|
|
MockRtcAsyncCallRequest mock_request("test.vhost", "live", "stream1");
|
|
MockRtcAsyncTaskExecutor mock_async_executor;
|
|
MockExpire mock_expire;
|
|
MockRtcPacketSender mock_packet_sender;
|
|
|
|
// Create RTC play stream with mock interfaces
|
|
SrsContextId cid;
|
|
cid.set_value("test-play-stream-cid");
|
|
SrsUniquePtr<SrsRtcPlayStream> play_stream(new SrsRtcPlayStream(&mock_async_executor, &mock_expire, &mock_packet_sender, cid));
|
|
|
|
// Mock the dependencies by setting the private members
|
|
play_stream->config_ = &mock_config;
|
|
play_stream->rtc_sources_ = &mock_rtc_sources;
|
|
play_stream->stat_ = &mock_stat;
|
|
|
|
// Create track descriptions for testing
|
|
std::map<uint32_t, SrsRtcTrackDescription *> sub_relations;
|
|
|
|
// Create audio track description
|
|
SrsRtcTrackDescription *audio_desc = new SrsRtcTrackDescription();
|
|
audio_desc->type_ = "audio";
|
|
audio_desc->id_ = "audio-track-id";
|
|
audio_desc->ssrc_ = 12345;
|
|
audio_desc->is_active_ = true;
|
|
sub_relations[12345] = audio_desc;
|
|
|
|
// Create video track description
|
|
SrsRtcTrackDescription *video_desc = new SrsRtcTrackDescription();
|
|
video_desc->type_ = "video";
|
|
video_desc->id_ = "video-track-id";
|
|
video_desc->ssrc_ = 67890;
|
|
video_desc->is_active_ = true;
|
|
sub_relations[67890] = video_desc;
|
|
|
|
// Initialize should succeed
|
|
HELPER_EXPECT_SUCCESS(play_stream->initialize(&mock_request, sub_relations));
|
|
|
|
// Verify stat->on_client was called
|
|
EXPECT_EQ(1, mock_stat.on_client_count_);
|
|
EXPECT_STREQ(cid.c_str(), mock_stat.last_client_id_.c_str());
|
|
EXPECT_EQ(SrsRtcConnPlay, mock_stat.last_client_type_);
|
|
|
|
// Verify rtc_sources->fetch_or_create was called
|
|
EXPECT_EQ(1, mock_rtc_sources.fetch_or_create_count_);
|
|
|
|
// Verify tracks were created
|
|
EXPECT_EQ(1, (int)play_stream->audio_tracks_.size());
|
|
EXPECT_EQ(1, (int)play_stream->video_tracks_.size());
|
|
EXPECT_TRUE(play_stream->audio_tracks_.find(12345) != play_stream->audio_tracks_.end());
|
|
EXPECT_TRUE(play_stream->video_tracks_.find(67890) != play_stream->video_tracks_.end());
|
|
|
|
// Verify NACK configuration was applied
|
|
EXPECT_TRUE(play_stream->nack_enabled_);
|
|
EXPECT_FALSE(play_stream->nack_no_copy_);
|
|
|
|
// Verify tracks have NACK configuration applied
|
|
SrsRtcAudioSendTrack *audio_track = play_stream->audio_tracks_[12345];
|
|
SrsRtcVideoSendTrack *video_track = play_stream->video_tracks_[67890];
|
|
EXPECT_TRUE(audio_track != NULL);
|
|
EXPECT_TRUE(video_track != NULL);
|
|
|
|
// Clean up track descriptions
|
|
srs_freep(audio_desc);
|
|
srs_freep(video_desc);
|
|
}
|
|
|
|
VOID TEST(RtcPlayStreamTest, OnStreamChangeSuccess)
|
|
{
|
|
srs_error_t err;
|
|
|
|
// Create mock objects
|
|
MockAppConfig mock_config;
|
|
MockRtcSourceManager mock_rtc_sources;
|
|
MockRtcStatistic mock_stat;
|
|
MockRtcAsyncCallRequest mock_request("test.vhost", "live", "stream1");
|
|
MockRtcAsyncTaskExecutor mock_async_executor;
|
|
MockExpire mock_expire;
|
|
MockRtcPacketSender mock_packet_sender;
|
|
|
|
// Create RTC play stream with mock interfaces
|
|
SrsContextId cid;
|
|
cid.set_value("test-stream-change-cid");
|
|
SrsUniquePtr<SrsRtcPlayStream> play_stream(new SrsRtcPlayStream(&mock_async_executor, &mock_expire, &mock_packet_sender, cid));
|
|
|
|
// Mock the dependencies by setting the private members
|
|
play_stream->config_ = &mock_config;
|
|
play_stream->rtc_sources_ = &mock_rtc_sources;
|
|
play_stream->stat_ = &mock_stat;
|
|
|
|
// Create mock PLI worker and replace the real one
|
|
MockRtcPliWorker *mock_pli_worker = new MockRtcPliWorker(play_stream.get());
|
|
srs_freep(play_stream->pli_worker_); // Free the original PLI worker
|
|
play_stream->pli_worker_ = mock_pli_worker;
|
|
|
|
// Create initial track descriptions for testing
|
|
std::map<uint32_t, SrsRtcTrackDescription *> sub_relations;
|
|
|
|
// Create audio track description
|
|
SrsRtcTrackDescription *audio_desc = new SrsRtcTrackDescription();
|
|
audio_desc->type_ = "audio";
|
|
audio_desc->id_ = "audio-track-id";
|
|
audio_desc->ssrc_ = 12345;
|
|
audio_desc->is_active_ = true;
|
|
audio_desc->media_ = new SrsCodecPayload();
|
|
audio_desc->media_->pt_ = 111;
|
|
audio_desc->media_->pt_of_publisher_ = 111;
|
|
sub_relations[12345] = audio_desc;
|
|
|
|
// Create video track description
|
|
SrsRtcTrackDescription *video_desc = new SrsRtcTrackDescription();
|
|
video_desc->type_ = "video";
|
|
video_desc->id_ = "video-track-id";
|
|
video_desc->ssrc_ = 67890;
|
|
video_desc->is_active_ = true;
|
|
video_desc->media_ = new SrsCodecPayload();
|
|
video_desc->media_->pt_ = 96;
|
|
video_desc->media_->pt_of_publisher_ = 96;
|
|
sub_relations[67890] = video_desc;
|
|
|
|
// Initialize the play stream first
|
|
HELPER_EXPECT_SUCCESS(play_stream->initialize(&mock_request, sub_relations));
|
|
|
|
// Reset PLI worker to clear any initialization requests
|
|
mock_pli_worker->reset();
|
|
|
|
// Create source description for stream change
|
|
SrsRtcSourceDescription *source_desc = new SrsRtcSourceDescription();
|
|
|
|
// Create new audio track description with different payload type
|
|
SrsRtcTrackDescription *new_audio_desc = new SrsRtcTrackDescription();
|
|
new_audio_desc->type_ = "audio";
|
|
new_audio_desc->id_ = "new-audio-track-id";
|
|
new_audio_desc->ssrc_ = 54321; // Different SSRC
|
|
new_audio_desc->is_active_ = true;
|
|
new_audio_desc->media_ = new SrsCodecPayload();
|
|
new_audio_desc->media_->pt_ = 112; // Different payload type
|
|
new_audio_desc->media_->pt_of_publisher_ = 112;
|
|
source_desc->audio_track_desc_ = new_audio_desc;
|
|
|
|
// Create new video track description with different payload type
|
|
SrsRtcTrackDescription *new_video_desc = new SrsRtcTrackDescription();
|
|
new_video_desc->type_ = "video";
|
|
new_video_desc->id_ = "new-video-track-id";
|
|
new_video_desc->ssrc_ = 98765; // Different SSRC
|
|
new_video_desc->is_active_ = true;
|
|
new_video_desc->media_ = new SrsCodecPayload();
|
|
new_video_desc->media_->pt_ = 97; // Different payload type
|
|
new_video_desc->media_->pt_of_publisher_ = 97;
|
|
source_desc->video_track_descs_.push_back(new_video_desc);
|
|
|
|
// Call on_stream_change
|
|
play_stream->on_stream_change(source_desc);
|
|
|
|
// Verify PLI worker received keyframe requests
|
|
EXPECT_EQ(2, mock_pli_worker->get_keyframe_request_count());
|
|
EXPECT_TRUE(mock_pli_worker->has_keyframe_request(54321, cid)); // Audio SSRC
|
|
EXPECT_TRUE(mock_pli_worker->has_keyframe_request(98765, cid)); // Video SSRC
|
|
|
|
// Verify audio track was updated with new SSRC and payload type
|
|
EXPECT_EQ(1, (int)play_stream->audio_tracks_.size());
|
|
EXPECT_TRUE(play_stream->audio_tracks_.find(54321) != play_stream->audio_tracks_.end());
|
|
EXPECT_TRUE(play_stream->audio_tracks_.find(12345) == play_stream->audio_tracks_.end());
|
|
SrsRtcAudioSendTrack *updated_audio_track = play_stream->audio_tracks_[54321];
|
|
EXPECT_TRUE(updated_audio_track != NULL);
|
|
EXPECT_EQ(112, updated_audio_track->track_desc_->media_->pt_of_publisher_);
|
|
|
|
// Verify video track was updated with new SSRC and payload type
|
|
EXPECT_EQ(1, (int)play_stream->video_tracks_.size());
|
|
EXPECT_TRUE(play_stream->video_tracks_.find(98765) != play_stream->video_tracks_.end());
|
|
EXPECT_TRUE(play_stream->video_tracks_.find(67890) == play_stream->video_tracks_.end());
|
|
SrsRtcVideoSendTrack *updated_video_track = play_stream->video_tracks_[98765];
|
|
EXPECT_TRUE(updated_video_track != NULL);
|
|
EXPECT_EQ(97, updated_video_track->track_desc_->media_->pt_of_publisher_);
|
|
|
|
// Clean up
|
|
srs_freep(audio_desc);
|
|
srs_freep(video_desc);
|
|
srs_freep(source_desc);
|
|
}
|
|
|
|
// Mock RTC async task executor implementation
|
|
MockRtcAsyncTaskExecutor::MockRtcAsyncTaskExecutor()
|
|
{
|
|
exec_error_ = srs_success;
|
|
exec_count_ = 0;
|
|
last_task_ = NULL;
|
|
}
|
|
|
|
MockRtcAsyncTaskExecutor::~MockRtcAsyncTaskExecutor()
|
|
{
|
|
}
|
|
|
|
srs_error_t MockRtcAsyncTaskExecutor::exec_rtc_async_work(ISrsAsyncCallTask *t)
|
|
{
|
|
exec_count_++;
|
|
last_task_ = t;
|
|
return exec_error_;
|
|
}
|
|
|
|
void MockRtcAsyncTaskExecutor::set_exec_error(srs_error_t err)
|
|
{
|
|
exec_error_ = err;
|
|
}
|
|
|
|
void MockRtcAsyncTaskExecutor::reset()
|
|
{
|
|
exec_error_ = srs_success;
|
|
exec_count_ = 0;
|
|
last_task_ = NULL;
|
|
}
|
|
|
|
// Mock RTC packet sender implementation
|
|
MockRtcPacketSender::MockRtcPacketSender()
|
|
{
|
|
send_packet_error_ = srs_success;
|
|
send_packet_count_ = 0;
|
|
last_sent_packet_ = NULL;
|
|
}
|
|
|
|
MockRtcPacketSender::~MockRtcPacketSender()
|
|
{
|
|
}
|
|
|
|
srs_error_t MockRtcPacketSender::do_send_packet(SrsRtpPacket *pkt)
|
|
{
|
|
send_packet_count_++;
|
|
last_sent_packet_ = pkt;
|
|
return send_packet_error_;
|
|
}
|
|
|
|
void MockRtcPacketSender::set_send_packet_error(srs_error_t err)
|
|
{
|
|
send_packet_error_ = err;
|
|
}
|
|
|
|
void MockRtcPacketSender::reset()
|
|
{
|
|
send_packet_error_ = srs_success;
|
|
send_packet_count_ = 0;
|
|
last_sent_packet_ = NULL;
|
|
}
|
|
|
|
// Mock RTC send track implementation
|
|
MockRtcSendTrack::MockRtcSendTrack(ISrsRtcPacketSender *sender, SrsRtcTrackDescription *track_desc, bool is_audio)
|
|
: SrsRtcSendTrack(sender, track_desc, is_audio)
|
|
{
|
|
on_rtp_error_ = srs_success;
|
|
on_nack_error_ = srs_success;
|
|
on_rtp_count_ = 0;
|
|
on_nack_count_ = 0;
|
|
last_rtp_packet_ = NULL;
|
|
last_nack_packet_ = NULL;
|
|
nack_set_to_null_ = false;
|
|
}
|
|
|
|
MockRtcSendTrack::~MockRtcSendTrack()
|
|
{
|
|
}
|
|
|
|
srs_error_t MockRtcSendTrack::on_rtp(SrsRtpPacket *pkt)
|
|
{
|
|
on_rtp_count_++;
|
|
last_rtp_packet_ = pkt;
|
|
return on_rtp_error_;
|
|
}
|
|
|
|
srs_error_t MockRtcSendTrack::on_rtcp(SrsRtpPacket *pkt)
|
|
{
|
|
return srs_success;
|
|
}
|
|
|
|
srs_error_t MockRtcSendTrack::on_nack(SrsRtpPacket **ppkt)
|
|
{
|
|
on_nack_count_++;
|
|
last_nack_packet_ = ppkt;
|
|
if (nack_set_to_null_ && ppkt && *ppkt) {
|
|
*ppkt = NULL;
|
|
}
|
|
return on_nack_error_;
|
|
}
|
|
|
|
void MockRtcSendTrack::set_on_rtp_error(srs_error_t err)
|
|
{
|
|
on_rtp_error_ = err;
|
|
}
|
|
|
|
void MockRtcSendTrack::set_on_nack_error(srs_error_t err)
|
|
{
|
|
on_nack_error_ = err;
|
|
}
|
|
|
|
void MockRtcSendTrack::set_nack_set_to_null(bool v)
|
|
{
|
|
nack_set_to_null_ = v;
|
|
}
|
|
|
|
void MockRtcSendTrack::reset()
|
|
{
|
|
on_rtp_error_ = srs_success;
|
|
on_nack_error_ = srs_success;
|
|
on_rtp_count_ = 0;
|
|
on_nack_count_ = 0;
|
|
last_rtp_packet_ = NULL;
|
|
last_nack_packet_ = NULL;
|
|
nack_set_to_null_ = false;
|
|
}
|
|
|
|
VOID TEST(RtcPlayStreamTest, SendPacketBasic)
|
|
{
|
|
srs_error_t err;
|
|
|
|
// Create mock objects
|
|
MockAppConfig mock_config;
|
|
MockRtcSourceManager mock_rtc_sources;
|
|
MockRtcStatistic mock_stat;
|
|
MockRtcAsyncCallRequest mock_request("test.vhost", "live", "stream1");
|
|
MockRtcAsyncTaskExecutor mock_async_executor;
|
|
MockExpire mock_expire;
|
|
MockRtcPacketSender mock_packet_sender;
|
|
|
|
// Create RTC play stream with mock interfaces
|
|
SrsContextId cid;
|
|
cid.set_value("test-send-packet-cid");
|
|
SrsUniquePtr<SrsRtcPlayStream> play_stream(new SrsRtcPlayStream(&mock_async_executor, &mock_expire, &mock_packet_sender, cid));
|
|
|
|
// Mock the dependencies by setting the private members
|
|
play_stream->config_ = &mock_config;
|
|
play_stream->rtc_sources_ = &mock_rtc_sources;
|
|
play_stream->stat_ = &mock_stat;
|
|
|
|
// Create track descriptions for audio and video
|
|
SrsRtcTrackDescription *audio_desc = new SrsRtcTrackDescription();
|
|
audio_desc->type_ = "audio";
|
|
audio_desc->media_ = new SrsAudioPayload(111, "opus", 48000, 2);
|
|
audio_desc->ssrc_ = 12345;
|
|
|
|
SrsRtcTrackDescription *video_desc = new SrsRtcTrackDescription();
|
|
video_desc->type_ = "video";
|
|
video_desc->media_ = new SrsVideoPayload(97, "H264", 90000);
|
|
video_desc->ssrc_ = 67890;
|
|
|
|
// Create mock send tracks
|
|
MockRtcSendTrack *mock_audio_track = new MockRtcSendTrack(&mock_packet_sender, audio_desc, true);
|
|
MockRtcSendTrack *mock_video_track = new MockRtcSendTrack(&mock_packet_sender, video_desc, false);
|
|
|
|
// Add tracks to play stream's collections
|
|
play_stream->audio_tracks_[12345] = (SrsRtcAudioSendTrack *)mock_audio_track;
|
|
play_stream->video_tracks_[67890] = (SrsRtcVideoSendTrack *)mock_video_track;
|
|
|
|
// Test 1: Send audio packet
|
|
SrsRtpPacket *audio_pkt = new SrsRtpPacket();
|
|
audio_pkt->header_.set_ssrc(12345);
|
|
audio_pkt->header_.set_sequence(100);
|
|
audio_pkt->header_.set_payload_type(111);
|
|
audio_pkt->frame_type_ = SrsFrameTypeAudio; // Set frame type for is_audio() check
|
|
|
|
HELPER_EXPECT_SUCCESS(play_stream->send_packet(audio_pkt));
|
|
|
|
// Verify audio track was called
|
|
EXPECT_EQ(1, mock_audio_track->on_rtp_count_);
|
|
EXPECT_EQ(audio_pkt, mock_audio_track->last_rtp_packet_);
|
|
EXPECT_EQ(0, mock_video_track->on_rtp_count_);
|
|
|
|
// Test 2: Send video packet
|
|
SrsRtpPacket *video_pkt = new SrsRtpPacket();
|
|
video_pkt->header_.set_ssrc(67890);
|
|
video_pkt->header_.set_sequence(200);
|
|
video_pkt->header_.set_payload_type(97);
|
|
video_pkt->frame_type_ = SrsFrameTypeVideo; // Set frame type for is_audio() check
|
|
|
|
HELPER_EXPECT_SUCCESS(play_stream->send_packet(video_pkt));
|
|
|
|
// Verify video track was called
|
|
EXPECT_EQ(1, mock_video_track->on_rtp_count_);
|
|
EXPECT_EQ(video_pkt, mock_video_track->last_rtp_packet_);
|
|
EXPECT_EQ(1, mock_audio_track->on_rtp_count_); // Should remain 1
|
|
|
|
// Test 3: Test cache functionality - send same audio packet again
|
|
SrsRtpPacket *audio_pkt2 = new SrsRtpPacket();
|
|
audio_pkt2->header_.set_ssrc(12345);
|
|
audio_pkt2->header_.set_sequence(101);
|
|
audio_pkt2->header_.set_payload_type(111);
|
|
audio_pkt2->frame_type_ = SrsFrameTypeAudio;
|
|
|
|
HELPER_EXPECT_SUCCESS(play_stream->send_packet(audio_pkt2));
|
|
|
|
// Verify cache was used (track should be called again)
|
|
EXPECT_EQ(2, mock_audio_track->on_rtp_count_);
|
|
EXPECT_EQ(audio_pkt2, mock_audio_track->last_rtp_packet_);
|
|
|
|
// Test 4: Test unknown SSRC (should be ignored)
|
|
SrsRtpPacket *unknown_pkt = new SrsRtpPacket();
|
|
unknown_pkt->header_.set_ssrc(99999);
|
|
unknown_pkt->header_.set_sequence(300);
|
|
unknown_pkt->header_.set_payload_type(96);
|
|
unknown_pkt->frame_type_ = SrsFrameTypeVideo;
|
|
|
|
HELPER_EXPECT_SUCCESS(play_stream->send_packet(unknown_pkt));
|
|
|
|
// Verify no tracks were called for unknown SSRC
|
|
EXPECT_EQ(2, mock_audio_track->on_rtp_count_); // Should remain 2
|
|
EXPECT_EQ(1, mock_video_track->on_rtp_count_); // Should remain 1
|
|
|
|
// Clear the track references from play_stream before cleanup to avoid double-free
|
|
play_stream->audio_tracks_.clear();
|
|
play_stream->video_tracks_.clear();
|
|
|
|
// Clean up
|
|
srs_freep(audio_pkt);
|
|
srs_freep(video_pkt);
|
|
srs_freep(audio_pkt2);
|
|
|
|
srs_freep(unknown_pkt);
|
|
srs_freep(mock_audio_track);
|
|
srs_freep(mock_video_track);
|
|
srs_freep(audio_desc);
|
|
srs_freep(video_desc);
|
|
}
|
|
|
|
// Note: NACK functionality test would require more complex setup
|
|
// including proper track initialization and NACK buffer management.
|
|
// The basic send_packet functionality is covered by SendPacketBasic test.
|
|
|
|
// Mock RTCP classes implementations
|
|
MockRtcpCommon::MockRtcpCommon(uint8_t type)
|
|
{
|
|
mock_type_ = type;
|
|
}
|
|
|
|
MockRtcpCommon::~MockRtcpCommon()
|
|
{
|
|
}
|
|
|
|
uint8_t MockRtcpCommon::type() const
|
|
{
|
|
return mock_type_;
|
|
}
|
|
|
|
MockRtcpRR::MockRtcpRR(uint32_t sender_ssrc) : SrsRtcpRR(sender_ssrc)
|
|
{
|
|
}
|
|
|
|
MockRtcpRR::~MockRtcpRR()
|
|
{
|
|
}
|
|
|
|
MockRtcpNack::MockRtcpNack(uint32_t sender_ssrc) : SrsRtcpNack(sender_ssrc)
|
|
{
|
|
}
|
|
|
|
MockRtcpNack::~MockRtcpNack()
|
|
{
|
|
}
|
|
|
|
MockRtcpFbCommon::MockRtcpFbCommon(uint8_t rc)
|
|
{
|
|
mock_rc_ = rc;
|
|
}
|
|
|
|
MockRtcpFbCommon::~MockRtcpFbCommon()
|
|
{
|
|
}
|
|
|
|
uint8_t MockRtcpFbCommon::get_rc() const
|
|
{
|
|
return mock_rc_;
|
|
}
|
|
|
|
MockRtcpXr::MockRtcpXr(uint32_t ssrc) : SrsRtcpXr(ssrc)
|
|
{
|
|
}
|
|
|
|
MockRtcpXr::~MockRtcpXr()
|
|
{
|
|
}
|
|
|
|
// Mock RTC send track with NACK response capability for testing on_rtcp_nack - implementation
|
|
MockRtcSendTrackForNack::MockRtcSendTrackForNack(ISrsRtcPacketSender *sender, SrsRtcTrackDescription *track_desc, bool is_audio, uint32_t ssrc)
|
|
: SrsRtcSendTrack(sender, track_desc, is_audio)
|
|
{
|
|
on_recv_nack_error_ = srs_success;
|
|
on_recv_nack_count_ = 0;
|
|
test_ssrc_ = ssrc;
|
|
track_enabled_ = true;
|
|
}
|
|
|
|
MockRtcSendTrackForNack::~MockRtcSendTrackForNack()
|
|
{
|
|
}
|
|
|
|
srs_error_t MockRtcSendTrackForNack::on_rtp(SrsRtpPacket *pkt)
|
|
{
|
|
return srs_success;
|
|
}
|
|
|
|
srs_error_t MockRtcSendTrackForNack::on_rtcp(SrsRtpPacket *pkt)
|
|
{
|
|
return srs_success;
|
|
}
|
|
|
|
srs_error_t MockRtcSendTrackForNack::on_recv_nack(const std::vector<uint16_t> &lost_seqs)
|
|
{
|
|
on_recv_nack_count_++;
|
|
last_lost_seqs_ = lost_seqs;
|
|
return on_recv_nack_error_;
|
|
}
|
|
|
|
bool MockRtcSendTrackForNack::has_ssrc(uint32_t ssrc)
|
|
{
|
|
return ssrc == test_ssrc_;
|
|
}
|
|
|
|
bool MockRtcSendTrackForNack::get_track_status()
|
|
{
|
|
return track_desc_->is_active_;
|
|
}
|
|
|
|
void MockRtcSendTrackForNack::set_track_enabled(bool enabled)
|
|
{
|
|
track_desc_->is_active_ = enabled;
|
|
}
|
|
|
|
void MockRtcSendTrackForNack::set_on_recv_nack_error(srs_error_t err)
|
|
{
|
|
on_recv_nack_error_ = err;
|
|
}
|
|
|
|
void MockRtcSendTrackForNack::reset()
|
|
{
|
|
on_recv_nack_error_ = srs_success;
|
|
on_recv_nack_count_ = 0;
|
|
last_lost_seqs_.clear();
|
|
}
|
|
|
|
VOID TEST(RtcPlayStreamTest, OnRtcpDispatch)
|
|
{
|
|
srs_error_t err;
|
|
|
|
// Create mock objects
|
|
MockAppConfig mock_config;
|
|
MockRtcSourceManager mock_source_manager;
|
|
MockRtcStatistic mock_stat;
|
|
MockRtcAsyncTaskExecutor mock_executor;
|
|
MockRtcPacketSender mock_sender;
|
|
MockExpire mock_expire;
|
|
|
|
// Set up mock config
|
|
mock_config.set_http_hooks_enabled(false);
|
|
|
|
// Create a mock request
|
|
MockRtcAsyncCallRequest mock_request("__defaultVhost__", "live", "test");
|
|
|
|
// Create SrsRtcPlayStream
|
|
SrsUniquePtr<SrsRtcPlayStream> play_stream(new SrsRtcPlayStream(&mock_executor, &mock_expire, &mock_sender, _srs_context->get_id()));
|
|
|
|
// Test case 1: RTCP RR packet
|
|
if (true) {
|
|
MockRtcpRR rr_packet(0x12345678);
|
|
HELPER_EXPECT_SUCCESS(play_stream->on_rtcp(&rr_packet));
|
|
}
|
|
|
|
// Test case 2: RTCP NACK packet (rtpfb type)
|
|
if (true) {
|
|
MockRtcpNack nack_packet(0x87654321);
|
|
HELPER_EXPECT_SUCCESS(play_stream->on_rtcp(&nack_packet));
|
|
}
|
|
|
|
// Test case 3: RTCP PS feedback packet (psfb type)
|
|
if (true) {
|
|
MockRtcpFbCommon psfb_packet(kPLI); // PLI feedback
|
|
HELPER_EXPECT_SUCCESS(play_stream->on_rtcp(&psfb_packet));
|
|
}
|
|
|
|
// Test case 4: RTCP XR packet
|
|
if (true) {
|
|
MockRtcpXr xr_packet(0xABCDEF00);
|
|
HELPER_EXPECT_SUCCESS(play_stream->on_rtcp(&xr_packet));
|
|
}
|
|
|
|
// Test case 5: RTCP BYE packet (should return success)
|
|
if (true) {
|
|
MockRtcpCommon bye_packet(SrsRtcpType_bye);
|
|
HELPER_EXPECT_SUCCESS(play_stream->on_rtcp(&bye_packet));
|
|
}
|
|
|
|
// Test case 6: Unknown RTCP type (should return error)
|
|
if (true) {
|
|
MockRtcpCommon unknown_packet(255); // Invalid type
|
|
HELPER_EXPECT_FAILED(play_stream->on_rtcp(&unknown_packet));
|
|
}
|
|
}
|
|
|
|
VOID TEST(RtcPlayStreamTest, OnRtcpNack)
|
|
{
|
|
srs_error_t err;
|
|
|
|
// Create mock objects
|
|
MockAppConfig mock_config;
|
|
MockRtcSourceManager mock_source_manager;
|
|
MockRtcStatistic mock_stat;
|
|
MockRtcAsyncTaskExecutor mock_executor;
|
|
MockRtcPacketSender mock_sender;
|
|
MockExpire mock_expire;
|
|
|
|
// Set up mock config
|
|
mock_config.set_http_hooks_enabled(false);
|
|
|
|
// Create SrsRtcPlayStream
|
|
SrsUniquePtr<SrsRtcPlayStream> play_stream(new SrsRtcPlayStream(&mock_executor, &mock_expire, &mock_sender, _srs_context->get_id()));
|
|
|
|
// Test case 1: NACK disabled - should succeed but not process NACK
|
|
if (true) {
|
|
// Disable NACK in play stream (simulate nack_enabled_ = false)
|
|
play_stream->nack_enabled_ = false;
|
|
|
|
MockRtcpNack nack_packet(0x12345678);
|
|
nack_packet.set_media_ssrc(0x87654321);
|
|
nack_packet.add_lost_sn(100);
|
|
nack_packet.add_lost_sn(102);
|
|
nack_packet.add_lost_sn(105);
|
|
|
|
HELPER_EXPECT_SUCCESS(play_stream->on_rtcp_nack(&nack_packet));
|
|
}
|
|
|
|
// Test case 2: NACK enabled but no matching track - should return error
|
|
if (true) {
|
|
play_stream->nack_enabled_ = true;
|
|
|
|
MockRtcpNack nack_packet(0x12345678);
|
|
nack_packet.set_media_ssrc(0x99999999); // SSRC that doesn't match any track
|
|
nack_packet.add_lost_sn(200);
|
|
|
|
HELPER_EXPECT_FAILED(play_stream->on_rtcp_nack(&nack_packet));
|
|
}
|
|
|
|
// Test case 3: NACK enabled with matching audio track - should succeed
|
|
if (true) {
|
|
play_stream->nack_enabled_ = true;
|
|
|
|
// Create track descriptions
|
|
SrsRtcTrackDescription *audio_desc = new SrsRtcTrackDescription();
|
|
audio_desc->type_ = "audio";
|
|
audio_desc->ssrc_ = 0x11111111;
|
|
audio_desc->is_active_ = true;
|
|
|
|
// Create mock audio track
|
|
MockRtcSendTrackForNack *mock_audio_track = new MockRtcSendTrackForNack(&mock_sender, audio_desc, true, 0x11111111);
|
|
mock_audio_track->set_track_enabled(true);
|
|
|
|
// Add track to play stream
|
|
play_stream->audio_tracks_[0x11111111] = (SrsRtcAudioSendTrack *)mock_audio_track;
|
|
|
|
// Create NACK packet for audio track
|
|
MockRtcpNack nack_packet(0x12345678);
|
|
nack_packet.set_media_ssrc(0x11111111);
|
|
nack_packet.add_lost_sn(300);
|
|
nack_packet.add_lost_sn(301);
|
|
|
|
HELPER_EXPECT_SUCCESS(play_stream->on_rtcp_nack(&nack_packet));
|
|
|
|
// Verify the track received the NACK
|
|
EXPECT_EQ(1, mock_audio_track->on_recv_nack_count_);
|
|
EXPECT_EQ(2, mock_audio_track->last_lost_seqs_.size());
|
|
EXPECT_EQ(300, mock_audio_track->last_lost_seqs_[0]);
|
|
EXPECT_EQ(301, mock_audio_track->last_lost_seqs_[1]);
|
|
|
|
// Clear track from play stream before freeing
|
|
play_stream->audio_tracks_.clear();
|
|
srs_freep(mock_audio_track);
|
|
srs_freep(audio_desc);
|
|
}
|
|
|
|
// Test case 4: NACK enabled with matching video track - should succeed
|
|
if (true) {
|
|
play_stream->nack_enabled_ = true;
|
|
|
|
// Create track descriptions
|
|
SrsRtcTrackDescription *video_desc = new SrsRtcTrackDescription();
|
|
video_desc->type_ = "video";
|
|
video_desc->ssrc_ = 0x22222222;
|
|
video_desc->is_active_ = true;
|
|
|
|
// Create mock video track
|
|
MockRtcSendTrackForNack *mock_video_track = new MockRtcSendTrackForNack(&mock_sender, video_desc, false, 0x22222222);
|
|
mock_video_track->set_track_enabled(true);
|
|
|
|
// Add track to play stream
|
|
play_stream->video_tracks_[0x22222222] = (SrsRtcVideoSendTrack *)mock_video_track;
|
|
|
|
// Create NACK packet for video track
|
|
MockRtcpNack nack_packet(0x12345678);
|
|
nack_packet.set_media_ssrc(0x22222222);
|
|
nack_packet.add_lost_sn(400);
|
|
nack_packet.add_lost_sn(402);
|
|
nack_packet.add_lost_sn(405);
|
|
|
|
HELPER_EXPECT_SUCCESS(play_stream->on_rtcp_nack(&nack_packet));
|
|
|
|
// Verify the track received the NACK
|
|
EXPECT_EQ(1, mock_video_track->on_recv_nack_count_);
|
|
EXPECT_EQ(3, mock_video_track->last_lost_seqs_.size());
|
|
EXPECT_EQ(400, mock_video_track->last_lost_seqs_[0]);
|
|
EXPECT_EQ(402, mock_video_track->last_lost_seqs_[1]);
|
|
EXPECT_EQ(405, mock_video_track->last_lost_seqs_[2]);
|
|
|
|
// Clear track from play stream before freeing
|
|
play_stream->video_tracks_.clear();
|
|
srs_freep(mock_video_track);
|
|
srs_freep(video_desc);
|
|
}
|
|
|
|
// Test case 5: NACK with disabled track - should not find track
|
|
if (true) {
|
|
play_stream->nack_enabled_ = true;
|
|
|
|
// Create track descriptions
|
|
SrsRtcTrackDescription *audio_desc = new SrsRtcTrackDescription();
|
|
audio_desc->type_ = "audio";
|
|
audio_desc->ssrc_ = 0x33333333;
|
|
audio_desc->is_active_ = true;
|
|
|
|
// Create mock audio track but disable it
|
|
MockRtcSendTrackForNack *mock_audio_track = new MockRtcSendTrackForNack(&mock_sender, audio_desc, true, 0x33333333);
|
|
mock_audio_track->set_track_enabled(false); // Disabled track
|
|
|
|
// Add track to play stream
|
|
play_stream->audio_tracks_[0x33333333] = (SrsRtcAudioSendTrack *)mock_audio_track;
|
|
|
|
// Create NACK packet for disabled track
|
|
MockRtcpNack nack_packet(0x12345678);
|
|
nack_packet.set_media_ssrc(0x33333333);
|
|
nack_packet.add_lost_sn(500);
|
|
|
|
HELPER_EXPECT_FAILED(play_stream->on_rtcp_nack(&nack_packet));
|
|
|
|
// Verify the track did not receive the NACK
|
|
EXPECT_EQ(0, mock_audio_track->on_recv_nack_count_);
|
|
|
|
// Clear track from play stream before freeing
|
|
play_stream->audio_tracks_.clear();
|
|
srs_freep(mock_audio_track);
|
|
srs_freep(audio_desc);
|
|
}
|
|
|
|
// Test case 6: NACK with track error - should propagate error
|
|
if (true) {
|
|
play_stream->nack_enabled_ = true;
|
|
|
|
// Create track descriptions
|
|
SrsRtcTrackDescription *video_desc = new SrsRtcTrackDescription();
|
|
video_desc->type_ = "video";
|
|
video_desc->ssrc_ = 0x44444444;
|
|
video_desc->is_active_ = true;
|
|
|
|
// Create mock video track that returns error
|
|
MockRtcSendTrackForNack *mock_video_track = new MockRtcSendTrackForNack(&mock_sender, video_desc, false, 0x44444444);
|
|
mock_video_track->set_track_enabled(true);
|
|
mock_video_track->set_on_recv_nack_error(srs_error_new(ERROR_RTC_STUN, "mock nack error"));
|
|
|
|
// Add track to play stream
|
|
play_stream->video_tracks_[0x44444444] = (SrsRtcVideoSendTrack *)mock_video_track;
|
|
|
|
// Create NACK packet for error track
|
|
MockRtcpNack nack_packet(0x12345678);
|
|
nack_packet.set_media_ssrc(0x44444444);
|
|
nack_packet.add_lost_sn(600);
|
|
|
|
HELPER_EXPECT_FAILED(play_stream->on_rtcp_nack(&nack_packet));
|
|
|
|
// Verify the track received the NACK but returned error
|
|
EXPECT_EQ(1, mock_video_track->on_recv_nack_count_);
|
|
|
|
// Clear track from play stream before freeing
|
|
play_stream->video_tracks_.clear();
|
|
srs_freep(mock_video_track);
|
|
srs_freep(video_desc);
|
|
}
|
|
}
|
|
|
|
VOID TEST(RtcPlayStreamTest, DoRequestKeyframe)
|
|
{
|
|
srs_error_t err;
|
|
|
|
// Create mock objects
|
|
MockAppConfig mock_config;
|
|
MockRtcSourceManager mock_rtc_sources;
|
|
MockRtcStatistic mock_stat;
|
|
MockRtcAsyncCallRequest mock_request("test.vhost", "live", "stream1");
|
|
MockRtcAsyncTaskExecutor mock_async_executor;
|
|
MockExpire mock_expire;
|
|
MockRtcPacketSender mock_packet_sender;
|
|
|
|
// Create RTC play stream with mock interfaces
|
|
SrsContextId cid;
|
|
cid.set_value("test-do-request-keyframe-cid");
|
|
SrsUniquePtr<SrsRtcPlayStream> play_stream(new SrsRtcPlayStream(&mock_async_executor, &mock_expire, &mock_packet_sender, cid));
|
|
|
|
// Mock the dependencies by setting the private members
|
|
play_stream->config_ = &mock_config;
|
|
play_stream->rtc_sources_ = &mock_rtc_sources;
|
|
play_stream->stat_ = &mock_stat;
|
|
|
|
// Initialize the play stream
|
|
std::map<uint32_t, SrsRtcTrackDescription *> sub_relations;
|
|
HELPER_EXPECT_SUCCESS(play_stream->initialize(&mock_request, sub_relations));
|
|
|
|
// Test 1: No publisher stream - should return success without doing anything
|
|
{
|
|
uint32_t test_ssrc = 12345;
|
|
SrsContextId test_cid;
|
|
test_cid.set_value("test-subscriber-cid");
|
|
|
|
HELPER_EXPECT_SUCCESS(play_stream->do_request_keyframe(test_ssrc, test_cid));
|
|
}
|
|
|
|
// Test 2: With publisher stream - should call request_keyframe on publisher
|
|
{
|
|
// Create mock publisher stream
|
|
MockRtcPublishStream *mock_publisher = new MockRtcPublishStream();
|
|
SrsContextId publisher_cid;
|
|
publisher_cid.set_value("test-publisher-cid");
|
|
mock_publisher->set_context_id(publisher_cid);
|
|
|
|
// Set the publisher stream in the source
|
|
play_stream->source_->set_publish_stream(mock_publisher);
|
|
|
|
uint32_t test_ssrc = 67890;
|
|
SrsContextId test_cid;
|
|
test_cid.set_value("test-subscriber-cid-2");
|
|
|
|
// Call do_request_keyframe
|
|
HELPER_EXPECT_SUCCESS(play_stream->do_request_keyframe(test_ssrc, test_cid));
|
|
|
|
// Verify that request_keyframe was called on the publisher
|
|
EXPECT_EQ(1, mock_publisher->request_keyframe_count_);
|
|
EXPECT_EQ(test_ssrc, mock_publisher->last_keyframe_ssrc_);
|
|
EXPECT_EQ(0, test_cid.compare(mock_publisher->last_keyframe_cid_));
|
|
|
|
// Clean up - the source will handle the publisher cleanup
|
|
play_stream->source_->set_publish_stream(NULL);
|
|
srs_freep(mock_publisher);
|
|
}
|
|
}
|
|
|
|
// Mock RTCP sender implementation
|
|
MockRtcRtcpSender::MockRtcRtcpSender()
|
|
{
|
|
is_sender_started_ = true;
|
|
is_sender_twcc_enabled_ = false;
|
|
send_rtcp_rr_error_ = srs_success;
|
|
send_rtcp_xr_rrtr_error_ = srs_success;
|
|
send_periodic_twcc_error_ = srs_success;
|
|
send_rtcp_rr_count_ = 0;
|
|
send_rtcp_xr_rrtr_count_ = 0;
|
|
send_periodic_twcc_count_ = 0;
|
|
}
|
|
|
|
MockRtcRtcpSender::~MockRtcRtcpSender()
|
|
{
|
|
srs_freep(send_rtcp_rr_error_);
|
|
srs_freep(send_rtcp_xr_rrtr_error_);
|
|
srs_freep(send_periodic_twcc_error_);
|
|
}
|
|
|
|
bool MockRtcRtcpSender::is_sender_started()
|
|
{
|
|
return is_sender_started_;
|
|
}
|
|
|
|
srs_error_t MockRtcRtcpSender::send_rtcp_rr()
|
|
{
|
|
send_rtcp_rr_count_++;
|
|
return srs_error_copy(send_rtcp_rr_error_);
|
|
}
|
|
|
|
srs_error_t MockRtcRtcpSender::send_rtcp_xr_rrtr()
|
|
{
|
|
send_rtcp_xr_rrtr_count_++;
|
|
return srs_error_copy(send_rtcp_xr_rrtr_error_);
|
|
}
|
|
|
|
bool MockRtcRtcpSender::is_sender_twcc_enabled()
|
|
{
|
|
return is_sender_twcc_enabled_;
|
|
}
|
|
|
|
srs_error_t MockRtcRtcpSender::send_periodic_twcc()
|
|
{
|
|
send_periodic_twcc_count_++;
|
|
return srs_error_copy(send_periodic_twcc_error_);
|
|
}
|
|
|
|
void MockRtcRtcpSender::set_sender_started(bool started)
|
|
{
|
|
is_sender_started_ = started;
|
|
}
|
|
|
|
void MockRtcRtcpSender::set_sender_twcc_enabled(bool enabled)
|
|
{
|
|
is_sender_twcc_enabled_ = enabled;
|
|
}
|
|
|
|
void MockRtcRtcpSender::set_send_rtcp_rr_error(srs_error_t err)
|
|
{
|
|
srs_freep(send_rtcp_rr_error_);
|
|
send_rtcp_rr_error_ = srs_error_copy(err);
|
|
}
|
|
|
|
void MockRtcRtcpSender::set_send_rtcp_xr_rrtr_error(srs_error_t err)
|
|
{
|
|
srs_freep(send_rtcp_xr_rrtr_error_);
|
|
send_rtcp_xr_rrtr_error_ = srs_error_copy(err);
|
|
}
|
|
|
|
void MockRtcRtcpSender::set_send_periodic_twcc_error(srs_error_t err)
|
|
{
|
|
srs_freep(send_periodic_twcc_error_);
|
|
send_periodic_twcc_error_ = srs_error_copy(err);
|
|
}
|
|
|
|
void MockRtcRtcpSender::reset()
|
|
{
|
|
is_sender_started_ = true;
|
|
is_sender_twcc_enabled_ = false;
|
|
srs_freep(send_rtcp_rr_error_);
|
|
srs_freep(send_rtcp_xr_rrtr_error_);
|
|
srs_freep(send_periodic_twcc_error_);
|
|
send_rtcp_rr_error_ = srs_success;
|
|
send_rtcp_xr_rrtr_error_ = srs_success;
|
|
send_periodic_twcc_error_ = srs_success;
|
|
send_rtcp_rr_count_ = 0;
|
|
send_rtcp_xr_rrtr_count_ = 0;
|
|
send_periodic_twcc_count_ = 0;
|
|
}
|
|
|
|
// Mock RTC packet receiver implementation
|
|
MockRtcPacketReceiver::MockRtcPacketReceiver()
|
|
{
|
|
send_rtcp_rr_error_ = srs_success;
|
|
send_rtcp_xr_rrtr_error_ = srs_success;
|
|
send_rtcp_error_ = srs_success;
|
|
send_rtcp_fb_pli_error_ = srs_success;
|
|
send_rtcp_rr_count_ = 0;
|
|
send_rtcp_xr_rrtr_count_ = 0;
|
|
send_rtcp_count_ = 0;
|
|
send_rtcp_fb_pli_count_ = 0;
|
|
check_send_nacks_count_ = 0;
|
|
}
|
|
|
|
MockRtcPacketReceiver::~MockRtcPacketReceiver()
|
|
{
|
|
}
|
|
|
|
srs_error_t MockRtcPacketReceiver::send_rtcp_rr(uint32_t ssrc, SrsRtpRingBuffer *rtp_queue, const uint64_t &last_send_systime, const SrsNtp &last_send_ntp)
|
|
{
|
|
send_rtcp_rr_count_++;
|
|
return send_rtcp_rr_error_;
|
|
}
|
|
|
|
srs_error_t MockRtcPacketReceiver::send_rtcp_xr_rrtr(uint32_t ssrc)
|
|
{
|
|
send_rtcp_xr_rrtr_count_++;
|
|
return send_rtcp_xr_rrtr_error_;
|
|
}
|
|
|
|
void MockRtcPacketReceiver::check_send_nacks(SrsRtpNackForReceiver *nack, uint32_t ssrc, uint32_t &sent_nacks, uint32_t &timeout_nacks)
|
|
{
|
|
check_send_nacks_count_++;
|
|
sent_nacks = 0;
|
|
timeout_nacks = 0;
|
|
}
|
|
|
|
srs_error_t MockRtcPacketReceiver::send_rtcp(char *data, int nb_data)
|
|
{
|
|
send_rtcp_count_++;
|
|
return send_rtcp_error_;
|
|
}
|
|
|
|
srs_error_t MockRtcPacketReceiver::send_rtcp_fb_pli(uint32_t ssrc, const SrsContextId &cid_of_subscriber)
|
|
{
|
|
send_rtcp_fb_pli_count_++;
|
|
return send_rtcp_fb_pli_error_;
|
|
}
|
|
|
|
void MockRtcPacketReceiver::set_send_rtcp_rr_error(srs_error_t err)
|
|
{
|
|
send_rtcp_rr_error_ = err;
|
|
}
|
|
|
|
void MockRtcPacketReceiver::set_send_rtcp_xr_rrtr_error(srs_error_t err)
|
|
{
|
|
send_rtcp_xr_rrtr_error_ = err;
|
|
}
|
|
|
|
void MockRtcPacketReceiver::set_send_rtcp_error(srs_error_t err)
|
|
{
|
|
send_rtcp_error_ = err;
|
|
}
|
|
|
|
void MockRtcPacketReceiver::set_send_rtcp_fb_pli_error(srs_error_t err)
|
|
{
|
|
send_rtcp_fb_pli_error_ = err;
|
|
}
|
|
|
|
void MockRtcPacketReceiver::reset()
|
|
{
|
|
send_rtcp_rr_error_ = srs_success;
|
|
send_rtcp_xr_rrtr_error_ = srs_success;
|
|
send_rtcp_error_ = srs_success;
|
|
send_rtcp_fb_pli_error_ = srs_success;
|
|
send_rtcp_rr_count_ = 0;
|
|
send_rtcp_xr_rrtr_count_ = 0;
|
|
send_rtcp_count_ = 0;
|
|
send_rtcp_fb_pli_count_ = 0;
|
|
check_send_nacks_count_ = 0;
|
|
}
|
|
|
|
VOID TEST(SrsRtcPublishRtcpTimerTest, OnTimer)
|
|
{
|
|
srs_error_t err;
|
|
|
|
// Create mock RTCP sender
|
|
MockRtcRtcpSender *mock_sender = new MockRtcRtcpSender();
|
|
|
|
// Create timer with mock sender
|
|
SrsUniquePtr<SrsRtcPublishRtcpTimer> timer(new SrsRtcPublishRtcpTimer(mock_sender));
|
|
|
|
// Test 1: Normal operation - sender started, both RTCP calls succeed
|
|
{
|
|
mock_sender->reset();
|
|
mock_sender->set_sender_started(true);
|
|
|
|
HELPER_EXPECT_SUCCESS(timer->on_timer(100 * SRS_UTIME_MILLISECONDS));
|
|
|
|
// Verify both RTCP methods were called
|
|
EXPECT_EQ(1, mock_sender->send_rtcp_rr_count_);
|
|
EXPECT_EQ(1, mock_sender->send_rtcp_xr_rrtr_count_);
|
|
}
|
|
|
|
// Test 2: Sender not started - should return success but not call RTCP methods
|
|
{
|
|
mock_sender->reset();
|
|
mock_sender->set_sender_started(false);
|
|
|
|
HELPER_EXPECT_SUCCESS(timer->on_timer(100 * SRS_UTIME_MILLISECONDS));
|
|
|
|
// Verify RTCP methods were not called
|
|
EXPECT_EQ(0, mock_sender->send_rtcp_rr_count_);
|
|
EXPECT_EQ(0, mock_sender->send_rtcp_xr_rrtr_count_);
|
|
}
|
|
|
|
// Test 3: send_rtcp_rr fails - should continue and call send_rtcp_xr_rrtr
|
|
{
|
|
mock_sender->reset();
|
|
mock_sender->set_sender_started(true);
|
|
mock_sender->set_send_rtcp_rr_error(srs_error_new(ERROR_RTC_RTP_MUXER, "mock rr error"));
|
|
|
|
HELPER_EXPECT_SUCCESS(timer->on_timer(100 * SRS_UTIME_MILLISECONDS));
|
|
|
|
// Verify both methods were called despite RR error
|
|
EXPECT_EQ(1, mock_sender->send_rtcp_rr_count_);
|
|
EXPECT_EQ(1, mock_sender->send_rtcp_xr_rrtr_count_);
|
|
}
|
|
|
|
// Test 4: send_rtcp_xr_rrtr fails - should still return success
|
|
{
|
|
mock_sender->reset();
|
|
mock_sender->set_sender_started(true);
|
|
mock_sender->set_send_rtcp_xr_rrtr_error(srs_error_new(ERROR_RTC_RTP_MUXER, "mock xr error"));
|
|
|
|
HELPER_EXPECT_SUCCESS(timer->on_timer(100 * SRS_UTIME_MILLISECONDS));
|
|
|
|
// Verify both methods were called
|
|
EXPECT_EQ(1, mock_sender->send_rtcp_rr_count_);
|
|
EXPECT_EQ(1, mock_sender->send_rtcp_xr_rrtr_count_);
|
|
}
|
|
|
|
// Test 5: Both RTCP methods fail - should still return success
|
|
{
|
|
mock_sender->reset();
|
|
mock_sender->set_sender_started(true);
|
|
mock_sender->set_send_rtcp_rr_error(srs_error_new(ERROR_RTC_RTP_MUXER, "mock rr error"));
|
|
mock_sender->set_send_rtcp_xr_rrtr_error(srs_error_new(ERROR_RTC_RTP_MUXER, "mock xr error"));
|
|
|
|
HELPER_EXPECT_SUCCESS(timer->on_timer(100 * SRS_UTIME_MILLISECONDS));
|
|
|
|
// Verify both methods were called despite errors
|
|
EXPECT_EQ(1, mock_sender->send_rtcp_rr_count_);
|
|
EXPECT_EQ(1, mock_sender->send_rtcp_xr_rrtr_count_);
|
|
}
|
|
|
|
srs_freep(mock_sender);
|
|
}
|
|
|
|
VOID TEST(SrsRtcPublishTwccTimerTest, OnTimer)
|
|
{
|
|
srs_error_t err;
|
|
|
|
// Create mock RTCP sender
|
|
MockRtcRtcpSender *mock_sender = new MockRtcRtcpSender();
|
|
|
|
// Create timer with mock sender
|
|
SrsUniquePtr<SrsRtcPublishTwccTimer> timer(new SrsRtcPublishTwccTimer(mock_sender));
|
|
|
|
// Test 1: Normal operation - sender started, TWCC enabled, circuit breaker not critical
|
|
if (true) {
|
|
mock_sender->reset();
|
|
mock_sender->set_sender_started(true);
|
|
mock_sender->set_sender_twcc_enabled(true);
|
|
|
|
// Mock circuit breaker to not be in critical state
|
|
MockCircuitBreaker mock_circuit_breaker;
|
|
mock_circuit_breaker.hybrid_critical_water_level_ = false;
|
|
ISrsCircuitBreaker *original_circuit_breaker = _srs_circuit_breaker;
|
|
_srs_circuit_breaker = &mock_circuit_breaker;
|
|
|
|
HELPER_EXPECT_SUCCESS(timer->on_timer(100 * SRS_UTIME_MILLISECONDS));
|
|
|
|
// Verify send_periodic_twcc was called
|
|
EXPECT_EQ(1, mock_sender->send_periodic_twcc_count_);
|
|
|
|
// Restore original circuit breaker
|
|
_srs_circuit_breaker = original_circuit_breaker;
|
|
}
|
|
|
|
// Test 2: Sender not started - should return early without calling TWCC methods
|
|
if (true) {
|
|
mock_sender->reset();
|
|
mock_sender->set_sender_started(false);
|
|
mock_sender->set_sender_twcc_enabled(true);
|
|
|
|
HELPER_EXPECT_SUCCESS(timer->on_timer(100 * SRS_UTIME_MILLISECONDS));
|
|
|
|
// Verify no TWCC methods were called
|
|
EXPECT_EQ(0, mock_sender->send_periodic_twcc_count_);
|
|
}
|
|
|
|
// Test 3: Sender started but TWCC disabled - should return early without calling send_periodic_twcc
|
|
if (true) {
|
|
mock_sender->reset();
|
|
mock_sender->set_sender_started(true);
|
|
mock_sender->set_sender_twcc_enabled(false);
|
|
|
|
HELPER_EXPECT_SUCCESS(timer->on_timer(100 * SRS_UTIME_MILLISECONDS));
|
|
|
|
// Verify send_periodic_twcc was not called
|
|
EXPECT_EQ(0, mock_sender->send_periodic_twcc_count_);
|
|
}
|
|
|
|
// Test 4: Circuit breaker in critical state - should return early without calling send_periodic_twcc
|
|
if (true) {
|
|
// Mock circuit breaker to be in critical state - must be set BEFORE creating timer
|
|
MockCircuitBreaker mock_circuit_breaker;
|
|
mock_circuit_breaker.hybrid_critical_water_level_ = true;
|
|
ISrsCircuitBreaker *original_circuit_breaker = _srs_circuit_breaker;
|
|
_srs_circuit_breaker = &mock_circuit_breaker;
|
|
|
|
// Create new timer with the mock circuit breaker
|
|
SrsUniquePtr<SrsRtcPublishTwccTimer> timer_with_mock_cb(new SrsRtcPublishTwccTimer(mock_sender));
|
|
|
|
mock_sender->reset();
|
|
mock_sender->set_sender_started(true);
|
|
mock_sender->set_sender_twcc_enabled(true);
|
|
|
|
HELPER_EXPECT_SUCCESS(timer_with_mock_cb->on_timer(100 * SRS_UTIME_MILLISECONDS));
|
|
|
|
// Verify send_periodic_twcc was not called due to circuit breaker
|
|
EXPECT_EQ(0, mock_sender->send_periodic_twcc_count_);
|
|
|
|
// Restore original circuit breaker
|
|
_srs_circuit_breaker = original_circuit_breaker;
|
|
}
|
|
|
|
// Test 5: send_periodic_twcc returns error - should handle error gracefully and continue
|
|
if (true) {
|
|
mock_sender->reset();
|
|
mock_sender->set_sender_started(true);
|
|
mock_sender->set_sender_twcc_enabled(true);
|
|
mock_sender->set_send_periodic_twcc_error(srs_error_new(ERROR_RTC_RTP_MUXER, "mock twcc error"));
|
|
|
|
// Mock circuit breaker to not be in critical state
|
|
MockCircuitBreaker mock_circuit_breaker;
|
|
mock_circuit_breaker.hybrid_critical_water_level_ = false;
|
|
ISrsCircuitBreaker *original_circuit_breaker = _srs_circuit_breaker;
|
|
_srs_circuit_breaker = &mock_circuit_breaker;
|
|
|
|
// Should still return success even when send_periodic_twcc fails
|
|
HELPER_EXPECT_SUCCESS(timer->on_timer(100 * SRS_UTIME_MILLISECONDS));
|
|
|
|
// Verify send_periodic_twcc was called despite error
|
|
EXPECT_EQ(1, mock_sender->send_periodic_twcc_count_);
|
|
|
|
// Restore original circuit breaker
|
|
_srs_circuit_breaker = original_circuit_breaker;
|
|
}
|
|
|
|
srs_freep(mock_sender);
|
|
}
|
|
|
|
// Unit tests for SrsRtcAsyncCallOnUnpublish::call()
|
|
VOID TEST(RtcAsyncCallOnUnpublishTest, CallWithHttpHooksDisabled)
|
|
{
|
|
srs_error_t err;
|
|
|
|
// Create mock objects
|
|
MockAppConfig mock_config;
|
|
MockHttpHooks mock_hooks;
|
|
MockRtcAsyncCallRequest mock_request("test.vhost", "live", "stream1");
|
|
|
|
// Disable HTTP hooks
|
|
mock_config.set_http_hooks_enabled(false);
|
|
|
|
// Create SrsRtcAsyncCallOnUnpublish with mocked dependencies
|
|
SrsContextId cid;
|
|
cid.set_value("test-context-id");
|
|
|
|
SrsUniquePtr<SrsRtcAsyncCallOnUnpublish> async_call(new SrsRtcAsyncCallOnUnpublish(cid, &mock_request));
|
|
|
|
// Replace the dependencies with mocks (private members are accessible due to #define private public)
|
|
async_call->hooks_ = &mock_hooks;
|
|
async_call->config_ = &mock_config;
|
|
|
|
// Call should succeed but not invoke hooks since they're disabled
|
|
HELPER_EXPECT_SUCCESS(async_call->call());
|
|
|
|
// Verify no hooks were called
|
|
EXPECT_EQ(0, mock_hooks.on_unpublish_count_);
|
|
EXPECT_EQ(0, (int)mock_hooks.on_unpublish_calls_.size());
|
|
}
|
|
|
|
VOID TEST(RtcAsyncCallOnUnpublishTest, CallWithNoOnUnpublishConfiguration)
|
|
{
|
|
srs_error_t err;
|
|
|
|
// Create mock objects
|
|
MockAppConfig mock_config;
|
|
MockHttpHooks mock_hooks;
|
|
MockRtcAsyncCallRequest mock_request("test.vhost", "live", "stream1");
|
|
|
|
// Enable HTTP hooks but don't configure on_unpublish URLs
|
|
mock_config.set_http_hooks_enabled(true);
|
|
// on_unpublish_directive_ remains NULL
|
|
|
|
// Create SrsRtcAsyncCallOnUnpublish with mocked dependencies
|
|
SrsContextId cid;
|
|
cid.set_value("test-context-id");
|
|
|
|
SrsUniquePtr<SrsRtcAsyncCallOnUnpublish> async_call(new SrsRtcAsyncCallOnUnpublish(cid, &mock_request));
|
|
|
|
// Replace the dependencies with mocks
|
|
async_call->hooks_ = &mock_hooks;
|
|
async_call->config_ = &mock_config;
|
|
|
|
// Call should succeed but not invoke hooks since no URLs are configured
|
|
HELPER_EXPECT_SUCCESS(async_call->call());
|
|
|
|
// Verify no hooks were called
|
|
EXPECT_EQ(0, mock_hooks.on_unpublish_count_);
|
|
EXPECT_EQ(0, (int)mock_hooks.on_unpublish_calls_.size());
|
|
}
|
|
|
|
VOID TEST(RtcAsyncCallOnUnpublishTest, CallWithSingleOnUnpublishUrl)
|
|
{
|
|
srs_error_t err;
|
|
|
|
// Create mock objects
|
|
MockAppConfig mock_config;
|
|
MockHttpHooks mock_hooks;
|
|
MockRtcAsyncCallRequest mock_request("test.vhost", "live", "stream1");
|
|
|
|
// Enable HTTP hooks and configure single on_unpublish URL
|
|
mock_config.set_http_hooks_enabled(true);
|
|
std::vector<std::string> urls;
|
|
urls.push_back("http://callback.server.com/on_unpublish");
|
|
mock_config.set_on_unpublish_urls(urls);
|
|
|
|
// Create SrsRtcAsyncCallOnUnpublish with mocked dependencies
|
|
SrsContextId cid;
|
|
cid.set_value("test-context-id");
|
|
|
|
SrsUniquePtr<SrsRtcAsyncCallOnUnpublish> async_call(new SrsRtcAsyncCallOnUnpublish(cid, &mock_request));
|
|
|
|
// Replace the dependencies with mocks
|
|
async_call->hooks_ = &mock_hooks;
|
|
async_call->config_ = &mock_config;
|
|
|
|
// Call should succeed and invoke hooks
|
|
HELPER_EXPECT_SUCCESS(async_call->call());
|
|
|
|
// Verify hooks were called once
|
|
EXPECT_EQ(1, mock_hooks.on_unpublish_count_);
|
|
EXPECT_EQ(1, (int)mock_hooks.on_unpublish_calls_.size());
|
|
EXPECT_STREQ("http://callback.server.com/on_unpublish", mock_hooks.on_unpublish_calls_[0].first.c_str());
|
|
// Note: The request pointer will be different because SrsRtcAsyncCallOnUnpublish creates a copy
|
|
EXPECT_TRUE(mock_hooks.on_unpublish_calls_[0].second != NULL);
|
|
}
|
|
|
|
VOID TEST(RtcAsyncCallOnUnpublishTest, CallWithMultipleOnUnpublishUrls)
|
|
{
|
|
srs_error_t err;
|
|
|
|
// Create mock objects
|
|
MockAppConfig mock_config;
|
|
MockHttpHooks mock_hooks;
|
|
MockRtcAsyncCallRequest mock_request("test.vhost", "live", "stream1");
|
|
|
|
// Enable HTTP hooks and configure multiple on_unpublish URLs
|
|
mock_config.set_http_hooks_enabled(true);
|
|
std::vector<std::string> urls;
|
|
urls.push_back("http://callback1.server.com/on_unpublish");
|
|
urls.push_back("http://callback2.server.com/on_unpublish");
|
|
urls.push_back("http://callback3.server.com/on_unpublish");
|
|
mock_config.set_on_unpublish_urls(urls);
|
|
|
|
// Create SrsRtcAsyncCallOnUnpublish with mocked dependencies
|
|
SrsContextId cid;
|
|
cid.set_value("test-context-id");
|
|
|
|
SrsUniquePtr<SrsRtcAsyncCallOnUnpublish> async_call(new SrsRtcAsyncCallOnUnpublish(cid, &mock_request));
|
|
|
|
// Replace the dependencies with mocks
|
|
async_call->hooks_ = &mock_hooks;
|
|
async_call->config_ = &mock_config;
|
|
|
|
// Call should succeed and invoke all hooks
|
|
HELPER_EXPECT_SUCCESS(async_call->call());
|
|
|
|
// Verify all hooks were called
|
|
EXPECT_EQ(3, mock_hooks.on_unpublish_count_);
|
|
EXPECT_EQ(3, (int)mock_hooks.on_unpublish_calls_.size());
|
|
EXPECT_STREQ("http://callback1.server.com/on_unpublish", mock_hooks.on_unpublish_calls_[0].first.c_str());
|
|
EXPECT_STREQ("http://callback2.server.com/on_unpublish", mock_hooks.on_unpublish_calls_[1].first.c_str());
|
|
EXPECT_STREQ("http://callback3.server.com/on_unpublish", mock_hooks.on_unpublish_calls_[2].first.c_str());
|
|
|
|
// All calls should use the same request object (but different from original due to copy)
|
|
for (int i = 0; i < 3; i++) {
|
|
// Note: The request pointer will be different because SrsRtcAsyncCallOnUnpublish creates a copy
|
|
EXPECT_TRUE(mock_hooks.on_unpublish_calls_[i].second != NULL);
|
|
}
|
|
}
|
|
|
|
VOID TEST(RtcAsyncCallOnUnpublishTest, CallWithContextSwitching)
|
|
{
|
|
srs_error_t err;
|
|
|
|
// Create mock objects
|
|
MockAppConfig mock_config;
|
|
MockHttpHooks mock_hooks;
|
|
MockRtcAsyncCallRequest mock_request("test.vhost", "live", "stream1");
|
|
|
|
// Enable HTTP hooks and configure on_unpublish URL
|
|
mock_config.set_http_hooks_enabled(true);
|
|
std::vector<std::string> urls;
|
|
urls.push_back("http://callback.server.com/on_unpublish");
|
|
mock_config.set_on_unpublish_urls(urls);
|
|
|
|
// Set up context IDs
|
|
SrsContextId original_cid;
|
|
original_cid.set_value("original-context-id");
|
|
SrsContextId new_cid;
|
|
new_cid.set_value("new-context-id");
|
|
|
|
// Create SrsRtcAsyncCallOnUnpublish with the new context ID
|
|
SrsUniquePtr<SrsRtcAsyncCallOnUnpublish> async_call(new SrsRtcAsyncCallOnUnpublish(new_cid, &mock_request));
|
|
|
|
// Replace the dependencies with mocks
|
|
async_call->hooks_ = &mock_hooks;
|
|
async_call->config_ = &mock_config;
|
|
|
|
// Call should succeed and perform context switching
|
|
HELPER_EXPECT_SUCCESS(async_call->call());
|
|
|
|
// Verify hooks were called
|
|
EXPECT_EQ(1, mock_hooks.on_unpublish_count_);
|
|
EXPECT_EQ(1, (int)mock_hooks.on_unpublish_calls_.size());
|
|
|
|
// Note: Context switching verification is not as straightforward for SrsRtcAsyncCallOnUnpublish
|
|
// because it uses _srs_context directly instead of a member variable like SrsRtcAsyncCallOnStop
|
|
}
|
|
|
|
// Mock live source manager implementation
|
|
MockLiveSourceManager::MockLiveSourceManager()
|
|
{
|
|
fetch_or_create_error_ = srs_success;
|
|
fetch_or_create_count_ = 0;
|
|
can_publish_ = true;
|
|
|
|
// Create a mock live source
|
|
mock_source_ = SrsSharedPtr<SrsLiveSource>(new MockLiveSource());
|
|
}
|
|
|
|
MockLiveSourceManager::~MockLiveSourceManager()
|
|
{
|
|
srs_freep(fetch_or_create_error_);
|
|
}
|
|
|
|
srs_error_t MockLiveSourceManager::fetch_or_create(ISrsRequest *r, SrsSharedPtr<SrsLiveSource> &pps)
|
|
{
|
|
fetch_or_create_count_++;
|
|
if (fetch_or_create_error_ != srs_success) {
|
|
return srs_error_copy(fetch_or_create_error_);
|
|
}
|
|
pps = mock_source_;
|
|
return srs_success;
|
|
}
|
|
|
|
SrsSharedPtr<SrsLiveSource> MockLiveSourceManager::fetch(ISrsRequest *r)
|
|
{
|
|
return mock_source_;
|
|
}
|
|
|
|
void MockLiveSourceManager::dispose()
|
|
{
|
|
// Mock implementation - no-op for testing
|
|
}
|
|
|
|
srs_error_t MockLiveSourceManager::initialize()
|
|
{
|
|
// Mock implementation - always succeeds
|
|
return srs_success;
|
|
}
|
|
|
|
void MockLiveSourceManager::set_fetch_or_create_error(srs_error_t err)
|
|
{
|
|
srs_freep(fetch_or_create_error_);
|
|
fetch_or_create_error_ = srs_error_copy(err);
|
|
}
|
|
|
|
void MockLiveSourceManager::set_can_publish(bool can_publish)
|
|
{
|
|
can_publish_ = can_publish;
|
|
if (mock_source_.get()) {
|
|
MockLiveSource *mock_live_source = dynamic_cast<MockLiveSource *>(mock_source_.get());
|
|
if (mock_live_source) {
|
|
mock_live_source->set_can_publish(can_publish);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MockLiveSourceManager::reset()
|
|
{
|
|
srs_freep(fetch_or_create_error_);
|
|
fetch_or_create_error_ = srs_success;
|
|
fetch_or_create_count_ = 0;
|
|
can_publish_ = true;
|
|
}
|
|
|
|
// Mock live source implementation
|
|
MockLiveSource::MockLiveSource()
|
|
{
|
|
can_publish_result_ = true;
|
|
}
|
|
|
|
MockLiveSource::~MockLiveSource()
|
|
{
|
|
}
|
|
|
|
bool MockLiveSource::can_publish(bool is_edge)
|
|
{
|
|
return can_publish_result_;
|
|
}
|
|
|
|
void MockLiveSource::set_can_publish(bool can_publish)
|
|
{
|
|
can_publish_result_ = can_publish;
|
|
}
|
|
|
|
srs_error_t MockLiveSource::on_publish()
|
|
{
|
|
// Mock implementation - just return success
|
|
return srs_success;
|
|
}
|
|
|
|
srs_error_t MockLiveSource::on_edge_start_publish()
|
|
{
|
|
// Mock implementation - just return success
|
|
return srs_success;
|
|
}
|
|
|
|
// Mock SRT source implementation
|
|
MockSrtSource::MockSrtSource()
|
|
{
|
|
can_publish_result_ = true;
|
|
}
|
|
|
|
MockSrtSource::~MockSrtSource()
|
|
{
|
|
}
|
|
|
|
bool MockSrtSource::can_publish()
|
|
{
|
|
return can_publish_result_;
|
|
}
|
|
|
|
void MockSrtSource::set_can_publish(bool can_publish)
|
|
{
|
|
can_publish_result_ = can_publish;
|
|
}
|
|
|
|
// Mock SRT source manager implementation
|
|
MockSrtSourceManager::MockSrtSourceManager()
|
|
{
|
|
initialize_error_ = srs_success;
|
|
fetch_or_create_error_ = srs_success;
|
|
initialize_count_ = 0;
|
|
fetch_or_create_count_ = 0;
|
|
can_publish_ = true;
|
|
|
|
// Create a mock SRT source
|
|
mock_source_ = SrsSharedPtr<SrsSrtSource>(new MockSrtSource());
|
|
}
|
|
|
|
MockSrtSourceManager::~MockSrtSourceManager()
|
|
{
|
|
srs_freep(initialize_error_);
|
|
srs_freep(fetch_or_create_error_);
|
|
}
|
|
|
|
srs_error_t MockSrtSourceManager::initialize()
|
|
{
|
|
initialize_count_++;
|
|
return srs_error_copy(initialize_error_);
|
|
}
|
|
|
|
srs_error_t MockSrtSourceManager::fetch_or_create(ISrsRequest *r, SrsSharedPtr<SrsSrtSource> &pps)
|
|
{
|
|
fetch_or_create_count_++;
|
|
if (fetch_or_create_error_ != srs_success) {
|
|
return srs_error_copy(fetch_or_create_error_);
|
|
}
|
|
pps = mock_source_;
|
|
return srs_success;
|
|
}
|
|
|
|
SrsSharedPtr<SrsSrtSource> MockSrtSourceManager::fetch(ISrsRequest *r)
|
|
{
|
|
return mock_source_;
|
|
}
|
|
|
|
void MockSrtSourceManager::set_initialize_error(srs_error_t err)
|
|
{
|
|
srs_freep(initialize_error_);
|
|
initialize_error_ = srs_error_copy(err);
|
|
}
|
|
|
|
void MockSrtSourceManager::set_fetch_or_create_error(srs_error_t err)
|
|
{
|
|
srs_freep(fetch_or_create_error_);
|
|
fetch_or_create_error_ = srs_error_copy(err);
|
|
}
|
|
|
|
void MockSrtSourceManager::set_can_publish(bool can_publish)
|
|
{
|
|
can_publish_ = can_publish;
|
|
if (mock_source_.get()) {
|
|
MockSrtSource *mock_srt_source = dynamic_cast<MockSrtSource *>(mock_source_.get());
|
|
if (mock_srt_source) {
|
|
mock_srt_source->set_can_publish(can_publish);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MockSrtSourceManager::reset()
|
|
{
|
|
srs_freep(initialize_error_);
|
|
srs_freep(fetch_or_create_error_);
|
|
initialize_error_ = srs_success;
|
|
fetch_or_create_error_ = srs_success;
|
|
initialize_count_ = 0;
|
|
fetch_or_create_count_ = 0;
|
|
can_publish_ = true;
|
|
}
|
|
|
|
VOID TEST(RtcPublishStreamTest, Initialize)
|
|
{
|
|
srs_error_t err;
|
|
|
|
// Create mock objects
|
|
MockRtcStatistic mock_stat;
|
|
MockAppConfig mock_config;
|
|
MockRtcSourceManager mock_rtc_sources;
|
|
MockLiveSourceManager mock_live_sources;
|
|
MockSrtSourceManager mock_srt_sources;
|
|
MockRtcPacketReceiver mock_receiver;
|
|
MockRtcAsyncCallRequest mock_request("test.vhost", "live", "stream1");
|
|
MockRtcAsyncTaskExecutor mock_exec;
|
|
MockExpire mock_expire;
|
|
|
|
// Create SrsRtcPublishStream with mock dependencies
|
|
SrsContextId cid;
|
|
cid.set_value("test-publish-stream-id");
|
|
|
|
SrsUniquePtr<SrsRtcPublishStream> publish_stream(new SrsRtcPublishStream(&mock_exec, &mock_expire, &mock_receiver, cid));
|
|
|
|
// Set mock dependencies
|
|
publish_stream->stat_ = &mock_stat;
|
|
publish_stream->config_ = &mock_config;
|
|
publish_stream->rtc_sources_ = &mock_rtc_sources;
|
|
publish_stream->live_sources_ = &mock_live_sources;
|
|
publish_stream->srt_sources_ = &mock_srt_sources;
|
|
|
|
// Create stream description with audio and video tracks
|
|
SrsUniquePtr<SrsRtcSourceDescription> stream_desc(new SrsRtcSourceDescription());
|
|
|
|
// Add audio track description
|
|
stream_desc->audio_track_desc_ = new SrsRtcTrackDescription();
|
|
stream_desc->audio_track_desc_->type_ = "audio";
|
|
stream_desc->audio_track_desc_->id_ = "audio-track-1";
|
|
stream_desc->audio_track_desc_->ssrc_ = 12345;
|
|
|
|
// Add video track description with TWCC extension
|
|
SrsRtcTrackDescription *video_track = new SrsRtcTrackDescription();
|
|
video_track->type_ = "video";
|
|
video_track->id_ = "video-track-1";
|
|
video_track->ssrc_ = 67890;
|
|
video_track->add_rtp_extension_desc(1, "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01");
|
|
stream_desc->video_track_descs_.push_back(video_track);
|
|
|
|
// Configure mock config
|
|
mock_config.set_rtc_nack_enabled(true);
|
|
mock_config.set_rtc_nack_no_copy(false);
|
|
mock_config.set_rtc_drop_for_pt(0);
|
|
mock_config.set_rtc_twcc_enabled(true);
|
|
mock_config.set_srt_enabled(true);
|
|
mock_config.set_rtc_to_rtmp(true);
|
|
|
|
// Test successful initialization
|
|
HELPER_EXPECT_SUCCESS(publish_stream->initialize(&mock_request, stream_desc.get()));
|
|
|
|
// Verify mock calls
|
|
EXPECT_EQ(1, mock_stat.on_client_count_);
|
|
EXPECT_EQ(1, mock_rtc_sources.fetch_or_create_count_);
|
|
EXPECT_EQ(1, mock_live_sources.fetch_or_create_count_);
|
|
EXPECT_EQ(1, mock_srt_sources.fetch_or_create_count_);
|
|
}
|
|
|
|
VOID TEST(SrsRtcPublishStreamTest, OnTwccSuccess)
|
|
{
|
|
srs_error_t err;
|
|
|
|
// Create mock objects
|
|
MockRtcAsyncTaskExecutor mock_exec;
|
|
MockRtcExpire mock_expire;
|
|
MockRtcPacketReceiver mock_receiver;
|
|
SrsContextId cid;
|
|
cid.set_value("test-twcc-stream-id");
|
|
|
|
// Create SrsRtcPublishStream with mock dependencies
|
|
SrsUniquePtr<SrsRtcPublishStream> publish_stream(new SrsRtcPublishStream(&mock_exec, &mock_expire, &mock_receiver, cid));
|
|
|
|
// Test on_twcc with a typical sequence number
|
|
uint16_t test_sn = 12345;
|
|
HELPER_EXPECT_SUCCESS(publish_stream->on_twcc(test_sn));
|
|
|
|
// Test on_twcc with different sequence numbers to verify it handles multiple packets
|
|
HELPER_EXPECT_SUCCESS(publish_stream->on_twcc(12346));
|
|
HELPER_EXPECT_SUCCESS(publish_stream->on_twcc(12347));
|
|
|
|
// Test duplicate sequence number should fail
|
|
HELPER_EXPECT_FAILED(publish_stream->on_twcc(12345));
|
|
}
|