1use std::fmt;
6
7#[derive(Debug, Clone, PartialEq, Eq)]
9#[non_exhaustive]
10pub struct SipSecurityMechanism {
11 mechanism: String,
12 params: Vec<(String, Option<String>)>,
13}
14
15impl SipSecurityMechanism {
16 pub fn mechanism(&self) -> &str {
18 &self.mechanism
19 }
20
21 pub fn params(&self) -> &[(String, Option<String>)] {
23 &self.params
24 }
25
26 pub fn param(&self, key: &str) -> Option<Option<&str>> {
28 self.params
29 .iter()
30 .find(|(k, _)| k.eq_ignore_ascii_case(key))
31 .map(|(_, v)| v.as_deref())
32 }
33
34 pub fn q(&self) -> Option<&str> {
36 self.param("q")
37 .flatten()
38 }
39
40 pub fn d_alg(&self) -> Option<&str> {
42 self.param("d-alg")
43 .flatten()
44 }
45
46 pub fn d_qop(&self) -> Option<&str> {
48 self.param("d-qop")
49 .flatten()
50 }
51}
52
53impl fmt::Display for SipSecurityMechanism {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 write!(f, "{}", self.mechanism)?;
56 for (key, value) in &self.params {
57 match value {
58 Some(v) => write!(f, ";{key}={v}")?,
59 None => write!(f, ";{key}")?,
60 }
61 }
62 Ok(())
63 }
64}
65
66#[derive(Debug, Clone, PartialEq, Eq)]
68#[non_exhaustive]
69pub enum SipSecurityError {
70 Empty,
72 InvalidFormat(String),
74}
75
76impl fmt::Display for SipSecurityError {
77 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 match self {
79 Self::Empty => write!(f, "empty security mechanism value"),
80 Self::InvalidFormat(raw) => {
81 write!(f, "invalid security mechanism: {raw}")
82 }
83 }
84 }
85}
86
87impl std::error::Error for SipSecurityError {}
88
89fn parse_mechanism(raw: &str) -> Result<SipSecurityMechanism, SipSecurityError> {
90 let raw = raw.trim();
91 if raw.is_empty() {
92 return Err(SipSecurityError::InvalidFormat(raw.to_string()));
93 }
94
95 let (mechanism_part, params_part) = match raw.split_once(';') {
96 Some((m, p)) => (m.trim(), Some(p)),
97 None => (raw, None),
98 };
99
100 if mechanism_part.is_empty() {
101 return Err(SipSecurityError::InvalidFormat(raw.to_string()));
102 }
103
104 let mechanism = mechanism_part.to_ascii_lowercase();
105 let mut params = Vec::new();
106
107 if let Some(params_str) = params_part {
108 for segment in params_str.split(';') {
109 let segment = segment.trim();
110 if segment.is_empty() {
111 continue;
112 }
113 if let Some((key, value)) = segment.split_once('=') {
114 params.push((
115 key.trim()
116 .to_ascii_lowercase(),
117 Some(
118 value
119 .trim()
120 .trim_matches('"')
121 .to_string(),
122 ),
123 ));
124 } else {
125 params.push((segment.to_ascii_lowercase(), None));
126 }
127 }
128 }
129
130 Ok(SipSecurityMechanism { mechanism, params })
131}
132
133#[derive(Debug, Clone, PartialEq, Eq)]
135#[non_exhaustive]
136pub struct SipSecurity(Vec<SipSecurityMechanism>);
137
138impl SipSecurity {
139 pub fn parse(raw: &str) -> Result<Self, SipSecurityError> {
141 let raw = raw.trim();
142 if raw.is_empty() {
143 return Err(SipSecurityError::Empty);
144 }
145 Self::from_entries(crate::split_comma_entries(raw))
146 }
147
148 pub fn from_entries<'a>(
150 entries: impl IntoIterator<Item = &'a str>,
151 ) -> Result<Self, SipSecurityError> {
152 let entries: Vec<_> = entries
153 .into_iter()
154 .map(parse_mechanism)
155 .collect::<Result<_, _>>()?;
156 if entries.is_empty() {
157 return Err(SipSecurityError::Empty);
158 }
159 Ok(Self(entries))
160 }
161
162 pub fn entries(&self) -> &[SipSecurityMechanism] {
164 &self.0
165 }
166
167 pub fn into_entries(self) -> Vec<SipSecurityMechanism> {
169 self.0
170 }
171
172 pub fn len(&self) -> usize {
174 self.0
175 .len()
176 }
177
178 pub fn is_empty(&self) -> bool {
180 self.0
181 .is_empty()
182 }
183}
184
185impl fmt::Display for SipSecurity {
186 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187 crate::fmt_joined(f, &self.0, ", ")
188 }
189}
190
191impl_from_str_via_parse!(SipSecurity, SipSecurityError);
192
193impl<'a> IntoIterator for &'a SipSecurity {
194 type Item = &'a SipSecurityMechanism;
195 type IntoIter = std::slice::Iter<'a, SipSecurityMechanism>;
196
197 fn into_iter(self) -> Self::IntoIter {
198 self.0
199 .iter()
200 }
201}
202
203impl IntoIterator for SipSecurity {
204 type Item = SipSecurityMechanism;
205 type IntoIter = std::vec::IntoIter<SipSecurityMechanism>;
206
207 fn into_iter(self) -> Self::IntoIter {
208 self.0
209 .into_iter()
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216
217 #[test]
218 fn single_mechanism() {
219 let sec = SipSecurity::parse("digest;d-qop=auth-int;q=0.1").unwrap();
220 assert_eq!(sec.len(), 1);
221 assert_eq!(sec.entries()[0].mechanism(), "digest");
222 assert_eq!(sec.entries()[0].d_qop(), Some("auth-int"));
223 assert_eq!(sec.entries()[0].q(), Some("0.1"));
224 }
225
226 #[test]
227 fn multiple_mechanisms() {
228 let sec = SipSecurity::parse("tls;q=0.2, digest;d-qop=auth;q=0.1").unwrap();
229 assert_eq!(sec.len(), 2);
230 assert_eq!(sec.entries()[0].mechanism(), "tls");
231 assert_eq!(sec.entries()[1].mechanism(), "digest");
232 }
233
234 #[test]
235 fn mechanism_no_params() {
236 let sec = SipSecurity::parse("tls").unwrap();
237 assert_eq!(sec.len(), 1);
238 assert_eq!(sec.entries()[0].mechanism(), "tls");
239 assert!(sec.entries()[0]
240 .params()
241 .is_empty());
242 }
243
244 #[test]
245 fn empty_input() {
246 assert!(matches!(
247 SipSecurity::parse(""),
248 Err(SipSecurityError::Empty)
249 ));
250 }
251
252 #[test]
253 fn display_roundtrip() {
254 let raw = "digest;d-qop=auth;q=0.1";
255 let sec = SipSecurity::parse(raw).unwrap();
256 assert_eq!(sec.to_string(), raw);
257 }
258
259 #[test]
260 fn from_str() {
261 let sec: SipSecurity = "tls;q=0.2"
262 .parse()
263 .unwrap();
264 assert_eq!(sec.len(), 1);
265 }
266
267 #[test]
268 fn d_alg_param() {
269 let sec = SipSecurity::parse("digest;d-alg=MD5;d-qop=auth").unwrap();
270 assert_eq!(sec.entries()[0].d_alg(), Some("MD5"));
271 }
272
273 #[test]
274 fn from_entries_matches_parse() {
275 let split = SipSecurity::from_entries(["tls;q=0.2", "digest;d-qop=auth;q=0.1"]).unwrap();
276 let joined = SipSecurity::parse("tls;q=0.2, digest;d-qop=auth;q=0.1").unwrap();
277 assert_eq!(split, joined);
278 }
279
280 #[test]
281 fn from_entries_bad_entry_is_error() {
282 assert!(matches!(
283 SipSecurity::from_entries(["tls", " "]),
284 Err(SipSecurityError::InvalidFormat(_))
285 ));
286 }
287
288 #[test]
289 fn from_entries_empty_is_empty_error() {
290 assert!(matches!(
291 SipSecurity::from_entries(std::iter::empty::<&str>()),
292 Err(SipSecurityError::Empty)
293 ));
294 }
295}