1use rtsp_types::{Message, Method, Request, Response, StatusCode, Version, headers};
20
21use crate::error::{Error, MessageKind, Result};
22use crate::state::{SessionState, server_next_state};
23use crate::transport::Transport;
24
25type Body = Vec<u8>;
27
28#[non_exhaustive]
30#[derive(Debug, Clone, PartialEq, Eq)]
31pub enum ServerEvent {
32 RequestAccepted {
34 method: Method,
36 cseq: u32,
38 state: SessionState,
40 },
41 MethodNotValid {
43 method: Method,
45 state: SessionState,
47 },
48 SessionSetup {
50 session_id: String,
52 transport: Transport,
54 },
55 MediaData {
59 channel: u8,
61 data: Vec<u8>,
63 },
64}
65
66type SessionIdSource = Box<dyn FnMut() -> u64 + Send>;
68
69pub struct ServerSession {
71 state: SessionState,
72 session_id: Option<String>,
73 session_timeout: Option<u64>,
74 session_ids: SessionIdSource,
75 negotiated_transport: Option<Transport>,
76 server_header: String,
77}
78
79impl core::fmt::Debug for ServerSession {
80 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
81 f.debug_struct("ServerSession")
82 .field("state", &self.state)
83 .field("session_id", &self.session_id)
84 .field("session_timeout", &self.session_timeout)
85 .field("negotiated_transport", &self.negotiated_transport)
86 .field("server_header", &self.server_header)
87 .finish_non_exhaustive()
88 }
89}
90
91const SESSION_SCOPED_METHODS: [Method; 4] = [
94 Method::Play,
95 Method::Pause,
96 Method::Record,
97 Method::Teardown,
98];
99
100impl ServerSession {
101 pub fn new(session_ids: impl FnMut() -> u64 + Send + 'static) -> Self {
108 ServerSession {
109 state: SessionState::Init,
110 session_id: None,
111 session_timeout: None,
112 session_ids: Box::new(session_ids),
113 negotiated_transport: None,
114 server_header: "rtsp-runtime".to_string(),
115 }
116 }
117
118 pub fn with_session_timeout(mut self, seconds: u64) -> Self {
120 self.session_timeout = Some(seconds);
121 self
122 }
123
124 #[doc(hidden)]
128 pub fn with_session_seed(mut self, seed: u64) -> Self {
129 let mut next = seed;
130 self.session_ids = Box::new(move || {
131 let id = next;
132 next = next.wrapping_add(1);
133 id
134 });
135 self
136 }
137
138 pub fn state(&self) -> SessionState {
140 self.state
141 }
142
143 pub fn session_id(&self) -> Option<&str> {
145 self.session_id.as_deref()
146 }
147
148 pub fn negotiated_transport(&self) -> Option<&Transport> {
150 self.negotiated_transport.as_ref()
151 }
152
153 pub fn handle_request(&mut self, data: &[u8]) -> Result<(Vec<u8>, Vec<ServerEvent>)> {
156 let (message, _consumed) =
157 Message::<Body>::parse(data).map_err(|e| Error::MessageParse {
158 what: MessageKind::Request,
159 source: Box::new(e),
160 })?;
161 let request = match message {
162 Message::Request(r) => r,
163 _ => {
164 return Err(Error::MessageParse {
165 what: MessageKind::Request,
166 source: "expected an RTSP request".into(),
167 });
168 }
169 };
170 self.handle_parsed(request)
171 }
172
173 fn handle_parsed(&mut self, request: Request<Body>) -> Result<(Vec<u8>, Vec<ServerEvent>)> {
174 let method = request.method().clone();
175 let cseq = header_value(request.header(&headers::CSEQ))
176 .and_then(|s| s.trim().parse::<u32>().ok())
177 .ok_or(Error::MissingCSeq)?;
178
179 if !self.session_header_matches(&method, header_value(request.header(&headers::SESSION))) {
180 let resp = self.build_response(StatusCode::SessionNotFound, cseq, |b| b);
181 return Ok((serialize(&Message::from(resp))?, Vec::new()));
182 }
183
184 let next_state = match server_next_state(self.state, &method) {
186 Ok(s) => s,
187 Err(_) => {
188 let resp = self.build_response(StatusCode::MethodNotValidInThisState, cseq, |b| b);
189 let bytes = serialize(&Message::from(resp))?;
190 return Ok((
191 bytes,
192 vec![ServerEvent::MethodNotValid {
193 method,
194 state: self.state,
195 }],
196 ));
197 }
198 };
199
200 let mut events = Vec::new();
201
202 if method == Method::Setup {
204 let chosen = header_value(request.header(&headers::TRANSPORT))
205 .and_then(|t| Transport::parse(t).ok())
206 .and_then(|t| t.first().cloned());
207 let Some(chosen) = chosen else {
208 let resp = self.build_response(StatusCode::UnsupportedTransport, cseq, |b| b);
209 return Ok((serialize(&Message::from(resp))?, events));
210 };
211 let transport = Transport::single(chosen);
213 let session_id = self
214 .session_id
215 .clone()
216 .unwrap_or_else(|| self.allocate_session());
217 self.session_id = Some(session_id.clone());
218 self.negotiated_transport = Some(transport.clone());
219
220 let sid = session_id.clone();
221 let mut session_hdr = crate::session_header::SessionHeader::new(sid.clone());
222 session_hdr.timeout = self.session_timeout.map(std::time::Duration::from_secs);
223 let session_hdr = session_hdr.to_header_value()?;
224 let transport_hdr = transport.to_header_value()?;
225 let resp = self.build_response(StatusCode::Ok, cseq, |b| {
226 b.header(headers::SESSION, session_hdr)
227 .header(headers::TRANSPORT, transport_hdr)
228 });
229 self.state = next_state;
230 events.push(ServerEvent::SessionSetup {
231 session_id,
232 transport,
233 });
234 events.push(ServerEvent::RequestAccepted {
235 method,
236 cseq,
237 state: self.state,
238 });
239 return Ok((serialize(&Message::from(resp))?, events));
240 }
241
242 let session_hdr = self.session_id.clone();
244 let resp = self.build_response(StatusCode::Ok, cseq, |mut b| {
245 if let Some(sid) = &session_hdr {
246 b = b.header(headers::SESSION, sid.clone());
247 }
248 b
249 });
250 self.state = next_state;
251 if method == Method::Teardown {
252 self.session_id = None;
253 self.negotiated_transport = None;
254 }
255 events.push(ServerEvent::RequestAccepted {
256 method,
257 cseq,
258 state: self.state,
259 });
260 Ok((serialize(&Message::from(resp))?, events))
261 }
262
263 fn allocate_session(&mut self) -> String {
264 let id = (self.session_ids)();
265 format!("{id:016x}")
266 }
267
268 fn session_header_matches(&self, method: &Method, header: Option<&str>) -> bool {
271 match (header, &self.session_id) {
272 (Some(value), Some(ours)) => {
273 let theirs = crate::session_header::SessionHeader::parse(value)
274 .map(|h| h.id)
275 .unwrap_or_default();
276 broadcast_auth::constant_time_eq(theirs.as_bytes(), ours.as_bytes())
277 }
278 (Some(_), None) => false,
279 (None, Some(_)) => !SESSION_SCOPED_METHODS.contains(method),
280 (None, None) => true,
281 }
282 }
283
284 fn build_response<F>(&self, status: StatusCode, cseq: u32, f: F) -> Response<Body>
287 where
288 F: FnOnce(rtsp_types::ResponseBuilder) -> rtsp_types::ResponseBuilder,
289 {
290 let builder = Response::builder(Version::V1_0, status)
291 .header(headers::CSEQ, cseq.to_string())
292 .header(headers::SERVER, self.server_header.clone());
293 f(builder).build(Vec::new())
294 }
295}
296
297fn header_value(h: Option<&headers::HeaderValue>) -> Option<&str> {
298 h.map(|v| v.as_str())
299}
300
301fn serialize(message: &Message<Body>) -> Result<Vec<u8>> {
302 let mut out = Vec::new();
303 message.write(&mut out).map_err(Error::MessageWrite)?;
304 Ok(out)
305}
306
307#[cfg(test)]
308mod tests {
309 use super::*;
310
311 fn test_session() -> ServerSession {
314 use std::sync::atomic::{AtomicU64, Ordering};
315 static NEXT: AtomicU64 = AtomicU64::new(0x9e37_79b9_7f4a_7c15);
316 ServerSession::new(|| NEXT.fetch_add(0x2545_f491_4f6c_dd1d, Ordering::Relaxed))
317 }
318
319 fn req(bytes: &str) -> Vec<u8> {
320 bytes.replace('\n', "\r\n").into_bytes()
321 }
322
323 #[test]
324 fn a_request_without_cseq_is_rejected() {
325 let mut s = test_session();
326 let err = s
327 .handle_request(&req("OPTIONS rtsp://h/s RTSP/1.0\n\n"))
328 .unwrap_err();
329 assert!(matches!(err, Error::MissingCSeq), "{err:?}");
330 }
331
332 #[test]
333 fn setup_transitions_init_to_ready() {
334 let mut s = test_session();
335 let (resp, events) = s
336 .handle_request(&req(
337 "SETUP rtsp://h/s RTSP/1.0\nCSeq: 1\nTransport: RTP/AVP/TCP;interleaved=0-1\n\n",
338 ))
339 .unwrap();
340 assert_eq!(s.state(), SessionState::Ready);
341 let text = String::from_utf8_lossy(&resp);
342 assert!(text.contains("200"));
343 assert!(text.contains("Session:"));
344 assert!(text.contains("Transport:"));
345 assert!(
346 events
347 .iter()
348 .any(|e| matches!(e, ServerEvent::SessionSetup { .. }))
349 );
350 }
351
352 #[test]
353 fn play_in_init_returns_455() {
354 let mut s = test_session();
355 let (resp, events) = s
356 .handle_request(&req("PLAY rtsp://h/s RTSP/1.0\nCSeq: 1\n\n"))
357 .unwrap();
358 assert_eq!(s.state(), SessionState::Init);
359 assert!(String::from_utf8_lossy(&resp).contains("455"));
360 assert!(
361 events
362 .iter()
363 .any(|e| matches!(e, ServerEvent::MethodNotValid { .. }))
364 );
365 }
366
367 fn setup(s: &mut ServerSession, transport: &str) -> String {
368 let (resp, _) = s
369 .handle_request(&req(&format!(
370 "SETUP rtsp://h/s RTSP/1.0\nCSeq: 1\nTransport: {transport}\n\n"
371 )))
372 .unwrap();
373 String::from_utf8(resp).unwrap()
374 }
375
376 #[test]
377 fn session_ids_differ_across_sessions_and_carry_64_bits() {
378 let mut a = test_session();
379 let mut b = test_session();
380 setup(&mut a, "RTP/AVP/TCP;interleaved=0-1");
381 setup(&mut b, "RTP/AVP/TCP;interleaved=0-1");
382 let (a, b) = (a.session_id().unwrap(), b.session_id().unwrap());
383 assert_ne!(a, b, "two sessions must not share an id");
384 assert!(a.len() >= 16, "id {a} is shorter than 64 bits of hex");
385 }
386
387 #[test]
388 fn request_with_wrong_session_header_gets_454() {
389 let mut s = test_session();
390 setup(&mut s, "RTP/AVP/TCP;interleaved=0-1");
391 let (resp, _) = s
392 .handle_request(&req(
393 "PLAY rtsp://h/s RTSP/1.0\nCSeq: 2\nSession: not-this-one\n\n",
394 ))
395 .unwrap();
396 assert!(
397 String::from_utf8_lossy(&resp).starts_with("RTSP/1.0 454"),
398 "got: {}",
399 String::from_utf8_lossy(&resp)
400 );
401 assert_eq!(s.state(), SessionState::Ready);
402 let (resp, _) = s
404 .handle_request(&req("TEARDOWN rtsp://h/s RTSP/1.0\nCSeq: 3\n\n"))
405 .unwrap();
406 assert!(String::from_utf8_lossy(&resp).starts_with("RTSP/1.0 454"));
407 assert_eq!(s.state(), SessionState::Ready);
408 let sid = s.session_id().unwrap().to_string();
410 let (resp, _) = s
411 .handle_request(&req(&format!(
412 "PLAY rtsp://h/s RTSP/1.0\nCSeq: 4\nSession: {sid};timeout=60\n\n"
413 )))
414 .unwrap();
415 assert!(String::from_utf8_lossy(&resp).starts_with("RTSP/1.0 200"));
416 assert_eq!(s.state(), SessionState::Playing);
417 }
418
419 #[test]
420 fn setup_answers_exactly_one_transport() {
421 let mut s = test_session();
422 let text = setup(
423 &mut s,
424 "RTP/AVP;unicast;client_port=8000-8001,RTP/AVP/TCP;unicast;interleaved=0-1",
425 );
426 let line = text.lines().find(|l| l.starts_with("Transport:")).unwrap();
427 let answered = Transport::parse(line["Transport:".len()..].trim()).unwrap();
428 assert_eq!(answered.specs.len(), 1, "got: {line}");
429 assert_eq!(answered.first().unwrap().client_port, Some((8000, 8001)));
430 assert_eq!(s.negotiated_transport().unwrap().specs.len(), 1);
431 }
432
433 #[test]
434 fn unparseable_transport_gets_461() {
435 let mut s = test_session();
436 let text = setup(&mut s, "not a transport;;");
437 assert!(text.starts_with("RTSP/1.0 461"), "got: {text}");
438 assert_eq!(s.state(), SessionState::Init);
439 }
440}