AI: Cover protocol HTTP/HTTPS/RTMP/RTC by utests. (#4493)

Co-authored-by: OSSRS-AI <winlinam@gmail.com>
This commit is contained in:
Winlin 2025-09-16 16:44:23 -04:00 committed by winlin
parent 49594b1846
commit b39aae1447
15 changed files with 4244 additions and 380 deletions

2
trunk/configure vendored
View File

@ -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

View File

@ -0,0 +1,3 @@
module httpmock
go 1.23.0

View File

@ -0,0 +1,7 @@
package main
import "fmt"
func main() {
fmt.Println("Hello, mock HTTP")
}

View File

@ -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)
}
}

View File

@ -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()) {

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -25,6 +25,15 @@ using namespace std;
#include <srs_app_srt_server.hpp>
#include <srt/srt.h>
// For RTMP test server
#include <srs_protocol_conn.hpp>
#include <srs_protocol_rtmp_conn.hpp>
#include <srs_protocol_rtmp_stack.hpp>
// For TCP test server and client
#include <srs_app_listener.hpp>
#include <srs_app_st.hpp>
// 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<SrsStSocket> skt_uptr(skt);
// Create SSL connection
SrsSslConnection *ssl = new SrsSslConnection(skt);
SrsUniquePtr<SrsSslConnection> 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;
}

View File

@ -28,6 +28,10 @@ using namespace std;
#include <srs_app_log.hpp>
#include <srs_kernel_stream.hpp>
// Include headers for TCP test classes
#include <srs_app_listener.hpp>
#include <srs_protocol_conn.hpp>
// we add an empty macro for upp to show the smart tips.
#define VOID
@ -139,6 +143,10 @@ private:
std::vector<void *> 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

View File

@ -11,6 +11,7 @@ using namespace std;
#include <srs_app_st.hpp>
#include <srs_core_autofree.hpp>
#include <srs_kernel_buffer.hpp>
#include <srs_kernel_codec.hpp>
#include <srs_kernel_error.hpp>
#include <srs_kernel_utility.hpp>
#include <srs_protocol_amf0.hpp>
@ -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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<std::string> 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));
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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 <srs_utest_protocol4.hpp>
*/
#include <srs_utest.hpp>
#endif

View File

@ -7,9 +7,11 @@
using namespace std;
#include <srs_app_http_conn.hpp>
#include <srs_app_listener.hpp>
#include <srs_core_deprecated.hpp>
#include <srs_kernel_error.hpp>
#include <srs_protocol_http_stack.hpp>
#include <srs_protocol_st.hpp>
#include <srs_protocol_utility.hpp>
@ -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<ISrsHttpMessage> 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<ISrsHttpMessage> 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<ISrsHttpMessage> 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:

View File

@ -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;