1use std::collections::{HashMap, HashSet};
7use std::net::SocketAddr;
8use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
9use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
10use std::time::Duration;
11
12use bytes::Bytes;
13use sipx_sip::build::ResponseBuilder;
14use sipx_sip::headers::{CSeq, Expires};
15use sipx_sip::{HeaderName, Method, Request, Response, StatusCode};
16use sipx_transport::{Handle, Incoming, Target, TransportKind};
17use sipx_ua::auth::new_cnonce;
18use sipx_ua::presence::{Compositor, PIDF_TYPE, Publish, Published};
19use sipx_ua::publication_client::{
20 CommandError, Config as PublisherConfig, Output, Publisher, Start, StartError, StateChange,
21 Timer,
22};
23use thiserror::Error;
24use tokio::sync::{mpsc, oneshot, watch};
25use tokio::task::JoinHandle;
26use tokio::time::Instant;
27use tokio_util::sync::CancellationToken;
28
29use crate::dispatch::with_to_tag;
30
31const DRIVER_QUEUE: usize = 32;
32const RETRY_AFTER: Duration = Duration::from_secs(1);
33
34#[derive(Debug, Clone)]
36pub struct PublicationConfig {
37 pub minimum_expiry: Duration,
39 pub default_expiry: Duration,
41 pub capacity: usize,
43 pub body_limit: usize,
45 pub publisher: PublisherConfig,
47}
48
49impl Default for PublicationConfig {
50 fn default() -> Self {
51 Self {
52 minimum_expiry: Duration::from_secs(60),
53 default_expiry: Duration::from_secs(3_600),
54 capacity: 1_024,
55 body_limit: 65_536,
56 publisher: PublisherConfig::default(),
57 }
58 }
59}
60
61impl PublicationConfig {
62 fn validate(&self) -> Result<(), PublicationError> {
63 self.publisher.validate()?;
64 if self.minimum_expiry.is_zero()
65 || self.default_expiry < self.minimum_expiry
66 || self.default_expiry.as_secs() > u64::from(u32::MAX)
67 || self.capacity == 0
68 || self.body_limit == 0
69 {
70 return Err(PublicationError::InvalidConfiguration);
71 }
72 Ok(())
73 }
74}
75
76pub trait PublicationAuthorization: Send + Sync + 'static {
78 fn authorize(&self, request: &Request, source: SocketAddr, transport: TransportKind) -> bool;
80}
81
82pub trait PublicationComposition: Send + Sync + 'static {
84 fn apply(
86 &self,
87 compositor: &mut Compositor,
88 entity: &str,
89 publication: Publish,
90 now: u64,
91 ) -> Published;
92}
93
94#[derive(Debug, Default)]
96pub struct ReplacePublicationState;
97
98impl PublicationComposition for ReplacePublicationState {
99 fn apply(
100 &self,
101 compositor: &mut Compositor,
102 entity: &str,
103 publication: Publish,
104 now: u64,
105 ) -> Published {
106 compositor.apply(entity, publication, now)
107 }
108}
109
110#[derive(Debug, Default)]
112pub struct AllowPublications;
113
114impl PublicationAuthorization for AllowPublications {
115 fn authorize(
116 &self,
117 _request: &Request,
118 _source: SocketAddr,
119 _transport: TransportKind,
120 ) -> bool {
121 true
122 }
123}
124
125#[derive(Debug, Clone, Copy, PartialEq, Eq)]
127pub struct PublicationCounts {
128 pub active_tasks: usize,
130 pub active_timers: usize,
132 pub active_transactions: usize,
134 pub active_publishers: usize,
136 pub active_publications: usize,
138 pub shed: u64,
140}
141
142#[derive(Debug, Default)]
143struct Counters {
144 tasks: AtomicUsize,
145 timers: AtomicUsize,
146 transactions: AtomicUsize,
147 publishers: AtomicUsize,
148 shed: AtomicU64,
149}
150
151#[derive(Debug, Error)]
153#[non_exhaustive]
154pub enum PublicationError {
155 #[error("publications are not attached to a dispatcher")]
157 NotAttached,
158 #[error("publication shutdown has closed admission")]
160 ShuttingDown,
161 #[error("invalid publication configuration")]
163 InvalidConfiguration,
164 #[error("publisher capacity exceeded")]
166 CapacityExceeded,
167 #[error("a publisher already owns this resource")]
169 DuplicateResource,
170 #[error(transparent)]
172 Command(#[from] CommandError),
173 #[error(transparent)]
175 Start(#[from] StartError),
176}
177
178#[derive(Debug)]
179struct Shared {
180 endpoint: Mutex<Option<Handle>>,
181 resources: Mutex<HashSet<Vec<u8>>>,
182 drivers: Mutex<HashMap<Vec<u8>, JoinHandle<()>>>,
183 config: PublisherConfig,
184 counters: Arc<Counters>,
185 shutdown: CancellationToken,
186}
187
188#[derive(Debug)]
190pub struct Publication {
191 commands: mpsc::Sender<Command>,
192 states: watch::Receiver<Option<StateChange>>,
193 body_limit: usize,
194}
195
196impl Publication {
197 pub async fn next_state(&mut self) -> Option<StateChange> {
199 self.states.changed().await.ok()?;
200 self.states.borrow_and_update().clone()
201 }
202
203 pub async fn modify(
205 &self,
206 body: Bytes,
207 content_type: impl Into<String>,
208 ) -> Result<(), PublicationError> {
209 if body.is_empty() || body.len() > self.body_limit {
210 return Err(CommandError::InvalidBody.into());
211 }
212 let (reply, result) = oneshot::channel();
213 self.commands
214 .send(Command::Modify(body, content_type.into(), reply))
215 .await
216 .map_err(|_| PublicationError::NotAttached)?;
217 result.await.map_err(|_| PublicationError::NotAttached)??;
218 Ok(())
219 }
220
221 pub async fn remove(&self) -> Result<(), PublicationError> {
223 let (reply, result) = oneshot::channel();
224 self.commands
225 .send(Command::Remove(reply))
226 .await
227 .map_err(|_| PublicationError::NotAttached)?;
228 result.await.map_err(|_| PublicationError::NotAttached)??;
229 Ok(())
230 }
231}
232
233impl Drop for Publication {
234 fn drop(&mut self) {
235 let (reply, _) = oneshot::channel();
236 let _ = self.commands.try_send(Command::Remove(reply));
238 }
239}
240
241#[derive(Debug, Clone)]
243pub struct PublicationsHandle {
244 shared: Arc<Shared>,
245 compositor: Arc<Mutex<Compositor>>,
246}
247
248impl PublicationsHandle {
249 pub fn publish(&self, start: Start) -> Result<Publication, PublicationError> {
251 let resource = start.resource.to_bytes().to_vec();
252 let mut drivers = lock(&self.shared.drivers);
253 drivers.retain(|_, task| !task.is_finished());
254 if self.shared.shutdown.is_cancelled() {
255 return Err(PublicationError::ShuttingDown);
256 }
257 if drivers.contains_key(&resource) {
258 return Err(PublicationError::DuplicateResource);
259 }
260 let endpoint = lock(&self.shared.endpoint)
261 .clone()
262 .ok_or(PublicationError::NotAttached)?;
263 reserve(&self.shared)?;
264 let mut resources = lock(&self.shared.resources);
265 if resources.contains(&resource) {
266 release(&self.shared.counters);
267 return Err(PublicationError::DuplicateResource);
268 }
269 let (publisher, initial) = match Publisher::start(self.shared.config.clone(), start) {
270 Ok(started) => started,
271 Err(error) => {
272 release(&self.shared.counters);
273 return Err(error.into());
274 }
275 };
276 resources.insert(resource.clone());
277 drop(resources);
278 let (commands, command_rx) = mpsc::channel(DRIVER_QUEUE);
279 let (states, state_rx) = watch::channel(None);
280 let driver = Driver {
281 endpoint,
282 publisher,
283 resource: resource.clone(),
284 commands: command_rx,
285 states,
286 events: None,
287 response: None,
288 timers: HashMap::new(),
289 shared: Arc::clone(&self.shared),
290 };
291 let task = tokio::spawn(driver.run(initial));
292 drivers.insert(resource, task);
293 drop(drivers);
294 Ok(Publication {
295 commands,
296 states: state_rx,
297 body_limit: self.shared.config.body_limit,
298 })
299 }
300
301 #[must_use]
303 pub fn compositor(&self) -> Arc<Mutex<Compositor>> {
304 Arc::clone(&self.compositor)
305 }
306
307 #[must_use]
309 pub fn counts(&self) -> PublicationCounts {
310 PublicationCounts {
311 active_tasks: self.shared.counters.tasks.load(Ordering::Relaxed),
312 active_timers: self.shared.counters.timers.load(Ordering::Relaxed),
313 active_transactions: self.shared.counters.transactions.load(Ordering::Relaxed),
314 active_publishers: self.shared.counters.publishers.load(Ordering::Relaxed),
315 active_publications: lock(&self.compositor).len(),
316 shed: self.shared.counters.shed.load(Ordering::Relaxed),
317 }
318 }
319}
320
321pub struct Publications {
323 endpoint: Option<Handle>,
324 compositor: Arc<Mutex<Compositor>>,
325 composition: Arc<dyn PublicationComposition>,
326 authorization: Arc<dyn PublicationAuthorization>,
327 config: PublicationConfig,
328 expiry_tasks: HashMap<String, JoinHandle<()>>,
329 origin: Instant,
330 shared: Arc<Shared>,
331}
332
333impl std::fmt::Debug for Publications {
334 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
335 formatter
336 .debug_struct("Publications")
337 .field("config", &self.config)
338 .field("inbound_tasks", &self.expiry_tasks.len())
339 .finish_non_exhaustive()
340 }
341}
342
343impl Publications {
344 pub fn new(
346 config: PublicationConfig,
347 compositor: Compositor,
348 composition: Arc<dyn PublicationComposition>,
349 authorization: Arc<dyn PublicationAuthorization>,
350 ) -> Result<Self, PublicationError> {
351 config.validate()?;
352 let compositor = Arc::new(Mutex::new(compositor));
353 let counters = Arc::new(Counters::default());
354 Ok(Self {
355 endpoint: None,
356 compositor: Arc::clone(&compositor),
357 composition,
358 authorization,
359 config: config.clone(),
360 expiry_tasks: HashMap::new(),
361 origin: Instant::now(),
362 shared: Arc::new(Shared {
363 endpoint: Mutex::new(None),
364 resources: Mutex::new(HashSet::new()),
365 drivers: Mutex::new(HashMap::new()),
366 config: config.publisher,
367 counters,
368 shutdown: CancellationToken::new(),
369 }),
370 })
371 }
372
373 #[must_use]
375 pub fn handle(&self) -> PublicationsHandle {
376 PublicationsHandle {
377 shared: Arc::clone(&self.shared),
378 compositor: Arc::clone(&self.compositor),
379 }
380 }
381
382 pub(crate) fn attach(&mut self, endpoint: Handle) {
383 self.endpoint = Some(endpoint.clone());
384 *lock(&self.shared.endpoint) = Some(endpoint);
385 }
386
387 #[allow(
389 clippy::too_many_lines,
390 reason = "the fail-closed inbound decision table stays in one visible protocol order"
391 )]
392 pub(crate) async fn receive(&mut self, incoming: &Incoming) {
393 self.expiry_tasks.retain(|_, task| !task.is_finished());
394 let Some(endpoint) = self.endpoint.clone() else {
395 return;
396 };
397 if !valid_request(&incoming.request) {
398 answer(&endpoint, incoming, 400, "Bad Request", None, None, None).await;
399 return;
400 }
401 if !self
402 .authorization
403 .authorize(&incoming.request, incoming.source, incoming.transport)
404 {
405 answer(&endpoint, incoming, 403, "Forbidden", None, None, None).await;
406 return;
407 }
408 if event(&incoming.request).as_deref() != Some("presence") {
409 answer(
410 &endpoint,
411 incoming,
412 489,
413 "Bad Event",
414 Some((HeaderName::AllowEvents, Bytes::from_static(b"presence"))),
415 None,
416 None,
417 )
418 .await;
419 return;
420 }
421 let Ok(tag) = conditional_tag(&incoming.request) else {
422 answer(&endpoint, incoming, 400, "Bad Request", None, None, None).await;
423 return;
424 };
425 let Some(expires) = requested_expiry(&incoming.request, self.config.default_expiry) else {
426 answer(&endpoint, incoming, 400, "Bad Request", None, None, None).await;
427 return;
428 };
429 if !expires.is_zero() && expires < self.config.minimum_expiry {
430 answer(
431 &endpoint,
432 incoming,
433 423,
434 "Interval Too Brief",
435 Some((
436 HeaderName::MinExpires,
437 Bytes::from(self.config.minimum_expiry.as_secs().to_string()),
438 )),
439 None,
440 None,
441 )
442 .await;
443 return;
444 }
445 if incoming.request.body().len() > self.config.body_limit {
446 answer(
447 &endpoint,
448 incoming,
449 413,
450 "Content Too Large",
451 None,
452 None,
453 None,
454 )
455 .await;
456 return;
457 }
458 let body = if incoming.request.body().is_empty() {
459 None
460 } else {
461 if incoming.request.headers.count(&HeaderName::ContentType) != 1
462 || incoming
463 .request
464 .headers
465 .value(&HeaderName::ContentType)
466 .as_deref()
467 != Some(PIDF_TYPE.as_bytes())
468 {
469 answer(
470 &endpoint,
471 incoming,
472 415,
473 "Unsupported Media Type",
474 None,
475 None,
476 None,
477 )
478 .await;
479 return;
480 }
481 let Ok(body) = std::str::from_utf8(incoming.request.body()) else {
482 answer(&endpoint, incoming, 400, "Bad Request", None, None, None).await;
483 return;
484 };
485 Some(body.to_owned())
486 };
487
488 let entity = String::from_utf8_lossy(&incoming.request.uri.to_bytes()).into_owned();
489 let now = self.origin.elapsed().as_secs();
490 let publication = Publish::read(tag, body, expires);
491 let outcome = {
492 let mut compositor = lock(&self.compositor);
493 compositor.expire(now);
494 let is_new = matches!(publication, Publish::Initial { .. })
495 && compositor.document(&entity).is_none();
496 if is_new && compositor.len() >= self.config.capacity {
497 None
498 } else {
499 Some(
500 self.composition
501 .apply(&mut compositor, &entity, publication, now),
502 )
503 }
504 };
505 let Some(outcome) = outcome else {
506 self.shared.counters.shed.fetch_add(1, Ordering::Relaxed);
507 answer(
508 &endpoint,
509 incoming,
510 503,
511 "Service Unavailable",
512 Some((
513 HeaderName::RetryAfter,
514 Bytes::from(RETRY_AFTER.as_secs().to_string()),
515 )),
516 None,
517 None,
518 )
519 .await;
520 return;
521 };
522 match outcome {
523 Published::Accepted { tag, expires } => {
524 answer(
525 &endpoint,
526 incoming,
527 200,
528 "OK",
529 None,
530 Some((&tag, expires)),
531 None,
532 )
533 .await;
534 self.arm_expiry(entity, expires).await;
535 }
536 Published::Removed { tag } => {
537 answer(
538 &endpoint,
539 incoming,
540 200,
541 "OK",
542 None,
543 Some((&tag, Duration::ZERO)),
544 None,
545 )
546 .await;
547 if let Some(task) = self.expiry_tasks.remove(&entity) {
548 abort_and_join(task).await;
549 }
550 }
551 Published::ConditionFailed => {
552 answer(
553 &endpoint,
554 incoming,
555 412,
556 "Conditional Request Failed",
557 None,
558 None,
559 None,
560 )
561 .await;
562 }
563 Published::Invalid => {
564 answer(&endpoint, incoming, 400, "Bad Request", None, None, None).await;
565 }
566 Published::Unavailable => {
567 answer(
568 &endpoint,
569 incoming,
570 500,
571 "Server Internal Error",
572 None,
573 None,
574 None,
575 )
576 .await;
577 }
578 }
579 }
580
581 async fn arm_expiry(&mut self, entity: String, expires: Duration) {
582 if let Some(previous) = self.expiry_tasks.remove(&entity) {
583 abort_and_join(previous).await;
584 }
585 let compositor = Arc::clone(&self.compositor);
586 let counters = Arc::clone(&self.shared.counters);
587 let origin = self.origin;
588 self.expiry_tasks.insert(
589 entity,
590 tokio::spawn(async move {
591 let _guard = WorkGuard::timer(counters);
592 tokio::time::sleep(expires).await;
594 lock(&compositor).expire(origin.elapsed().as_secs());
595 }),
596 );
597 }
598
599 pub(crate) async fn shutdown(&mut self) {
601 let drivers: Vec<_> = {
602 let mut drivers = lock(&self.shared.drivers);
603 self.shared.shutdown.cancel();
604 drivers.drain().map(|(_, task)| task).collect()
605 };
606 let expiry_tasks: Vec<_> = self.expiry_tasks.drain().map(|(_, task)| task).collect();
607 for task in expiry_tasks {
608 abort_and_join(task).await;
609 }
610 for task in drivers {
611 if let Err(error) = task.await {
612 tracing::warn!(%error, "publication driver did not join cleanly");
613 }
614 }
615 }
616}
617
618impl Drop for Publications {
619 fn drop(&mut self) {
620 let _drivers = lock(&self.shared.drivers);
621 self.shared.shutdown.cancel();
622 for task in self.expiry_tasks.values() {
623 task.abort();
624 }
625 }
626}
627
628#[derive(Debug)]
629enum Command {
630 Modify(Bytes, String, oneshot::Sender<Result<(), CommandError>>),
631 Remove(oneshot::Sender<Result<(), CommandError>>),
632}
633
634#[derive(Debug)]
635enum RuntimeEvent {
636 Response(Option<Response>),
637 Timer(Timer, u64),
638}
639
640struct Driver {
641 endpoint: Handle,
642 publisher: Publisher,
643 resource: Vec<u8>,
644 commands: mpsc::Receiver<Command>,
645 states: watch::Sender<Option<StateChange>>,
646 events: Option<(mpsc::Sender<RuntimeEvent>, mpsc::Receiver<RuntimeEvent>)>,
647 response: Option<JoinHandle<()>>,
648 timers: HashMap<Timer, JoinHandle<()>>,
649 shared: Arc<Shared>,
650}
651
652impl Driver {
653 async fn run(mut self, initial: Vec<Output>) {
654 let _guard = WorkGuard::publisher(Arc::clone(&self.shared.counters));
655 let (events, event_rx) = mpsc::channel(DRIVER_QUEUE);
656 self.events = Some((events, event_rx));
657 self.apply(initial).await;
658 while self.publisher.is_active() {
659 let input = {
660 let Some((_, events)) = self.events.as_mut() else {
661 break;
662 };
663 tokio::select! {
664 biased;
665 () = self.shared.shutdown.cancelled() => DriverInput::Shutdown,
666 command = self.commands.recv() => DriverInput::Command(command),
667 event = events.recv() => DriverInput::Event(event),
668 }
669 };
670 let outputs = match input {
671 DriverInput::Command(Some(Command::Modify(body, content_type, reply))) => {
672 command_result(self.publisher.modify(body, content_type), reply)
673 }
674 DriverInput::Command(Some(Command::Remove(reply))) => {
675 command_result(self.publisher.remove(), reply)
676 }
677 DriverInput::Event(Some(RuntimeEvent::Response(response))) => {
678 self.publisher.response(response.as_ref(), &new_cnonce())
679 }
680 DriverInput::Event(Some(RuntimeEvent::Timer(timer, generation))) => {
681 self.publisher.timer_fired(timer, generation)
682 }
683 DriverInput::Shutdown | DriverInput::Command(None) | DriverInput::Event(None) => {
684 self.publisher.shutdown_deadline()
685 }
686 };
687 self.apply(outputs).await;
688 }
689 if let Some(response) = self.response.take() {
690 abort_and_join(response).await;
691 }
692 for (_, timer) in self.timers.drain() {
693 abort_and_join(timer).await;
694 }
695 lock(&self.shared.resources).remove(&self.resource);
696 }
697
698 async fn apply(&mut self, outputs: Vec<Output>) {
699 for output in outputs {
700 match output {
701 Output::SendPublish { request, target } => self.send(*request, target).await,
702 Output::ArmTimer {
703 timer,
704 generation,
705 after,
706 } => self.arm(timer, generation, after).await,
707 Output::CancelTimer { timer, .. } => {
708 if let Some(task) = self.timers.remove(&timer) {
709 abort_and_join(task).await;
710 }
711 }
712 Output::StateChanged(change) => {
713 let _ = self.states.send(Some(change));
715 }
716 }
717 }
718 }
719
720 async fn send(&mut self, request: Request, peer: sipx_ua::event_client::Peer) {
721 if let Some(previous) = self.response.take() {
722 abort_and_join(previous).await;
723 }
724 let Some((events, _)) = self.events.as_ref() else {
725 return;
726 };
727 match self.endpoint.send(request, transport_target(peer)).await {
728 Ok(mut responses) => {
729 let events = events.clone();
730 let counters = Arc::clone(&self.shared.counters);
731 self.response = Some(tokio::spawn(async move {
732 let _guard = WorkGuard::transaction(counters);
733 let response = responses.final_response().await;
734 let _ = events.send(RuntimeEvent::Response(response)).await;
736 }));
737 }
738 Err(error) => {
739 tracing::warn!(%error, "could not send PUBLISH");
740 let _ = events.try_send(RuntimeEvent::Response(None));
742 }
743 }
744 }
745
746 async fn arm(&mut self, timer: Timer, generation: u64, after: Duration) {
747 if let Some(previous) = self.timers.remove(&timer) {
748 abort_and_join(previous).await;
749 }
750 let Some((events, _)) = self.events.as_ref() else {
751 return;
752 };
753 let events = events.clone();
754 let counters = Arc::clone(&self.shared.counters);
755 self.timers.insert(
756 timer,
757 tokio::spawn(async move {
758 let _guard = WorkGuard::timer(counters);
759 tokio::time::sleep(after).await;
761 let _ = events.send(RuntimeEvent::Timer(timer, generation)).await;
763 }),
764 );
765 }
766}
767
768async fn abort_and_join(task: JoinHandle<()>) {
769 task.abort();
770 let _ = task.await;
772}
773
774enum DriverInput {
775 Command(Option<Command>),
776 Event(Option<RuntimeEvent>),
777 Shutdown,
778}
779
780fn command_result(
781 result: Result<Vec<Output>, CommandError>,
782 reply: oneshot::Sender<Result<(), CommandError>>,
783) -> Vec<Output> {
784 match result {
785 Ok(outputs) => {
786 let _ = reply.send(Ok(()));
788 outputs
789 }
790 Err(error) => {
791 let _ = reply.send(Err(error));
793 Vec::new()
794 }
795 }
796}
797
798enum WorkKind {
799 Publisher,
800 Timer,
801 Transaction,
802}
803
804struct WorkGuard {
805 counters: Arc<Counters>,
806 kind: WorkKind,
807}
808
809impl WorkGuard {
810 fn publisher(counters: Arc<Counters>) -> Self {
811 counters.tasks.fetch_add(1, Ordering::Relaxed);
812 Self {
813 counters,
814 kind: WorkKind::Publisher,
815 }
816 }
817
818 fn timer(counters: Arc<Counters>) -> Self {
819 counters.tasks.fetch_add(1, Ordering::Relaxed);
820 counters.timers.fetch_add(1, Ordering::Relaxed);
821 Self {
822 counters,
823 kind: WorkKind::Timer,
824 }
825 }
826
827 fn transaction(counters: Arc<Counters>) -> Self {
828 counters.transactions.fetch_add(1, Ordering::Relaxed);
829 Self {
830 counters,
831 kind: WorkKind::Transaction,
832 }
833 }
834}
835
836impl Drop for WorkGuard {
837 fn drop(&mut self) {
838 match self.kind {
839 WorkKind::Publisher => {
840 self.counters.tasks.fetch_sub(1, Ordering::Relaxed);
841 self.counters.publishers.fetch_sub(1, Ordering::Relaxed);
842 }
843 WorkKind::Timer => {
844 self.counters.tasks.fetch_sub(1, Ordering::Relaxed);
845 self.counters.timers.fetch_sub(1, Ordering::Relaxed);
846 }
847 WorkKind::Transaction => {
848 self.counters.transactions.fetch_sub(1, Ordering::Relaxed);
849 }
850 }
851 }
852}
853
854#[allow(deprecated)]
857fn reserve(shared: &Shared) -> Result<(), PublicationError> {
858 shared
859 .counters
860 .publishers
861 .fetch_update(Ordering::AcqRel, Ordering::Relaxed, |current| {
862 (current < shared.config.capacity).then_some(current.saturating_add(1))
863 })
864 .map_err(|_| {
865 shared.counters.shed.fetch_add(1, Ordering::Relaxed);
866 PublicationError::CapacityExceeded
867 })?;
868 Ok(())
869}
870
871fn release(counters: &Counters) {
872 counters.publishers.fetch_sub(1, Ordering::Relaxed);
873}
874
875fn valid_request(request: &Request) -> bool {
876 request.method == Method::Publish
877 && request.headers.count(&HeaderName::CallId) == 1
878 && request.headers.count(&HeaderName::From) == 1
879 && request.headers.count(&HeaderName::To) == 1
880 && request.headers.count(&HeaderName::CSeq) == 1
881 && matches!(
882 request.headers.typed::<CSeq>(),
883 Some(Ok(CSeq {
884 method: Method::Publish,
885 ..
886 }))
887 )
888}
889
890fn event(request: &Request) -> Option<String> {
891 if request.headers.count(&HeaderName::Event) != 1 {
892 return None;
893 }
894 let value = request.headers.value(&HeaderName::Event)?;
895 let token = std::str::from_utf8(&value).ok()?.split(';').next()?.trim();
896 (!token.is_empty()).then(|| token.to_ascii_lowercase())
897}
898
899fn conditional_tag(request: &Request) -> Result<Option<String>, ()> {
900 match request.headers.count(&HeaderName::SipIfMatch) {
901 0 => Ok(None),
902 1 => {
903 let value = request.headers.value(&HeaderName::SipIfMatch).ok_or(())?;
904 if opaque_token(&value) {
905 Ok(Some(String::from_utf8_lossy(&value).into_owned()))
906 } else {
907 Err(())
908 }
909 }
910 _ => Err(()),
911 }
912}
913
914fn requested_expiry(request: &Request, default: Duration) -> Option<Duration> {
915 match request.headers.count(&HeaderName::Expires) {
916 0 => Some(default),
917 1 => request
918 .headers
919 .typed::<Expires>()?
920 .ok()
921 .map(|expires| Duration::from_secs(u64::from(expires.0))),
922 _ => None,
923 }
924}
925
926fn opaque_token(value: &[u8]) -> bool {
927 !value.is_empty()
928 && value.iter().all(|byte| {
929 byte.is_ascii_alphanumeric()
930 || matches!(
931 byte,
932 b'-' | b'.' | b'!' | b'%' | b'*' | b'_' | b'+' | b'`' | b'\'' | b'~'
933 )
934 })
935}
936
937async fn answer(
938 endpoint: &Handle,
939 incoming: &Incoming,
940 status: u16,
941 reason: &'static str,
942 extra: Option<(HeaderName, Bytes)>,
943 authority: Option<(&str, Duration)>,
944 to_tag: Option<&str>,
945) {
946 let Some(status) = StatusCode::new(status) else {
947 return;
948 };
949 let built = ResponseBuilder::to_request(&incoming.request, status, reason)
950 .and_then(|builder| with_to_tag(builder, &incoming.request, to_tag))
951 .and_then(|builder| match extra {
952 Some((name, value)) => builder.header(name, value),
953 None => Ok(builder),
954 })
955 .and_then(|builder| match authority {
956 Some((tag, expires)) => builder
957 .header(HeaderName::SipETag, Bytes::from(tag.to_owned()))?
958 .header(
959 HeaderName::Expires,
960 Bytes::from(expires.as_secs().to_string()),
961 ),
962 None => Ok(builder),
963 });
964 let Ok(builder) = built else {
965 return;
966 };
967 if let Err(error) = endpoint.respond(&incoming.key, builder.build()).await {
968 tracing::warn!(%error, "could not answer PUBLISH");
969 }
970}
971
972fn transport(value: sipx_ua::event_client::Transport) -> TransportKind {
973 match value {
974 sipx_ua::event_client::Transport::Udp => TransportKind::Udp,
975 sipx_ua::event_client::Transport::Tcp => TransportKind::Tcp,
976 sipx_ua::event_client::Transport::Tls => TransportKind::Tls,
977 sipx_ua::event_client::Transport::Ws => TransportKind::Ws,
978 sipx_ua::event_client::Transport::Wss => TransportKind::Wss,
979 sipx_ua::event_client::Transport::Quic => TransportKind::Quic,
980 }
981}
982
983fn transport_target(peer: sipx_ua::event_client::Peer) -> Target {
984 let mut target = Target::new(peer.address, transport(peer.transport));
985 if let Some(identity) = peer.identity {
986 target = target.verifying(identity);
987 }
988 if let Some(path) = peer.path {
989 target = target.at_path(path);
990 }
991 target
992}
993
994fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
995 mutex.lock().unwrap_or_else(PoisonError::into_inner)
996}
997
998#[cfg(test)]
999#[allow(
1000 clippy::unwrap_used,
1001 clippy::expect_used,
1002 clippy::panic,
1003 clippy::indexing_slicing
1004)]
1005mod admission_tests {
1006 use std::sync::{Arc, Barrier};
1007 use std::time::Duration;
1008
1009 use bytes::Bytes;
1010 use sipx_transport::{Config as TransportConfig, bind};
1011 use sipx_ua::event_client::{Peer, Transport};
1012 use sipx_ua::presence::Compositor;
1013 use sipx_ua::publication_client::Start;
1014
1015 use super::*;
1016
1017 fn start(target: std::net::SocketAddr) -> Start {
1018 Start {
1019 resource: sipx_sip::Uri::parse(Bytes::from_static(b"sip:resource@example.test"))
1020 .expect("URI"),
1021 local_identity: "<sip:client@example.test>".to_owned(),
1022 target: Peer::new(target, Transport::Udp),
1023 event: "presence".to_owned(),
1024 expires: Duration::from_secs(60),
1025 body: Bytes::from_static(b"<presence/>"),
1026 content_type: "application/pidf+xml".to_owned(),
1027 credentials: None,
1028 call_id: "admission@example.test".to_owned(),
1029 from_tag: "admission".to_owned(),
1030 initial_cseq: 1,
1031 }
1032 }
1033
1034 #[tokio::test]
1035 async fn racing_shutdown_closes_admission_before_any_spawn() {
1036 let (endpoint, _) = bind(TransportConfig::new(
1037 "127.0.0.1:0".parse().expect("address"),
1038 ))
1039 .await
1040 .expect("endpoint");
1041 let mut runtime = Publications::new(
1042 PublicationConfig::default(),
1043 Compositor::new(Duration::from_secs(60)),
1044 Arc::new(ReplacePublicationState),
1045 Arc::new(AllowPublications),
1046 )
1047 .expect("runtime");
1048 runtime.attach(endpoint.clone());
1049 let handle = runtime.handle();
1050 let post_shutdown = handle.clone();
1051 let shared = Arc::clone(&runtime.shared);
1052 let drivers = lock(&shared.drivers);
1053 let barrier = Arc::new(Barrier::new(2));
1054 let contender = Arc::clone(&barrier);
1055 let target = endpoint.local_addr();
1056 let attempt = std::thread::spawn(move || {
1057 contender.wait();
1058 handle.publish(start(target))
1059 });
1060 barrier.wait();
1061 shared.shutdown.cancel();
1062 drop(drivers);
1063 assert!(matches!(
1064 attempt.join().expect("thread"),
1065 Err(PublicationError::ShuttingDown)
1066 ));
1067 assert!(matches!(
1068 post_shutdown.publish(start(target)),
1069 Err(PublicationError::ShuttingDown)
1070 ));
1071 assert!(lock(&shared.drivers).is_empty());
1072 endpoint.shutdown().await;
1073 }
1074}
1075
1076#[cfg(test)]
1077#[allow(
1078 clippy::unwrap_used,
1079 clippy::expect_used,
1080 clippy::panic,
1081 clippy::indexing_slicing
1082)]
1083mod tests {
1084 use super::*;
1085 use sipx_ua::event_client::{Peer, Transport};
1086
1087 #[test]
1088 fn publication_driver_preserves_secure_target_identity_and_resource() {
1089 let peer = Peer::new("192.0.2.20:7443".parse().expect("peer"), Transport::Wss)
1090 .verifying("compositor.example.test")
1091 .at_path("/publish");
1092 let target = transport_target(peer);
1093 assert_eq!(target.transport, TransportKind::Wss);
1094 assert_eq!(target.verify_as.as_deref(), Some("compositor.example.test"));
1095 assert_eq!(target.path.as_deref(), Some("/publish"));
1096 }
1097}