1pub mod whep;
10pub mod whip;
11
12mod mux;
13
14use std::collections::HashMap;
15use std::net::SocketAddr;
16use std::sync::{Arc, Mutex};
17use std::time::Duration;
18
19use axum::Router;
20use axum::http::{HeaderValue, StatusCode, Uri};
21use tokio::sync::{OnceCell, oneshot};
22
23use crate::{Error, Result};
24use mux::Mux;
25
26pub struct Response {
30 pub resource_id: String,
32 pub answer: String,
34 session: AcceptedSession,
35}
36
37impl Response {
38 pub async fn run(self) -> Result<()> {
40 self.session.run().await
41 }
42}
43
44impl std::fmt::Debug for Response {
45 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46 f.debug_struct("Response")
47 .field("resource_id", &self.resource_id)
48 .field("answer", &self.answer)
49 .finish_non_exhaustive()
50 }
51}
52
53struct AcceptedSession {
58 server: Server,
59 resource_id: String,
60 session: Option<crate::session::Session>,
61 registration: Option<mux::Registration>,
62 cancel: Option<oneshot::Receiver<()>>,
63 role: &'static str,
64 broadcast: Option<moq_net::broadcast::Producer>,
67}
68
69impl AcceptedSession {
70 async fn run(mut self) -> Result<()> {
71 let session = self.session.take().expect("accepted session missing driver");
72 let registration = self
73 .registration
74 .take()
75 .expect("accepted session missing mux registration");
76 let cancel = self.cancel.take().expect("accepted session missing cancel receiver");
77
78 let result = {
79 let _registration = registration;
82 tokio::select! {
83 res = session.run() => {
84 crate::session::log_session_end(self.role, &res);
85 res
86 }
87 _ = cancel => {
88 tracing::debug!(role = self.role, "webrtc session terminated by DELETE");
89 if let Some(broadcast) = self.broadcast.take() {
92 broadcast.finish();
93 }
94 Ok(())
95 }
96 }
97 };
98 normalize_session_result(result)
99 }
100}
101
102impl Drop for AcceptedSession {
103 fn drop(&mut self) {
104 self.server.unregister_session(&self.resource_id);
105 }
106}
107
108fn normalize_session_result(result: Result<()>) -> Result<()> {
111 match result {
112 Ok(()) | Err(Error::SessionClosed) => Ok(()),
113 Err(err) => Err(err),
114 }
115}
116
117pub(crate) fn session_location(uri: &Uri, resource_id: &str) -> Option<HeaderValue> {
121 let base = uri.path().trim_end_matches('/');
122 let path = if base.is_empty() {
123 format!("/{resource_id}")
124 } else {
125 format!("{base}/{resource_id}")
126 };
127 HeaderValue::from_str(&path).ok()
128}
129
130#[derive(Clone, Debug)]
132#[non_exhaustive]
133pub struct Config {
134 pub ice_candidates: Vec<SocketAddr>,
142
143 pub udp_bind: SocketAddr,
148
149 pub max_age: Option<Duration>,
160
161 pub bandwidth: moq_net::bandwidth::Allocator,
164}
165
166impl Default for Config {
167 fn default() -> Self {
168 Self {
169 ice_candidates: Vec::new(),
170 udp_bind: SocketAddr::from(([0, 0, 0, 0], 0)),
171 max_age: None,
172 bandwidth: moq_net::bandwidth::Allocator::unlimited(),
173 }
174 }
175}
176
177#[derive(Clone)]
179pub struct Server {
180 inner: Arc<Inner>,
181}
182
183struct Inner {
184 config: Config,
185 mux: OnceCell<Mux>,
188 sessions: Mutex<HashMap<String, oneshot::Sender<()>>>,
192}
193
194impl Server {
195 pub fn new(config: Config) -> Self {
197 Self {
198 inner: Arc::new(Inner {
199 config,
200 mux: OnceCell::new(),
201 sessions: Mutex::new(HashMap::new()),
202 }),
203 }
204 }
205
206 pub(crate) async fn mux(&self) -> Result<&Mux> {
208 self.inner
209 .mux
210 .get_or_try_init(|| Mux::bind(self.inner.config.udp_bind, &self.inner.config.ice_candidates))
211 .await
212 }
213
214 pub fn publish_router(&self, publisher: moq_net::origin::Producer) -> Router {
222 whip::router(self.clone(), publisher)
223 }
224
225 pub fn subscribe_router(&self, subscriber: moq_net::origin::Consumer) -> Router {
233 whep::router(self.clone(), subscriber)
234 }
235
236 pub(crate) fn config(&self) -> &Config {
237 &self.inner.config
238 }
239
240 pub(crate) fn register_session(&self, resource_id: String) -> oneshot::Receiver<()> {
244 let (tx, rx) = oneshot::channel();
245 self.inner.sessions.lock().unwrap().insert(resource_id, tx);
246 rx
247 }
248
249 pub(crate) fn unregister_session(&self, resource_id: &str) {
251 self.inner.sessions.lock().unwrap().remove(resource_id);
252 }
253
254 pub fn terminate(&self, resource_id: &str) -> bool {
261 if let Some(cancel) = self.inner.sessions.lock().unwrap().remove(resource_id) {
262 let _ = cancel.send(());
263 true
264 } else {
265 false
266 }
267 }
268}
269
270pub(crate) fn delete(server: &Server, path: &str) -> StatusCode {
273 match crate::sdp::parse_resource_id(path) {
274 Ok(id) if server.terminate(&id.to_string()) => StatusCode::OK,
275 Ok(_) => StatusCode::NOT_FOUND,
276 Err(_) => StatusCode::BAD_REQUEST,
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283
284 fn server() -> Server {
285 Server::new(Config::default())
286 }
287
288 #[test]
289 fn terminate_unknown_session_is_false() {
290 assert!(!server().terminate("00000000-0000-0000-0000-000000000000"));
291 }
292
293 #[test]
294 fn terminate_registered_session_once() {
295 let server = server();
296 let id = "11111111-1111-1111-1111-111111111111";
297 let _cancel = server.register_session(id.to_string());
298 assert!(server.terminate(id), "first terminate finds the session");
299 assert!(!server.terminate(id), "second terminate is a no-op");
300 }
301
302 #[test]
303 fn unregister_drops_the_entry() {
304 let server = server();
305 let id = "22222222-2222-2222-2222-222222222222";
306 let _cancel = server.register_session(id.to_string());
307 server.unregister_session(id);
308 assert!(!server.terminate(id), "unregistered session can't be terminated");
309 }
310
311 #[test]
312 fn peer_close_is_a_successful_session_result() {
313 assert!(normalize_session_result(Err(Error::SessionClosed)).is_ok());
314 }
315
316 #[test]
317 fn session_location_preserves_mount_path() {
318 let uri: Uri = "/whip/live/cam0?token=secret".parse().unwrap();
319 let location = session_location(&uri, "session-id").expect("header value");
320 assert_eq!(location, "/whip/live/cam0/session-id");
321 }
322}