// // Copyright (c) 2013-2025 The SRS Authors // // SPDX-License-Identifier: MIT // #include using namespace std; #include #include #include #include #include #include #include #include #include // 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 transport(new TestableSecurityTransport(&mock_network, &mock_dtls)); SrsUniquePtr 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 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 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 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 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 transport(new TestableSecurityTransport(&mock_network, &mock_dtls)); SrsUniquePtr 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 transport(new TestableSecurityTransport(&mock_network, &mock_dtls)); SrsUniquePtr 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 transport(new TestableSecurityTransport(&mock_network, &mock_dtls)); SrsUniquePtr 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 transport(new TestableSecurityTransport(&mock_network, &mock_dtls)); SrsUniquePtr 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 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 transport(new TestableSecurityTransport(&mock_network, &mock_dtls)); // Initialize first SrsUniquePtr 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 transport(new SrsSemiSecurityTransport(&mock_network)); // Test inherited initialize method SrsUniquePtr 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 transport(new SrsPlaintextTransport(&mock_network)); // initialize should always return success without doing anything SrsUniquePtr 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 publish_stream(new SrsRtcPublishStream(&mock_exec, &mock_expire, &mock_receiver, cid)); // Create mock track descriptions SrsUniquePtr video_track_desc(new SrsRtcTrackDescription()); video_track_desc->type_ = "video"; video_track_desc->id_ = "video_track_1"; video_track_desc->ssrc_ = 12345; SrsUniquePtr 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 publish_stream(new SrsRtcPublishStream(&mock_exec, &mock_expire, &mock_receiver, cid)); // Create mock track descriptions SrsUniquePtr 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 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 publish_stream(new SrsRtcPublishStream(&mock_exec, &mock_expire, &mock_receiver, cid)); // Create multiple video track descriptions SrsUniquePtr video_track_desc1(new SrsRtcTrackDescription()); video_track_desc1->type_ = "video"; video_track_desc1->id_ = "video_track_1"; video_track_desc1->ssrc_ = 12345; SrsUniquePtr 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 audio_track_desc1(new SrsRtcTrackDescription()); audio_track_desc1->type_ = "audio"; audio_track_desc1->id_ = "audio_track_1"; audio_track_desc1->ssrc_ = 67890; SrsUniquePtr 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 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 transport(new SrsPlaintextTransport(&mock_network)); // Initialize first SrsUniquePtr 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 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 >::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 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 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 &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 &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 &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(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 &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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 &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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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(new MockLiveSource()); } MockLiveSourceManager::~MockLiveSourceManager() { srs_freep(fetch_or_create_error_); } srs_error_t MockLiveSourceManager::fetch_or_create(ISrsRequest *r, SrsSharedPtr &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 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(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(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 &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 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(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 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 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 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)); }