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}