1use crate::call_info::{SipCallInfo, SipCallInfoError};
8use crate::header_addr::{ParseSipHeaderAddrError, SipHeaderAddr};
9use crate::history_info::{HistoryInfo, HistoryInfoError};
10
11#[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 pub enum SipHeader {
31 Accept => "Accept",
33 AcceptContact => "Accept-Contact",
35 AcceptEncoding => "Accept-Encoding",
37 AcceptLanguage => "Accept-Language",
39 AcceptResourcePriority => "Accept-Resource-Priority",
41 AdditionalIdentity => "Additional-Identity",
43 AlertInfo => "Alert-Info",
45 AlertmsgError => "AlertMsg-Error",
47 Allow => "Allow",
49 AllowEvents => "Allow-Events",
51 AnswerMode => "Answer-Mode",
53 AttestationInfo => "Attestation-Info",
55 AuthenticationInfo => "Authentication-Info",
57 Authorization => "Authorization",
59 CallId => "Call-ID",
61 CallInfo => "Call-Info",
63 CellularNetworkInfo => "Cellular-Network-Info",
65 Contact => "Contact",
67 ContentDisposition => "Content-Disposition",
69 ContentEncoding => "Content-Encoding",
71 ContentId => "Content-ID",
73 ContentLanguage => "Content-Language",
75 ContentLength => "Content-Length",
77 ContentType => "Content-Type",
79 Cseq => "CSeq",
81 Date => "Date",
83 DcInfo => "DC-Info",
85 Encryption => "Encryption",
87 ErrorInfo => "Error-Info",
89 Event => "Event",
91 Expires => "Expires",
93 FeatureCaps => "Feature-Caps",
95 FlowTimer => "Flow-Timer",
97 From => "From",
99 Geolocation => "Geolocation",
101 GeolocationError => "Geolocation-Error",
103 GeolocationRouting => "Geolocation-Routing",
105 Hide => "Hide",
107 HistoryInfo => "History-Info",
109 Identity => "Identity",
111 IdentityInfo => "Identity-Info",
113 InfoPackage => "Info-Package",
115 InReplyTo => "In-Reply-To",
117 Join => "Join",
119 MaxBreadth => "Max-Breadth",
121 MaxForwards => "Max-Forwards",
123 MimeVersion => "MIME-Version",
125 MinExpires => "Min-Expires",
127 MinSe => "Min-SE",
129 Organization => "Organization",
131 OriginationId => "Origination-Id",
133 PAccessNetworkInfo => "P-Access-Network-Info",
135 PAnswerState => "P-Answer-State",
137 PAssertedIdentity => "P-Asserted-Identity",
139 PAssertedService => "P-Asserted-Service",
141 PAssociatedUri => "P-Associated-URI",
143 PCalledPartyId => "P-Called-Party-ID",
145 PChargeInfo => "P-Charge-Info",
147 PChargingFunctionAddresses => "P-Charging-Function-Addresses",
149 PChargingVector => "P-Charging-Vector",
151 PDcsTracePartyId => "P-DCS-Trace-Party-ID",
153 PDcsOsps => "P-DCS-OSPS",
155 PDcsBillingInfo => "P-DCS-Billing-Info",
157 PDcsLaes => "P-DCS-LAES",
159 PDcsRedirect => "P-DCS-Redirect",
161 PEarlyMedia => "P-Early-Media",
163 PMediaAuthorization => "P-Media-Authorization",
165 PPreferredIdentity => "P-Preferred-Identity",
167 PPreferredService => "P-Preferred-Service",
169 PPrivateNetworkIndication => "P-Private-Network-Indication",
171 PProfileKey => "P-Profile-Key",
173 PRefusedUriList => "P-Refused-URI-List",
175 PServedUser => "P-Served-User",
177 PUserDatabase => "P-User-Database",
179 PVisitedNetworkId => "P-Visited-Network-ID",
181 Path => "Path",
183 PermissionMissing => "Permission-Missing",
185 PolicyContact => "Policy-Contact",
187 PolicyId => "Policy-ID",
189 Priority => "Priority",
191 PriorityShare => "Priority-Share",
193 PriorityVerstat => "Priority-Verstat",
195 PrivAnswerMode => "Priv-Answer-Mode",
197 Privacy => "Privacy",
199 ProxyAuthenticate => "Proxy-Authenticate",
201 ProxyAuthorization => "Proxy-Authorization",
203 ProxyRequire => "Proxy-Require",
205 Rack => "RAck",
207 Reason => "Reason",
209 ReasonPhrase => "Reason-Phrase",
211 RecordRoute => "Record-Route",
213 RecvInfo => "Recv-Info",
215 ReferEventsAt => "Refer-Events-At",
217 ReferSub => "Refer-Sub",
219 ReferTo => "Refer-To",
221 ReferredBy => "Referred-By",
223 RejectContact => "Reject-Contact",
225 RelayedCharge => "Relayed-Charge",
227 Replaces => "Replaces",
229 ReplyTo => "Reply-To",
231 RequestDisposition => "Request-Disposition",
233 Require => "Require",
235 ResourcePriority => "Resource-Priority",
237 ResourceShare => "Resource-Share",
239 ResponseKey => "Response-Key",
241 ResponseSource => "Response-Source",
243 RestorationInfo => "Restoration-Info",
245 RetryAfter => "Retry-After",
247 Route => "Route",
249 Rseq => "RSeq",
251 SecurityClient => "Security-Client",
253 SecurityServer => "Security-Server",
255 SecurityVerify => "Security-Verify",
257 Server => "Server",
259 ServiceInteractInfo => "Service-Interact-Info",
261 ServiceRoute => "Service-Route",
263 SessionExpires => "Session-Expires",
265 SessionId => "Session-ID",
267 SipEtag => "SIP-ETag",
269 SipIfMatch => "SIP-If-Match",
271 Subject => "Subject",
273 SubscriptionState => "Subscription-State",
275 Supported => "Supported",
277 SuppressIfMatch => "Suppress-If-Match",
279 TargetDialog => "Target-Dialog",
281 Timestamp => "Timestamp",
283 To => "To",
285 TriggerConsent => "Trigger-Consent",
287 Unsupported => "Unsupported",
289 UserAgent => "User-Agent",
291 UserToUser => "User-to-User",
293 Via => "Via",
295 Warning => "Warning",
297 WwwAuthenticate => "WWW-Authenticate",
299 }
300}
301
302const 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 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 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 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
363pub trait SipHeaderLookup {
389 fn sip_header_str(&self, name: &str) -> Option<&str>;
391
392 fn sip_header(&self, name: SipHeader) -> Option<&str> {
394 self.sip_header_str(name.as_str())
395 }
396
397 fn call_info_raw(&self) -> Option<&str> {
399 self.sip_header(SipHeader::CallInfo)
400 }
401
402 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 fn history_info_raw(&self) -> Option<&str> {
414 self.sip_header(SipHeader::HistoryInfo)
415 }
416
417 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 fn p_asserted_identity_raw(&self) -> Option<&str> {
429 self.sip_header(SipHeader::PAssertedIdentity)
430 }
431
432 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}