PR #25713 opened by Kilian (lusoris) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25713 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25713.patch
This adds a FATE test for RTSP command serialization, as suggested in the automated review of PR 25618. It is stacked on PR 25618 and should land after it; the first two commits here are that PR's commits. The test sends commands through `ff_rtsp_send_cmd_with_content_async()` into an in-memory URLProtocol and prints the result, the bytes written and the CSeq before and after each call. It covers: - a normal request, and one that exactly fills the 4096-byte command buffer; - a request one byte over the buffer, which must be rejected with nothing written and CSeq unchanged; - header and body write failures, which must be returned to the caller; - a tunneled request with a body, which must be rejected before anything is written; - an overflow with Digest authentication, where the nonce count must not advance. **Tests** - `fate-rtsp-cmd`, `fate-rtsp`, `fate-rtpdec` and `fate-srtp` pass on top of PR 25618, and `fate-source` passes. - With `libavformat/rtsp.c` from current master, `fate-rtsp-cmd` fails on five of the seven lines: the overflow cases write 4095 bytes and return 0, the write failures return 0, and the tunneled body sends 124 bytes. >From 28a39f28ceb1efe2ac2e8d4e137396788d9cbaa1 Mon Sep 17 00:00:00 2001 From: lusoris <[email protected]> Date: Fri, 30 Oct 2026 15:22:03 +0100 Subject: [PATCH 1/3] avformat/rtsp: check command write errors before using replies A failed command write returns before reading the reply. Ignoring that error can make callers inspect an uninitialised reply, including when a request is rejected before it is sent. Return header and body write errors and check command results before using replies in setup, playback, subscription and publishing paths. Keep keepalive failures nonfatal: warn and advance the attempt time so the next packet does not immediately retry the failed request. Map non-OK RECORD replies to invalid data rather than passing an internal -1 error to the caller. Propagate failures when resuming after a subscription and while falling back from UDP to TCP. Injected ENOSPC and EPIPE now propagate without a reply read. Ten reply caller cases and both keepalive methods fail without the caller changes and pass with them. A controlled clock verifies five packet reads, one warning and no retry before timeout/2, with the reply queue unchanged. The loopback RTP session and RTSP, RTP and SRTP FATE tests pass. Signed-off-by: lusoris <[email protected]> --- libavformat/rtsp.c | 15 +++++++++---- libavformat/rtspdec.c | 51 +++++++++++++++++++++++++++++-------------- libavformat/rtspenc.c | 20 ++++++++++------- 3 files changed, 58 insertions(+), 28 deletions(-) diff --git a/libavformat/rtsp.c b/libavformat/rtsp.c index 29f219c366a..d912eedb7be 100644 --- a/libavformat/rtsp.c +++ b/libavformat/rtsp.c @@ -1478,6 +1478,7 @@ int ff_rtsp_send_cmd_with_content_async(AVFormatContext *s, RTSPState *rt = s->priv_data; char buf[MAX_URL_SIZE], *out_buf; char base64buf[AV_BASE64_SIZE(sizeof(buf))]; + int ret; if (!rt->rtsp_hd_out) return AVERROR(ENOTCONN); @@ -1513,13 +1514,15 @@ int ff_rtsp_send_cmd_with_content_async(AVFormatContext *s, av_log(s, AV_LOG_TRACE, "Sending:\n%s--\n", buf); - ffurl_write(rt->rtsp_hd_out, out_buf, strlen(out_buf)); + if ((ret = ffurl_write(rt->rtsp_hd_out, out_buf, strlen(out_buf))) < 0) + return ret; if (send_content_length > 0 && send_content) { if (rt->control_transport == RTSP_MODE_TUNNEL) { avpriv_report_missing_feature(s, "Tunneling of RTSP requests with content data"); return AVERROR_PATCHWELCOME; } - ffurl_write(rt->rtsp_hd_out, send_content, send_content_length); + if ((ret = ffurl_write(rt->rtsp_hd_out, send_content, send_content_length)) < 0) + return ret; } rt->last_cmd_time = av_gettime_relative(); @@ -1774,7 +1777,9 @@ int ff_rtsp_make_setup_request(AVFormatContext *s, const char *host, int port, "RealChallenge2: %s, sd=%s\r\n", rt->session_id, real_res, real_csum); } - ff_rtsp_send_cmd(s, "SETUP", rtsp_st->control_url, cmd, reply, NULL); + err = ff_rtsp_send_cmd(s, "SETUP", rtsp_st->control_url, cmd, reply, NULL); + if (err < 0) + goto fail; if (reply->status_code == 461 /* Unsupported protocol */ && i == 0) { err = 1; goto fail; @@ -2190,7 +2195,9 @@ redirect: "CompanyID: KnKV4M4I/B2FjJ1TToLycw==\r\n" "GUID: 00000000-0000-0000-0000-000000000000\r\n", sizeof(cmd)); - ff_rtsp_send_cmd(s, "OPTIONS", rt->control_uri, cmd, reply, NULL); + err = ff_rtsp_send_cmd(s, "OPTIONS", rt->control_uri, cmd, reply, NULL); + if (err < 0) + goto fail; if (reply->status_code != RTSP_STATUS_OK) { err = ff_rtsp_averror(reply->status_code, AVERROR_INVALIDDATA); goto fail; diff --git a/libavformat/rtspdec.c b/libavformat/rtspdec.c index 0e592eebaed..0c7e9098b39 100644 --- a/libavformat/rtspdec.c +++ b/libavformat/rtspdec.c @@ -542,7 +542,7 @@ static int rtsp_read_play(AVFormatContext *s) { RTSPState *rt = s->priv_data; RTSPMessageHeader reply1, *reply = &reply1; - int i; + int i, ret; char cmd[MAX_URL_SIZE]; av_log(s, AV_LOG_DEBUG, "hello state=%d\n", rt->state); @@ -584,7 +584,8 @@ static int rtsp_read_play(AVFormatContext *s) rt->seek_timestamp / AV_TIME_BASE, rt->seek_timestamp / (AV_TIME_BASE / 1000) % 1000); } - ff_rtsp_send_cmd(s, "PLAY", rt->control_uri, cmd, reply, NULL); + if ((ret = ff_rtsp_send_cmd(s, "PLAY", rt->control_uri, cmd, reply, NULL)) < 0) + return ret; if (reply->status_code != RTSP_STATUS_OK) { return ff_rtsp_averror(reply->status_code, -1); } @@ -613,11 +614,13 @@ static int rtsp_read_pause(AVFormatContext *s) { RTSPState *rt = s->priv_data; RTSPMessageHeader reply1, *reply = &reply1; + int ret; if (rt->state != RTSP_STATE_STREAMING) return 0; else if (!(rt->server_type == RTSP_SERVER_REAL && rt->need_subscription)) { - ff_rtsp_send_cmd(s, "PAUSE", rt->control_uri, NULL, reply, NULL); + if ((ret = ff_rtsp_send_cmd(s, "PAUSE", rt->control_uri, NULL, reply, NULL)) < 0) + return ret; if (reply->status_code != RTSP_STATUS_OK) { return ff_rtsp_averror(reply->status_code, -1); } @@ -751,7 +754,11 @@ int ff_rtsp_setup_input_streams(AVFormatContext *s, RTSPMessageHeader *reply) "Require: com.real.retain-entity-for-setup\r\n", sizeof(cmd)); } - ff_rtsp_send_cmd(s, "DESCRIBE", rt->control_uri, cmd, reply, &content); + ret = ff_rtsp_send_cmd(s, "DESCRIBE", rt->control_uri, cmd, reply, &content); + if (ret < 0) { + av_freep(&content); + return ret; + } if (reply->status_code != RTSP_STATUS_OK) { av_freep(&content); return ff_rtsp_averror(reply->status_code, AVERROR_INVALIDDATA); @@ -987,8 +994,10 @@ retry: snprintf(cmd, sizeof(cmd), "Unsubscribe: %s\r\n", rt->last_subscription); - ff_rtsp_send_cmd(s, "SET_PARAMETER", rt->control_uri, - cmd, reply, NULL); + ret = ff_rtsp_send_cmd(s, "SET_PARAMETER", rt->control_uri, + cmd, reply, NULL); + if (ret < 0) + return ret; if (reply->status_code != RTSP_STATUS_OK) return ff_rtsp_averror(reply->status_code, AVERROR_INVALIDDATA); rt->need_subscription = 1; @@ -1022,14 +1031,19 @@ retry: } } av_strlcatf(cmd, sizeof(cmd), "%s\r\n", rt->last_subscription); - ff_rtsp_send_cmd(s, "SET_PARAMETER", rt->control_uri, - cmd, reply, NULL); + ret = ff_rtsp_send_cmd(s, "SET_PARAMETER", rt->control_uri, + cmd, reply, NULL); + if (ret < 0) + return ret; if (reply->status_code != RTSP_STATUS_OK) return ff_rtsp_averror(reply->status_code, AVERROR_INVALIDDATA); rt->need_subscription = 0; - if (rt->state == RTSP_STATE_STREAMING) - rtsp_read_play (s); + if (rt->state == RTSP_STATE_STREAMING) { + ret = rtsp_read_play(s); + if (ret < 0) + return ret; + } } } @@ -1040,8 +1054,8 @@ retry: rt->lower_transport_mask & (1 << RTSP_LOWER_TRANSPORT_TCP)) { RTSPMessageHeader reply1, *reply = &reply1; av_log(s, AV_LOG_WARNING, "UDP timeout, retrying with TCP\n"); - if (rtsp_read_pause(s) != 0) - return -1; + if ((ret = rtsp_read_pause(s)) != 0) + return ret; // TEARDOWN is required on Real-RTSP, but might make // other servers close the connection. if (rt->server_type == RTSP_SERVER_REAL) @@ -1051,8 +1065,8 @@ retry: if (resetup_tcp(s) == 0) { rt->state = RTSP_STATE_IDLE; rt->need_subscription = 1; - if (rtsp_read_play(s) != 0) - return -1; + if ((ret = rtsp_read_play(s)) != 0) + return ret; goto retry; } } @@ -1068,9 +1082,14 @@ retry: if (rt->server_type == RTSP_SERVER_WMS || (rt->server_type != RTSP_SERVER_REAL && rt->get_parameter_supported)) { - ff_rtsp_send_cmd_async(s, "GET_PARAMETER", rt->control_uri, NULL); + ret = ff_rtsp_send_cmd_async(s, "GET_PARAMETER", rt->control_uri, NULL); } else { - ff_rtsp_send_cmd_async(s, "OPTIONS", rt->control_uri, NULL); + ret = ff_rtsp_send_cmd_async(s, "OPTIONS", rt->control_uri, NULL); + } + if (ret < 0) { + av_log(s, AV_LOG_WARNING, "Failed to send RTSP keepalive: %s\n", + av_err2str(ret)); + rt->last_cmd_time = av_gettime_relative(); } /* The stale flag should be reset when creating the auth response in * ff_rtsp_send_cmd_async, but reset it here just in case we never diff --git a/libavformat/rtspenc.c b/libavformat/rtspenc.c index f2f4b6d5258..370260a0eb8 100644 --- a/libavformat/rtspenc.c +++ b/libavformat/rtspenc.c @@ -48,7 +48,7 @@ int ff_rtsp_setup_output_streams(AVFormatContext *s, const char *addr) { RTSPState *rt = s->priv_data; RTSPMessageHeader reply1, *reply = &reply1; - int i; + int i, ret; char *sdp; AVFormatContext sdp_ctx, *ctx_array[1]; char url[MAX_URL_SIZE]; @@ -82,10 +82,12 @@ int ff_rtsp_setup_output_streams(AVFormatContext *s, const char *addr) return AVERROR_INVALIDDATA; } av_log(s, AV_LOG_VERBOSE, "SDP:\n%s\n", sdp); - ff_rtsp_send_cmd_with_content(s, "ANNOUNCE", rt->control_uri, - "Content-Type: application/sdp\r\n", - reply, NULL, sdp, strlen(sdp)); + ret = ff_rtsp_send_cmd_with_content(s, "ANNOUNCE", rt->control_uri, + "Content-Type: application/sdp\r\n", + reply, NULL, sdp, strlen(sdp)); av_free(sdp); + if (ret < 0) + return ret; if (reply->status_code != RTSP_STATUS_OK) return ff_rtsp_averror(reply->status_code, AVERROR_INVALIDDATA); @@ -114,12 +116,14 @@ static int rtsp_write_record(AVFormatContext *s) RTSPState *rt = s->priv_data; RTSPMessageHeader reply1, *reply = &reply1; char cmd[MAX_URL_SIZE]; + int ret; snprintf(cmd, sizeof(cmd), "Range: npt=0.000-\r\n"); - ff_rtsp_send_cmd(s, "RECORD", rt->control_uri, cmd, reply, NULL); + if ((ret = ff_rtsp_send_cmd(s, "RECORD", rt->control_uri, cmd, reply, NULL)) < 0) + return ret; if (reply->status_code != RTSP_STATUS_OK) - return ff_rtsp_averror(reply->status_code, -1); + return ff_rtsp_averror(reply->status_code, AVERROR_INVALIDDATA); rt->state = RTSP_STATE_STREAMING; return 0; } @@ -132,10 +136,10 @@ static int rtsp_write_header(AVFormatContext *s) if (ret) return ret; - if (rtsp_write_record(s) < 0) { + if ((ret = rtsp_write_record(s)) < 0) { ff_rtsp_close_streams(s); ff_rtsp_close_connections(s); - return AVERROR_INVALIDDATA; + return ret; } return 0; } -- 2.54.0 >From b7419f19531d5bedd17cb8cae5dd24f834b4064d Mon Sep 17 00:00:00 2001 From: lusoris <[email protected]> Date: Fri, 30 Oct 2026 15:23:53 +0100 Subject: [PATCH 2/3] avformat/rtsp: reject truncated command requests Unchecked appends can fill the fixed request buffer before the header terminator is added, sending an incomplete request to the server. Build the request with a bounded AVBPrint and reject truncation before writing anything or advancing CSeq. The preceding caller checks handle this rejection before inspecting a reply that has not been read. Restore Digest authentication state when a too-long request is rejected, so the request has no authentication-side effects. Reject tunneled request bodies before building or writing a header, advancing CSeq, or changing authentication state. A loopback ffprobe test with a 4040-byte SDP control URL sent a 4095-byte SETUP without CRLFCRLF before this change. It now logs an error and sends no SETUP bytes. Direct checks cover overflowing headers, authentication, content length, tunneling and the exact-fit boundary. The loopback RTP session and RTSP, RTP and SRTP FATE tests pass. Signed-off-by: lusoris <[email protected]> --- libavformat/rtsp.c | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/libavformat/rtsp.c b/libavformat/rtsp.c index d912eedb7be..aa85b68cf87 100644 --- a/libavformat/rtsp.c +++ b/libavformat/rtsp.c @@ -1478,33 +1478,50 @@ int ff_rtsp_send_cmd_with_content_async(AVFormatContext *s, RTSPState *rt = s->priv_data; char buf[MAX_URL_SIZE], *out_buf; char base64buf[AV_BASE64_SIZE(sizeof(buf))]; + AVBPrint request; + HTTPAuthState auth_saved; int ret; if (!rt->rtsp_hd_out) return AVERROR(ENOTCONN); + if (rt->control_transport == RTSP_MODE_TUNNEL && send_content_length > 0 && + send_content) { + avpriv_report_missing_feature(s, "Tunneling of RTSP requests with content data"); + return AVERROR_PATCHWELCOME; + } /* Add in RTSP headers */ out_buf = buf; - rt->seq++; - snprintf(buf, sizeof(buf), "%s %s RTSP/1.0\r\n", method, url); + if (rt->auth[0]) + auth_saved = rt->auth_state; + av_bprint_init_for_buffer(&request, buf, sizeof(buf)); + av_bprintf(&request, "%s %s RTSP/1.0\r\n", method, url); if (headers) - av_strlcat(buf, headers, sizeof(buf)); - av_strlcatf(buf, sizeof(buf), "CSeq: %d\r\n", rt->seq); - av_strlcatf(buf, sizeof(buf), "User-Agent: %s\r\n", rt->user_agent); + av_bprintf(&request, "%s", headers); + av_bprintf(&request, "CSeq: %d\r\n", rt->seq + 1); + av_bprintf(&request, "User-Agent: %s\r\n", rt->user_agent); if (rt->session_id[0] != '\0' && (!headers || !strstr(headers, "\nIf-Match:"))) { - av_strlcatf(buf, sizeof(buf), "Session: %s\r\n", rt->session_id); + av_bprintf(&request, "Session: %s\r\n", rt->session_id); } if (rt->auth[0]) { char *str = ff_http_auth_create_response(&rt->auth_state, rt->auth, url, method); if (str) - av_strlcat(buf, str, sizeof(buf)); + av_bprintf(&request, "%s", str); av_free(str); } if (send_content_length > 0 && send_content) - av_strlcatf(buf, sizeof(buf), "Content-Length: %d\r\n", send_content_length); - av_strlcat(buf, "\r\n", sizeof(buf)); + av_bprintf(&request, "Content-Length: %d\r\n", send_content_length); + av_bprintf(&request, "\r\n"); + + if (!av_bprint_is_complete(&request)) { + if (rt->auth[0]) + rt->auth_state = auth_saved; + av_log(s, AV_LOG_ERROR, "RTSP request too long\n"); + return AVERROR(EINVAL); + } + rt->seq++; /* base64 encode rtsp if tunneling */ if (rt->control_transport == RTSP_MODE_TUNNEL) { @@ -1517,10 +1534,6 @@ int ff_rtsp_send_cmd_with_content_async(AVFormatContext *s, if ((ret = ffurl_write(rt->rtsp_hd_out, out_buf, strlen(out_buf))) < 0) return ret; if (send_content_length > 0 && send_content) { - if (rt->control_transport == RTSP_MODE_TUNNEL) { - avpriv_report_missing_feature(s, "Tunneling of RTSP requests with content data"); - return AVERROR_PATCHWELCOME; - } if ((ret = ffurl_write(rt->rtsp_hd_out, send_content, send_content_length)) < 0) return ret; } -- 2.54.0 >From 2690fbddced5d8c697676674c9031ea1cf2fd588 Mon Sep 17 00:00:00 2001 From: lusoris <[email protected]> Date: Tue, 3 Nov 2026 13:47:42 +0100 Subject: [PATCH 3/3] avformat/tests: add an RTSP command serialization test The RTSP FATE tests only cover SDP sample-rate parsing. Add a test that sends commands through ff_rtsp_send_cmd_with_content_async() into an in-memory URLProtocol and prints the result, the bytes written and the CSeq before and after each call. It covers a normal request, a request that exactly fills the command buffer, one byte over it, header and body write failures, a tunneled request with a body, and an overflow with Digest authentication, where the nonce count must not advance. The control handle is heap-allocated, so the test also holds if a failed write closes the connection. Signed-off-by: lusoris <[email protected]> --- libavformat/Makefile | 1 + libavformat/tests/rtsp.c | 206 +++++++++++++++++++++++++++++++++++++ tests/fate/libavformat.mak | 4 + tests/ref/fate/rtsp-cmd | 7 ++ 4 files changed, 218 insertions(+) create mode 100644 libavformat/tests/rtsp.c create mode 100644 tests/ref/fate/rtsp-cmd diff --git a/libavformat/Makefile b/libavformat/Makefile index e479c5c67cf..53b649c4e88 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -795,6 +795,7 @@ TESTPROGS = id3v2 \ url \ seek_utils TESTPROGS-$(CONFIG_RTPDEC) += rtpdec +TESTPROGS-$(CONFIG_RTSP_MUXER) += rtsp # async \ FIFO-MUXER-TESTPROGS-$(CONFIG_NETWORK) += fifo_muxer diff --git a/libavformat/tests/rtsp.c b/libavformat/tests/rtsp.c new file mode 100644 index 00000000000..d8d70b9cca7 --- /dev/null +++ b/libavformat/tests/rtsp.c @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2026 lusoris + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "libavformat/avformat.h" +#include "libavformat/httpauth.h" +#include "libavformat/internal.h" +#include "libavformat/rtsp.h" +#include "libavformat/url.h" +#include "libavutil/error.h" +#include "libavutil/avstring.h" +#include "libavutil/mem.h" + +typedef struct TestContext { + unsigned char buf[MAX_URL_SIZE]; + int len; + int fail_write; + int error; + int writes; +} TestContext; + +static int test_write(URLContext *h, const unsigned char *buf, int size) +{ + TestContext *ctx = h->priv_data; + + if (ctx->writes++ == ctx->fail_write) + return ctx->error; + if (size > MAX_URL_SIZE - ctx->len) + return AVERROR(ENOSPC); + memcpy(ctx->buf + ctx->len, buf, size); + ctx->len += size; + return size; +} + +static const URLProtocol test_protocol = { + .name = "test", + .url_write = test_write, +}; + +static char user_agent[] = "rtsp-test"; + +/** + * The control handle is heap-allocated, as ff_rtsp_close_connections() may + * free it after a failed write. + */ +static void init_context(AVFormatContext *s, RTSPState *rt, TestContext *ctx) +{ + URLContext *url; + + ffurl_closep(&rt->rtsp_hd_out); + url = av_mallocz(sizeof(*url)); + if (!url) + exit(1); + *url = (URLContext) { .prot = &test_protocol, .priv_data = ctx, + .flags = AVIO_FLAG_WRITE }; + *s = (AVFormatContext) { .priv_data = rt }; + *rt = (RTSPState) { .rtsp_hd_out = url, .seq = 41, + .user_agent = user_agent }; + *ctx = (TestContext) { .fail_write = -1 }; +} + +static int ends_with_crlfcrlf(const TestContext *ctx) +{ + return ctx->len >= 4 && !memcmp(ctx->buf + ctx->len - 4, "\r\n\r\n", 4); +} + +static const char *err_name(int ret) +{ + if (ret >= 0) + return "0"; + if (ret == AVERROR(EINVAL)) + return "EINVAL"; + if (ret == AVERROR(ENOSPC)) + return "ENOSPC"; + if (ret == AVERROR(EPIPE)) + return "EPIPE"; + if (ret == AVERROR_PATCHWELCOME) + return "PATCHWELCOME"; + return "other"; +} + +static void print_result(const char *name, const char *method, int ret, + const TestContext *ctx, int seq_before, int seq_after, + int ends_with_crlfcrlf) +{ + printf("%s method=%s rc=%s bytes=%d cseq=%d/%d crlf=%d\n", name, method, + err_name(ret), ctx->len, seq_before, seq_after, ends_with_crlfcrlf); +} + +static int send_rtsp(AVFormatContext *s, const char *method, const char *headers, + const unsigned char *content, int content_length) +{ + return ff_rtsp_send_cmd_with_content_async(s, method, "rtsp://example/test", + headers, content, content_length); +} + +static void set_digest_auth(RTSPState *rt) +{ + av_strlcpy(rt->auth, "user:pass", sizeof(rt->auth)); + rt->auth_state.auth_type = HTTP_AUTH_DIGEST; + av_strlcpy(rt->auth_state.realm, "realm", sizeof(rt->auth_state.realm)); + av_strlcpy(rt->auth_state.digest_params.nonce, "nonce", + sizeof(rt->auth_state.digest_params.nonce)); + av_strlcpy(rt->auth_state.digest_params.qop, "auth", + sizeof(rt->auth_state.digest_params.qop)); + rt->auth_state.digest_params.nc = 7; +} + +int main(void) +{ + AVFormatContext s; + RTSPState rt = { 0 }; + TestContext ctx; + unsigned char content[] = "body"; + char headers[MAX_URL_SIZE + 1]; + int bare_length, digest_length, fit_length, ret, seq_before, nc_before; + + init_context(&s, &rt, &ctx); + seq_before = rt.seq; + ret = send_rtsp(&s, "OPTIONS", NULL, NULL, 0); + bare_length = ctx.len; + print_result("normal", "OPTIONS", ret, &ctx, seq_before, rt.seq, + ends_with_crlfcrlf(&ctx)); + + fit_length = MAX_URL_SIZE - 1 - bare_length; + memset(headers, 'a', fit_length + 1); + headers[0] = 'X'; + headers[1] = ':'; + headers[2] = ' '; + headers[fit_length - 2] = '\r'; + headers[fit_length - 1] = '\n'; + headers[fit_length] = 0; + + init_context(&s, &rt, &ctx); + seq_before = rt.seq; + ret = send_rtsp(&s, "OPTIONS", headers, NULL, 0); + print_result("exact", "OPTIONS", ret, &ctx, seq_before, rt.seq, + ends_with_crlfcrlf(&ctx)); + + headers[fit_length] = 'a'; + headers[fit_length + 1] = 0; + init_context(&s, &rt, &ctx); + seq_before = rt.seq; + ret = send_rtsp(&s, "OPTIONS", headers, NULL, 0); + print_result("overflow", "OPTIONS", ret, &ctx, seq_before, rt.seq, 0); + + init_context(&s, &rt, &ctx); + ctx.fail_write = 0; + ctx.error = AVERROR(ENOSPC); + seq_before = rt.seq; + ret = send_rtsp(&s, "OPTIONS", NULL, NULL, 0); + print_result("header-fail", "OPTIONS", ret, &ctx, seq_before, rt.seq, 0); + + init_context(&s, &rt, &ctx); + ctx.fail_write = 1; + ctx.error = AVERROR(EPIPE); + seq_before = rt.seq; + ret = send_rtsp(&s, "ANNOUNCE", NULL, content, sizeof(content) - 1); + print_result("body-fail", "ANNOUNCE", ret, &ctx, seq_before, rt.seq, 0); + + init_context(&s, &rt, &ctx); + rt.control_transport = RTSP_MODE_TUNNEL; + seq_before = rt.seq; + ret = send_rtsp(&s, "ANNOUNCE", NULL, content, sizeof(content) - 1); + print_result("tunnel-body", "ANNOUNCE", ret, &ctx, seq_before, rt.seq, 0); + + init_context(&s, &rt, &ctx); + set_digest_auth(&rt); + ret = send_rtsp(&s, "OPTIONS", NULL, NULL, 0); + digest_length = ctx.len; + fit_length = MAX_URL_SIZE - digest_length; + memset(headers, 'a', fit_length + 1); + headers[fit_length] = 0; + + init_context(&s, &rt, &ctx); + set_digest_auth(&rt); + seq_before = rt.seq; + nc_before = rt.auth_state.digest_params.nc; + ret = send_rtsp(&s, "OPTIONS", headers, NULL, 0); + printf("digest-overflow method=OPTIONS rc=%s bytes=%d cseq=%d/%d nc=%d/%d\n", + err_name(ret), ctx.len, seq_before, rt.seq, nc_before, + rt.auth_state.digest_params.nc); + + ffurl_closep(&rt.rtsp_hd_out); + return 0; +} diff --git a/tests/fate/libavformat.mak b/tests/fate/libavformat.mak index e7854626c33..89b744d3478 100644 --- a/tests/fate/libavformat.mak +++ b/tests/fate/libavformat.mak @@ -64,6 +64,10 @@ FATE_LIBAVFORMAT-$(CONFIG_RTPDEC) += fate-rtpdec fate-rtpdec: libavformat/tests/rtpdec$(EXESUF) fate-rtpdec: CMD = run libavformat/tests/rtpdec$(EXESUF) +FATE_LIBAVFORMAT-$(CONFIG_RTSP_MUXER) += fate-rtsp-cmd +fate-rtsp-cmd: libavformat/tests/rtsp$(EXESUF) +fate-rtsp-cmd: CMD = run libavformat/tests/rtsp$(EXESUF) + FATE_LIBAVFORMAT-$(CONFIG_SRTP) += fate-srtp fate-srtp: libavformat/tests/srtp$(EXESUF) fate-srtp: CMD = run libavformat/tests/srtp$(EXESUF) diff --git a/tests/ref/fate/rtsp-cmd b/tests/ref/fate/rtsp-cmd new file mode 100644 index 00000000000..156d7b14e92 --- /dev/null +++ b/tests/ref/fate/rtsp-cmd @@ -0,0 +1,7 @@ +normal method=OPTIONS rc=0 bytes=73 cseq=41/42 crlf=1 +exact method=OPTIONS rc=0 bytes=4095 cseq=41/42 crlf=1 +overflow method=OPTIONS rc=EINVAL bytes=0 cseq=41/41 crlf=0 +header-fail method=OPTIONS rc=ENOSPC bytes=0 cseq=41/42 crlf=0 +body-fail method=ANNOUNCE rc=EPIPE bytes=93 cseq=41/42 crlf=0 +tunnel-body method=ANNOUNCE rc=PATCHWELCOME bytes=0 cseq=41/41 crlf=0 +digest-overflow method=OPTIONS rc=EINVAL bytes=0 cseq=41/41 nc=7/7 -- 2.54.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
