// // Copyright (c) 2013-2025 The SRS Authors // // SPDX-License-Identifier: MIT // #include #include #include #include #include #include #include #include #include #include using namespace std; #include #include #include #include #include // For RTMP test server #include #include #include // For TCP test server and client #include #include #include // Temporary disk config. std::string _srs_tmp_file_prefix = "/tmp/srs-utest-"; // Temporary network config. std::string _srs_tmp_host = "127.0.0.1"; int _srs_tmp_port = 11935; srs_utime_t _srs_tmp_timeout = (100 * SRS_UTIME_MILLISECONDS); // kernel module. ISrsLog *_srs_log = NULL; ISrsContext *_srs_context = NULL; // app module. SrsConfig *_srs_config = NULL; bool _srs_in_docker = false; bool _srs_config_by_env = false; // @global kernel factory. ISrsAppFactory *_srs_app_factory = new SrsAppFactory(); ISrsKernelFactory *_srs_kernel_factory = _srs_app_factory; // Register FFmpeg log callback funciton. SrsFFmpegLogHelper _srs_ffmpeg_log_helper; // The binary name of SRS. const char *_srs_binary = NULL; #include static void srs_srt_utest_null_log_handler(void *opaque, int level, const char *file, int line, const char *area, const char *message) { // srt null log handler, do no print any log. } // Initialize global settings. srs_error_t prepare_main() { srs_error_t err = srs_success; // Root global objects, should be created before any other global objects. _srs_log = new SrsFileLog(); _srs_context = new SrsThreadContext(); _srs_config = new SrsConfig(); // For background context id. _srs_context->set_id(_srs_context->generate_id()); if ((err = srs_global_initialize()) != srs_success) { return srs_error_wrap(err, "init global"); } _srs_server = new SrsServer(); srs_freep(_srs_log); _srs_log = new MockEmptyLog(SrsLogLevelError); if ((err = _srs_rtc_dtls_certificate->initialize()) != srs_success) { return srs_error_wrap(err, "rtc dtls certificate initialize"); } srs_freep(_srs_context); _srs_context = new SrsThreadContext(); if ((err = srs_srt_log_initialize()) != srs_success) { return srs_error_wrap(err, "srt log initialize"); } // Disable FFmpeg detail log in utest. _srs_ffmpeg_log_helper.disable_ffmpeg_log(); // Prevent the output of srt logs in utest. srt_setloghandler(NULL, srs_srt_utest_null_log_handler); // Set SRT log level to FATAL to suppress ERROR and WARNING logs in unit tests. // LOG_CRIT (2) is the highest level that suppresses most logs. srt_setloglevel(LOG_CRIT); _srt_eventloop = new SrsSrtEventLoop(); if ((err = _srt_eventloop->initialize()) != srs_success) { return srs_error_wrap(err, "srt poller initialize"); } if ((err = _srt_eventloop->start()) != srs_success) { return srs_error_wrap(err, "srt poller start"); } return err; } // We could do something in the main of utest. // Copy from gtest-1.6.0/src/gtest_main.cc GTEST_API_ int main(int argc, char **argv) { srs_error_t err = srs_success; _srs_binary = argv[0]; if ((err = prepare_main()) != srs_success) { fprintf(stderr, "Failed, %s\n", srs_error_desc(err).c_str()); int ret = srs_error_code(err); srs_freep(err); return ret; } testing::InitGoogleTest(&argc, argv); int r0 = RUN_ALL_TESTS(); return r0; } MockEmptyLog::MockEmptyLog(SrsLogLevel l) { level_ = l; } MockEmptyLog::~MockEmptyLog() { } void srs_bytes_print(char *pa, int size) { for (int i = 0; i < size; i++) { char v = pa[i]; printf("%#x ", v); } printf("\n"); } // basic test and samples. VOID TEST(SampleTest, FastSampleInt64Test) { EXPECT_EQ(1, (int)sizeof(int8_t)); EXPECT_EQ(2, (int)sizeof(int16_t)); EXPECT_EQ(4, (int)sizeof(int32_t)); EXPECT_EQ(8, (int)sizeof(int64_t)); } VOID TEST(SampleTest, FastSampleMacrosTest) { EXPECT_TRUE(1); EXPECT_FALSE(0); EXPECT_EQ(1, 1); // == EXPECT_NE(1, 2); // != EXPECT_LE(1, 2); // <= EXPECT_LT(1, 2); // < EXPECT_GE(2, 1); // >= EXPECT_GT(2, 1); // > EXPECT_STREQ("winlin", "winlin"); EXPECT_STRNE("winlin", "srs"); EXPECT_STRCASEEQ("winlin", "Winlin"); EXPECT_STRCASENE("winlin", "srs"); EXPECT_FLOAT_EQ(1.0, 1.000000000000001); EXPECT_DOUBLE_EQ(1.0, 1.0000000000000001); EXPECT_NEAR(10, 15, 5); } VOID TEST(SampleTest, StringEQTest) { string str = "100"; EXPECT_TRUE("100" == str); EXPECT_EQ("100", str); EXPECT_STREQ("100", str.c_str()); } class MockSrsContextId { public: MockSrsContextId() { bind_ = NULL; } MockSrsContextId(const MockSrsContextId &cp) { bind_ = NULL; if (cp.bind_) { bind_ = cp.bind_->copy(); } } MockSrsContextId &operator=(const MockSrsContextId &cp) { srs_freep(bind_); if (cp.bind_) { bind_ = cp.bind_->copy(); } return *this; } virtual ~MockSrsContextId() { srs_freep(bind_); } public: MockSrsContextId *copy() const { MockSrsContextId *cp = new MockSrsContextId(); if (bind_) { cp->bind_ = bind_->copy(); } return cp; } // clang-format off SRS_DECLARE_PRIVATE: // clang-format on MockSrsContextId *bind_; }; VOID TEST(SampleTest, ContextTest) { MockSrsContextId cid; cid.bind_ = new MockSrsContextId(); static std::map cache; cache[0] = cid; cache[0] = cid; } MockProtectedBuffer::MockProtectedBuffer() : raw_memory_(NULL), size_(0), data_(NULL) { } MockProtectedBuffer::~MockProtectedBuffer() { if (size_ && raw_memory_) { long page_size = sysconf(_SC_PAGESIZE); munmap(raw_memory_, page_size * 2); } } int MockProtectedBuffer::alloc(int size) { srs_assert(!raw_memory_); long page_size = sysconf(_SC_PAGESIZE); if (size >= page_size) return -1; char *data = (char *)mmap(NULL, page_size * 2, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); if (data == MAP_FAILED) { return -1; } size_ = size; raw_memory_ = data; data_ = data + page_size - size; int r0 = mprotect(data + page_size, page_size, PROT_NONE); if (r0 < 0) { return r0; } return 0; } SrsCoroutineChan::SrsCoroutineChan() { trd_ = NULL; lock_ = srs_mutex_new(); } SrsCoroutineChan::~SrsCoroutineChan() { srs_mutex_destroy(lock_); } SrsCoroutineChan &SrsCoroutineChan::push(void *value) { SrsLocker(&lock_); args_.push_back(value); return *this; } void *SrsCoroutineChan::pop() { SrsLocker(&lock_); void *arg = *args_.begin(); args_.erase(args_.begin()); return arg; } SrsCoroutineChan *SrsCoroutineChan::copy() { SrsLocker(&lock_); 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] SrsRand rand; port_ = 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; // Retry up to 3 times with different random ports if listen fails for (int retry = 0; retry < 3; retry++) { if ((err = srs_tcp_listen(ip_, port_, &fd_)) == srs_success) { break; } // If this is not the last retry, generate a new random port and try again if (retry < 2) { srs_freep(err); SrsRand rand; port_ = rand.integer(30000, 60000); srs_trace("HTTP test server listen failed on %s:%d, retry %d with new port %d", ip_.c_str(), port_, retry + 1, port_); } } if (err != srs_success) { return srs_error_wrap(err, "listen %s:%d after 3 retries", 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] SrsRand rand; port_ = rand.integer(30000, 60000); } SrsHttpsTestServer::~SrsHttpsTestServer() { close(); srs_freep(trd_); } srs_error_t SrsHttpsTestServer::start() { srs_error_t err = srs_success; // Retry up to 3 times with different random ports if listen fails for (int retry = 0; retry < 3; retry++) { if ((err = srs_tcp_listen(ip_, port_, &fd_)) == srs_success) { break; } // If this is not the last retry, generate a new random port and try again if (retry < 2) { srs_freep(err); SrsRand rand; port_ = rand.integer(30000, 60000); srs_trace("HTTPS test server listen failed on %s:%d, retry %d with new port %d", ip_.c_str(), port_, retry + 1, port_); } } if (err != srs_success) { return srs_error_wrap(err, "listen %s:%d after 3 retries", 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] SrsRand rand; port_ = 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; // Retry up to 3 times with different random ports if listen fails for (int retry = 0; retry < 3; retry++) { if ((err = srs_tcp_listen(ip_, port_, &fd_)) == srs_success) { break; } // If this is not the last retry, generate a new random port and try again if (retry < 2) { srs_freep(err); SrsRand rand; port_ = rand.integer(30000, 60000); srs_trace("RTMP test server listen failed on %s:%d, retry %d with new port %d", ip_.c_str(), port_, retry + 1, port_); } } if (err != srs_success) { return srs_error_wrap(err, "listen %s:%d after 3 retries", 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] SrsRand rand; port_ = 30000 + (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] SrsRand rand; port_ = 30000 + (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 - retry up to 3 times with different random ports if listen fails for (int retry = 0; retry < 3; retry++) { if ((err = srs_udp_listen(host_, port_, &lfd_)) == srs_success) { break; } // If this is not the last retry, generate a new random port and try again if (retry < 2) { srs_freep(err); SrsRand rand; port_ = 30000 + (rand.integer() % (60000 - 30000 + 1)); srs_trace("UDP test server listen failed on %s:%d, retry %d with new port %d", host_.c_str(), port_, retry + 1, port_); } } if (err != srs_success) { return srs_error_wrap(err, "udp listen %s:%d after 3 retries", 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; }