Hi maintainers, while reviewing gortsplib's RTSP implementation against RFC 2326, we noticed several places where the current behavior appears to differ from the specification. Each item below cites the relevant RFC text alongside the corresponding source code for your reference. We hope this is helpful for improving RFC conformance.
(Findings below are based on gortsplib v5.6.0-1-gad82b9c6 (branch main, commitad82b9c60d45e2d07f40854d6436fd9a6d313ef8, 2026-06-11). Source line numbers in each item refer to this revision.)
1. PAUSE Accepted in Ready State Rather Than Rejected with 455
RFC Reference: RFC 2326 Section 11.3.6 (455 Method Not Valid in This State) and Appendix A.2 (Server State Machine)
"455 Method Not Valid In This State — The client or server cannot process this request in its current state."
(Appendix A.2, Server State Machine) PAUSE is listed as a valid transition only from Playing and Recording states; it is NOT listed for Ready or Init.
Analysis:
The PAUSE handler at server_session.go:1385 calls checkState with an allowed set that includes ServerSessionStatePrePlay and ServerSessionStatePreRecord, which correspond to RFC 2326's Ready state (SETUP completed but stream not playing/recording). When a PAUSE request arrives in the Ready state, the state check passes and the framework delegates to the user-implemented OnPause handler, which may return StatusOK (200). The post-OK state-transition block at server_session.go:1405-1453 is skipped entirely when the session is in PrePlay/PreRecord (since it only triggers for Play/Record states), so a 200 OK response is sent without any actual pause action occurring. Per RFC 2326 Appendix A.2, PAUSE is only valid from Playing and Recording states; the current allowed set appears broader than the specification intends.
Source Code Evidence (server_session.go):
// server_session.go:1384-1395
case base.Pause:
err := ss.checkState(map[ServerSessionState]struct{}{
ServerSessionStatePrePlay: {}, // RFC "Ready" — PAUSE not listed for this state
ServerSessionStatePlay: {},
ServerSessionStatePreRecord: {}, // RFC "Ready" — PAUSE not listed for this state
ServerSessionStateRecord: {},
})
if err != nil {
return &base.Response{
StatusCode: base.StatusBadRequest, // 400 rather than 455
}, err
}
2. RECORD on PLAY-Only Session Returns 400 Rather Than 405
RFC Reference: RFC 2326 Section 11.3.1 (405 Method Not Allowed)
"405 Method Not Allowed — The method specified in the request is not allowed for the resource identified by the request URI. The response MUST include an Allow header containing a list of valid methods for the requested resource. This status code is also to be used if a request attempts to use a method not indicated during SETUP, e.g., if a RECORD request is issued even though the mode parameter in the Transport header only specified PLAY."
Analysis:
The RECORD handler at server_session.go:1314-1320 calls checkState allowing only ServerSessionStatePreRecord. When the session is in a PLAY-only state (PrePlay or Play), checkState fails and the handler returns StatusBadRequest (400) instead of StatusMethodNotAllowed (405). The StatusMethodNotAllowed (405) constant is defined at pkg/base/response.go:26 but is never referenced in any server handler code path (confirmed by exhaustive grep). The RFC explicitly states that 405 should be used "if a RECORD request is issued even though the mode parameter in the Transport header only specified PLAY." The current implementation does block the RECORD attempt (the security intent is met), but the response code differs from what the RFC specifies.
Source Code Evidence (server_session.go):
// server_session.go:1313-1321
case base.Record:
err := ss.checkState(map[ServerSessionState]struct{}{
ServerSessionStatePreRecord: {}, // only PreRecord allowed; PrePlay (PLAY-only) rejected
})
if err != nil {
return &base.Response{
StatusCode: base.StatusBadRequest, // 400 rather than RFC-specified 405
}, err
}
3. RECORD with Unsupported append Transport Parameter Not Refused
RFC Reference: RFC 2326 Section 12.39 (Transport — append parameter)
"append: If the mode parameter includes RECORD, the append parameter indicates that the media data should append to the existing resource rather than overwrite it. If appending is requested and the server does not support this, it MUST refuse the request rather than overwrite the resource identified by the URI."
Analysis:
The Transport header parser at pkg/headers/transport.go:259-261 silently ignores non-standard keys via the default case, including the append parameter. The Transport struct has no Append field, and no code anywhere in the codebase checks for append support. The RECORD handler at server_session.go:1313-1382 proceeds to call the user-implemented OnRecord handler without any append-related guard. If the handler returns StatusOK (200), the session transitions to Record state and recording begins — the append request is effectively treated as a normal overwrite RECORD. Since gortsplib has no concept of append support (AppendSupported is always effectively false), the RFC's requirement that the server "MUST refuse the request" when append is requested but unsupported is not enforced by the library.
Source Code Evidence (pkg/headers/transport.go):
// pkg/headers/transport.go:259-261
default:
// ignore non-standard keys // 'append' silently discarded here
}
// server_session.go:1337-1346 — OnRecord called with no append check
res, err := ss.s.Handler.(ServerHandlerOnRecord).OnRecord(&ServerHandlerOnRecordCtx{
Session: ss,
Conn: sc,
Request: req,
Path: path,
Query: query,
})
if res.StatusCode == base.StatusOK {
ss.state = ServerSessionStateRecord // recording starts, append silently ignored
4. SETUP with Unsupported Require Header Option Not Rejected with 551
RFC Reference: RFC 2326 Section 12.32 (Require) and Section 11.3.14 (551 Option not supported)
"The Require header is used by clients to query the server about options that it may or may not support. The server MUST respond to this header by using the Unsupported header to negatively acknowledge those options which are NOT supported."
(Section 11.3.14) "551 Option not supported"
Analysis:
The only server-side code that reads the Require header is checkBackChannelsEnabled at server_conn.go:44-51, which checks for exactly one hardcoded value (www.onvif.org/ver20/backchannel) used solely during DESCRIBE processing. The SETUP handler at server_session.go:825-1217 validates session state, transport headers, protocol, and mode, but never examines the Require header. There is no mechanism to maintain a list of supported options, compare incoming Require options against supported ones, or produce a 551 response. The StatusOptionNotSupported (551) constant is defined at pkg/base/response.go:60 but is never referenced in any request-processing code path. The library simply passes the request to the OnSetup handler without Require validation, which differs from the RFC's mandate that the server "MUST respond" with 551 for unsupported options.
Source Code Evidence (server_conn.go and server_session.go):
// server_conn.go:44-51 — the ONLY server-side Require reader; checks one hardcoded value
func checkBackChannelsEnabled(header base.Header) bool {
if vals, ok := header["Require"]; ok {
if slices.Contains(vals, "www.onvif.org/ver20/backchannel") {
return true
}
}
return false
}
// server_session.go:825-835 — SETUP handler entry; no Require header check
case base.Setup:
err := ss.checkState(map[ServerSessionState]struct{}{
ServerSessionStateInitial: {},
ServerSessionStatePrePlay: {},
ServerSessionStatePreRecord: {},
})
// ... proceeds to transport parsing, OnSetup callback — Require never examined
Hi maintainers, while reviewing gortsplib's RTSP implementation against RFC 2326, we noticed several places where the current behavior appears to differ from the specification. Each item below cites the relevant RFC text alongside the corresponding source code for your reference. We hope this is helpful for improving RFC conformance.
(Findings below are based on gortsplib
v5.6.0-1-gad82b9c6(branchmain, commitad82b9c60d45e2d07f40854d6436fd9a6d313ef8, 2026-06-11). Source line numbers in each item refer to this revision.)1. PAUSE Accepted in Ready State Rather Than Rejected with 455
RFC Reference: RFC 2326 Section 11.3.6 (455 Method Not Valid in This State) and Appendix A.2 (Server State Machine)
Analysis:
The PAUSE handler at
server_session.go:1385callscheckStatewith an allowed set that includesServerSessionStatePrePlayandServerSessionStatePreRecord, which correspond to RFC 2326'sReadystate (SETUP completed but stream not playing/recording). When a PAUSE request arrives in the Ready state, the state check passes and the framework delegates to the user-implementedOnPausehandler, which may returnStatusOK (200). The post-OK state-transition block atserver_session.go:1405-1453is skipped entirely when the session is in PrePlay/PreRecord (since it only triggers for Play/Record states), so a 200 OK response is sent without any actual pause action occurring. Per RFC 2326 Appendix A.2, PAUSE is only valid from Playing and Recording states; the current allowed set appears broader than the specification intends.Source Code Evidence (
server_session.go):2. RECORD on PLAY-Only Session Returns 400 Rather Than 405
RFC Reference: RFC 2326 Section 11.3.1 (405 Method Not Allowed)
Analysis:
The RECORD handler at
server_session.go:1314-1320callscheckStateallowing onlyServerSessionStatePreRecord. When the session is in a PLAY-only state (PrePlay or Play),checkStatefails and the handler returnsStatusBadRequest (400)instead ofStatusMethodNotAllowed (405). TheStatusMethodNotAllowed (405)constant is defined atpkg/base/response.go:26but is never referenced in any server handler code path (confirmed by exhaustive grep). The RFC explicitly states that 405 should be used "if a RECORD request is issued even though the mode parameter in the Transport header only specified PLAY." The current implementation does block the RECORD attempt (the security intent is met), but the response code differs from what the RFC specifies.Source Code Evidence (
server_session.go):3. RECORD with Unsupported
appendTransport Parameter Not RefusedRFC Reference: RFC 2326 Section 12.39 (Transport —
appendparameter)Analysis:
The Transport header parser at
pkg/headers/transport.go:259-261silently ignores non-standard keys via thedefaultcase, including theappendparameter. TheTransportstruct has noAppendfield, and no code anywhere in the codebase checks for append support. The RECORD handler atserver_session.go:1313-1382proceeds to call the user-implementedOnRecordhandler without any append-related guard. If the handler returnsStatusOK (200), the session transitions to Record state and recording begins — theappendrequest is effectively treated as a normal overwrite RECORD. Since gortsplib has no concept of append support (AppendSupportedis always effectivelyfalse), the RFC's requirement that the server "MUST refuse the request" when append is requested but unsupported is not enforced by the library.Source Code Evidence (
pkg/headers/transport.go):4. SETUP with Unsupported Require Header Option Not Rejected with 551
RFC Reference: RFC 2326 Section 12.32 (Require) and Section 11.3.14 (551 Option not supported)
Analysis:
The only server-side code that reads the Require header is
checkBackChannelsEnabledatserver_conn.go:44-51, which checks for exactly one hardcoded value (www.onvif.org/ver20/backchannel) used solely during DESCRIBE processing. The SETUP handler atserver_session.go:825-1217validates session state, transport headers, protocol, and mode, but never examines the Require header. There is no mechanism to maintain a list of supported options, compare incoming Require options against supported ones, or produce a 551 response. TheStatusOptionNotSupported (551)constant is defined atpkg/base/response.go:60but is never referenced in any request-processing code path. The library simply passes the request to theOnSetuphandler without Require validation, which differs from the RFC's mandate that the server "MUST respond" with 551 for unsupported options.Source Code Evidence (
server_conn.goandserver_session.go):