Skip to main content

sip_header/
header.rs

1//! Standard SIP header names and typed lookup trait (RFC 3261 and extensions).
2//!
3//! Protocol-agnostic catalog of SIP header names with canonical wire casing,
4//! plus a [`SipHeaderLookup`] trait providing typed convenience accessors for
5//! any key-value store that can look up headers by name.
6
7use crate::call_info::{SipCallInfo, SipCallInfoError};
8use crate::header_addr::{ParseSipHeaderAddrError, SipHeaderAddr};
9use crate::history_info::{HistoryInfo, HistoryInfoError};
10
11/// Error returned when parsing an unrecognized SIP header name.
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct ParseSipHeaderError(pub String);
14
15impl std::fmt::Display for ParseSipHeaderError {
16    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17        write!(f, "unknown SIP header: {}", self.0)
18    }
19}
20
21impl std::error::Error for ParseSipHeaderError {}
22
23define_header_enum! {
24    error_type: ParseSipHeaderError,
25    /// Standard SIP header names with canonical wire casing.
26    ///
27    /// Each variant maps to the header's canonical form as defined in the
28    /// relevant RFC. `FromStr` is case-insensitive; `Display` always emits
29    /// the canonical form.
30    pub enum SipHeader {
31        /// `Accept` (RFC 3261).
32        Accept => "Accept",
33        /// `Accept-Contact`.
34        AcceptContact => "Accept-Contact",
35        /// `Accept-Encoding` (RFC 3261).
36        AcceptEncoding => "Accept-Encoding",
37        /// `Accept-Language` (RFC 3261).
38        AcceptLanguage => "Accept-Language",
39        /// `Accept-Resource-Priority`.
40        AcceptResourcePriority => "Accept-Resource-Priority",
41        /// `Additional-Identity`.
42        AdditionalIdentity => "Additional-Identity",
43        /// `Alert-Info` (RFC 3261).
44        AlertInfo => "Alert-Info",
45        /// `AlertMsg-Error`.
46        AlertmsgError => "AlertMsg-Error",
47        /// `Allow` (RFC 3261).
48        Allow => "Allow",
49        /// `Allow-Events` (RFC 6665).
50        AllowEvents => "Allow-Events",
51        /// `Answer-Mode`.
52        AnswerMode => "Answer-Mode",
53        /// `Attestation-Info`.
54        AttestationInfo => "Attestation-Info",
55        /// `Authentication-Info`.
56        AuthenticationInfo => "Authentication-Info",
57        /// `Authorization` (RFC 3261).
58        Authorization => "Authorization",
59        /// `Call-ID` (RFC 3261).
60        CallId => "Call-ID",
61        /// `Call-Info` (RFC 3261).
62        CallInfo => "Call-Info",
63        /// `Cellular-Network-Info`.
64        CellularNetworkInfo => "Cellular-Network-Info",
65        /// `Contact` (RFC 3261).
66        Contact => "Contact",
67        /// `Content-Disposition` (RFC 3261).
68        ContentDisposition => "Content-Disposition",
69        /// `Content-Encoding` (RFC 3261).
70        ContentEncoding => "Content-Encoding",
71        /// `Content-ID`.
72        ContentId => "Content-ID",
73        /// `Content-Language` (RFC 3261).
74        ContentLanguage => "Content-Language",
75        /// `Content-Length` (RFC 3261).
76        ContentLength => "Content-Length",
77        /// `Content-Type` (RFC 3261).
78        ContentType => "Content-Type",
79        /// `CSeq` (RFC 3261).
80        Cseq => "CSeq",
81        /// `Date` (RFC 3261).
82        Date => "Date",
83        /// `DC-Info`.
84        DcInfo => "DC-Info",
85        /// `Encryption` (deprecated in RFC 3261).
86        Encryption => "Encryption",
87        /// `Error-Info` (RFC 3261).
88        ErrorInfo => "Error-Info",
89        /// `Event` (RFC 6665).
90        Event => "Event",
91        /// `Expires` (RFC 3261).
92        Expires => "Expires",
93        /// `Feature-Caps`.
94        FeatureCaps => "Feature-Caps",
95        /// `Flow-Timer`.
96        FlowTimer => "Flow-Timer",
97        /// `From` (RFC 3261).
98        From => "From",
99        /// `Geolocation` (RFC 6442).
100        Geolocation => "Geolocation",
101        /// `Geolocation-Error` (RFC 6442).
102        GeolocationError => "Geolocation-Error",
103        /// `Geolocation-Routing` (RFC 6442).
104        GeolocationRouting => "Geolocation-Routing",
105        /// `Hide` (deprecated in RFC 3261).
106        Hide => "Hide",
107        /// `History-Info` (RFC 7044).
108        HistoryInfo => "History-Info",
109        /// `Identity` (RFC 8224).
110        Identity => "Identity",
111        /// `Identity-Info`.
112        IdentityInfo => "Identity-Info",
113        /// `Info-Package`.
114        InfoPackage => "Info-Package",
115        /// `In-Reply-To` (RFC 3261).
116        InReplyTo => "In-Reply-To",
117        /// `Join` (RFC 3911).
118        Join => "Join",
119        /// `Max-Breadth`.
120        MaxBreadth => "Max-Breadth",
121        /// `Max-Forwards` (RFC 3261).
122        MaxForwards => "Max-Forwards",
123        /// `MIME-Version` (RFC 3261).
124        MimeVersion => "MIME-Version",
125        /// `Min-Expires` (RFC 3261).
126        MinExpires => "Min-Expires",
127        /// `Min-SE` (RFC 4028).
128        MinSe => "Min-SE",
129        /// `Organization` (RFC 3261).
130        Organization => "Organization",
131        /// `Origination-Id`.
132        OriginationId => "Origination-Id",
133        /// `P-Access-Network-Info`.
134        PAccessNetworkInfo => "P-Access-Network-Info",
135        /// `P-Answer-State`.
136        PAnswerState => "P-Answer-State",
137        /// `P-Asserted-Identity` (RFC 3325).
138        PAssertedIdentity => "P-Asserted-Identity",
139        /// `P-Asserted-Service`.
140        PAssertedService => "P-Asserted-Service",
141        /// `P-Associated-URI`.
142        PAssociatedUri => "P-Associated-URI",
143        /// `P-Called-Party-ID`.
144        PCalledPartyId => "P-Called-Party-ID",
145        /// `P-Charge-Info`.
146        PChargeInfo => "P-Charge-Info",
147        /// `P-Charging-Function-Addresses`.
148        PChargingFunctionAddresses => "P-Charging-Function-Addresses",
149        /// `P-Charging-Vector`.
150        PChargingVector => "P-Charging-Vector",
151        /// `P-DCS-Trace-Party-ID`.
152        PDcsTracePartyId => "P-DCS-Trace-Party-ID",
153        /// `P-DCS-OSPS`.
154        PDcsOsps => "P-DCS-OSPS",
155        /// `P-DCS-Billing-Info`.
156        PDcsBillingInfo => "P-DCS-Billing-Info",
157        /// `P-DCS-LAES`.
158        PDcsLaes => "P-DCS-LAES",
159        /// `P-DCS-Redirect`.
160        PDcsRedirect => "P-DCS-Redirect",
161        /// `P-Early-Media`.
162        PEarlyMedia => "P-Early-Media",
163        /// `P-Media-Authorization`.
164        PMediaAuthorization => "P-Media-Authorization",
165        /// `P-Preferred-Identity` (RFC 3325).
166        PPreferredIdentity => "P-Preferred-Identity",
167        /// `P-Preferred-Service`.
168        PPreferredService => "P-Preferred-Service",
169        /// `P-Private-Network-Indication`.
170        PPrivateNetworkIndication => "P-Private-Network-Indication",
171        /// `P-Profile-Key`.
172        PProfileKey => "P-Profile-Key",
173        /// `P-Refused-URI-List`.
174        PRefusedUriList => "P-Refused-URI-List",
175        /// `P-Served-User`.
176        PServedUser => "P-Served-User",
177        /// `P-User-Database`.
178        PUserDatabase => "P-User-Database",
179        /// `P-Visited-Network-ID`.
180        PVisitedNetworkId => "P-Visited-Network-ID",
181        /// `Path` (RFC 3327).
182        Path => "Path",
183        /// `Permission-Missing`.
184        PermissionMissing => "Permission-Missing",
185        /// `Policy-Contact`.
186        PolicyContact => "Policy-Contact",
187        /// `Policy-ID`.
188        PolicyId => "Policy-ID",
189        /// `Priority` (RFC 3261).
190        Priority => "Priority",
191        /// `Priority-Share`.
192        PriorityShare => "Priority-Share",
193        /// `Priority-Verstat`.
194        PriorityVerstat => "Priority-Verstat",
195        /// `Priv-Answer-Mode`.
196        PrivAnswerMode => "Priv-Answer-Mode",
197        /// `Privacy` (RFC 3323).
198        Privacy => "Privacy",
199        /// `Proxy-Authenticate` (RFC 3261).
200        ProxyAuthenticate => "Proxy-Authenticate",
201        /// `Proxy-Authorization` (RFC 3261).
202        ProxyAuthorization => "Proxy-Authorization",
203        /// `Proxy-Require` (RFC 3261).
204        ProxyRequire => "Proxy-Require",
205        /// `RAck`.
206        Rack => "RAck",
207        /// `Reason` (RFC 3326).
208        Reason => "Reason",
209        /// `Reason-Phrase`.
210        ReasonPhrase => "Reason-Phrase",
211        /// `Record-Route` (RFC 3261).
212        RecordRoute => "Record-Route",
213        /// `Recv-Info`.
214        RecvInfo => "Recv-Info",
215        /// `Refer-Events-At`.
216        ReferEventsAt => "Refer-Events-At",
217        /// `Refer-Sub`.
218        ReferSub => "Refer-Sub",
219        /// `Refer-To` (RFC 3515).
220        ReferTo => "Refer-To",
221        /// `Referred-By` (RFC 3892).
222        ReferredBy => "Referred-By",
223        /// `Reject-Contact`.
224        RejectContact => "Reject-Contact",
225        /// `Relayed-Charge`.
226        RelayedCharge => "Relayed-Charge",
227        /// `Replaces` (RFC 3891).
228        Replaces => "Replaces",
229        /// `Reply-To` (RFC 3261).
230        ReplyTo => "Reply-To",
231        /// `Request-Disposition`.
232        RequestDisposition => "Request-Disposition",
233        /// `Require` (RFC 3261).
234        Require => "Require",
235        /// `Resource-Priority`.
236        ResourcePriority => "Resource-Priority",
237        /// `Resource-Share`.
238        ResourceShare => "Resource-Share",
239        /// `Response-Key` (deprecated in RFC 3261).
240        ResponseKey => "Response-Key",
241        /// `Response-Source`.
242        ResponseSource => "Response-Source",
243        /// `Restoration-Info`.
244        RestorationInfo => "Restoration-Info",
245        /// `Retry-After` (RFC 3261).
246        RetryAfter => "Retry-After",
247        /// `Route` (RFC 3261).
248        Route => "Route",
249        /// `RSeq`.
250        Rseq => "RSeq",
251        /// `Security-Client` (RFC 3329).
252        SecurityClient => "Security-Client",
253        /// `Security-Server` (RFC 3329).
254        SecurityServer => "Security-Server",
255        /// `Security-Verify` (RFC 3329).
256        SecurityVerify => "Security-Verify",
257        /// `Server` (RFC 3261).
258        Server => "Server",
259        /// `Service-Interact-Info`.
260        ServiceInteractInfo => "Service-Interact-Info",
261        /// `Service-Route` (RFC 3608).
262        ServiceRoute => "Service-Route",
263        /// `Session-Expires` (RFC 4028).
264        SessionExpires => "Session-Expires",
265        /// `Session-ID`.
266        SessionId => "Session-ID",
267        /// `SIP-ETag`.
268        SipEtag => "SIP-ETag",
269        /// `SIP-If-Match`.
270        SipIfMatch => "SIP-If-Match",
271        /// `Subject` (RFC 3261).
272        Subject => "Subject",
273        /// `Subscription-State` (RFC 6665).
274        SubscriptionState => "Subscription-State",
275        /// `Supported` (RFC 3261).
276        Supported => "Supported",
277        /// `Suppress-If-Match`.
278        SuppressIfMatch => "Suppress-If-Match",
279        /// `Target-Dialog` (RFC 4538).
280        TargetDialog => "Target-Dialog",
281        /// `Timestamp` (RFC 3261).
282        Timestamp => "Timestamp",
283        /// `To` (RFC 3261).
284        To => "To",
285        /// `Trigger-Consent`.
286        TriggerConsent => "Trigger-Consent",
287        /// `Unsupported` (RFC 3261).
288        Unsupported => "Unsupported",
289        /// `User-Agent` (RFC 3261).
290        UserAgent => "User-Agent",
291        /// `User-to-User` (RFC 7433).
292        UserToUser => "User-to-User",
293        /// `Via` (RFC 3261).
294        Via => "Via",
295        /// `Warning` (RFC 3261).
296        Warning => "Warning",
297        /// `WWW-Authenticate` (RFC 3261).
298        WwwAuthenticate => "WWW-Authenticate",
299    }
300}
301
302/// RFC 3261 §7.3.3 compact header form mappings.
303///
304/// Includes forms from RFC 3261, RFC 3515, RFC 3841, RFC 3892, RFC 4028,
305/// RFC 4474, and RFC 6665.
306const COMPACT_FORMS: &[(u8, SipHeader)] = &[
307    (b'a', SipHeader::AcceptContact),
308    (b'b', SipHeader::ReferredBy),
309    (b'c', SipHeader::ContentType),
310    (b'd', SipHeader::RequestDisposition),
311    (b'e', SipHeader::ContentEncoding),
312    (b'f', SipHeader::From),
313    (b'i', SipHeader::CallId),
314    (b'j', SipHeader::RejectContact),
315    (b'k', SipHeader::Supported),
316    (b'l', SipHeader::ContentLength),
317    (b'm', SipHeader::Contact),
318    (b'n', SipHeader::IdentityInfo),
319    (b'o', SipHeader::Event),
320    (b'r', SipHeader::ReferTo),
321    (b's', SipHeader::Subject),
322    (b't', SipHeader::To),
323    (b'u', SipHeader::AllowEvents),
324    (b'v', SipHeader::Via),
325    (b'x', SipHeader::SessionExpires),
326    (b'y', SipHeader::Identity),
327];
328
329impl SipHeader {
330    /// Resolve a compact form letter to the corresponding header (RFC 3261 §7.3.3).
331    ///
332    /// Case-insensitive: both `'f'` and `'F'` resolve to [`SipHeader::From`].
333    pub fn from_compact(ch: u8) -> Option<Self> {
334        let lower = ch.to_ascii_lowercase();
335        COMPACT_FORMS
336            .iter()
337            .find(|(c, _)| *c == lower)
338            .map(|(_, h)| *h)
339    }
340
341    /// Return the compact form letter for this header, if one exists.
342    pub fn compact_form(&self) -> Option<char> {
343        COMPACT_FORMS
344            .iter()
345            .find(|(_, h)| h == self)
346            .map(|(c, _)| *c as char)
347    }
348
349    /// Parse a header name, including RFC 3261 §7.3.3 compact forms.
350    ///
351    /// Tries compact form resolution for single-character input, then
352    /// falls back to case-insensitive canonical name matching.
353    pub fn parse_name(name: &str) -> Result<Self, ParseSipHeaderError> {
354        if name.len() == 1 {
355            if let Some(h) = Self::from_compact(name.as_bytes()[0]) {
356                return Ok(h);
357            }
358        }
359        name.parse()
360    }
361}
362
363/// Trait for looking up standard SIP headers from any key-value store.
364///
365/// Implementors provide `sip_header_str()` and get all typed accessors as
366/// default implementations.
367///
368/// # Example
369///
370/// ```
371/// use std::collections::HashMap;
372/// use sip_header::{SipHeaderLookup, SipHeader};
373///
374/// let mut headers = HashMap::new();
375/// headers.insert(
376///     "Call-Info".to_string(),
377///     "<urn:emergency:uid:callid:abc>;purpose=emergency-CallId".to_string(),
378/// );
379///
380/// assert_eq!(
381///     headers.sip_header(SipHeader::CallInfo),
382///     Some("<urn:emergency:uid:callid:abc>;purpose=emergency-CallId"),
383/// );
384///
385/// let ci = headers.call_info().unwrap().unwrap();
386/// assert_eq!(ci.entries()[0].purpose(), Some("emergency-CallId"));
387/// ```
388pub trait SipHeaderLookup {
389    /// Look up a SIP header by its canonical name (e.g. `"Call-Info"`).
390    fn sip_header_str(&self, name: &str) -> Option<&str>;
391
392    /// Look up a SIP header by its [`SipHeader`] enum variant.
393    fn sip_header(&self, name: SipHeader) -> Option<&str> {
394        self.sip_header_str(name.as_str())
395    }
396
397    /// Raw `Call-Info` header value (RFC 3261 section 20.9).
398    fn call_info_raw(&self) -> Option<&str> {
399        self.sip_header(SipHeader::CallInfo)
400    }
401
402    /// Parse the `Call-Info` header into a [`SipCallInfo`].
403    ///
404    /// Returns `Ok(None)` if the header is absent, `Err` if present but unparseable.
405    fn call_info(&self) -> Result<Option<SipCallInfo>, SipCallInfoError> {
406        match self.call_info_raw() {
407            Some(s) => SipCallInfo::parse(s).map(Some),
408            None => Ok(None),
409        }
410    }
411
412    /// Raw `History-Info` header value (RFC 7044).
413    fn history_info_raw(&self) -> Option<&str> {
414        self.sip_header(SipHeader::HistoryInfo)
415    }
416
417    /// Parse the `History-Info` header into a [`HistoryInfo`].
418    ///
419    /// Returns `Ok(None)` if the header is absent, `Err` if present but unparseable.
420    fn history_info(&self) -> Result<Option<HistoryInfo>, HistoryInfoError> {
421        match self.history_info_raw() {
422            Some(s) => HistoryInfo::parse(s).map(Some),
423            None => Ok(None),
424        }
425    }
426
427    /// Raw `P-Asserted-Identity` header value (RFC 3325).
428    fn p_asserted_identity_raw(&self) -> Option<&str> {
429        self.sip_header(SipHeader::PAssertedIdentity)
430    }
431
432    /// Parse the `P-Asserted-Identity` header into a [`SipHeaderAddr`].
433    ///
434    /// Returns `Ok(None)` if the header is absent, `Err` if present but unparseable.
435    fn p_asserted_identity(&self) -> Result<Option<SipHeaderAddr>, ParseSipHeaderAddrError> {
436        match self.p_asserted_identity_raw() {
437            Some(s) => s
438                .parse::<SipHeaderAddr>()
439                .map(Some),
440            None => Ok(None),
441        }
442    }
443}
444
445impl SipHeaderLookup for std::collections::HashMap<String, String> {
446    fn sip_header_str(&self, name: &str) -> Option<&str> {
447        self.get(name)
448            .map(|s| s.as_str())
449    }
450}
451
452#[cfg(test)]
453mod tests {
454    use super::*;
455    use std::collections::HashMap;
456
457    #[test]
458    fn display_round_trip() {
459        assert_eq!(SipHeader::CallInfo.to_string(), "Call-Info");
460        assert_eq!(SipHeader::HistoryInfo.to_string(), "History-Info");
461        assert_eq!(
462            SipHeader::PAssertedIdentity.to_string(),
463            "P-Asserted-Identity"
464        );
465    }
466
467    #[test]
468    fn as_ref_str() {
469        let h: &str = SipHeader::CallInfo.as_ref();
470        assert_eq!(h, "Call-Info");
471    }
472
473    #[test]
474    fn from_str_case_insensitive() {
475        assert_eq!("call-info".parse::<SipHeader>(), Ok(SipHeader::CallInfo));
476        assert_eq!("CALL-INFO".parse::<SipHeader>(), Ok(SipHeader::CallInfo));
477        assert_eq!(
478            "history-info".parse::<SipHeader>(),
479            Ok(SipHeader::HistoryInfo)
480        );
481        assert_eq!(
482            "p-asserted-identity".parse::<SipHeader>(),
483            Ok(SipHeader::PAssertedIdentity)
484        );
485        assert_eq!(
486            "P-ASSERTED-IDENTITY".parse::<SipHeader>(),
487            Ok(SipHeader::PAssertedIdentity)
488        );
489    }
490
491    #[test]
492    fn from_str_unknown() {
493        assert!("X-Custom"
494            .parse::<SipHeader>()
495            .is_err());
496    }
497
498    #[test]
499    fn from_str_round_trip_all() {
500        let variants = [
501            SipHeader::Accept,
502            SipHeader::AcceptContact,
503            SipHeader::AcceptEncoding,
504            SipHeader::AcceptLanguage,
505            SipHeader::AcceptResourcePriority,
506            SipHeader::AdditionalIdentity,
507            SipHeader::AlertInfo,
508            SipHeader::AlertmsgError,
509            SipHeader::Allow,
510            SipHeader::AllowEvents,
511            SipHeader::AnswerMode,
512            SipHeader::AttestationInfo,
513            SipHeader::AuthenticationInfo,
514            SipHeader::Authorization,
515            SipHeader::CallId,
516            SipHeader::CallInfo,
517            SipHeader::CellularNetworkInfo,
518            SipHeader::Contact,
519            SipHeader::ContentDisposition,
520            SipHeader::ContentEncoding,
521            SipHeader::ContentId,
522            SipHeader::ContentLanguage,
523            SipHeader::ContentLength,
524            SipHeader::ContentType,
525            SipHeader::Cseq,
526            SipHeader::Date,
527            SipHeader::DcInfo,
528            SipHeader::Encryption,
529            SipHeader::ErrorInfo,
530            SipHeader::Event,
531            SipHeader::Expires,
532            SipHeader::FeatureCaps,
533            SipHeader::FlowTimer,
534            SipHeader::From,
535            SipHeader::Geolocation,
536            SipHeader::GeolocationError,
537            SipHeader::GeolocationRouting,
538            SipHeader::Hide,
539            SipHeader::HistoryInfo,
540            SipHeader::Identity,
541            SipHeader::IdentityInfo,
542            SipHeader::InfoPackage,
543            SipHeader::InReplyTo,
544            SipHeader::Join,
545            SipHeader::MaxBreadth,
546            SipHeader::MaxForwards,
547            SipHeader::MimeVersion,
548            SipHeader::MinExpires,
549            SipHeader::MinSe,
550            SipHeader::Organization,
551            SipHeader::OriginationId,
552            SipHeader::PAccessNetworkInfo,
553            SipHeader::PAnswerState,
554            SipHeader::PAssertedIdentity,
555            SipHeader::PAssertedService,
556            SipHeader::PAssociatedUri,
557            SipHeader::PCalledPartyId,
558            SipHeader::PChargeInfo,
559            SipHeader::PChargingFunctionAddresses,
560            SipHeader::PChargingVector,
561            SipHeader::PDcsTracePartyId,
562            SipHeader::PDcsOsps,
563            SipHeader::PDcsBillingInfo,
564            SipHeader::PDcsLaes,
565            SipHeader::PDcsRedirect,
566            SipHeader::PEarlyMedia,
567            SipHeader::PMediaAuthorization,
568            SipHeader::PPreferredIdentity,
569            SipHeader::PPreferredService,
570            SipHeader::PPrivateNetworkIndication,
571            SipHeader::PProfileKey,
572            SipHeader::PRefusedUriList,
573            SipHeader::PServedUser,
574            SipHeader::PUserDatabase,
575            SipHeader::PVisitedNetworkId,
576            SipHeader::Path,
577            SipHeader::PermissionMissing,
578            SipHeader::PolicyContact,
579            SipHeader::PolicyId,
580            SipHeader::Priority,
581            SipHeader::PriorityShare,
582            SipHeader::PriorityVerstat,
583            SipHeader::PrivAnswerMode,
584            SipHeader::Privacy,
585            SipHeader::ProxyAuthenticate,
586            SipHeader::ProxyAuthorization,
587            SipHeader::ProxyRequire,
588            SipHeader::Rack,
589            SipHeader::Reason,
590            SipHeader::ReasonPhrase,
591            SipHeader::RecordRoute,
592            SipHeader::RecvInfo,
593            SipHeader::ReferEventsAt,
594            SipHeader::ReferSub,
595            SipHeader::ReferTo,
596            SipHeader::ReferredBy,
597            SipHeader::RejectContact,
598            SipHeader::RelayedCharge,
599            SipHeader::Replaces,
600            SipHeader::ReplyTo,
601            SipHeader::RequestDisposition,
602            SipHeader::Require,
603            SipHeader::ResourcePriority,
604            SipHeader::ResourceShare,
605            SipHeader::ResponseKey,
606            SipHeader::ResponseSource,
607            SipHeader::RestorationInfo,
608            SipHeader::RetryAfter,
609            SipHeader::Route,
610            SipHeader::Rseq,
611            SipHeader::SecurityClient,
612            SipHeader::SecurityServer,
613            SipHeader::SecurityVerify,
614            SipHeader::Server,
615            SipHeader::ServiceInteractInfo,
616            SipHeader::ServiceRoute,
617            SipHeader::SessionExpires,
618            SipHeader::SessionId,
619            SipHeader::SipEtag,
620            SipHeader::SipIfMatch,
621            SipHeader::Subject,
622            SipHeader::SubscriptionState,
623            SipHeader::Supported,
624            SipHeader::SuppressIfMatch,
625            SipHeader::TargetDialog,
626            SipHeader::Timestamp,
627            SipHeader::To,
628            SipHeader::TriggerConsent,
629            SipHeader::Unsupported,
630            SipHeader::UserAgent,
631            SipHeader::UserToUser,
632            SipHeader::Via,
633            SipHeader::Warning,
634            SipHeader::WwwAuthenticate,
635        ];
636        for v in variants {
637            let wire = v.to_string();
638            let parsed: SipHeader = wire
639                .parse()
640                .unwrap();
641            assert_eq!(parsed, v, "round-trip failed for {wire}");
642        }
643    }
644
645    fn headers_with(pairs: &[(&str, &str)]) -> HashMap<String, String> {
646        pairs
647            .iter()
648            .map(|(k, v)| (k.to_string(), v.to_string()))
649            .collect()
650    }
651
652    #[test]
653    fn sip_header_by_enum() {
654        let h = headers_with(&[("Call-Info", "<urn:x>;purpose=icon")]);
655        assert_eq!(
656            h.sip_header(SipHeader::CallInfo),
657            Some("<urn:x>;purpose=icon")
658        );
659    }
660
661    #[test]
662    fn call_info_raw_lookup() {
663        let h = headers_with(&[(
664            "Call-Info",
665            "<urn:emergency:uid:callid:test:bcf.example.com>;purpose=emergency-CallId",
666        )]);
667        assert_eq!(
668            h.call_info_raw(),
669            Some("<urn:emergency:uid:callid:test:bcf.example.com>;purpose=emergency-CallId")
670        );
671    }
672
673    #[test]
674    fn call_info_typed() {
675        let h = headers_with(&[(
676            "Call-Info",
677            "<urn:emergency:uid:callid:test:bcf.example.com>;purpose=emergency-CallId",
678        )]);
679        let ci = h
680            .call_info()
681            .unwrap()
682            .unwrap();
683        assert_eq!(ci.len(), 1);
684        assert_eq!(ci.entries()[0].purpose(), Some("emergency-CallId"));
685    }
686
687    #[test]
688    fn call_info_absent() {
689        let h = headers_with(&[]);
690        assert_eq!(
691            h.call_info()
692                .unwrap(),
693            None
694        );
695    }
696
697    #[test]
698    fn p_asserted_identity_raw_lookup() {
699        let h = headers_with(&[(
700            "P-Asserted-Identity",
701            r#""EXAMPLE CO" <sip:+15551234567@198.51.100.1>"#,
702        )]);
703        assert_eq!(
704            h.p_asserted_identity_raw(),
705            Some(r#""EXAMPLE CO" <sip:+15551234567@198.51.100.1>"#)
706        );
707    }
708
709    #[test]
710    fn p_asserted_identity_typed() {
711        let h = headers_with(&[(
712            "P-Asserted-Identity",
713            r#""EXAMPLE CO" <sip:+15551234567@198.51.100.1>"#,
714        )]);
715        let pai = h
716            .p_asserted_identity()
717            .unwrap()
718            .unwrap();
719        assert_eq!(pai.display_name(), Some("EXAMPLE CO"));
720    }
721
722    #[test]
723    fn p_asserted_identity_absent() {
724        let h = headers_with(&[]);
725        assert_eq!(
726            h.p_asserted_identity()
727                .unwrap(),
728            None
729        );
730    }
731
732    #[test]
733    fn history_info_raw_lookup() {
734        let h = headers_with(&[(
735            "History-Info",
736            "<sip:alice@esrp.example.com>;index=1,<sip:sos@psap.example.com>;index=1.1",
737        )]);
738        assert!(h
739            .history_info_raw()
740            .unwrap()
741            .contains("esrp.example.com"));
742    }
743
744    #[test]
745    fn history_info_typed() {
746        let h = headers_with(&[(
747            "History-Info",
748            "<sip:alice@esrp.example.com>;index=1,<sip:sos@psap.example.com>;index=1.1",
749        )]);
750        let hi = h
751            .history_info()
752            .unwrap()
753            .unwrap();
754        assert_eq!(hi.len(), 2);
755        assert_eq!(hi.entries()[0].index(), Some("1"));
756        assert_eq!(hi.entries()[1].index(), Some("1.1"));
757    }
758
759    #[test]
760    fn history_info_absent() {
761        let h = headers_with(&[]);
762        assert_eq!(
763            h.history_info()
764                .unwrap(),
765            None
766        );
767    }
768
769    #[test]
770    fn extract_from_sip_message() {
771        let msg = concat!(
772            "INVITE sip:bob@host SIP/2.0\r\n",
773            "Call-Info: <urn:emergency:uid:callid:abc>;purpose=emergency-CallId\r\n",
774            "History-Info: <sip:esrp@example.com>;index=1\r\n",
775            "P-Asserted-Identity: \"Corp\" <sip:+15551234567@198.51.100.1>\r\n",
776            "\r\n",
777        );
778        assert_eq!(
779            SipHeader::CallInfo.extract_from(msg),
780            Some("<urn:emergency:uid:callid:abc>;purpose=emergency-CallId".into())
781        );
782        assert_eq!(
783            SipHeader::HistoryInfo.extract_from(msg),
784            Some("<sip:esrp@example.com>;index=1".into())
785        );
786        assert_eq!(
787            SipHeader::PAssertedIdentity.extract_from(msg),
788            Some("\"Corp\" <sip:+15551234567@198.51.100.1>".into())
789        );
790    }
791
792    #[test]
793    fn extract_from_missing() {
794        let msg = concat!(
795            "INVITE sip:bob@host SIP/2.0\r\n",
796            "From: Alice <sip:alice@host>\r\n",
797            "\r\n",
798        );
799        assert_eq!(SipHeader::CallInfo.extract_from(msg), None);
800        assert_eq!(SipHeader::PAssertedIdentity.extract_from(msg), None);
801    }
802
803    #[test]
804    fn missing_headers_return_none() {
805        let h = headers_with(&[]);
806        assert_eq!(h.call_info_raw(), None);
807        assert_eq!(
808            h.call_info()
809                .unwrap(),
810            None
811        );
812        assert_eq!(h.history_info_raw(), None);
813        assert_eq!(
814            h.history_info()
815                .unwrap(),
816            None
817        );
818        assert_eq!(h.p_asserted_identity_raw(), None);
819        assert_eq!(
820            h.p_asserted_identity()
821                .unwrap(),
822            None
823        );
824    }
825}
826
827#[cfg(test)]
828mod compact_form_tests {
829    use super::*;
830
831    #[test]
832    fn from_compact_known() {
833        assert_eq!(SipHeader::from_compact(b'f'), Some(SipHeader::From));
834        assert_eq!(SipHeader::from_compact(b'F'), Some(SipHeader::From));
835        assert_eq!(SipHeader::from_compact(b'v'), Some(SipHeader::Via));
836        assert_eq!(SipHeader::from_compact(b'i'), Some(SipHeader::CallId));
837        assert_eq!(SipHeader::from_compact(b'm'), Some(SipHeader::Contact));
838        assert_eq!(SipHeader::from_compact(b't'), Some(SipHeader::To));
839        assert_eq!(SipHeader::from_compact(b'c'), Some(SipHeader::ContentType));
840    }
841
842    #[test]
843    fn from_compact_unknown() {
844        assert_eq!(SipHeader::from_compact(b'z'), None);
845        assert_eq!(SipHeader::from_compact(b'g'), None);
846    }
847
848    #[test]
849    fn compact_form_roundtrip() {
850        assert_eq!(SipHeader::From.compact_form(), Some('f'));
851        assert_eq!(SipHeader::Via.compact_form(), Some('v'));
852        assert_eq!(SipHeader::CallId.compact_form(), Some('i'));
853        assert_eq!(SipHeader::Contact.compact_form(), Some('m'));
854    }
855
856    #[test]
857    fn compact_form_absent() {
858        assert_eq!(SipHeader::HistoryInfo.compact_form(), None);
859        assert_eq!(SipHeader::PAssertedIdentity.compact_form(), None);
860    }
861
862    #[test]
863    fn parse_name_compact() {
864        assert_eq!(SipHeader::parse_name("f"), Ok(SipHeader::From));
865        assert_eq!(SipHeader::parse_name("F"), Ok(SipHeader::From));
866        assert_eq!(SipHeader::parse_name("v"), Ok(SipHeader::Via));
867    }
868
869    #[test]
870    fn parse_name_full() {
871        assert_eq!(SipHeader::parse_name("From"), Ok(SipHeader::From));
872        assert_eq!(SipHeader::parse_name("Via"), Ok(SipHeader::Via));
873    }
874
875    #[test]
876    fn parse_name_unknown() {
877        assert!(SipHeader::parse_name("X-Custom").is_err());
878    }
879
880    #[test]
881    fn all_compact_forms_resolve() {
882        let expected = [
883            ('a', SipHeader::AcceptContact),
884            ('b', SipHeader::ReferredBy),
885            ('c', SipHeader::ContentType),
886            ('d', SipHeader::RequestDisposition),
887            ('e', SipHeader::ContentEncoding),
888            ('f', SipHeader::From),
889            ('i', SipHeader::CallId),
890            ('j', SipHeader::RejectContact),
891            ('k', SipHeader::Supported),
892            ('l', SipHeader::ContentLength),
893            ('m', SipHeader::Contact),
894            ('n', SipHeader::IdentityInfo),
895            ('o', SipHeader::Event),
896            ('r', SipHeader::ReferTo),
897            ('s', SipHeader::Subject),
898            ('t', SipHeader::To),
899            ('u', SipHeader::AllowEvents),
900            ('v', SipHeader::Via),
901            ('x', SipHeader::SessionExpires),
902            ('y', SipHeader::Identity),
903        ];
904        for (ch, header) in expected {
905            assert_eq!(
906                SipHeader::from_compact(ch as u8),
907                Some(header),
908                "compact form '{ch}' failed"
909            );
910            assert_eq!(
911                header.compact_form(),
912                Some(ch),
913                "compact_form() for {} failed",
914                header
915            );
916        }
917    }
918}
919
920#[cfg(test)]
921mod special_case_tests {
922    use super::*;
923
924    #[test]
925    fn cseq_variants() {
926        assert_eq!("CSeq".parse::<SipHeader>(), Ok(SipHeader::Cseq));
927        assert_eq!("cseq".parse::<SipHeader>(), Ok(SipHeader::Cseq));
928        assert_eq!("CSEQ".parse::<SipHeader>(), Ok(SipHeader::Cseq));
929        assert_eq!(SipHeader::Cseq.to_string(), "CSeq");
930    }
931
932    #[test]
933    fn www_authenticate_variants() {
934        assert_eq!(
935            "WWW-Authenticate".parse::<SipHeader>(),
936            Ok(SipHeader::WwwAuthenticate)
937        );
938        assert_eq!(
939            "www-authenticate".parse::<SipHeader>(),
940            Ok(SipHeader::WwwAuthenticate)
941        );
942        assert_eq!(SipHeader::WwwAuthenticate.to_string(), "WWW-Authenticate");
943    }
944
945    #[test]
946    fn rack_rseq_variants() {
947        assert_eq!("RAck".parse::<SipHeader>(), Ok(SipHeader::Rack));
948        assert_eq!("rack".parse::<SipHeader>(), Ok(SipHeader::Rack));
949        assert_eq!(SipHeader::Rack.to_string(), "RAck");
950
951        assert_eq!("RSeq".parse::<SipHeader>(), Ok(SipHeader::Rseq));
952        assert_eq!("rseq".parse::<SipHeader>(), Ok(SipHeader::Rseq));
953        assert_eq!(SipHeader::Rseq.to_string(), "RSeq");
954    }
955
956    #[test]
957    fn user_to_user_variants() {
958        assert_eq!(
959            "User-to-User".parse::<SipHeader>(),
960            Ok(SipHeader::UserToUser)
961        );
962        assert_eq!(
963            "user-to-user".parse::<SipHeader>(),
964            Ok(SipHeader::UserToUser)
965        );
966        assert_eq!(SipHeader::UserToUser.to_string(), "User-to-User");
967    }
968
969    #[test]
970    fn p_header_variants() {
971        assert_eq!(
972            "P-DCS-Trace-Party-ID".parse::<SipHeader>(),
973            Ok(SipHeader::PDcsTracePartyId)
974        );
975        assert_eq!(
976            "p-dcs-trace-party-id".parse::<SipHeader>(),
977            Ok(SipHeader::PDcsTracePartyId)
978        );
979        assert_eq!(
980            SipHeader::PDcsTracePartyId.to_string(),
981            "P-DCS-Trace-Party-ID"
982        );
983    }
984}