ftth_rsipstack/dialog/
mod.rs

1use crate::{Error, Result};
2use rsip::{
3    prelude::{HeadersExt, UntypedHeader},
4    Request, Response,
5};
6
7pub mod authenticate;
8pub mod client_dialog;
9pub mod dialog;
10pub mod dialog_layer;
11pub mod invitation;
12pub mod registration;
13pub mod server_dialog;
14
15#[cfg(test)]
16mod tests;
17
18/// SIP Dialog Identifier
19///
20/// `DialogId` uniquely identifies a SIP dialog. According to RFC 3261, a dialog is
21/// identified by the Call-ID, local tag, and remote tag.
22///
23/// # Fields
24///
25/// * `call_id` - The Call-ID header field value from SIP messages, identifying a call session
26/// * `from_tag` - The tag parameter from the From header field, identifying the dialog initiator
27/// * `to_tag` - The tag parameter from the To header field, identifying the dialog recipient
28///
29/// # Examples
30///
31/// ```rust
32/// use rsipstack::dialog::DialogId;
33///
34/// let dialog_id = DialogId {
35///     call_id: "1234567890@example.com".to_string(),
36///     from_tag: "alice-tag-123".to_string(),
37///     to_tag: "bob-tag-456".to_string(),
38/// };
39///
40/// println!("Dialog ID: {}", dialog_id);
41/// ```
42///
43/// # Notes
44///
45/// - During early dialog establishment, `to_tag` may be an empty string
46/// - Dialog ID remains constant throughout the dialog lifetime
47/// - Used for managing and routing SIP messages at the dialog layer
48#[derive(Clone, Debug)]
49pub struct DialogId {
50    pub call_id: String,
51    pub from_tag: String,
52    pub to_tag: String,
53}
54
55impl PartialEq for DialogId {
56    fn eq(&self, other: &DialogId) -> bool {
57        if self.call_id != other.call_id {
58            return false;
59        }
60        if self.from_tag == other.from_tag && self.to_tag == other.to_tag {
61            return true;
62        }
63        if self.from_tag == other.to_tag && self.to_tag == other.from_tag {
64            return true;
65        }
66        false
67    }
68}
69
70impl Eq for DialogId {}
71impl std::hash::Hash for DialogId {
72    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
73        self.call_id.hash(state);
74        if self.from_tag > self.to_tag {
75            self.from_tag.hash(state);
76            self.to_tag.hash(state);
77        } else {
78            self.to_tag.hash(state);
79            self.from_tag.hash(state);
80        }
81    }
82}
83
84impl TryFrom<&Request> for DialogId {
85    type Error = crate::Error;
86
87    fn try_from(request: &Request) -> Result<Self> {
88        let call_id = request.call_id_header()?.value().to_string();
89
90        let from_tag = match request.from_header()?.tag()? {
91            Some(tag) => tag.value().to_string(),
92            None => return Err(Error::Error("from tag not found".to_string())),
93        };
94
95        let to_tag = match request.to_header()?.tag()? {
96            Some(tag) => tag.value().to_string(),
97            None => "".to_string(),
98        };
99
100        Ok(DialogId {
101            call_id,
102            from_tag,
103            to_tag,
104        })
105    }
106}
107
108impl TryFrom<&Response> for DialogId {
109    type Error = crate::Error;
110
111    fn try_from(resp: &Response) -> Result<Self> {
112        let call_id = resp.call_id_header()?.value().to_string();
113
114        let from_tag = match resp.from_header()?.tag()? {
115            Some(tag) => tag.value().to_string(),
116            None => return Err(Error::Error("from tag not found".to_string())),
117        };
118
119        let to_tag = match resp.to_header()?.tag()? {
120            Some(tag) => tag.value().to_string(),
121            None => return Err(Error::Error("to tag not found".to_string())),
122        };
123
124        Ok(DialogId {
125            call_id,
126            from_tag,
127            to_tag,
128        })
129    }
130}
131
132impl std::fmt::Display for DialogId {
133    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134        if self.from_tag > self.to_tag {
135            write!(f, "{}-{}-{}", self.call_id, self.from_tag, self.to_tag)
136        } else {
137            write!(f, "{}-{}-{}", self.call_id, self.to_tag, self.from_tag)
138        }
139    }
140}