1use crate::header::HeaderName;
2use crate::message::{SipMessage, SipMethod, StatusCode};
3use std::time::{Duration, Instant};
4
5pub const T1: Duration = Duration::from_millis(500);
7pub const T2: Duration = Duration::from_secs(4);
8pub const T4: Duration = Duration::from_secs(5);
9pub const TIMER_B: Duration = Duration::from_secs(32); pub const TIMER_D: Duration = Duration::from_secs(32);
11pub const TIMER_F: Duration = Duration::from_secs(32);
12pub const TIMER_H: Duration = Duration::from_secs(32);
13
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub enum TransactionKind {
16 ClientInvite,
17 ClientNonInvite,
18 ServerInvite,
19 ServerNonInvite,
20}
21
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub enum TransactionState {
24 Trying,
26 Proceeding,
28 Completed,
30 Confirmed,
32 Terminated,
34}
35
36#[derive(Debug, Clone)]
37pub struct SipTransaction {
38 pub id: String,
39 pub kind: TransactionKind,
40 pub state: TransactionState,
41 pub method: SipMethod,
42 pub branch: String,
43 pub call_id: String,
44 pub original_request: Option<SipMessage>,
45 pub last_response: Option<SipMessage>,
46 pub retransmit_count: u32,
47 pub created_at: Instant,
48 pub last_retransmit: Option<Instant>,
49}
50
51impl SipTransaction {
52 pub fn new_client(request: &SipMessage) -> Option<Self> {
54 if let SipMessage::Request(req) = request {
55 let branch = Self::extract_branch(&request)?;
56 let call_id = req.headers.get(&HeaderName::CallId)?.0.clone();
57
58 let kind = if req.method == SipMethod::Invite {
59 TransactionKind::ClientInvite
60 } else {
61 TransactionKind::ClientNonInvite
62 };
63
64 let id = format!("{}:{}", branch, req.method);
65
66 Some(Self {
67 id,
68 kind,
69 state: TransactionState::Trying,
70 method: req.method.clone(),
71 branch,
72 call_id,
73 original_request: Some(request.clone()),
74 last_response: None,
75 retransmit_count: 0,
76 created_at: Instant::now(),
77 last_retransmit: None,
78 })
79 } else {
80 None
81 }
82 }
83
84 pub fn new_server(request: &SipMessage) -> Option<Self> {
86 if let SipMessage::Request(req) = request {
87 let branch = Self::extract_branch(&request)?;
88 let call_id = req.headers.get(&HeaderName::CallId)?.0.clone();
89
90 let kind = if req.method == SipMethod::Invite {
91 TransactionKind::ServerInvite
92 } else {
93 TransactionKind::ServerNonInvite
94 };
95
96 let id = format!("{}:{}", branch, req.method);
97
98 Some(Self {
99 id,
100 kind,
101 state: TransactionState::Trying,
102 method: req.method.clone(),
103 branch,
104 call_id,
105 original_request: Some(request.clone()),
106 last_response: None,
107 retransmit_count: 0,
108 created_at: Instant::now(),
109 last_retransmit: None,
110 })
111 } else {
112 None
113 }
114 }
115
116 pub fn process_response(&mut self, response: &SipMessage) -> TransactionAction {
118 if let SipMessage::Response(res) = response {
119 match &self.kind {
120 TransactionKind::ClientInvite => {
121 self.process_client_invite_response(res.status)
122 }
123 TransactionKind::ClientNonInvite => {
124 self.process_client_non_invite_response(res.status)
125 }
126 _ => TransactionAction::None,
127 }
128 } else {
129 TransactionAction::None
130 }
131 }
132
133 fn process_client_invite_response(&mut self, status: StatusCode) -> TransactionAction {
134 match self.state {
135 TransactionState::Trying | TransactionState::Proceeding => {
136 if status.is_provisional() {
137 self.state = TransactionState::Proceeding;
138 TransactionAction::PassToTU
139 } else if status.is_success() {
140 self.state = TransactionState::Terminated;
141 TransactionAction::PassToTU
142 } else {
143 self.state = TransactionState::Completed;
145 TransactionAction::SendAck
146 }
147 }
148 TransactionState::Completed => {
149 TransactionAction::SendAck
151 }
152 _ => TransactionAction::None,
153 }
154 }
155
156 fn process_client_non_invite_response(&mut self, status: StatusCode) -> TransactionAction {
157 match self.state {
158 TransactionState::Trying | TransactionState::Proceeding => {
159 if status.is_provisional() {
160 self.state = TransactionState::Proceeding;
161 TransactionAction::PassToTU
162 } else {
163 self.state = TransactionState::Completed;
164 TransactionAction::PassToTU
165 }
166 }
167 _ => TransactionAction::None,
168 }
169 }
170
171 pub fn send_response(&mut self, response: &SipMessage) -> TransactionAction {
173 if let SipMessage::Response(res) = response {
174 self.last_response = Some(response.clone());
175
176 match &self.kind {
177 TransactionKind::ServerInvite => {
178 if res.status.is_provisional() {
179 self.state = TransactionState::Proceeding;
180 TransactionAction::SendResponse
181 } else if res.status.is_success() {
182 self.state = TransactionState::Terminated;
183 TransactionAction::SendResponse
184 } else {
185 self.state = TransactionState::Completed;
186 TransactionAction::SendResponse
187 }
188 }
189 TransactionKind::ServerNonInvite => {
190 if res.status.is_provisional() {
191 self.state = TransactionState::Proceeding;
192 TransactionAction::SendResponse
193 } else {
194 self.state = TransactionState::Completed;
195 TransactionAction::SendResponse
196 }
197 }
198 _ => TransactionAction::None,
199 }
200 } else {
201 TransactionAction::None
202 }
203 }
204
205 pub fn should_retransmit(&self) -> bool {
207 if self.kind != TransactionKind::ClientInvite && self.kind != TransactionKind::ClientNonInvite {
208 return false;
209 }
210
211 match self.state {
212 TransactionState::Trying => true,
213 TransactionState::Proceeding if self.kind == TransactionKind::ClientInvite => true,
214 _ => false,
215 }
216 }
217
218 pub fn retransmit_interval(&self) -> Duration {
220 let base = T1;
221 let multiplier = 2u32.pow(self.retransmit_count.min(6));
222 let interval = base * multiplier;
223
224 match self.kind {
225 TransactionKind::ClientInvite => interval.min(T2),
226 TransactionKind::ClientNonInvite => interval.min(T2),
227 _ => interval,
228 }
229 }
230
231 pub fn mark_retransmit(&mut self) {
233 self.retransmit_count += 1;
234 self.last_retransmit = Some(Instant::now());
235 }
236
237 pub fn is_timed_out(&self) -> bool {
239 let elapsed = self.created_at.elapsed();
240 match self.kind {
241 TransactionKind::ClientInvite => elapsed > TIMER_B,
242 TransactionKind::ClientNonInvite => elapsed > TIMER_F,
243 TransactionKind::ServerInvite => {
244 self.state == TransactionState::Completed && elapsed > TIMER_H
245 }
246 TransactionKind::ServerNonInvite => {
247 self.state == TransactionState::Completed && elapsed > Duration::from_secs(32)
248 }
249 }
250 }
251
252 pub fn is_terminated(&self) -> bool {
254 self.state == TransactionState::Terminated
255 }
256
257 pub fn matches(&self, msg: &SipMessage) -> bool {
259 if let Some(branch) = Self::extract_branch(msg) {
260 if branch == self.branch {
261 if let Some((_seq, method)) = msg.cseq() {
263 return method == self.method;
264 }
265 return true;
266 }
267 }
268 false
269 }
270
271 fn extract_branch(msg: &SipMessage) -> Option<String> {
272 let via = msg.headers().get(&HeaderName::Via)?;
273 let via_str = via.as_str();
274 for param in via_str.split(';') {
275 let param = param.trim();
276 if let Some(branch) = param.strip_prefix("branch=") {
277 return Some(branch.to_string());
278 }
279 }
280 None
281 }
282}
283
284#[derive(Debug, Clone, PartialEq, Eq)]
285pub enum TransactionAction {
286 None,
288 PassToTU,
290 SendAck,
292 SendResponse,
294}
295
296#[cfg(test)]
297mod tests {
298 use super::*;
299 use crate::header::Headers;
300 use crate::message::{SipRequest, SipResponse};
301
302 fn make_request(method: SipMethod, branch: &str, call_id: &str) -> SipMessage {
303 let mut headers = Headers::new();
304 headers.add(
305 HeaderName::Via,
306 format!("SIP/2.0/UDP 10.0.0.1:5060;branch={}", branch),
307 );
308 headers.add(HeaderName::From, "<sip:alice@a.com>;tag=t1");
309 headers.add(HeaderName::To, "<sip:bob@b.com>");
310 headers.add(HeaderName::CallId, call_id);
311 headers.add(
312 HeaderName::CSeq,
313 format!("1 {}", method.as_str()),
314 );
315 headers.add(HeaderName::ContentLength, "0");
316
317 SipMessage::Request(SipRequest {
318 method,
319 uri: "sip:bob@b.com".to_string(),
320 version: "SIP/2.0".to_string(),
321 headers,
322 body: None,
323 })
324 }
325
326 fn make_response(status: StatusCode, branch: &str, call_id: &str, method: &str) -> SipMessage {
327 let mut headers = Headers::new();
328 headers.add(
329 HeaderName::Via,
330 format!("SIP/2.0/UDP 10.0.0.1:5060;branch={}", branch),
331 );
332 headers.add(HeaderName::From, "<sip:alice@a.com>;tag=t1");
333 headers.add(HeaderName::To, "<sip:bob@b.com>;tag=t2");
334 headers.add(HeaderName::CallId, call_id);
335 headers.add(HeaderName::CSeq, format!("1 {}", method));
336 headers.add(HeaderName::ContentLength, "0");
337
338 SipMessage::Response(SipResponse {
339 version: "SIP/2.0".to_string(),
340 status,
341 reason: status.reason_phrase().to_string(),
342 headers,
343 body: None,
344 })
345 }
346
347 #[test]
348 fn test_create_client_invite_transaction() {
349 let req = make_request(SipMethod::Invite, "z9hG4bK776", "call-1");
350 let txn = SipTransaction::new_client(&req).unwrap();
351
352 assert_eq!(txn.kind, TransactionKind::ClientInvite);
353 assert_eq!(txn.state, TransactionState::Trying);
354 assert_eq!(txn.method, SipMethod::Invite);
355 assert_eq!(txn.branch, "z9hG4bK776");
356 assert_eq!(txn.call_id, "call-1");
357 }
358
359 #[test]
360 fn test_create_client_non_invite_transaction() {
361 let req = make_request(SipMethod::Register, "z9hG4bK777", "call-2");
362 let txn = SipTransaction::new_client(&req).unwrap();
363
364 assert_eq!(txn.kind, TransactionKind::ClientNonInvite);
365 assert_eq!(txn.state, TransactionState::Trying);
366 assert_eq!(txn.method, SipMethod::Register);
367 }
368
369 #[test]
370 fn test_create_server_transaction() {
371 let req = make_request(SipMethod::Invite, "z9hG4bK778", "call-3");
372 let txn = SipTransaction::new_server(&req).unwrap();
373
374 assert_eq!(txn.kind, TransactionKind::ServerInvite);
375 assert_eq!(txn.state, TransactionState::Trying);
376 }
377
378 #[test]
379 fn test_client_invite_provisional_response() {
380 let req = make_request(SipMethod::Invite, "z9hG4bK779", "call-4");
381 let mut txn = SipTransaction::new_client(&req).unwrap();
382
383 let ringing = make_response(StatusCode::RINGING, "z9hG4bK779", "call-4", "INVITE");
384 let action = txn.process_response(&ringing);
385
386 assert_eq!(action, TransactionAction::PassToTU);
387 assert_eq!(txn.state, TransactionState::Proceeding);
388 }
389
390 #[test]
391 fn test_client_invite_success_response() {
392 let req = make_request(SipMethod::Invite, "z9hG4bK780", "call-5");
393 let mut txn = SipTransaction::new_client(&req).unwrap();
394
395 let ok = make_response(StatusCode::OK, "z9hG4bK780", "call-5", "INVITE");
396 let action = txn.process_response(&ok);
397
398 assert_eq!(action, TransactionAction::PassToTU);
399 assert_eq!(txn.state, TransactionState::Terminated);
400 }
401
402 #[test]
403 fn test_client_invite_error_response() {
404 let req = make_request(SipMethod::Invite, "z9hG4bK781", "call-6");
405 let mut txn = SipTransaction::new_client(&req).unwrap();
406
407 let not_found = make_response(StatusCode::NOT_FOUND, "z9hG4bK781", "call-6", "INVITE");
408 let action = txn.process_response(¬_found);
409
410 assert_eq!(action, TransactionAction::SendAck);
411 assert_eq!(txn.state, TransactionState::Completed);
412 }
413
414 #[test]
415 fn test_client_non_invite_success() {
416 let req = make_request(SipMethod::Register, "z9hG4bK782", "call-7");
417 let mut txn = SipTransaction::new_client(&req).unwrap();
418
419 let ok = make_response(StatusCode::OK, "z9hG4bK782", "call-7", "REGISTER");
420 let action = txn.process_response(&ok);
421
422 assert_eq!(action, TransactionAction::PassToTU);
423 assert_eq!(txn.state, TransactionState::Completed);
424 }
425
426 #[test]
427 fn test_server_invite_provisional() {
428 let req = make_request(SipMethod::Invite, "z9hG4bK783", "call-8");
429 let mut txn = SipTransaction::new_server(&req).unwrap();
430
431 let ringing = make_response(StatusCode::RINGING, "z9hG4bK783", "call-8", "INVITE");
432 let action = txn.send_response(&ringing);
433
434 assert_eq!(action, TransactionAction::SendResponse);
435 assert_eq!(txn.state, TransactionState::Proceeding);
436 }
437
438 #[test]
439 fn test_server_invite_success() {
440 let req = make_request(SipMethod::Invite, "z9hG4bK784", "call-9");
441 let mut txn = SipTransaction::new_server(&req).unwrap();
442
443 let ok = make_response(StatusCode::OK, "z9hG4bK784", "call-9", "INVITE");
444 let action = txn.send_response(&ok);
445
446 assert_eq!(action, TransactionAction::SendResponse);
447 assert_eq!(txn.state, TransactionState::Terminated);
448 }
449
450 #[test]
451 fn test_transaction_matching() {
452 let req = make_request(SipMethod::Invite, "z9hG4bK785", "call-10");
453 let txn = SipTransaction::new_client(&req).unwrap();
454
455 let response = make_response(StatusCode::OK, "z9hG4bK785", "call-10", "INVITE");
457 assert!(txn.matches(&response));
458
459 let other = make_response(StatusCode::OK, "z9hG4bK999", "call-10", "INVITE");
461 assert!(!txn.matches(&other));
462 }
463
464 #[test]
465 fn test_retransmit_interval() {
466 let req = make_request(SipMethod::Invite, "z9hG4bK786", "call-11");
467 let mut txn = SipTransaction::new_client(&req).unwrap();
468
469 assert_eq!(txn.retransmit_interval(), T1); txn.mark_retransmit();
471 assert_eq!(txn.retransmit_interval(), T1 * 2); txn.mark_retransmit();
473 assert_eq!(txn.retransmit_interval(), T1 * 4); txn.mark_retransmit();
475 assert_eq!(txn.retransmit_interval(), T2); }
477
478 #[test]
479 fn test_should_retransmit() {
480 let req = make_request(SipMethod::Invite, "z9hG4bK787", "call-12");
481 let mut txn = SipTransaction::new_client(&req).unwrap();
482
483 assert!(txn.should_retransmit()); let ringing = make_response(StatusCode::RINGING, "z9hG4bK787", "call-12", "INVITE");
486 txn.process_response(&ringing);
487 assert!(txn.should_retransmit()); let ok = make_response(StatusCode::OK, "z9hG4bK787", "call-12", "INVITE");
490 txn.process_response(&ok);
491 assert!(!txn.should_retransmit()); }
493
494 #[test]
495 fn test_transaction_terminated() {
496 let req = make_request(SipMethod::Register, "z9hG4bK788", "call-13");
497 let mut txn = SipTransaction::new_client(&req).unwrap();
498
499 assert!(!txn.is_terminated());
500
501 let ok = make_response(StatusCode::OK, "z9hG4bK788", "call-13", "REGISTER");
502 txn.process_response(&ok);
503
504 assert!(!txn.is_terminated());
506 assert_eq!(txn.state, TransactionState::Completed);
507 }
508
509 #[test]
510 fn test_create_from_response_fails() {
511 let response = make_response(StatusCode::OK, "z9hG4bK789", "call-14", "INVITE");
512 assert!(SipTransaction::new_client(&response).is_none());
513 assert!(SipTransaction::new_server(&response).is_none());
514 }
515
516 #[test]
517 fn test_process_response_on_request() {
518 let req = make_request(SipMethod::Invite, "z9hG4bK790", "call-15");
519 let mut txn = SipTransaction::new_client(&req).unwrap();
520
521 let action = txn.process_response(&req);
523 assert_eq!(action, TransactionAction::None);
524 }
525
526 #[test]
527 fn test_server_non_invite_response() {
528 let req = make_request(SipMethod::Register, "z9hG4bK791", "call-16");
529 let mut txn = SipTransaction::new_server(&req).unwrap();
530
531 assert_eq!(txn.kind, TransactionKind::ServerNonInvite);
532
533 let ok = make_response(StatusCode::OK, "z9hG4bK791", "call-16", "REGISTER");
534 let action = txn.send_response(&ok);
535 assert_eq!(action, TransactionAction::SendResponse);
536 assert_eq!(txn.state, TransactionState::Completed);
537 }
538}