diff --git a/trunk/doc/CHANGELOG.md b/trunk/doc/CHANGELOG.md
index afd74ff51..748bdc545 100644
--- a/trunk/doc/CHANGELOG.md
+++ b/trunk/doc/CHANGELOG.md
@@ -7,6 +7,7 @@ The changelog for SRS.
## SRS 7.0 Changelog
+* v7.0, 2025-11-07, AI: Kernel: Add srs_error_transform to wrap errors with new error codes. v7.0.121 (#4502)
* v7.0, 2025-11-07, AI: HLS: Support query string in hls_key_url for JWT tokens. v7.0.120 (#4426)
* v7.0, 2025-11-07, AI: RTC: Support keep_original_ssrc to preserve SSRC and timestamps. v7.0.119 (#3850)
* v7.0, 2025-11-05, AI: WebRTC: Report video/audio codec info and frame stats in HTTP API. v7.0.118 (#4554)
diff --git a/trunk/src/app/srs_app_rtc_api.cpp b/trunk/src/app/srs_app_rtc_api.cpp
index 4b5f3d2e5..23aa75c69 100644
--- a/trunk/src/app/srs_app_rtc_api.cpp
+++ b/trunk/src/app/srs_app_rtc_api.cpp
@@ -554,12 +554,12 @@ srs_error_t SrsGoApiRtcPublish::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMe
}
if ((err = security_->check(SrsRtcConnPublish, ruc->req_->ip_, ruc->req_)) != srs_success) {
- return srs_error_wrap(err, "RTC: security check");
+ return srs_error_transform(ERROR_SYSTEM_AUTH, err, "RTC: security check");
}
// We must do hook after stat, because depends on it.
if ((err = http_hooks_on_publish(ruc->req_)) != srs_success) {
- return srs_error_wrap(err, "RTC: http_hooks_on_publish");
+ return srs_error_transform(ERROR_SYSTEM_AUTH, err, "RTC: http_hooks_on_publish");
}
ostringstream os;
@@ -661,6 +661,44 @@ SrsGoApiRtcWhip::~SrsGoApiRtcWhip()
}
srs_error_t SrsGoApiRtcWhip::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r)
+{
+ int code = 0;
+ string code_str;
+ if (true) {
+ srs_error_t err = srs_success;
+
+ err = serve_http_with(w, r);
+ if (err == srs_success) {
+ return err;
+ }
+
+ code = srs_error_code(err);
+ code_str = srs_error_code_str(err);
+ srs_warn("WHIP: serve http for %s with err %d:%s, %s",
+ r->url().c_str(), code, code_str.c_str(), srs_error_desc(err).c_str());
+ srs_freep(err);
+ }
+
+ if (code == ERROR_RTC_INVALID_SDP || code == ERROR_RTC_SDP_DECODE || code == ERROR_RTC_SDP_EXCHANGE) {
+ string msg = srs_fmt_sprintf("%d: %s", code, code_str.c_str());
+ return srs_go_http_error(w, SRS_CONSTS_HTTP_BadRequest, msg);
+ }
+
+ if (code == ERROR_SYSTEM_STREAM_BUSY) {
+ string msg = srs_fmt_sprintf("%d: %s", code, code_str.c_str());
+ return srs_go_http_error(w, SRS_CONSTS_HTTP_Conflict, msg);
+ }
+
+ if (code == ERROR_SYSTEM_AUTH) {
+ string msg = srs_fmt_sprintf("%d: %s", code, code_str.c_str());
+ return srs_go_http_error(w, SRS_CONSTS_HTTP_Unauthorized, msg);
+ }
+
+ string msg = srs_fmt_sprintf("%d: %s", code, code_str.c_str());
+ return srs_go_http_error(w, SRS_CONSTS_HTTP_InternalServerError, msg);
+}
+
+srs_error_t SrsGoApiRtcWhip::serve_http_with(ISrsHttpResponseWriter *w, ISrsHttpMessage *r)
{
srs_error_t err = srs_success;
@@ -691,14 +729,14 @@ srs_error_t SrsGoApiRtcWhip::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessa
}
SrsRtcUserConfig ruc;
- if ((err = do_serve_http(w, r, &ruc)) != srs_success) {
+ if ((err = do_serve_http_with(w, r, &ruc)) != srs_success) {
return srs_error_wrap(err, "serve");
}
- if (ruc.local_sdp_str_.empty()) {
- return srs_go_http_error(w, SRS_CONSTS_HTTP_InternalServerError);
- }
// The SDP to response.
+ if (ruc.local_sdp_str_.empty()) {
+ return srs_error_new(ERROR_RTC_INVALID_SDP, "empty local sdp");
+ }
string sdp = ruc.local_sdp_str_;
// Setup the content type to SDP.
@@ -714,7 +752,7 @@ srs_error_t SrsGoApiRtcWhip::serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessa
return w->write((char *)sdp.data(), (int)sdp.length());
}
-srs_error_t SrsGoApiRtcWhip::do_serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r, SrsRtcUserConfig *ruc)
+srs_error_t SrsGoApiRtcWhip::do_serve_http_with(ISrsHttpResponseWriter *w, ISrsHttpMessage *r, SrsRtcUserConfig *ruc)
{
srs_error_t err = srs_success;
@@ -796,6 +834,9 @@ srs_error_t SrsGoApiRtcWhip::do_serve_http(ISrsHttpResponseWriter *w, ISrsHttpMe
// TODO: FIXME: It seems remote_sdp doesn't represents the full SDP information.
ruc->remote_sdp_str_ = remote_sdp_str;
+ if (ruc->remote_sdp_str_.empty()) {
+ return srs_error_new(ERROR_RTC_INVALID_SDP, "empty remote sdp");
+ }
if ((err = ruc->remote_sdp_.parse(remote_sdp_str)) != srs_success) {
return srs_error_wrap(err, "parse sdp failed: %s", remote_sdp_str.c_str());
}
diff --git a/trunk/src/app/srs_app_rtc_api.hpp b/trunk/src/app/srs_app_rtc_api.hpp
index d402eb43e..18ac4390d 100644
--- a/trunk/src/app/srs_app_rtc_api.hpp
+++ b/trunk/src/app/srs_app_rtc_api.hpp
@@ -119,7 +119,8 @@ public:
// clang-format off
SRS_DECLARE_PRIVATE: // clang-format on
- virtual srs_error_t do_serve_http(ISrsHttpResponseWriter *w, ISrsHttpMessage *r, SrsRtcUserConfig *ruc);
+ virtual srs_error_t serve_http_with(ISrsHttpResponseWriter *w, ISrsHttpMessage *r);
+ virtual srs_error_t do_serve_http_with(ISrsHttpResponseWriter *w, ISrsHttpMessage *r, SrsRtcUserConfig *ruc);
};
class SrsGoApiRtcNACK : public ISrsHttpHandler
diff --git a/trunk/src/core/srs_core_version7.hpp b/trunk/src/core/srs_core_version7.hpp
index 9ccc2ecd5..c9a8684cc 100644
--- a/trunk/src/core/srs_core_version7.hpp
+++ b/trunk/src/core/srs_core_version7.hpp
@@ -9,6 +9,6 @@
#define VERSION_MAJOR 7
#define VERSION_MINOR 0
-#define VERSION_REVISION 120
+#define VERSION_REVISION 121
#endif
\ No newline at end of file
diff --git a/trunk/src/kernel/srs_kernel_error.cpp b/trunk/src/kernel/srs_kernel_error.cpp
index c6bfd620d..00c31c6fe 100644
--- a/trunk/src/kernel/srs_kernel_error.cpp
+++ b/trunk/src/kernel/srs_kernel_error.cpp
@@ -327,18 +327,21 @@ SrsCplxError *SrsCplxError::wrap(const char *func, const char *file, int line, S
int r0 = vsnprintf(buffer, maxLogBuf, fmt, ap);
va_end(ap);
+ // Ensure buffer is null-terminated even if vsnprintf failed or truncated.
+ if (r0 < 0) {
+ buffer[0] = '\0';
+ } else if (r0 >= maxLogBuf) {
+ buffer[maxLogBuf - 1] = '\0';
+ }
+
SrsCplxError *err = new SrsCplxError();
err->func_ = func;
err->file_ = file;
err->line_ = line;
- if (v) {
- err->code_ = v->code_;
- }
+ err->code_ = srs_error_code(v);
err->rerrno_ = rerrno;
- if (r0 > 0 && r0 < maxLogBuf) {
- err->msg_ = string(buffer, r0);
- }
+ err->msg_ = string(buffer);
err->wrapped_ = v;
if (_srs_context) {
err->cid_ = _srs_context->get_id();
@@ -347,6 +350,29 @@ SrsCplxError *SrsCplxError::wrap(const char *func, const char *file, int line, S
return err;
}
+SrsCplxError *SrsCplxError::transform(const char *func, const char *file, int line, int code, SrsCplxError *v, const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ static char *buffer = new char[maxLogBuf];
+ int r0 = vsnprintf(buffer, maxLogBuf, fmt, ap);
+ va_end(ap);
+
+ // Ensure buffer is null-terminated even if vsnprintf failed or truncated.
+ if (r0 < 0) {
+ buffer[0] = '\0';
+ } else if (r0 >= maxLogBuf) {
+ buffer[maxLogBuf - 1] = '\0';
+ }
+
+ // Wrap the error with additional context showing the code transformation.
+ SrsCplxError *err = NULL;
+ err = wrap(func, file, line, v, "code transformed (%d => %d): %s", srs_error_code(v), code, buffer);
+ err->code_ = code;
+
+ return err;
+}
+
SrsCplxError *SrsCplxError::success()
{
return NULL;
diff --git a/trunk/src/kernel/srs_kernel_error.hpp b/trunk/src/kernel/srs_kernel_error.hpp
index 67b79846c..0fe936f32 100644
--- a/trunk/src/kernel/srs_kernel_error.hpp
+++ b/trunk/src/kernel/srs_kernel_error.hpp
@@ -111,7 +111,9 @@
XX(ERROR_STREAM_DISPOSING, 1098, "StreamDisposing", "Stream is disposing") \
XX(ERROR_NOT_IMPLEMENTED, 1099, "NotImplemented", "Feature is not implemented") \
XX(ERROR_NOT_SUPPORTED, 1100, "NotSupported", "Feature is not supported") \
- XX(ERROR_SYSTEM_FILE_UNLINK, 1101, "FileUnlink", "Failed to unlink file")
+ XX(ERROR_SYSTEM_FILE_UNLINK, 1101, "FileUnlink", "Failed to unlink file") \
+ XX(ERROR_SYSTEM_AUTH, 1102, "SystemAuth", "Failed to authenticate stream")
+
/**************************************************/
/* RTMP protocol error. */
@@ -380,7 +382,8 @@
XX(ERROR_RTSP_NO_TRACK, 5039, "RtspNoTrack", "Drop RTSP packet for track not found") \
XX(ERROR_RTSP_TOKEN_NOT_NORMAL, 5040, "RtspToken", "Invalid RTSP token state not normal") \
XX(ERROR_RTSP_REQUEST_HEADER_EOF, 5041, "RtspHeaderEof", "Invalid RTSP request for header EOF") \
- XX(ERROR_RTSP_NEED_MORE_DATA, 5042, "RtspNeedMoreData", "Need more data to complete RTCP frame parsing")
+ XX(ERROR_RTSP_NEED_MORE_DATA, 5042, "RtspNeedMoreData", "Need more data to complete RTCP frame parsing") \
+ XX(ERROR_RTC_INVALID_SDP, 5043, "RtcInvalidSdp", "Invalid SDP for RTC")
/**************************************************/
/* SRT protocol error. */
@@ -465,6 +468,7 @@ SRS_DECLARE_PRIVATE: // clang-format on
public:
static SrsCplxError *create(const char *func, const char *file, int line, int code, const char *fmt, ...);
static SrsCplxError *wrap(const char *func, const char *file, int line, SrsCplxError *err, const char *fmt, ...);
+ static SrsCplxError *transform(const char *func, const char *file, int line, int code, SrsCplxError *err, const char *fmt, ...);
static SrsCplxError *success();
static SrsCplxError *copy(SrsCplxError *from);
static std::string description(SrsCplxError *err);
@@ -479,13 +483,73 @@ public:
// Error helpers, should use these functions to new or wrap an error.
#define srs_success NULL // SrsCplxError::success()
-#define srs_error_new(ret, fmt, ...) SrsCplxError::create(__FUNCTION__, __FILE__, __LINE__, ret, fmt, ##__VA_ARGS__)
+
+// Create a new error with the specified error code and message.
+//
+// Example:
+// if (fd < 0) {
+// return srs_error_new(ERROR_SOCKET_CREATE, "create socket fd=%d", fd);
+// }
+#define srs_error_new(code, fmt, ...) SrsCplxError::create(__FUNCTION__, __FILE__, __LINE__, code, fmt, ##__VA_ARGS__)
+
+// Wrap an existing error with additional context. The error code is
+// preserved from the wrapped error.
+//
+// Example:
+// if ((err = do_connect(host, port)) != srs_success) {
+// return srs_error_wrap(err, "connect to %s:%d", host.c_str(), port);
+// }
#define srs_error_wrap(err, fmt, ...) SrsCplxError::wrap(__FUNCTION__, __FILE__, __LINE__, err, fmt, ##__VA_ARGS__)
+
+// Transform an error by wrapping it and changing its error code. Useful
+// for converting internal errors to protocol-specific error codes (e.g.,
+// HTTP, RTMP). The wrapped error chain is preserved.
+//
+// Example:
+// if ((err = http_hooks_on_publish(ruc->req_)) != srs_success) {
+// return srs_error_transform(ERROR_SYSTEM_AUTH, err, "RTC: http_hooks_on_publish");
+// }
+#define srs_error_transform(code, err, fmt, ...) SrsCplxError::transform(__FUNCTION__, __FILE__, __LINE__, code, err, fmt, ##__VA_ARGS__)
+
+// Copy an error object. Returns a new error object with the same content.
+//
+// Example:
+// srs_error_t err_copy = srs_error_copy(err);
#define srs_error_copy(err) SrsCplxError::copy(err)
+
+// Get the full description of an error including the entire error chain.
+//
+// Example:
+// srs_error_t err = do_something();
+// srs_warn("error: %s", srs_error_desc(err).c_str());
#define srs_error_desc(err) SrsCplxError::description(err)
+
+// Get a brief summary of an error (only the top-level message).
+//
+// Example:
+// srs_error_t err = do_something();
+// srs_trace("error summary: %s", srs_error_summary(err).c_str());
#define srs_error_summary(err) SrsCplxError::summary(err)
+
+// Get the error code as an integer.
+//
+// Example:
+// int code = srs_error_code(err);
+// if (code == ERROR_SOCKET_TIMEOUT) { ... }
#define srs_error_code(err) SrsCplxError::error_code(err)
+
+// Get the error code as a short string (e.g., "SocketTimeout").
+//
+// Example:
+// string code_str = srs_error_code_str(err);
+// srs_trace("error code: %s", code_str.c_str());
#define srs_error_code_str(err) SrsCplxError::error_code_str(err)
+
+// Get the error code description (e.g., "Socket io timeout").
+//
+// Example:
+// string desc = srs_error_code_strlong(err);
+// srs_warn("error description: %s", desc.c_str());
#define srs_error_code_strlong(err) SrsCplxError::error_code_strlong(err)
#ifndef srs_assert
diff --git a/trunk/src/utest/srs_utest_ai17.cpp b/trunk/src/utest/srs_utest_ai17.cpp
index 0e8385e53..cb36a7e7a 100644
--- a/trunk/src/utest/srs_utest_ai17.cpp
+++ b/trunk/src/utest/srs_utest_ai17.cpp
@@ -2437,7 +2437,7 @@ VOID TEST(SrsGoApiRtcWhipTest, DoServeHttpPublishSuccess)
SrsUniquePtr ruc(new SrsRtcUserConfig());
// Call do_serve_http - major use scenario
- HELPER_EXPECT_SUCCESS(whip->do_serve_http(mock_writer.get(), mock_request.get(), ruc.get()));
+ HELPER_EXPECT_SUCCESS(whip->do_serve_http_with(mock_writer.get(), mock_request.get(), ruc.get()));
// Verify request fields were populated correctly
EXPECT_STREQ("192.168.1.100", ruc->req_->ip_.c_str());