Skip to main content

sip_core/
transaction.rs

1use crate::header::HeaderName;
2use crate::message::{SipMessage, SipMethod, StatusCode};
3use std::time::{Duration, Instant};
4
5/// SIP transaction timer values (RFC 3261 Section 17)
6pub 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); // 64*T1
10pub 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    /// Initial state — request not yet sent/received
25    Trying,
26    /// INVITE client: 1xx received
27    Proceeding,
28    /// INVITE client: 2xx received (pass to TU), INVITE server: 2xx sent
29    Completed,
30    /// ACK sent (INVITE client) or ACK received (INVITE server)
31    Confirmed,
32    /// Transaction is done and can be cleaned up
33    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    /// Create a new client transaction from an outgoing request
53    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    /// Create a new server transaction from an incoming request
85    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    /// Process an incoming response for a client transaction
117    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                    // 3xx-6xx
144                    self.state = TransactionState::Completed;
145                    TransactionAction::SendAck
146                }
147            }
148            TransactionState::Completed => {
149                // Retransmission of final response
150                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    /// Process an outgoing response for a server transaction
172    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    /// Check if the transaction should retransmit (for unreliable transport)
206    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    /// Get the next retransmit interval
219    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    /// Mark that a retransmission was done
232    pub fn mark_retransmit(&mut self) {
233        self.retransmit_count += 1;
234        self.last_retransmit = Some(Instant::now());
235    }
236
237    /// Check if the transaction has timed out
238    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    /// Check if the transaction is in a terminal state
253    pub fn is_terminated(&self) -> bool {
254        self.state == TransactionState::Terminated
255    }
256
257    /// Check if this transaction matches a given message
258    pub fn matches(&self, msg: &SipMessage) -> bool {
259        if let Some(branch) = Self::extract_branch(msg) {
260            if branch == self.branch {
261                // Also check method for CANCEL matching
262                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    /// No action needed
287    None,
288    /// Pass the message to the Transaction User (dialog layer)
289    PassToTU,
290    /// Send an ACK for a non-2xx final response
291    SendAck,
292    /// Send the response on the transport
293    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(&not_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        // Same branch should match
456        let response = make_response(StatusCode::OK, "z9hG4bK785", "call-10", "INVITE");
457        assert!(txn.matches(&response));
458
459        // Different branch should not match
460        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); // 500ms
470        txn.mark_retransmit();
471        assert_eq!(txn.retransmit_interval(), T1 * 2); // 1000ms
472        txn.mark_retransmit();
473        assert_eq!(txn.retransmit_interval(), T1 * 4); // 2000ms
474        txn.mark_retransmit();
475        assert_eq!(txn.retransmit_interval(), T2); // 4000ms (capped)
476    }
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()); // Trying state
484
485        let ringing = make_response(StatusCode::RINGING, "z9hG4bK787", "call-12", "INVITE");
486        txn.process_response(&ringing);
487        assert!(txn.should_retransmit()); // Proceeding state for INVITE
488
489        let ok = make_response(StatusCode::OK, "z9hG4bK787", "call-12", "INVITE");
490        txn.process_response(&ok);
491        assert!(!txn.should_retransmit()); // Terminated
492    }
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        // Non-invite goes to Completed, not Terminated
505        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        // Passing a request to process_response should return None action
522        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}