ftth_rsipstack/transaction/
message.rs

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