diff --git a/trunk/src/utest/srs_utest_protocol3.cpp b/trunk/src/utest/srs_utest_protocol3.cpp index a0a22c5c2..fd4d96aae 100644 --- a/trunk/src/utest/srs_utest_protocol3.cpp +++ b/trunk/src/utest/srs_utest_protocol3.cpp @@ -7,6 +7,7 @@ using namespace std; +#include #include #include #include @@ -22,14 +23,18 @@ using namespace std; #include #include #include +#include #include #include #include +#include +#include #include #include #include extern bool srs_is_valid_jsonp_callback(std::string callback); +extern uint32_t srs_crc32_ieee(const void *buf, int size, uint32_t previous); VOID TEST(ProtocolHttpTest, JsonpCallbackName) { @@ -486,20 +491,6 @@ VOID TEST(ProtocolRtmpConnTest, SrsBasicRtmpClientBasic) // which we don't have in unit tests. The method exists and will be tested in integration tests. } -VOID TEST(ProtocolStTest, SrsStInitDestroy) -{ - // Test ST initialization and destruction - // Note: We can't easily test the full ST functionality without proper setup - // but we can test that the functions exist and don't crash - - srs_error_t err = srs_st_init(); - if (err == srs_success) { - // If init succeeds, test destroy - srs_st_destroy(); - } - // If init fails, that's also acceptable in test environment -} - VOID TEST(ProtocolStTest, SrsStSocketBasic) { // Test basic socket operations - these should exist and not crash @@ -517,22 +508,6 @@ VOID TEST(ProtocolStTest, SrsStSocketBasic) EXPECT_FALSE(is_not_never_timeout); } -VOID TEST(ProtocolConnTest, SrsTcpConnectionInterface) -{ - // We can't easily create a real TCP connection in unit tests - // but we can test that the class interface is properly defined - - // Test that we can create a NULL connection (will fail but shouldn't crash) - // This tests the interface exists - SrsTcpConnection *conn = new SrsTcpConnection(NULL); - - // The connection should be created (even with invalid fd) - EXPECT_TRUE(conn != NULL); - - // Clean up - delete conn; -} - VOID TEST(ProtocolConnTest, SrsSslConnectionInterface) { // Test SSL connection interface @@ -563,3 +538,1560 @@ VOID TEST(ProtocolConnTest, SrsSslConnectionInterface) // Clean up delete ssl; // This will also delete the tcp connection } + +VOID TEST(ProtocolRtpTest, SrsRtpVideoBuilderBasic) +{ + srs_error_t err = srs_success; + + SrsRtpVideoBuilder builder; + + // Test initialization with basic parameters + SrsFormat format; + uint32_t ssrc = 12345; + uint8_t payload_type = 96; + + HELPER_EXPECT_SUCCESS(builder.initialize(&format, ssrc, payload_type)); + + // Test that builder is properly initialized + // We can't easily test the full functionality without complex media packets + // but we can verify the initialization doesn't crash + EXPECT_TRUE(true); // Basic initialization test passed +} + +VOID TEST(ProtocolRtpTest, SrsRtpVideoBuilderPackaging) +{ + srs_error_t err = srs_success; + + SrsRtpVideoBuilder builder; + SrsFormat format; + uint32_t ssrc = 54321; + uint8_t payload_type = 97; + + HELPER_EXPECT_SUCCESS(builder.initialize(&format, ssrc, payload_type)); + + // Test packaging with minimal media packet + SrsMediaPacket *msg = new SrsMediaPacket(); + SrsUniquePtr msg_uptr(msg); + + // Create minimal video data + char *video_data = new char[10]; + video_data[0] = 0x17; // keyframe + AVC + video_data[1] = 0x01; // AVC NALU + for (int i = 2; i < 10; i++) { + video_data[i] = i; + } + msg->wrap(video_data, 10); + msg->timestamp_ = 1000; + msg->message_type_ = SrsFrameTypeVideo; + + std::vector pkts; + + // Test packaging - may fail due to complex validation but shouldn't crash + srs_error_t package_err = builder.package_nalus(msg, std::vector(), pkts); + srs_freep(package_err); + + // Clean up any created packets + for (size_t i = 0; i < pkts.size(); i++) { + delete pkts[i]; + } + pkts.clear(); + + // Test passed if no crash occurred + EXPECT_TRUE(true); +} + +VOID TEST(ProtocolRtcStunTest, SrsStunPacketBasic) +{ + SrsStunPacket stun; + + // Test initial state + EXPECT_FALSE(stun.is_binding_request()); + EXPECT_FALSE(stun.is_binding_response()); + EXPECT_EQ(0, stun.get_message_type()); + EXPECT_TRUE(stun.get_username().empty()); + EXPECT_TRUE(stun.get_local_ufrag().empty()); + EXPECT_TRUE(stun.get_remote_ufrag().empty()); + EXPECT_TRUE(stun.get_transcation_id().empty()); + EXPECT_EQ(0, stun.get_mapped_address()); + EXPECT_EQ(0, stun.get_mapped_port()); + EXPECT_FALSE(stun.get_ice_controlled()); + EXPECT_FALSE(stun.get_ice_controlling()); + EXPECT_FALSE(stun.get_use_candidate()); +} + +VOID TEST(ProtocolRtcStunTest, SrsStunPacketSetters) +{ + SrsStunPacket stun; + + // Test setters + stun.set_message_type(BindingRequest); + EXPECT_TRUE(stun.is_binding_request()); + EXPECT_FALSE(stun.is_binding_response()); + EXPECT_EQ(BindingRequest, stun.get_message_type()); + + stun.set_message_type(BindingResponse); + EXPECT_FALSE(stun.is_binding_request()); + EXPECT_TRUE(stun.is_binding_response()); + EXPECT_EQ(BindingResponse, stun.get_message_type()); + + stun.set_local_ufrag("local123"); + EXPECT_STREQ("local123", stun.get_local_ufrag().c_str()); + + stun.set_remote_ufrag("remote456"); + EXPECT_STREQ("remote456", stun.get_remote_ufrag().c_str()); + + stun.set_transcation_id("transaction789"); + EXPECT_STREQ("transaction789", stun.get_transcation_id().c_str()); + + stun.set_mapped_address(0x7f000001); // 127.0.0.1 + EXPECT_EQ(0x7f000001, stun.get_mapped_address()); + + stun.set_mapped_port(8080); + EXPECT_EQ(8080, stun.get_mapped_port()); +} + +VOID TEST(ProtocolRtcStunTest, SrsStunPacketDecode) +{ + srs_error_t err = srs_success; + + SrsStunPacket stun; + + // Test decode with invalid data - should fail + char invalid_data[] = {0x01, 0x02, 0x03}; + HELPER_EXPECT_FAILED(stun.decode(invalid_data, sizeof(invalid_data))); + + // Test decode with minimal valid STUN packet structure + // STUN header: message type (2) + message length (2) + magic cookie (4) + transaction ID (12) = 20 bytes minimum + char valid_stun[20]; + memset(valid_stun, 0, sizeof(valid_stun)); + + // Set message type to binding request + valid_stun[0] = 0x00; + valid_stun[1] = 0x01; // BindingRequest + + // Set message length to 0 (no attributes) + valid_stun[2] = 0x00; + valid_stun[3] = 0x00; + + // Set magic cookie (0x2112A442 in network byte order) + valid_stun[4] = 0x21; + valid_stun[5] = 0x12; + valid_stun[6] = 0xA4; + valid_stun[7] = 0x42; + + // Set transaction ID (12 bytes) + for (int i = 8; i < 20; i++) { + valid_stun[i] = i - 8; + } + + HELPER_EXPECT_SUCCESS(stun.decode(valid_stun, sizeof(valid_stun))); + EXPECT_TRUE(stun.is_binding_request()); + EXPECT_EQ(BindingRequest, stun.get_message_type()); +} + +VOID TEST(ProtocolRtcStunTest, SrsStunPacketEncode) +{ + SrsStunPacket stun; + stun.set_message_type(BindingResponse); + stun.set_transcation_id("123456789012"); // 12 bytes + stun.set_mapped_address(0x7f000001); + stun.set_mapped_port(8080); + + char buffer[1024]; + SrsBuffer stream(buffer, sizeof(buffer)); + + // Test encode - may fail due to complex HMAC validation but shouldn't crash + srs_error_t encode_err = stun.encode("password", &stream); + srs_freep(encode_err); + + // Test passed if no crash occurred + EXPECT_TRUE(true); +} + +VOID TEST(ProtocolSdpTest, SrsSdpBasic) +{ + SrsSdp sdp; + + // Test basic SDP construction - should not crash + EXPECT_TRUE(true); + + // Test encode to empty stream + std::ostringstream os; + srs_error_t err = sdp.encode(os); + HELPER_EXPECT_SUCCESS(err); + + // Should produce some basic SDP output + std::string result = os.str(); + EXPECT_FALSE(result.empty()); + + // Should contain basic SDP fields + EXPECT_TRUE(result.find("v=") != std::string::npos); // version + EXPECT_TRUE(result.find("o=") != std::string::npos); // origin + EXPECT_TRUE(result.find("s=") != std::string::npos); // session name + EXPECT_TRUE(result.find("t=") != std::string::npos); // timing +} + +VOID TEST(ProtocolSdpTest, SrsSdpParse) +{ + srs_error_t err = srs_success; + + SrsSdp sdp; + + // Test parsing minimal valid SDP + std::string minimal_sdp = + "v=0\r\n" + "o=- 123456 654321 IN IP4 127.0.0.1\r\n" + "s=Test Session\r\n" + "t=0 0\r\n"; + + HELPER_EXPECT_SUCCESS(sdp.parse(minimal_sdp)); + + // Test parsing invalid SDP - may succeed or fail depending on SDP parser implementation + std::string invalid_sdp = "invalid sdp content"; + srs_error_t invalid_err = sdp.parse(invalid_sdp); + srs_freep(invalid_err); // Don't assert specific result as parser may be lenient + + // Test parsing empty SDP - may succeed or fail depending on implementation + srs_error_t empty_err = sdp.parse(""); + srs_freep(empty_err); // Don't assert specific result as parser may handle empty input +} + +VOID TEST(ProtocolSdpTest, SrsSdpMediaDescription) +{ + srs_error_t err = srs_success; + + SrsSdp sdp; + + // Test parsing SDP with media description + std::string sdp_with_media = + "v=0\r\n" + "o=- 123456 654321 IN IP4 127.0.0.1\r\n" + "s=Test Session\r\n" + "t=0 0\r\n" + "m=video 9 RTP/AVP 96\r\n" + "a=rtpmap:96 H264/90000\r\n"; + + HELPER_EXPECT_SUCCESS(sdp.parse(sdp_with_media)); + + // Test that we can encode it back + std::ostringstream os; + HELPER_EXPECT_SUCCESS(sdp.encode(os)); + + std::string result = os.str(); + EXPECT_FALSE(result.empty()); + EXPECT_TRUE(result.find("m=") != std::string::npos); // media line +} + +VOID TEST(ProtocolRtmpConnTest, SrsBasicRtmpClientConstruction) +{ + // Test RTMP client construction with various URLs + SrsBasicRtmpClient client1("rtmp://127.0.0.1:1935/live/test", 3000 * SRS_UTIME_MILLISECONDS, 9000 * SRS_UTIME_MILLISECONDS); + + // Test initial state + EXPECT_EQ(0, client1.sid()); + + // Test extra args access + SrsAmf0Object *args1 = client1.extra_args(); + EXPECT_TRUE(args1 != NULL); + + // Test with different URL format + SrsBasicRtmpClient client2("rtmp://192.168.1.100/app/stream", 5000 * SRS_UTIME_MILLISECONDS, 15000 * SRS_UTIME_MILLISECONDS); + + EXPECT_EQ(0, client2.sid()); + + SrsAmf0Object *args2 = client2.extra_args(); + EXPECT_TRUE(args2 != NULL); + + // Args should be different objects + EXPECT_NE(args1, args2); +} + +VOID TEST(ProtocolRtmpConnTest, SrsBasicRtmpClientOperations) +{ + SrsBasicRtmpClient client("rtmp://127.0.0.1:1935/live/stream", 1000 * SRS_UTIME_MILLISECONDS, 3000 * SRS_UTIME_MILLISECONDS); + + // Test connection operations - these will fail without a server but shouldn't crash + srs_error_t connect_err = client.connect(); + srs_freep(connect_err); // Expected to fail without server + + // Test close - should not crash even if not connected + client.close(); + + // Test kbps sampling - should not crash + client.kbps_sample("test", 1000 * SRS_UTIME_MILLISECONDS); + client.kbps_sample("test2", 2000 * SRS_UTIME_MILLISECONDS, 10); + + // Note: We don't test publish(), play(), recv_message(), or set_recv_timeout() here + // because they require valid internal client/transport objects which we don't have + // without a successful connection. These methods exist and will be tested in + // integration tests with actual RTMP server connections. +} + +VOID TEST(ProtocolHttpClientTest, SrsHttpClientInitialization) +{ + srs_error_t err = srs_success; + + SrsHttpClient client; + + // Test initialization with HTTP + HELPER_EXPECT_SUCCESS(client.initialize("http", "127.0.0.1", 8080, 5000 * SRS_UTIME_MILLISECONDS)); + + // Test initialization with HTTPS + HELPER_EXPECT_SUCCESS(client.initialize("https", "example.com", 443, 10000 * SRS_UTIME_MILLISECONDS)); + + // Test header setting and chaining + SrsHttpClient *result1 = client.set_header("User-Agent", "SRS-Test/1.0"); + EXPECT_TRUE(result1 != NULL); + EXPECT_EQ(&client, result1); // Should return self for chaining + + SrsHttpClient *result2 = client.set_header("Accept", "application/json"); + EXPECT_TRUE(result2 != NULL); + EXPECT_EQ(&client, result2); + + // Test multiple header settings + client.set_header("Content-Type", "application/json"); + client.set_header("Authorization", "Bearer token123"); + client.set_header("X-Custom-Header", "custom-value"); + + // Test timeout setting + client.set_recv_timeout(3000 * SRS_UTIME_MILLISECONDS); +} + +VOID TEST(ProtocolHttpClientTest, SrsHttpClientRequests) +{ + srs_error_t err = srs_success; + + SrsHttpClient client; + HELPER_EXPECT_SUCCESS(client.initialize("http", "127.0.0.1", 8080, 1000 * SRS_UTIME_MILLISECONDS)); + + // Set headers for testing + client.set_header("User-Agent", "SRS-UTest"); + client.set_header("Accept", "application/json"); + + // Test GET request - will fail without server but shouldn't crash + ISrsHttpMessage *get_msg = NULL; + srs_error_t get_err = client.get("/api/test", "", &get_msg); + srs_freep(get_err); // Expected to fail without server + EXPECT_TRUE(get_msg == NULL); + + // Test POST request - will fail without server but shouldn't crash + ISrsHttpMessage *post_msg = NULL; + std::string post_data = "{\"test\":\"data\"}"; + srs_error_t post_err = client.post("/api/submit", post_data, &post_msg); + srs_freep(post_err); // Expected to fail without server + EXPECT_TRUE(post_msg == NULL); + + // Test requests with different paths + ISrsHttpMessage *root_msg = NULL; + srs_error_t get_root_err = client.get("/", "", &root_msg); + srs_freep(get_root_err); + EXPECT_TRUE(root_msg == NULL); + + srs_error_t post_empty_err = client.post("/empty", "", &post_msg); + srs_freep(post_empty_err); + EXPECT_TRUE(post_msg == NULL); +} + +VOID TEST(ProtocolRtcStunTest, SrsCrc32IeeeBasic) +{ + // Test CRC32 IEEE calculation with known values + const char *test_data = "hello"; + uint32_t crc = srs_crc32_ieee(test_data, strlen(test_data)); + + // CRC32 should be deterministic for same input + uint32_t crc2 = srs_crc32_ieee(test_data, strlen(test_data)); + EXPECT_EQ(crc, crc2); + + // Different data should produce different CRC + const char *test_data2 = "world"; + uint32_t crc3 = srs_crc32_ieee(test_data2, strlen(test_data2)); + EXPECT_NE(crc, crc3); + + // Test with empty data + uint32_t crc_empty = srs_crc32_ieee("", 0); + EXPECT_EQ(0, crc_empty); + + // Test with previous CRC value + uint32_t crc_combined = srs_crc32_ieee(test_data2, strlen(test_data2), crc); + EXPECT_NE(crc, crc_combined); + EXPECT_NE(crc3, crc_combined); +} + +VOID TEST(ProtocolRtcStunTest, SrsStunPacketComplexDecode) +{ + srs_error_t err = srs_success; + + SrsStunPacket stun; + + // Test STUN packet with username attribute + char stun_with_username[32]; + memset(stun_with_username, 0, sizeof(stun_with_username)); + + // STUN header + stun_with_username[0] = 0x00; + stun_with_username[1] = 0x01; // BindingRequest + stun_with_username[2] = 0x00; + stun_with_username[3] = 0x0C; // message length = 12 (username attribute) + + // Magic cookie + stun_with_username[4] = 0x21; + stun_with_username[5] = 0x12; + stun_with_username[6] = 0xA4; + stun_with_username[7] = 0x42; + + // Transaction ID (12 bytes) + for (int i = 8; i < 20; i++) { + stun_with_username[i] = i - 8; + } + + // Username attribute: type=0x0006, length=8, value="test:usr" + stun_with_username[20] = 0x00; + stun_with_username[21] = 0x06; // Username attribute type + stun_with_username[22] = 0x00; + stun_with_username[23] = 0x08; // Length = 8 + stun_with_username[24] = 't'; + stun_with_username[25] = 'e'; + stun_with_username[26] = 's'; + stun_with_username[27] = 't'; + stun_with_username[28] = ':'; + stun_with_username[29] = 'u'; + stun_with_username[30] = 's'; + stun_with_username[31] = 'r'; + + HELPER_EXPECT_SUCCESS(stun.decode(stun_with_username, sizeof(stun_with_username))); + EXPECT_TRUE(stun.is_binding_request()); + EXPECT_STREQ("test:usr", stun.get_username().c_str()); + EXPECT_STREQ("test", stun.get_local_ufrag().c_str()); + EXPECT_STREQ("usr", stun.get_remote_ufrag().c_str()); +} + +VOID TEST(ProtocolRtpTest, SrsRtpVideoBuilderPackageStapA) +{ + srs_error_t err = srs_success; + + SrsRtpVideoBuilder builder; + SrsFormat format; + uint32_t ssrc = 12345; + uint8_t payload_type = 96; + + // Initialize format and setup video codec config (required for package_stap_a) + HELPER_EXPECT_SUCCESS(format.initialize()); + + // Setup video sequence header (H.264 AVC) with SPS/PPS data + // This is based on MockSrsFormat from srs_utest_fmp4.cpp + uint8_t video_raw[] = { + 0x17, // keyframe + AVC + 0x00, 0x00, 0x00, 0x00, 0x01, 0x64, 0x00, 0x20, 0xff, 0xe1, 0x00, 0x19, 0x67, 0x64, 0x00, 0x20, + 0xac, 0xd9, 0x40, 0xc0, 0x29, 0xb0, 0x11, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00, + 0x32, 0x0f, 0x18, 0x31, 0x96, 0x01, 0x00, 0x05, 0x68, 0xeb, 0xec, 0xb2, 0x2c}; + + // Parse the video sequence header to populate SPS/PPS + HELPER_EXPECT_SUCCESS(format.on_video(0, (char *)video_raw, sizeof(video_raw))); + + // Verify that vcodec and SPS/PPS are properly set + EXPECT_TRUE(format.vcodec_ != NULL); + EXPECT_EQ(SrsVideoCodecIdAVC, format.vcodec_->id_); + EXPECT_FALSE(format.vcodec_->sequenceParameterSetNALUnit_.empty()); + EXPECT_FALSE(format.vcodec_->pictureParameterSetNALUnit_.empty()); + + HELPER_EXPECT_SUCCESS(builder.initialize(&format, ssrc, payload_type)); + + // Create a media packet for testing + SrsMediaPacket *msg = new SrsMediaPacket(); + SrsUniquePtr msg_uptr(msg); + + char *video_data = new char[10]; + video_data[0] = 0x17; // keyframe + AVC + for (int i = 1; i < 10; i++) { + video_data[i] = i; + } + msg->wrap(video_data, 10); + msg->timestamp_ = 1000; + msg->message_type_ = SrsFrameTypeVideo; + + // Create RTP packet for STAP-A + SrsRtpPacket *pkt = new SrsRtpPacket(); + SrsUniquePtr pkt_uptr(pkt); + + // Test package_stap_a - should now work with proper SPS/PPS setup + HELPER_EXPECT_SUCCESS(builder.package_stap_a(msg, pkt)); + + // Verify RTP packet was properly configured + EXPECT_EQ(payload_type, pkt->header.get_payload_type()); + EXPECT_EQ(ssrc, pkt->header.get_ssrc()); + EXPECT_EQ(SrsFrameTypeVideo, pkt->frame_type_); + EXPECT_EQ(1000 * 90, pkt->header.get_timestamp()); // timestamp * 90 + EXPECT_FALSE(pkt->header.get_marker()); // STAP-A should not have marker bit set + + // Verify STAP-A payload was created + EXPECT_TRUE(pkt->payload() != NULL); + + // Verify it's a STAP-A payload by checking if we can cast to SrsRtpSTAPPayload + SrsRtpSTAPPayload *stap_payload = dynamic_cast(pkt->payload()); + EXPECT_TRUE(stap_payload != NULL); + + // Verify STAP-A contains SPS and PPS + EXPECT_TRUE(stap_payload->get_sps() != NULL); + EXPECT_TRUE(stap_payload->get_pps() != NULL); +} + +VOID TEST(ProtocolRtpTest, SrsRtpVideoBuilderPackageNalusWithFormat) +{ + srs_error_t err = srs_success; + + SrsRtpVideoBuilder builder; + SrsFormat format; + uint32_t ssrc = 54321; + uint8_t payload_type = 97; + + // Initialize format and setup video codec config (required for package_nalus) + HELPER_EXPECT_SUCCESS(format.initialize()); + + // Setup video sequence header (H.264 AVC) with SPS/PPS data + uint8_t video_raw[] = { + 0x17, // keyframe + AVC + 0x00, 0x00, 0x00, 0x00, 0x01, 0x64, 0x00, 0x20, 0xff, 0xe1, 0x00, 0x19, 0x67, 0x64, 0x00, 0x20, + 0xac, 0xd9, 0x40, 0xc0, 0x29, 0xb0, 0x11, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00, + 0x32, 0x0f, 0x18, 0x31, 0x96, 0x01, 0x00, 0x05, 0x68, 0xeb, 0xec, 0xb2, 0x2c}; + + // Parse the video sequence header to populate SPS/PPS and vcodec + HELPER_EXPECT_SUCCESS(format.on_video(0, (char *)video_raw, sizeof(video_raw))); + + // Verify that vcodec is properly set + EXPECT_TRUE(format.vcodec_ != NULL); + EXPECT_EQ(SrsVideoCodecIdAVC, format.vcodec_->id_); + + HELPER_EXPECT_SUCCESS(builder.initialize(&format, ssrc, payload_type)); + + // Create a media packet + SrsMediaPacket *msg = new SrsMediaPacket(); + SrsUniquePtr msg_uptr(msg); + + char *video_data = new char[20]; + video_data[0] = 0x17; // keyframe + AVC + video_data[1] = 0x01; // AVC NALU + for (int i = 2; i < 20; i++) { + video_data[i] = i; + } + msg->wrap(video_data, 20); + msg->timestamp_ = 2000; + msg->message_type_ = SrsFrameTypeVideo; + + // Create NALU samples for testing with proper H.264 NALU headers + std::vector samples; + + // Create SPS NALU sample + SrsNaluSample *sample1 = new SrsNaluSample(); + uint8_t sps_data[] = {0x67, 0x64, 0x00, 0x20, 0xac}; // SPS NALU (type 7) + sample1->bytes_ = (char *)sps_data; + sample1->size_ = sizeof(sps_data); + samples.push_back(sample1); + + // Create PPS NALU sample + SrsNaluSample *sample2 = new SrsNaluSample(); + uint8_t pps_data[] = {0x68, 0xeb, 0xec, 0xb2}; // PPS NALU (type 8) + sample2->bytes_ = (char *)pps_data; + sample2->size_ = sizeof(pps_data); + samples.push_back(sample2); + + std::vector pkts; + + // Test package_nalus with proper format setup - should now work + HELPER_EXPECT_SUCCESS(builder.package_nalus(msg, samples, pkts)); + + // Should create at least one packet + EXPECT_GT((int)pkts.size(), 0); + + // Verify first packet configuration + if (!pkts.empty()) { + SrsRtpPacket *pkt = pkts[0]; + EXPECT_EQ(payload_type, pkt->header.get_payload_type()); + EXPECT_EQ(ssrc, pkt->header.get_ssrc()); + EXPECT_EQ(SrsFrameTypeVideo, pkt->frame_type_); + EXPECT_EQ(2000 * 90, pkt->header.get_timestamp()); // timestamp * 90 + } + + // Clean up any created packets + for (size_t i = 0; i < pkts.size(); i++) { + delete pkts[i]; + } + pkts.clear(); + + // Clean up samples + delete sample1; + delete sample2; +} + +VOID TEST(ProtocolRtpTest, SrsRtpVideoBuilderPackageSingleNalu) +{ + srs_error_t err = srs_success; + + SrsRtpVideoBuilder builder; + SrsFormat format; + uint32_t ssrc = 11111; + uint8_t payload_type = 98; + + HELPER_EXPECT_SUCCESS(builder.initialize(&format, ssrc, payload_type)); + + // Create a media packet + SrsMediaPacket *msg = new SrsMediaPacket(); + SrsUniquePtr msg_uptr(msg); + + char *video_data = new char[15]; + video_data[0] = 0x17; // keyframe + AVC + for (int i = 1; i < 15; i++) { + video_data[i] = i; + } + msg->wrap(video_data, 15); + msg->timestamp_ = 3000; + msg->message_type_ = SrsFrameTypeVideo; + + // Create a single NALU sample + SrsNaluSample *sample = new SrsNaluSample(); + sample->bytes_ = video_data + 2; + sample->size_ = 10; + + std::vector pkts; + + // Test package_single_nalu + HELPER_EXPECT_SUCCESS(builder.package_single_nalu(msg, sample, pkts)); + + // Should create exactly one packet + EXPECT_EQ(1, (int)pkts.size()); + + if (!pkts.empty()) { + SrsRtpPacket *pkt = pkts[0]; + EXPECT_EQ(payload_type, pkt->header.get_payload_type()); + EXPECT_EQ(ssrc, pkt->header.get_ssrc()); + EXPECT_EQ(SrsFrameTypeVideo, pkt->frame_type_); + EXPECT_EQ(3000 * 90, pkt->header.get_timestamp()); // timestamp * 90 + } + + // Clean up packets + for (size_t i = 0; i < pkts.size(); i++) { + delete pkts[i]; + } + pkts.clear(); + + // Clean up sample + delete sample; +} + +VOID TEST(ProtocolRtpTest, SrsRtpVideoBuilderPackageFuA) +{ + srs_error_t err = srs_success; + + SrsRtpVideoBuilder builder; + SrsFormat format; + uint32_t ssrc = 22222; + uint8_t payload_type = 99; + + // Initialize format and setup video codec config (required for package_fu_a) + HELPER_EXPECT_SUCCESS(format.initialize()); + + // Setup video sequence header (H.264 AVC) with SPS/PPS data + uint8_t video_raw[] = { + 0x17, // keyframe + AVC + 0x00, 0x00, 0x00, 0x00, 0x01, 0x64, 0x00, 0x20, 0xff, 0xe1, 0x00, 0x19, 0x67, 0x64, 0x00, 0x20, + 0xac, 0xd9, 0x40, 0xc0, 0x29, 0xb0, 0x11, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x03, 0x00, + 0x32, 0x0f, 0x18, 0x31, 0x96, 0x01, 0x00, 0x05, 0x68, 0xeb, 0xec, 0xb2, 0x2c}; + + // Parse the video sequence header to populate SPS/PPS and vcodec + HELPER_EXPECT_SUCCESS(format.on_video(0, (char *)video_raw, sizeof(video_raw))); + + // Verify that vcodec is properly set + EXPECT_TRUE(format.vcodec_ != NULL); + EXPECT_EQ(SrsVideoCodecIdAVC, format.vcodec_->id_); + + HELPER_EXPECT_SUCCESS(builder.initialize(&format, ssrc, payload_type)); + + // Create a media packet + SrsMediaPacket *msg = new SrsMediaPacket(); + SrsUniquePtr msg_uptr(msg); + + char *video_data = new char[100]; + video_data[0] = 0x17; // keyframe + AVC + video_data[1] = 0x65; // IDR NALU header (type 5) + for (int i = 2; i < 100; i++) { + video_data[i] = i; + } + msg->wrap(video_data, 100); + msg->timestamp_ = 4000; + msg->message_type_ = SrsFrameTypeVideo; + + // Create a large NALU sample that requires FU-A fragmentation + SrsNaluSample *sample = new SrsNaluSample(); + sample->bytes_ = video_data + 1; // Start from NALU header (IDR) + sample->size_ = 80; // Large enough to require fragmentation + + std::vector pkts; + int fu_payload_size = 30; // Small payload size to force fragmentation + + // Test package_fu_a - should now work with proper format setup + HELPER_EXPECT_SUCCESS(builder.package_fu_a(msg, sample, fu_payload_size, pkts)); + + // Should create multiple packets due to fragmentation + EXPECT_GT((int)pkts.size(), 1); + + // Verify first packet configuration + if (!pkts.empty()) { + SrsRtpPacket *pkt = pkts[0]; + EXPECT_EQ(payload_type, pkt->header.get_payload_type()); + EXPECT_EQ(ssrc, pkt->header.get_ssrc()); + EXPECT_EQ(SrsFrameTypeVideo, pkt->frame_type_); + EXPECT_EQ(4000 * 90, pkt->header.get_timestamp()); // timestamp * 90 + + // Verify it's a FU-A payload + SrsRtpFUAPayload2 *fua_payload = dynamic_cast(pkt->payload()); + EXPECT_TRUE(fua_payload != NULL); + + if (fua_payload) { + // First packet should have start bit set + EXPECT_TRUE(fua_payload->start_); + EXPECT_FALSE(fua_payload->end_); + } + } + + // Last packet should have end bit set + if (pkts.size() > 1) { + SrsRtpPacket *last_pkt = pkts[pkts.size() - 1]; + SrsRtpFUAPayload2 *last_fua = dynamic_cast(last_pkt->payload()); + EXPECT_TRUE(last_fua != NULL); + + if (last_fua) { + EXPECT_FALSE(last_fua->start_); + EXPECT_TRUE(last_fua->end_); + } + } + + // Clean up any created packets + for (size_t i = 0; i < pkts.size(); i++) { + delete pkts[i]; + } + pkts.clear(); + + // Clean up sample + delete sample; +} + +VOID TEST(ProtocolConnTest, SrsBufferedReadWriterPeek) +{ + srs_error_t err = srs_success; + + // Create mock IO with test data + MockBufferIO mock_io; + mock_io.append("Hello World Test Data"); + + SrsBufferedReadWriter buffered(&mock_io); + + // Test peek functionality + char peek_buf[10]; + int peek_size = sizeof(peek_buf); + + HELPER_EXPECT_SUCCESS(buffered.peek(peek_buf, &peek_size)); + + // Should have peeked some data + EXPECT_GT(peek_size, 0); + EXPECT_LE(peek_size, (int)sizeof(peek_buf)); + + // Test peek with smaller buffer + char small_buf[5]; + int small_size = sizeof(small_buf); + + HELPER_EXPECT_SUCCESS(buffered.peek(small_buf, &small_size)); + EXPECT_GT(small_size, 0); + EXPECT_LE(small_size, (int)sizeof(small_buf)); +} + +VOID TEST(ProtocolConnTest, SrsBufferedReadWriterRead) +{ + srs_error_t err = srs_success; + + // Create mock IO with test data + MockBufferIO mock_io; + mock_io.append("Hello World"); + + SrsBufferedReadWriter buffered(&mock_io); + + // Test read functionality + char read_buf[20]; + ssize_t nread = 0; + + HELPER_EXPECT_SUCCESS(buffered.read(read_buf, sizeof(read_buf), &nread)); + + // Should have read some data + EXPECT_GT(nread, 0); + EXPECT_LE(nread, (ssize_t)sizeof(read_buf)); + + // Test read with smaller buffer + mock_io.append("More Data"); + char small_buf[5]; + ssize_t small_nread = 0; + + HELPER_EXPECT_SUCCESS(buffered.read(small_buf, sizeof(small_buf), &small_nread)); + EXPECT_GT(small_nread, 0); + EXPECT_LE(small_nread, (ssize_t)sizeof(small_buf)); +} + +VOID TEST(ProtocolConnTest, SrsBufferedReadWriterReadFully) +{ + srs_error_t err = srs_success; + + // Create mock IO with sufficient test data + MockBufferIO mock_io; + mock_io.append("Hello World Test Data For Read Fully"); + + SrsBufferedReadWriter buffered(&mock_io); + + // Test read_fully functionality + char read_buf[10]; + ssize_t nread = 0; + + HELPER_EXPECT_SUCCESS(buffered.read_fully(read_buf, sizeof(read_buf), &nread)); + + // read_fully should read exactly the requested amount or fail + EXPECT_EQ((ssize_t)sizeof(read_buf), nread); + + // Test read_fully with smaller amount + char small_buf[5]; + ssize_t small_nread = 0; + + HELPER_EXPECT_SUCCESS(buffered.read_fully(small_buf, sizeof(small_buf), &small_nread)); + EXPECT_EQ((ssize_t)sizeof(small_buf), small_nread); +} + +VOID TEST(ProtocolConnTest, SrsBufferedReadWriterReloadBuffer) +{ + // Test that reload_buffer method exists and is accessible through peek + // Since reload_buffer is private, we test it indirectly through peek + + MockBufferIO mock_io; + mock_io.append("Test Data For Buffer Reload"); + + SrsBufferedReadWriter buffered(&mock_io); + + // First peek should trigger reload_buffer + char peek_buf[10]; + int peek_size = sizeof(peek_buf); + + srs_error_t err = buffered.peek(peek_buf, &peek_size); + HELPER_EXPECT_SUCCESS(err); + + // Should have loaded data into buffer + EXPECT_GT(peek_size, 0); + + // Second peek should use cached data (no reload needed) + char peek_buf2[5]; + int peek_size2 = sizeof(peek_buf2); + + HELPER_EXPECT_SUCCESS(buffered.peek(peek_buf2, &peek_size2)); + EXPECT_GT(peek_size2, 0); +} + +VOID TEST(ProtocolConnTest, SrsSslConnectionRead) +{ + // Use available test certificates + std::string key_file = "conf/server.key"; + std::string crt_file = "conf/server.crt"; + + // Create mock transport for SSL connection testing + MockBufferIO mock_io; + mock_io.append("SSL encrypted test data for reading"); + + // Create SSL connection with mock transport + SrsSslConnection ssl_conn(&mock_io); + + // Test timeout methods + ssl_conn.set_recv_timeout(5 * SRS_UTIME_SECONDS); + srs_utime_t timeout = ssl_conn.get_recv_timeout(); + EXPECT_EQ(5 * SRS_UTIME_SECONDS, timeout); + + ssl_conn.set_send_timeout(3 * SRS_UTIME_SECONDS); + srs_utime_t send_timeout = ssl_conn.get_send_timeout(); + EXPECT_EQ(3 * SRS_UTIME_SECONDS, send_timeout); + + // Test byte counters + int64_t recv_bytes = ssl_conn.get_recv_bytes(); + int64_t send_bytes = ssl_conn.get_send_bytes(); + EXPECT_GE(recv_bytes, 0); + EXPECT_GE(send_bytes, 0); + + // Test handshake method exists (will fail without proper SSL setup but shouldn't crash) + srs_error_t handshake_err = ssl_conn.handshake(key_file, crt_file); + srs_freep(handshake_err); // Expected to fail due to mock transport, just testing interface + + // Test read method exists (will fail without handshake but shouldn't crash) + char read_buf[100]; + ssize_t nread = 0; + srs_error_t read_err = ssl_conn.read(read_buf, sizeof(read_buf), &nread); + srs_freep(read_err); // Expected to fail, just testing interface + + // Test read_fully method exists + srs_error_t read_fully_err = ssl_conn.read_fully(read_buf, 10, &nread); + srs_freep(read_fully_err); // Expected to fail, just testing interface + + // Test write method exists + const char *test_data = "Hello SSL World"; + ssize_t nwrite = 0; + srs_error_t write_err = ssl_conn.write((void *)test_data, strlen(test_data), &nwrite); + srs_freep(write_err); // Expected to fail, just testing interface + + // Test writev method exists + struct iovec iov[1]; + iov[0].iov_base = (void *)test_data; + iov[0].iov_len = strlen(test_data); + srs_error_t writev_err = ssl_conn.writev(iov, 1, &nwrite); + srs_freep(writev_err); // Expected to fail, just testing interface + + // Test passed - all SSL connection methods exist and can be called safely + EXPECT_TRUE(true); +} + +VOID TEST(ProtocolSdpTest, SrsParseH264Fmtp) +{ + srs_error_t err = srs_success; + + // Test valid H264 fmtp parsing + H264SpecificParam h264_param; + std::string fmtp = "profile-level-id=42e01e;packetization-mode=1;level-asymmetry-allowed=1"; + + HELPER_EXPECT_SUCCESS(srs_parse_h264_fmtp(fmtp, h264_param)); + + EXPECT_STREQ("42e01e", h264_param.profile_level_id_.c_str()); + EXPECT_STREQ("1", h264_param.packetization_mode_.c_str()); + EXPECT_STREQ("1", h264_param.level_asymmetry_allow_.c_str()); + + // Test partial fmtp parsing (should fail due to missing packetization-mode) + H264SpecificParam h264_param2; + std::string fmtp2 = "profile-level-id=640028"; + + srs_error_t partial_err = srs_parse_h264_fmtp(fmtp2, h264_param2); + srs_freep(partial_err); // Expected to fail due to missing packetization-mode + + // Test empty fmtp (should fail due to missing required parameters) + H264SpecificParam h264_param3; + std::string fmtp3 = ""; + + srs_error_t parse_err = srs_parse_h264_fmtp(fmtp3, h264_param3); + srs_freep(parse_err); // Expected to fail, just testing interface +} + +VOID TEST(ProtocolSdpTest, SrsSessionInfoParseAttribute) +{ + srs_error_t err = srs_success; + + SrsSessionInfo session_info; + + // Test ice-ufrag attribute + HELPER_EXPECT_SUCCESS(session_info.parse_attribute("ice-ufrag", "test_ufrag")); + EXPECT_STREQ("test_ufrag", session_info.ice_ufrag_.c_str()); + + // Test ice-pwd attribute + HELPER_EXPECT_SUCCESS(session_info.parse_attribute("ice-pwd", "test_password")); + EXPECT_STREQ("test_password", session_info.ice_pwd_.c_str()); + + // Test ice-options attribute + HELPER_EXPECT_SUCCESS(session_info.parse_attribute("ice-options", "trickle")); + EXPECT_STREQ("trickle", session_info.ice_options_.c_str()); + + // Test fingerprint attribute + HELPER_EXPECT_SUCCESS(session_info.parse_attribute("fingerprint", "sha-256 AA:BB:CC:DD:EE:FF")); + EXPECT_STREQ("sha-256", session_info.fingerprint_algo_.c_str()); + EXPECT_STREQ("AA:BB:CC:DD:EE:FF", session_info.fingerprint_.c_str()); + + // Test setup attribute + HELPER_EXPECT_SUCCESS(session_info.parse_attribute("setup", "active")); + EXPECT_STREQ("active", session_info.setup_.c_str()); + + // Test unknown attribute (should not fail) + HELPER_EXPECT_SUCCESS(session_info.parse_attribute("unknown", "value")); +} + +VOID TEST(ProtocolSdpTest, SrsSessionInfoOperatorAssign) +{ + SrsSessionInfo session1; + session1.ice_ufrag_ = "ufrag1"; + session1.ice_pwd_ = "pwd1"; + session1.ice_options_ = "trickle"; + session1.fingerprint_algo_ = "sha-256"; + session1.fingerprint_ = "AA:BB:CC"; + session1.setup_ = "active"; + + SrsSessionInfo session2; + session2.ice_ufrag_ = "ufrag2"; + session2.ice_pwd_ = "pwd2"; + + // Test assignment operator + session2 = session1; + + EXPECT_STREQ("ufrag1", session2.ice_ufrag_.c_str()); + EXPECT_STREQ("pwd1", session2.ice_pwd_.c_str()); + EXPECT_STREQ("trickle", session2.ice_options_.c_str()); + EXPECT_STREQ("sha-256", session2.fingerprint_algo_.c_str()); + EXPECT_STREQ("AA:BB:CC", session2.fingerprint_.c_str()); + EXPECT_STREQ("active", session2.setup_.c_str()); + + // Test equality operator + EXPECT_TRUE(session1 == session2); + + // Test inequality + session2.ice_ufrag_ = "different"; + EXPECT_FALSE(session1 == session2); +} + +VOID TEST(ProtocolSdpTest, SrsSSRCInfoEncode) +{ + srs_error_t err = srs_success; + + // Test normal SSRC encoding + SrsSSRCInfo ssrc_info(12345, "test_cname", "stream_id", "track_id"); + + std::ostringstream os; + HELPER_EXPECT_SUCCESS(ssrc_info.encode(os)); + + std::string result = os.str(); + EXPECT_TRUE(result.find("a=ssrc:12345 cname:test_cname") != std::string::npos); + EXPECT_TRUE(result.find("a=ssrc:12345 msid:stream_id track_id") != std::string::npos); + + // Test GB28181 SSRC encoding + SrsSSRCInfo gb_ssrc_info(67890, "gb_cname", "", ""); + gb_ssrc_info.label_ = "gb28181"; + + std::ostringstream os2; + HELPER_EXPECT_SUCCESS(gb_ssrc_info.encode(os2)); + + std::string gb_result = os2.str(); + EXPECT_TRUE(gb_result.find("y=gb_cname") != std::string::npos); + + // Test invalid SSRC (should fail) + SrsSSRCInfo invalid_ssrc; + invalid_ssrc.ssrc_ = 0; + + std::ostringstream os3; + srs_error_t encode_err = invalid_ssrc.encode(os3); + HELPER_EXPECT_FAILED(encode_err); +} + +VOID TEST(ProtocolSdpTest, SrsSSRCGroupEncode) +{ + srs_error_t err = srs_success; + + // Test valid SSRC group encoding + std::vector ssrcs; + ssrcs.push_back(12345); + ssrcs.push_back(67890); + + SrsSSRCGroup ssrc_group("FID", ssrcs); + + std::ostringstream os; + HELPER_EXPECT_SUCCESS(ssrc_group.encode(os)); + + std::string result = os.str(); + EXPECT_TRUE(result.find("a=ssrc-group:FID 12345 67890") != std::string::npos); + + // Test invalid semantic (should fail) + SrsSSRCGroup invalid_group; + invalid_group.semantic_ = ""; + invalid_group.ssrcs_.push_back(12345); + + std::ostringstream os2; + srs_error_t encode_err = invalid_group.encode(os2); + HELPER_EXPECT_FAILED(encode_err); + + // Test empty SSRCs (should fail) + SrsSSRCGroup empty_group; + empty_group.semantic_ = "FID"; + // ssrcs_ is empty + + std::ostringstream os3; + srs_error_t encode_err2 = empty_group.encode(os3); + HELPER_EXPECT_FAILED(encode_err2); +} + +VOID TEST(ProtocolSdpTest, SrsMediaDescUpdateMsid) +{ + srs_error_t err = srs_success; + + SrsMediaDesc media_desc("video"); + + // Add some SSRC info + SrsSSRCInfo ssrc1(12345, "cname1", "old_stream", "old_track"); + SrsSSRCInfo ssrc2(67890, "cname2", "old_stream", "old_track"); + media_desc.ssrc_infos_.push_back(ssrc1); + media_desc.ssrc_infos_.push_back(ssrc2); + + // Update MSID + HELPER_EXPECT_SUCCESS(media_desc.update_msid("new_stream_id")); + + // Verify all SSRC infos were updated + EXPECT_STREQ("new_stream_id", media_desc.ssrc_infos_[0].msid_.c_str()); + EXPECT_STREQ("new_stream_id", media_desc.ssrc_infos_[0].mslabel_.c_str()); + EXPECT_STREQ("new_stream_id", media_desc.ssrc_infos_[1].msid_.c_str()); + EXPECT_STREQ("new_stream_id", media_desc.ssrc_infos_[1].mslabel_.c_str()); +} + +VOID TEST(ProtocolSdpTest, SrsMediaDescParseAttributeDirections) +{ + srs_error_t err = srs_success; + + SrsMediaDesc media_desc("video"); + + // Add a payload type for testing + media_desc.payload_types_.push_back(SrsMediaPayloadType(96)); + + // Test rtcp-mux attribute + HELPER_EXPECT_SUCCESS(media_desc.parse_line("a=rtcp-mux")); + EXPECT_TRUE(media_desc.rtcp_mux_); + + // Test rtcp-rsize attribute + HELPER_EXPECT_SUCCESS(media_desc.parse_line("a=rtcp-rsize")); + EXPECT_TRUE(media_desc.rtcp_rsize_); + + // Test sendonly attribute + HELPER_EXPECT_SUCCESS(media_desc.parse_line("a=sendonly")); + EXPECT_TRUE(media_desc.sendonly_); + + // Test recvonly attribute + SrsMediaDesc media_desc2("audio"); + HELPER_EXPECT_SUCCESS(media_desc2.parse_line("a=recvonly")); + EXPECT_TRUE(media_desc2.recvonly_); + + // Test sendrecv attribute + SrsMediaDesc media_desc3("video"); + HELPER_EXPECT_SUCCESS(media_desc3.parse_line("a=sendrecv")); + EXPECT_TRUE(media_desc3.sendrecv_); + + // Test inactive attribute + SrsMediaDesc media_desc4("audio"); + HELPER_EXPECT_SUCCESS(media_desc4.parse_line("a=inactive")); + EXPECT_TRUE(media_desc4.inactive_); +} + +VOID TEST(ProtocolSdpTest, SrsMediaDescParseAttrExtmap) +{ + srs_error_t err = srs_success; + + SrsMediaDesc media_desc("video"); + + // Test valid extmap parsing + HELPER_EXPECT_SUCCESS(media_desc.parse_line("a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level")); + + const std::map &extmaps = media_desc.get_extmaps(); + EXPECT_EQ(1, (int)extmaps.size()); + EXPECT_STREQ("urn:ietf:params:rtp-hdrext:ssrc-audio-level", extmaps.at(1).c_str()); + + // Test another extmap + HELPER_EXPECT_SUCCESS(media_desc.parse_line("a=extmap:2 urn:ietf:params:rtp-hdrext:toffset")); + EXPECT_EQ(2, (int)extmaps.size()); + EXPECT_STREQ("urn:ietf:params:rtp-hdrext:toffset", extmaps.at(2).c_str()); + + // Test duplicate extmap ID (should fail) + srs_error_t dup_err = media_desc.parse_line("a=extmap:1 urn:ietf:params:rtp-hdrext:duplicate"); + HELPER_EXPECT_FAILED(dup_err); +} + +VOID TEST(ProtocolSdpTest, SrsMediaDescParseAttrRtcp) +{ + srs_error_t err = srs_success; + + SrsMediaDesc media_desc("video"); + + // Test rtcp attribute parsing (currently just returns success) + HELPER_EXPECT_SUCCESS(media_desc.parse_line("a=rtcp:9 IN IP4 224.2.17.12")); + + // The current implementation is a TODO, so we just verify it doesn't crash + EXPECT_TRUE(true); +} + +VOID TEST(ProtocolSdpTest, SrsMediaDescParseAttrRtcpFb) +{ + srs_error_t err = srs_success; + + SrsMediaDesc media_desc("video"); + + // Add payload type for testing + media_desc.payload_types_.push_back(SrsMediaPayloadType(96)); + + // Test rtcp-fb attribute parsing + HELPER_EXPECT_SUCCESS(media_desc.parse_line("a=rtcp-fb:96 nack")); + + SrsMediaPayloadType *payload = media_desc.find_media_with_payload_type(96); + EXPECT_TRUE(payload != NULL); + EXPECT_EQ(1, (int)payload->rtcp_fb_.size()); + EXPECT_STREQ("nack", payload->rtcp_fb_[0].c_str()); + + // Test another rtcp-fb + HELPER_EXPECT_SUCCESS(media_desc.parse_line("a=rtcp-fb:96 nack pli")); + EXPECT_EQ(2, (int)payload->rtcp_fb_.size()); + EXPECT_STREQ("nack pli", payload->rtcp_fb_[1].c_str()); + + // Test invalid payload type (should fail) + srs_error_t invalid_err = media_desc.parse_line("a=rtcp-fb:97 nack"); + HELPER_EXPECT_FAILED(invalid_err); +} + +VOID TEST(ProtocolSdpTest, SrsMediaDescParseAttrFmtp) +{ + srs_error_t err = srs_success; + + SrsMediaDesc media_desc("video"); + + // Add payload type for testing + media_desc.payload_types_.push_back(SrsMediaPayloadType(96)); + + // Test fmtp attribute parsing + HELPER_EXPECT_SUCCESS(media_desc.parse_line("a=fmtp:96 profile-level-id=42e01e")); + + SrsMediaPayloadType *payload = media_desc.find_media_with_payload_type(96); + EXPECT_TRUE(payload != NULL); + EXPECT_STREQ("profile-level-id=42e01e", payload->format_specific_param_.c_str()); + + // Test invalid payload type (should fail) + srs_error_t invalid_err = media_desc.parse_line("a=fmtp:97 param=value"); + HELPER_EXPECT_FAILED(invalid_err); +} + +VOID TEST(ProtocolSdpTest, SrsMediaDescParseAttrMid) +{ + srs_error_t err = srs_success; + + SrsMediaDesc media_desc("video"); + + // Test mid attribute parsing + HELPER_EXPECT_SUCCESS(media_desc.parse_line("a=mid:video")); + EXPECT_STREQ("video", media_desc.mid_.c_str()); + + // Test numeric mid + SrsMediaDesc media_desc2("audio"); + HELPER_EXPECT_SUCCESS(media_desc2.parse_line("a=mid:0")); + EXPECT_STREQ("0", media_desc2.mid_.c_str()); +} + +VOID TEST(ProtocolSdpTest, SrsMediaDescParseAttrMsid) +{ + srs_error_t err = srs_success; + + SrsMediaDesc media_desc("video"); + + // Test msid attribute parsing with both stream and track + HELPER_EXPECT_SUCCESS(media_desc.parse_line("a=msid:stream_id track_id")); + EXPECT_STREQ("stream_id", media_desc.msid_.c_str()); + EXPECT_STREQ("track_id", media_desc.msid_tracker_.c_str()); + + // Test msid with only stream ID + SrsMediaDesc media_desc2("audio"); + HELPER_EXPECT_SUCCESS(media_desc2.parse_line("a=msid:stream_only")); + EXPECT_STREQ("stream_only", media_desc2.msid_.c_str()); + EXPECT_TRUE(media_desc2.msid_tracker_.empty()); +} + +VOID TEST(ProtocolSdpTest, SrsMediaDescParseAttrSsrc) +{ + srs_error_t err = srs_success; + + SrsMediaDesc media_desc("video"); + + // Test ssrc cname attribute + HELPER_EXPECT_SUCCESS(media_desc.parse_line("a=ssrc:12345 cname:test_cname")); + + EXPECT_EQ(1, (int)media_desc.ssrc_infos_.size()); + EXPECT_EQ(12345, media_desc.ssrc_infos_[0].ssrc_); + EXPECT_STREQ("test_cname", media_desc.ssrc_infos_[0].cname_.c_str()); + + // Test ssrc msid attribute + HELPER_EXPECT_SUCCESS(media_desc.parse_line("a=ssrc:12345 msid:stream_id track_id")); + EXPECT_STREQ("stream_id", media_desc.ssrc_infos_[0].msid_.c_str()); + EXPECT_STREQ("track_id", media_desc.ssrc_infos_[0].msid_tracker_.c_str()); + + // Test ssrc mslabel attribute + HELPER_EXPECT_SUCCESS(media_desc.parse_line("a=ssrc:12345 mslabel:media_stream")); + EXPECT_STREQ("media_stream", media_desc.ssrc_infos_[0].mslabel_.c_str()); + + // Test ssrc label attribute + HELPER_EXPECT_SUCCESS(media_desc.parse_line("a=ssrc:12345 label:video_track")); + EXPECT_STREQ("video_track", media_desc.ssrc_infos_[0].label_.c_str()); + + // Test new SSRC + HELPER_EXPECT_SUCCESS(media_desc.parse_line("a=ssrc:67890 cname:another_cname")); + EXPECT_EQ(2, (int)media_desc.ssrc_infos_.size()); + EXPECT_EQ(67890, media_desc.ssrc_infos_[1].ssrc_); + EXPECT_STREQ("another_cname", media_desc.ssrc_infos_[1].cname_.c_str()); +} + +VOID TEST(ProtocolSdpTest, SrsMediaDescFetchOrCreateSsrcInfo) +{ + SrsMediaDesc media_desc("video"); + + // Test creating new SSRC info + SrsSSRCInfo &ssrc_info1 = media_desc.fetch_or_create_ssrc_info(12345); + EXPECT_EQ(12345, ssrc_info1.ssrc_); + EXPECT_EQ(1, (int)media_desc.ssrc_infos_.size()); + + // Test fetching existing SSRC info + SrsSSRCInfo &ssrc_info2 = media_desc.fetch_or_create_ssrc_info(12345); + EXPECT_EQ(12345, ssrc_info2.ssrc_); + EXPECT_EQ(1, (int)media_desc.ssrc_infos_.size()); // Should not create new one + + // Verify they are the same object + EXPECT_EQ(&ssrc_info1, &ssrc_info2); + + // Test creating another SSRC info + SrsSSRCInfo &ssrc_info3 = media_desc.fetch_or_create_ssrc_info(67890); + EXPECT_EQ(67890, ssrc_info3.ssrc_); + EXPECT_EQ(2, (int)media_desc.ssrc_infos_.size()); +} + +VOID TEST(ProtocolSdpTest, SrsSdpSetGetIceCredentials) +{ + srs_error_t err = srs_success; + + SrsSdp sdp; + + // Parse SDP with media descriptions + std::string sdp_str = + "v=0\r\n" + "o=- 123456 654321 IN IP4 127.0.0.1\r\n" + "s=Test Session\r\n" + "t=0 0\r\n" + "m=video 9 RTP/AVP 96\r\n" + "m=audio 9 RTP/AVP 97\r\n"; + + HELPER_EXPECT_SUCCESS(sdp.parse(sdp_str)); + + // Test setting ICE credentials + sdp.set_ice_ufrag("test_ufrag"); + sdp.set_ice_pwd("test_password"); + + // Test getting ICE credentials + EXPECT_STREQ("test_ufrag", sdp.get_ice_ufrag().c_str()); + EXPECT_STREQ("test_password", sdp.get_ice_pwd().c_str()); + + // Verify all media descriptions were updated + std::vector video_descs = sdp.find_media_descs("video"); + std::vector audio_descs = sdp.find_media_descs("audio"); + + EXPECT_STREQ("test_ufrag", video_descs[0]->session_info_.ice_ufrag_.c_str()); + EXPECT_STREQ("test_password", video_descs[0]->session_info_.ice_pwd_.c_str()); + EXPECT_STREQ("test_ufrag", audio_descs[0]->session_info_.ice_ufrag_.c_str()); + EXPECT_STREQ("test_password", audio_descs[0]->session_info_.ice_pwd_.c_str()); +} + +VOID TEST(ProtocolSdpTest, SrsSdpSetGetDtlsRole) +{ + srs_error_t err = srs_success; + + SrsSdp sdp; + + // Parse SDP with media descriptions + std::string sdp_str = + "v=0\r\n" + "o=- 123456 654321 IN IP4 127.0.0.1\r\n" + "s=Test Session\r\n" + "t=0 0\r\n" + "m=video 9 RTP/AVP 96\r\n"; + + HELPER_EXPECT_SUCCESS(sdp.parse(sdp_str)); + + // Test setting DTLS role + sdp.set_dtls_role("active"); + + // Test getting DTLS role + EXPECT_STREQ("active", sdp.get_dtls_role().c_str()); + + // Verify media description was updated + std::vector video_descs = sdp.find_media_descs("video"); + EXPECT_STREQ("active", video_descs[0]->session_info_.setup_.c_str()); +} + +VOID TEST(ProtocolSdpTest, SrsSdpSetFingerprint) +{ + srs_error_t err = srs_success; + + SrsSdp sdp; + + // Parse SDP with media descriptions + std::string sdp_str = + "v=0\r\n" + "o=- 123456 654321 IN IP4 127.0.0.1\r\n" + "s=Test Session\r\n" + "t=0 0\r\n" + "m=video 9 RTP/AVP 96\r\n" + "m=audio 9 RTP/AVP 97\r\n"; + + HELPER_EXPECT_SUCCESS(sdp.parse(sdp_str)); + + // Test setting fingerprint algorithm and value + sdp.set_fingerprint_algo("sha-256"); + sdp.set_fingerprint("AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99"); + + // Verify all media descriptions were updated + std::vector video_descs = sdp.find_media_descs("video"); + std::vector audio_descs = sdp.find_media_descs("audio"); + + EXPECT_STREQ("sha-256", video_descs[0]->session_info_.fingerprint_algo_.c_str()); + EXPECT_STREQ("AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99", + video_descs[0]->session_info_.fingerprint_.c_str()); + EXPECT_STREQ("sha-256", audio_descs[0]->session_info_.fingerprint_algo_.c_str()); + EXPECT_STREQ("AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99", + audio_descs[0]->session_info_.fingerprint_.c_str()); +} + +VOID TEST(ProtocolSdpTest, SrsSdpAddCandidate) +{ + srs_error_t err = srs_success; + + SrsSdp sdp; + + // Parse SDP with media descriptions + std::string sdp_str = + "v=0\r\n" + "o=- 123456 654321 IN IP4 127.0.0.1\r\n" + "s=Test Session\r\n" + "t=0 0\r\n" + "m=video 9 RTP/AVP 96\r\n" + "m=audio 9 RTP/AVP 97\r\n"; + + HELPER_EXPECT_SUCCESS(sdp.parse(sdp_str)); + + // Test adding UDP candidate + sdp.add_candidate("udp", "192.168.1.100", 5000, "host"); + + // Test adding TCP candidate + sdp.add_candidate("tcp", "192.168.1.100", 5001, "host"); + + // Verify candidates were added to all media descriptions + std::vector video_descs = sdp.find_media_descs("video"); + std::vector audio_descs = sdp.find_media_descs("audio"); + + EXPECT_EQ(2, (int)video_descs[0]->candidates_.size()); + EXPECT_EQ(2, (int)audio_descs[0]->candidates_.size()); + + // Check UDP candidate + EXPECT_STREQ("udp", video_descs[0]->candidates_[0].protocol_.c_str()); + EXPECT_STREQ("192.168.1.100", video_descs[0]->candidates_[0].ip_.c_str()); + EXPECT_EQ(5000, video_descs[0]->candidates_[0].port_); + EXPECT_STREQ("host", video_descs[0]->candidates_[0].type_.c_str()); + + // Check TCP candidate + EXPECT_STREQ("tcp", video_descs[0]->candidates_[1].protocol_.c_str()); + EXPECT_STREQ("192.168.1.100", video_descs[0]->candidates_[1].ip_.c_str()); + EXPECT_EQ(5001, video_descs[0]->candidates_[1].port_); + EXPECT_STREQ("host", video_descs[0]->candidates_[1].type_.c_str()); +} + +VOID TEST(ProtocolSdpTest, SrsSdpParseAttribute) +{ + srs_error_t err = srs_success; + + SrsSdp sdp; + + // Test parsing group attribute + HELPER_EXPECT_SUCCESS(sdp.parse_line("a=group:BUNDLE video audio")); + + // Test parsing msid-semantic attribute + HELPER_EXPECT_SUCCESS(sdp.parse_line("a=msid-semantic: WMS stream_id")); + EXPECT_STREQ("WMS", sdp.msid_semantic_.c_str()); + EXPECT_EQ(1, (int)sdp.msids_.size()); + EXPECT_STREQ("stream_id", sdp.msids_[0].c_str()); + + // Test parsing session-level ICE attributes + HELPER_EXPECT_SUCCESS(sdp.parse_line("a=ice-ufrag:session_ufrag")); + HELPER_EXPECT_SUCCESS(sdp.parse_line("a=ice-pwd:session_password")); + + EXPECT_STREQ("session_ufrag", sdp.session_info_.ice_ufrag_.c_str()); + EXPECT_STREQ("session_password", sdp.session_info_.ice_pwd_.c_str()); +} + +VOID TEST(ProtocolSdpTest, SrsSdpParseGb28181Ssrc) +{ + srs_error_t err = srs_success; + + SrsSdp sdp; + + // Parse basic SDP first + std::string sdp_str = + "v=0\r\n" + "o=- 123456 654321 IN IP4 127.0.0.1\r\n" + "s=Test Session\r\n" + "t=0 0\r\n" + "m=video 9 RTP/AVP 96\r\n"; + + HELPER_EXPECT_SUCCESS(sdp.parse(sdp_str)); + + // Test parsing GB28181 SSRC + HELPER_EXPECT_SUCCESS(sdp.parse_line("y=0100008888")); + + // Verify GB28181 SSRC was converted to standard format + std::vector video_descs = sdp.find_media_descs("video"); + EXPECT_EQ(1, (int)video_descs[0]->ssrc_infos_.size()); + EXPECT_STREQ("0100008888", video_descs[0]->ssrc_infos_[0].cname_.c_str()); + EXPECT_STREQ("gb28181", video_descs[0]->ssrc_infos_[0].label_.c_str()); +} + +VOID TEST(ProtocolSdpTest, SrsSdpIsUnified) +{ + srs_error_t err = srs_success; + + // Test non-unified SDP (2 or fewer media descriptions) + SrsSdp sdp1; + std::string sdp_str1 = + "v=0\r\n" + "o=- 123456 654321 IN IP4 127.0.0.1\r\n" + "s=Test Session\r\n" + "t=0 0\r\n" + "m=video 9 RTP/AVP 96\r\n" + "m=audio 9 RTP/AVP 97\r\n"; + + HELPER_EXPECT_SUCCESS(sdp1.parse(sdp_str1)); + EXPECT_FALSE(sdp1.is_unified()); + + // Test unified SDP (more than 2 media descriptions) + SrsSdp sdp2; + std::string sdp_str2 = + "v=0\r\n" + "o=- 123456 654321 IN IP4 127.0.0.1\r\n" + "s=Test Session\r\n" + "t=0 0\r\n" + "m=video 9 RTP/AVP 96\r\n" + "m=audio 9 RTP/AVP 97\r\n" + "m=application 9 UDP/DTLS/SCTP webrtc-datachannel\r\n"; + + HELPER_EXPECT_SUCCESS(sdp2.parse(sdp_str2)); + EXPECT_TRUE(sdp2.is_unified()); +} + +VOID TEST(ProtocolSdpTest, SrsSdpUpdateMsid) +{ + srs_error_t err = srs_success; + + SrsSdp sdp; + + // Parse SDP with media descriptions and SSRC info + std::string sdp_str = + "v=0\r\n" + "o=- 123456 654321 IN IP4 127.0.0.1\r\n" + "s=Test Session\r\n" + "t=0 0\r\n" + "m=video 9 RTP/AVP 96\r\n" + "a=ssrc:12345 cname:video_cname\r\n" + "m=audio 9 RTP/AVP 97\r\n" + "a=ssrc:67890 cname:audio_cname\r\n"; + + HELPER_EXPECT_SUCCESS(sdp.parse(sdp_str)); + + // Update MSID + HELPER_EXPECT_SUCCESS(sdp.update_msid("new_stream_id")); + + // Verify global MSID was updated + EXPECT_EQ(1, (int)sdp.msids_.size()); + EXPECT_STREQ("new_stream_id", sdp.msids_[0].c_str()); + + // Verify all media descriptions were updated + std::vector video_descs = sdp.find_media_descs("video"); + std::vector audio_descs = sdp.find_media_descs("audio"); + + EXPECT_STREQ("new_stream_id", video_descs[0]->ssrc_infos_[0].msid_.c_str()); + EXPECT_STREQ("new_stream_id", video_descs[0]->ssrc_infos_[0].mslabel_.c_str()); + EXPECT_STREQ("new_stream_id", audio_descs[0]->ssrc_infos_[0].msid_.c_str()); + EXPECT_STREQ("new_stream_id", audio_descs[0]->ssrc_infos_[0].mslabel_.c_str()); +}