1use crate::header::{extract_tag, extract_uri, HeaderName};
2use crate::message::{SipMessage, SipMethod};
3
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub enum DialogState {
6 Early,
8 Confirmed,
10 Terminated,
12}
13
14#[derive(Debug, Clone)]
15pub struct SipDialog {
16 pub call_id: String,
17 pub local_tag: String,
18 pub remote_tag: Option<String>,
19 pub local_uri: String,
20 pub remote_uri: String,
21 pub remote_target: Option<String>,
22 pub local_cseq: u32,
23 pub remote_cseq: Option<u32>,
24 pub state: DialogState,
25}
26
27impl SipDialog {
28 pub fn new_uac(call_id: String, local_tag: String, local_uri: String, remote_uri: String) -> Self {
30 Self {
31 call_id,
32 local_tag,
33 remote_tag: None,
34 local_uri,
35 remote_uri,
36 remote_target: None,
37 local_cseq: 1,
38 remote_cseq: None,
39 state: DialogState::Early,
40 }
41 }
42
43 pub fn new_uas(
45 call_id: String,
46 local_tag: String,
47 remote_tag: String,
48 local_uri: String,
49 remote_uri: String,
50 ) -> Self {
51 Self {
52 call_id,
53 local_tag,
54 remote_tag: Some(remote_tag),
55 local_uri,
56 remote_uri,
57 remote_target: None,
58 local_cseq: 1,
59 remote_cseq: None,
60 state: DialogState::Early,
61 }
62 }
63
64 pub fn from_invite(msg: &SipMessage) -> Option<Self> {
66 if let SipMessage::Request(req) = msg {
67 if req.method != SipMethod::Invite {
68 return None;
69 }
70
71 let call_id = req.headers.get(&HeaderName::CallId)?.0.clone();
72
73 let from_val = req.headers.get(&HeaderName::From)?.as_str();
74 let remote_tag = extract_tag(from_val)?;
75 let remote_uri = extract_uri(from_val)?;
76
77 let to_val = req.headers.get(&HeaderName::To)?.as_str();
78 let local_uri = extract_uri(to_val)?;
79
80 let local_tag = crate::header::generate_tag();
81
82 let contact = req
83 .headers
84 .get(&HeaderName::Contact)
85 .and_then(|v| extract_uri(v.as_str()));
86
87 Some(Self {
88 call_id,
89 local_tag,
90 remote_tag: Some(remote_tag),
91 local_uri,
92 remote_uri,
93 remote_target: contact,
94 local_cseq: 1,
95 remote_cseq: None,
96 state: DialogState::Early,
97 })
98 } else {
99 None
100 }
101 }
102
103 pub fn process_response(&mut self, msg: &SipMessage) -> bool {
105 if let SipMessage::Response(res) = msg {
106 if let Some(call_id) = res.headers.get(&HeaderName::CallId) {
108 if call_id.0 != self.call_id {
109 return false;
110 }
111 } else {
112 return false;
113 }
114
115 if let Some(to_val) = res.headers.get(&HeaderName::To) {
117 if let Some(tag) = extract_tag(to_val.as_str()) {
118 self.remote_tag = Some(tag);
119 }
120 }
121
122 if let Some(contact) = res.headers.get(&HeaderName::Contact) {
124 self.remote_target = extract_uri(contact.as_str());
125 }
126
127 match res.status.0 {
129 100..=199 => {
130 if self.state == DialogState::Early {
132 }
134 }
135 200..=299 => {
136 self.state = DialogState::Confirmed;
137 }
138 300..=699 => {
139 self.state = DialogState::Terminated;
140 }
141 _ => {}
142 }
143
144 true
145 } else {
146 false
147 }
148 }
149
150 pub fn process_bye(&mut self, msg: &SipMessage) -> bool {
152 if let SipMessage::Request(req) = msg {
153 if req.method == SipMethod::Bye {
154 if let Some(call_id) = req.headers.get(&HeaderName::CallId) {
155 if call_id.0 == self.call_id {
156 self.state = DialogState::Terminated;
157 return true;
158 }
159 }
160 }
161 }
162 false
163 }
164
165 pub fn terminate(&mut self) {
167 self.state = DialogState::Terminated;
168 }
169
170 pub fn next_cseq(&mut self) -> u32 {
172 self.local_cseq += 1;
173 self.local_cseq
174 }
175
176 pub fn matches(&self, msg: &SipMessage) -> bool {
178 let headers = msg.headers();
179
180 if let Some(call_id) = headers.get(&HeaderName::CallId) {
182 if call_id.0 != self.call_id {
183 return false;
184 }
185 } else {
186 return false;
187 }
188
189 if let Some(from_val) = headers.get(&HeaderName::From) {
191 let from_tag = extract_tag(from_val.as_str());
192 if let Some(to_val) = headers.get(&HeaderName::To) {
193 let to_tag = extract_tag(to_val.as_str());
194
195 if msg.is_request() {
197 if let Some(ref rt) = self.remote_tag {
198 if from_tag.as_deref() != Some(rt.as_str()) {
199 return false;
200 }
201 }
202 if to_tag.as_deref() != Some(self.local_tag.as_str()) {
203 return false;
204 }
205 } else {
206 if let Some(ref rt) = self.remote_tag {
208 if to_tag.as_deref() != Some(rt.as_str())
209 && from_tag.as_deref() != Some(self.local_tag.as_str())
210 {
211 return false;
212 }
213 }
214 }
215 }
216 }
217
218 true
219 }
220
221 pub fn is_confirmed(&self) -> bool {
222 self.state == DialogState::Confirmed
223 }
224
225 pub fn is_terminated(&self) -> bool {
226 self.state == DialogState::Terminated
227 }
228
229 pub fn is_early(&self) -> bool {
230 self.state == DialogState::Early
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237 use crate::header::Headers;
238 use crate::message::{SipRequest, SipResponse, StatusCode};
239
240 fn make_invite() -> SipMessage {
241 let mut headers = Headers::new();
242 headers.add(
243 HeaderName::Via,
244 "SIP/2.0/UDP 10.0.0.1:5060;branch=z9hG4bK776",
245 );
246 headers.add(
247 HeaderName::From,
248 "\"Alice\" <sip:alice@atlanta.com>;tag=abc123",
249 );
250 headers.add(HeaderName::To, "<sip:bob@biloxi.com>");
251 headers.add(HeaderName::CallId, "test-call-id-12345");
252 headers.add(HeaderName::CSeq, "1 INVITE");
253 headers.add(HeaderName::Contact, "<sip:alice@10.0.0.1:5060>");
254 headers.add(HeaderName::MaxForwards, "70");
255 headers.add(HeaderName::ContentLength, "0");
256
257 SipMessage::Request(SipRequest {
258 method: SipMethod::Invite,
259 uri: "sip:bob@biloxi.com".to_string(),
260 version: "SIP/2.0".to_string(),
261 headers,
262 body: None,
263 })
264 }
265
266 fn make_200_ok(call_id: &str, from_tag: &str, to_tag: &str) -> SipMessage {
267 let mut headers = Headers::new();
268 headers.add(
269 HeaderName::Via,
270 "SIP/2.0/UDP 10.0.0.1:5060;branch=z9hG4bK776",
271 );
272 headers.add(
273 HeaderName::From,
274 format!("<sip:alice@atlanta.com>;tag={}", from_tag),
275 );
276 headers.add(
277 HeaderName::To,
278 format!("<sip:bob@biloxi.com>;tag={}", to_tag),
279 );
280 headers.add(HeaderName::CallId, call_id);
281 headers.add(HeaderName::CSeq, "1 INVITE");
282 headers.add(HeaderName::Contact, "<sip:bob@10.0.0.2:5060>");
283 headers.add(HeaderName::ContentLength, "0");
284
285 SipMessage::Response(SipResponse {
286 version: "SIP/2.0".to_string(),
287 status: StatusCode::OK,
288 reason: "OK".to_string(),
289 headers,
290 body: None,
291 })
292 }
293
294 fn make_bye(call_id: &str, from_tag: &str, to_tag: &str) -> SipMessage {
295 let mut headers = Headers::new();
296 headers.add(
297 HeaderName::Via,
298 "SIP/2.0/UDP 10.0.0.2:5060;branch=z9hG4bKbye",
299 );
300 headers.add(
301 HeaderName::From,
302 format!("<sip:bob@biloxi.com>;tag={}", from_tag),
303 );
304 headers.add(
305 HeaderName::To,
306 format!("<sip:alice@atlanta.com>;tag={}", to_tag),
307 );
308 headers.add(HeaderName::CallId, call_id);
309 headers.add(HeaderName::CSeq, "1 BYE");
310 headers.add(HeaderName::ContentLength, "0");
311
312 SipMessage::Request(SipRequest {
313 method: SipMethod::Bye,
314 uri: "sip:alice@10.0.0.1:5060".to_string(),
315 version: "SIP/2.0".to_string(),
316 headers,
317 body: None,
318 })
319 }
320
321 #[test]
322 fn test_dialog_new_uac() {
323 let dialog = SipDialog::new_uac(
324 "call-123".to_string(),
325 "tag-local".to_string(),
326 "sip:alice@atlanta.com".to_string(),
327 "sip:bob@biloxi.com".to_string(),
328 );
329
330 assert_eq!(dialog.state, DialogState::Early);
331 assert_eq!(dialog.call_id, "call-123");
332 assert_eq!(dialog.local_tag, "tag-local");
333 assert!(dialog.remote_tag.is_none());
334 assert!(dialog.is_early());
335 }
336
337 #[test]
338 fn test_dialog_from_invite() {
339 let invite = make_invite();
340 let dialog = SipDialog::from_invite(&invite).unwrap();
341
342 assert_eq!(dialog.call_id, "test-call-id-12345");
343 assert_eq!(dialog.remote_tag, Some("abc123".to_string()));
344 assert_eq!(dialog.remote_uri, "sip:alice@atlanta.com");
345 assert_eq!(dialog.local_uri, "sip:bob@biloxi.com");
346 assert_eq!(dialog.state, DialogState::Early);
347 }
348
349 #[test]
350 fn test_dialog_from_invite_requires_invite_method() {
351 let mut headers = Headers::new();
352 headers.add(HeaderName::From, "<sip:alice@atlanta.com>;tag=abc");
353 headers.add(HeaderName::To, "<sip:bob@biloxi.com>");
354 headers.add(HeaderName::CallId, "test");
355
356 let bye = SipMessage::Request(SipRequest {
357 method: SipMethod::Bye,
358 uri: "sip:bob@biloxi.com".to_string(),
359 version: "SIP/2.0".to_string(),
360 headers,
361 body: None,
362 });
363
364 assert!(SipDialog::from_invite(&bye).is_none());
365 }
366
367 #[test]
368 fn test_dialog_process_response_ok() {
369 let mut dialog = SipDialog::new_uac(
370 "test-call-id-12345".to_string(),
371 "local-tag".to_string(),
372 "sip:alice@atlanta.com".to_string(),
373 "sip:bob@biloxi.com".to_string(),
374 );
375
376 let ok = make_200_ok("test-call-id-12345", "local-tag", "remote-tag");
377 assert!(dialog.process_response(&ok));
378 assert_eq!(dialog.state, DialogState::Confirmed);
379 assert_eq!(dialog.remote_tag, Some("remote-tag".to_string()));
380 assert!(dialog.is_confirmed());
381 }
382
383 #[test]
384 fn test_dialog_process_response_wrong_callid() {
385 let mut dialog = SipDialog::new_uac(
386 "call-1".to_string(),
387 "tag-1".to_string(),
388 "sip:alice@a.com".to_string(),
389 "sip:bob@b.com".to_string(),
390 );
391
392 let ok = make_200_ok("call-2", "tag-1", "tag-remote");
393 assert!(!dialog.process_response(&ok));
394 assert_eq!(dialog.state, DialogState::Early);
395 }
396
397 #[test]
398 fn test_dialog_process_bye() {
399 let mut dialog = SipDialog::new_uac(
400 "call-123".to_string(),
401 "local-tag".to_string(),
402 "sip:alice@atlanta.com".to_string(),
403 "sip:bob@biloxi.com".to_string(),
404 );
405 dialog.state = DialogState::Confirmed;
406 dialog.remote_tag = Some("remote-tag".to_string());
407
408 let bye = make_bye("call-123", "remote-tag", "local-tag");
409 assert!(dialog.process_bye(&bye));
410 assert_eq!(dialog.state, DialogState::Terminated);
411 assert!(dialog.is_terminated());
412 }
413
414 #[test]
415 fn test_dialog_terminate() {
416 let mut dialog = SipDialog::new_uac(
417 "call-1".to_string(),
418 "t1".to_string(),
419 "sip:a@a.com".to_string(),
420 "sip:b@b.com".to_string(),
421 );
422 dialog.state = DialogState::Confirmed;
423 dialog.terminate();
424 assert_eq!(dialog.state, DialogState::Terminated);
425 }
426
427 #[test]
428 fn test_dialog_next_cseq() {
429 let mut dialog = SipDialog::new_uac(
430 "call-1".to_string(),
431 "t1".to_string(),
432 "sip:a@a.com".to_string(),
433 "sip:b@b.com".to_string(),
434 );
435 assert_eq!(dialog.next_cseq(), 2);
436 assert_eq!(dialog.next_cseq(), 3);
437 assert_eq!(dialog.next_cseq(), 4);
438 }
439
440 #[test]
441 fn test_dialog_provisional_keeps_early() {
442 let mut dialog = SipDialog::new_uac(
443 "call-prov".to_string(),
444 "local-tag".to_string(),
445 "sip:alice@a.com".to_string(),
446 "sip:bob@b.com".to_string(),
447 );
448
449 let mut headers = Headers::new();
450 headers.add(HeaderName::Via, "SIP/2.0/UDP 10.0.0.1:5060;branch=z9hG4bK1");
451 headers.add(HeaderName::From, "<sip:alice@a.com>;tag=local-tag");
452 headers.add(HeaderName::To, "<sip:bob@b.com>;tag=remote-tag");
453 headers.add(HeaderName::CallId, "call-prov");
454 headers.add(HeaderName::CSeq, "1 INVITE");
455 headers.add(HeaderName::ContentLength, "0");
456
457 let ringing = SipMessage::Response(SipResponse {
458 version: "SIP/2.0".to_string(),
459 status: StatusCode::RINGING,
460 reason: "Ringing".to_string(),
461 headers,
462 body: None,
463 });
464
465 assert!(dialog.process_response(&ringing));
466 assert_eq!(dialog.state, DialogState::Early);
467 assert_eq!(dialog.remote_tag, Some("remote-tag".to_string()));
468 }
469
470 #[test]
471 fn test_dialog_error_response_terminates() {
472 let mut dialog = SipDialog::new_uac(
473 "call-err".to_string(),
474 "local-tag".to_string(),
475 "sip:alice@a.com".to_string(),
476 "sip:bob@b.com".to_string(),
477 );
478
479 let mut headers = Headers::new();
480 headers.add(HeaderName::Via, "SIP/2.0/UDP 10.0.0.1:5060;branch=z9hG4bK1");
481 headers.add(HeaderName::From, "<sip:alice@a.com>;tag=local-tag");
482 headers.add(HeaderName::To, "<sip:bob@b.com>;tag=remote-tag");
483 headers.add(HeaderName::CallId, "call-err");
484 headers.add(HeaderName::CSeq, "1 INVITE");
485 headers.add(HeaderName::ContentLength, "0");
486
487 let not_found = SipMessage::Response(SipResponse {
488 version: "SIP/2.0".to_string(),
489 status: StatusCode::NOT_FOUND,
490 reason: "Not Found".to_string(),
491 headers,
492 body: None,
493 });
494
495 assert!(dialog.process_response(¬_found));
496 assert_eq!(dialog.state, DialogState::Terminated);
497 }
498
499 #[test]
500 fn test_dialog_new_uas() {
501 let dialog = SipDialog::new_uas(
502 "call-uas".to_string(),
503 "local-tag".to_string(),
504 "remote-tag".to_string(),
505 "sip:bob@b.com".to_string(),
506 "sip:alice@a.com".to_string(),
507 );
508
509 assert_eq!(dialog.state, DialogState::Early);
510 assert_eq!(dialog.remote_tag, Some("remote-tag".to_string()));
511 }
512}