use crate::accept::{SipAccept, SipAcceptError};
use crate::accept_encoding::{SipAcceptEncoding, SipAcceptEncodingError};
use crate::accept_language::{SipAcceptLanguage, SipAcceptLanguageError};
use crate::auth::{SipAuthError, SipAuthValue};
use crate::contact::ContactValue;
use crate::header_addr::{ParseSipHeaderAddrError, SipHeaderAddr};
use crate::history_info::{HistoryInfo, HistoryInfoError};
use crate::security::{SipSecurity, SipSecurityError};
use crate::uri_info::{UriInfo, UriInfoError};
use crate::via::{SipVia, SipViaError};
use crate::warning::{SipWarning, SipWarningError};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseSipHeaderError(pub String);
impl std::fmt::Display for ParseSipHeaderError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "unknown SIP header: {}", self.0)
}
}
impl std::error::Error for ParseSipHeaderError {}
define_header_enum! {
error_type: ParseSipHeaderError,
pub enum SipHeader {
Accept => "Accept",
AcceptContact => "Accept-Contact",
AcceptEncoding => "Accept-Encoding",
AcceptLanguage => "Accept-Language",
AcceptResourcePriority => "Accept-Resource-Priority",
AdditionalIdentity => "Additional-Identity",
AlertInfo => "Alert-Info",
AlertmsgError => "AlertMsg-Error",
Allow => "Allow",
AllowEvents => "Allow-Events",
AnswerMode => "Answer-Mode",
AttestationInfo => "Attestation-Info",
AuthenticationInfo => "Authentication-Info",
Authorization => "Authorization",
CallId => "Call-ID",
CallInfo => "Call-Info",
CellularNetworkInfo => "Cellular-Network-Info",
Contact => "Contact",
ContentDisposition => "Content-Disposition",
ContentEncoding => "Content-Encoding",
ContentId => "Content-ID",
ContentLanguage => "Content-Language",
ContentLength => "Content-Length",
ContentType => "Content-Type",
Cseq => "CSeq",
Date => "Date",
DcInfo => "DC-Info",
Encryption => "Encryption",
ErrorInfo => "Error-Info",
Event => "Event",
Expires => "Expires",
FeatureCaps => "Feature-Caps",
FlowTimer => "Flow-Timer",
From => "From",
Geolocation => "Geolocation",
GeolocationError => "Geolocation-Error",
GeolocationRouting => "Geolocation-Routing",
Hide => "Hide",
HistoryInfo => "History-Info",
Identity => "Identity",
IdentityInfo => "Identity-Info",
InfoPackage => "Info-Package",
InReplyTo => "In-Reply-To",
Join => "Join",
MaxBreadth => "Max-Breadth",
MaxForwards => "Max-Forwards",
MimeVersion => "MIME-Version",
MinExpires => "Min-Expires",
MinSe => "Min-SE",
Organization => "Organization",
OriginationId => "Origination-Id",
PAccessNetworkInfo => "P-Access-Network-Info",
PAnswerState => "P-Answer-State",
PAssertedIdentity => "P-Asserted-Identity",
PAssertedService => "P-Asserted-Service",
PAssociatedUri => "P-Associated-URI",
PCalledPartyId => "P-Called-Party-ID",
PChargeInfo => "P-Charge-Info",
PChargingFunctionAddresses => "P-Charging-Function-Addresses",
PChargingVector => "P-Charging-Vector",
PDcsTracePartyId => "P-DCS-Trace-Party-ID",
PDcsOsps => "P-DCS-OSPS",
PDcsBillingInfo => "P-DCS-Billing-Info",
PDcsLaes => "P-DCS-LAES",
PDcsRedirect => "P-DCS-Redirect",
PEarlyMedia => "P-Early-Media",
PMediaAuthorization => "P-Media-Authorization",
PPreferredIdentity => "P-Preferred-Identity",
PPreferredService => "P-Preferred-Service",
PPrivateNetworkIndication => "P-Private-Network-Indication",
PProfileKey => "P-Profile-Key",
PRefusedUriList => "P-Refused-URI-List",
PServedUser => "P-Served-User",
PUserDatabase => "P-User-Database",
PVisitedNetworkId => "P-Visited-Network-ID",
Path => "Path",
PermissionMissing => "Permission-Missing",
PolicyContact => "Policy-Contact",
PolicyId => "Policy-ID",
Priority => "Priority",
PriorityShare => "Priority-Share",
PriorityVerstat => "Priority-Verstat",
PrivAnswerMode => "Priv-Answer-Mode",
Privacy => "Privacy",
ProxyAuthenticate => "Proxy-Authenticate",
ProxyAuthorization => "Proxy-Authorization",
ProxyRequire => "Proxy-Require",
Rack => "RAck",
Reason => "Reason",
ReasonPhrase => "Reason-Phrase",
RecordRoute => "Record-Route",
RecvInfo => "Recv-Info",
ReferEventsAt => "Refer-Events-At",
ReferSub => "Refer-Sub",
ReferTo => "Refer-To",
ReferredBy => "Referred-By",
RejectContact => "Reject-Contact",
RelayedCharge => "Relayed-Charge",
Replaces => "Replaces",
ReplyTo => "Reply-To",
RequestDisposition => "Request-Disposition",
Require => "Require",
ResourcePriority => "Resource-Priority",
ResourceShare => "Resource-Share",
ResponseKey => "Response-Key",
ResponseSource => "Response-Source",
RestorationInfo => "Restoration-Info",
RetryAfter => "Retry-After",
Route => "Route",
Rseq => "RSeq",
SecurityClient => "Security-Client",
SecurityServer => "Security-Server",
SecurityVerify => "Security-Verify",
Server => "Server",
ServiceInteractInfo => "Service-Interact-Info",
ServiceRoute => "Service-Route",
SessionExpires => "Session-Expires",
SessionId => "Session-ID",
SipEtag => "SIP-ETag",
SipIfMatch => "SIP-If-Match",
Subject => "Subject",
SubscriptionState => "Subscription-State",
Supported => "Supported",
SuppressIfMatch => "Suppress-If-Match",
TargetDialog => "Target-Dialog",
Timestamp => "Timestamp",
To => "To",
TriggerConsent => "Trigger-Consent",
Unsupported => "Unsupported",
UserAgent => "User-Agent",
UserToUser => "User-to-User",
Via => "Via",
Warning => "Warning",
WwwAuthenticate => "WWW-Authenticate",
#[cfg(feature = "draft")]
Diversion => "Diversion",
#[cfg(feature = "draft")]
RemotePartyId => "Remote-Party-ID",
}
}
const COMPACT_FORMS: &[(u8, SipHeader)] = &[
(b'a', SipHeader::AcceptContact),
(b'b', SipHeader::ReferredBy),
(b'c', SipHeader::ContentType),
(b'd', SipHeader::RequestDisposition),
(b'e', SipHeader::ContentEncoding),
(b'f', SipHeader::From),
(b'i', SipHeader::CallId),
(b'j', SipHeader::RejectContact),
(b'k', SipHeader::Supported),
(b'l', SipHeader::ContentLength),
(b'm', SipHeader::Contact),
(b'n', SipHeader::IdentityInfo),
(b'o', SipHeader::Event),
(b'r', SipHeader::ReferTo),
(b's', SipHeader::Subject),
(b't', SipHeader::To),
(b'u', SipHeader::AllowEvents),
(b'v', SipHeader::Via),
(b'x', SipHeader::SessionExpires),
(b'y', SipHeader::Identity),
];
impl SipHeader {
pub fn from_compact(ch: u8) -> Option<Self> {
let lower = ch.to_ascii_lowercase();
COMPACT_FORMS
.iter()
.find(|(c, _)| *c == lower)
.map(|(_, h)| *h)
}
pub fn compact_form(&self) -> Option<char> {
COMPACT_FORMS
.iter()
.find(|(_, h)| h == self)
.map(|(c, _)| *c as char)
}
pub fn is_multi_valued(&self) -> bool {
if matches!(
self,
Self::Via
| Self::Route
| Self::RecordRoute
| Self::Contact
| Self::Allow
| Self::Supported
| Self::Require
| Self::ProxyRequire
| Self::Unsupported
| Self::Authorization
| Self::ProxyAuthorization
| Self::WwwAuthenticate
| Self::ProxyAuthenticate
| Self::Warning
| Self::ErrorInfo
| Self::CallInfo
| Self::AlertInfo
| Self::Accept
| Self::AcceptEncoding
| Self::AcceptLanguage
| Self::ContentEncoding
| Self::ContentLanguage
| Self::InReplyTo
| Self::PAssertedIdentity
| Self::PPreferredIdentity
| Self::AllowEvents
| Self::SecurityClient
| Self::SecurityServer
| Self::SecurityVerify
| Self::Path
| Self::ServiceRoute
| Self::HistoryInfo
) {
return true;
}
#[cfg(feature = "draft")]
if matches!(
self,
Self::Diversion
| Self::RemotePartyId
) {
return true;
}
false
}
pub fn parse_name(name: &str) -> Result<Self, ParseSipHeaderError> {
if name.len() == 1 {
if let Some(h) = Self::from_compact(name.as_bytes()[0]) {
return Ok(h);
}
}
name.parse()
}
}
pub trait SipHeaderLookup {
fn sip_header_str(&self, name: &str) -> Option<&str>;
fn sip_header(&self, name: SipHeader) -> Option<&str> {
self.sip_header_str(name.as_str())
}
fn sip_header_all_str<'a>(&'a self, name: &str) -> Vec<&'a str> {
self.sip_header_str(name)
.into_iter()
.collect()
}
fn sip_header_all(&self, name: SipHeader) -> Vec<&str> {
self.sip_header_all_str(name.as_str())
}
fn call_info(&self) -> Result<Option<UriInfo>, UriInfoError> {
match self.sip_header(SipHeader::CallInfo) {
Some(s) => UriInfo::parse(s).map(Some),
None => Ok(None),
}
}
fn history_info(&self) -> Result<Option<HistoryInfo>, HistoryInfoError> {
match self.sip_header(SipHeader::HistoryInfo) {
Some(s) => HistoryInfo::parse(s).map(Some),
None => Ok(None),
}
}
fn p_asserted_identity(&self) -> Result<Vec<SipHeaderAddr>, ParseSipHeaderAddrError> {
parse_addr_list(self.sip_header_all(SipHeader::PAssertedIdentity))
}
fn p_preferred_identity(&self) -> Result<Vec<SipHeaderAddr>, ParseSipHeaderAddrError> {
parse_addr_list(self.sip_header_all(SipHeader::PPreferredIdentity))
}
fn route(&self) -> Result<Vec<SipHeaderAddr>, ParseSipHeaderAddrError> {
parse_addr_list(self.sip_header_all(SipHeader::Route))
}
fn record_route(&self) -> Result<Vec<SipHeaderAddr>, ParseSipHeaderAddrError> {
parse_addr_list(self.sip_header_all(SipHeader::RecordRoute))
}
fn path(&self) -> Result<Vec<SipHeaderAddr>, ParseSipHeaderAddrError> {
parse_addr_list(self.sip_header_all(SipHeader::Path))
}
fn service_route(&self) -> Result<Vec<SipHeaderAddr>, ParseSipHeaderAddrError> {
parse_addr_list(self.sip_header_all(SipHeader::ServiceRoute))
}
fn contact(&self) -> Result<Vec<ContactValue>, ParseSipHeaderAddrError> {
match self.sip_header(SipHeader::Contact) {
Some(s) => crate::contact::parse_contact_list(s),
None => Ok(Vec::new()),
}
}
fn alert_info(&self) -> Result<Option<UriInfo>, UriInfoError> {
match self.sip_header(SipHeader::AlertInfo) {
Some(s) => UriInfo::parse(s).map(Some),
None => Ok(None),
}
}
fn error_info(&self) -> Result<Option<UriInfo>, UriInfoError> {
match self.sip_header(SipHeader::ErrorInfo) {
Some(s) => UriInfo::parse(s).map(Some),
None => Ok(None),
}
}
fn allow(&self) -> Vec<&str> {
split_trim(self.sip_header(SipHeader::Allow))
}
fn supported(&self) -> Vec<&str> {
split_trim(self.sip_header(SipHeader::Supported))
}
fn require_header(&self) -> Vec<&str> {
split_trim(self.sip_header(SipHeader::Require))
}
fn proxy_require(&self) -> Vec<&str> {
split_trim(self.sip_header(SipHeader::ProxyRequire))
}
fn unsupported(&self) -> Vec<&str> {
split_trim(self.sip_header(SipHeader::Unsupported))
}
fn allow_events(&self) -> Vec<&str> {
split_trim(self.sip_header(SipHeader::AllowEvents))
}
fn content_encoding(&self) -> Vec<&str> {
split_trim(self.sip_header(SipHeader::ContentEncoding))
}
fn content_language(&self) -> Vec<&str> {
split_trim(self.sip_header(SipHeader::ContentLanguage))
}
fn in_reply_to(&self) -> Vec<&str> {
split_trim(self.sip_header(SipHeader::InReplyTo))
}
fn via(&self) -> Result<Option<SipVia>, SipViaError> {
match self.sip_header(SipHeader::Via) {
Some(s) => SipVia::parse(s).map(Some),
None => Ok(None),
}
}
fn authorization(&self) -> Result<Vec<SipAuthValue>, SipAuthError> {
self.sip_header_all(SipHeader::Authorization)
.into_iter()
.map(|s| s.parse::<SipAuthValue>())
.collect()
}
fn proxy_authorization(&self) -> Result<Vec<SipAuthValue>, SipAuthError> {
self.sip_header_all(SipHeader::ProxyAuthorization)
.into_iter()
.map(|s| s.parse::<SipAuthValue>())
.collect()
}
fn www_authenticate(&self) -> Result<Vec<SipAuthValue>, SipAuthError> {
self.sip_header_all(SipHeader::WwwAuthenticate)
.into_iter()
.map(|s| s.parse::<SipAuthValue>())
.collect()
}
fn proxy_authenticate(&self) -> Result<Vec<SipAuthValue>, SipAuthError> {
self.sip_header_all(SipHeader::ProxyAuthenticate)
.into_iter()
.map(|s| s.parse::<SipAuthValue>())
.collect()
}
fn warning(&self) -> Result<Option<SipWarning>, SipWarningError> {
match self.sip_header(SipHeader::Warning) {
Some(s) => SipWarning::parse(s).map(Some),
None => Ok(None),
}
}
fn security_client(&self) -> Result<Option<SipSecurity>, SipSecurityError> {
match self.sip_header(SipHeader::SecurityClient) {
Some(s) => SipSecurity::parse(s).map(Some),
None => Ok(None),
}
}
fn security_server(&self) -> Result<Option<SipSecurity>, SipSecurityError> {
match self.sip_header(SipHeader::SecurityServer) {
Some(s) => SipSecurity::parse(s).map(Some),
None => Ok(None),
}
}
fn security_verify(&self) -> Result<Option<SipSecurity>, SipSecurityError> {
match self.sip_header(SipHeader::SecurityVerify) {
Some(s) => SipSecurity::parse(s).map(Some),
None => Ok(None),
}
}
fn accept(&self) -> Result<Option<SipAccept>, SipAcceptError> {
match self.sip_header(SipHeader::Accept) {
Some(s) => SipAccept::parse(s).map(Some),
None => Ok(None),
}
}
fn accept_encoding(&self) -> Result<Option<SipAcceptEncoding>, SipAcceptEncodingError> {
match self.sip_header(SipHeader::AcceptEncoding) {
Some(s) => SipAcceptEncoding::parse(s).map(Some),
None => Ok(None),
}
}
fn accept_language(&self) -> Result<Option<SipAcceptLanguage>, SipAcceptLanguageError> {
match self.sip_header(SipHeader::AcceptLanguage) {
Some(s) => SipAcceptLanguage::parse(s).map(Some),
None => Ok(None),
}
}
#[cfg(feature = "draft")]
fn diversion(&self) -> Result<Vec<SipHeaderAddr>, ParseSipHeaderAddrError> {
parse_addr_list(self.sip_header_all(SipHeader::Diversion))
}
#[cfg(feature = "draft")]
fn remote_party_id(&self) -> Result<Vec<SipHeaderAddr>, ParseSipHeaderAddrError> {
parse_addr_list(self.sip_header_all(SipHeader::RemotePartyId))
}
}
fn parse_addr_list(raw: Vec<&str>) -> Result<Vec<SipHeaderAddr>, ParseSipHeaderAddrError> {
if raw.is_empty() {
return Ok(Vec::new());
}
raw.into_iter()
.flat_map(|s| crate::split_comma_entries(s))
.map(|s| {
s.trim()
.parse::<SipHeaderAddr>()
})
.collect()
}
fn split_trim(raw: Option<&str>) -> Vec<&str> {
match raw {
Some(s) => crate::split_comma_entries(s)
.into_iter()
.map(str::trim)
.collect(),
None => Vec::new(),
}
}
impl SipHeaderLookup for std::collections::HashMap<String, String> {
fn sip_header_str(&self, name: &str) -> Option<&str> {
self.get(name)
.map(|s| s.as_str())
}
}
impl SipHeaderLookup for std::collections::HashMap<String, Vec<String>> {
fn sip_header_str(&self, name: &str) -> Option<&str> {
self.get(name)
.and_then(|v| v.first())
.map(|s| s.as_str())
}
fn sip_header_all_str(&self, name: &str) -> Vec<&str> {
self.get(name)
.map(|v| {
v.iter()
.map(|s| s.as_str())
.collect()
})
.unwrap_or_default()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
#[test]
fn display_round_trip() {
assert_eq!(SipHeader::CallInfo.to_string(), "Call-Info");
assert_eq!(SipHeader::HistoryInfo.to_string(), "History-Info");
assert_eq!(
SipHeader::PAssertedIdentity.to_string(),
"P-Asserted-Identity"
);
}
#[test]
fn as_ref_str() {
let h: &str = SipHeader::CallInfo.as_ref();
assert_eq!(h, "Call-Info");
}
#[test]
fn from_str_case_insensitive() {
assert_eq!("call-info".parse::<SipHeader>(), Ok(SipHeader::CallInfo));
assert_eq!("CALL-INFO".parse::<SipHeader>(), Ok(SipHeader::CallInfo));
assert_eq!(
"history-info".parse::<SipHeader>(),
Ok(SipHeader::HistoryInfo)
);
assert_eq!(
"p-asserted-identity".parse::<SipHeader>(),
Ok(SipHeader::PAssertedIdentity)
);
assert_eq!(
"P-ASSERTED-IDENTITY".parse::<SipHeader>(),
Ok(SipHeader::PAssertedIdentity)
);
}
#[test]
fn from_str_unknown() {
assert!("X-Custom"
.parse::<SipHeader>()
.is_err());
}
#[test]
fn from_str_round_trip_all() {
let variants = [
SipHeader::Accept,
SipHeader::AcceptContact,
SipHeader::AcceptEncoding,
SipHeader::AcceptLanguage,
SipHeader::AcceptResourcePriority,
SipHeader::AdditionalIdentity,
SipHeader::AlertInfo,
SipHeader::AlertmsgError,
SipHeader::Allow,
SipHeader::AllowEvents,
SipHeader::AnswerMode,
SipHeader::AttestationInfo,
SipHeader::AuthenticationInfo,
SipHeader::Authorization,
SipHeader::CallId,
SipHeader::CallInfo,
SipHeader::CellularNetworkInfo,
SipHeader::Contact,
SipHeader::ContentDisposition,
SipHeader::ContentEncoding,
SipHeader::ContentId,
SipHeader::ContentLanguage,
SipHeader::ContentLength,
SipHeader::ContentType,
SipHeader::Cseq,
SipHeader::Date,
SipHeader::DcInfo,
SipHeader::Encryption,
SipHeader::ErrorInfo,
SipHeader::Event,
SipHeader::Expires,
SipHeader::FeatureCaps,
SipHeader::FlowTimer,
SipHeader::From,
SipHeader::Geolocation,
SipHeader::GeolocationError,
SipHeader::GeolocationRouting,
SipHeader::Hide,
SipHeader::HistoryInfo,
SipHeader::Identity,
SipHeader::IdentityInfo,
SipHeader::InfoPackage,
SipHeader::InReplyTo,
SipHeader::Join,
SipHeader::MaxBreadth,
SipHeader::MaxForwards,
SipHeader::MimeVersion,
SipHeader::MinExpires,
SipHeader::MinSe,
SipHeader::Organization,
SipHeader::OriginationId,
SipHeader::PAccessNetworkInfo,
SipHeader::PAnswerState,
SipHeader::PAssertedIdentity,
SipHeader::PAssertedService,
SipHeader::PAssociatedUri,
SipHeader::PCalledPartyId,
SipHeader::PChargeInfo,
SipHeader::PChargingFunctionAddresses,
SipHeader::PChargingVector,
SipHeader::PDcsTracePartyId,
SipHeader::PDcsOsps,
SipHeader::PDcsBillingInfo,
SipHeader::PDcsLaes,
SipHeader::PDcsRedirect,
SipHeader::PEarlyMedia,
SipHeader::PMediaAuthorization,
SipHeader::PPreferredIdentity,
SipHeader::PPreferredService,
SipHeader::PPrivateNetworkIndication,
SipHeader::PProfileKey,
SipHeader::PRefusedUriList,
SipHeader::PServedUser,
SipHeader::PUserDatabase,
SipHeader::PVisitedNetworkId,
SipHeader::Path,
SipHeader::PermissionMissing,
SipHeader::PolicyContact,
SipHeader::PolicyId,
SipHeader::Priority,
SipHeader::PriorityShare,
SipHeader::PriorityVerstat,
SipHeader::PrivAnswerMode,
SipHeader::Privacy,
SipHeader::ProxyAuthenticate,
SipHeader::ProxyAuthorization,
SipHeader::ProxyRequire,
SipHeader::Rack,
SipHeader::Reason,
SipHeader::ReasonPhrase,
SipHeader::RecordRoute,
SipHeader::RecvInfo,
SipHeader::ReferEventsAt,
SipHeader::ReferSub,
SipHeader::ReferTo,
SipHeader::ReferredBy,
SipHeader::RejectContact,
SipHeader::RelayedCharge,
SipHeader::Replaces,
SipHeader::ReplyTo,
SipHeader::RequestDisposition,
SipHeader::Require,
SipHeader::ResourcePriority,
SipHeader::ResourceShare,
SipHeader::ResponseKey,
SipHeader::ResponseSource,
SipHeader::RestorationInfo,
SipHeader::RetryAfter,
SipHeader::Route,
SipHeader::Rseq,
SipHeader::SecurityClient,
SipHeader::SecurityServer,
SipHeader::SecurityVerify,
SipHeader::Server,
SipHeader::ServiceInteractInfo,
SipHeader::ServiceRoute,
SipHeader::SessionExpires,
SipHeader::SessionId,
SipHeader::SipEtag,
SipHeader::SipIfMatch,
SipHeader::Subject,
SipHeader::SubscriptionState,
SipHeader::Supported,
SipHeader::SuppressIfMatch,
SipHeader::TargetDialog,
SipHeader::Timestamp,
SipHeader::To,
SipHeader::TriggerConsent,
SipHeader::Unsupported,
SipHeader::UserAgent,
SipHeader::UserToUser,
SipHeader::Via,
SipHeader::Warning,
SipHeader::WwwAuthenticate,
];
for v in variants {
let wire = v.to_string();
let parsed: SipHeader = wire
.parse()
.unwrap();
assert_eq!(parsed, v, "round-trip failed for {wire}");
}
}
fn headers_with(pairs: &[(&str, &str)]) -> HashMap<String, String> {
pairs
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect()
}
#[test]
fn sip_header_by_enum() {
let h = headers_with(&[("Call-Info", "<urn:x>;purpose=icon")]);
assert_eq!(
h.sip_header(SipHeader::CallInfo),
Some("<urn:x>;purpose=icon")
);
}
#[test]
fn call_info_raw_lookup() {
let h = headers_with(&[(
"Call-Info",
"<urn:emergency:uid:callid:test:bcf.example.com>;purpose=emergency-CallId",
)]);
assert_eq!(
h.sip_header(SipHeader::CallInfo),
Some("<urn:emergency:uid:callid:test:bcf.example.com>;purpose=emergency-CallId")
);
}
#[test]
fn call_info_typed() {
let h = headers_with(&[(
"Call-Info",
"<urn:emergency:uid:callid:test:bcf.example.com>;purpose=emergency-CallId",
)]);
let ci = h
.call_info()
.unwrap()
.unwrap();
assert_eq!(ci.len(), 1);
assert_eq!(ci.entries()[0].purpose(), Some("emergency-CallId"));
}
#[test]
fn call_info_absent() {
let h = headers_with(&[]);
assert_eq!(
h.call_info()
.unwrap(),
None
);
}
#[test]
fn p_asserted_identity_typed() {
let h = headers_with(&[(
"P-Asserted-Identity",
r#""EXAMPLE CO" <sip:+15551234567@198.51.100.1>"#,
)]);
let pais = h
.p_asserted_identity()
.unwrap();
assert_eq!(pais.len(), 1);
assert_eq!(pais[0].display_name(), Some("EXAMPLE CO"));
}
#[test]
fn p_asserted_identity_multi_value() {
let h = headers_with(&[(
"P-Asserted-Identity",
r#""EXAMPLE CO" <sip:+15551234567@198.51.100.1>, <tel:+15551234567>"#,
)]);
let pais = h
.p_asserted_identity()
.unwrap();
assert_eq!(pais.len(), 2);
assert_eq!(pais[0].display_name(), Some("EXAMPLE CO"));
assert!(pais[1]
.uri()
.to_string()
.contains("+15551234567"));
}
#[test]
fn p_asserted_identity_absent() {
let h = headers_with(&[]);
assert!(h
.p_asserted_identity()
.unwrap()
.is_empty());
}
#[test]
fn history_info_raw_lookup() {
let h = headers_with(&[(
"History-Info",
"<sip:alice@esrp.example.com>;index=1,<sip:sos@psap.example.com>;index=1.1",
)]);
assert!(h
.sip_header(SipHeader::HistoryInfo)
.unwrap()
.contains("esrp.example.com"));
}
#[test]
fn history_info_typed() {
let h = headers_with(&[(
"History-Info",
"<sip:alice@esrp.example.com>;index=1,<sip:sos@psap.example.com>;index=1.1",
)]);
let hi = h
.history_info()
.unwrap()
.unwrap();
assert_eq!(hi.len(), 2);
assert_eq!(hi.entries()[0].index(), Some("1"));
assert_eq!(hi.entries()[1].index(), Some("1.1"));
}
#[test]
fn history_info_absent() {
let h = headers_with(&[]);
assert_eq!(
h.history_info()
.unwrap(),
None
);
}
#[test]
fn sip_header_all_str_default() {
let h = headers_with(&[("Via", "SIP/2.0/UDP host1")]);
let all = h.sip_header_all(SipHeader::Via);
assert_eq!(all.len(), 1);
assert_eq!(all[0], "SIP/2.0/UDP host1");
}
#[test]
fn sip_header_all_str_absent() {
let h = headers_with(&[]);
assert!(h
.sip_header_all(SipHeader::Via)
.is_empty());
}
#[test]
fn hashmap_vec_impl() {
let mut h: HashMap<String, Vec<String>> = HashMap::new();
h.insert(
"Via".into(),
vec!["SIP/2.0/UDP host1".into(), "SIP/2.0/UDP host2".into()],
);
assert_eq!(h.sip_header_str("Via"), Some("SIP/2.0/UDP host1"));
let all = h.sip_header_all_str("Via");
assert_eq!(all.len(), 2);
assert_eq!(all[0], "SIP/2.0/UDP host1");
assert_eq!(all[1], "SIP/2.0/UDP host2");
}
#[test]
fn extract_from_sip_message() {
let msg = concat!(
"INVITE sip:bob@host SIP/2.0\r\n",
"Call-Info: <urn:emergency:uid:callid:abc>;purpose=emergency-CallId\r\n",
"History-Info: <sip:esrp@example.com>;index=1\r\n",
"P-Asserted-Identity: \"Corp\" <sip:+15551234567@198.51.100.1>\r\n",
"\r\n",
);
let ci = SipHeader::CallInfo.extract_from(msg);
assert_eq!(ci.len(), 1);
assert_eq!(
ci[0],
"<urn:emergency:uid:callid:abc>;purpose=emergency-CallId"
);
let hi = SipHeader::HistoryInfo.extract_from(msg);
assert_eq!(hi.len(), 1);
assert_eq!(hi[0], "<sip:esrp@example.com>;index=1");
let pai = SipHeader::PAssertedIdentity.extract_from(msg);
assert_eq!(pai.len(), 1);
assert_eq!(pai[0], "\"Corp\" <sip:+15551234567@198.51.100.1>");
}
#[test]
fn extract_from_missing() {
let msg = concat!(
"INVITE sip:bob@host SIP/2.0\r\n",
"From: Alice <sip:alice@host>\r\n",
"\r\n",
);
assert!(SipHeader::CallInfo
.extract_from(msg)
.is_empty());
assert!(SipHeader::PAssertedIdentity
.extract_from(msg)
.is_empty());
}
#[test]
fn missing_headers_return_none() {
let h = headers_with(&[]);
assert_eq!(h.sip_header(SipHeader::CallInfo), None);
assert_eq!(
h.call_info()
.unwrap(),
None
);
assert_eq!(h.sip_header(SipHeader::HistoryInfo), None);
assert_eq!(
h.history_info()
.unwrap(),
None
);
assert_eq!(h.sip_header(SipHeader::PAssertedIdentity), None);
assert!(h
.p_asserted_identity()
.unwrap()
.is_empty());
}
#[test]
fn route_accessor() {
let h = headers_with(&[(
"Route",
"<sip:proxy1.example.com;lr>, <sip:proxy2.example.com;lr>",
)]);
let routes = h
.route()
.unwrap();
assert_eq!(routes.len(), 2);
assert!(routes[0]
.uri()
.to_string()
.contains("proxy1"));
assert!(routes[1]
.uri()
.to_string()
.contains("proxy2"));
}
#[test]
fn route_absent() {
let h = headers_with(&[]);
assert!(h
.route()
.unwrap()
.is_empty());
}
#[test]
fn record_route_accessor() {
let h = headers_with(&[("Record-Route", "<sip:ss1.example.com;lr>")]);
let rr = h
.record_route()
.unwrap();
assert_eq!(rr.len(), 1);
}
#[test]
fn allow_accessor() {
let h = headers_with(&[("Allow", "INVITE, ACK, OPTIONS, BYE")]);
let methods = h.allow();
assert_eq!(methods, vec!["INVITE", "ACK", "OPTIONS", "BYE"]);
}
#[test]
fn allow_absent() {
let h = headers_with(&[]);
assert!(h
.allow()
.is_empty());
}
#[test]
fn supported_accessor() {
let h = headers_with(&[("Supported", "100rel, timer")]);
let opts = h.supported();
assert_eq!(opts, vec!["100rel", "timer"]);
}
#[test]
fn require_header_accessor() {
let h = headers_with(&[("Require", "100rel")]);
assert_eq!(h.require_header(), vec!["100rel"]);
}
#[test]
fn alert_info_accessor() {
let h = headers_with(&[("Alert-Info", "<http://www.example.com/sounds/moo.wav>")]);
let ai = h
.alert_info()
.unwrap()
.unwrap();
assert_eq!(ai.len(), 1);
assert!(ai.entries()[0]
.data
.contains("moo.wav"));
}
#[test]
fn error_info_accessor() {
let h = headers_with(&[("Error-Info", "<sip:not-in-service@example.com>")]);
let ei = h
.error_info()
.unwrap()
.unwrap();
assert_eq!(ei.len(), 1);
}
#[test]
fn p_preferred_identity_accessor() {
let h = headers_with(&[(
"P-Preferred-Identity",
r#""User" <sip:+15551234567@198.51.100.1>"#,
)]);
let ppi = h
.p_preferred_identity()
.unwrap();
assert_eq!(ppi.len(), 1);
assert_eq!(ppi[0].display_name(), Some("User"));
}
#[test]
fn content_encoding_accessor() {
let h = headers_with(&[("Content-Encoding", "gzip")]);
assert_eq!(h.content_encoding(), vec!["gzip"]);
}
#[test]
fn contact_accessor() {
let h = headers_with(&[("Contact", "<sip:alice@198.51.100.1>")]);
let contacts = h
.contact()
.unwrap();
assert_eq!(contacts.len(), 1);
assert!(matches!(&contacts[0], ContactValue::Addr(_)));
}
#[test]
fn contact_wildcard() {
let h = headers_with(&[("Contact", "*")]);
let contacts = h
.contact()
.unwrap();
assert_eq!(contacts.len(), 1);
assert!(matches!(contacts[0], ContactValue::Wildcard));
}
#[test]
fn contact_absent() {
let h = headers_with(&[]);
assert!(h
.contact()
.unwrap()
.is_empty());
}
#[test]
fn in_reply_to_accessor() {
let h = headers_with(&[("In-Reply-To", "call1@example.com, call2@example.com")]);
let calls = h.in_reply_to();
assert_eq!(calls.len(), 2);
assert_eq!(calls[0], "call1@example.com");
}
#[test]
fn via_accessor() {
let h = headers_with(&[("Via", "SIP/2.0/UDP 198.51.100.1:5060;branch=z9hG4bK776")]);
let via = h
.via()
.unwrap()
.unwrap();
assert_eq!(via.len(), 1);
assert_eq!(via.entries()[0].transport(), "UDP");
assert_eq!(via.entries()[0].host(), "198.51.100.1");
}
#[test]
fn authorization_accessor() {
let h = headers_with(&[(
"Authorization",
"Digest username=\"alice\", realm=\"example.com\", nonce=\"abc123\"",
)]);
let auth = h
.authorization()
.unwrap();
assert_eq!(auth.len(), 1);
assert_eq!(auth[0].scheme(), "Digest");
assert_eq!(auth[0].username(), Some("alice"));
assert_eq!(auth[0].realm(), Some("example.com"));
}
#[test]
fn www_authenticate_accessor() {
let h = headers_with(&[(
"WWW-Authenticate",
"Digest realm=\"example.com\", nonce=\"xyz789\"",
)]);
let challenges = h
.www_authenticate()
.unwrap();
assert_eq!(challenges.len(), 1);
assert_eq!(challenges[0].realm(), Some("example.com"));
}
#[test]
fn warning_accessor() {
let h = headers_with(&[(
"Warning",
"301 198.51.100.1 \"Incompatible network protocol\"",
)]);
let w = h
.warning()
.unwrap()
.unwrap();
assert_eq!(w.len(), 1);
assert_eq!(w.entries()[0].code(), 301);
}
#[test]
fn security_client_accessor() {
let h = headers_with(&[("Security-Client", "tls;q=0.2, digest;d-qop=auth;q=0.1")]);
let sec = h
.security_client()
.unwrap()
.unwrap();
assert_eq!(sec.len(), 2);
assert_eq!(sec.entries()[0].mechanism(), "tls");
}
#[test]
fn accept_accessor() {
let h = headers_with(&[("Accept", "application/sdp, application/pidf+xml;q=0.5")]);
let accept = h
.accept()
.unwrap()
.unwrap();
assert_eq!(accept.len(), 2);
assert_eq!(accept.entries()[0].media_range(), "application/sdp");
}
#[test]
fn accept_encoding_accessor() {
let h = headers_with(&[("Accept-Encoding", "gzip;q=1.0, identity;q=0.5")]);
let ae = h
.accept_encoding()
.unwrap()
.unwrap();
assert_eq!(ae.len(), 2);
assert_eq!(ae.entries()[0].encoding(), "gzip");
}
#[test]
fn accept_language_accessor() {
let h = headers_with(&[("Accept-Language", "en;q=0.9, fr;q=0.8")]);
let al = h
.accept_language()
.unwrap()
.unwrap();
assert_eq!(al.len(), 2);
assert_eq!(al.entries()[0].language(), "en");
}
}
#[cfg(test)]
mod compact_form_tests {
use super::*;
#[test]
fn from_compact_known() {
assert_eq!(SipHeader::from_compact(b'f'), Some(SipHeader::From));
assert_eq!(SipHeader::from_compact(b'F'), Some(SipHeader::From));
assert_eq!(SipHeader::from_compact(b'v'), Some(SipHeader::Via));
assert_eq!(SipHeader::from_compact(b'i'), Some(SipHeader::CallId));
assert_eq!(SipHeader::from_compact(b'm'), Some(SipHeader::Contact));
assert_eq!(SipHeader::from_compact(b't'), Some(SipHeader::To));
assert_eq!(SipHeader::from_compact(b'c'), Some(SipHeader::ContentType));
}
#[test]
fn from_compact_unknown() {
assert_eq!(SipHeader::from_compact(b'z'), None);
assert_eq!(SipHeader::from_compact(b'g'), None);
}
#[test]
fn compact_form_roundtrip() {
assert_eq!(SipHeader::From.compact_form(), Some('f'));
assert_eq!(SipHeader::Via.compact_form(), Some('v'));
assert_eq!(SipHeader::CallId.compact_form(), Some('i'));
assert_eq!(SipHeader::Contact.compact_form(), Some('m'));
}
#[test]
fn compact_form_absent() {
assert_eq!(SipHeader::HistoryInfo.compact_form(), None);
assert_eq!(SipHeader::PAssertedIdentity.compact_form(), None);
}
#[test]
fn parse_name_compact() {
assert_eq!(SipHeader::parse_name("f"), Ok(SipHeader::From));
assert_eq!(SipHeader::parse_name("F"), Ok(SipHeader::From));
assert_eq!(SipHeader::parse_name("v"), Ok(SipHeader::Via));
}
#[test]
fn parse_name_full() {
assert_eq!(SipHeader::parse_name("From"), Ok(SipHeader::From));
assert_eq!(SipHeader::parse_name("Via"), Ok(SipHeader::Via));
}
#[test]
fn parse_name_unknown() {
assert!(SipHeader::parse_name("X-Custom").is_err());
}
#[test]
fn all_compact_forms_resolve() {
let expected = [
('a', SipHeader::AcceptContact),
('b', SipHeader::ReferredBy),
('c', SipHeader::ContentType),
('d', SipHeader::RequestDisposition),
('e', SipHeader::ContentEncoding),
('f', SipHeader::From),
('i', SipHeader::CallId),
('j', SipHeader::RejectContact),
('k', SipHeader::Supported),
('l', SipHeader::ContentLength),
('m', SipHeader::Contact),
('n', SipHeader::IdentityInfo),
('o', SipHeader::Event),
('r', SipHeader::ReferTo),
('s', SipHeader::Subject),
('t', SipHeader::To),
('u', SipHeader::AllowEvents),
('v', SipHeader::Via),
('x', SipHeader::SessionExpires),
('y', SipHeader::Identity),
];
for (ch, header) in expected {
assert_eq!(
SipHeader::from_compact(ch as u8),
Some(header),
"compact form '{ch}' failed"
);
assert_eq!(
header.compact_form(),
Some(ch),
"compact_form() for {} failed",
header
);
}
}
}
#[cfg(test)]
mod multi_valued_tests {
use super::*;
#[test]
fn rfc3261_multi_valued_headers() {
assert!(SipHeader::Via.is_multi_valued());
assert!(SipHeader::Route.is_multi_valued());
assert!(SipHeader::RecordRoute.is_multi_valued());
assert!(SipHeader::Contact.is_multi_valued());
assert!(SipHeader::Allow.is_multi_valued());
assert!(SipHeader::Supported.is_multi_valued());
assert!(SipHeader::Require.is_multi_valued());
assert!(SipHeader::ProxyRequire.is_multi_valued());
assert!(SipHeader::Unsupported.is_multi_valued());
assert!(SipHeader::Authorization.is_multi_valued());
assert!(SipHeader::ProxyAuthorization.is_multi_valued());
assert!(SipHeader::WwwAuthenticate.is_multi_valued());
assert!(SipHeader::ProxyAuthenticate.is_multi_valued());
assert!(SipHeader::Warning.is_multi_valued());
assert!(SipHeader::ErrorInfo.is_multi_valued());
assert!(SipHeader::CallInfo.is_multi_valued());
assert!(SipHeader::AlertInfo.is_multi_valued());
assert!(SipHeader::Accept.is_multi_valued());
assert!(SipHeader::AcceptEncoding.is_multi_valued());
assert!(SipHeader::AcceptLanguage.is_multi_valued());
assert!(SipHeader::ContentEncoding.is_multi_valued());
assert!(SipHeader::ContentLanguage.is_multi_valued());
assert!(SipHeader::InReplyTo.is_multi_valued());
}
#[test]
fn extension_multi_valued_headers() {
assert!(SipHeader::PAssertedIdentity.is_multi_valued());
assert!(SipHeader::PPreferredIdentity.is_multi_valued());
assert!(SipHeader::AllowEvents.is_multi_valued());
assert!(SipHeader::SecurityClient.is_multi_valued());
assert!(SipHeader::SecurityServer.is_multi_valued());
assert!(SipHeader::SecurityVerify.is_multi_valued());
assert!(SipHeader::Path.is_multi_valued());
assert!(SipHeader::ServiceRoute.is_multi_valued());
assert!(SipHeader::HistoryInfo.is_multi_valued());
}
#[test]
fn single_valued_headers() {
assert!(!SipHeader::From.is_multi_valued());
assert!(!SipHeader::To.is_multi_valued());
assert!(!SipHeader::CallId.is_multi_valued());
assert!(!SipHeader::Cseq.is_multi_valued());
assert!(!SipHeader::MaxForwards.is_multi_valued());
assert!(!SipHeader::ContentType.is_multi_valued());
assert!(!SipHeader::ContentLength.is_multi_valued());
assert!(!SipHeader::Expires.is_multi_valued());
assert!(!SipHeader::Date.is_multi_valued());
assert!(!SipHeader::Subject.is_multi_valued());
assert!(!SipHeader::ReplyTo.is_multi_valued());
assert!(!SipHeader::Server.is_multi_valued());
assert!(!SipHeader::UserAgent.is_multi_valued());
}
#[test]
#[cfg(feature = "draft")]
fn draft_multi_valued_headers() {
assert!(SipHeader::Diversion.is_multi_valued());
assert!(SipHeader::RemotePartyId.is_multi_valued());
}
#[test]
#[cfg(feature = "draft")]
fn draft_parse_roundtrip() {
let d: SipHeader = "Diversion"
.parse()
.unwrap();
assert_eq!(d, SipHeader::Diversion);
assert_eq!(d.to_string(), "Diversion");
let r: SipHeader = "remote-party-id"
.parse()
.unwrap();
assert_eq!(r, SipHeader::RemotePartyId);
assert_eq!(r.to_string(), "Remote-Party-ID");
}
}
#[cfg(test)]
mod special_case_tests {
use super::*;
#[test]
fn cseq_variants() {
assert_eq!("CSeq".parse::<SipHeader>(), Ok(SipHeader::Cseq));
assert_eq!("cseq".parse::<SipHeader>(), Ok(SipHeader::Cseq));
assert_eq!("CSEQ".parse::<SipHeader>(), Ok(SipHeader::Cseq));
assert_eq!(SipHeader::Cseq.to_string(), "CSeq");
}
#[test]
fn www_authenticate_variants() {
assert_eq!(
"WWW-Authenticate".parse::<SipHeader>(),
Ok(SipHeader::WwwAuthenticate)
);
assert_eq!(
"www-authenticate".parse::<SipHeader>(),
Ok(SipHeader::WwwAuthenticate)
);
assert_eq!(SipHeader::WwwAuthenticate.to_string(), "WWW-Authenticate");
}
#[test]
fn rack_rseq_variants() {
assert_eq!("RAck".parse::<SipHeader>(), Ok(SipHeader::Rack));
assert_eq!("rack".parse::<SipHeader>(), Ok(SipHeader::Rack));
assert_eq!(SipHeader::Rack.to_string(), "RAck");
assert_eq!("RSeq".parse::<SipHeader>(), Ok(SipHeader::Rseq));
assert_eq!("rseq".parse::<SipHeader>(), Ok(SipHeader::Rseq));
assert_eq!(SipHeader::Rseq.to_string(), "RSeq");
}
#[test]
fn user_to_user_variants() {
assert_eq!(
"User-to-User".parse::<SipHeader>(),
Ok(SipHeader::UserToUser)
);
assert_eq!(
"user-to-user".parse::<SipHeader>(),
Ok(SipHeader::UserToUser)
);
assert_eq!(SipHeader::UserToUser.to_string(), "User-to-User");
}
#[test]
fn p_header_variants() {
assert_eq!(
"P-DCS-Trace-Party-ID".parse::<SipHeader>(),
Ok(SipHeader::PDcsTracePartyId)
);
assert_eq!(
"p-dcs-trace-party-id".parse::<SipHeader>(),
Ok(SipHeader::PDcsTracePartyId)
);
assert_eq!(
SipHeader::PDcsTracePartyId.to_string(),
"P-DCS-Trace-Party-ID"
);
}
}