diff --git a/trunk/configure b/trunk/configure index ca4cdfb6b..efa9aaba9 100755 --- a/trunk/configure +++ b/trunk/configure @@ -381,7 +381,7 @@ if [[ $SRS_UTEST == YES ]]; then "srs_utest_protocol" "srs_utest_protocol2" "srs_utest_kernel2" "srs_utest_protocol3" "srs_utest_st" "srs_utest_rtc2" "srs_utest_rtc3" "srs_utest_fmp4" "srs_utest_source_lock" "srs_utest_stream_token" "srs_utest_rtc_recv_track" "srs_utest_st2" "srs_utest_hevc_structs" - "srs_utest_coworkers" "srs_utest_pithy_print" "srs_utest_kernel3") + "srs_utest_coworkers" "srs_utest_pithy_print" "srs_utest_kernel3" "srs_utest_protocol4") # Always include SRT utest MODULE_FILES+=("srs_utest_srt") if [[ $SRS_GB28181 == YES ]]; then diff --git a/trunk/research/httpmock/go.mod b/trunk/research/httpmock/go.mod new file mode 100644 index 000000000..bb2475114 --- /dev/null +++ b/trunk/research/httpmock/go.mod @@ -0,0 +1,3 @@ +module httpmock + +go 1.23.0 diff --git a/trunk/research/httpmock/main.go b/trunk/research/httpmock/main.go new file mode 100644 index 000000000..148a21a5b --- /dev/null +++ b/trunk/research/httpmock/main.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println("Hello, mock HTTP") +} diff --git a/trunk/research/httpmock/main_test.go b/trunk/research/httpmock/main_test.go new file mode 100644 index 000000000..0afe45642 --- /dev/null +++ b/trunk/research/httpmock/main_test.go @@ -0,0 +1,24 @@ +package main + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func TestHttpMock(t *testing.T) { + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("Hello, World!")) + })) + defer s.Close() + + resp, err := http.Get(s.URL) + if err != nil { + t.Fatal(err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + t.Fatalf("Expected status code %d, got %d", http.StatusOK, resp.StatusCode) + } +} diff --git a/trunk/src/kernel/srs_kernel_utility.cpp b/trunk/src/kernel/srs_kernel_utility.cpp index 1653b9e01..a549dfcc0 100644 --- a/trunk/src/kernel/srs_kernel_utility.cpp +++ b/trunk/src/kernel/srs_kernel_utility.cpp @@ -701,6 +701,11 @@ long srs_rand_integer() return random(); } +long srs_rand_integer(long min, long max) +{ + return min + (srs_rand_integer() % (max - min + 1)); +} + bool srs_is_digit_number(string str) { if (str.empty()) { diff --git a/trunk/src/kernel/srs_kernel_utility.hpp b/trunk/src/kernel/srs_kernel_utility.hpp index 56340e602..5f89a2024 100644 --- a/trunk/src/kernel/srs_kernel_utility.hpp +++ b/trunk/src/kernel/srs_kernel_utility.hpp @@ -160,6 +160,7 @@ extern std::string srs_rand_gen_str(int len); // Generate random value, use srandom(now_us) to init seed if not initialized. extern long srs_rand_integer(); +extern long srs_rand_integer(long min, long max); // Whether string is digit number // is_digit("0") is true diff --git a/trunk/src/protocol/srs_protocol_log.cpp b/trunk/src/protocol/srs_protocol_log.cpp index 9bea66872..c2d20f586 100644 --- a/trunk/src/protocol/srs_protocol_log.cpp +++ b/trunk/src/protocol/srs_protocol_log.cpp @@ -93,65 +93,6 @@ const SrsContextId &srs_context_set_cid_of(srs_thread_t trd, const SrsContextId return v; } -// LCOV_EXCL_START -SrsConsoleLog::SrsConsoleLog(SrsLogLevel l, bool u) -{ - level_ = l; - utc_ = u; - - buffer_ = new char[SRS_BASIC_LOG_SIZE]; -} - -SrsConsoleLog::~SrsConsoleLog() -{ - srs_freepa(buffer_); -} - -srs_error_t SrsConsoleLog::initialize() -{ - return srs_success; -} - -void SrsConsoleLog::reopen() -{ -} - -void SrsConsoleLog::log(SrsLogLevel level, const char *tag, const SrsContextId &context_id, const char *fmt, va_list args) -{ - if (level < level_ || level >= SrsLogLevelDisabled) { - return; - } - - int size = 0; - if (!srs_log_header(buffer_, SRS_BASIC_LOG_SIZE, utc_, level >= SrsLogLevelWarn, tag, context_id, srs_log_level_strings[level], &size)) { - return; - } - - // Something not expected, drop the log. - int r0 = vsnprintf(buffer_ + size, SRS_BASIC_LOG_SIZE - size, fmt, args); - if (r0 <= 0 || r0 >= SRS_BASIC_LOG_SIZE - size) { - return; - } - size += r0; - - // Add errno and strerror() if error. - if (level == SrsLogLevelError && errno != 0) { - r0 = snprintf(buffer_ + size, SRS_BASIC_LOG_SIZE - size, "(%s)", strerror(errno)); - - // Something not expected, drop the log. - if (r0 <= 0 || r0 >= SRS_BASIC_LOG_SIZE - size) { - return; - } - size += r0; - } - - if (level >= SrsLogLevelWarn) { - fprintf(stderr, "%s\n", buffer_); - } else { - fprintf(stdout, "%s\n", buffer_); - } -} - bool srs_log_header(char *buffer, int size, bool utc, bool dangerous, const char *tag, SrsContextId cid, const char *level, int *psize) { // clock time diff --git a/trunk/src/protocol/srs_protocol_log.hpp b/trunk/src/protocol/srs_protocol_log.hpp index 350dde9b4..015cb9f64 100644 --- a/trunk/src/protocol/srs_protocol_log.hpp +++ b/trunk/src/protocol/srs_protocol_log.hpp @@ -38,26 +38,6 @@ private: // Set the context id of specified thread, not self. extern const SrsContextId &srs_context_set_cid_of(srs_thread_t trd, const SrsContextId &v); -// The basic console log, which write log to console. -class SrsConsoleLog : public ISrsLog -{ -private: - SrsLogLevel level_; - bool utc_; - -private: - char *buffer_; - -public: - SrsConsoleLog(SrsLogLevel l, bool u); - virtual ~SrsConsoleLog(); - // Interface ISrsLog -public: - virtual srs_error_t initialize(); - virtual void reopen(); - virtual void log(SrsLogLevel level, const char *tag, const SrsContextId &context_id, const char *fmt, va_list args); -}; - // Generate the log header. // @param dangerous Whether log is warning or error, log the errno if true. // @param utc Whether use UTC time format in the log header. diff --git a/trunk/src/utest/srs_utest.cpp b/trunk/src/utest/srs_utest.cpp index abfa0984d..4aae55032 100644 --- a/trunk/src/utest/srs_utest.cpp +++ b/trunk/src/utest/srs_utest.cpp @@ -25,6 +25,15 @@ using namespace std; #include #include +// For RTMP test server +#include +#include +#include + +// For TCP test server and client +#include +#include + // Temporary disk config. std::string _srs_tmp_file_prefix = "/tmp/srs-utest-"; // Temporary network config. @@ -262,6 +271,7 @@ int MockProtectedBuffer::alloc(int size) SrsCoroutineChan::SrsCoroutineChan() { + trd_ = NULL; lock_ = srs_mutex_new(); } @@ -293,5 +303,717 @@ SrsCoroutineChan *SrsCoroutineChan::copy() SrsCoroutineChan *cp = new SrsCoroutineChan(); cp->args_ = args_; + cp->trd_ = trd_; return cp; } + +extern string mock_http_response(int status, string content); + +SrsHttpTestServer::SrsHttpTestServer(string response_body) : response_body_(response_body) +{ + trd_ = new SrsSTCoroutine("http-test", this); + fd_ = NULL; + ip_ = "127.0.0.1"; + // Generate random port in range [30000, 60000] + port_ = srs_rand_integer(30000, 60000); +} + +SrsHttpTestServer::~SrsHttpTestServer() +{ + close(); + srs_freep(trd_); + srs_close_stfd(fd_); +} + +srs_error_t SrsHttpTestServer::start() +{ + srs_error_t err = srs_success; + + if ((err = srs_tcp_listen(ip_, port_, &fd_)) != srs_success) { + return srs_error_wrap(err, "listen %s:%d", ip_.c_str(), port_); + } + + return trd_->start(); +} + +void SrsHttpTestServer::close() +{ + if (trd_) { + trd_->stop(); + } + srs_close_stfd(fd_); +} + +string SrsHttpTestServer::url() +{ + return "http://" + ip_ + ":" + srs_strconv_format_int(port_); +} + +int SrsHttpTestServer::get_port() +{ + return port_; +} + +srs_error_t SrsHttpTestServer::cycle() +{ + srs_error_t err = srs_success; + + srs_netfd_t cfd = srs_accept(fd_, NULL, NULL, SRS_UTIME_NO_TIMEOUT); + if (cfd == NULL) { + return err; + } + + err = do_cycle(cfd); + srs_close_stfd(cfd); + srs_freep(err); + + return err; +} + +srs_error_t SrsHttpTestServer::do_cycle(srs_netfd_t cfd) +{ + srs_error_t err = srs_success; + + SrsStSocket skt(cfd); + skt.set_recv_timeout(1 * SRS_UTIME_SECONDS); + skt.set_send_timeout(1 * SRS_UTIME_SECONDS); + + while (true) { + if ((err = trd_->pull()) != srs_success) { + return err; + } + + char buf[1024]; + if ((err = skt.read(buf, 1024, NULL)) != srs_success) { + return err; + } + + // Generate proper HTTP response + string res = mock_http_response(200, response_body_); + if ((err = skt.write((char *)res.data(), (int)res.length(), NULL)) != srs_success) { + return err; + } + } + + return err; +} + +SrsHttpsTestServer::SrsHttpsTestServer(string response_body, string key_file, string cert_file) + : response_body_(response_body), ssl_key_file_(key_file), ssl_cert_file_(cert_file) +{ + trd_ = new SrsFastCoroutine("https-test", this); + fd_ = NULL; + ip_ = "127.0.0.1"; + // Generate random port in range [30000, 60000] + port_ = srs_rand_integer(30000, 60000); +} + +SrsHttpsTestServer::~SrsHttpsTestServer() +{ + close(); + srs_freep(trd_); +} + +srs_error_t SrsHttpsTestServer::start() +{ + srs_error_t err = srs_success; + + if ((err = srs_tcp_listen(ip_, port_, &fd_)) != srs_success) { + return srs_error_wrap(err, "listen %s:%d", ip_.c_str(), port_); + } + + if ((err = trd_->start()) != srs_success) { + return srs_error_wrap(err, "start coroutine"); + } + + return err; +} + +void SrsHttpsTestServer::close() +{ + if (trd_) { + trd_->stop(); + } + if (fd_) { + srs_close_stfd(fd_); + fd_ = NULL; + } +} + +string SrsHttpsTestServer::url() +{ + return "https://" + ip_ + ":" + srs_strconv_format_int(port_); +} + +int SrsHttpsTestServer::get_port() +{ + return port_; +} + +srs_error_t SrsHttpsTestServer::cycle() +{ + srs_error_t err = srs_success; + + while (true) { + if ((err = trd_->pull()) != srs_success) { + return srs_error_wrap(err, "pull"); + } + + srs_netfd_t client_fd = srs_accept(fd_, NULL, NULL, SRS_UTIME_NO_TIMEOUT); + if (client_fd == NULL) { + return srs_error_new(ERROR_SOCKET_ACCEPT, "accept failed"); + } + + if ((err = handle_client(client_fd)) != srs_success) { + srs_warn("handle client failed, err=%s", srs_error_desc(err).c_str()); + srs_freep(err); + } + } + + return err; +} + +srs_error_t SrsHttpsTestServer::handle_client(srs_netfd_t client_fd) +{ + srs_error_t err = srs_success; + + SrsStSocket *skt = new SrsStSocket(client_fd); + SrsUniquePtr skt_uptr(skt); + + // Create SSL connection + SrsSslConnection *ssl = new SrsSslConnection(skt); + SrsUniquePtr ssl_uptr(ssl); + + // Perform SSL handshake + if ((err = ssl->handshake(ssl_key_file_, ssl_cert_file_)) != srs_success) { + return srs_error_wrap(err, "ssl handshake"); + } + + // Read HTTP request (simplified - just read some data) + char buf[4096]; + ssize_t nread = 0; + if ((err = ssl->read(buf, sizeof(buf), &nread)) != srs_success) { + return srs_error_wrap(err, "read request"); + } + + // Send HTTP response + string response = mock_http_response(200, response_body_); + if ((err = ssl->write((void *)response.data(), response.length(), NULL)) != srs_success) { + return srs_error_wrap(err, "write response"); + } + + return err; +} + +SrsRtmpTestServer::SrsRtmpTestServer(string app, string stream) : app_(app), stream_(stream) +{ + trd_ = new SrsSTCoroutine("rtmp-test", this); + fd_ = NULL; + ip_ = "127.0.0.1"; + enable_publish_ = true; + enable_play_ = true; + // Generate random port in range [30000, 60000] + port_ = srs_rand_integer(30000, 60000); +} + +SrsRtmpTestServer::~SrsRtmpTestServer() +{ + close(); + srs_freep(trd_); + srs_close_stfd(fd_); +} + +srs_error_t SrsRtmpTestServer::start() +{ + srs_error_t err = srs_success; + + if ((err = srs_tcp_listen(ip_, port_, &fd_)) != srs_success) { + return srs_error_wrap(err, "listen %s:%d", ip_.c_str(), port_); + } + + return trd_->start(); +} + +void SrsRtmpTestServer::close() +{ + if (trd_) { + trd_->stop(); + } + srs_close_stfd(fd_); +} + +string SrsRtmpTestServer::url() +{ + return "rtmp://" + ip_ + ":" + srs_strconv_format_int(port_) + "/" + app_ + "/" + stream_; +} + +int SrsRtmpTestServer::get_port() +{ + return port_; +} + +void SrsRtmpTestServer::enable_publish(bool v) +{ + enable_publish_ = v; +} + +void SrsRtmpTestServer::enable_play(bool v) +{ + enable_play_ = v; +} + +srs_error_t SrsRtmpTestServer::cycle() +{ + srs_error_t err = srs_success; + + srs_netfd_t cfd = srs_accept(fd_, NULL, NULL, SRS_UTIME_NO_TIMEOUT); + if (cfd == NULL) { + return err; + } + + err = do_cycle(cfd); + srs_close_stfd(cfd); + srs_freep(err); + + return err; +} + +srs_error_t SrsRtmpTestServer::do_cycle(srs_netfd_t cfd) +{ + return handle_rtmp_client(cfd); +} + +srs_error_t SrsRtmpTestServer::handle_rtmp_client(srs_netfd_t cfd) +{ + srs_error_t err = srs_success; + + SrsStSocket skt(cfd); + skt.set_recv_timeout(5 * SRS_UTIME_SECONDS); + skt.set_send_timeout(5 * SRS_UTIME_SECONDS); + + // Create RTMP server to handle the client + SrsRtmpServer rtmp(&skt); + + // Perform RTMP handshake + if ((err = rtmp.handshake()) != srs_success) { + return srs_error_wrap(err, "rtmp handshake"); + } + + // Handle connect app + SrsRequest req; + if ((err = rtmp.connect_app(&req)) != srs_success) { + return srs_error_wrap(err, "rtmp connect app"); + } + + // Respond to connect app + if ((err = rtmp.response_connect_app(&req, ip_.c_str())) != srs_success) { + return srs_error_wrap(err, "rtmp response connect app"); + } + + // Set window ack size + if ((err = rtmp.set_window_ack_size(2500000)) != srs_success) { + return srs_error_wrap(err, "rtmp set window ack size"); + } + + // Set peer bandwidth + if ((err = rtmp.set_peer_bandwidth(2500000, 2)) != srs_success) { + return srs_error_wrap(err, "rtmp set peer bandwidth"); + } + + // Send onBWDone + if ((err = rtmp.on_bw_done()) != srs_success) { + return srs_error_wrap(err, "rtmp on bw done"); + } + + // Identify client (play or publish) + int stream_id = 1; // Use a fixed stream ID for testing + SrsRtmpConnType type = SrsRtmpConnUnknown; + string stream_name; + srs_utime_t duration = 0; + + if ((err = rtmp.identify_client(stream_id, type, stream_name, duration)) != srs_success) { + return srs_error_wrap(err, "rtmp identify client"); + } + + // Set chunk size + if ((err = rtmp.set_chunk_size(4096)) != srs_success) { + return srs_error_wrap(err, "rtmp set chunk size"); + } + + // Handle based on client type + if (srs_client_type_is_publish(type)) { + if (!enable_publish_) { + return srs_error_new(ERROR_RTMP_ACCESS_DENIED, "publish not enabled"); + } + // For publish, we just accept it and don't send any response + // The client will start sending media data + } else { + if (!enable_play_) { + return srs_error_new(ERROR_RTMP_ACCESS_DENIED, "play not enabled"); + } + // For play, send play start response + if ((err = rtmp.start_play(stream_id)) != srs_success) { + return srs_error_wrap(err, "rtmp start play"); + } + } + + return err; +} + +SrsTestTcpServer::SrsTestTcpServer(string ip) +{ + trd_ = NULL; + listener_ = NULL; + conn_ = NULL; + ip_ = ip; + // Generate random port in range [30000, 60000] + port_ = 30000 + (srs_rand_integer() % (60000 - 30000 + 1)); +} + +SrsTestTcpServer::~SrsTestTcpServer() +{ + close(); + srs_freep(conn_); +} + +srs_error_t SrsTestTcpServer::start() +{ + srs_error_t err = srs_success; + + listener_ = new SrsTcpListener(this); + listener_->set_endpoint(ip_, port_); + + if ((err = listener_->listen()) != srs_success) { + return srs_error_wrap(err, "tcp listen %s:%d", ip_.c_str(), port_); + } + + // Get the actual port that was assigned + port_ = listener_->port(); + + trd_ = new SrsSTCoroutine("tcp-test", this); + if ((err = trd_->start()) != srs_success) { + return srs_error_wrap(err, "start tcp test server"); + } + + return err; +} + +void SrsTestTcpServer::close() +{ + if (listener_) { + listener_->close(); + srs_freep(listener_); + } + + if (trd_) { + trd_->stop(); + srs_freep(trd_); + } +} + +int SrsTestTcpServer::get_port() +{ + return port_; +} + +SrsTcpConnection *SrsTestTcpServer::get_connection() +{ + return conn_; +} + +srs_error_t SrsTestTcpServer::cycle() +{ + srs_error_t err = srs_success; + + while (true) { + if ((err = trd_->pull()) != srs_success) { + return srs_error_wrap(err, "tcp test server"); + } + + // Just wait for connections, the listener handles them via on_tcp_client + srs_usleep(10 * SRS_UTIME_MILLISECONDS); + } + + return err; +} + +srs_error_t SrsTestTcpServer::on_tcp_client(ISrsListener *listener, srs_netfd_t stfd) +{ + srs_freep(conn_); + conn_ = new SrsTcpConnection(stfd); + return srs_success; +} + +SrsTestTcpClient::SrsTestTcpClient(string host, int port, srs_utime_t timeout) +{ + client_ = NULL; + conn_ = NULL; + host_ = host; + port_ = port; + timeout_ = timeout; +} + +SrsTestTcpClient::~SrsTestTcpClient() +{ + close(); +} + +srs_error_t SrsTestTcpClient::connect() +{ + srs_error_t err = srs_success; + + close(); // Close any existing connection + + client_ = new SrsTcpClient(host_, port_, timeout_); + if ((err = client_->connect()) != srs_success) { + return srs_error_wrap(err, "tcp client connect %s:%d", host_.c_str(), port_); + } + + // Create SrsTcpConnection from the connected client + // We need to get the file descriptor from the client + // Since SrsTcpClient doesn't expose the fd directly, we'll create a new connection + srs_netfd_t stfd = NULL; + if ((err = srs_tcp_connect(host_, port_, timeout_, &stfd)) != srs_success) { + return srs_error_wrap(err, "tcp connect for connection %s:%d", host_.c_str(), port_); + } + + conn_ = new SrsTcpConnection(stfd); + + return err; +} + +void SrsTestTcpClient::close() +{ + srs_freep(client_); + srs_freep(conn_); +} + +SrsTcpConnection *SrsTestTcpClient::get_connection() +{ + return conn_; +} + +srs_error_t SrsTestTcpClient::write(void *buf, size_t size, ssize_t *nwrite) +{ + if (!client_) { + return srs_error_new(ERROR_SOCKET_WRITE, "client not connected"); + } + return client_->write(buf, size, nwrite); +} + +srs_error_t SrsTestTcpClient::read(void *buf, size_t size, ssize_t *nread) +{ + if (!client_) { + return srs_error_new(ERROR_SOCKET_READ, "client not connected"); + } + return client_->read(buf, size, nread); +} + +SrsUdpTestServer::SrsUdpTestServer(string host) +{ + host_ = host; + lfd_ = NULL; + trd_ = NULL; + socket_ = NULL; + started_ = false; + // Generate random port in range [30000, 60000] + port_ = 30000 + (srs_rand_integer() % (60000 - 30000 + 1)); +} + +SrsUdpTestServer::~SrsUdpTestServer() +{ + stop(); + srs_freep(socket_); + srs_close_stfd(lfd_); +} + +srs_error_t SrsUdpTestServer::start() +{ + srs_error_t err = srs_success; + + if (started_) { + return err; + } + + // Create UDP socket + if ((err = srs_udp_listen(host_, port_, &lfd_)) != srs_success) { + return srs_error_wrap(err, "udp listen %s:%d", host_.c_str(), port_); + } + + // Get the actual port that was assigned + int actual_fd = srs_netfd_fileno(lfd_); + sockaddr_storage addr; + socklen_t addrlen = sizeof(addr); + if (getsockname(actual_fd, (sockaddr *)&addr, &addrlen) == 0) { + if (addr.ss_family == AF_INET) { + port_ = ntohs(((sockaddr_in *)&addr)->sin_port); + } else if (addr.ss_family == AF_INET6) { + port_ = ntohs(((sockaddr_in6 *)&addr)->sin6_port); + } + } + + // Create socket wrapper + socket_ = new SrsStSocket(lfd_); + + // Start coroutine to handle packets + trd_ = new SrsSTCoroutine("udp-test", this); + if ((err = trd_->start()) != srs_success) { + return srs_error_wrap(err, "start udp test server"); + } + + started_ = true; + return err; +} + +void SrsUdpTestServer::stop() +{ + started_ = false; + if (trd_) { + trd_->stop(); + srs_freep(trd_); + } +} + +int SrsUdpTestServer::get_port() +{ + return port_; +} + +SrsStSocket *SrsUdpTestServer::get_socket() +{ + return socket_; +} + +srs_error_t SrsUdpTestServer::cycle() +{ + srs_error_t err = srs_success; + + while (started_) { + if ((err = trd_->pull()) != srs_success) { + return srs_error_wrap(err, "udp test server"); + } + + // Simple echo server - receive and echo back using recvfrom/sendto + char buf[1024]; + sockaddr_storage from; + int fromlen = sizeof(from); + + ssize_t nread = srs_recvfrom(lfd_, buf, sizeof(buf), (sockaddr *)&from, &fromlen, 10 * SRS_UTIME_MILLISECONDS); + if (nread <= 0) { + continue; // Timeout or error, continue + } + + // Echo back the data + ssize_t nwrite = srs_sendto(lfd_, buf, nread, (sockaddr *)&from, fromlen, 10 * SRS_UTIME_MILLISECONDS); + if (nwrite <= 0) { + continue; // Error sending, continue + } + } + + return err; +} + +SrsUdpTestClient::SrsUdpTestClient(string host, int port, srs_utime_t timeout) +{ + host_ = host; + port_ = port; + timeout_ = timeout; + stfd_ = NULL; + socket_ = NULL; + memset(&server_addr_, 0, sizeof(server_addr_)); + server_addrlen_ = 0; +} + +SrsUdpTestClient::~SrsUdpTestClient() +{ + close(); +} + +srs_error_t SrsUdpTestClient::connect() +{ + srs_error_t err = srs_success; + + close(); + + // Create UDP socket + int sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock == -1) { + return srs_error_new(ERROR_SOCKET_CREATE, "create udp socket"); + } + + stfd_ = srs_netfd_open_socket(sock); + if (stfd_ == NULL) { + ::close(sock); + return srs_error_new(ERROR_ST_OPEN_SOCKET, "open udp socket"); + } + + // Setup server address + sockaddr_in *addr = (sockaddr_in *)&server_addr_; + addr->sin_family = AF_INET; + addr->sin_port = htons(port_); + if (inet_pton(AF_INET, host_.c_str(), &addr->sin_addr) <= 0) { + close(); + return srs_error_new(ERROR_SYSTEM_IP_INVALID, "invalid ip %s", host_.c_str()); + } + server_addrlen_ = sizeof(sockaddr_in); + + // Create socket wrapper + socket_ = new SrsStSocket(stfd_); + socket_->set_recv_timeout(timeout_); + socket_->set_send_timeout(timeout_); + + return err; +} + +void SrsUdpTestClient::close() +{ + srs_freep(socket_); + srs_close_stfd(stfd_); +} + +SrsStSocket *SrsUdpTestClient::get_socket() +{ + return socket_; +} + +srs_error_t SrsUdpTestClient::sendto(void *buf, size_t size, ssize_t *nwrite) +{ + if (!stfd_) { + return srs_error_new(ERROR_SOCKET_WRITE, "udp client not connected"); + } + + ssize_t nb_write = srs_sendto(stfd_, buf, size, (sockaddr *)&server_addr_, server_addrlen_, timeout_); + if (nb_write <= 0) { + if (nb_write < 0 && errno == ETIME) { + return srs_error_new(ERROR_SOCKET_TIMEOUT, "sendto timeout %d ms", srsu2msi(timeout_)); + } + return srs_error_new(ERROR_SOCKET_WRITE, "sendto failed"); + } + + if (nwrite) { + *nwrite = nb_write; + } + return srs_success; +} + +srs_error_t SrsUdpTestClient::recvfrom(void *buf, size_t size, ssize_t *nread) +{ + if (!stfd_) { + return srs_error_new(ERROR_SOCKET_READ, "udp client not connected"); + } + + sockaddr_storage from; + int fromlen = sizeof(from); + ssize_t nb_read = srs_recvfrom(stfd_, buf, size, (sockaddr *)&from, &fromlen, timeout_); + if (nb_read <= 0) { + if (nb_read < 0 && errno == ETIME) { + return srs_error_new(ERROR_SOCKET_TIMEOUT, "recvfrom timeout %d ms", srsu2msi(timeout_)); + } + return srs_error_new(ERROR_SOCKET_READ, "recvfrom failed"); + } + + if (nread) { + *nread = nb_read; + } + return srs_success; +} diff --git a/trunk/src/utest/srs_utest.hpp b/trunk/src/utest/srs_utest.hpp index dfb9fa1ed..4cc9bccd3 100644 --- a/trunk/src/utest/srs_utest.hpp +++ b/trunk/src/utest/srs_utest.hpp @@ -28,6 +28,10 @@ using namespace std; #include #include +// Include headers for TCP test classes +#include +#include + // we add an empty macro for upp to show the smart tips. #define VOID @@ -139,6 +143,10 @@ private: std::vector args_; srs_mutex_t lock_; +public: + // The thread to run the coroutine. + ISrsCoroutine *trd_; + public: SrsCoroutineChan(); virtual ~SrsCoroutineChan(); @@ -192,36 +200,32 @@ public: // // The coroutine will be stopped and wait for it to terminate. // // So maybe it won't execute all your code there. // -// Enjoiy the sugar for coroutines. -#define SRS_COROUTINE_GO_IMPL(context, id, code_block) \ - class AnonymousCoroutineHandler##id : public ISrsCoroutineHandler \ - { \ - private: \ - SrsCoroutineChan *ctx_; \ - \ - public: \ - AnonymousCoroutineHandler##id(SrsCoroutineChan *c) \ - { \ - /* Copy the context so that we can pop it in different coroutines. */ \ - ctx_ = c->copy(); \ - } \ - ~AnonymousCoroutineHandler##id() \ - { \ - srs_freep(ctx_); \ - } \ - \ - public: \ - virtual srs_error_t cycle() \ - { \ - SrsCoroutineChan &ctx = *ctx_; \ - (void)ctx; \ - code_block; \ - return srs_success; \ - } \ - }; \ - AnonymousCoroutineHandler##id handler##id(context); \ - SrsSTCoroutine st##id("anonymous", &handler##id); \ - srs_error_t err_coroutine##id = st##id.start(); \ +// Warning: Donot use this macro unless you don't need to debug the code block, +// because it's impossible to debug it. Accordingly, you should use it when the +// code block is very simple. +#define SRS_COROUTINE_GO_IMPL(context, id, code_block) \ + class AnonymousCoroutineHandler##id : public ISrsCoroutineHandler \ + { \ + private: \ + SrsCoroutineChan *ctx_; \ + \ + public: \ + AnonymousCoroutineHandler##id() : ctx_(NULL) {} \ + ~AnonymousCoroutineHandler##id() { srs_freep(ctx_); } \ + void set_ctx(SrsCoroutineChan *c) { ctx_ = c->copy(); } \ + virtual srs_error_t cycle() \ + { \ + SrsCoroutineChan &ctx = *ctx_; \ + (void)ctx; \ + code_block; \ + return srs_success; \ + } \ + }; \ + AnonymousCoroutineHandler##id handler##id; \ + SrsSTCoroutine st##id("anonymous", &handler##id); \ + (context)->trd_ = &st##id; \ + handler##id.set_ctx(context); \ + srs_error_t err_coroutine##id = st##id.start(); \ srs_assert(err_coroutine##id == srs_success) // A helper to create a anonymous coroutine like goroutine in Go. @@ -274,4 +278,197 @@ public: #define SRS_COROUTINE_GO_CTX2(ctx, id, code_block) \ SRS_COROUTINE_GO_IMPL(ctx, id, code_block) +// Simple HTTP test server similar to Go's httptest.NewServer +// This is a simplified version that uses the raw socket approach like MockOnCycleThread4 +// but with proper HTTP response formatting +class SrsHttpTestServer : public ISrsCoroutineHandler +{ +private: + ISrsCoroutine *trd_; + srs_netfd_t fd_; + string response_body_; + string ip_; + int port_; + +public: + SrsHttpTestServer(string response_body); + virtual ~SrsHttpTestServer(); + +public: + virtual srs_error_t start(); + virtual void close(); + virtual string url(); + virtual int get_port(); + + // Interface ISrsCoroutineHandler +public: + virtual srs_error_t cycle(); + +private: + virtual srs_error_t do_cycle(srs_netfd_t cfd); +}; + +// Simple HTTPS test server similar to Go's httptest.NewServer but with SSL support +class SrsHttpsTestServer : public ISrsCoroutineHandler +{ +private: + ISrsCoroutine *trd_; + srs_netfd_t fd_; + string response_body_; + string ssl_key_file_; + string ssl_cert_file_; + string ip_; + int port_; + +public: + SrsHttpsTestServer(string response_body, string key_file = "./conf/server.key", string cert_file = "./conf/server.crt"); + virtual ~SrsHttpsTestServer(); + virtual srs_error_t start(); + virtual void close(); + virtual string url(); + virtual int get_port(); + + // Interface ISrsCoroutineHandler +private: + virtual srs_error_t cycle(); + +private: + srs_error_t handle_client(srs_netfd_t client_fd); +}; + +// Simple RTMP test server similar to SrsHttpTestServer but for RTMP protocol +// This server handles basic RTMP handshake and connect app operations +class SrsRtmpTestServer : public ISrsCoroutineHandler +{ +private: + ISrsCoroutine *trd_; + srs_netfd_t fd_; + string app_; + string stream_; + string ip_; + int port_; + bool enable_publish_; + bool enable_play_; + +public: + SrsRtmpTestServer(string app = "live", string stream = "test"); + virtual ~SrsRtmpTestServer(); + +public: + virtual srs_error_t start(); + virtual void close(); + virtual string url(); + virtual int get_port(); + virtual void enable_publish(bool v = true); + virtual void enable_play(bool v = true); + + // Interface ISrsCoroutineHandler +public: + virtual srs_error_t cycle(); + +private: + virtual srs_error_t do_cycle(srs_netfd_t cfd); + virtual srs_error_t handle_rtmp_client(srs_netfd_t cfd); +}; + +// Test TCP server for testing SrsTcpConnection +class SrsTestTcpServer : public ISrsCoroutineHandler, public ISrsTcpHandler +{ +private: + ISrsCoroutine *trd_; + SrsTcpListener *listener_; + string ip_; + int port_; + SrsTcpConnection *conn_; + +public: + SrsTestTcpServer(string ip = "127.0.0.1"); + virtual ~SrsTestTcpServer(); + +public: + virtual srs_error_t start(); + virtual void close(); + virtual int get_port(); + virtual SrsTcpConnection *get_connection(); + + // Interface ISrsCoroutineHandler +public: + virtual srs_error_t cycle(); + + // Interface ISrsTcpHandler +public: + virtual srs_error_t on_tcp_client(ISrsListener *listener, srs_netfd_t stfd); +}; + +// Test TCP client for testing SrsTcpConnection +class SrsTestTcpClient +{ +private: + SrsTcpClient *client_; + SrsTcpConnection *conn_; + string host_; + int port_; + srs_utime_t timeout_; + +public: + SrsTestTcpClient(string host, int port, srs_utime_t timeout = 1 * SRS_UTIME_SECONDS); + virtual ~SrsTestTcpClient(); + +public: + virtual srs_error_t connect(); + virtual void close(); + virtual SrsTcpConnection *get_connection(); + virtual srs_error_t write(void *buf, size_t size, ssize_t *nwrite); + virtual srs_error_t read(void *buf, size_t size, ssize_t *nread); +}; + +// Test UDP server for testing UDP socket communication +class SrsUdpTestServer : public ISrsCoroutineHandler +{ +private: + srs_netfd_t lfd_; + ISrsCoroutine *trd_; + SrsStSocket *socket_; + string host_; + int port_; + bool started_; + +public: + SrsUdpTestServer(string host); + virtual ~SrsUdpTestServer(); + +public: + virtual srs_error_t start(); + virtual void stop(); + virtual int get_port(); + virtual SrsStSocket *get_socket(); + +public: + virtual srs_error_t cycle(); +}; + +// Test UDP client for testing UDP socket communication +class SrsUdpTestClient +{ +private: + srs_netfd_t stfd_; + SrsStSocket *socket_; + string host_; + int port_; + srs_utime_t timeout_; + sockaddr_storage server_addr_; + int server_addrlen_; + +public: + SrsUdpTestClient(string host, int port, srs_utime_t timeout = 1 * SRS_UTIME_SECONDS); + virtual ~SrsUdpTestClient(); + +public: + virtual srs_error_t connect(); + virtual void close(); + virtual SrsStSocket *get_socket(); + virtual srs_error_t sendto(void *buf, size_t size, ssize_t *nwrite); + virtual srs_error_t recvfrom(void *buf, size_t size, ssize_t *nread); +}; + #endif diff --git a/trunk/src/utest/srs_utest_protocol3.cpp b/trunk/src/utest/srs_utest_protocol3.cpp index 78cbfc1a0..42feb3171 100644 --- a/trunk/src/utest/srs_utest_protocol3.cpp +++ b/trunk/src/utest/srs_utest_protocol3.cpp @@ -11,6 +11,7 @@ using namespace std; #include #include #include +#include #include #include #include @@ -400,21 +401,668 @@ VOID TEST(ProtocolRawAvcTest, SrsRawHEVCStreamBasic) (void)is_pps3; } -VOID TEST(ProtocolHttpClientTest, SrsHttpClientBasic) +VOID TEST(ProtocolRawAvcTest, SrsRawHEVCStreamVpsDemux) { - SrsHttpClient client; + srs_error_t err = srs_success; - // Test basic initialization - should not crash - // We can't easily test actual HTTP requests without a server + SrsRawHEVCStream hevc; - // Test header setting - SrsHttpClient *result = client.set_header("User-Agent", "SRS-Test"); - EXPECT_TRUE(result != NULL); - EXPECT_EQ(&client, result); // Should return self for chaining + // Test VPS demux with valid VPS data + if (true) { + // Create VPS NALU data (NALU type 32 = 0x40) + unsigned char vps_data[] = {0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x5d, 0xac, 0x09}; + std::string vps_output; - // Test multiple headers - client.set_header("Content-Type", "application/json"); - client.set_header("Accept", "application/json"); + HELPER_EXPECT_SUCCESS(hevc.vps_demux((char *)vps_data, sizeof(vps_data), vps_output)); + + // Should copy the VPS data + EXPECT_EQ(sizeof(vps_data), vps_output.length()); + EXPECT_EQ(0, memcmp(vps_data, vps_output.data(), sizeof(vps_data))); + } + + // Test VPS demux with empty data - should fail + if (true) { + std::string vps_output; + HELPER_EXPECT_FAILED(hevc.vps_demux(NULL, 0, vps_output)); + } + + // Test VPS demux with minimal data + if (true) { + unsigned char minimal_vps[] = {0x40}; + std::string vps_output; + + HELPER_EXPECT_SUCCESS(hevc.vps_demux((char *)minimal_vps, sizeof(minimal_vps), vps_output)); + EXPECT_EQ(sizeof(minimal_vps), vps_output.length()); + } +} + +VOID TEST(ProtocolRawAvcTest, SrsRawHEVCStreamSpsDemux) +{ + srs_error_t err = srs_success; + + SrsRawHEVCStream hevc; + + // Test SPS demux with valid SPS data + if (true) { + // Create SPS NALU data (NALU type 33 = 0x42) + unsigned char sps_data[] = {0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x5d, 0xa0, 0x02, 0x80, 0x80, 0x2d, 0x16, 0x59, 0x59, 0xa4, 0x93, 0x2b, 0xc0, 0x5a, 0x70, 0x80, 0x00, 0x01, 0xf4, 0x80, 0x00, 0x5d, 0xc0, 0x47, 0xe1, 0x81, 0x65, 0x80}; + std::string sps_output; + + HELPER_EXPECT_SUCCESS(hevc.sps_demux((char *)sps_data, sizeof(sps_data), sps_output)); + + // Should copy the SPS data + EXPECT_EQ(sizeof(sps_data), sps_output.length()); + EXPECT_EQ(0, memcmp(sps_data, sps_output.data(), sizeof(sps_data))); + } + + // Test SPS demux with insufficient data (less than 4 bytes) - should succeed but return empty + if (true) { + unsigned char short_sps[] = {0x42, 0x01, 0x01}; + std::string sps_output; + + HELPER_EXPECT_SUCCESS(hevc.sps_demux((char *)short_sps, sizeof(short_sps), sps_output)); + EXPECT_TRUE(sps_output.empty()); + } + + // Test SPS demux with exactly 4 bytes + if (true) { + unsigned char min_sps[] = {0x42, 0x01, 0x01, 0x01}; + std::string sps_output; + + HELPER_EXPECT_SUCCESS(hevc.sps_demux((char *)min_sps, sizeof(min_sps), sps_output)); + EXPECT_EQ(sizeof(min_sps), sps_output.length()); + } +} + +VOID TEST(ProtocolRawAvcTest, SrsRawHEVCStreamPpsDemux) +{ + srs_error_t err = srs_success; + + SrsRawHEVCStream hevc; + + // Test PPS demux with valid PPS data + if (true) { + // Create PPS NALU data (NALU type 34 = 0x44) + unsigned char pps_data[] = {0x44, 0x01, 0xc1, 0x73, 0xd1, 0x89, 0x00}; + std::string pps_output; + + HELPER_EXPECT_SUCCESS(hevc.pps_demux((char *)pps_data, sizeof(pps_data), pps_output)); + + // Should copy the PPS data + EXPECT_EQ(sizeof(pps_data), pps_output.length()); + EXPECT_EQ(0, memcmp(pps_data, pps_output.data(), sizeof(pps_data))); + } + + // Test PPS demux with empty data - should fail + if (true) { + std::string pps_output; + HELPER_EXPECT_FAILED(hevc.pps_demux(NULL, 0, pps_output)); + } + + // Test PPS demux with minimal data + if (true) { + unsigned char minimal_pps[] = {0x44}; + std::string pps_output; + + HELPER_EXPECT_SUCCESS(hevc.pps_demux((char *)minimal_pps, sizeof(minimal_pps), pps_output)); + EXPECT_EQ(sizeof(minimal_pps), pps_output.length()); + } +} + +VOID TEST(ProtocolRawAvcTest, SrsRawHEVCStreamMuxSequenceHeader) +{ + srs_error_t err = srs_success; + + SrsRawHEVCStream hevc; + + // Test mux_sequence_header with valid HEVC VPS, SPS, and PPS data + if (true) { + // Create valid HEVC VPS data that will pass demux validation + // VPS NALU: forbidden_zero_bit(1) + nal_unit_type(6) + nuh_layer_id(6) + nuh_temporal_id_plus1(3) + RBSP + // NALU type 32 (VPS) = 0x40 (32 << 1), nuh_layer_id=0, nuh_temporal_id_plus1=1 + unsigned char vps_raw[] = { + 0x40, 0x01, // NALU header: type=32(VPS), layer_id=0, temporal_id=1 + 0x0C, 0x01, 0xFF, 0xFF, 0x01, 0x60, 0x00, 0x00, // VPS RBSP data + 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x03, 0x00, 0x5D, 0xAC, 0x09}; + std::string vps_data((char *)vps_raw, sizeof(vps_raw)); + + // Create valid HEVC SPS data that will pass demux validation + // SPS NALU: NALU type 33 (SPS) = 0x42 (33 << 1) + unsigned char sps_raw[] = { + 0x42, 0x01, // NALU header: type=33(SPS), layer_id=0, temporal_id=1 + 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x90, // SPS RBSP data + 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x5D, + 0xA0, 0x02, 0x80, 0x80, 0x2D, 0x16, 0x59, 0x59, + 0xA4, 0x93, 0x2B, 0xC0, 0x5A, 0x70, 0x80}; + std::string sps_data((char *)sps_raw, sizeof(sps_raw)); + + // Create valid HEVC PPS data + // PPS NALU: NALU type 34 (PPS) = 0x44 (34 << 1) + std::vector pps_list; + unsigned char pps_raw[] = { + 0x44, 0x01, // NALU header: type=34(PPS), layer_id=0, temporal_id=1 + 0xC1, 0x73, 0xD1, 0x89, 0x00 // PPS RBSP data + }; + pps_list.push_back(std::string((char *)pps_raw, sizeof(pps_raw))); + + std::string hvcC_output; + + // This should now succeed with valid HEVC data and cover the configuration record generation + HELPER_EXPECT_SUCCESS(hevc.mux_sequence_header(vps_data, sps_data, pps_list, hvcC_output)); + + // Should produce non-empty HEVCDecoderConfigurationRecord + EXPECT_FALSE(hvcC_output.empty()); + EXPECT_GT(hvcC_output.length(), 23); // Minimum HEVC configuration record size + + // Verify HEVCDecoderConfigurationRecord structure + const char *data = hvcC_output.data(); + EXPECT_EQ(0x01, (unsigned char)data[0]); // configurationVersion = 1 + + // Verify the configuration record contains our VPS, SPS, PPS data + // The exact structure depends on the implementation, but it should be significantly larger than header + EXPECT_GT(hvcC_output.length(), vps_data.length() + sps_data.length() + pps_list[0].length()); + } + + // Test mux_sequence_header with empty VPS - should fail + if (true) { + std::string empty_vps; + std::string sps_data = std::string("\x42\x01\x01\x01\x60", 5); + std::vector pps_list; + pps_list.push_back(std::string("\x44\x01", 2)); + std::string hvcC_output; + + srs_error_t mux_err = hevc.mux_sequence_header(empty_vps, sps_data, pps_list, hvcC_output); + HELPER_EXPECT_FAILED(mux_err); // Should fail due to empty VPS + } + + // Test mux_sequence_header with empty PPS list - should fail + if (true) { + std::string vps_data = std::string("\x40\x01\x0c", 3); + std::string sps_data = std::string("\x42\x01\x01\x01\x60", 5); + std::vector empty_pps_list; + std::string hvcC_output; + + srs_error_t mux_err = hevc.mux_sequence_header(vps_data, sps_data, empty_pps_list, hvcC_output); + HELPER_EXPECT_FAILED(mux_err); // Should fail due to empty PPS list + } + + // Test mux_sequence_header with multiple PPS + if (true) { + std::string vps_data = std::string("\x40\x01", 2); + std::string sps_data = std::string("\x42\x01\x01\x01", 4); + + std::vector pps_list; + pps_list.push_back(std::string("\x44\x01", 2)); + pps_list.push_back(std::string("\x44\x02", 2)); + pps_list.push_back(std::string("\x44\x03", 2)); + + std::string hvcC_output; + + // This may fail due to HEVC validation but shouldn't crash + srs_error_t mux_err = hevc.mux_sequence_header(vps_data, sps_data, pps_list, hvcC_output); + srs_freep(mux_err); // Don't assert success since HEVC validation is complex + + // Test passed if no crash occurred + EXPECT_TRUE(true); + } + + // Test mux_sequence_header with different HEVC profile/level to cover more configuration record generation + if (true) { + // Create VPS with different profile space and tier flag + unsigned char vps_raw2[] = { + 0x40, 0x01, // NALU header: type=32(VPS) + 0x2C, 0x01, 0xFF, 0xFF, 0x02, 0x20, 0x00, 0x00, // VPS RBSP with different profile_space=1, tier_flag=1 + 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x03, 0x00, 0x78, 0xAC, 0x09}; + std::string vps_data2((char *)vps_raw2, sizeof(vps_raw2)); + + // Create SPS with different parameters + unsigned char sps_raw2[] = { + 0x42, 0x01, // NALU header: type=33(SPS) + 0x02, 0x20, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, // SPS RBSP with different profile_idc + 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0xA0, + 0x03, 0x50, 0x80, 0x32, 0x16, 0x59, 0x59, 0xA4, + 0x93, 0x2B, 0xC0, 0x40, 0x40, 0x40, 0x80}; + std::string sps_data2((char *)sps_raw2, sizeof(sps_raw2)); + + std::vector pps_list2; + unsigned char pps_raw2[] = { + 0x44, 0x01, // NALU header: type=34(PPS) + 0xC2, 0x73, 0xD1, 0x89, 0x10 // PPS RBSP data + }; + pps_list2.push_back(std::string((char *)pps_raw2, sizeof(pps_raw2))); + + std::string hvcC_output2; + + // This should succeed and generate different configuration record + HELPER_EXPECT_SUCCESS(hevc.mux_sequence_header(vps_data2, sps_data2, pps_list2, hvcC_output2)); + + // Should produce non-empty HEVCDecoderConfigurationRecord + EXPECT_FALSE(hvcC_output2.empty()); + EXPECT_GT(hvcC_output2.length(), 23); // Minimum HEVC configuration record size + + // Verify HEVCDecoderConfigurationRecord structure + const char *data2 = hvcC_output2.data(); + EXPECT_EQ(0x01, (unsigned char)data2[0]); // configurationVersion = 1 + + // The second byte should contain profile_space, tier_flag, and profile_idc + // This tests the bit manipulation code: temp8bits |= ((hevc_info->general_profile_space_ & 0x03) << 6); + uint8_t profile_byte = (unsigned char)data2[1]; + uint8_t profile_space = (profile_byte >> 6) & 0x03; + uint8_t tier_flag = (profile_byte >> 5) & 0x01; + uint8_t profile_idc = profile_byte & 0x1f; + + // These values should be extracted from the VPS/SPS parsing + EXPECT_GE(profile_space, 0); + EXPECT_LE(profile_space, 3); + EXPECT_GE(tier_flag, 0); + EXPECT_LE(tier_flag, 1); + EXPECT_GE(profile_idc, 0); + EXPECT_LE(profile_idc, 31); + } +} + +VOID TEST(ProtocolRawAvcTest, SrsRawHEVCStreamMuxIpbFrame) +{ + srs_error_t err = srs_success; + + SrsRawHEVCStream hevc; + + // Test mux_ipb_frame with valid frame data + if (true) { + // Create test HEVC frame data (IDR slice) + unsigned char frame_data[] = {0x26, 0x01, 0xaf, 0x06, 0xb8, 0x63, 0xef, 0x3a, 0x7f, 0x3c, 0x00, 0x01, 0x00, 0x80}; + std::string ibp_output; + + HELPER_EXPECT_SUCCESS(hevc.mux_ipb_frame((char *)frame_data, sizeof(frame_data), ibp_output)); + + // Should produce frame with 4-byte length prefix + frame data + EXPECT_EQ(4 + sizeof(frame_data), ibp_output.length()); + + // Check length prefix (big-endian) + const char *data = ibp_output.data(); + uint32_t length = ((unsigned char)data[0] << 24) | ((unsigned char)data[1] << 16) | ((unsigned char)data[2] << 8) | (unsigned char)data[3]; + EXPECT_EQ(sizeof(frame_data), length); + + // Check frame data follows length prefix + EXPECT_EQ(0, memcmp(frame_data, data + 4, sizeof(frame_data))); + } + + // Test mux_ipb_frame with empty frame - should still work + if (true) { + unsigned char empty_frame[] = {}; + std::string ibp_output; + + HELPER_EXPECT_SUCCESS(hevc.mux_ipb_frame((char *)empty_frame, 0, ibp_output)); + + // Should produce only 4-byte length prefix with zero length + EXPECT_EQ(4, ibp_output.length()); + + const char *data = ibp_output.data(); + uint32_t length = ((unsigned char)data[0] << 24) | ((unsigned char)data[1] << 16) | ((unsigned char)data[2] << 8) | (unsigned char)data[3]; + EXPECT_EQ(0, length); + } + + // Test mux_ipb_frame with large frame + if (true) { + unsigned char large_frame[1000]; + for (int i = 0; i < 1000; i++) { + large_frame[i] = i % 256; + } + std::string ibp_output; + + HELPER_EXPECT_SUCCESS(hevc.mux_ipb_frame((char *)large_frame, sizeof(large_frame), ibp_output)); + + // Should produce frame with 4-byte length prefix + frame data + EXPECT_EQ(4 + sizeof(large_frame), ibp_output.length()); + + // Check length prefix + const char *data = ibp_output.data(); + uint32_t length = ((unsigned char)data[0] << 24) | ((unsigned char)data[1] << 16) | ((unsigned char)data[2] << 8) | (unsigned char)data[3]; + EXPECT_EQ(sizeof(large_frame), length); + } + + // Test mux_ipb_frame with different HEVC NALU types + if (true) { + // Test with P-frame (TRAIL_R NALU type 1) + unsigned char p_frame[] = {0x02, 0x01, 0x50, 0x80, 0x12, 0x34, 0x56, 0x78}; + std::string ibp_output; + + HELPER_EXPECT_SUCCESS(hevc.mux_ipb_frame((char *)p_frame, sizeof(p_frame), ibp_output)); + + // Verify output format + EXPECT_EQ(4 + sizeof(p_frame), ibp_output.length()); + const char *data = ibp_output.data(); + uint32_t length = ((unsigned char)data[0] << 24) | ((unsigned char)data[1] << 16) | ((unsigned char)data[2] << 8) | (unsigned char)data[3]; + EXPECT_EQ(sizeof(p_frame), length); + EXPECT_EQ(0, memcmp(p_frame, data + 4, sizeof(p_frame))); + } + + // Test mux_ipb_frame with B-frame (TSA_N NALU type 2) + if (true) { + unsigned char b_frame[] = {0x04, 0x01, 0x60, 0x90, 0xab, 0xcd, 0xef}; + std::string ibp_output; + + HELPER_EXPECT_SUCCESS(hevc.mux_ipb_frame((char *)b_frame, sizeof(b_frame), ibp_output)); + + // Verify output format + EXPECT_EQ(4 + sizeof(b_frame), ibp_output.length()); + const char *data = ibp_output.data(); + uint32_t length = ((unsigned char)data[0] << 24) | ((unsigned char)data[1] << 16) | ((unsigned char)data[2] << 8) | (unsigned char)data[3]; + EXPECT_EQ(sizeof(b_frame), length); + EXPECT_EQ(0, memcmp(b_frame, data + 4, sizeof(b_frame))); + } +} + +VOID TEST(ProtocolRawAvcTest, SrsRawHEVCStreamMuxAvc2flv) +{ + srs_error_t err = srs_success; + + SrsRawHEVCStream hevc; + + // Test mux_avc2flv with sequence header + if (true) { + std::string video_data = std::string("\x01\x64\x00\x20\xff\xe1\x00\x19\x67\x64\x00\x20", 12); + int8_t frame_type = 1; // keyframe + int8_t avc_packet_type = 0; // sequence header + uint32_t dts = 1000; + uint32_t pts = 1000; + char *flv_data = NULL; + int nb_flv = 0; + + HELPER_EXPECT_SUCCESS(hevc.mux_avc2flv(video_data, frame_type, avc_packet_type, dts, pts, &flv_data, &nb_flv)); + + // Should produce FLV packet with 5-byte header + video data + EXPECT_TRUE(flv_data != NULL); + EXPECT_EQ(5 + video_data.length(), nb_flv); + + if (flv_data) { + // Check FLV header format + EXPECT_EQ((frame_type << 4) | 12, (uint8_t)flv_data[0]); // frame_type | SrsVideoCodecIdHEVC(12) + EXPECT_EQ(avc_packet_type, flv_data[1]); // AVCPacketType + + // Check composition time (CTS = PTS - DTS = 0) + uint32_t cts = ((unsigned char)flv_data[2] << 16) | ((unsigned char)flv_data[3] << 8) | (unsigned char)flv_data[4]; + EXPECT_EQ(0, cts); + + // Check video data follows header + EXPECT_EQ(0, memcmp(video_data.data(), flv_data + 5, video_data.length())); + + delete[] flv_data; + } + } + + // Test mux_avc2flv with NALU frame + if (true) { + std::string video_data = std::string("\x00\x00\x00\x0e\x26\x01\xaf\x06\xb8\x63\xef\x3a\x7f\x3c\x00\x01\x00\x80", 18); + int8_t frame_type = 1; // keyframe + int8_t avc_packet_type = 1; // NALU + uint32_t dts = 2000; + uint32_t pts = 2100; // PTS > DTS + char *flv_data = NULL; + int nb_flv = 0; + + HELPER_EXPECT_SUCCESS(hevc.mux_avc2flv(video_data, frame_type, avc_packet_type, dts, pts, &flv_data, &nb_flv)); + + EXPECT_TRUE(flv_data != NULL); + EXPECT_EQ(5 + video_data.length(), nb_flv); + + if (flv_data) { + // Check composition time (CTS = PTS - DTS = 100) + uint32_t cts = ((unsigned char)flv_data[2] << 16) | ((unsigned char)flv_data[3] << 8) | (unsigned char)flv_data[4]; + EXPECT_EQ(100, cts); + + delete[] flv_data; + } + } + + // Test mux_avc2flv with inter frame + if (true) { + std::string video_data = std::string("\x00\x00\x00\x08\x02\x01\xd0\x80\x93\x25\x88\x84", 12); + int8_t frame_type = 2; // inter frame + int8_t avc_packet_type = 1; // NALU + uint32_t dts = 3000; + uint32_t pts = 3000; + char *flv_data = NULL; + int nb_flv = 0; + + HELPER_EXPECT_SUCCESS(hevc.mux_avc2flv(video_data, frame_type, avc_packet_type, dts, pts, &flv_data, &nb_flv)); + + EXPECT_TRUE(flv_data != NULL); + if (flv_data) { + // Check frame type in FLV header + EXPECT_EQ((frame_type << 4) | 12, (uint8_t)flv_data[0]); // inter frame | HEVC codec + delete[] flv_data; + } + } + + // Test mux_avc2flv with empty video data + if (true) { + std::string empty_video_data; + int8_t frame_type = 1; // keyframe + int8_t avc_packet_type = 0; // sequence header + uint32_t dts = 4000; + uint32_t pts = 4000; + char *flv_data = NULL; + int nb_flv = 0; + + HELPER_EXPECT_SUCCESS(hevc.mux_avc2flv(empty_video_data, frame_type, avc_packet_type, dts, pts, &flv_data, &nb_flv)); + + // Should produce FLV packet with 5-byte header only + EXPECT_TRUE(flv_data != NULL); + EXPECT_EQ(5, nb_flv); + + if (flv_data) { + // Check FLV header format + EXPECT_EQ((frame_type << 4) | 12, (uint8_t)flv_data[0]); // frame_type | SrsVideoCodecIdHEVC(12) + EXPECT_EQ(avc_packet_type, flv_data[1]); // AVCPacketType + + // Check composition time (CTS = PTS - DTS = 0) + uint32_t cts = ((unsigned char)flv_data[2] << 16) | ((unsigned char)flv_data[3] << 8) | (unsigned char)flv_data[4]; + EXPECT_EQ(0, cts); + + delete[] flv_data; + } + } + + // Test mux_avc2flv with large composition time offset + if (true) { + std::string video_data = std::string("\x00\x00\x00\x04\x26\x01\xaf\x06", 8); + int8_t frame_type = 1; // keyframe + int8_t avc_packet_type = 1; // NALU + uint32_t dts = 5000; + uint32_t pts = 10000; // Large PTS offset + char *flv_data = NULL; + int nb_flv = 0; + + HELPER_EXPECT_SUCCESS(hevc.mux_avc2flv(video_data, frame_type, avc_packet_type, dts, pts, &flv_data, &nb_flv)); + + EXPECT_TRUE(flv_data != NULL); + if (flv_data) { + // Check composition time (CTS = PTS - DTS = 5000) + uint32_t cts = ((unsigned char)flv_data[2] << 16) | ((unsigned char)flv_data[3] << 8) | (unsigned char)flv_data[4]; + EXPECT_EQ(5000, cts); + + delete[] flv_data; + } + } +} + +VOID TEST(ProtocolRawAvcTest, SrsRawHEVCStreamMuxAvc2flvEnhanced) +{ + srs_error_t err = srs_success; + + SrsRawHEVCStream hevc; + + // Test mux_avc2flv_enhanced with sequence header + if (true) { + std::string video_data = std::string("\x01\x64\x00\x20\xff\xe1\x00\x19\x67\x64\x00\x20", 12); + int8_t frame_type = 1; // keyframe + int8_t packet_type = 0; // sequence start + uint32_t dts = 1000; + uint32_t pts = 1000; + char *flv_data = NULL; + int nb_flv = 0; + + HELPER_EXPECT_SUCCESS(hevc.mux_avc2flv_enhanced(video_data, frame_type, packet_type, dts, pts, &flv_data, &nb_flv)); + + // Should produce enhanced FLV packet with 5-byte header + video data + EXPECT_TRUE(flv_data != NULL); + EXPECT_EQ(5 + video_data.length(), nb_flv); + + if (flv_data) { + // Check enhanced FLV header format + uint8_t header_byte = flv_data[0]; + EXPECT_TRUE((header_byte & 0x80) != 0); // SRS_FLV_IS_EX_HEADER bit set + EXPECT_EQ(frame_type, (header_byte >> 4) & 0x07); // frame type (3 bits, mask out EX_HEADER bit) + EXPECT_EQ(packet_type, header_byte & 0x0f); // packet type + + // Check HEVC fourcc 'hvc1' + EXPECT_EQ('h', flv_data[1]); + EXPECT_EQ('v', flv_data[2]); + EXPECT_EQ('c', flv_data[3]); + EXPECT_EQ('1', flv_data[4]); + + // Check video data follows header + EXPECT_EQ(0, memcmp(video_data.data(), flv_data + 5, video_data.length())); + + delete[] flv_data; + } + } + + // Test mux_avc2flv_enhanced with coded frames + if (true) { + std::string video_data = std::string("\x00\x00\x00\x0e\x26\x01\xaf\x06\xb8\x63\xef\x3a\x7f\x3c\x00\x01\x00\x80", 18); + int8_t frame_type = 1; // keyframe + int8_t packet_type = 1; // coded frames + uint32_t dts = 2000; + uint32_t pts = 2000; + char *flv_data = NULL; + int nb_flv = 0; + + HELPER_EXPECT_SUCCESS(hevc.mux_avc2flv_enhanced(video_data, frame_type, packet_type, dts, pts, &flv_data, &nb_flv)); + + EXPECT_TRUE(flv_data != NULL); + EXPECT_EQ(5 + video_data.length(), nb_flv); + + if (flv_data) { + // Check enhanced header with coded frames packet type + uint8_t header_byte = flv_data[0]; + EXPECT_EQ(packet_type, header_byte & 0x0f); // coded frames packet type + + delete[] flv_data; + } + } + + // Test mux_avc2flv_enhanced with inter frame + if (true) { + std::string video_data = std::string("\x00\x00\x00\x08\x02\x01\xd0\x80\x93\x25\x88\x84", 12); + int8_t frame_type = 2; // inter frame + int8_t packet_type = 1; // coded frames + uint32_t dts = 3000; + uint32_t pts = 3000; + char *flv_data = NULL; + int nb_flv = 0; + + HELPER_EXPECT_SUCCESS(hevc.mux_avc2flv_enhanced(video_data, frame_type, packet_type, dts, pts, &flv_data, &nb_flv)); + + EXPECT_TRUE(flv_data != NULL); + if (flv_data) { + // Check inter frame type in enhanced header + uint8_t header_byte = flv_data[0]; + EXPECT_EQ(frame_type, (header_byte >> 4) & 0x07); // inter frame type (3 bits, mask out EX_HEADER bit) + + delete[] flv_data; + } + } + + // Test mux_avc2flv_enhanced with sequence end packet + if (true) { + std::string video_data; // Empty for sequence end + int8_t frame_type = 1; // keyframe + int8_t packet_type = 2; // sequence end + uint32_t dts = 4000; + uint32_t pts = 4000; + char *flv_data = NULL; + int nb_flv = 0; + + HELPER_EXPECT_SUCCESS(hevc.mux_avc2flv_enhanced(video_data, frame_type, packet_type, dts, pts, &flv_data, &nb_flv)); + + EXPECT_TRUE(flv_data != NULL); + EXPECT_EQ(5, nb_flv); // Should produce 5-byte header only + + if (flv_data) { + // Check enhanced FLV header format for sequence end + uint8_t header_byte = flv_data[0]; + EXPECT_TRUE((header_byte & 0x80) != 0); // SRS_FLV_IS_EX_HEADER bit set + EXPECT_EQ(frame_type, (header_byte >> 4) & 0x07); // frame type + EXPECT_EQ(packet_type, header_byte & 0x0f); // sequence end packet type + + // Check HEVC fourcc 'hvc1' + EXPECT_EQ('h', flv_data[1]); + EXPECT_EQ('v', flv_data[2]); + EXPECT_EQ('c', flv_data[3]); + EXPECT_EQ('1', flv_data[4]); + + delete[] flv_data; + } + } + + // Test mux_avc2flv_enhanced with different frame types + if (true) { + std::string video_data = std::string("\x00\x00\x00\x06\x04\x01\x70\x80\x12\x34", 10); + int8_t frame_type = 3; // disposable inter frame + int8_t packet_type = 1; // coded frames + uint32_t dts = 5000; + uint32_t pts = 5000; + char *flv_data = NULL; + int nb_flv = 0; + + HELPER_EXPECT_SUCCESS(hevc.mux_avc2flv_enhanced(video_data, frame_type, packet_type, dts, pts, &flv_data, &nb_flv)); + + EXPECT_TRUE(flv_data != NULL); + if (flv_data) { + // Check disposable inter frame type in enhanced header + uint8_t header_byte = flv_data[0]; + EXPECT_EQ(frame_type, (header_byte >> 4) & 0x07); // disposable inter frame type + + delete[] flv_data; + } + } + + // Test mux_avc2flv_enhanced with empty video data + if (true) { + std::string empty_video_data; + int8_t frame_type = 1; // keyframe + int8_t packet_type = 0; // sequence start + uint32_t dts = 6000; + uint32_t pts = 6000; + char *flv_data = NULL; + int nb_flv = 0; + + HELPER_EXPECT_SUCCESS(hevc.mux_avc2flv_enhanced(empty_video_data, frame_type, packet_type, dts, pts, &flv_data, &nb_flv)); + + // Should produce enhanced FLV packet with 5-byte header only + EXPECT_TRUE(flv_data != NULL); + EXPECT_EQ(5, nb_flv); + + if (flv_data) { + // Check enhanced FLV header format + uint8_t header_byte = flv_data[0]; + EXPECT_TRUE((header_byte & 0x80) != 0); // SRS_FLV_IS_EX_HEADER bit set + EXPECT_EQ(frame_type, (header_byte >> 4) & 0x07); // frame type + EXPECT_EQ(packet_type, header_byte & 0x0f); // packet type + + // Check HEVC fourcc 'hvc1' + EXPECT_EQ('h', flv_data[1]); + EXPECT_EQ('v', flv_data[2]); + EXPECT_EQ('c', flv_data[3]); + EXPECT_EQ('1', flv_data[4]); + + delete[] flv_data; + } + } } VOID TEST(ProtocolStreamTest, SrsFastStreamBasic) @@ -456,25 +1104,6 @@ VOID TEST(ProtocolLogTest, SrsThreadContextBasic) EXPECT_EQ(0, new_id.compare(set_result)); } -VOID TEST(ProtocolLogTest, SrsConsoleLogBasic) -{ - // SrsConsoleLog requires parameters: level and utc flag - SrsConsoleLog console_log(SrsLogLevelTrace, false); - - // Test basic functionality - should not crash - // We can't easily test actual logging without capturing output - - // Test initialization - srs_error_t err = console_log.initialize(); - HELPER_EXPECT_SUCCESS(err); - - // Test reopen - should not crash - console_log.reopen(); - - // The console log should be constructible and destructible without issues - EXPECT_TRUE(true); // Just verify we can create and destroy the object -} - VOID TEST(ProtocolRtmpConnTest, SrsBasicRtmpClientBasic) { // Test basic RTMP client construction @@ -508,37 +1137,6 @@ VOID TEST(ProtocolStTest, SrsStSocketBasic) EXPECT_FALSE(is_not_never_timeout); } -VOID TEST(ProtocolConnTest, SrsSslConnectionInterface) -{ - // Test SSL connection interface - // We can't easily test full SSL functionality without certificates - - // Create a TCP connection first (with invalid fd for testing) - SrsTcpConnection *tcp = new SrsTcpConnection(NULL); - - // Create SSL connection wrapper - SrsSslConnection *ssl = new SrsSslConnection(tcp); - - // The SSL connection should be created - EXPECT_TRUE(ssl != NULL); - - // Test timeout methods exist - ssl->set_recv_timeout(1000 * SRS_UTIME_MILLISECONDS); - srs_utime_t timeout = ssl->get_recv_timeout(); - EXPECT_EQ(1000 * SRS_UTIME_MILLISECONDS, timeout); - - ssl->set_send_timeout(2000 * SRS_UTIME_MILLISECONDS); - srs_utime_t send_timeout = ssl->get_send_timeout(); - EXPECT_EQ(2000 * SRS_UTIME_MILLISECONDS, send_timeout); - - // Test byte counters - EXPECT_EQ(0, ssl->get_recv_bytes()); - EXPECT_EQ(0, ssl->get_send_bytes()); - - // Clean up - delete ssl; // This will also delete the tcp connection -} - VOID TEST(ProtocolRtpTest, SrsRtpVideoBuilderBasic) { srs_error_t err = srs_success; @@ -827,71 +1425,6 @@ VOID TEST(ProtocolRtmpConnTest, SrsBasicRtmpClientOperations) // 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 @@ -2095,3 +2628,137 @@ VOID TEST(ProtocolSdpTest, SrsSdpUpdateMsid) 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()); } + +VOID TEST(RawHEVCStreamTest, VpsDemux) +{ + srs_error_t err; + + // Test vps_demux method with valid VPS data + if (true) { + SrsRawHEVCStream hevc; + + // Create mock VPS NALU data (HEVC VPS type 32) + char vps_data[] = { + (char)((32 << 1) | 0), // HEVC VPS NALU type 32, layer_id=0 + (char)1, // layer_id bits + temporal_id=1 + 0x01, 0x02, 0x03, 0x04 // Mock VPS payload + }; + int vps_size = sizeof(vps_data); + + string vps_output; + HELPER_ASSERT_SUCCESS(hevc.vps_demux(vps_data, vps_size, vps_output)); + + // Verify VPS output matches input + EXPECT_EQ(vps_size, (int)vps_output.length()); + EXPECT_EQ(0, memcmp(vps_data, vps_output.data(), vps_size)); + } + + // Test vps_demux with empty frame (should fail) + if (true) { + SrsRawHEVCStream hevc; + + string vps_output; + HELPER_EXPECT_FAILED(hevc.vps_demux(NULL, 0, vps_output)); + } + + // Test vps_demux with negative frame size (should fail) + if (true) { + SrsRawHEVCStream hevc; + + char dummy_data[] = {0x01, 0x02}; + string vps_output; + HELPER_EXPECT_FAILED(hevc.vps_demux(dummy_data, -1, vps_output)); + } +} + +VOID TEST(RawHEVCStreamTest, SpsDemux) +{ + srs_error_t err; + + // Test sps_demux method with valid SPS data + if (true) { + SrsRawHEVCStream hevc; + + // Create mock SPS NALU data (HEVC SPS type 33) + char sps_data[] = { + (char)((33 << 1) | 0), // HEVC SPS NALU type 33, layer_id=0 + (char)1, // layer_id bits + temporal_id=1 + 0x10, 0x20, 0x30, 0x40, 0x50 // Mock SPS payload (>= 4 bytes total) + }; + int sps_size = sizeof(sps_data); + + string sps_output; + HELPER_ASSERT_SUCCESS(hevc.sps_demux(sps_data, sps_size, sps_output)); + + // Verify SPS output matches input + EXPECT_EQ(sps_size, (int)sps_output.length()); + EXPECT_EQ(0, memcmp(sps_data, sps_output.data(), sps_size)); + } + + // Test sps_demux with frame size < 4 (should succeed but return empty) + if (true) { + SrsRawHEVCStream hevc; + + char small_data[] = {0x01, 0x02}; // Only 2 bytes + string sps_output; + HELPER_ASSERT_SUCCESS(hevc.sps_demux(small_data, 2, sps_output)); + + // Should return empty string for frames < 4 bytes + EXPECT_EQ(0, (int)sps_output.length()); + } + + // Test sps_demux with exactly 4 bytes + if (true) { + SrsRawHEVCStream hevc; + + char sps_data[] = {0x42, 0x01, 0x01, 0x01}; // Exactly 4 bytes + string sps_output; + HELPER_ASSERT_SUCCESS(hevc.sps_demux(sps_data, 4, sps_output)); + + // Should return the 4 bytes + EXPECT_EQ(4, (int)sps_output.length()); + EXPECT_EQ(0, memcmp(sps_data, sps_output.data(), 4)); + } +} + +VOID TEST(RawHEVCStreamTest, PpsDemux) +{ + srs_error_t err; + + // Test pps_demux method with valid PPS data + if (true) { + SrsRawHEVCStream hevc; + + // Create mock PPS NALU data (HEVC PPS type 34) + unsigned char pps_data[] = { + (unsigned char)((34 << 1) | 0), // HEVC PPS NALU type 34, layer_id=0 + (unsigned char)1, // layer_id bits + temporal_id=1 + 0xA0, 0xB0, 0xC0 // Mock PPS payload + }; + int pps_size = sizeof(pps_data); + + string pps_output; + HELPER_ASSERT_SUCCESS(hevc.pps_demux((char *)pps_data, pps_size, pps_output)); + + // Verify PPS output matches input + EXPECT_EQ(pps_size, (int)pps_output.length()); + EXPECT_EQ(0, memcmp(pps_data, pps_output.data(), pps_size)); + } + + // Test pps_demux with empty frame (should fail) + if (true) { + SrsRawHEVCStream hevc; + + string pps_output; + HELPER_EXPECT_FAILED(hevc.pps_demux(NULL, 0, pps_output)); + } + + // Test pps_demux with negative frame size (should fail) + if (true) { + SrsRawHEVCStream hevc; + + char dummy_data[] = {0x01, 0x02}; + string pps_output; + HELPER_EXPECT_FAILED(hevc.pps_demux(dummy_data, -1, pps_output)); + } +} diff --git a/trunk/src/utest/srs_utest_protocol4.cpp b/trunk/src/utest/srs_utest_protocol4.cpp new file mode 100644 index 000000000..bfaedf74f --- /dev/null +++ b/trunk/src/utest/srs_utest_protocol4.cpp @@ -0,0 +1,2416 @@ +// +// Copyright (c) 2013-2025 The SRS Authors +// +// SPDX-License-Identifier: MIT +// + +#include + +using namespace std; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +VOID TEST(HTTPClientTest, HTTPClientUtility) +{ + srs_error_t err; + + // Typical HTTP POST. + if (true) { + SrsHttpTestServer server("OK"); + HELPER_ASSERT_SUCCESS(server.start()); + + SrsHttpClient client; + HELPER_ASSERT_SUCCESS(client.initialize("http", "127.0.0.1", server.get_port(), 1 * SRS_UTIME_SECONDS)); + + ISrsHttpMessage *res = NULL; + HELPER_ASSERT_SUCCESS(client.post("/api/v1", "", &res)); + SrsUniquePtr res_uptr(res); + + ISrsHttpResponseReader *br = res->body_reader(); + ASSERT_FALSE(br->eof()); + + ssize_t nn = 0; + char buf[1024]; + HELPER_ARRAY_INIT(buf, sizeof(buf), 0); + HELPER_ASSERT_SUCCESS(br->read(buf, sizeof(buf), &nn)); + ASSERT_EQ(2, nn); + EXPECT_STREQ("OK", buf); + } + + // Typical HTTP GET. + if (true) { + SrsHttpTestServer server("OK"); + HELPER_ASSERT_SUCCESS(server.start()); + + SrsHttpClient client; + HELPER_ASSERT_SUCCESS(client.initialize("http", "127.0.0.1", server.get_port(), 1 * SRS_UTIME_SECONDS)); + + ISrsHttpMessage *res = NULL; + HELPER_ASSERT_SUCCESS(client.get("/api/v1", "", &res)); + SrsUniquePtr res_uptr(res); + + ISrsHttpResponseReader *br = res->body_reader(); + ASSERT_FALSE(br->eof()); + + ssize_t nn = 0; + char buf[1024]; + HELPER_ARRAY_INIT(buf, sizeof(buf), 0); + HELPER_ASSERT_SUCCESS(br->read(buf, sizeof(buf), &nn)); + ASSERT_EQ(2, nn); + EXPECT_STREQ("OK", buf); + } + + // Set receive timeout and Kbps sample. + if (true) { + SrsHttpTestServer server("OK"); + HELPER_ASSERT_SUCCESS(server.start()); + + SrsHttpClient client; + HELPER_ASSERT_SUCCESS(client.initialize("http", "127.0.0.1", server.get_port(), 1 * SRS_UTIME_SECONDS)); + client.set_recv_timeout(1 * SRS_UTIME_SECONDS); + client.set_header("agent", "srs"); + + ISrsHttpMessage *res = NULL; + HELPER_ASSERT_SUCCESS(client.get("/api/v1", "", &res)); + SrsUniquePtr res_uptr(res); + + ISrsHttpResponseReader *br = res->body_reader(); + ASSERT_FALSE(br->eof()); + + ssize_t nn = 0; + char buf[1024]; + HELPER_ARRAY_INIT(buf, sizeof(buf), 0); + HELPER_ASSERT_SUCCESS(br->read(buf, sizeof(buf), &nn)); + ASSERT_EQ(2, nn); + EXPECT_STREQ("OK", buf); + + client.kbps_sample("SRS", 0); + } +} + +VOID TEST(HTTPSClientTest, HTTPSClientPost) +{ + srs_error_t err; + + // Test HTTPS POST request + if (true) { + SrsHttpsTestServer server("HTTPS OK"); + HELPER_ASSERT_SUCCESS(server.start()); + + // Give server time to start + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + SrsHttpClient client; + HELPER_ASSERT_SUCCESS(client.initialize("https", "127.0.0.1", server.get_port(), 5 * SRS_UTIME_SECONDS)); + + string post_data = "{\"test\": \"data\"}"; + ISrsHttpMessage *res = NULL; + HELPER_ASSERT_SUCCESS(client.post("/api/test", post_data, &res)); + SrsUniquePtr res_uptr(res); + + ISrsHttpResponseReader *br = res->body_reader(); + ASSERT_FALSE(br->eof()); + + ssize_t nn = 0; + char buf[1024]; + HELPER_ARRAY_INIT(buf, sizeof(buf), 0); + HELPER_ASSERT_SUCCESS(br->read(buf, sizeof(buf), &nn)); + ASSERT_EQ(8, nn); + EXPECT_STREQ("HTTPS OK", buf); + } + + // Test HTTPS GET request + if (true) { + SrsHttpsTestServer server("HTTPS OK"); + HELPER_ASSERT_SUCCESS(server.start()); + + // Give server time to start + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + SrsHttpClient client; + HELPER_ASSERT_SUCCESS(client.initialize("https", "127.0.0.1", server.get_port(), 5 * SRS_UTIME_SECONDS)); + + ISrsHttpMessage *res = NULL; + HELPER_ASSERT_SUCCESS(client.get("/api/test", "", &res)); + SrsUniquePtr res_uptr(res); + + ISrsHttpResponseReader *br = res->body_reader(); + ASSERT_FALSE(br->eof()); + + ssize_t nn = 0; + char buf[1024]; + HELPER_ARRAY_INIT(buf, sizeof(buf), 0); + HELPER_ASSERT_SUCCESS(br->read(buf, sizeof(buf), &nn)); + ASSERT_EQ(8, nn); + EXPECT_STREQ("HTTPS OK", buf); + } +} + +VOID TEST(HTTPClientTest, HTTPClientHeaders) +{ + srs_error_t err; + + // Test custom headers + if (true) { + SrsHttpTestServer server("Header Test"); + HELPER_ASSERT_SUCCESS(server.start()); + + SrsHttpClient client; + HELPER_ASSERT_SUCCESS(client.initialize("http", "127.0.0.1", server.get_port(), 1 * SRS_UTIME_SECONDS)); + + // Set custom headers + client.set_header("X-Custom-Header", "test-value"); + client.set_header("Authorization", "Bearer token123"); + client.set_header("Content-Type", "application/xml"); + + ISrsHttpMessage *res = NULL; + HELPER_ASSERT_SUCCESS(client.post("/api/headers", "test", &res)); + SrsUniquePtr res_uptr(res); + + ISrsHttpResponseReader *br = res->body_reader(); + ASSERT_FALSE(br->eof()); + + ssize_t nn = 0; + char buf[1024]; + HELPER_ARRAY_INIT(buf, sizeof(buf), 0); + HELPER_ASSERT_SUCCESS(br->read(buf, sizeof(buf), &nn)); + ASSERT_EQ(11, nn); + EXPECT_STREQ("Header Test", buf); + } + + // Test header chaining + if (true) { + SrsHttpTestServer server("Chain Test"); + HELPER_ASSERT_SUCCESS(server.start()); + + SrsHttpClient client; + HELPER_ASSERT_SUCCESS(client.initialize("http", "127.0.0.1", server.get_port(), 1 * SRS_UTIME_SECONDS)); + + // Chain header setting + client.set_header("X-Test-1", "value1")->set_header("X-Test-2", "value2"); + + ISrsHttpMessage *res = NULL; + HELPER_ASSERT_SUCCESS(client.get("/api/chain", "", &res)); + SrsUniquePtr res_uptr(res); + + ISrsHttpResponseReader *br = res->body_reader(); + ASSERT_FALSE(br->eof()); + + ssize_t nn = 0; + char buf[1024]; + HELPER_ARRAY_INIT(buf, sizeof(buf), 0); + HELPER_ASSERT_SUCCESS(br->read(buf, sizeof(buf), &nn)); + ASSERT_EQ(10, nn); + EXPECT_STREQ("Chain Test", buf); + } +} + +VOID TEST(HTTPClientTest, HTTPClientErrorHandling) +{ + srs_error_t err; + + // Test connection to non-existent server + if (true) { + SrsHttpClient client; + HELPER_ASSERT_SUCCESS(client.initialize("http", "127.0.0.1", 1, 100 * SRS_UTIME_MILLISECONDS)); + + ISrsHttpMessage *res = NULL; + HELPER_EXPECT_FAILED(client.post("/api/test", "data", &res)); + EXPECT_TRUE(res == NULL); + } + + // Test empty path handling + if (true) { + SrsHttpTestServer server("Empty Path"); + HELPER_ASSERT_SUCCESS(server.start()); + + SrsHttpClient client; + HELPER_ASSERT_SUCCESS(client.initialize("http", "127.0.0.1", server.get_port(), 1 * SRS_UTIME_SECONDS)); + + ISrsHttpMessage *res = NULL; + HELPER_ASSERT_SUCCESS(client.get("", "", &res)); + SrsUniquePtr res_uptr(res); + + ISrsHttpResponseReader *br = res->body_reader(); + ASSERT_FALSE(br->eof()); + + ssize_t nn = 0; + char buf[1024]; + HELPER_ARRAY_INIT(buf, sizeof(buf), 0); + HELPER_ASSERT_SUCCESS(br->read(buf, sizeof(buf), &nn)); + ASSERT_EQ(10, nn); + EXPECT_STREQ("Empty Path", buf); + } +} + +VOID TEST(HTTPClientTest, HTTPClientLargeData) +{ + srs_error_t err; + + // Test large POST data + if (true) { + SrsHttpTestServer server("Large Data OK"); + HELPER_ASSERT_SUCCESS(server.start()); + + SrsHttpClient client; + HELPER_ASSERT_SUCCESS(client.initialize("http", "127.0.0.1", server.get_port(), 5 * SRS_UTIME_SECONDS)); + + // Create large data (1KB) + string large_data(1024, 'A'); + + ISrsHttpMessage *res = NULL; + HELPER_ASSERT_SUCCESS(client.post("/api/large", large_data, &res)); + SrsUniquePtr res_uptr(res); + + ISrsHttpResponseReader *br = res->body_reader(); + ASSERT_FALSE(br->eof()); + + ssize_t nn = 0; + char buf[1024]; + HELPER_ARRAY_INIT(buf, sizeof(buf), 0); + HELPER_ASSERT_SUCCESS(br->read(buf, sizeof(buf), &nn)); + ASSERT_EQ(13, nn); + EXPECT_STREQ("Large Data OK", buf); + } + + // Test empty POST data + if (true) { + SrsHttpTestServer server("Empty Data OK"); + HELPER_ASSERT_SUCCESS(server.start()); + + SrsHttpClient client; + HELPER_ASSERT_SUCCESS(client.initialize("http", "127.0.0.1", server.get_port(), 1 * SRS_UTIME_SECONDS)); + + ISrsHttpMessage *res = NULL; + HELPER_ASSERT_SUCCESS(client.post("/api/empty", "", &res)); + SrsUniquePtr res_uptr(res); + + ISrsHttpResponseReader *br = res->body_reader(); + ASSERT_FALSE(br->eof()); + + ssize_t nn = 0; + char buf[1024]; + HELPER_ARRAY_INIT(buf, sizeof(buf), 0); + HELPER_ASSERT_SUCCESS(br->read(buf, sizeof(buf), &nn)); + ASSERT_EQ(13, nn); + EXPECT_STREQ("Empty Data OK", buf); + } +} + +VOID TEST(HTTPSClientTest, HTTPSClientErrorHandling) +{ + srs_error_t err; + + // Test HTTPS connection to HTTP server (should fail) + if (true) { + SrsHttpTestServer server("HTTP Server"); + HELPER_ASSERT_SUCCESS(server.start()); + + SrsHttpClient client; + HELPER_ASSERT_SUCCESS(client.initialize("https", "127.0.0.1", server.get_port(), 1 * SRS_UTIME_SECONDS)); + + ISrsHttpMessage *res = NULL; + HELPER_EXPECT_FAILED(client.get("/api/test", "", &res)); + EXPECT_TRUE(res == NULL); + } + + // Test HTTPS with invalid port + if (true) { + SrsHttpClient client; + HELPER_ASSERT_SUCCESS(client.initialize("https", "127.0.0.1", 1, 100 * SRS_UTIME_MILLISECONDS)); + + ISrsHttpMessage *res = NULL; + HELPER_EXPECT_FAILED(client.post("/api/test", "data", &res)); + EXPECT_TRUE(res == NULL); + } +} + +VOID TEST(HTTPSClientTest, HTTPSClientHeaders) +{ + srs_error_t err; + + // Test HTTPS with custom headers + if (true) { + SrsHttpsTestServer server("HTTPS Headers"); + HELPER_ASSERT_SUCCESS(server.start()); + + // Give server time to start + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + SrsHttpClient client; + HELPER_ASSERT_SUCCESS(client.initialize("https", "127.0.0.1", server.get_port(), 5 * SRS_UTIME_SECONDS)); + + // Set custom headers for HTTPS + client.set_header("X-SSL-Test", "secure-value"); + client.set_header("Authorization", "Bearer ssl-token"); + client.set_recv_timeout(2 * SRS_UTIME_SECONDS); + + string post_data = "{\"ssl\": \"test\"}"; + ISrsHttpMessage *res = NULL; + HELPER_ASSERT_SUCCESS(client.post("/api/ssl-headers", post_data, &res)); + SrsUniquePtr res_uptr(res); + + ISrsHttpResponseReader *br = res->body_reader(); + ASSERT_FALSE(br->eof()); + + ssize_t nn = 0; + char buf[1024]; + HELPER_ARRAY_INIT(buf, sizeof(buf), 0); + HELPER_ASSERT_SUCCESS(br->read(buf, sizeof(buf), &nn)); + ASSERT_EQ(13, nn); + EXPECT_STREQ("HTTPS Headers", buf); + } +} + +VOID TEST(HTTPClientTest, HTTPClientConnectionReuse) +{ + srs_error_t err; + + // Test connection reuse for multiple requests + if (true) { + SrsHttpTestServer server("Reuse Test"); + HELPER_ASSERT_SUCCESS(server.start()); + + SrsHttpClient client; + HELPER_ASSERT_SUCCESS(client.initialize("http", "127.0.0.1", server.get_port(), 2 * SRS_UTIME_SECONDS)); + + // First request + ISrsHttpMessage *res1 = NULL; + HELPER_ASSERT_SUCCESS(client.get("/api/first", "", &res1)); + SrsUniquePtr res1_uptr(res1); + + ISrsHttpResponseReader *br1 = res1->body_reader(); + ssize_t nn1 = 0; + char buf1[1024]; + HELPER_ARRAY_INIT(buf1, sizeof(buf1), 0); + HELPER_ASSERT_SUCCESS(br1->read(buf1, sizeof(buf1), &nn1)); + ASSERT_EQ(10, nn1); + EXPECT_STREQ("Reuse Test", buf1); + + // Second request on same connection + ISrsHttpMessage *res2 = NULL; + HELPER_ASSERT_SUCCESS(client.post("/api/second", "data", &res2)); + SrsUniquePtr res2_uptr(res2); + + ISrsHttpResponseReader *br2 = res2->body_reader(); + ssize_t nn2 = 0; + char buf2[1024]; + HELPER_ARRAY_INIT(buf2, sizeof(buf2), 0); + HELPER_ASSERT_SUCCESS(br2->read(buf2, sizeof(buf2), &nn2)); + ASSERT_EQ(10, nn2); + EXPECT_STREQ("Reuse Test", buf2); + + // Third request with different headers + client.set_header("X-Request-Count", "3"); + ISrsHttpMessage *res3 = NULL; + HELPER_ASSERT_SUCCESS(client.get("/api/third", "", &res3)); + SrsUniquePtr res3_uptr(res3); + + ISrsHttpResponseReader *br3 = res3->body_reader(); + ssize_t nn3 = 0; + char buf3[1024]; + HELPER_ARRAY_INIT(buf3, sizeof(buf3), 0); + HELPER_ASSERT_SUCCESS(br3->read(buf3, sizeof(buf3), &nn3)); + ASSERT_EQ(10, nn3); + EXPECT_STREQ("Reuse Test", buf3); + } +} + +VOID TEST(HTTPClientTest, HTTPClientSpecialCases) +{ + srs_error_t err; + + // Test client reinitialization + if (true) { + SrsHttpTestServer server1("Server 1"); + HELPER_ASSERT_SUCCESS(server1.start()); + + SrsHttpTestServer server2("Server 2"); + HELPER_ASSERT_SUCCESS(server2.start()); + + SrsHttpClient client; + + // Initialize with first server + HELPER_ASSERT_SUCCESS(client.initialize("http", "127.0.0.1", server1.get_port(), 1 * SRS_UTIME_SECONDS)); + + ISrsHttpMessage *res1 = NULL; + HELPER_ASSERT_SUCCESS(client.get("/api/test", "", &res1)); + SrsUniquePtr res1_uptr(res1); + + ISrsHttpResponseReader *br1 = res1->body_reader(); + ssize_t nn1 = 0; + char buf1[1024]; + HELPER_ARRAY_INIT(buf1, sizeof(buf1), 0); + HELPER_ASSERT_SUCCESS(br1->read(buf1, sizeof(buf1), &nn1)); + ASSERT_EQ(8, nn1); + EXPECT_STREQ("Server 1", buf1); + + // Reinitialize with second server + HELPER_ASSERT_SUCCESS(client.initialize("http", "127.0.0.1", server2.get_port(), 1 * SRS_UTIME_SECONDS)); + + ISrsHttpMessage *res2 = NULL; + HELPER_ASSERT_SUCCESS(client.get("/api/test", "", &res2)); + SrsUniquePtr res2_uptr(res2); + + ISrsHttpResponseReader *br2 = res2->body_reader(); + ssize_t nn2 = 0; + char buf2[1024]; + HELPER_ARRAY_INIT(buf2, sizeof(buf2), 0); + HELPER_ASSERT_SUCCESS(br2->read(buf2, sizeof(buf2), &nn2)); + ASSERT_EQ(8, nn2); + EXPECT_STREQ("Server 2", buf2); + } + + // Test header override + if (true) { + SrsHttpTestServer server("Header Override"); + HELPER_ASSERT_SUCCESS(server.start()); + + SrsHttpClient client; + HELPER_ASSERT_SUCCESS(client.initialize("http", "127.0.0.1", server.get_port(), 1 * SRS_UTIME_SECONDS)); + + // Set initial header + client.set_header("X-Test", "initial"); + + // Override the same header + client.set_header("X-Test", "overridden"); + + // Override default headers + client.set_header("User-Agent", "Custom-Agent/1.0"); + client.set_header("Content-Type", "text/plain"); + + ISrsHttpMessage *res = NULL; + HELPER_ASSERT_SUCCESS(client.post("/api/override", "test data", &res)); + SrsUniquePtr res_uptr(res); + + ISrsHttpResponseReader *br = res->body_reader(); + ssize_t nn = 0; + char buf[1024]; + HELPER_ARRAY_INIT(buf, sizeof(buf), 0); + HELPER_ASSERT_SUCCESS(br->read(buf, sizeof(buf), &nn)); + ASSERT_EQ(15, nn); + EXPECT_STREQ("Header Override", buf); + } +} + +VOID TEST(HTTPSClientTest, HTTPSClientLargeData) +{ + srs_error_t err; + + // Test HTTPS with large data transfer + if (true) { + SrsHttpsTestServer server("HTTPS Large"); + HELPER_ASSERT_SUCCESS(server.start()); + + // Give server time to start + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + SrsHttpClient client; + HELPER_ASSERT_SUCCESS(client.initialize("https", "127.0.0.1", server.get_port(), 10 * SRS_UTIME_SECONDS)); + client.set_recv_timeout(5 * SRS_UTIME_SECONDS); + + // Create large data (2KB) + string large_data(2048, 'S'); + + ISrsHttpMessage *res = NULL; + HELPER_ASSERT_SUCCESS(client.post("/api/ssl-large", large_data, &res)); + SrsUniquePtr res_uptr(res); + + ISrsHttpResponseReader *br = res->body_reader(); + ASSERT_FALSE(br->eof()); + + ssize_t nn = 0; + char buf[1024]; + HELPER_ARRAY_INIT(buf, sizeof(buf), 0); + HELPER_ASSERT_SUCCESS(br->read(buf, sizeof(buf), &nn)); + ASSERT_EQ(11, nn); + EXPECT_STREQ("HTTPS Large", buf); + + // Test kbps sampling after large transfer + client.kbps_sample("HTTPS-LARGE", 500 * SRS_UTIME_MILLISECONDS); + } +} + +VOID TEST(RTMPClientTest, RTMPClientConnect) +{ + srs_error_t err; + + // Test basic RTMP connection + if (true) { + SrsRtmpTestServer server("live", "test"); + HELPER_ASSERT_SUCCESS(server.start()); + + // Give server time to start + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + SrsBasicRtmpClient client(server.url(), 5 * SRS_UTIME_SECONDS, 5 * SRS_UTIME_SECONDS); + HELPER_ASSERT_SUCCESS(client.connect()); + + // Test that connection is established by checking stream ID + EXPECT_TRUE(client.sid() > 0); + } + + // Test connection with custom timeout + if (true) { + SrsRtmpTestServer server("app", "stream"); + HELPER_ASSERT_SUCCESS(server.start()); + + // Give server time to start + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + SrsBasicRtmpClient client(server.url(), 1 * SRS_UTIME_SECONDS, 1 * SRS_UTIME_SECONDS); + HELPER_ASSERT_SUCCESS(client.connect()); + + EXPECT_TRUE(client.sid() > 0); + } +} + +VOID TEST(RTMPClientTest, RTMPClientPublish) +{ + srs_error_t err; + + // Test RTMP publish + if (true) { + SrsRtmpTestServer server("live", "publish_test"); + server.enable_publish(true); + server.enable_play(false); + HELPER_ASSERT_SUCCESS(server.start()); + + // Give server time to start + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + SrsBasicRtmpClient client(server.url(), 5 * SRS_UTIME_SECONDS, 5 * SRS_UTIME_SECONDS); + HELPER_ASSERT_SUCCESS(client.connect()); + HELPER_ASSERT_SUCCESS(client.publish(4096)); + + EXPECT_TRUE(client.sid() > 0); + } + + // Test publish with different chunk size + if (true) { + SrsRtmpTestServer server("live", "publish_chunk"); + server.enable_publish(true); + HELPER_ASSERT_SUCCESS(server.start()); + + // Give server time to start + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + SrsBasicRtmpClient client(server.url(), 2 * SRS_UTIME_SECONDS, 2 * SRS_UTIME_SECONDS); + HELPER_ASSERT_SUCCESS(client.connect()); + HELPER_ASSERT_SUCCESS(client.publish(8192)); + + EXPECT_TRUE(client.sid() > 0); + } +} + +VOID TEST(RTMPClientTest, RTMPClientPlay) +{ + srs_error_t err; + + // Test RTMP play + if (true) { + SrsRtmpTestServer server("live", "play_test"); + server.enable_publish(false); + server.enable_play(true); + HELPER_ASSERT_SUCCESS(server.start()); + + // Give server time to start + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + SrsBasicRtmpClient client(server.url(), 5 * SRS_UTIME_SECONDS, 5 * SRS_UTIME_SECONDS); + HELPER_ASSERT_SUCCESS(client.connect()); + HELPER_ASSERT_SUCCESS(client.play(4096)); + + EXPECT_TRUE(client.sid() > 0); + } + + // Test play with different chunk size + if (true) { + SrsRtmpTestServer server("live", "play_chunk"); + server.enable_play(true); + HELPER_ASSERT_SUCCESS(server.start()); + + // Give server time to start + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + SrsBasicRtmpClient client(server.url(), 2 * SRS_UTIME_SECONDS, 2 * SRS_UTIME_SECONDS); + HELPER_ASSERT_SUCCESS(client.connect()); + HELPER_ASSERT_SUCCESS(client.play(8192)); + + EXPECT_TRUE(client.sid() > 0); + } +} + +VOID TEST(RTMPClientTest, RTMPClientSendMessages) +{ + srs_error_t err; + + // Test send_and_free_message and send_and_free_messages + if (true) { + SrsRtmpTestServer server("live", "send_test"); + HELPER_ASSERT_SUCCESS(server.start()); + + // Give server time to start + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + SrsBasicRtmpClient client(server.url(), 5 * SRS_UTIME_SECONDS, 5 * SRS_UTIME_SECONDS); + HELPER_ASSERT_SUCCESS(client.connect()); + HELPER_ASSERT_SUCCESS(client.publish(4096)); + + // Test send_and_free_message with video data + if (true) { + SrsMediaPacket *video_msg = new SrsMediaPacket(); + video_msg->timestamp_ = 1000; + video_msg->message_type_ = SrsFrameTypeVideo; + video_msg->stream_id_ = client.sid(); + + char *video_data = new char[10]; + memset(video_data, 0x17, 10); // Video keyframe marker + video_msg->wrap(video_data, 10); + + HELPER_ASSERT_SUCCESS(client.send_and_free_message(video_msg)); + } + + // Test send_and_free_message with audio data + if (true) { + SrsMediaPacket *audio_msg = new SrsMediaPacket(); + audio_msg->timestamp_ = 1100; + audio_msg->message_type_ = SrsFrameTypeAudio; + audio_msg->stream_id_ = client.sid(); + + char *audio_data = new char[8]; + memset(audio_data, 0xAF, 8); // Audio AAC marker + audio_msg->wrap(audio_data, 8); + + HELPER_ASSERT_SUCCESS(client.send_and_free_message(audio_msg)); + } + + // Test send_and_free_messages with multiple messages + if (true) { + const int nb_msgs = 3; + SrsMediaPacket **msgs = new SrsMediaPacket *[nb_msgs]; + + // Create video message + msgs[0] = new SrsMediaPacket(); + msgs[0]->timestamp_ = 2000; + msgs[0]->message_type_ = SrsFrameTypeVideo; + msgs[0]->stream_id_ = client.sid(); + char *video_data = new char[12]; + memset(video_data, 0x27, 12); // Video inter frame marker + msgs[0]->wrap(video_data, 12); + + // Create audio message + msgs[1] = new SrsMediaPacket(); + msgs[1]->timestamp_ = 2050; + msgs[1]->message_type_ = SrsFrameTypeAudio; + msgs[1]->stream_id_ = client.sid(); + char *audio_data = new char[6]; + memset(audio_data, 0xAF, 6); + msgs[1]->wrap(audio_data, 6); + + // Create script message + msgs[2] = new SrsMediaPacket(); + msgs[2]->timestamp_ = 2100; + msgs[2]->message_type_ = SrsFrameTypeScript; + msgs[2]->stream_id_ = client.sid(); + char *script_data = new char[4]; + memcpy(script_data, "test", 4); + msgs[2]->wrap(script_data, 4); + + HELPER_ASSERT_SUCCESS(client.send_and_free_messages(msgs, nb_msgs)); + + // Messages are freed by send_and_free_messages, just free the array + delete[] msgs; + } + } +} + +VOID TEST(RTMPClientTest, RTMPClientMessageEdgeCases) +{ + srs_error_t err; + + // Test edge cases for message handling + if (true) { + SrsRtmpTestServer server("live", "edge_test"); + HELPER_ASSERT_SUCCESS(server.start()); + + // Give server time to start + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + SrsBasicRtmpClient client(server.url(), 5 * SRS_UTIME_SECONDS, 5 * SRS_UTIME_SECONDS); + HELPER_ASSERT_SUCCESS(client.connect()); + HELPER_ASSERT_SUCCESS(client.publish(4096)); + + // Test send_and_free_message with small message + if (true) { + SrsMediaPacket *small_msg = new SrsMediaPacket(); + small_msg->timestamp_ = 3000; + small_msg->message_type_ = SrsFrameTypeVideo; + small_msg->stream_id_ = client.sid(); + + // Create a small payload + char *small_data = new char[1]; + small_data[0] = 0x17; // Video keyframe marker + small_msg->wrap(small_data, 1); + + HELPER_ASSERT_SUCCESS(client.send_and_free_message(small_msg)); + } + + // Test send_and_free_messages with single message + if (true) { + const int nb_msgs = 1; + SrsMediaPacket **msgs = new SrsMediaPacket *[nb_msgs]; + + msgs[0] = new SrsMediaPacket(); + msgs[0]->timestamp_ = 4000; + msgs[0]->message_type_ = SrsFrameTypeAudio; + msgs[0]->stream_id_ = client.sid(); + char *audio_data = new char[2]; + memset(audio_data, 0xAF, 2); + msgs[0]->wrap(audio_data, 2); + + HELPER_ASSERT_SUCCESS(client.send_and_free_messages(msgs, nb_msgs)); + + // Messages are freed by send_and_free_messages, just free the array + delete[] msgs; + } + } +} + +VOID TEST(TCPConnectionTest, TCPConnectionNodelay) +{ + srs_error_t err; + + // Test TCP nodelay functionality + if (true) { + SrsTestTcpServer server("127.0.0.1"); + HELPER_ASSERT_SUCCESS(server.start()); + + // Give server time to start + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + SrsTestTcpClient client("127.0.0.1", server.get_port()); + HELPER_ASSERT_SUCCESS(client.connect()); + + // Give time for connection to be established + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + SrsTcpConnection *server_conn = server.get_connection(); + SrsTcpConnection *client_conn = client.get_connection(); + + ASSERT_TRUE(server_conn != NULL); + ASSERT_TRUE(client_conn != NULL); + + // Test set_tcp_nodelay on server connection + HELPER_ASSERT_SUCCESS(server_conn->set_tcp_nodelay(true)); + HELPER_ASSERT_SUCCESS(server_conn->set_tcp_nodelay(false)); + + // Test set_tcp_nodelay on client connection + HELPER_ASSERT_SUCCESS(client_conn->set_tcp_nodelay(true)); + HELPER_ASSERT_SUCCESS(client_conn->set_tcp_nodelay(false)); + + // Test multiple calls with same value + HELPER_ASSERT_SUCCESS(server_conn->set_tcp_nodelay(true)); + HELPER_ASSERT_SUCCESS(server_conn->set_tcp_nodelay(true)); + } +} + +VOID TEST(TCPConnectionTest, TCPConnectionSocketBuffer) +{ + srs_error_t err; + + // Test socket buffer functionality + if (true) { + SrsTestTcpServer server("127.0.0.1"); + HELPER_ASSERT_SUCCESS(server.start()); + + // Give server time to start + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + SrsTestTcpClient client("127.0.0.1", server.get_port()); + HELPER_ASSERT_SUCCESS(client.connect()); + + // Give time for connection to be established + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + SrsTcpConnection *server_conn = server.get_connection(); + SrsTcpConnection *client_conn = client.get_connection(); + + ASSERT_TRUE(server_conn != NULL); + ASSERT_TRUE(client_conn != NULL); + + // Test set_socket_buffer on server connection with different buffer sizes + HELPER_ASSERT_SUCCESS(server_conn->set_socket_buffer(1 * SRS_UTIME_SECONDS)); + HELPER_ASSERT_SUCCESS(server_conn->set_socket_buffer(2 * SRS_UTIME_SECONDS)); + HELPER_ASSERT_SUCCESS(server_conn->set_socket_buffer(5 * SRS_UTIME_SECONDS)); + + // Test set_socket_buffer on client connection + HELPER_ASSERT_SUCCESS(client_conn->set_socket_buffer(1 * SRS_UTIME_SECONDS)); + HELPER_ASSERT_SUCCESS(client_conn->set_socket_buffer(3 * SRS_UTIME_SECONDS)); + + // Test with smaller buffer sizes + HELPER_ASSERT_SUCCESS(server_conn->set_socket_buffer(500 * SRS_UTIME_MILLISECONDS)); + HELPER_ASSERT_SUCCESS(client_conn->set_socket_buffer(100 * SRS_UTIME_MILLISECONDS)); + } +} + +VOID TEST(TCPConnectionTest, TCPConnectionPingPong) +{ + srs_error_t err; + + // Test ping-pong communication between client and server + if (true) { + SrsTestTcpServer server("127.0.0.1"); + HELPER_ASSERT_SUCCESS(server.start()); + + // Give server time to start + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + SrsTestTcpClient client("127.0.0.1", server.get_port()); + HELPER_ASSERT_SUCCESS(client.connect()); + + // Give time for connection to be established + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + SrsTcpConnection *server_conn = server.get_connection(); + SrsTcpConnection *client_conn = client.get_connection(); + + ASSERT_TRUE(server_conn != NULL); + ASSERT_TRUE(client_conn != NULL); + + // Client sends "ping" to server + string ping_msg = "ping"; + ssize_t nwrite = 0; + HELPER_ASSERT_SUCCESS(client_conn->write((void *)ping_msg.data(), ping_msg.length(), &nwrite)); + EXPECT_EQ(4, nwrite); + + // Server receives "ping" + char recv_buf[1024]; + ssize_t nread = 0; + HELPER_ASSERT_SUCCESS(server_conn->read(recv_buf, sizeof(recv_buf), &nread)); + EXPECT_EQ(4, nread); + EXPECT_STREQ("ping", string(recv_buf, nread).c_str()); + + // Server sends "pong" back to client + string pong_msg = "pong"; + ssize_t nwrite2 = 0; + HELPER_ASSERT_SUCCESS(server_conn->write((void *)pong_msg.data(), pong_msg.length(), &nwrite2)); + EXPECT_EQ(4, nwrite2); + + // Client receives "pong" + char recv_buf2[1024]; + ssize_t nread2 = 0; + HELPER_ASSERT_SUCCESS(client_conn->read(recv_buf2, sizeof(recv_buf2), &nread2)); + EXPECT_EQ(4, nread2); + EXPECT_STREQ("pong", string(recv_buf2, nread2).c_str()); + } + + // Test multiple ping-pong exchanges + if (true) { + SrsTestTcpServer server("127.0.0.1"); + HELPER_ASSERT_SUCCESS(server.start()); + + // Give server time to start + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + SrsTestTcpClient client("127.0.0.1", server.get_port()); + HELPER_ASSERT_SUCCESS(client.connect()); + + // Give time for connection to be established + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + SrsTcpConnection *server_conn = server.get_connection(); + SrsTcpConnection *client_conn = client.get_connection(); + + ASSERT_TRUE(server_conn != NULL); + ASSERT_TRUE(client_conn != NULL); + + // Perform multiple ping-pong exchanges + for (int i = 0; i < 3; i++) { + // Client sends ping with sequence number + string ping_msg = "ping" + srs_strconv_format_int(i); + ssize_t nwrite = 0; + HELPER_ASSERT_SUCCESS(client_conn->write((void *)ping_msg.data(), ping_msg.length(), &nwrite)); + EXPECT_EQ((ssize_t)ping_msg.length(), nwrite); + + // Server receives ping + char recv_buf[1024]; + ssize_t nread = 0; + HELPER_ASSERT_SUCCESS(server_conn->read(recv_buf, sizeof(recv_buf), &nread)); + EXPECT_EQ((ssize_t)ping_msg.length(), nread); + EXPECT_STREQ(ping_msg.c_str(), string(recv_buf, nread).c_str()); + + // Server sends pong with sequence number + string pong_msg = "pong" + srs_strconv_format_int(i); + ssize_t nwrite2 = 0; + HELPER_ASSERT_SUCCESS(server_conn->write((void *)pong_msg.data(), pong_msg.length(), &nwrite2)); + EXPECT_EQ((ssize_t)pong_msg.length(), nwrite2); + + // Client receives pong + char recv_buf2[1024]; + ssize_t nread2 = 0; + HELPER_ASSERT_SUCCESS(client_conn->read(recv_buf2, sizeof(recv_buf2), &nread2)); + EXPECT_EQ((ssize_t)pong_msg.length(), nread2); + EXPECT_STREQ(pong_msg.c_str(), string(recv_buf2, nread2).c_str()); + } + } +} + +VOID TEST(TCPConnectionTest, BufferedReadWriterReadFully) +{ + srs_error_t err; + + // Test SrsBufferedReadWriter::read_fully when buf_ is not empty + if (true) { + SrsTestTcpServer server("127.0.0.1"); + HELPER_ASSERT_SUCCESS(server.start()); + + // Give server time to start + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + SrsTestTcpClient client("127.0.0.1", server.get_port()); + HELPER_ASSERT_SUCCESS(client.connect()); + + // Give time for connection to be established + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + SrsTcpConnection *server_conn = server.get_connection(); + SrsTcpConnection *client_conn = client.get_connection(); + + ASSERT_TRUE(server_conn != NULL); + ASSERT_TRUE(client_conn != NULL); + + // Create SrsBufferedReadWriter wrapping the server connection + SrsBufferedReadWriter buffered_reader(server_conn); + + // Client sends enough data to fill the cache (16 bytes) and more + string test_msg = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; // 36 bytes + ssize_t nwrite = 0; + HELPER_ASSERT_SUCCESS(client_conn->write((void *)test_msg.data(), test_msg.length(), &nwrite)); + EXPECT_EQ((ssize_t)test_msg.length(), nwrite); + + // First, use peek() to populate the internal buffer (buf_) + // This will trigger reload_buffer() and fill the internal cache_[16] buffer + char peek_buf[4]; + int peek_size = sizeof(peek_buf); + HELPER_ASSERT_SUCCESS(buffered_reader.peek(peek_buf, &peek_size)); + EXPECT_EQ(4, peek_size); + EXPECT_STREQ("0123", string(peek_buf, peek_size).c_str()); + + // Now call read_fully() which should use the buffered data first + // Since buf_ is not empty after peek(), it should read from buffer first + char read_buf[20]; + ssize_t nread = 0; + HELPER_ASSERT_SUCCESS(buffered_reader.read_fully(read_buf, sizeof(read_buf), &nread)); + EXPECT_EQ(20, nread); + // The first 16 bytes should come from the buffer, the rest from underlying io + EXPECT_STREQ("0123456789ABCDEFGHIJ", string(read_buf, nread).c_str()); + + // Read the remaining data to verify the full message + char remaining_buf[16]; + ssize_t nread2 = 0; + HELPER_ASSERT_SUCCESS(buffered_reader.read_fully(remaining_buf, sizeof(remaining_buf), &nread2)); + EXPECT_EQ(16, nread2); + EXPECT_STREQ("KLMNOPQRSTUVWXYZ", string(remaining_buf, nread2).c_str()); + } +} + +VOID TEST(TCPConnectionTest, BufferedReadWriterTimeoutAndStats) +{ + srs_error_t err; + + // Test SrsBufferedReadWriter timeout and statistics methods + if (true) { + SrsTestTcpServer server("127.0.0.1"); + HELPER_ASSERT_SUCCESS(server.start()); + + // Give server time to start + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + SrsTestTcpClient client("127.0.0.1", server.get_port()); + HELPER_ASSERT_SUCCESS(client.connect()); + + // Give time for connection to be established + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + SrsTcpConnection *server_conn = server.get_connection(); + SrsTcpConnection *client_conn = client.get_connection(); + + ASSERT_TRUE(server_conn != NULL); + ASSERT_TRUE(client_conn != NULL); + + // Create SrsBufferedReadWriter wrapping the server connection + SrsBufferedReadWriter buffered_reader(server_conn); + + // Test timeout methods + srs_utime_t original_recv_timeout = buffered_reader.get_recv_timeout(); + srs_utime_t original_send_timeout = buffered_reader.get_send_timeout(); + + // Set new timeouts + srs_utime_t new_recv_timeout = 5 * SRS_UTIME_SECONDS; + srs_utime_t new_send_timeout = 3 * SRS_UTIME_SECONDS; + + buffered_reader.set_recv_timeout(new_recv_timeout); + buffered_reader.set_send_timeout(new_send_timeout); + + // Verify timeouts were set correctly + EXPECT_EQ(new_recv_timeout, buffered_reader.get_recv_timeout()); + EXPECT_EQ(new_send_timeout, buffered_reader.get_send_timeout()); + + // Test statistics methods - initial values + int64_t initial_recv_bytes = buffered_reader.get_recv_bytes(); + + // Client sends data to server to test recv statistics + string test_msg = "Hello Statistics Test!"; + ssize_t nwrite = 0; + HELPER_ASSERT_SUCCESS(client_conn->write((void *)test_msg.data(), test_msg.length(), &nwrite)); + EXPECT_EQ((ssize_t)test_msg.length(), nwrite); + + // Server reads data using buffered reader + char read_buf[32]; + ssize_t nread = 0; + HELPER_ASSERT_SUCCESS(buffered_reader.read_fully(read_buf, test_msg.length(), &nread)); + EXPECT_EQ((ssize_t)test_msg.length(), nread); + EXPECT_STREQ(test_msg.c_str(), string(read_buf, nread).c_str()); + + // Check that recv bytes increased + int64_t final_recv_bytes = buffered_reader.get_recv_bytes(); + EXPECT_GT(final_recv_bytes, initial_recv_bytes); + + // Restore original timeouts + buffered_reader.set_recv_timeout(original_recv_timeout); + buffered_reader.set_send_timeout(original_send_timeout); + } +} + +VOID TEST(TCPConnectionTest, BufferedReadWriterWriteMethods) +{ + srs_error_t err; + + // Test SrsBufferedReadWriter write and writev methods + if (true) { + SrsTestTcpServer server("127.0.0.1"); + HELPER_ASSERT_SUCCESS(server.start()); + + // Give server time to start + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + SrsTestTcpClient client("127.0.0.1", server.get_port()); + HELPER_ASSERT_SUCCESS(client.connect()); + + // Give time for connection to be established + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + SrsTcpConnection *server_conn = server.get_connection(); + SrsTcpConnection *client_conn = client.get_connection(); + + ASSERT_TRUE(server_conn != NULL); + ASSERT_TRUE(client_conn != NULL); + + // Create SrsBufferedReadWriter wrapping the server connection + SrsBufferedReadWriter buffered_writer(server_conn); + + // Test write() method + string test_msg1 = "Hello Write Test!"; + ssize_t nwrite = 0; + HELPER_ASSERT_SUCCESS(buffered_writer.write((void *)test_msg1.data(), test_msg1.length(), &nwrite)); + EXPECT_EQ((ssize_t)test_msg1.length(), nwrite); + + // Client reads the data to verify write worked + char read_buf1[32]; + ssize_t nread1 = 0; + HELPER_ASSERT_SUCCESS(client_conn->read_fully(read_buf1, test_msg1.length(), &nread1)); + EXPECT_EQ((ssize_t)test_msg1.length(), nread1); + EXPECT_STREQ(test_msg1.c_str(), string(read_buf1, nread1).c_str()); + + // Test writev() method with multiple buffers + string part1 = "Hello "; + string part2 = "Writev "; + string part3 = "Test!"; + + struct iovec iov[3]; + iov[0].iov_base = (void *)part1.data(); + iov[0].iov_len = part1.length(); + iov[1].iov_base = (void *)part2.data(); + iov[1].iov_len = part2.length(); + iov[2].iov_base = (void *)part3.data(); + iov[2].iov_len = part3.length(); + + ssize_t nwrite_vec = 0; + HELPER_ASSERT_SUCCESS(buffered_writer.writev(iov, 3, &nwrite_vec)); + size_t expected_total = part1.length() + part2.length() + part3.length(); + EXPECT_EQ((ssize_t)expected_total, nwrite_vec); + + // Client reads the vectored data to verify writev worked + char read_buf2[32]; + ssize_t nread2 = 0; + HELPER_ASSERT_SUCCESS(client_conn->read_fully(read_buf2, expected_total, &nread2)); + EXPECT_EQ((ssize_t)expected_total, nread2); + string expected_result = part1 + part2 + part3; + EXPECT_STREQ(expected_result.c_str(), string(read_buf2, nread2).c_str()); + + // Test send bytes statistics after write operations + int64_t send_bytes = buffered_writer.get_send_bytes(); + EXPECT_GT(send_bytes, 0); + } +} + +VOID TEST(TCPConnectionTest, TcpConnectionTimeoutAndStats) +{ + srs_error_t err; + + // Test SrsTcpConnection timeout and statistics methods + if (true) { + SrsTestTcpServer server("127.0.0.1"); + HELPER_ASSERT_SUCCESS(server.start()); + + // Give server time to start + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + SrsTestTcpClient client("127.0.0.1", server.get_port()); + HELPER_ASSERT_SUCCESS(client.connect()); + + // Give time for connection to be established + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + SrsTcpConnection *server_conn = server.get_connection(); + SrsTcpConnection *client_conn = client.get_connection(); + + ASSERT_TRUE(server_conn != NULL); + ASSERT_TRUE(client_conn != NULL); + + // Test timeout methods + srs_utime_t original_recv_timeout = server_conn->get_recv_timeout(); + + // Set new receive timeout + srs_utime_t new_recv_timeout = 10 * SRS_UTIME_SECONDS; + server_conn->set_recv_timeout(new_recv_timeout); + + // Verify timeout was set correctly + EXPECT_EQ(new_recv_timeout, server_conn->get_recv_timeout()); + + // Test statistics methods - initial values + int64_t initial_recv_bytes = server_conn->get_recv_bytes(); + int64_t initial_send_bytes = server_conn->get_send_bytes(); + + // Client sends data to server to test recv statistics + string test_msg = "Hello TCP Connection Statistics!"; + ssize_t nwrite = 0; + HELPER_ASSERT_SUCCESS(client_conn->write((void *)test_msg.data(), test_msg.length(), &nwrite)); + EXPECT_EQ((ssize_t)test_msg.length(), nwrite); + + // Server reads data using read_fully + char read_buf[64]; + ssize_t nread = 0; + HELPER_ASSERT_SUCCESS(server_conn->read_fully(read_buf, test_msg.length(), &nread)); + EXPECT_EQ((ssize_t)test_msg.length(), nread); + EXPECT_STREQ(test_msg.c_str(), string(read_buf, nread).c_str()); + + // Check that recv bytes increased + int64_t final_recv_bytes = server_conn->get_recv_bytes(); + EXPECT_GT(final_recv_bytes, initial_recv_bytes); + + // Server sends response to test send statistics + string response_msg = "Response from server!"; + ssize_t nwrite_resp = 0; + HELPER_ASSERT_SUCCESS(server_conn->write((void *)response_msg.data(), response_msg.length(), &nwrite_resp)); + EXPECT_EQ((ssize_t)response_msg.length(), nwrite_resp); + + // Check that send bytes increased + int64_t final_send_bytes = server_conn->get_send_bytes(); + EXPECT_GT(final_send_bytes, initial_send_bytes); + + // Client reads response to verify + char response_buf[32]; + ssize_t nread_resp = 0; + HELPER_ASSERT_SUCCESS(client_conn->read_fully(response_buf, response_msg.length(), &nread_resp)); + EXPECT_EQ((ssize_t)response_msg.length(), nread_resp); + EXPECT_STREQ(response_msg.c_str(), string(response_buf, nread_resp).c_str()); + + // Restore original timeout + server_conn->set_recv_timeout(original_recv_timeout); + } +} + +VOID TEST(TCPConnectionTest, TcpConnectionReadFully) +{ + srs_error_t err; + + // Test SrsTcpConnection::read_fully method with various scenarios + if (true) { + SrsTestTcpServer server("127.0.0.1"); + HELPER_ASSERT_SUCCESS(server.start()); + + // Give server time to start + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + SrsTestTcpClient client("127.0.0.1", server.get_port()); + HELPER_ASSERT_SUCCESS(client.connect()); + + // Give time for connection to be established + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + SrsTcpConnection *server_conn = server.get_connection(); + SrsTcpConnection *client_conn = client.get_connection(); + + ASSERT_TRUE(server_conn != NULL); + ASSERT_TRUE(client_conn != NULL); + + // Test read_fully with exact size + string test_msg1 = "Exact size test message!"; + ssize_t nwrite1 = 0; + HELPER_ASSERT_SUCCESS(client_conn->write((void *)test_msg1.data(), test_msg1.length(), &nwrite1)); + EXPECT_EQ((ssize_t)test_msg1.length(), nwrite1); + + char read_buf1[32]; + ssize_t nread1 = 0; + HELPER_ASSERT_SUCCESS(server_conn->read_fully(read_buf1, test_msg1.length(), &nread1)); + EXPECT_EQ((ssize_t)test_msg1.length(), nread1); + EXPECT_STREQ(test_msg1.c_str(), string(read_buf1, nread1).c_str()); + + // Test read_fully with larger buffer + string test_msg2 = "Large buffer test!"; + ssize_t nwrite2 = 0; + HELPER_ASSERT_SUCCESS(client_conn->write((void *)test_msg2.data(), test_msg2.length(), &nwrite2)); + EXPECT_EQ((ssize_t)test_msg2.length(), nwrite2); + + char read_buf2[64]; // Larger buffer than needed + ssize_t nread2 = 0; + HELPER_ASSERT_SUCCESS(server_conn->read_fully(read_buf2, test_msg2.length(), &nread2)); + EXPECT_EQ((ssize_t)test_msg2.length(), nread2); + EXPECT_STREQ(test_msg2.c_str(), string(read_buf2, nread2).c_str()); + + // Test read_fully with multiple small reads + string part1 = "Part1"; + string part2 = "Part2"; + string part3 = "Part3"; + + // Send parts separately + ssize_t nwrite_p1 = 0, nwrite_p2 = 0, nwrite_p3 = 0; + HELPER_ASSERT_SUCCESS(client_conn->write((void *)part1.data(), part1.length(), &nwrite_p1)); + HELPER_ASSERT_SUCCESS(client_conn->write((void *)part2.data(), part2.length(), &nwrite_p2)); + HELPER_ASSERT_SUCCESS(client_conn->write((void *)part3.data(), part3.length(), &nwrite_p3)); + + // Read all parts in one read_fully call + size_t total_size = part1.length() + part2.length() + part3.length(); + char read_buf3[32]; + ssize_t nread3 = 0; + HELPER_ASSERT_SUCCESS(server_conn->read_fully(read_buf3, total_size, &nread3)); + EXPECT_EQ((ssize_t)total_size, nread3); + + string expected_combined = part1 + part2 + part3; + EXPECT_STREQ(expected_combined.c_str(), string(read_buf3, nread3).c_str()); + } +} + +VOID TEST(UDPConnectionTest, UdpConnectionPingPong) +{ + srs_error_t err; + + // Test UDP ping-pong communication using SrsUdpTestServer and SrsUdpTestClient + if (true) { + SrsUdpTestServer server("127.0.0.1"); + HELPER_ASSERT_SUCCESS(server.start()); + + // Give server time to start + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + SrsUdpTestClient client("127.0.0.1", server.get_port()); + HELPER_ASSERT_SUCCESS(client.connect()); + + // Give time for connection to be established + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + SrsStSocket *server_socket = server.get_socket(); + SrsStSocket *client_socket = client.get_socket(); + + ASSERT_TRUE(server_socket != NULL); + ASSERT_TRUE(client_socket != NULL); + + // Test basic ping-pong + string ping_msg = "ping"; + ssize_t nwrite = 0; + HELPER_ASSERT_SUCCESS(client.sendto((void *)ping_msg.data(), ping_msg.length(), &nwrite)); + EXPECT_EQ((ssize_t)ping_msg.length(), nwrite); + + // Give time for server to process and echo back + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + // Client receives the echoed message + char pong_buf[32]; + ssize_t nread = 0; + HELPER_ASSERT_SUCCESS(client.recvfrom(pong_buf, sizeof(pong_buf), &nread)); + EXPECT_EQ((ssize_t)ping_msg.length(), nread); + EXPECT_STREQ(ping_msg.c_str(), string(pong_buf, nread).c_str()); + + // Test multiple ping-pong exchanges + for (int i = 0; i < 3; i++) { + string ping_msg_seq = "ping" + srs_strconv_format_int(i); + ssize_t nwrite_seq = 0; + HELPER_ASSERT_SUCCESS(client.sendto((void *)ping_msg_seq.data(), ping_msg_seq.length(), &nwrite_seq)); + EXPECT_EQ((ssize_t)ping_msg_seq.length(), nwrite_seq); + + // Give time for server to process and echo back + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + // Client receives the echoed message + char pong_buf_seq[32]; + ssize_t nread_seq = 0; + HELPER_ASSERT_SUCCESS(client.recvfrom(pong_buf_seq, sizeof(pong_buf_seq), &nread_seq)); + EXPECT_EQ((ssize_t)ping_msg_seq.length(), nread_seq); + EXPECT_STREQ(ping_msg_seq.c_str(), string(pong_buf_seq, nread_seq).c_str()); + } + + // Test larger message + string large_ping = "This is a larger ping message for UDP testing!"; + ssize_t nwrite_large = 0; + HELPER_ASSERT_SUCCESS(client.sendto((void *)large_ping.data(), large_ping.length(), &nwrite_large)); + EXPECT_EQ((ssize_t)large_ping.length(), nwrite_large); + + // Give time for server to process and echo back + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + + // Client receives the echoed large message + char large_pong_buf[128]; + ssize_t nread_large = 0; + HELPER_ASSERT_SUCCESS(client.recvfrom(large_pong_buf, sizeof(large_pong_buf), &nread_large)); + EXPECT_EQ((ssize_t)large_ping.length(), nread_large); + EXPECT_STREQ(large_ping.c_str(), string(large_pong_buf, nread_large).c_str()); + + // Stop server + server.stop(); + } +} + +VOID TEST(RTPVideoBuilderTest, PackageStapAHevc) +{ + srs_error_t err; + + // Test HEVC STAP-A packaging with VPS/SPS/PPS NALUs + if (true) { + // Create mock SrsRtpVideoBuilder + SrsRtpVideoBuilder builder; + builder.video_payload_type_ = 96; + builder.video_ssrc_ = 0x12345678; + builder.video_sequence_ = 100; + + // Create mock SrsFormat with HEVC codec + SrsFormat format; + format.vcodec_ = new SrsVideoCodecConfig(); + format.vcodec_->id_ = SrsVideoCodecIdHEVC; + + // Create HEVC decoder configuration record with VPS/SPS/PPS NALUs + SrsHevcHvccNalu vps_nalu; + vps_nalu.nal_unit_type_ = SrsHevcNaluType_VPS; + vps_nalu.num_nalus_ = 1; + + SrsHevcNalData vps_data; + vps_data.nal_unit_length_ = 24; + // Mock VPS data (24 bytes) + uint8_t vps_bytes[] = { + 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60, + 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x03, 0x00, 0x5d, 0x95, 0x98, 0x09}; + vps_data.nal_unit_data_.assign(vps_bytes, vps_bytes + 24); + vps_nalu.nal_data_vec_.push_back(vps_data); + + SrsHevcHvccNalu sps_nalu; + sps_nalu.nal_unit_type_ = SrsHevcNaluType_SPS; + sps_nalu.num_nalus_ = 1; + + SrsHevcNalData sps_data; + sps_data.nal_unit_length_ = 40; + // Mock SPS data (40 bytes) + uint8_t sps_bytes[] = { + 0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, + 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, + 0x00, 0x5d, 0xa0, 0x02, 0x80, 0x80, 0x2d, 0x16, + 0x59, 0x59, 0xa4, 0x93, 0x2b, 0xc0, 0x40, 0x40, + 0x00, 0x00, 0x03, 0x00, 0x40, 0x00, 0x00, 0x07}; + sps_data.nal_unit_data_.assign(sps_bytes, sps_bytes + 40); + sps_nalu.nal_data_vec_.push_back(sps_data); + + SrsHevcHvccNalu pps_nalu; + pps_nalu.nal_unit_type_ = SrsHevcNaluType_PPS; + pps_nalu.num_nalus_ = 1; + + SrsHevcNalData pps_data; + pps_data.nal_unit_length_ = 8; + // Mock PPS data (8 bytes) + uint8_t pps_bytes[] = { + 0x44, 0x01, 0xc1, 0x72, 0xb4, 0x62, 0x40, 0x00}; + pps_data.nal_unit_data_.assign(pps_bytes, pps_bytes + 8); + pps_nalu.nal_data_vec_.push_back(pps_data); + + // Add NALUs to decoder configuration record + format.vcodec_->hevc_dec_conf_record_.nalu_vec_.push_back(vps_nalu); + format.vcodec_->hevc_dec_conf_record_.nalu_vec_.push_back(sps_nalu); + format.vcodec_->hevc_dec_conf_record_.nalu_vec_.push_back(pps_nalu); + + // Set format in builder + builder.format_ = &format; + + // Create mock media packet + SrsMediaPacket msg; + msg.timestamp_ = 1000; + + // Create RTP packet for STAP-A + SrsRtpPacket pkt; + + // Test package_stap_a method + HELPER_ASSERT_SUCCESS(builder.package_stap_a(&msg, &pkt)); + + // Verify RTP header settings + EXPECT_EQ(96, pkt.header.get_payload_type()); + EXPECT_EQ(0x12345678, pkt.header.get_ssrc()); + EXPECT_EQ(SrsFrameTypeVideo, pkt.frame_type_); + EXPECT_FALSE(pkt.header.get_marker()); + EXPECT_EQ(100, pkt.header.get_sequence()); + EXPECT_EQ(90000, pkt.header.get_timestamp()); // 1000 * 90 + + // Verify NALU type is set to HEVC STAP + EXPECT_EQ(kStapHevc, pkt.nalu_type_); + + // Verify payload type is HEVC STAP + EXPECT_EQ(SrsRtpPacketPayloadTypeSTAPHevc, pkt.payload_type_); + + // Verify payload is SrsRtpSTAPPayloadHevc + ASSERT_TRUE(pkt.payload_ != NULL); + SrsRtpSTAPPayloadHevc *stap_payload = static_cast(pkt.payload_); + + // Verify NALUs are correctly added + EXPECT_EQ(3, (int)stap_payload->nalus_.size()); + + // Verify VPS NALU + SrsNaluSample *vps_sample = stap_payload->get_vps(); + ASSERT_TRUE(vps_sample != NULL); + EXPECT_EQ(24, vps_sample->size_); + EXPECT_EQ(SrsHevcNaluType_VPS, SrsHevcNaluTypeParse(vps_sample->bytes_[0])); + + // Verify SPS NALU + SrsNaluSample *sps_sample = stap_payload->get_sps(); + ASSERT_TRUE(sps_sample != NULL); + EXPECT_EQ(40, sps_sample->size_); + EXPECT_EQ(SrsHevcNaluType_SPS, SrsHevcNaluTypeParse(sps_sample->bytes_[0])); + + // Verify PPS NALU + SrsNaluSample *pps_sample = stap_payload->get_pps(); + ASSERT_TRUE(pps_sample != NULL); + EXPECT_EQ(8, pps_sample->size_); + EXPECT_EQ(SrsHevcNaluType_PPS, SrsHevcNaluTypeParse(pps_sample->bytes_[0])); + + // Verify data integrity - check first few bytes of each NALU + EXPECT_EQ(0x40, (uint8_t)vps_sample->bytes_[0]); // VPS header + EXPECT_EQ(0x42, (uint8_t)sps_sample->bytes_[0]); // SPS header + EXPECT_EQ(0x44, (uint8_t)pps_sample->bytes_[0]); // PPS header + + // Cleanup + srs_freep(format.vcodec_); + } +} + +VOID TEST(RTPVideoBuilderTest, PackageNalusWithEmptySamples) +{ + srs_error_t err; + + // Test package_nalus method with empty samples to cover the continue path + if (true) { + // Create mock SrsRtpVideoBuilder + SrsRtpVideoBuilder builder; + builder.video_payload_type_ = 96; + builder.video_ssrc_ = 0x12345678; + builder.video_sequence_ = 200; + + // Create mock SrsFormat with H.264 codec + SrsFormat format; + format.vcodec_ = new SrsVideoCodecConfig(); + format.vcodec_->id_ = SrsVideoCodecIdAVC; + + // Set format in builder + builder.format_ = &format; + + // Create mock media packet + SrsMediaPacket msg; + msg.timestamp_ = 2000; + + // Create vector of NALU samples with some empty samples + vector samples; + + // Create first empty sample (size = 0) - this should be skipped + SrsNaluSample *empty_sample1 = new SrsNaluSample(); + empty_sample1->size_ = 0; + empty_sample1->bytes_ = NULL; + samples.push_back(empty_sample1); + + // Create valid IDR sample + SrsNaluSample *idr_sample = new SrsNaluSample(); + idr_sample->size_ = 20; + uint8_t idr_bytes[] = { + 0x65, 0x88, 0x84, 0x00, 0x33, 0x56, 0x7e, 0x00, + 0x43, 0x8a, 0x02, 0x09, 0x96, 0xe5, 0x93, 0x2b, + 0x80, 0x40, 0x45, 0x02}; + idr_sample->bytes_ = (char *)malloc(idr_sample->size_); + memcpy(idr_sample->bytes_, idr_bytes, idr_sample->size_); + samples.push_back(idr_sample); + + // Create second empty sample (size = 0) - this should also be skipped + SrsNaluSample *empty_sample2 = new SrsNaluSample(); + empty_sample2->size_ = 0; + empty_sample2->bytes_ = NULL; + samples.push_back(empty_sample2); + + // Create valid P-frame sample + SrsNaluSample *p_sample = new SrsNaluSample(); + p_sample->size_ = 15; + uint8_t p_bytes[] = { + 0x41, 0x9a, 0x24, 0x66, 0x54, 0x66, 0x90, 0x80, + 0x00, 0x47, 0xbe, 0x7c, 0x05, 0x5a, 0x80}; + p_sample->bytes_ = (char *)malloc(p_sample->size_); + memcpy(p_sample->bytes_, p_bytes, p_sample->size_); + samples.push_back(p_sample); + + // Create third empty sample (size = 0) - this should also be skipped + SrsNaluSample *empty_sample3 = new SrsNaluSample(); + empty_sample3->size_ = 0; + empty_sample3->bytes_ = NULL; + samples.push_back(empty_sample3); + + // Create RTP packets vector + vector pkts; + + // Test package_nalus method - should skip empty samples and package only valid ones + HELPER_ASSERT_SUCCESS(builder.package_nalus(&msg, samples, pkts)); + + // Verify that only one RTP packet was created (combining the two valid NALUs) + EXPECT_EQ(1, (int)pkts.size()); + + if (!pkts.empty()) { + SrsRtpPacket *pkt = pkts[0]; + + // Verify RTP header settings + EXPECT_EQ(96, pkt->header.get_payload_type()); + EXPECT_EQ(0x12345678, pkt->header.get_ssrc()); + EXPECT_EQ(SrsFrameTypeVideo, pkt->frame_type_); + EXPECT_EQ(200, pkt->header.get_sequence()); + EXPECT_EQ(180000, pkt->header.get_timestamp()); // 2000 * 90 + + // Verify NALU type is set to first valid NALU type (IDR) + EXPECT_EQ(SrsAvcNaluTypeIDR, pkt->nalu_type_); + + // Verify payload type is NALU + EXPECT_EQ(SrsRtpPacketPayloadTypeNALU, pkt->payload_type_); + + // Verify payload exists + ASSERT_TRUE(pkt->payload_ != NULL); + SrsRtpRawNALUs *raw_nalus = static_cast(pkt->payload_); + + // Verify that 3 NALUs are in the payload (2 valid NALUs + 1 separator) + // SrsRtpRawNALUs adds separator bytes (\0\0\1) between NALUs + EXPECT_EQ(3, (int)raw_nalus->nalus_.size()); + + // Verify total bytes (should be sum of valid samples + separator) + EXPECT_EQ(38, (int)raw_nalus->nb_bytes()); // 20 + 15 + 3 separator = 38 bytes + } + + // Cleanup RTP packets + for (size_t i = 0; i < pkts.size(); i++) { + srs_freep(pkts[i]); + } + + // Cleanup samples + for (size_t i = 0; i < samples.size(); i++) { + SrsNaluSample *sample = samples[i]; + if (sample->bytes_) { + free(sample->bytes_); + } + srs_freep(sample); + } + + // Cleanup format + srs_freep(format.vcodec_); + } +} + +VOID TEST(RTPVideoBuilderTest, PackageNalusAllEmptySamples) +{ + srs_error_t err; + + // Test package_nalus method with all empty samples - should create no RTP packets + if (true) { + // Create mock SrsRtpVideoBuilder + SrsRtpVideoBuilder builder; + builder.video_payload_type_ = 96; + builder.video_ssrc_ = 0x12345678; + builder.video_sequence_ = 300; + + // Create mock SrsFormat with HEVC codec + SrsFormat format; + format.vcodec_ = new SrsVideoCodecConfig(); + format.vcodec_->id_ = SrsVideoCodecIdHEVC; + + // Set format in builder + builder.format_ = &format; + + // Create mock media packet + SrsMediaPacket msg; + msg.timestamp_ = 3000; + + // Create vector of NALU samples with ALL empty samples + vector samples; + + // Create multiple empty samples + for (int i = 0; i < 5; i++) { + SrsNaluSample *empty_sample = new SrsNaluSample(); + empty_sample->size_ = 0; + empty_sample->bytes_ = NULL; + samples.push_back(empty_sample); + } + + // Create RTP packets vector + vector pkts; + + // Test package_nalus method - should skip all empty samples and create no packets + HELPER_ASSERT_SUCCESS(builder.package_nalus(&msg, samples, pkts)); + + // Verify that no RTP packets were created (all samples were empty) + EXPECT_EQ(0, (int)pkts.size()); + + // Cleanup samples + for (size_t i = 0; i < samples.size(); i++) { + srs_freep(samples[i]); + } + + // Cleanup format + srs_freep(format.vcodec_); + } +} + +VOID TEST(RTPVideoBuilderTest, PackageNalusLargePayload) +{ + srs_error_t err; + + // Test package_nalus method with large payload that exceeds kRtpMaxPayloadSize (1200 bytes) + // This should trigger FU-A fragmentation path + if (true) { + // Create mock SrsRtpVideoBuilder + SrsRtpVideoBuilder builder; + builder.video_payload_type_ = 96; + builder.video_ssrc_ = 0x87654321; + builder.video_sequence_ = 400; + + // Create mock SrsFormat with H.264 codec + SrsFormat format; + format.vcodec_ = new SrsVideoCodecConfig(); + format.vcodec_->id_ = SrsVideoCodecIdAVC; + + // Set format in builder + builder.format_ = &format; + + // Create mock media packet + SrsMediaPacket msg; + msg.timestamp_ = 4000; + + // Create vector with single large IDR NALU sample > kRtpMaxPayloadSize (1200 bytes) + vector samples; + + // Create single large IDR sample (10240 bytes) that will be fragmented + SrsNaluSample *large_idr_sample = new SrsNaluSample(); + large_idr_sample->size_ = 10240; + large_idr_sample->bytes_ = (char *)malloc(large_idr_sample->size_); + // Fill with IDR NALU header and dummy data + large_idr_sample->bytes_[0] = 0x65; // IDR NALU type + for (int i = 1; i < large_idr_sample->size_; i++) { + large_idr_sample->bytes_[i] = (char)(0x80 + (i % 128)); + } + samples.push_back(large_idr_sample); + + // Total size: 10240 bytes >> kRtpMaxPayloadSize (1200) + + // Create RTP packets vector + vector pkts; + + // Test package_nalus method - should create multiple FU-A packets due to large size + HELPER_ASSERT_SUCCESS(builder.package_nalus(&msg, samples, pkts)); + + // Verify that multiple RTP packets were created (FU-A fragmentation) + EXPECT_GT((int)pkts.size(), 1); + + // Verify all packets have correct RTP header settings + for (size_t i = 0; i < pkts.size(); i++) { + SrsRtpPacket *pkt = pkts[i]; + + // Verify RTP header settings + EXPECT_EQ(96, pkt->header.get_payload_type()); + EXPECT_EQ(0x87654321, pkt->header.get_ssrc()); + EXPECT_EQ(SrsFrameTypeVideo, pkt->frame_type_); + EXPECT_EQ(400 + i, pkt->header.get_sequence()); // Sequence increments + EXPECT_EQ(360000, pkt->header.get_timestamp()); // 4000 * 90 + + // Verify NALU type is FU-A for fragmented packets + EXPECT_EQ(kFuA, pkt->nalu_type_); + + // Verify payload type is FUA + EXPECT_EQ(SrsRtpPacketPayloadTypeFUA, pkt->payload_type_); + + // Verify payload exists + ASSERT_TRUE(pkt->payload_ != NULL); + SrsRtpFUAPayload *fua_payload = static_cast(pkt->payload_); + + // Verify FU-A header settings + EXPECT_EQ(SrsAvcNaluTypeIDR, fua_payload->nalu_type_); // First NALU type + + // First packet should have start=true, end=false + // Last packet should have start=false, end=true + // Middle packets should have start=false, end=false + if (i == 0) { + EXPECT_TRUE(fua_payload->start_); + EXPECT_FALSE(fua_payload->end_); + } else if (i == pkts.size() - 1) { + EXPECT_FALSE(fua_payload->start_); + EXPECT_TRUE(fua_payload->end_); + } else { + EXPECT_FALSE(fua_payload->start_); + EXPECT_FALSE(fua_payload->end_); + } + + // Verify payload has NALU samples + EXPECT_GT((int)fua_payload->nalus_.size(), 0); + } + + // Cleanup RTP packets + for (size_t i = 0; i < pkts.size(); i++) { + srs_freep(pkts[i]); + } + + // Cleanup sample + for (size_t i = 0; i < samples.size(); i++) { + SrsNaluSample *sample = samples[i]; + if (sample->bytes_) { + free(sample->bytes_); + } + srs_freep(sample); + } + + // Cleanup format + srs_freep(format.vcodec_); + } +} + +VOID TEST(RTPVideoBuilderTest, PackageNalusLargePayloadHevc) +{ + srs_error_t err; + + // Test package_nalus method with large HEVC payload that exceeds kRtpMaxPayloadSize (1200 bytes) + // This should trigger HEVC FU-A fragmentation path + if (true) { + // Create mock SrsRtpVideoBuilder + SrsRtpVideoBuilder builder; + builder.video_payload_type_ = 96; + builder.video_ssrc_ = 0x11223344; + builder.video_sequence_ = 500; + + // Create mock SrsFormat with HEVC codec + SrsFormat format; + format.vcodec_ = new SrsVideoCodecConfig(); + format.vcodec_->id_ = SrsVideoCodecIdHEVC; + + // Set format in builder + builder.format_ = &format; + + // Create mock media packet + SrsMediaPacket msg; + msg.timestamp_ = 5000; + + // Create vector with single large HEVC IDR NALU sample > kRtpMaxPayloadSize (1200 bytes) + vector samples; + + // Create single large HEVC IDR sample (10240 bytes) that will be fragmented + SrsNaluSample *large_hevc_idr_sample = new SrsNaluSample(); + large_hevc_idr_sample->size_ = 10240; + large_hevc_idr_sample->bytes_ = (char *)malloc(large_hevc_idr_sample->size_); + // Fill with HEVC IDR NALU header and dummy data + // HEVC IDR NALU type is 19 (0x13), with layer_id=0, temporal_id=1 + large_hevc_idr_sample->bytes_[0] = (19 << 1) | 0; // NALU type 19, layer_id bit 0 + large_hevc_idr_sample->bytes_[1] = 1; // layer_id bits + temporal_id + for (int i = 2; i < large_hevc_idr_sample->size_; i++) { + large_hevc_idr_sample->bytes_[i] = (char)(0x90 + (i % 128)); + } + samples.push_back(large_hevc_idr_sample); + + // Total size: 10240 bytes >> kRtpMaxPayloadSize (1200) + + // Create RTP packets vector + vector pkts; + + // Test package_nalus method - should create multiple HEVC FU-A packets due to large size + HELPER_ASSERT_SUCCESS(builder.package_nalus(&msg, samples, pkts)); + + // Verify that multiple RTP packets were created (HEVC FU-A fragmentation) + EXPECT_GT((int)pkts.size(), 1); + + // Verify all packets have correct RTP header settings + for (size_t i = 0; i < pkts.size(); i++) { + SrsRtpPacket *pkt = pkts[i]; + + // Verify RTP header settings + EXPECT_EQ(96, pkt->header.get_payload_type()); + EXPECT_EQ(0x11223344, pkt->header.get_ssrc()); + EXPECT_EQ(SrsFrameTypeVideo, pkt->frame_type_); + EXPECT_EQ(500 + i, pkt->header.get_sequence()); // Sequence increments + EXPECT_EQ(450000, pkt->header.get_timestamp()); // 5000 * 90 + + // Verify NALU type is FU-A for fragmented packets + EXPECT_EQ(kFuA, pkt->nalu_type_); + + // Verify payload type is HEVC FUA + EXPECT_EQ(SrsRtpPacketPayloadTypeFUAHevc, pkt->payload_type_); + + // Verify payload exists + ASSERT_TRUE(pkt->payload_ != NULL); + SrsRtpFUAPayloadHevc *fua_payload = static_cast(pkt->payload_); + + // Verify HEVC FU-A header settings + EXPECT_EQ(SrsHevcNaluType_CODED_SLICE_IDR, fua_payload->nalu_type_); // First NALU type (19) + + // First packet should have start=true, end=false + // Last packet should have start=false, end=true + // Middle packets should have start=false, end=false + if (i == 0) { + EXPECT_TRUE(fua_payload->start_); + EXPECT_FALSE(fua_payload->end_); + } else if (i == pkts.size() - 1) { + EXPECT_FALSE(fua_payload->start_); + EXPECT_TRUE(fua_payload->end_); + } else { + EXPECT_FALSE(fua_payload->start_); + EXPECT_FALSE(fua_payload->end_); + } + + // Verify payload has NALU samples + EXPECT_GT((int)fua_payload->nalus_.size(), 0); + } + + // Cleanup RTP packets + for (size_t i = 0; i < pkts.size(); i++) { + srs_freep(pkts[i]); + } + + // Cleanup sample + for (size_t i = 0; i < samples.size(); i++) { + SrsNaluSample *sample = samples[i]; + if (sample->bytes_) { + free(sample->bytes_); + } + srs_freep(sample); + } + + // Cleanup format + srs_freep(format.vcodec_); + } +} + +VOID TEST(RTPVideoBuilderTest, PackageNalusMultipleSamplesLargePayload) +{ + srs_error_t err; + + // Test package_nalus method with multiple IDR NALU samples in one frame that exceed kRtpMaxPayloadSize + // This simulates a real IDR frame with multiple IDR NALU samples + if (true) { + // Create mock SrsRtpVideoBuilder + SrsRtpVideoBuilder builder; + builder.video_payload_type_ = 96; + builder.video_ssrc_ = 0xAABBCCDD; + builder.video_sequence_ = 600; + + // Create mock SrsFormat with H.264 codec + SrsFormat format; + format.vcodec_ = new SrsVideoCodecConfig(); + format.vcodec_->id_ = SrsVideoCodecIdAVC; + + // Set format in builder + builder.format_ = &format; + + // Create mock media packet + SrsMediaPacket msg; + msg.timestamp_ = 6000; + + // Create vector with multiple IDR NALU samples that together exceed kRtpMaxPayloadSize + vector samples; + + // Create first IDR NALU sample (3000 bytes) + SrsNaluSample *idr_sample1 = new SrsNaluSample(); + idr_sample1->size_ = 3000; + idr_sample1->bytes_ = (char *)malloc(idr_sample1->size_); + idr_sample1->bytes_[0] = 0x65; // IDR NALU type + for (int i = 1; i < idr_sample1->size_; i++) { + idr_sample1->bytes_[i] = (char)(0x10 + (i % 128)); + } + samples.push_back(idr_sample1); + + // Create second IDR NALU sample (2500 bytes) + SrsNaluSample *idr_sample2 = new SrsNaluSample(); + idr_sample2->size_ = 2500; + idr_sample2->bytes_ = (char *)malloc(idr_sample2->size_); + idr_sample2->bytes_[0] = 0x65; // IDR NALU type + for (int i = 1; i < idr_sample2->size_; i++) { + idr_sample2->bytes_[i] = (char)(0x20 + (i % 128)); + } + samples.push_back(idr_sample2); + + // Create third IDR NALU sample (4000 bytes) + SrsNaluSample *idr_sample3 = new SrsNaluSample(); + idr_sample3->size_ = 4000; + idr_sample3->bytes_ = (char *)malloc(idr_sample3->size_); + idr_sample3->bytes_[0] = 0x65; // IDR NALU type + for (int i = 1; i < idr_sample3->size_; i++) { + idr_sample3->bytes_[i] = (char)(0x30 + (i % 128)); + } + samples.push_back(idr_sample3); + + // Total size: 3000 + 2500 + 4000 + 6 separators = 9506 bytes >> kRtpMaxPayloadSize (1200) + + // Create RTP packets vector + vector pkts; + + // Test package_nalus method - should create multiple FU-A packets due to large total size + HELPER_ASSERT_SUCCESS(builder.package_nalus(&msg, samples, pkts)); + + // Verify that multiple RTP packets were created (FU-A fragmentation) + EXPECT_GT((int)pkts.size(), 1); + + // Verify all packets have correct RTP header settings + for (size_t i = 0; i < pkts.size(); i++) { + SrsRtpPacket *pkt = pkts[i]; + + // Verify RTP header settings + EXPECT_EQ(96, pkt->header.get_payload_type()); + EXPECT_EQ(0xAABBCCDD, pkt->header.get_ssrc()); + EXPECT_EQ(SrsFrameTypeVideo, pkt->frame_type_); + EXPECT_EQ(600 + i, pkt->header.get_sequence()); // Sequence increments + EXPECT_EQ(540000, pkt->header.get_timestamp()); // 6000 * 90 + + // Verify NALU type is FU-A for fragmented packets + EXPECT_EQ(kFuA, pkt->nalu_type_); + + // Verify payload type is FUA + EXPECT_EQ(SrsRtpPacketPayloadTypeFUA, pkt->payload_type_); + + // Verify payload exists + ASSERT_TRUE(pkt->payload_ != NULL); + SrsRtpFUAPayload *fua_payload = static_cast(pkt->payload_); + + // Verify FU-A header settings - NALU type should be from first NALU (IDR) + EXPECT_EQ(SrsAvcNaluTypeIDR, fua_payload->nalu_type_); // First NALU type + + // First packet should have start=true, end=false + // Last packet should have start=false, end=true + // Middle packets should have start=false, end=false + if (i == 0) { + EXPECT_TRUE(fua_payload->start_); + EXPECT_FALSE(fua_payload->end_); + } else if (i == pkts.size() - 1) { + EXPECT_FALSE(fua_payload->start_); + EXPECT_TRUE(fua_payload->end_); + } else { + EXPECT_FALSE(fua_payload->start_); + EXPECT_FALSE(fua_payload->end_); + } + + // Verify payload has NALU samples + EXPECT_GT((int)fua_payload->nalus_.size(), 0); + } + + // Cleanup RTP packets + for (size_t i = 0; i < pkts.size(); i++) { + srs_freep(pkts[i]); + } + + // Cleanup samples + for (size_t i = 0; i < samples.size(); i++) { + SrsNaluSample *sample = samples[i]; + if (sample->bytes_) { + free(sample->bytes_); + } + srs_freep(sample); + } + + // Cleanup format + srs_freep(format.vcodec_); + } +} + +VOID TEST(RTPVideoBuilderTest, PackageNalusMultipleSamplesLargePayloadHevc) +{ + srs_error_t err; + + // Test package_nalus method with multiple HEVC IDR NALU samples in one frame that exceed kRtpMaxPayloadSize + // This simulates a real HEVC IDR frame with multiple IDR NALU samples + if (true) { + // Create mock SrsRtpVideoBuilder + SrsRtpVideoBuilder builder; + builder.video_payload_type_ = 96; + builder.video_ssrc_ = 0x12345678; + builder.video_sequence_ = 700; + + // Create mock SrsFormat with HEVC codec + SrsFormat format; + format.vcodec_ = new SrsVideoCodecConfig(); + format.vcodec_->id_ = SrsVideoCodecIdHEVC; + + // Set format in builder + builder.format_ = &format; + + // Create mock media packet + SrsMediaPacket msg; + msg.timestamp_ = 7000; + + // Create vector with multiple HEVC IDR NALU samples that together exceed kRtpMaxPayloadSize + vector samples; + + // Create first HEVC IDR NALU sample (2800 bytes) + SrsNaluSample *idr_sample1 = new SrsNaluSample(); + idr_sample1->size_ = 2800; + idr_sample1->bytes_ = (char *)malloc(idr_sample1->size_); + // HEVC IDR NALU type is 19, with layer_id=0, temporal_id=1 + idr_sample1->bytes_[0] = (19 << 1) | 0; // NALU type 19, layer_id bit 0 + idr_sample1->bytes_[1] = 1; // layer_id bits + temporal_id + for (int i = 2; i < idr_sample1->size_; i++) { + idr_sample1->bytes_[i] = (char)(0x10 + (i % 128)); + } + samples.push_back(idr_sample1); + + // Create second HEVC IDR NALU sample (3200 bytes) + SrsNaluSample *idr_sample2 = new SrsNaluSample(); + idr_sample2->size_ = 3200; + idr_sample2->bytes_ = (char *)malloc(idr_sample2->size_); + // HEVC IDR NALU type is 19, with layer_id=0, temporal_id=1 + idr_sample2->bytes_[0] = (19 << 1) | 0; // NALU type 19, layer_id bit 0 + idr_sample2->bytes_[1] = 1; // layer_id bits + temporal_id + for (int i = 2; i < idr_sample2->size_; i++) { + idr_sample2->bytes_[i] = (char)(0x20 + (i % 128)); + } + samples.push_back(idr_sample2); + + // Create third HEVC IDR NALU sample (2600 bytes) + SrsNaluSample *idr_sample3 = new SrsNaluSample(); + idr_sample3->size_ = 2600; + idr_sample3->bytes_ = (char *)malloc(idr_sample3->size_); + // HEVC IDR NALU type is 19, with layer_id=0, temporal_id=1 + idr_sample3->bytes_[0] = (19 << 1) | 0; // NALU type 19, layer_id bit 0 + idr_sample3->bytes_[1] = 1; // layer_id bits + temporal_id + for (int i = 2; i < idr_sample3->size_; i++) { + idr_sample3->bytes_[i] = (char)(0x30 + (i % 128)); + } + samples.push_back(idr_sample3); + + // Total size: 2800 + 3200 + 2600 + 6 separators = 8606 bytes >> kRtpMaxPayloadSize (1200) + + // Create RTP packets vector + vector pkts; + + // Test package_nalus method - should create multiple HEVC FU-A packets due to large total size + HELPER_ASSERT_SUCCESS(builder.package_nalus(&msg, samples, pkts)); + + // Verify that multiple RTP packets were created (HEVC FU-A fragmentation) + EXPECT_GT((int)pkts.size(), 1); + + // Verify all packets have correct RTP header settings + for (size_t i = 0; i < pkts.size(); i++) { + SrsRtpPacket *pkt = pkts[i]; + + // Verify RTP header settings + EXPECT_EQ(96, pkt->header.get_payload_type()); + EXPECT_EQ(0x12345678, pkt->header.get_ssrc()); + EXPECT_EQ(SrsFrameTypeVideo, pkt->frame_type_); + EXPECT_EQ(700 + i, pkt->header.get_sequence()); // Sequence increments + EXPECT_EQ(630000, pkt->header.get_timestamp()); // 7000 * 90 + + // Verify NALU type is FU-A for fragmented packets + EXPECT_EQ(kFuA, pkt->nalu_type_); + + // Verify payload type is HEVC FUA + EXPECT_EQ(SrsRtpPacketPayloadTypeFUAHevc, pkt->payload_type_); + + // Verify payload exists + ASSERT_TRUE(pkt->payload_ != NULL); + SrsRtpFUAPayloadHevc *fua_payload = static_cast(pkt->payload_); + + // Verify HEVC FU-A header settings - NALU type should be from first NALU (IDR) + EXPECT_EQ(SrsHevcNaluType_CODED_SLICE_IDR, fua_payload->nalu_type_); // First NALU type (19) + + // First packet should have start=true, end=false + // Last packet should have start=false, end=true + // Middle packets should have start=false, end=false + if (i == 0) { + EXPECT_TRUE(fua_payload->start_); + EXPECT_FALSE(fua_payload->end_); + } else if (i == pkts.size() - 1) { + EXPECT_FALSE(fua_payload->start_); + EXPECT_TRUE(fua_payload->end_); + } else { + EXPECT_FALSE(fua_payload->start_); + EXPECT_FALSE(fua_payload->end_); + } + + // Verify payload has NALU samples + EXPECT_GT((int)fua_payload->nalus_.size(), 0); + } + + // Cleanup RTP packets + for (size_t i = 0; i < pkts.size(); i++) { + srs_freep(pkts[i]); + } + + // Cleanup samples + for (size_t i = 0; i < samples.size(); i++) { + SrsNaluSample *sample = samples[i]; + if (sample->bytes_) { + free(sample->bytes_); + } + srs_freep(sample); + } + + // Cleanup format + srs_freep(format.vcodec_); + } +} + +VOID TEST(RTPVideoBuilderTest, PackageFuAHevc) +{ + srs_error_t err; + + // Test package_fu_a method with HEVC codec - covers SrsRtpFUAPayloadHevc2 code path + if (true) { + // Create mock SrsRtpVideoBuilder + SrsRtpVideoBuilder builder; + builder.video_payload_type_ = 96; + builder.video_ssrc_ = 0x11223344; + builder.video_sequence_ = 800; + + // Create mock SrsFormat with HEVC codec + SrsFormat format; + format.vcodec_ = new SrsVideoCodecConfig(); + format.vcodec_->id_ = SrsVideoCodecIdHEVC; + + // Set format in builder + builder.format_ = &format; + + // Create mock media packet + SrsMediaPacket msg; + msg.timestamp_ = 8000; + + // Create large HEVC IDR NALU sample that will be fragmented (3000 bytes) + SrsNaluSample *sample = new SrsNaluSample(); + sample->size_ = 3000; + sample->bytes_ = (char *)malloc(sample->size_); + // HEVC IDR NALU type is 19, with layer_id=0, temporal_id=1 + sample->bytes_[0] = (19 << 1) | 0; // NALU type 19, layer_id bit 0 + sample->bytes_[1] = 1; // layer_id bits + temporal_id + for (int i = 2; i < sample->size_; i++) { + sample->bytes_[i] = (char)(0x50 + (i % 128)); + } + + // Create RTP packets vector + vector pkts; + + // Test package_fu_a method with payload size that will create multiple fragments + int fu_payload_size = 1000; // Smaller than sample size to force fragmentation + HELPER_ASSERT_SUCCESS(builder.package_fu_a(&msg, sample, fu_payload_size, pkts)); + + // Verify that multiple RTP packets were created (FU-A fragmentation) + EXPECT_GT((int)pkts.size(), 1); + int expected_packets = 1 + (sample->size_ - SrsHevcNaluHeaderSize - 1) / fu_payload_size; + EXPECT_EQ(expected_packets, (int)pkts.size()); + + // Verify all packets have correct RTP header settings and HEVC FU-A payload + for (size_t i = 0; i < pkts.size(); i++) { + SrsRtpPacket *pkt = pkts[i]; + + // Verify RTP header settings + EXPECT_EQ(96, pkt->header.get_payload_type()); + EXPECT_EQ(0x11223344, pkt->header.get_ssrc()); + EXPECT_EQ(SrsFrameTypeVideo, pkt->frame_type_); + EXPECT_EQ(800 + i, pkt->header.get_sequence()); // Sequence increments + EXPECT_EQ(720000, pkt->header.get_timestamp()); // 8000 * 90 + + // Verify NALU type is HEVC FU-A for fragmented packets + EXPECT_EQ(kFuHevc, pkt->nalu_type_); + + // Verify payload type is HEVC FUA2 + EXPECT_EQ(SrsRtpPacketPayloadTypeFUAHevc2, pkt->payload_type_); + + // Verify payload exists and is SrsRtpFUAPayloadHevc2 + ASSERT_TRUE(pkt->payload_ != NULL); + SrsRtpFUAPayloadHevc2 *fua_payload = static_cast(pkt->payload_); + + // Verify HEVC FU-A header settings - NALU type should be IDR (19) + EXPECT_EQ(SrsHevcNaluType_CODED_SLICE_IDR, fua_payload->nalu_type_); + + // First packet should have start=true, end=false + // Last packet should have start=false, end=true + // Middle packets should have start=false, end=false + if (i == 0) { + EXPECT_TRUE(fua_payload->start_); + EXPECT_FALSE(fua_payload->end_); + } else if (i == pkts.size() - 1) { + EXPECT_FALSE(fua_payload->start_); + EXPECT_TRUE(fua_payload->end_); + } else { + EXPECT_FALSE(fua_payload->start_); + EXPECT_FALSE(fua_payload->end_); + } + + // Verify payload points to correct data and has correct size + EXPECT_TRUE(fua_payload->payload_ != NULL); + EXPECT_GT(fua_payload->size_, 0); + EXPECT_LE(fua_payload->size_, fu_payload_size); + + // For last packet, verify it contains remaining data + if (i == pkts.size() - 1) { + int remaining_size = sample->size_ - SrsHevcNaluHeaderSize - (int)i * fu_payload_size; + EXPECT_EQ(remaining_size, fua_payload->size_); + } else { + EXPECT_EQ(fu_payload_size, fua_payload->size_); + } + } + + // Cleanup RTP packets + for (size_t i = 0; i < pkts.size(); i++) { + srs_freep(pkts[i]); + } + + // Cleanup sample + if (sample->bytes_) { + free(sample->bytes_); + } + srs_freep(sample); + + // Cleanup format + srs_freep(format.vcodec_); + } +} + +VOID TEST(RTPVideoBuilderTest, PackageFuAH264) +{ + srs_error_t err; + + // Test package_fu_a method with H.264 codec - covers SrsRtpFUAPayload2 code path + if (true) { + // Create mock SrsRtpVideoBuilder + SrsRtpVideoBuilder builder; + builder.video_payload_type_ = 96; + builder.video_ssrc_ = 0x55667788; + builder.video_sequence_ = 900; + + // Create mock SrsFormat with H.264 codec + SrsFormat format; + format.vcodec_ = new SrsVideoCodecConfig(); + format.vcodec_->id_ = SrsVideoCodecIdAVC; + + // Set format in builder + builder.format_ = &format; + + // Create mock media packet + SrsMediaPacket msg; + msg.timestamp_ = 9000; + + // Create large H.264 IDR NALU sample that will be fragmented (2500 bytes) + SrsNaluSample *sample = new SrsNaluSample(); + sample->size_ = 2500; + sample->bytes_ = (char *)malloc(sample->size_); + sample->bytes_[0] = 0x65; // H.264 IDR NALU type + for (int i = 1; i < sample->size_; i++) { + sample->bytes_[i] = (char)(0x60 + (i % 128)); + } + + // Create RTP packets vector + vector pkts; + + // Test package_fu_a method with payload size that will create multiple fragments + int fu_payload_size = 800; // Smaller than sample size to force fragmentation + HELPER_ASSERT_SUCCESS(builder.package_fu_a(&msg, sample, fu_payload_size, pkts)); + + // Verify that multiple RTP packets were created (FU-A fragmentation) + EXPECT_GT((int)pkts.size(), 1); + int expected_packets = 1 + (sample->size_ - SrsAvcNaluHeaderSize - 1) / fu_payload_size; + EXPECT_EQ(expected_packets, (int)pkts.size()); + + // Verify all packets have correct RTP header settings and H.264 FU-A payload + for (size_t i = 0; i < pkts.size(); i++) { + SrsRtpPacket *pkt = pkts[i]; + + // Verify RTP header settings + EXPECT_EQ(96, pkt->header.get_payload_type()); + EXPECT_EQ(0x55667788, pkt->header.get_ssrc()); + EXPECT_EQ(SrsFrameTypeVideo, pkt->frame_type_); + EXPECT_EQ(900 + i, pkt->header.get_sequence()); // Sequence increments + EXPECT_EQ(810000, pkt->header.get_timestamp()); // 9000 * 90 + + // Verify NALU type is H.264 FU-A for fragmented packets + EXPECT_EQ(kFuA, pkt->nalu_type_); + + // Verify payload type is H.264 FUA2 + EXPECT_EQ(SrsRtpPacketPayloadTypeFUA2, pkt->payload_type_); + + // Verify payload exists and is SrsRtpFUAPayload2 + ASSERT_TRUE(pkt->payload_ != NULL); + SrsRtpFUAPayload2 *fua_payload = static_cast(pkt->payload_); + + // Verify H.264 FU-A header settings - NALU type should be IDR (5) + EXPECT_EQ(SrsAvcNaluTypeIDR, fua_payload->nalu_type_); + EXPECT_EQ((SrsAvcNaluType)0x65, fua_payload->nri_); // NRI from original header + + // First packet should have start=true, end=false + // Last packet should have start=false, end=true + // Middle packets should have start=false, end=false + if (i == 0) { + EXPECT_TRUE(fua_payload->start_); + EXPECT_FALSE(fua_payload->end_); + } else if (i == pkts.size() - 1) { + EXPECT_FALSE(fua_payload->start_); + EXPECT_TRUE(fua_payload->end_); + } else { + EXPECT_FALSE(fua_payload->start_); + EXPECT_FALSE(fua_payload->end_); + } + + // Verify payload points to correct data and has correct size + EXPECT_TRUE(fua_payload->payload_ != NULL); + EXPECT_GT(fua_payload->size_, 0); + EXPECT_LE(fua_payload->size_, fu_payload_size); + + // For last packet, verify it contains remaining data + if (i == pkts.size() - 1) { + int remaining_size = sample->size_ - SrsAvcNaluHeaderSize - (int)i * fu_payload_size; + EXPECT_EQ(remaining_size, fua_payload->size_); + } else { + EXPECT_EQ(fu_payload_size, fua_payload->size_); + } + } + + // Cleanup RTP packets + for (size_t i = 0; i < pkts.size(); i++) { + srs_freep(pkts[i]); + } + + // Cleanup sample + if (sample->bytes_) { + free(sample->bytes_); + } + srs_freep(sample); + + // Cleanup format + srs_freep(format.vcodec_); + } +} diff --git a/trunk/src/utest/srs_utest_protocol4.hpp b/trunk/src/utest/srs_utest_protocol4.hpp new file mode 100644 index 000000000..569cf897e --- /dev/null +++ b/trunk/src/utest/srs_utest_protocol4.hpp @@ -0,0 +1,15 @@ +// +// Copyright (c) 2013-2025 The SRS Authors +// +// SPDX-License-Identifier: MIT +// + +#ifndef SRS_UTEST_PROTOCOL4_HPP +#define SRS_UTEST_PROTOCOL4_HPP + +/* +#include +*/ +#include + +#endif diff --git a/trunk/src/utest/srs_utest_service.cpp b/trunk/src/utest/srs_utest_service.cpp index a2bea3393..d0d4e5909 100644 --- a/trunk/src/utest/srs_utest_service.cpp +++ b/trunk/src/utest/srs_utest_service.cpp @@ -7,9 +7,11 @@ using namespace std; +#include #include #include #include +#include #include #include @@ -1332,150 +1334,6 @@ VOID TEST(TCPServerTest, CoverUtility) } } -class MockOnCycleThread4 : public ISrsCoroutineHandler -{ -public: - SrsSTCoroutine trd; - srs_netfd_t fd; - MockOnCycleThread4() : trd("mock", this) - { - fd = NULL; - }; - virtual ~MockOnCycleThread4() - { - trd.stop(); - srs_close_stfd(fd); - } - virtual srs_error_t start(string ip, int port) - { - srs_error_t err = srs_success; - if ((err = srs_tcp_listen(ip, port, &fd)) != srs_success) { - return err; - } - - return trd.start(); - } - virtual srs_error_t do_cycle(srs_netfd_t cfd) - { - srs_error_t err = srs_success; - - SrsStSocket skt(cfd); - skt.set_recv_timeout(1 * SRS_UTIME_SECONDS); - skt.set_send_timeout(1 * SRS_UTIME_SECONDS); - - while (true) { - if ((err = trd.pull()) != srs_success) { - return err; - } - - char buf[1024]; - if ((err = skt.read(buf, 1024, NULL)) != srs_success) { - return err; - } - - string res = mock_http_response(200, "OK"); - if ((err = skt.write((char *)res.data(), (int)res.length(), NULL)) != srs_success) { - return err; - } - } - - return err; - } - virtual srs_error_t cycle() - { - srs_error_t err = srs_success; - - srs_netfd_t cfd = srs_accept(fd, NULL, NULL, SRS_UTIME_NO_TIMEOUT); - if (cfd == NULL) { - return err; - } - - err = do_cycle(cfd); - srs_close_stfd(cfd); - srs_freep(err); - - return err; - } -}; - -VOID TEST(HTTPClientTest, HTTPClientUtility) -{ - srs_error_t err; - - // Typical HTTP POST. - if (true) { - MockOnCycleThread4 trd; - HELPER_ASSERT_SUCCESS(trd.start("127.0.0.1", 8080)); - - SrsHttpClient client; - HELPER_ASSERT_SUCCESS(client.initialize("http", "127.0.0.1", 8080, 1 * SRS_UTIME_SECONDS)); - - ISrsHttpMessage *res = NULL; - HELPER_ASSERT_SUCCESS(client.post("/api/v1", "", &res)); - SrsUniquePtr res_uptr(res); - - ISrsHttpResponseReader *br = res->body_reader(); - ASSERT_FALSE(br->eof()); - - ssize_t nn = 0; - char buf[1024]; - HELPER_ARRAY_INIT(buf, sizeof(buf), 0); - HELPER_ASSERT_SUCCESS(br->read(buf, sizeof(buf), &nn)); - ASSERT_EQ(2, nn); - EXPECT_STREQ("OK", buf); - } - - // Typical HTTP GET. - if (true) { - MockOnCycleThread4 trd; - HELPER_ASSERT_SUCCESS(trd.start("127.0.0.1", 8080)); - - SrsHttpClient client; - HELPER_ASSERT_SUCCESS(client.initialize("http", "127.0.0.1", 8080, 1 * SRS_UTIME_SECONDS)); - - ISrsHttpMessage *res = NULL; - HELPER_ASSERT_SUCCESS(client.get("/api/v1", "", &res)); - SrsUniquePtr res_uptr(res); - - ISrsHttpResponseReader *br = res->body_reader(); - ASSERT_FALSE(br->eof()); - - ssize_t nn = 0; - char buf[1024]; - HELPER_ARRAY_INIT(buf, sizeof(buf), 0); - HELPER_ASSERT_SUCCESS(br->read(buf, sizeof(buf), &nn)); - ASSERT_EQ(2, nn); - EXPECT_STREQ("OK", buf); - } - - // Set receive timeout and Kbps ample. - if (true) { - MockOnCycleThread4 trd; - HELPER_ASSERT_SUCCESS(trd.start("127.0.0.1", 8080)); - - SrsHttpClient client; - HELPER_ASSERT_SUCCESS(client.initialize("http", "127.0.0.1", 8080, 1 * SRS_UTIME_SECONDS)); - client.set_recv_timeout(1 * SRS_UTIME_SECONDS); - client.set_header("agent", "srs"); - - ISrsHttpMessage *res = NULL; - HELPER_ASSERT_SUCCESS(client.get("/api/v1", "", &res)); - SrsUniquePtr res_uptr(res); - - ISrsHttpResponseReader *br = res->body_reader(); - ASSERT_FALSE(br->eof()); - - ssize_t nn = 0; - char buf[1024]; - HELPER_ARRAY_INIT(buf, sizeof(buf), 0); - HELPER_ASSERT_SUCCESS(br->read(buf, sizeof(buf), &nn)); - ASSERT_EQ(2, nn); - EXPECT_STREQ("OK", buf); - - client.kbps_sample("SRS", 0); - } -} - class MockConnectionManager : public ISrsResourceManager { public: diff --git a/trunk/src/utest/srs_utest_st2.cpp b/trunk/src/utest/srs_utest_st2.cpp index bc25e89e7..7e98002c2 100644 --- a/trunk/src/utest/srs_utest_st2.cpp +++ b/trunk/src/utest/srs_utest_st2.cpp @@ -45,6 +45,34 @@ VOID TEST(StTest, AnonymouseMultipleCoroutines) srs_usleep(50 * SRS_UTIME_MILLISECONDS); } +VOID TEST(StTest, AnonymouseCoroutinePull) +{ + int counter = 0; + + if (true) { + SrsCoroutineChan ctx; + ctx.push(&counter); + + SRS_COROUTINE_GO_CTX(&ctx, { + int *counter = (int *)ctx.pop(); + srs_assert(ctx.trd_); + while (ctx.trd_->pull() == srs_success) { + srs_usleep(1 * SRS_UTIME_MILLISECONDS); + } + (*counter)++; + }); + + // Coroutine not terminated, so the counter is not increased. + EXPECT_TRUE(counter == 0); + + // Wait for coroutine to run and terminated, or it will crash + // because the ctx.pop is called after coroutine terminated. + srs_usleep(50 * SRS_UTIME_MILLISECONDS); + } + + EXPECT_TRUE(counter == 1); +} + VOID TEST(StTest, AnonymouseCoroutineWithContext) { int counter = 0;