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}