Skip to main content

sip_core/
header.rs

1use std::fmt;
2
3#[derive(Debug, Clone, PartialEq, Eq, Hash)]
4pub enum HeaderName {
5    Via,
6    From,
7    To,
8    CallId,
9    CSeq,
10    Contact,
11    MaxForwards,
12    ContentType,
13    ContentLength,
14    Authorization,
15    WwwAuthenticate,
16    ProxyAuthenticate,
17    ProxyAuthorization,
18    Expires,
19    UserAgent,
20    Allow,
21    Supported,
22    Require,
23    RAck,
24    RSeq,
25    ReferTo,
26    ReferredBy,
27    Event,
28    SubscriptionState,
29    Other(String),
30}
31
32impl HeaderName {
33    pub fn from_str(s: &str) -> Self {
34        match s.to_lowercase().as_str() {
35            "via" | "v" => HeaderName::Via,
36            "from" | "f" => HeaderName::From,
37            "to" | "t" => HeaderName::To,
38            "call-id" | "i" => HeaderName::CallId,
39            "cseq" => HeaderName::CSeq,
40            "contact" | "m" => HeaderName::Contact,
41            "max-forwards" => HeaderName::MaxForwards,
42            "content-type" | "c" => HeaderName::ContentType,
43            "content-length" | "l" => HeaderName::ContentLength,
44            "authorization" => HeaderName::Authorization,
45            "www-authenticate" => HeaderName::WwwAuthenticate,
46            "proxy-authenticate" => HeaderName::ProxyAuthenticate,
47            "proxy-authorization" => HeaderName::ProxyAuthorization,
48            "expires" => HeaderName::Expires,
49            "user-agent" => HeaderName::UserAgent,
50            "allow" => HeaderName::Allow,
51            "supported" | "k" => HeaderName::Supported,
52            "require" => HeaderName::Require,
53            "rack" => HeaderName::RAck,
54            "rseq" => HeaderName::RSeq,
55            "refer-to" | "r" => HeaderName::ReferTo,
56            "referred-by" | "b" => HeaderName::ReferredBy,
57            "event" | "o" => HeaderName::Event,
58            "subscription-state" => HeaderName::SubscriptionState,
59            other => HeaderName::Other(other.to_string()),
60        }
61    }
62
63    pub fn as_str(&self) -> &str {
64        match self {
65            HeaderName::Via => "Via",
66            HeaderName::From => "From",
67            HeaderName::To => "To",
68            HeaderName::CallId => "Call-ID",
69            HeaderName::CSeq => "CSeq",
70            HeaderName::Contact => "Contact",
71            HeaderName::MaxForwards => "Max-Forwards",
72            HeaderName::ContentType => "Content-Type",
73            HeaderName::ContentLength => "Content-Length",
74            HeaderName::Authorization => "Authorization",
75            HeaderName::WwwAuthenticate => "WWW-Authenticate",
76            HeaderName::ProxyAuthenticate => "Proxy-Authenticate",
77            HeaderName::ProxyAuthorization => "Proxy-Authorization",
78            HeaderName::Expires => "Expires",
79            HeaderName::UserAgent => "User-Agent",
80            HeaderName::Allow => "Allow",
81            HeaderName::Supported => "Supported",
82            HeaderName::Require => "Require",
83            HeaderName::RAck => "RAck",
84            HeaderName::RSeq => "RSeq",
85            HeaderName::ReferTo => "Refer-To",
86            HeaderName::ReferredBy => "Referred-By",
87            HeaderName::Event => "Event",
88            HeaderName::SubscriptionState => "Subscription-State",
89            HeaderName::Other(s) => s.as_str(),
90        }
91    }
92}
93
94impl fmt::Display for HeaderName {
95    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96        write!(f, "{}", self.as_str())
97    }
98}
99
100#[derive(Debug, Clone, PartialEq, Eq)]
101pub struct HeaderValue(pub String);
102
103impl HeaderValue {
104    pub fn new(s: impl Into<String>) -> Self {
105        Self(s.into())
106    }
107
108    pub fn as_str(&self) -> &str {
109        &self.0
110    }
111}
112
113impl fmt::Display for HeaderValue {
114    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115        write!(f, "{}", self.0)
116    }
117}
118
119#[derive(Debug, Clone, PartialEq, Eq)]
120pub struct Header {
121    pub name: HeaderName,
122    pub value: HeaderValue,
123}
124
125impl Header {
126    pub fn new(name: HeaderName, value: impl Into<String>) -> Self {
127        Self {
128            name,
129            value: HeaderValue::new(value),
130        }
131    }
132}
133
134impl fmt::Display for Header {
135    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136        write!(f, "{}: {}", self.name, self.value)
137    }
138}
139
140#[derive(Debug, Clone, Default)]
141pub struct Headers {
142    headers: Vec<Header>,
143}
144
145impl Headers {
146    pub fn new() -> Self {
147        Self {
148            headers: Vec::new(),
149        }
150    }
151
152    pub fn add(&mut self, name: HeaderName, value: impl Into<String>) {
153        self.headers.push(Header::new(name, value));
154    }
155
156    pub fn get(&self, name: &HeaderName) -> Option<&HeaderValue> {
157        self.headers
158            .iter()
159            .find(|h| &h.name == name)
160            .map(|h| &h.value)
161    }
162
163    pub fn get_all(&self, name: &HeaderName) -> Vec<&HeaderValue> {
164        self.headers
165            .iter()
166            .filter(|h| &h.name == name)
167            .map(|h| &h.value)
168            .collect()
169    }
170
171    pub fn set(&mut self, name: HeaderName, value: impl Into<String>) {
172        if let Some(header) = self.headers.iter_mut().find(|h| h.name == name) {
173            header.value = HeaderValue::new(value);
174        } else {
175            self.add(name, value);
176        }
177    }
178
179    pub fn remove(&mut self, name: &HeaderName) {
180        self.headers.retain(|h| &h.name != name);
181    }
182
183    pub fn iter(&self) -> impl Iterator<Item = &Header> {
184        self.headers.iter()
185    }
186
187    pub fn is_empty(&self) -> bool {
188        self.headers.is_empty()
189    }
190
191    pub fn len(&self) -> usize {
192        self.headers.len()
193    }
194}
195
196impl fmt::Display for Headers {
197    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198        for header in &self.headers {
199            writeln!(f, "{}", header)?;
200        }
201        Ok(())
202    }
203}
204
205/// Parse a SIP URI tag parameter, e.g. extract "tag=xyz" from a From/To header value.
206pub fn extract_tag(header_value: &str) -> Option<String> {
207    header_value
208        .split(';')
209        .find_map(|param| {
210            let param = param.trim();
211            if let Some(tag) = param.strip_prefix("tag=") {
212                Some(tag.to_string())
213            } else {
214                None
215            }
216        })
217}
218
219/// Extract the URI from a header value like `"Alice" <sip:alice@example.com>;tag=xyz`
220pub fn extract_uri(header_value: &str) -> Option<String> {
221    if let Some(start) = header_value.find('<') {
222        if let Some(end) = header_value.find('>') {
223            return Some(header_value[start + 1..end].to_string());
224        }
225    }
226    // If no angle brackets, the value itself might be a URI
227    let uri = header_value.split(';').next()?.trim();
228    if uri.starts_with("sip:") || uri.starts_with("sips:") {
229        Some(uri.to_string())
230    } else {
231        None
232    }
233}
234
235/// Generate a random tag for From/To headers
236pub fn generate_tag() -> String {
237    use rand::Rng;
238    let mut rng = rand::thread_rng();
239    let tag: u64 = rng.gen();
240    format!("{:x}", tag)
241}
242
243/// Generate a random branch parameter for Via headers (RFC 3261 magic cookie)
244pub fn generate_branch() -> String {
245    use rand::Rng;
246    let mut rng = rand::thread_rng();
247    let branch: u64 = rng.gen();
248    format!("z9hG4bK{:x}", branch)
249}
250
251#[cfg(test)]
252mod tests {
253    use super::*;
254
255    #[test]
256    fn test_header_name_parsing() {
257        assert_eq!(HeaderName::from_str("Via"), HeaderName::Via);
258        assert_eq!(HeaderName::from_str("v"), HeaderName::Via);
259        assert_eq!(HeaderName::from_str("VIA"), HeaderName::Via);
260        assert_eq!(HeaderName::from_str("from"), HeaderName::From);
261        assert_eq!(HeaderName::from_str("f"), HeaderName::From);
262        assert_eq!(HeaderName::from_str("Call-ID"), HeaderName::CallId);
263        assert_eq!(HeaderName::from_str("i"), HeaderName::CallId);
264        assert_eq!(HeaderName::from_str("CSeq"), HeaderName::CSeq);
265        assert_eq!(
266            HeaderName::from_str("X-Custom"),
267            HeaderName::Other("x-custom".to_string())
268        );
269    }
270
271    #[test]
272    fn test_header_name_display() {
273        assert_eq!(HeaderName::Via.as_str(), "Via");
274        assert_eq!(HeaderName::CallId.as_str(), "Call-ID");
275        assert_eq!(HeaderName::ContentType.as_str(), "Content-Type");
276    }
277
278    #[test]
279    fn test_headers_collection() {
280        let mut headers = Headers::new();
281        headers.add(HeaderName::Via, "SIP/2.0/UDP 10.0.0.1:5060;branch=z9hG4bK776asdhds");
282        headers.add(HeaderName::From, "<sip:alice@atlanta.com>;tag=1928301774");
283        headers.add(HeaderName::To, "<sip:bob@biloxi.com>");
284        headers.add(HeaderName::CallId, "a84b4c76e66710@pc33.atlanta.com");
285
286        assert_eq!(headers.len(), 4);
287        assert!(!headers.is_empty());
288
289        assert_eq!(
290            headers.get(&HeaderName::From).unwrap().as_str(),
291            "<sip:alice@atlanta.com>;tag=1928301774"
292        );
293
294        // Multiple Via headers
295        headers.add(HeaderName::Via, "SIP/2.0/UDP 10.0.0.2:5060;branch=z9hG4bKnashds8");
296        assert_eq!(headers.get_all(&HeaderName::Via).len(), 2);
297    }
298
299    #[test]
300    fn test_headers_set_replaces() {
301        let mut headers = Headers::new();
302        headers.add(HeaderName::ContentLength, "0");
303        headers.set(HeaderName::ContentLength, "150");
304        assert_eq!(headers.get(&HeaderName::ContentLength).unwrap().as_str(), "150");
305        assert_eq!(headers.len(), 1);
306    }
307
308    #[test]
309    fn test_headers_remove() {
310        let mut headers = Headers::new();
311        headers.add(HeaderName::Via, "SIP/2.0/UDP 10.0.0.1:5060");
312        headers.add(HeaderName::From, "<sip:alice@atlanta.com>");
313        headers.remove(&HeaderName::Via);
314        assert!(headers.get(&HeaderName::Via).is_none());
315        assert_eq!(headers.len(), 1);
316    }
317
318    #[test]
319    fn test_extract_tag() {
320        assert_eq!(
321            extract_tag("<sip:alice@atlanta.com>;tag=1928301774"),
322            Some("1928301774".to_string())
323        );
324        assert_eq!(extract_tag("<sip:bob@biloxi.com>"), None);
325        assert_eq!(
326            extract_tag("\"Alice\" <sip:alice@atlanta.com>;tag=abc123"),
327            Some("abc123".to_string())
328        );
329    }
330
331    #[test]
332    fn test_extract_uri() {
333        assert_eq!(
334            extract_uri("<sip:alice@atlanta.com>;tag=1928301774"),
335            Some("sip:alice@atlanta.com".to_string())
336        );
337        assert_eq!(
338            extract_uri("\"Alice\" <sip:alice@atlanta.com>"),
339            Some("sip:alice@atlanta.com".to_string())
340        );
341        assert_eq!(
342            extract_uri("sip:bob@biloxi.com"),
343            Some("sip:bob@biloxi.com".to_string())
344        );
345    }
346
347    #[test]
348    fn test_generate_tag() {
349        let tag = generate_tag();
350        assert!(!tag.is_empty());
351        // Should be different each time
352        let tag2 = generate_tag();
353        assert_ne!(tag, tag2);
354    }
355
356    #[test]
357    fn test_generate_branch() {
358        let branch = generate_branch();
359        assert!(branch.starts_with("z9hG4bK"));
360    }
361
362    #[test]
363    fn test_header_display() {
364        let header = Header::new(HeaderName::From, "<sip:alice@atlanta.com>;tag=123");
365        assert_eq!(
366            header.to_string(),
367            "From: <sip:alice@atlanta.com>;tag=123"
368        );
369    }
370}