freeswitch_types/
sip_header.rs1use crate::sip_header_addr::{ParseSipHeaderAddrError, SipHeaderAddr};
8use crate::variables::{SipCallInfo, SipCallInfoError};
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct ParseSipHeaderError(pub String);
13
14impl std::fmt::Display for ParseSipHeaderError {
15 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16 write!(f, "unknown SIP header: {}", self.0)
17 }
18}
19
20impl std::error::Error for ParseSipHeaderError {}
21
22define_header_enum! {
23 error_type: ParseSipHeaderError,
24 pub enum SipHeader {
30 CallInfo => "Call-Info",
32 PAssertedIdentity => "P-Asserted-Identity",
34 }
35}
36
37impl SipHeader {
38 pub fn extract_from(&self, message: &str) -> Option<String> {
43 crate::sip_message::extract_header(message, self.as_str())
44 }
45}
46
47pub trait SipHeaderLookup {
73 fn sip_header_str(&self, name: &str) -> Option<&str>;
75
76 fn sip_header(&self, name: SipHeader) -> Option<&str> {
78 self.sip_header_str(name.as_str())
79 }
80
81 fn call_info_raw(&self) -> Option<&str> {
83 self.sip_header(SipHeader::CallInfo)
84 }
85
86 fn call_info(&self) -> Result<Option<SipCallInfo>, SipCallInfoError> {
90 match self.call_info_raw() {
91 Some(s) => SipCallInfo::parse(s).map(Some),
92 None => Ok(None),
93 }
94 }
95
96 fn p_asserted_identity_raw(&self) -> Option<&str> {
98 self.sip_header(SipHeader::PAssertedIdentity)
99 }
100
101 fn p_asserted_identity(&self) -> Result<Option<SipHeaderAddr>, ParseSipHeaderAddrError> {
105 match self.p_asserted_identity_raw() {
106 Some(s) => s
107 .parse::<SipHeaderAddr>()
108 .map(Some),
109 None => Ok(None),
110 }
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117 use std::collections::HashMap;
118
119 #[test]
120 fn display_round_trip() {
121 assert_eq!(SipHeader::CallInfo.to_string(), "Call-Info");
122 assert_eq!(
123 SipHeader::PAssertedIdentity.to_string(),
124 "P-Asserted-Identity"
125 );
126 }
127
128 #[test]
129 fn as_ref_str() {
130 let h: &str = SipHeader::CallInfo.as_ref();
131 assert_eq!(h, "Call-Info");
132 }
133
134 #[test]
135 fn from_str_case_insensitive() {
136 assert_eq!("call-info".parse::<SipHeader>(), Ok(SipHeader::CallInfo));
137 assert_eq!("CALL-INFO".parse::<SipHeader>(), Ok(SipHeader::CallInfo));
138 assert_eq!(
139 "p-asserted-identity".parse::<SipHeader>(),
140 Ok(SipHeader::PAssertedIdentity)
141 );
142 assert_eq!(
143 "P-ASSERTED-IDENTITY".parse::<SipHeader>(),
144 Ok(SipHeader::PAssertedIdentity)
145 );
146 }
147
148 #[test]
149 fn from_str_unknown() {
150 assert!("X-Custom"
151 .parse::<SipHeader>()
152 .is_err());
153 }
154
155 #[test]
156 fn from_str_round_trip_all() {
157 let variants = [SipHeader::CallInfo, SipHeader::PAssertedIdentity];
158 for v in variants {
159 let wire = v.to_string();
160 let parsed: SipHeader = wire
161 .parse()
162 .unwrap();
163 assert_eq!(parsed, v, "round-trip failed for {wire}");
164 }
165 }
166
167 fn headers_with(pairs: &[(&str, &str)]) -> HashMap<String, String> {
168 pairs
169 .iter()
170 .map(|(k, v)| (k.to_string(), v.to_string()))
171 .collect()
172 }
173
174 #[test]
175 fn sip_header_by_enum() {
176 let h = headers_with(&[("Call-Info", "<urn:x>;purpose=icon")]);
177 assert_eq!(
178 h.sip_header(SipHeader::CallInfo),
179 Some("<urn:x>;purpose=icon")
180 );
181 }
182
183 #[test]
184 fn call_info_raw_lookup() {
185 let h = headers_with(&[(
186 "Call-Info",
187 "<urn:emergency:uid:callid:test:bcf.example.com>;purpose=emergency-CallId",
188 )]);
189 assert_eq!(
190 h.call_info_raw(),
191 Some("<urn:emergency:uid:callid:test:bcf.example.com>;purpose=emergency-CallId")
192 );
193 }
194
195 #[test]
196 fn call_info_typed() {
197 let h = headers_with(&[(
198 "Call-Info",
199 "<urn:emergency:uid:callid:test:bcf.example.com>;purpose=emergency-CallId",
200 )]);
201 let ci = h
202 .call_info()
203 .unwrap()
204 .unwrap();
205 assert_eq!(ci.len(), 1);
206 assert_eq!(ci.entries()[0].purpose(), Some("emergency-CallId"));
207 }
208
209 #[test]
210 fn call_info_absent() {
211 let h = headers_with(&[]);
212 assert_eq!(
213 h.call_info()
214 .unwrap(),
215 None
216 );
217 }
218
219 #[test]
220 fn p_asserted_identity_raw_lookup() {
221 let h = headers_with(&[(
222 "P-Asserted-Identity",
223 r#""EXAMPLE CO" <sip:+15551234567@198.51.100.1>"#,
224 )]);
225 assert_eq!(
226 h.p_asserted_identity_raw(),
227 Some(r#""EXAMPLE CO" <sip:+15551234567@198.51.100.1>"#)
228 );
229 }
230
231 #[test]
232 fn p_asserted_identity_typed() {
233 let h = headers_with(&[(
234 "P-Asserted-Identity",
235 r#""EXAMPLE CO" <sip:+15551234567@198.51.100.1>"#,
236 )]);
237 let pai = h
238 .p_asserted_identity()
239 .unwrap()
240 .unwrap();
241 assert_eq!(pai.display_name(), Some("EXAMPLE CO"));
242 }
243
244 #[test]
245 fn p_asserted_identity_absent() {
246 let h = headers_with(&[]);
247 assert_eq!(
248 h.p_asserted_identity()
249 .unwrap(),
250 None
251 );
252 }
253
254 #[test]
255 fn extract_from_sip_message() {
256 let msg = concat!(
257 "INVITE sip:bob@host SIP/2.0\r\n",
258 "Call-Info: <urn:emergency:uid:callid:abc>;purpose=emergency-CallId\r\n",
259 "P-Asserted-Identity: \"Corp\" <sip:+15551234567@198.51.100.1>\r\n",
260 "\r\n",
261 );
262 assert_eq!(
263 SipHeader::CallInfo.extract_from(msg),
264 Some("<urn:emergency:uid:callid:abc>;purpose=emergency-CallId".into())
265 );
266 assert_eq!(
267 SipHeader::PAssertedIdentity.extract_from(msg),
268 Some("\"Corp\" <sip:+15551234567@198.51.100.1>".into())
269 );
270 }
271
272 #[test]
273 fn extract_from_missing() {
274 let msg = concat!(
275 "INVITE sip:bob@host SIP/2.0\r\n",
276 "From: Alice <sip:alice@host>\r\n",
277 "\r\n",
278 );
279 assert_eq!(SipHeader::CallInfo.extract_from(msg), None);
280 assert_eq!(SipHeader::PAssertedIdentity.extract_from(msg), None);
281 }
282
283 #[test]
284 fn missing_headers_return_none() {
285 let h = headers_with(&[]);
286 assert_eq!(h.call_info_raw(), None);
287 assert_eq!(
288 h.call_info()
289 .unwrap(),
290 None
291 );
292 assert_eq!(h.p_asserted_identity_raw(), None);
293 assert_eq!(
294 h.p_asserted_identity()
295 .unwrap(),
296 None
297 );
298 }
299}