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        // Draft headers — appended after IANA variants to preserve discriminants.
300        /// `Diversion` (draft-levy-sip-diversion-08, superseded by RFC 7044).
301        #[cfg(feature = "draft")]
302        Diversion => "Diversion",
303        /// `Remote-Party-ID` (draft-ietf-sip-privacy-01, superseded by RFC 3325).
304        #[cfg(feature = "draft")]
305        RemotePartyId => "Remote-Party-ID",
306    }
307}
308
309/// RFC 3261 §7.3.3 compact header form mappings.
310///
311/// Includes forms from RFC 3261, RFC 3515, RFC 3841, RFC 3892, RFC 4028,
312/// RFC 4474, and RFC 6665.
313const COMPACT_FORMS: &[(u8, SipHeader)] = &[
314    (b'a', SipHeader::AcceptContact),
315    (b'b', SipHeader::ReferredBy),
316    (b'c', SipHeader::ContentType),
317    (b'd', SipHeader::RequestDisposition),
318    (b'e', SipHeader::ContentEncoding),
319    (b'f', SipHeader::From),
320    (b'i', SipHeader::CallId),
321    (b'j', SipHeader::RejectContact),
322    (b'k', SipHeader::Supported),
323    (b'l', SipHeader::ContentLength),
324    (b'm', SipHeader::Contact),
325    (b'n', SipHeader::IdentityInfo),
326    (b'o', SipHeader::Event),
327    (b'r', SipHeader::ReferTo),
328    (b's', SipHeader::Subject),
329    (b't', SipHeader::To),
330    (b'u', SipHeader::AllowEvents),
331    (b'v', SipHeader::Via),
332    (b'x', SipHeader::SessionExpires),
333    (b'y', SipHeader::Identity),
334];
335
336impl SipHeader {
337    /// Resolve a compact form letter to the corresponding header (RFC 3261 §7.3.3).
338    ///
339    /// Case-insensitive: both `'f'` and `'F'` resolve to [`SipHeader::From`].
340    pub fn from_compact(ch: u8) -> Option<Self> {
341        let lower = ch.to_ascii_lowercase();
342        COMPACT_FORMS
343            .iter()
344            .find(|(c, _)| *c == lower)
345            .map(|(_, h)| *h)
346    }
347
348    /// Return the compact form letter for this header, if one exists.
349    pub fn compact_form(&self) -> Option<char> {
350        COMPACT_FORMS
351            .iter()
352            .find(|(_, h)| h == self)
353            .map(|(c, _)| *c as char)
354    }
355
356    /// Whether this header may appear multiple times in a SIP message.
357    ///
358    /// Headers listed here use comma-separated or repeated-header semantics
359    /// per RFC 3261 §7.3.1 and their defining RFCs.
360    pub fn is_multi_valued(&self) -> bool {
361        if matches!(
362            self,
363            // RFC 3261 core
364            Self::Via
365                | Self::Route
366                | Self::RecordRoute
367                | Self::Contact
368                | Self::Allow
369                | Self::Supported
370                | Self::Require
371                | Self::ProxyRequire
372                | Self::Unsupported
373                | Self::Authorization
374                | Self::ProxyAuthorization
375                | Self::WwwAuthenticate
376                | Self::ProxyAuthenticate
377                | Self::Warning
378                | Self::ErrorInfo
379                | Self::CallInfo
380                | Self::AlertInfo
381                | Self::Accept
382                | Self::AcceptEncoding
383                | Self::AcceptLanguage
384                | Self::ContentEncoding
385                | Self::ContentLanguage
386                | Self::InReplyTo
387                // RFC 3325
388                | Self::PAssertedIdentity
389                | Self::PPreferredIdentity
390                // RFC 6665
391                | Self::AllowEvents
392                // RFC 3329
393                | Self::SecurityClient
394                | Self::SecurityServer
395                | Self::SecurityVerify
396                // RFC 3327
397                | Self::Path
398                // RFC 3608
399                | Self::ServiceRoute
400                // RFC 7044
401                | Self::HistoryInfo
402        ) {
403            return true;
404        }
405
406        #[cfg(feature = "draft")]
407        if matches!(
408            self,
409            // draft-levy-sip-diversion-08
410            Self::Diversion
411                // draft-ietf-sip-privacy-01
412                | Self::RemotePartyId
413        ) {
414            return true;
415        }
416
417        false
418    }
419
420    /// Parse a header name, including RFC 3261 §7.3.3 compact forms.
421    ///
422    /// Tries compact form resolution for single-character input, then
423    /// falls back to case-insensitive canonical name matching.
424    pub fn parse_name(name: &str) -> Result<Self, ParseSipHeaderError> {
425        if name.len() == 1 {
426            if let Some(h) = Self::from_compact(name.as_bytes()[0]) {
427                return Ok(h);
428            }
429        }
430        name.parse()
431    }
432}
433
434/// Trait for looking up standard SIP headers from any key-value store.
435///
436/// Implementors provide `sip_header_str()` and get all typed accessors as
437/// default implementations.
438///
439/// # Example
440///
441/// ```
442/// use std::collections::HashMap;
443/// use sip_header::{SipHeaderLookup, SipHeader};
444///
445/// let mut headers = HashMap::new();
446/// headers.insert(
447///     "Call-Info".to_string(),
448///     "<urn:emergency:uid:callid:abc>;purpose=emergency-CallId".to_string(),
449/// );
450///
451/// assert_eq!(
452///     headers.sip_header(SipHeader::CallInfo),
453///     Some("<urn:emergency:uid:callid:abc>;purpose=emergency-CallId"),
454/// );
455///
456/// let ci = headers.call_info().unwrap().unwrap();
457/// assert_eq!(ci.entries()[0].purpose(), Some("emergency-CallId"));
458/// ```
459pub trait SipHeaderLookup {
460    /// Look up a SIP header by its canonical name (e.g. `"Call-Info"`).
461    fn sip_header_str(&self, name: &str) -> Option<&str>;
462
463    /// Look up a SIP header by its [`SipHeader`] enum variant.
464    fn sip_header(&self, name: SipHeader) -> Option<&str> {
465        self.sip_header_str(name.as_str())
466    }
467
468    /// Raw `Call-Info` header value (RFC 3261 section 20.9).
469    fn call_info_raw(&self) -> Option<&str> {
470        self.sip_header(SipHeader::CallInfo)
471    }
472
473    /// Parse the `Call-Info` header into a [`SipCallInfo`].
474    ///
475    /// Returns `Ok(None)` if the header is absent, `Err` if present but unparseable.
476    fn call_info(&self) -> Result<Option<SipCallInfo>, SipCallInfoError> {
477        match self.call_info_raw() {
478            Some(s) => SipCallInfo::parse(s).map(Some),
479            None => Ok(None),
480        }
481    }
482
483    /// Raw `History-Info` header value (RFC 7044).
484    fn history_info_raw(&self) -> Option<&str> {
485        self.sip_header(SipHeader::HistoryInfo)
486    }
487
488    /// Parse the `History-Info` header into a [`HistoryInfo`].
489    ///
490    /// Returns `Ok(None)` if the header is absent, `Err` if present but unparseable.
491    fn history_info(&self) -> Result<Option<HistoryInfo>, HistoryInfoError> {
492        match self.history_info_raw() {
493            Some(s) => HistoryInfo::parse(s).map(Some),
494            None => Ok(None),
495        }
496    }
497
498    /// Raw `P-Asserted-Identity` header value (RFC 3325).
499    fn p_asserted_identity_raw(&self) -> Option<&str> {
500        self.sip_header(SipHeader::PAssertedIdentity)
501    }
502
503    /// Parse the `P-Asserted-Identity` header into a [`SipHeaderAddr`].
504    ///
505    /// Returns `Ok(None)` if the header is absent, `Err` if present but unparseable.
506    fn p_asserted_identity(&self) -> Result<Option<SipHeaderAddr>, ParseSipHeaderAddrError> {
507        match self.p_asserted_identity_raw() {
508            Some(s) => s
509                .parse::<SipHeaderAddr>()
510                .map(Some),
511            None => Ok(None),
512        }
513    }
514}
515
516impl SipHeaderLookup for std::collections::HashMap<String, String> {
517    fn sip_header_str(&self, name: &str) -> Option<&str> {
518        self.get(name)
519            .map(|s| s.as_str())
520    }
521}
522
523#[cfg(test)]
524mod tests {
525    use super::*;
526    use std::collections::HashMap;
527
528    #[test]
529    fn display_round_trip() {
530        assert_eq!(SipHeader::CallInfo.to_string(), "Call-Info");
531        assert_eq!(SipHeader::HistoryInfo.to_string(), "History-Info");
532        assert_eq!(
533            SipHeader::PAssertedIdentity.to_string(),
534            "P-Asserted-Identity"
535        );
536    }
537
538    #[test]
539    fn as_ref_str() {
540        let h: &str = SipHeader::CallInfo.as_ref();
541        assert_eq!(h, "Call-Info");
542    }
543
544    #[test]
545    fn from_str_case_insensitive() {
546        assert_eq!("call-info".parse::<SipHeader>(), Ok(SipHeader::CallInfo));
547        assert_eq!("CALL-INFO".parse::<SipHeader>(), Ok(SipHeader::CallInfo));
548        assert_eq!(
549            "history-info".parse::<SipHeader>(),
550            Ok(SipHeader::HistoryInfo)
551        );
552        assert_eq!(
553            "p-asserted-identity".parse::<SipHeader>(),
554            Ok(SipHeader::PAssertedIdentity)
555        );
556        assert_eq!(
557            "P-ASSERTED-IDENTITY".parse::<SipHeader>(),
558            Ok(SipHeader::PAssertedIdentity)
559        );
560    }
561
562    #[test]
563    fn from_str_unknown() {
564        assert!("X-Custom"
565            .parse::<SipHeader>()
566            .is_err());
567    }
568
569    #[test]
570    fn from_str_round_trip_all() {
571        let variants = [
572            SipHeader::Accept,
573            SipHeader::AcceptContact,
574            SipHeader::AcceptEncoding,
575            SipHeader::AcceptLanguage,
576            SipHeader::AcceptResourcePriority,
577            SipHeader::AdditionalIdentity,
578            SipHeader::AlertInfo,
579            SipHeader::AlertmsgError,
580            SipHeader::Allow,
581            SipHeader::AllowEvents,
582            SipHeader::AnswerMode,
583            SipHeader::AttestationInfo,
584            SipHeader::AuthenticationInfo,
585            SipHeader::Authorization,
586            SipHeader::CallId,
587            SipHeader::CallInfo,
588            SipHeader::CellularNetworkInfo,
589            SipHeader::Contact,
590            SipHeader::ContentDisposition,
591            SipHeader::ContentEncoding,
592            SipHeader::ContentId,
593            SipHeader::ContentLanguage,
594            SipHeader::ContentLength,
595            SipHeader::ContentType,
596            SipHeader::Cseq,
597            SipHeader::Date,
598            SipHeader::DcInfo,
599            SipHeader::Encryption,
600            SipHeader::ErrorInfo,
601            SipHeader::Event,
602            SipHeader::Expires,
603            SipHeader::FeatureCaps,
604            SipHeader::FlowTimer,
605            SipHeader::From,
606            SipHeader::Geolocation,
607            SipHeader::GeolocationError,
608            SipHeader::GeolocationRouting,
609            SipHeader::Hide,
610            SipHeader::HistoryInfo,
611            SipHeader::Identity,
612            SipHeader::IdentityInfo,
613            SipHeader::InfoPackage,
614            SipHeader::InReplyTo,
615            SipHeader::Join,
616            SipHeader::MaxBreadth,
617            SipHeader::MaxForwards,
618            SipHeader::MimeVersion,
619            SipHeader::MinExpires,
620            SipHeader::MinSe,
621            SipHeader::Organization,
622            SipHeader::OriginationId,
623            SipHeader::PAccessNetworkInfo,
624            SipHeader::PAnswerState,
625            SipHeader::PAssertedIdentity,
626            SipHeader::PAssertedService,
627            SipHeader::PAssociatedUri,
628            SipHeader::PCalledPartyId,
629            SipHeader::PChargeInfo,
630            SipHeader::PChargingFunctionAddresses,
631            SipHeader::PChargingVector,
632            SipHeader::PDcsTracePartyId,
633            SipHeader::PDcsOsps,
634            SipHeader::PDcsBillingInfo,
635            SipHeader::PDcsLaes,
636            SipHeader::PDcsRedirect,
637            SipHeader::PEarlyMedia,
638            SipHeader::PMediaAuthorization,
639            SipHeader::PPreferredIdentity,
640            SipHeader::PPreferredService,
641            SipHeader::PPrivateNetworkIndication,
642            SipHeader::PProfileKey,
643            SipHeader::PRefusedUriList,
644            SipHeader::PServedUser,
645            SipHeader::PUserDatabase,
646            SipHeader::PVisitedNetworkId,
647            SipHeader::Path,
648            SipHeader::PermissionMissing,
649            SipHeader::PolicyContact,
650            SipHeader::PolicyId,
651            SipHeader::Priority,
652            SipHeader::PriorityShare,
653            SipHeader::PriorityVerstat,
654            SipHeader::PrivAnswerMode,
655            SipHeader::Privacy,
656            SipHeader::ProxyAuthenticate,
657            SipHeader::ProxyAuthorization,
658            SipHeader::ProxyRequire,
659            SipHeader::Rack,
660            SipHeader::Reason,
661            SipHeader::ReasonPhrase,
662            SipHeader::RecordRoute,
663            SipHeader::RecvInfo,
664            SipHeader::ReferEventsAt,
665            SipHeader::ReferSub,
666            SipHeader::ReferTo,
667            SipHeader::ReferredBy,
668            SipHeader::RejectContact,
669            SipHeader::RelayedCharge,
670            SipHeader::Replaces,
671            SipHeader::ReplyTo,
672            SipHeader::RequestDisposition,
673            SipHeader::Require,
674            SipHeader::ResourcePriority,
675            SipHeader::ResourceShare,
676            SipHeader::ResponseKey,
677            SipHeader::ResponseSource,
678            SipHeader::RestorationInfo,
679            SipHeader::RetryAfter,
680            SipHeader::Route,
681            SipHeader::Rseq,
682            SipHeader::SecurityClient,
683            SipHeader::SecurityServer,
684            SipHeader::SecurityVerify,
685            SipHeader::Server,
686            SipHeader::ServiceInteractInfo,
687            SipHeader::ServiceRoute,
688            SipHeader::SessionExpires,
689            SipHeader::SessionId,
690            SipHeader::SipEtag,
691            SipHeader::SipIfMatch,
692            SipHeader::Subject,
693            SipHeader::SubscriptionState,
694            SipHeader::Supported,
695            SipHeader::SuppressIfMatch,
696            SipHeader::TargetDialog,
697            SipHeader::Timestamp,
698            SipHeader::To,
699            SipHeader::TriggerConsent,
700            SipHeader::Unsupported,
701            SipHeader::UserAgent,
702            SipHeader::UserToUser,
703            SipHeader::Via,
704            SipHeader::Warning,
705            SipHeader::WwwAuthenticate,
706        ];
707        for v in variants {
708            let wire = v.to_string();
709            let parsed: SipHeader = wire
710                .parse()
711                .unwrap();
712            assert_eq!(parsed, v, "round-trip failed for {wire}");
713        }
714    }
715
716    fn headers_with(pairs: &[(&str, &str)]) -> HashMap<String, String> {
717        pairs
718            .iter()
719            .map(|(k, v)| (k.to_string(), v.to_string()))
720            .collect()
721    }
722
723    #[test]
724    fn sip_header_by_enum() {
725        let h = headers_with(&[("Call-Info", "<urn:x>;purpose=icon")]);
726        assert_eq!(
727            h.sip_header(SipHeader::CallInfo),
728            Some("<urn:x>;purpose=icon")
729        );
730    }
731
732    #[test]
733    fn call_info_raw_lookup() {
734        let h = headers_with(&[(
735            "Call-Info",
736            "<urn:emergency:uid:callid:test:bcf.example.com>;purpose=emergency-CallId",
737        )]);
738        assert_eq!(
739            h.call_info_raw(),
740            Some("<urn:emergency:uid:callid:test:bcf.example.com>;purpose=emergency-CallId")
741        );
742    }
743
744    #[test]
745    fn call_info_typed() {
746        let h = headers_with(&[(
747            "Call-Info",
748            "<urn:emergency:uid:callid:test:bcf.example.com>;purpose=emergency-CallId",
749        )]);
750        let ci = h
751            .call_info()
752            .unwrap()
753            .unwrap();
754        assert_eq!(ci.len(), 1);
755        assert_eq!(ci.entries()[0].purpose(), Some("emergency-CallId"));
756    }
757
758    #[test]
759    fn call_info_absent() {
760        let h = headers_with(&[]);
761        assert_eq!(
762            h.call_info()
763                .unwrap(),
764            None
765        );
766    }
767
768    #[test]
769    fn p_asserted_identity_raw_lookup() {
770        let h = headers_with(&[(
771            "P-Asserted-Identity",
772            r#""EXAMPLE CO" <sip:+15551234567@198.51.100.1>"#,
773        )]);
774        assert_eq!(
775            h.p_asserted_identity_raw(),
776            Some(r#""EXAMPLE CO" <sip:+15551234567@198.51.100.1>"#)
777        );
778    }
779
780    #[test]
781    fn p_asserted_identity_typed() {
782        let h = headers_with(&[(
783            "P-Asserted-Identity",
784            r#""EXAMPLE CO" <sip:+15551234567@198.51.100.1>"#,
785        )]);
786        let pai = h
787            .p_asserted_identity()
788            .unwrap()
789            .unwrap();
790        assert_eq!(pai.display_name(), Some("EXAMPLE CO"));
791    }
792
793    #[test]
794    fn p_asserted_identity_absent() {
795        let h = headers_with(&[]);
796        assert_eq!(
797            h.p_asserted_identity()
798                .unwrap(),
799            None
800        );
801    }
802
803    #[test]
804    fn history_info_raw_lookup() {
805        let h = headers_with(&[(
806            "History-Info",
807            "<sip:alice@esrp.example.com>;index=1,<sip:sos@psap.example.com>;index=1.1",
808        )]);
809        assert!(h
810            .history_info_raw()
811            .unwrap()
812            .contains("esrp.example.com"));
813    }
814
815    #[test]
816    fn history_info_typed() {
817        let h = headers_with(&[(
818            "History-Info",
819            "<sip:alice@esrp.example.com>;index=1,<sip:sos@psap.example.com>;index=1.1",
820        )]);
821        let hi = h
822            .history_info()
823            .unwrap()
824            .unwrap();
825        assert_eq!(hi.len(), 2);
826        assert_eq!(hi.entries()[0].index(), Some("1"));
827        assert_eq!(hi.entries()[1].index(), Some("1.1"));
828    }
829
830    #[test]
831    fn history_info_absent() {
832        let h = headers_with(&[]);
833        assert_eq!(
834            h.history_info()
835                .unwrap(),
836            None
837        );
838    }
839
840    #[test]
841    fn extract_from_sip_message() {
842        let msg = concat!(
843            "INVITE sip:bob@host SIP/2.0\r\n",
844            "Call-Info: <urn:emergency:uid:callid:abc>;purpose=emergency-CallId\r\n",
845            "History-Info: <sip:esrp@example.com>;index=1\r\n",
846            "P-Asserted-Identity: \"Corp\" <sip:+15551234567@198.51.100.1>\r\n",
847            "\r\n",
848        );
849        assert_eq!(
850            SipHeader::CallInfo.extract_from(msg),
851            Some("<urn:emergency:uid:callid:abc>;purpose=emergency-CallId".into())
852        );
853        assert_eq!(
854            SipHeader::HistoryInfo.extract_from(msg),
855            Some("<sip:esrp@example.com>;index=1".into())
856        );
857        assert_eq!(
858            SipHeader::PAssertedIdentity.extract_from(msg),
859            Some("\"Corp\" <sip:+15551234567@198.51.100.1>".into())
860        );
861    }
862
863    #[test]
864    fn extract_from_missing() {
865        let msg = concat!(
866            "INVITE sip:bob@host SIP/2.0\r\n",
867            "From: Alice <sip:alice@host>\r\n",
868            "\r\n",
869        );
870        assert_eq!(SipHeader::CallInfo.extract_from(msg), None);
871        assert_eq!(SipHeader::PAssertedIdentity.extract_from(msg), None);
872    }
873
874    #[test]
875    fn missing_headers_return_none() {
876        let h = headers_with(&[]);
877        assert_eq!(h.call_info_raw(), None);
878        assert_eq!(
879            h.call_info()
880                .unwrap(),
881            None
882        );
883        assert_eq!(h.history_info_raw(), None);
884        assert_eq!(
885            h.history_info()
886                .unwrap(),
887            None
888        );
889        assert_eq!(h.p_asserted_identity_raw(), None);
890        assert_eq!(
891            h.p_asserted_identity()
892                .unwrap(),
893            None
894        );
895    }
896}
897
898#[cfg(test)]
899mod compact_form_tests {
900    use super::*;
901
902    #[test]
903    fn from_compact_known() {
904        assert_eq!(SipHeader::from_compact(b'f'), Some(SipHeader::From));
905        assert_eq!(SipHeader::from_compact(b'F'), Some(SipHeader::From));
906        assert_eq!(SipHeader::from_compact(b'v'), Some(SipHeader::Via));
907        assert_eq!(SipHeader::from_compact(b'i'), Some(SipHeader::CallId));
908        assert_eq!(SipHeader::from_compact(b'm'), Some(SipHeader::Contact));
909        assert_eq!(SipHeader::from_compact(b't'), Some(SipHeader::To));
910        assert_eq!(SipHeader::from_compact(b'c'), Some(SipHeader::ContentType));
911    }
912
913    #[test]
914    fn from_compact_unknown() {
915        assert_eq!(SipHeader::from_compact(b'z'), None);
916        assert_eq!(SipHeader::from_compact(b'g'), None);
917    }
918
919    #[test]
920    fn compact_form_roundtrip() {
921        assert_eq!(SipHeader::From.compact_form(), Some('f'));
922        assert_eq!(SipHeader::Via.compact_form(), Some('v'));
923        assert_eq!(SipHeader::CallId.compact_form(), Some('i'));
924        assert_eq!(SipHeader::Contact.compact_form(), Some('m'));
925    }
926
927    #[test]
928    fn compact_form_absent() {
929        assert_eq!(SipHeader::HistoryInfo.compact_form(), None);
930        assert_eq!(SipHeader::PAssertedIdentity.compact_form(), None);
931    }
932
933    #[test]
934    fn parse_name_compact() {
935        assert_eq!(SipHeader::parse_name("f"), Ok(SipHeader::From));
936        assert_eq!(SipHeader::parse_name("F"), Ok(SipHeader::From));
937        assert_eq!(SipHeader::parse_name("v"), Ok(SipHeader::Via));
938    }
939
940    #[test]
941    fn parse_name_full() {
942        assert_eq!(SipHeader::parse_name("From"), Ok(SipHeader::From));
943        assert_eq!(SipHeader::parse_name("Via"), Ok(SipHeader::Via));
944    }
945
946    #[test]
947    fn parse_name_unknown() {
948        assert!(SipHeader::parse_name("X-Custom").is_err());
949    }
950
951    #[test]
952    fn all_compact_forms_resolve() {
953        let expected = [
954            ('a', SipHeader::AcceptContact),
955            ('b', SipHeader::ReferredBy),
956            ('c', SipHeader::ContentType),
957            ('d', SipHeader::RequestDisposition),
958            ('e', SipHeader::ContentEncoding),
959            ('f', SipHeader::From),
960            ('i', SipHeader::CallId),
961            ('j', SipHeader::RejectContact),
962            ('k', SipHeader::Supported),
963            ('l', SipHeader::ContentLength),
964            ('m', SipHeader::Contact),
965            ('n', SipHeader::IdentityInfo),
966            ('o', SipHeader::Event),
967            ('r', SipHeader::ReferTo),
968            ('s', SipHeader::Subject),
969            ('t', SipHeader::To),
970            ('u', SipHeader::AllowEvents),
971            ('v', SipHeader::Via),
972            ('x', SipHeader::SessionExpires),
973            ('y', SipHeader::Identity),
974        ];
975        for (ch, header) in expected {
976            assert_eq!(
977                SipHeader::from_compact(ch as u8),
978                Some(header),
979                "compact form '{ch}' failed"
980            );
981            assert_eq!(
982                header.compact_form(),
983                Some(ch),
984                "compact_form() for {} failed",
985                header
986            );
987        }
988    }
989}
990
991#[cfg(test)]
992mod multi_valued_tests {
993    use super::*;
994
995    #[test]
996    fn rfc3261_multi_valued_headers() {
997        assert!(SipHeader::Via.is_multi_valued());
998        assert!(SipHeader::Route.is_multi_valued());
999        assert!(SipHeader::RecordRoute.is_multi_valued());
1000        assert!(SipHeader::Contact.is_multi_valued());
1001        assert!(SipHeader::Allow.is_multi_valued());
1002        assert!(SipHeader::Supported.is_multi_valued());
1003        assert!(SipHeader::Require.is_multi_valued());
1004        assert!(SipHeader::ProxyRequire.is_multi_valued());
1005        assert!(SipHeader::Unsupported.is_multi_valued());
1006        assert!(SipHeader::Authorization.is_multi_valued());
1007        assert!(SipHeader::ProxyAuthorization.is_multi_valued());
1008        assert!(SipHeader::WwwAuthenticate.is_multi_valued());
1009        assert!(SipHeader::ProxyAuthenticate.is_multi_valued());
1010        assert!(SipHeader::Warning.is_multi_valued());
1011        assert!(SipHeader::ErrorInfo.is_multi_valued());
1012        assert!(SipHeader::CallInfo.is_multi_valued());
1013        assert!(SipHeader::AlertInfo.is_multi_valued());
1014        assert!(SipHeader::Accept.is_multi_valued());
1015        assert!(SipHeader::AcceptEncoding.is_multi_valued());
1016        assert!(SipHeader::AcceptLanguage.is_multi_valued());
1017        assert!(SipHeader::ContentEncoding.is_multi_valued());
1018        assert!(SipHeader::ContentLanguage.is_multi_valued());
1019        assert!(SipHeader::InReplyTo.is_multi_valued());
1020    }
1021
1022    #[test]
1023    fn extension_multi_valued_headers() {
1024        assert!(SipHeader::PAssertedIdentity.is_multi_valued());
1025        assert!(SipHeader::PPreferredIdentity.is_multi_valued());
1026        assert!(SipHeader::AllowEvents.is_multi_valued());
1027        assert!(SipHeader::SecurityClient.is_multi_valued());
1028        assert!(SipHeader::SecurityServer.is_multi_valued());
1029        assert!(SipHeader::SecurityVerify.is_multi_valued());
1030        assert!(SipHeader::Path.is_multi_valued());
1031        assert!(SipHeader::ServiceRoute.is_multi_valued());
1032        assert!(SipHeader::HistoryInfo.is_multi_valued());
1033    }
1034
1035    #[test]
1036    fn single_valued_headers() {
1037        assert!(!SipHeader::From.is_multi_valued());
1038        assert!(!SipHeader::To.is_multi_valued());
1039        assert!(!SipHeader::CallId.is_multi_valued());
1040        assert!(!SipHeader::Cseq.is_multi_valued());
1041        assert!(!SipHeader::MaxForwards.is_multi_valued());
1042        assert!(!SipHeader::ContentType.is_multi_valued());
1043        assert!(!SipHeader::ContentLength.is_multi_valued());
1044        assert!(!SipHeader::Expires.is_multi_valued());
1045        assert!(!SipHeader::Date.is_multi_valued());
1046        assert!(!SipHeader::Subject.is_multi_valued());
1047        assert!(!SipHeader::ReplyTo.is_multi_valued());
1048        assert!(!SipHeader::Server.is_multi_valued());
1049        assert!(!SipHeader::UserAgent.is_multi_valued());
1050    }
1051
1052    #[test]
1053    #[cfg(feature = "draft")]
1054    fn draft_multi_valued_headers() {
1055        assert!(SipHeader::Diversion.is_multi_valued());
1056        assert!(SipHeader::RemotePartyId.is_multi_valued());
1057    }
1058
1059    #[test]
1060    #[cfg(feature = "draft")]
1061    fn draft_parse_roundtrip() {
1062        let d: SipHeader = "Diversion"
1063            .parse()
1064            .unwrap();
1065        assert_eq!(d, SipHeader::Diversion);
1066        assert_eq!(d.to_string(), "Diversion");
1067
1068        let r: SipHeader = "remote-party-id"
1069            .parse()
1070            .unwrap();
1071        assert_eq!(r, SipHeader::RemotePartyId);
1072        assert_eq!(r.to_string(), "Remote-Party-ID");
1073    }
1074}
1075
1076#[cfg(test)]
1077mod special_case_tests {
1078    use super::*;
1079
1080    #[test]
1081    fn cseq_variants() {
1082        assert_eq!("CSeq".parse::<SipHeader>(), Ok(SipHeader::Cseq));
1083        assert_eq!("cseq".parse::<SipHeader>(), Ok(SipHeader::Cseq));
1084        assert_eq!("CSEQ".parse::<SipHeader>(), Ok(SipHeader::Cseq));
1085        assert_eq!(SipHeader::Cseq.to_string(), "CSeq");
1086    }
1087
1088    #[test]
1089    fn www_authenticate_variants() {
1090        assert_eq!(
1091            "WWW-Authenticate".parse::<SipHeader>(),
1092            Ok(SipHeader::WwwAuthenticate)
1093        );
1094        assert_eq!(
1095            "www-authenticate".parse::<SipHeader>(),
1096            Ok(SipHeader::WwwAuthenticate)
1097        );
1098        assert_eq!(SipHeader::WwwAuthenticate.to_string(), "WWW-Authenticate");
1099    }
1100
1101    #[test]
1102    fn rack_rseq_variants() {
1103        assert_eq!("RAck".parse::<SipHeader>(), Ok(SipHeader::Rack));
1104        assert_eq!("rack".parse::<SipHeader>(), Ok(SipHeader::Rack));
1105        assert_eq!(SipHeader::Rack.to_string(), "RAck");
1106
1107        assert_eq!("RSeq".parse::<SipHeader>(), Ok(SipHeader::Rseq));
1108        assert_eq!("rseq".parse::<SipHeader>(), Ok(SipHeader::Rseq));
1109        assert_eq!(SipHeader::Rseq.to_string(), "RSeq");
1110    }
1111
1112    #[test]
1113    fn user_to_user_variants() {
1114        assert_eq!(
1115            "User-to-User".parse::<SipHeader>(),
1116            Ok(SipHeader::UserToUser)
1117        );
1118        assert_eq!(
1119            "user-to-user".parse::<SipHeader>(),
1120            Ok(SipHeader::UserToUser)
1121        );
1122        assert_eq!(SipHeader::UserToUser.to_string(), "User-to-User");
1123    }
1124
1125    #[test]
1126    fn p_header_variants() {
1127        assert_eq!(
1128            "P-DCS-Trace-Party-ID".parse::<SipHeader>(),
1129            Ok(SipHeader::PDcsTracePartyId)
1130        );
1131        assert_eq!(
1132            "p-dcs-trace-party-id".parse::<SipHeader>(),
1133            Ok(SipHeader::PDcsTracePartyId)
1134        );
1135        assert_eq!(
1136            SipHeader::PDcsTracePartyId.to_string(),
1137            "P-DCS-Trace-Party-ID"
1138        );
1139    }
1140}