ftth_rsipstack/transaction/
message.rs

1use super::{endpoint::EndpointInner, make_call_id};
2use crate::{rsip_ext::extract_uri_from_contact, transaction::make_via_branch, Result};
3use rsip::{
4    header,
5    headers::Route,
6    prelude::{HeadersExt, ToTypedHeader, UntypedHeader},
7    Error, Header, Request, Response, StatusCode,
8};
9
10impl EndpointInner {
11    /// Create a SIP request message
12    ///
13    /// Constructs a properly formatted SIP request with all required headers
14    /// according to RFC 3261. This method is used internally by the endpoint
15    /// to create outgoing SIP requests for various purposes.
16    ///
17    /// # Parameters
18    ///
19    /// * `method` - SIP method (INVITE, REGISTER, BYE, etc.)
20    /// * `req_uri` - Request-URI indicating the target of the request
21    /// * `via` - Via header for response routing
22    /// * `from` - From header identifying the request originator
23    /// * `to` - To header identifying the request target
24    /// * `seq` - CSeq sequence number for the request
25    ///
26    /// # Returns
27    ///
28    /// A complete SIP request with all mandatory headers
29    ///
30    /// # Generated Headers
31    ///
32    /// The method automatically includes these mandatory headers:
33    /// * **Via** - Response routing information
34    /// * **Call-ID** - Unique identifier for the call/session
35    /// * **From** - Request originator with tag parameter
36    /// * **To** - Request target (tag added by recipient)
37    /// * **CSeq** - Command sequence with method and number
38    /// * **Max-Forwards** - Hop count limit (set to 70)
39    /// * **User-Agent** - Endpoint identification
40    ///
41    /// # Examples
42    ///
43    /// ```rust,no_run
44    /// # use rsipstack::transaction::endpoint::EndpointInner;
45    /// # async fn example(endpoint: &EndpointInner) -> rsipstack::Result<()> {
46    /// // Create an INVITE request
47    /// let via = endpoint.get_via(None, None)?;
48    /// let from = rsip::typed::From {
49    ///     display_name: None,
50    ///     uri: rsip::Uri::try_from("sip:alice@example.com")?,
51    ///     params: vec![rsip::Param::Tag("alice-tag".into())],
52    /// };
53    /// let to = rsip::typed::To {
54    ///     display_name: None,
55    ///     uri: rsip::Uri::try_from("sip:bob@example.com")?,
56    ///     params: vec![],
57    /// };
58    ///
59    /// let request = endpoint.make_request(
60    ///     rsip::Method::Invite,
61    ///     rsip::Uri::try_from("sip:bob@example.com")?,
62    ///     via,
63    ///     from,
64    ///     to,
65    ///     1
66    /// );
67    /// # Ok(())
68    /// # }
69    /// ```
70    ///
71    /// # Usage Context
72    ///
73    /// This method is typically used by:
74    /// * Dialog layer for creating in-dialog requests
75    /// * Registration module for REGISTER requests
76    /// * Transaction layer for creating client transactions
77    /// * Application layer for custom request types
78    ///
79    /// # Header Ordering
80    ///
81    /// Headers are added in the order specified by RFC 3261 recommendations:
82    /// 1. Via (topmost first)
83    /// 2. Call-ID
84    /// 3. From
85    /// 4. To
86    /// 5. CSeq
87    /// 6. Max-Forwards
88    /// 7. User-Agent
89    ///
90    /// Additional headers can be added after creation using the headers API.
91    pub fn make_request(
92        &self,
93        method: rsip::Method,
94        req_uri: rsip::Uri,
95        via: rsip::typed::Via,
96        from: rsip::typed::From,
97        to: rsip::typed::To,
98        seq: u32,
99    ) -> rsip::Request {
100        let headers = vec![
101            Header::Via(via.into()),
102            Header::CallId(make_call_id(self.option.callid_suffix.as_deref())),
103            Header::From(from.into()),
104            Header::To(to.into()),
105            Header::CSeq(rsip::typed::CSeq { seq, method }.into()),
106            Header::MaxForwards(70.into()),
107            Header::UserAgent(self.user_agent.clone().into()),
108        ];
109        rsip::Request {
110            method,
111            uri: req_uri,
112            headers: headers.into(),
113            body: vec![],
114            version: rsip::Version::V2,
115        }
116    }
117
118    /// Create a SIP response message
119    ///
120    /// Constructs a properly formatted SIP response based on the received
121    /// request. This method copies appropriate headers from the request
122    /// and adds the response-specific information according to RFC 3261.
123    ///
124    /// # Parameters
125    ///
126    /// * `req` - Original request being responded to
127    /// * `status_code` - SIP response status code (1xx-6xx)
128    /// * `body` - Optional response body content
129    ///
130    /// # Returns
131    ///
132    /// A complete SIP response ready to be sent
133    ///
134    /// # Header Processing
135    ///
136    /// The method processes headers as follows:
137    /// * **Copied from request**: Via, Call-ID, From, To, CSeq, Max-Forwards
138    /// * **Added by endpoint**: User-Agent
139    /// * **Filtered out**: All other headers from the request
140    ///
141    /// Additional response-specific headers should be added after creation.
142    ///
143    /// # Examples
144    ///
145    /// ## Success Response
146    ///
147    /// ```rust,no_run
148    /// # use rsipstack::transaction::endpoint::EndpointInner;
149    /// # fn example(endpoint: &EndpointInner, request: &rsip::Request, sdp_answer: String) {
150    /// let response = endpoint.make_response(
151    ///     &request,
152    ///     rsip::StatusCode::OK,
153    ///     Some(sdp_answer.into_bytes())
154    /// );
155    /// # }
156    /// ```
157    ///
158    /// ## Error Response
159    ///
160    /// ```rust,no_run
161    /// # use rsipstack::transaction::endpoint::EndpointInner;
162    /// # fn example(endpoint: &EndpointInner, request: &rsip::Request) {
163    /// let response = endpoint.make_response(
164    ///     &request,
165    ///     rsip::StatusCode::NotFound,
166    ///     None
167    /// );
168    /// # }
169    /// ```
170    ///
171    /// ## Provisional Response
172    ///
173    /// ```rust,no_run
174    /// # use rsipstack::transaction::endpoint::EndpointInner;
175    /// # fn example(endpoint: &EndpointInner, request: &rsip::Request) {
176    /// let response = endpoint.make_response(
177    ///     &request,
178    ///     rsip::StatusCode::Ringing,
179    ///     None
180    /// );
181    /// # }
182    /// ```
183    ///
184    /// # Response Categories
185    ///
186    /// * **1xx Provisional** - Request received, processing continues
187    /// * **2xx Success** - Request successfully received, understood, and accepted
188    /// * **3xx Redirection** - Further action needed to complete request
189    /// * **4xx Client Error** - Request contains bad syntax or cannot be fulfilled
190    /// * **5xx Server Error** - Server failed to fulfill valid request
191    /// * **6xx Global Failure** - Request cannot be fulfilled at any server
192    ///
193    /// # Usage Context
194    ///
195    /// This method is used by:
196    /// * Server transactions to create responses
197    /// * Dialog layer for dialog-specific responses
198    /// * Application layer for handling incoming requests
199    /// * Error handling for protocol violations
200    ///
201    /// # Header Compliance
202    ///
203    /// The response includes all headers required by RFC 3261:
204    /// * Via headers are copied exactly (for response routing)
205    /// * Call-ID is preserved (dialog/transaction identification)
206    /// * From/To headers maintain dialog state
207    /// * CSeq is copied for transaction matching
208    /// * User-Agent identifies the responding endpoint
209    ///
210    /// # Content Handling
211    ///
212    /// * If body is provided, Content-Length should be added separately
213    /// * Content-Type should be added for non-empty bodies
214    /// * Body encoding is handled by the application layer
215    pub fn make_response(
216        &self,
217        req: &Request,
218        status_code: StatusCode,
219        body: Option<Vec<u8>>,
220    ) -> Response {
221        let mut headers = req.headers.clone();
222        headers.retain(|h| {
223            matches!(
224                h,
225                Header::Via(_)
226                    | Header::CallId(_)
227                    | Header::From(_)
228                    | Header::To(_)
229                    | Header::MaxForwards(_)
230                    | Header::CSeq(_)
231            )
232        });
233        headers.unique_push(Header::UserAgent(self.user_agent.clone().into()));
234        Response {
235            status_code,
236            version: req.version().clone(),
237            headers,
238            body: body.unwrap_or_default(),
239        }
240    }
241
242    pub fn make_ack(&self, mut uri: rsip::Uri, resp: &Response) -> Result<Request> {
243        let mut headers = resp.headers.clone();
244        // Check if response to INVITE
245        let mut resp_to_invite = false;
246        if let Ok(cseq) = resp.cseq_header() {
247            if let Ok(rsip::Method::Invite) = cseq.method() {
248                resp_to_invite = true;
249            }
250        }
251
252        // For 200 OK responses to INVITE, this creates an ACK with:
253        // 1. Request-URI from Contact header
254        // 2. Route headers from Record-Route headers
255        // 3. New Via branch (creates new transaction)
256        //
257        // **FIXME**: This duplicates logic in `client_dialog.rs` - should be refactored
258        if resp_to_invite && resp.status_code.kind() == rsip::StatusCodeKind::Successful {
259            if let Ok(top_most_via) = header!(
260                headers.iter_mut(),
261                Header::Via,
262                Error::missing_header("Via")
263            ) {
264                if let Ok(mut typed_via) = top_most_via.typed() {
265                    for param in typed_via.params.iter_mut() {
266                        if let rsip::Param::Branch(_) = param {
267                            *param = make_via_branch();
268                        }
269                    }
270                    *top_most_via = typed_via.into();
271                }
272            }
273
274            let contact = resp.contact_header()?;
275
276            let contact_uri = if let Ok(typed_contact) = contact.typed() {
277                typed_contact.uri
278            } else {
279                let mut uri = extract_uri_from_contact(contact.value())?;
280                uri.headers.clear();
281                uri
282            };
283            uri = contact_uri;
284
285            // update route set from Record-Route header
286            let mut route_set = Vec::new();
287            for header in resp.headers.iter() {
288                if let Header::RecordRoute(record_route) = header {
289                    route_set.push(Header::Route(Route::from(record_route.value())));
290                }
291            }
292
293            route_set.reverse();
294            headers.extend(route_set);
295        }
296
297        headers.retain(|h| {
298            matches!(
299                h,
300                Header::Via(_)
301                    | Header::CallId(_)
302                    | Header::From(_)
303                    | Header::To(_)
304                    | Header::CSeq(_)
305                    | Header::Route(_)
306            )
307        });
308        headers.push(Header::MaxForwards(70.into()));
309        headers.iter_mut().for_each(|h| {
310            if let Header::CSeq(cseq) = h {
311                cseq.mut_method(rsip::Method::Ack).ok();
312            }
313        });
314        headers.unique_push(Header::UserAgent(self.user_agent.clone().into()));
315        Ok(rsip::Request {
316            method: rsip::Method::Ack,
317            uri,
318            headers: headers.into(),
319            body: vec![],
320            version: rsip::Version::V2,
321        })
322    }
323}