wavekat_sip/inbound.rs
1//! Inbound in-dialog requests surfaced to the consumer.
2//!
3//! By default [`SipEndpoint`](crate::SipEndpoint) auto-answers every in-dialog
4//! request (`BYE` / `OPTIONS` / `INFO` / re-`INVITE`) with `200 OK`. A consumer
5//! that needs to *handle* a peer's re-`INVITE` (e.g. answer an RFC 4028 session
6//! refresh with a fresh SDP answer, or apply a peer-initiated hold) or read an
7//! inbound `INFO` (e.g. SIP-INFO DTMF) opts in via
8//! [`Call::inbound_requests`](crate::Call::inbound_requests). From then on the
9//! endpoint routes that dialog's re-`INVITE` / `INFO` here instead of
10//! auto-answering them; the consumer answers each [`InboundRequest`] itself.
11//!
12//! `BYE` and `OPTIONS` are always auto-answered by the endpoint, even after
13//! opting in — call teardown and keepalive don't need consumer involvement.
14
15use std::sync::Arc;
16
17use rsip::{Headers, Method, Request, StatusCode};
18
19use crate::stack::response::{build_response, ResponseBody};
20use crate::stack::transaction::TransactionKey;
21use crate::stack::ua::Ua;
22
23/// A single inbound in-dialog request (a peer re-`INVITE` or `INFO`) awaiting a
24/// response from the consumer.
25///
26/// Inspect it with [`method`](Self::method) / [`headers`](Self::headers) /
27/// [`body`](Self::body), then answer exactly once with
28/// [`respond`](Self::respond) or [`ok`](Self::ok). Dropping it without
29/// answering leaves the peer's transaction to time out.
30pub struct InboundRequest {
31 ua: Arc<Ua>,
32 key: TransactionKey,
33 request: Request,
34}
35
36impl InboundRequest {
37 pub(crate) fn new(ua: Arc<Ua>, key: TransactionKey, request: Request) -> Self {
38 Self { ua, key, request }
39 }
40
41 /// The request method — [`Method::Invite`] for a re-INVITE (re-offer /
42 /// session refresh), [`Method::Info`] for SIP INFO, [`Method::Notify`] for a
43 /// transfer-progress sipfrag, or [`Method::Refer`] for a peer-initiated
44 /// transfer.
45 pub fn method(&self) -> &Method {
46 self.request.method()
47 }
48
49 /// The request's headers (e.g. to read `Session-Expires` on a refresh).
50 pub fn headers(&self) -> &Headers {
51 &self.request.headers
52 }
53
54 /// The request body (e.g. an SDP re-offer or a `application/dtmf-relay`
55 /// INFO payload).
56 pub fn body(&self) -> &[u8] {
57 &self.request.body
58 }
59
60 /// The `CSeq` method+number is in [`headers`](Self::headers); this is the
61 /// raw request for callers that need full access.
62 pub fn request(&self) -> &Request {
63 &self.request
64 }
65
66 /// Answer with `status`, optional `extra_headers`, and an optional SDP body
67 /// (sent as `application/sdp`). Returns `true` if the engine sent it.
68 ///
69 /// Use this to answer a re-INVITE: `200 OK` with the SDP answer for a
70 /// session refresh or a peer hold, or a non-2xx to decline.
71 pub async fn respond(
72 self,
73 status: StatusCode,
74 extra_headers: Vec<rsip::Header>,
75 sdp: Option<Vec<u8>>,
76 ) -> bool {
77 let body = sdp.map(|bytes| ResponseBody {
78 content_type: "application/sdp",
79 bytes,
80 });
81 let Some(mut response) = build_response(&self.request, status, None, None, body) else {
82 return false;
83 };
84 for header in extra_headers {
85 response.headers.push(header);
86 }
87 self.ua.answer(self.key, response).await
88 }
89
90 /// Answer `200 OK` with no body — the right reply for an `INFO`, or a
91 /// re-INVITE you accept without renegotiating media.
92 pub async fn ok(self) -> bool {
93 self.respond(StatusCode::OK, Vec::new(), None).await
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use rsip::message::HeadersExt;
101
102 fn info_request() -> Request {
103 let raw = "INFO sip:bob@10.0.0.1:5060 SIP/2.0\r\n\
104 Via: SIP/2.0/UDP 1.2.3.4:5060;branch=z9hG4bK-info\r\n\
105 From: <sip:alice@atlanta.example.com>;tag=alice\r\n\
106 To: <sip:bob@biloxi.example.com>;tag=ourtag\r\n\
107 Call-ID: call-dlg\r\n\
108 CSeq: 2 INFO\r\n\
109 Content-Type: application/dtmf-relay\r\n\
110 Content-Length: 21\r\n\r\n\
111 Signal=5\nDuration=160";
112 Request::try_from(raw.as_bytes()).unwrap()
113 }
114
115 #[test]
116 fn exposes_method_headers_and_body() {
117 // Construction needs a Ua, which needs a runtime; the accessors are
118 // pure on the request, so test them on the parsed request directly.
119 let req = info_request();
120 assert_eq!(*req.method(), Method::Info);
121 assert_eq!(req.body, b"Signal=5\nDuration=160");
122 assert!(req.cseq_header().is_ok());
123 }
124}