use rtsp_types as rtsp; use std::io; use std::net::SocketAddr; use std::sync::Arc; use tokio::io::AsyncReadExt; use tokio::net::{TcpListener, TcpStream}; use tokio::sync::{broadcast, Mutex, RwLock}; use crate::config::RtspConfig; use crate::error::{AppError, Result}; use crate::utils::{bind_socket_addr, bind_tcp_listener}; use crate::video::VideoStreamManager; use super::auth::{extract_basic_auth, rtsp_auth_credentials}; use super::codec::rtsp_codec_to_video; use super::protocol::{ is_tcp_transport_request, is_valid_rtsp_path, parse_interleaved_channel, parse_rtsp_request, take_rtsp_request_from_buffer, OPTIONS_PUBLIC_CAPABILITIES, }; use super::response::{send_response, send_simple_response}; use super::sdp::build_sdp; use super::state::SharedRtspState; use super::streaming::{stream_video_interleaved, RTSP_BUF_SIZE}; use super::types::RtspConnectionState; pub use super::types::RtspServiceStatus; pub struct RtspService { config: Arc>, status: Arc>, video_manager: Arc, shutdown_tx: broadcast::Sender<()>, server_handle: Arc>>>, client_handles: Arc>>>, shared_state: SharedRtspState, } impl RtspService { pub fn new(config: RtspConfig, video_manager: Arc) -> Self { let (shutdown_tx, _) = broadcast::channel(1); Self { config: Arc::new(RwLock::new(config)), status: Arc::new(RwLock::new(RtspServiceStatus::Stopped)), video_manager, shutdown_tx, server_handle: Arc::new(Mutex::new(None)), client_handles: Arc::new(Mutex::new(Vec::new())), shared_state: SharedRtspState::new(), } } pub async fn start(&self) -> Result<()> { let config = self.config.read().await.clone(); if !config.enabled { *self.status.write().await = RtspServiceStatus::Stopped; return Ok(()); } if matches!(*self.status.read().await, RtspServiceStatus::Running) { return Ok(()); } *self.status.write().await = RtspServiceStatus::Starting; let codec = rtsp_codec_to_video(config.codec); if let Err(err) = self.video_manager.set_video_codec(codec).await { let message = format!("failed to set codec before RTSP start: {}", err); *self.status.write().await = RtspServiceStatus::Error(message.clone()); return Err(AppError::VideoError(message)); } if let Err(err) = self.video_manager.request_keyframe().await { tracing::debug!("Failed to request keyframe on RTSP start: {}", err); } let bind_addr = bind_socket_addr(&config.bind, config.port) .map_err(|e| AppError::BadRequest(format!("Invalid RTSP bind address: {}", e)))?; let listener = bind_tcp_listener(bind_addr).map_err(|e| { AppError::Io(io::Error::new(e.kind(), format!("RTSP bind failed: {}", e))) })?; let listener = TcpListener::from_std(listener).map_err(|e| { AppError::Io(io::Error::new( e.kind(), format!("RTSP listener setup failed: {}", e), )) })?; let service_config = self.config.clone(); let video_manager = self.video_manager.clone(); let shared_state = self.shared_state.clone(); let mut shutdown_rx = self.shutdown_tx.subscribe(); let status = self.status.clone(); let client_handles = self.client_handles.clone(); *self.status.write().await = RtspServiceStatus::Running; let handle = tokio::spawn(async move { tracing::info!("RTSP service listening on {}", bind_addr); loop { tokio::select! { _ = shutdown_rx.recv() => { tracing::info!("RTSP service shutdown signal received"); break; } result = listener.accept() => { match result { Ok((stream, addr)) => { let cfg = service_config.clone(); let vm = video_manager.clone(); let shared = shared_state.clone(); let handle = tokio::spawn(async move { if let Err(e) = handle_client(stream, addr, cfg, vm, shared).await { tracing::warn!("RTSP client {} ended with error: {}", addr, e); } }); let mut handles = client_handles.lock().await; handles.retain(|task| !task.is_finished()); handles.push(handle); } Err(e) => { tracing::warn!("RTSP accept failed: {}", e); } } } } } *status.write().await = RtspServiceStatus::Stopped; }); *self.server_handle.lock().await = Some(handle); Ok(()) } pub async fn stop(&self) -> Result<()> { let _ = self.shutdown_tx.send(()); if let Some(handle) = self.server_handle.lock().await.take() { handle.abort(); } let mut client_handles = self.client_handles.lock().await; for handle in client_handles.drain(..) { handle.abort(); } *self.shared_state.active_client.lock().await = None; *self.status.write().await = RtspServiceStatus::Stopped; Ok(()) } pub async fn restart(&self, config: RtspConfig) -> Result<()> { self.update_config(config).await; self.stop().await?; self.start().await } pub async fn update_config(&self, config: RtspConfig) { *self.config.write().await = config; } pub async fn config(&self) -> RtspConfig { self.config.read().await.clone() } pub async fn status(&self) -> RtspServiceStatus { self.status.read().await.clone() } } async fn handle_client( mut stream: TcpStream, peer: SocketAddr, config: Arc>, video_manager: Arc, shared: SharedRtspState, ) -> Result<()> { let cfg_snapshot = config.read().await.clone(); let expected_auth = rtsp_auth_credentials(&cfg_snapshot); if cfg_snapshot.allow_one_client { let mut active_guard = shared.active_client.lock().await; if let Some(active) = *active_guard { if active != peer { send_simple_response( &mut stream, 453, "Not Enough Bandwidth", None, "another client is active", ) .await?; return Ok(()); } } else { *active_guard = Some(peer); } } let mut state = RtspConnectionState::new(); let mut read_buf = [0u8; RTSP_BUF_SIZE]; let mut request_buffer = Vec::with_capacity(RTSP_BUF_SIZE); 'client_loop: loop { let n = stream.read(&mut read_buf).await?; if n == 0 { break; } request_buffer.extend_from_slice(&read_buf[..n]); while let Some(req_text) = take_rtsp_request_from_buffer(&mut request_buffer) { let req = match parse_rtsp_request(&req_text) { Some(r) => r, None => { send_simple_response(&mut stream, 400, "Bad Request", None, "").await?; continue; } }; if !is_valid_rtsp_path(&req.method, &req.uri, &cfg_snapshot.path) { send_response(&mut stream, &req, 404, "Not Found", vec![], "", "").await?; continue; } if let Some((expected_user, expected_pass)) = expected_auth.as_ref() { let ok = extract_basic_auth(&req) .map(|(u, p)| u == expected_user.as_str() && p == expected_pass.as_str()) .unwrap_or(false); if !ok { send_response( &mut stream, &req, 401, "Unauthorized", vec![( "WWW-Authenticate".to_string(), "Basic realm=\"One-KVM RTSP\"".to_string(), )], "", "", ) .await?; continue; } } match &req.method { rtsp::Method::Options => { send_response( &mut stream, &req, 200, "OK", vec![( "Public".to_string(), OPTIONS_PUBLIC_CAPABILITIES.to_string(), )], "", "", ) .await?; } rtsp::Method::Describe => { let codec = rtsp_codec_to_video(cfg_snapshot.codec.clone()); let params = shared.parameter_sets.read().await.clone(); let sdp = build_sdp(&cfg_snapshot, codec, ¶ms); if sdp.is_empty() { send_response( &mut stream, &req, 500, "Internal Server Error", vec![], "", &state.session_id, ) .await?; continue; } send_response( &mut stream, &req, 200, "OK", vec![("Content-Type".to_string(), "application/sdp".to_string())], &sdp, &state.session_id, ) .await?; } rtsp::Method::Setup => { let transport = req.headers.get("transport").cloned().unwrap_or_default(); if !is_tcp_transport_request(&transport) { send_response( &mut stream, &req, 461, "Unsupported Transport", vec![( "Transport".to_string(), "RTP/AVP/TCP;unicast;interleaved=0-1".to_string(), )], "", &state.session_id, ) .await?; continue; } let interleaved = parse_interleaved_channel(&transport).unwrap_or(0); state.setup_done = true; state.interleaved_channel = interleaved; let transport_resp = format!( "RTP/AVP/TCP;unicast;interleaved={}-{}", interleaved, interleaved.saturating_add(1) ); send_response( &mut stream, &req, 200, "OK", vec![("Transport".to_string(), transport_resp)], "", &state.session_id, ) .await?; } rtsp::Method::Play => { if !state.setup_done { send_response( &mut stream, &req, 455, "Method Not Valid in This State", vec![], "", &state.session_id, ) .await?; continue; } send_response(&mut stream, &req, 200, "OK", vec![], "", &state.session_id) .await?; if let Err(e) = stream_video_interleaved( stream, &video_manager, cfg_snapshot.codec.clone(), state.interleaved_channel, shared.clone(), state.session_id.clone(), ) .await { tracing::warn!("RTSP stream loop ended for {}: {}", peer, e); } break 'client_loop; } rtsp::Method::Teardown => { send_response(&mut stream, &req, 200, "OK", vec![], "", &state.session_id) .await?; break 'client_loop; } _ => { send_response( &mut stream, &req, 405, "Method Not Allowed", vec![], "", &state.session_id, ) .await?; } } } } if cfg_snapshot.allow_one_client { let mut active_guard = shared.active_client.lock().await; if active_guard.as_ref().copied() == Some(peer) { *active_guard = None; } } Ok(()) }