1pub mod whep;
10pub mod whip;
11
12mod mux;
13
14use std::collections::HashMap;
15use std::net::SocketAddr;
16use std::sync::{Arc, Mutex};
17
18use axum::Router;
19use axum::extract::{Path, State};
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(mut 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
150impl Default for Config {
151 fn default() -> Self {
152 Self {
153 ice_candidates: Vec::new(),
154 udp_bind: SocketAddr::from(([0, 0, 0, 0], 0)),
155 }
156 }
157}
158
159#[derive(Clone)]
165pub struct Server {
166 inner: Arc<Inner>,
167}
168
169struct Inner {
170 config: Config,
171 publisher: moq_net::origin::Producer,
172 subscriber: moq_net::origin::Consumer,
174 mux: OnceCell<Mux>,
177 sessions: Mutex<HashMap<String, oneshot::Sender<()>>>,
181}
182
183impl Server {
184 pub fn new(config: Config, publisher: moq_net::origin::Producer, subscriber: moq_net::origin::Consumer) -> Self {
187 Self {
188 inner: Arc::new(Inner {
189 config,
190 publisher,
191 subscriber,
192 mux: OnceCell::new(),
193 sessions: Mutex::new(HashMap::new()),
194 }),
195 }
196 }
197
198 pub(crate) async fn mux(&self) -> Result<&Mux> {
200 self.inner
201 .mux
202 .get_or_try_init(|| Mux::bind(self.inner.config.udp_bind, &self.inner.config.ice_candidates))
203 .await
204 }
205
206 pub fn publish_router(&self) -> Router {
214 whip::router(self.clone())
215 }
216
217 pub fn subscribe_router(&self) -> Router {
225 whep::router(self.clone())
226 }
227
228 pub(crate) fn publisher(&self) -> &moq_net::origin::Producer {
229 &self.inner.publisher
230 }
231
232 pub(crate) fn subscriber(&self) -> &moq_net::origin::Consumer {
233 &self.inner.subscriber
234 }
235
236 pub(crate) fn register_session(&self, resource_id: String) -> oneshot::Receiver<()> {
240 let (tx, rx) = oneshot::channel();
241 self.inner.sessions.lock().unwrap().insert(resource_id, tx);
242 rx
243 }
244
245 pub(crate) fn unregister_session(&self, resource_id: &str) {
247 self.inner.sessions.lock().unwrap().remove(resource_id);
248 }
249
250 pub fn terminate(&self, resource_id: &str) -> bool {
257 if let Some(cancel) = self.inner.sessions.lock().unwrap().remove(resource_id) {
258 let _ = cancel.send(());
259 true
260 } else {
261 false
262 }
263 }
264}
265
266pub(crate) async fn delete(State(server): State<Server>, Path(path): Path<String>) -> StatusCode {
269 match crate::sdp::parse_resource_id(&path) {
270 Ok(id) if server.terminate(&id.to_string()) => StatusCode::OK,
271 Ok(_) => StatusCode::NOT_FOUND,
272 Err(_) => StatusCode::BAD_REQUEST,
273 }
274}
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279
280 fn server() -> Server {
281 let publisher = moq_net::Origin::random().produce();
282 let subscriber = moq_net::Origin::random().produce().consume();
283 Server::new(Config::default(), publisher, subscriber)
284 }
285
286 #[test]
287 fn terminate_unknown_session_is_false() {
288 assert!(!server().terminate("00000000-0000-0000-0000-000000000000"));
289 }
290
291 #[test]
292 fn terminate_registered_session_once() {
293 let server = server();
294 let id = "11111111-1111-1111-1111-111111111111";
295 let _cancel = server.register_session(id.to_string());
296 assert!(server.terminate(id), "first terminate finds the session");
297 assert!(!server.terminate(id), "second terminate is a no-op");
298 }
299
300 #[test]
301 fn unregister_drops_the_entry() {
302 let server = server();
303 let id = "22222222-2222-2222-2222-222222222222";
304 let _cancel = server.register_session(id.to_string());
305 server.unregister_session(id);
306 assert!(!server.terminate(id), "unregistered session can't be terminated");
307 }
308
309 #[test]
310 fn peer_close_is_a_successful_session_result() {
311 assert!(normalize_session_result(Err(Error::SessionClosed)).is_ok());
312 }
313
314 #[test]
315 fn session_location_preserves_mount_path() {
316 let uri: Uri = "/whip/live/cam0?token=secret".parse().unwrap();
317 let location = session_location(&uri, "session-id").expect("header value");
318 assert_eq!(location, "/whip/live/cam0/session-id");
319 }
320}