1use std::net::IpAddr;
8use std::sync::Arc;
9use std::time::Duration;
10
11use bytes::Bytes;
12use sipx_sdp::{Capabilities, Direction, SessionDescription};
13use sipx_sip::build::{RequestBuilder, ResponseBuilder};
14use sipx_sip::rel::{self, Numbering, Offered, RAck, RSeq, Reliability};
15use sipx_sip::transaction::TransactionKey;
16use sipx_sip::update;
17use sipx_sip::{HeaderName, Method, Response, StatusCode};
18use sipx_transport::{Handle, Incoming, Target};
19
20use crate::call::{Early, EarlyOffer, MediaAddress};
21use crate::dialog::{Dialog, strip_header_params};
22use crate::error::{Error, Result};
23use crate::media_policy::{Codecs, MediaPolicy};
24
25const T1: Duration = Duration::from_millis(500);
27
28const GIVE_UP: Duration = Duration::from_secs(32);
31
32#[must_use]
39pub fn prack_body(
40 invite_offered: bool,
41 provisional_body: &[u8],
42 capabilities: &Capabilities,
43) -> Option<SessionDescription> {
44 if invite_offered || provisional_body.is_empty() {
45 return None;
46 }
47 let offer = sipx_sdp::parse(&String::from_utf8_lossy(provisional_body)).ok()?;
48 Some(sipx_sdp::answer(&offer, capabilities))
49}
50
51#[must_use]
57pub fn reliable_sequence(response: &Response) -> Option<u32> {
58 const TRYING: u16 = 100;
59 if response.status.code() <= TRYING || response.status.is_final() {
60 return None;
61 }
62 if !response
63 .headers
64 .get_all(&HeaderName::Require)
65 .any(|header| contains_100rel(&header.value()))
66 {
67 return None;
68 }
69 response
70 .headers
71 .typed::<RSeq>()
72 .and_then(std::result::Result::ok)
73 .map(|seq| seq.0)
74}
75
76fn contains_100rel(value: &[u8]) -> bool {
77 value.split(|&b| b == b',').any(|tag| {
78 let tag: &[u8] = tag
79 .iter()
80 .position(|b| !b.is_ascii_whitespace())
81 .map_or(&[][..], |start| tag.get(start..).unwrap_or_default());
82 let end = tag
83 .iter()
84 .rposition(|b| !b.is_ascii_whitespace())
85 .map_or(0, |last| last + 1);
86 tag.get(..end)
87 .unwrap_or_default()
88 .eq_ignore_ascii_case(rel::OPTION_TAG.as_bytes())
89 })
90}
91
92pub async fn send_prack(
98 endpoint: &Handle,
99 dialog: &mut Dialog,
100 target: &Target,
101 rseq: u32,
102 invite_cseq: u32,
103 body: Option<SessionDescription>,
104) -> Result<()> {
105 let (local, remote) = dialog.local_and_remote();
106 let cseq = dialog.next_cseq();
107 let (uri, routes) = dialog.request_target();
108 let ack = RAck {
109 rseq,
110 cseq: invite_cseq,
111 method: Method::Invite.as_bytes().to_vec(),
112 };
113
114 let mut builder = RequestBuilder::new(Method::Prack, uri)
115 .header(HeaderName::To, Bytes::from(remote))?
116 .header(HeaderName::From, Bytes::from(local))?
117 .header(HeaderName::CallId, Bytes::from(dialog.id.call_id.clone()))?
118 .cseq(cseq, &Method::Prack)?
119 .header(HeaderName::RAck, Bytes::from(ack.to_string()))?
120 .max_forwards(70);
121 if let Some(answer) = body {
122 builder = builder
123 .header(
124 HeaderName::ContentType,
125 Bytes::from_static(b"application/sdp"),
126 )?
127 .body(Bytes::from(answer.to_string_sdp()));
128 }
129
130 let request = crate::call::add_routes(builder, &routes)?.build();
131 let mut responses = endpoint.send(request, target.clone()).await?;
132 match responses.final_response().await {
136 Some(response) if response.status.is_success() => Ok(()),
137 Some(response) => Err(Error::Rejected {
138 status: response.status.code(),
139 reason: String::from_utf8_lossy(&response.reason).into_owned(),
140 }),
141 None => Err(Error::NoResponse),
142 }
143}
144
145#[derive(Debug)]
151pub struct Ringing {
152 endpoint: Handle,
153 tag: String,
154 invite_cseq: u32,
155 numbering: Numbering,
156 reliable: bool,
157 stop: Option<Arc<tokio::sync::Notify>>,
158 acknowledged: bool,
159 dialog: Option<Dialog>,
166 target: Target,
168 negotiation: update::Negotiation,
170 peer_allows_update: bool,
172 early: Option<Early>,
177 early_offer: Option<Box<EarlyOffer>>,
179}
180
181impl Ringing {
182 #[must_use]
188 pub fn tag(&self) -> &str {
189 &self.tag
190 }
191
192 #[must_use]
194 pub fn is_reliable(&self) -> bool {
195 self.reliable
196 }
197
198 #[must_use]
200 pub fn is_acknowledged(&self) -> bool {
201 self.acknowledged || !self.reliable
202 }
203
204 #[must_use]
206 pub fn peer_allows_update(&self) -> bool {
207 self.peer_allows_update
208 }
209
210 #[must_use]
217 pub fn has_early_session(&self) -> bool {
218 self.early.is_some()
219 }
220
221 #[must_use]
227 pub fn media(&self) -> Option<&sipx_media::MediaSession> {
228 self.early.as_ref().map(|early| &early.media)
229 }
230
231 pub(crate) fn take_early(&mut self) -> Result<(Early, Dialog, update::Negotiation, bool)> {
236 let early = self.early.take().ok_or(Error::NoEarlySession)?;
237 let dialog = self.dialog.take().ok_or(Error::NoDialog)?;
238 Ok((early, dialog, self.negotiation, self.peer_allows_update))
239 }
240
241 fn early_dialog(&mut self) -> Option<crate::update::EarlyDialog<'_>> {
246 Some(crate::update::EarlyDialog {
247 endpoint: &self.endpoint,
248 dialog: self.dialog.as_mut()?,
249 target: &mut self.target,
250 negotiation: &mut self.negotiation,
251 peer_allows: &mut self.peer_allows_update,
252 early: self.early.as_mut(),
253 })
254 }
255
256 pub async fn on_update(&mut self, incoming: &Incoming) -> Result<bool> {
268 let Some(early) = self.early_dialog() else {
269 return Ok(false);
270 };
271 crate::update::receive(early, incoming).await
272 }
273
274 pub async fn update(&mut self, direction: Direction) -> Result<()> {
280 if self.early.is_none() {
285 return Err(Error::NoDialog);
286 }
287 let Some(early) = self.early_dialog() else {
288 return Err(Error::NoDialog);
289 };
290 crate::update::offer(early, direction).await
291 }
292
293 pub async fn on_prack(&mut self, incoming: &Incoming) -> Result<bool> {
300 if incoming.request.method != Method::Prack {
301 return Ok(false);
302 }
303 let ack = incoming
304 .request
305 .headers
306 .typed::<RAck>()
307 .and_then(std::result::Result::ok);
308
309 let matched = ack.is_some_and(|ack| {
310 self.numbering
311 .acknowledge(&ack, self.invite_cseq, Method::Invite.as_bytes())
312 });
313
314 let negotiated = if matched {
315 match self.early_offer.take() {
316 Some(offered) => {
317 let answer = sipx_sdp::parse(&String::from_utf8_lossy(incoming.request.body()))
318 .map_err(|error| Error::Sdp(error.to_string()));
319 match answer {
320 Ok(answer) => match offered.settle(&answer).await {
321 Ok(early) => {
322 self.early = Some(early);
323 self.negotiation.received_answer();
324 Ok(())
325 }
326 Err(error) => Err(error),
327 },
328 Err(error) => Err(error),
329 }
330 }
331 None => Ok(()),
332 }
333 } else {
334 Ok(())
335 };
336 let (status, reason) = match (&negotiated, matched) {
337 (Err(_), true) => (488, "Not Acceptable Here"),
338 (_, true) => (200, "OK"),
339 (_, false) => (481, "Call/Transaction Does Not Exist"),
340 };
341 let code = StatusCode::new(status)
342 .ok_or_else(|| Error::Sdp("unreachable: literal status".to_owned()))?;
343 let response = ResponseBuilder::to_request(&incoming.request, code, reason)?.build();
344 self.endpoint.respond(&incoming.key, response).await?;
345
346 if matched {
347 self.acknowledged = true;
348 if let Some(stop) = self.stop.take() {
349 stop.notify_waiters();
350 }
351 }
352 negotiated?;
353 Ok(matched)
354 }
355}
356
357impl Drop for Ringing {
358 fn drop(&mut self) {
359 if let Some(stop) = self.stop.take() {
362 stop.notify_waiters();
363 }
364 }
365}
366
367pub async fn ring(
374 endpoint: &Handle,
375 incoming: &Incoming,
376 status: u16,
377 reason: &'static str,
378 enabled: bool,
379) -> Result<Ringing> {
380 ring_with(endpoint, incoming, status, reason, enabled, None).await
381}
382
383pub async fn ring_offer_early(
389 endpoint: &Handle,
390 incoming: &Incoming,
391 status: u16,
392 reason: &'static str,
393 media_address: IpAddr,
394 direction: Direction,
395) -> Result<Ringing> {
396 ring_offer_early_with_policy(
397 endpoint,
398 incoming,
399 status,
400 reason,
401 media_address,
402 direction,
403 MediaPolicy::default(),
404 )
405 .await
406}
407
408pub async fn ring_offer_early_with_policy(
410 endpoint: &Handle,
411 incoming: &Incoming,
412 status: u16,
413 reason: &'static str,
414 media_address: IpAddr,
415 direction: Direction,
416 policy: MediaPolicy,
417) -> Result<Ringing> {
418 ring_offer_early_with_policy_at(
419 endpoint,
420 incoming,
421 status,
422 reason,
423 MediaAddress::new(media_address),
424 direction,
425 policy,
426 )
427 .await
428}
429
430pub async fn ring_offer_early_with_policy_at(
432 endpoint: &Handle,
433 incoming: &Incoming,
434 status: u16,
435 reason: &'static str,
436 media_address: MediaAddress,
437 direction: Direction,
438 policy: MediaPolicy,
439) -> Result<Ringing> {
440 if !incoming.request.body().is_empty() {
441 return Err(Error::Rejected {
442 status: 500,
443 reason: "the INVITE already carries an offer".to_owned(),
444 });
445 }
446 if !Offered::in_request(&incoming.request).supported {
447 return Err(Error::Rejected {
448 status: 421,
449 reason: "the caller did not offer 100rel, so no offer may go in a provisional"
450 .to_owned(),
451 });
452 }
453 let offered = EarlyOffer::bind(
454 media_address,
455 incoming.transport.is_secure(),
456 direction,
457 policy,
458 )
459 .await?;
460 ring_with(
461 endpoint,
462 incoming,
463 status,
464 reason,
465 true,
466 Some(ProvisionalSession::Offer(Box::new(offered))),
467 )
468 .await
469}
470
471pub async fn ring_early(
491 endpoint: &Handle,
492 incoming: &Incoming,
493 status: u16,
494 reason: &'static str,
495 media_address: IpAddr,
496) -> Result<Ringing> {
497 ring_early_with(
498 endpoint,
499 incoming,
500 status,
501 reason,
502 media_address,
503 Codecs::default(),
504 )
505 .await
506}
507
508pub async fn ring_early_with(
514 endpoint: &Handle,
515 incoming: &Incoming,
516 status: u16,
517 reason: &'static str,
518 media_address: IpAddr,
519 codecs: Codecs,
520) -> Result<Ringing> {
521 ring_early_with_policy(
522 endpoint,
523 incoming,
524 status,
525 reason,
526 media_address,
527 MediaPolicy::default().with_codecs(codecs),
528 )
529 .await
530}
531
532pub async fn ring_early_with_policy(
537 endpoint: &Handle,
538 incoming: &Incoming,
539 status: u16,
540 reason: &'static str,
541 media_address: IpAddr,
542 policy: MediaPolicy,
543) -> Result<Ringing> {
544 ring_early_with_policy_at(
545 endpoint,
546 incoming,
547 status,
548 reason,
549 MediaAddress::new(media_address),
550 policy,
551 )
552 .await
553}
554
555pub async fn ring_early_with_policy_at(
557 endpoint: &Handle,
558 incoming: &Incoming,
559 status: u16,
560 reason: &'static str,
561 media_address: MediaAddress,
562 policy: MediaPolicy,
563) -> Result<Ringing> {
564 if !Offered::in_request(&incoming.request).supported {
565 return Err(Error::Rejected {
568 status: 421,
569 reason: "the caller did not offer 100rel, so no answer may go in a provisional"
570 .to_owned(),
571 });
572 }
573 let offer = sipx_sdp::parse(&String::from_utf8_lossy(incoming.request.body()))
574 .map_err(|error| Error::Sdp(error.to_string()))?;
575 let settled = Early::settle(
576 media_address,
577 incoming.transport.is_secure(),
578 &offer,
579 policy,
580 )
581 .await?;
582 ring_with(
583 endpoint,
584 incoming,
585 status,
586 reason,
587 true,
588 Some(ProvisionalSession::Answer(
589 Box::new(settled.0),
590 Box::new(settled.1),
591 )),
592 )
593 .await
594}
595
596enum ProvisionalSession {
597 Answer(Box<Early>, Box<SessionDescription>),
598 Offer(Box<EarlyOffer>),
599}
600
601#[allow(
602 clippy::too_many_lines,
603 reason = "one construction keeps reliability, SDP carrier and retained media state aligned"
604)]
605async fn ring_with(
606 endpoint: &Handle,
607 incoming: &Incoming,
608 status: u16,
609 reason: &'static str,
610 enabled: bool,
611 session: Option<ProvisionalSession>,
612) -> Result<Ringing> {
613 let offered = Offered::in_request(&incoming.request);
614 let decision = rel::reliability(offered, enabled);
615
616 if decision == Reliability::Refuse {
617 return refuse_bad_extension(endpoint, incoming).await;
618 }
619
620 let tag = crate::call::token();
621 let invite_cseq = incoming
622 .request
623 .headers
624 .typed::<sipx_sip::CSeq>()
625 .and_then(std::result::Result::ok)
626 .map_or(1, |cseq| cseq.sequence);
627
628 let mut numbering = Numbering::starting_at({
633 use rand::Rng as _;
634 rand::rng().random_range(1..=rel::MAX_FIRST_RSEQ)
635 });
636
637 let code = StatusCode::new(status)
638 .ok_or_else(|| Error::Sdp(format!("status {status} out of range")))?;
639 let to_with_tag = {
640 let existing = incoming
641 .request
642 .headers
643 .value(&HeaderName::To)
644 .map(|value| String::from_utf8_lossy(&value).into_owned())
645 .unwrap_or_default();
646 format!("{};tag={tag}", strip_header_params(&existing))
647 };
648
649 let mut builder = ResponseBuilder::to_request(&incoming.request, code, reason)?
650 .set_header(&HeaderName::To, Bytes::from(to_with_tag))?
651 .header(
652 HeaderName::Contact,
653 Bytes::from(crate::call::contact_for(endpoint, incoming.transport)),
654 )?
655 .header(
661 HeaderName::Allow,
662 Bytes::from_static(update::ALLOW.as_bytes()),
663 )?;
664
665 let reliable = decision != Reliability::Forbidden;
666 if reliable {
667 let allocated = numbering
668 .allocate()
669 .ok_or_else(|| Error::Sdp("unreachable: first allocation".to_owned()))?;
670 builder = builder
671 .header(HeaderName::Require, Bytes::from_static(b"100rel"))?
672 .header(HeaderName::RSeq, Bytes::from(allocated.to_string()))?;
673 }
674
675 let (early, early_offer) = match session {
680 Some(ProvisionalSession::Answer(settled, answer)) if reliable => {
681 builder = builder
682 .header(
683 HeaderName::ContentType,
684 Bytes::from_static(b"application/sdp"),
685 )?
686 .body(Bytes::from(answer.to_string_sdp()));
687 (Some(*settled), None)
688 }
689 Some(ProvisionalSession::Offer(offered)) if reliable => {
690 builder = builder
691 .header(
692 HeaderName::ContentType,
693 Bytes::from_static(b"application/sdp"),
694 )?
695 .body(Bytes::from(offered.description().to_string_sdp()));
696 (None, Some(offered))
697 }
698 _ => (None, None),
699 };
700
701 let response = builder.build();
702 endpoint.respond(&incoming.key, response.clone()).await?;
703
704 let stop = reliable.then(|| {
705 let stop = Arc::new(tokio::sync::Notify::new());
706 tokio::spawn(retransmit_until_pracked(
707 endpoint.clone(),
708 incoming.key.clone(),
709 response,
710 Arc::clone(&stop),
711 ));
712 stop
713 });
714
715 let negotiation = if early_offer.is_some() {
720 update::Negotiation::offering()
721 } else if early.is_none() && crate::update::carries_offer(&incoming.request) {
722 update::Negotiation::owing()
723 } else {
724 update::Negotiation::idle()
725 };
726
727 let dialog = Dialog::from_request(&incoming.request, &tag);
728 let target = dialog.as_ref().map_or_else(
729 || Target::new(incoming.source, incoming.transport),
730 |dialog| {
731 crate::call::in_dialog_target(dialog, Target::new(incoming.source, incoming.transport))
732 },
733 );
734
735 Ok(Ringing {
736 endpoint: endpoint.clone(),
737 tag,
738 invite_cseq,
739 numbering,
740 reliable,
741 stop,
742 acknowledged: false,
743 dialog,
744 target,
745 negotiation,
746 peer_allows_update: update::peer_allows(&incoming.request.headers),
747 early,
748 early_offer,
749 })
750}
751
752async fn refuse_bad_extension(endpoint: &Handle, incoming: &Incoming) -> Result<Ringing> {
758 const BAD_EXTENSION: u16 = 420;
759 let code = StatusCode::new(BAD_EXTENSION)
760 .ok_or_else(|| Error::Sdp("unreachable: literal status".to_owned()))?;
761 let refusal = ResponseBuilder::to_request(&incoming.request, code, "Bad Extension")?
762 .header(HeaderName::Unsupported, Bytes::from_static(b"100rel"))?
763 .build();
764 endpoint.respond(&incoming.key, refusal).await?;
765 Err(Error::Rejected {
766 status: BAD_EXTENSION,
767 reason: "Bad Extension".to_owned(),
768 })
769}
770
771async fn retransmit_until_pracked(
778 endpoint: Handle,
779 key: TransactionKey,
780 response: Response,
781 stop: Arc<tokio::sync::Notify>,
782) {
783 let deadline = tokio::time::Instant::now() + GIVE_UP;
784 let mut interval = T1;
785 loop {
786 let wake = tokio::time::Instant::now() + interval;
787 if wake >= deadline {
788 return;
789 }
790 tokio::select! {
791 () = stop.notified() => return,
792 () = tokio::time::sleep_until(wake) => {}
793 }
794 if endpoint.respond(&key, response.clone()).await.is_err() {
795 return;
796 }
797 interval = interval.saturating_mul(2);
798 }
799}