1use rtsp_types::{Message, Method, Request, Response, StatusCode, Version, headers};
20
21use crate::error::{Error, 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(format!("{e:?}")))?;
158 let request = match message {
159 Message::Request(r) => r,
160 _ => return Err(Error::MessageParse("expected an RTSP request".into())),
161 };
162 self.handle_parsed(request)
163 }
164
165 fn handle_parsed(&mut self, request: Request<Body>) -> Result<(Vec<u8>, Vec<ServerEvent>)> {
166 let method = request.method().clone();
167 let cseq = header_value(request.header(&headers::CSEQ))
168 .and_then(|s| s.trim().parse::<u32>().ok())
169 .ok_or(Error::MissingCSeq)?;
170
171 if !self.session_header_matches(&method, header_value(request.header(&headers::SESSION))) {
172 let resp = self.build_response(StatusCode::SessionNotFound, cseq, |b| b);
173 return Ok((serialize(&Message::from(resp))?, Vec::new()));
174 }
175
176 let next_state = match server_next_state(self.state, &method) {
178 Ok(s) => s,
179 Err(_) => {
180 let resp = self.build_response(StatusCode::MethodNotValidInThisState, cseq, |b| b);
181 let bytes = serialize(&Message::from(resp))?;
182 return Ok((
183 bytes,
184 vec![ServerEvent::MethodNotValid {
185 method,
186 state: self.state,
187 }],
188 ));
189 }
190 };
191
192 let mut events = Vec::new();
193
194 if method == Method::Setup {
196 let chosen = header_value(request.header(&headers::TRANSPORT))
197 .and_then(|t| Transport::parse(t).ok())
198 .and_then(|t| t.first().cloned());
199 let Some(chosen) = chosen else {
200 let resp = self.build_response(StatusCode::UnsupportedTransport, cseq, |b| b);
201 return Ok((serialize(&Message::from(resp))?, events));
202 };
203 let transport = Transport::single(chosen);
205 let session_id = self
206 .session_id
207 .clone()
208 .unwrap_or_else(|| self.allocate_session());
209 self.session_id = Some(session_id.clone());
210 self.negotiated_transport = Some(transport.clone());
211
212 let sid = session_id.clone();
213 let mut session_hdr = crate::session_header::SessionHeader::new(sid.clone());
214 session_hdr.timeout = self.session_timeout.map(std::time::Duration::from_secs);
215 let session_hdr = session_hdr.to_header_value()?;
216 let transport_hdr = transport.to_header_value()?;
217 let resp = self.build_response(StatusCode::Ok, cseq, |b| {
218 b.header(headers::SESSION, session_hdr)
219 .header(headers::TRANSPORT, transport_hdr)
220 });
221 self.state = next_state;
222 events.push(ServerEvent::SessionSetup {
223 session_id,
224 transport,
225 });
226 events.push(ServerEvent::RequestAccepted {
227 method,
228 cseq,
229 state: self.state,
230 });
231 return Ok((serialize(&Message::from(resp))?, events));
232 }
233
234 let session_hdr = self.session_id.clone();
236 let resp = self.build_response(StatusCode::Ok, cseq, |mut b| {
237 if let Some(sid) = &session_hdr {
238 b = b.header(headers::SESSION, sid.clone());
239 }
240 b
241 });
242 self.state = next_state;
243 if method == Method::Teardown {
244 self.session_id = None;
245 self.negotiated_transport = None;
246 }
247 events.push(ServerEvent::RequestAccepted {
248 method,
249 cseq,
250 state: self.state,
251 });
252 Ok((serialize(&Message::from(resp))?, events))
253 }
254
255 fn allocate_session(&mut self) -> String {
256 let id = (self.session_ids)();
257 format!("{id:016x}")
258 }
259
260 fn session_header_matches(&self, method: &Method, header: Option<&str>) -> bool {
263 match (header, &self.session_id) {
264 (Some(value), Some(ours)) => {
265 let theirs = crate::session_header::SessionHeader::parse(value)
266 .map(|h| h.id)
267 .unwrap_or_default();
268 ids_equal(theirs.as_bytes(), ours.as_bytes())
269 }
270 (Some(_), None) => false,
271 (None, Some(_)) => !SESSION_SCOPED_METHODS.contains(method),
272 (None, None) => true,
273 }
274 }
275
276 fn build_response<F>(&self, status: StatusCode, cseq: u32, f: F) -> Response<Body>
279 where
280 F: FnOnce(rtsp_types::ResponseBuilder) -> rtsp_types::ResponseBuilder,
281 {
282 let builder = Response::builder(Version::V1_0, status)
283 .header(headers::CSEQ, cseq.to_string())
284 .header(headers::SERVER, self.server_header.clone());
285 f(builder).build(Vec::new())
286 }
287}
288
289fn ids_equal(a: &[u8], b: &[u8]) -> bool {
292 a.len() == b.len() && a.iter().zip(b).fold(0u8, |acc, (x, y)| acc | (x ^ y)) == 0
293}
294
295fn header_value(h: Option<&headers::HeaderValue>) -> Option<&str> {
296 h.map(|v| v.as_str())
297}
298
299fn serialize(message: &Message<Body>) -> Result<Vec<u8>> {
300 let mut out = Vec::new();
301 message
302 .write(&mut out)
303 .map_err(|e| Error::MessageWrite(e.to_string()))?;
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 setup_transitions_init_to_ready() {
325 let mut s = test_session();
326 let (resp, events) = s
327 .handle_request(&req(
328 "SETUP rtsp://h/s RTSP/1.0\nCSeq: 1\nTransport: RTP/AVP/TCP;interleaved=0-1\n\n",
329 ))
330 .unwrap();
331 assert_eq!(s.state(), SessionState::Ready);
332 let text = String::from_utf8_lossy(&resp);
333 assert!(text.contains("200"));
334 assert!(text.contains("Session:"));
335 assert!(text.contains("Transport:"));
336 assert!(
337 events
338 .iter()
339 .any(|e| matches!(e, ServerEvent::SessionSetup { .. }))
340 );
341 }
342
343 #[test]
344 fn play_in_init_returns_455() {
345 let mut s = test_session();
346 let (resp, events) = s
347 .handle_request(&req("PLAY rtsp://h/s RTSP/1.0\nCSeq: 1\n\n"))
348 .unwrap();
349 assert_eq!(s.state(), SessionState::Init);
350 assert!(String::from_utf8_lossy(&resp).contains("455"));
351 assert!(
352 events
353 .iter()
354 .any(|e| matches!(e, ServerEvent::MethodNotValid { .. }))
355 );
356 }
357
358 fn setup(s: &mut ServerSession, transport: &str) -> String {
359 let (resp, _) = s
360 .handle_request(&req(&format!(
361 "SETUP rtsp://h/s RTSP/1.0\nCSeq: 1\nTransport: {transport}\n\n"
362 )))
363 .unwrap();
364 String::from_utf8(resp).unwrap()
365 }
366
367 #[test]
368 fn session_ids_differ_across_sessions_and_carry_64_bits() {
369 let mut a = test_session();
370 let mut b = test_session();
371 setup(&mut a, "RTP/AVP/TCP;interleaved=0-1");
372 setup(&mut b, "RTP/AVP/TCP;interleaved=0-1");
373 let (a, b) = (a.session_id().unwrap(), b.session_id().unwrap());
374 assert_ne!(a, b, "two sessions must not share an id");
375 assert!(a.len() >= 16, "id {a} is shorter than 64 bits of hex");
376 }
377
378 #[test]
379 fn request_with_wrong_session_header_gets_454() {
380 let mut s = test_session();
381 setup(&mut s, "RTP/AVP/TCP;interleaved=0-1");
382 let (resp, _) = s
383 .handle_request(&req(
384 "PLAY rtsp://h/s RTSP/1.0\nCSeq: 2\nSession: not-this-one\n\n",
385 ))
386 .unwrap();
387 assert!(
388 String::from_utf8_lossy(&resp).starts_with("RTSP/1.0 454"),
389 "got: {}",
390 String::from_utf8_lossy(&resp)
391 );
392 assert_eq!(s.state(), SessionState::Ready);
393 let (resp, _) = s
395 .handle_request(&req("TEARDOWN rtsp://h/s RTSP/1.0\nCSeq: 3\n\n"))
396 .unwrap();
397 assert!(String::from_utf8_lossy(&resp).starts_with("RTSP/1.0 454"));
398 assert_eq!(s.state(), SessionState::Ready);
399 let sid = s.session_id().unwrap().to_string();
401 let (resp, _) = s
402 .handle_request(&req(&format!(
403 "PLAY rtsp://h/s RTSP/1.0\nCSeq: 4\nSession: {sid};timeout=60\n\n"
404 )))
405 .unwrap();
406 assert!(String::from_utf8_lossy(&resp).starts_with("RTSP/1.0 200"));
407 assert_eq!(s.state(), SessionState::Playing);
408 }
409
410 #[test]
411 fn setup_answers_exactly_one_transport() {
412 let mut s = test_session();
413 let text = setup(
414 &mut s,
415 "RTP/AVP;unicast;client_port=8000-8001,RTP/AVP/TCP;unicast;interleaved=0-1",
416 );
417 let line = text.lines().find(|l| l.starts_with("Transport:")).unwrap();
418 let answered = Transport::parse(line["Transport:".len()..].trim()).unwrap();
419 assert_eq!(answered.specs.len(), 1, "got: {line}");
420 assert_eq!(answered.first().unwrap().client_port, Some((8000, 8001)));
421 assert_eq!(s.negotiated_transport().unwrap().specs.len(), 1);
422 }
423
424 #[test]
425 fn unparseable_transport_gets_461() {
426 let mut s = test_session();
427 let text = setup(&mut s, "not a transport;;");
428 assert!(text.starts_with("RTSP/1.0 461"), "got: {text}");
429 assert_eq!(s.state(), SessionState::Init);
430 }
431}