Skip to main content

freeswitch_types/variables/
sip_invite.rs

1//! Raw SIP INVITE header variables (`sip_i_*`).
2//!
3//! These are set by `sofia_parse_all_invite_headers()` when
4//! `parse-all-invite-headers` is enabled on the sofia profile.
5//! Each variable contains the verbatim serialized SIP header value.
6//!
7//! Some headers may repeat in a SIP message. Those are stored using
8//! FreeSWITCH's ARRAY format (`ARRAY::value1|:value2`). Parse them
9//! with [`EslArray`](super::EslArray).
10
11/// Error returned when parsing an unrecognized SIP invite header variable name.
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct ParseSipInviteHeaderError(pub String);
14
15impl std::fmt::Display for ParseSipInviteHeaderError {
16    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17        write!(f, "unknown SIP invite header variable: {}", self.0)
18    }
19}
20
21impl std::error::Error for ParseSipInviteHeaderError {}
22
23sip_header::define_header_enum! {
24    error_type: ParseSipInviteHeaderError,
25    /// Raw SIP INVITE headers preserved verbatim as channel variables.
26    ///
27    /// Set by `sofia_parse_all_invite_headers()` when the sofia profile has
28    /// `parse-all-invite-headers` enabled. Access via
29    /// [`HeaderLookup::variable()`](crate::HeaderLookup::variable).
30    ///
31    /// Variants marked "ARRAY" may contain multiple values in
32    /// `ARRAY::val1|:val2` format when the SIP message has repeated headers.
33    /// Parse with [`EslArray`](super::EslArray). Variants marked "single"
34    /// contain one serialized header value.
35    ///
36    /// For headers not covered by this enum (dynamic unknown headers stored
37    /// as `sip_i_<lowercased_name>`), use
38    /// [`variable_str()`](crate::HeaderLookup::variable_str).
39    pub enum SipInviteHeader {
40        // --- Single-value headers ---
41
42        /// SIP From header.
43        From => "sip_i_from",
44        /// SIP To header.
45        To => "sip_i_to",
46        /// SIP Call-ID header.
47        CallId => "sip_i_call_id",
48        /// SIP CSeq header.
49        Cseq => "sip_i_cseq",
50        /// SIP Identity header (RFC 8224).
51        Identity => "sip_i_identity",
52        /// SIP Route header.
53        Route => "sip_i_route",
54        /// SIP Max-Forwards header.
55        MaxForwards => "sip_i_max_forwards",
56        /// SIP Proxy-Require header.
57        ProxyRequire => "sip_i_proxy_require",
58        /// SIP Contact header.
59        Contact => "sip_i_contact",
60        /// SIP User-Agent header.
61        UserAgent => "sip_i_user_agent",
62        /// SIP Subject header.
63        Subject => "sip_i_subject",
64        /// SIP Priority header.
65        Priority => "sip_i_priority",
66        /// SIP Organization header.
67        Organization => "sip_i_organization",
68        /// SIP In-Reply-To header.
69        InReplyTo => "sip_i_in_reply_to",
70        /// SIP Accept-Encoding header.
71        AcceptEncoding => "sip_i_accept_encoding",
72        /// SIP Accept-Language header.
73        AcceptLanguage => "sip_i_accept_language",
74        /// SIP Allow header.
75        Allow => "sip_i_allow",
76        /// SIP Require header.
77        Require => "sip_i_require",
78        /// SIP Supported header.
79        Supported => "sip_i_supported",
80        /// SIP Date header.
81        Date => "sip_i_date",
82        /// SIP Timestamp header.
83        Timestamp => "sip_i_timestamp",
84        /// SIP Expires header.
85        Expires => "sip_i_expires",
86        /// SIP Min-Expires header.
87        MinExpires => "sip_i_min_expires",
88        /// SIP Session-Expires header.
89        SessionExpires => "sip_i_session_expires",
90        /// SIP Min-SE header.
91        MinSe => "sip_i_min_se",
92        /// SIP Privacy header.
93        Privacy => "sip_i_privacy",
94        /// SIP MIME-Version header.
95        MimeVersion => "sip_i_mime_version",
96        /// SIP Content-Type header.
97        ContentType => "sip_i_content_type",
98        /// SIP Content-Encoding header.
99        ContentEncoding => "sip_i_content_encoding",
100        /// SIP Content-Language header.
101        ContentLanguage => "sip_i_content_language",
102        /// SIP Content-Disposition header.
103        ContentDisposition => "sip_i_content_disposition",
104        /// SIP Content-Length header.
105        ContentLength => "sip_i_content_length",
106
107        // --- ARRAY headers (may contain multiple values) ---
108
109        /// SIP Via headers. ARRAY when multiple hops present.
110        Via => "sip_i_via",
111        /// SIP Record-Route headers. ARRAY when multiple proxies present.
112        RecordRoute => "sip_i_record_route",
113        /// SIP Proxy-Authorization headers. ARRAY when multiple credentials present.
114        ProxyAuthorization => "sip_i_proxy_authorization",
115        /// SIP Call-Info headers. ARRAY when multiple info URIs present.
116        CallInfo => "sip_i_call_info",
117        /// SIP Accept headers. ARRAY when multiple media types present.
118        Accept => "sip_i_accept",
119        /// SIP Authorization headers. ARRAY when multiple credentials present.
120        Authorization => "sip_i_authorization",
121        /// SIP Alert-Info headers. ARRAY when multiple alert URIs present.
122        AlertInfo => "sip_i_alert_info",
123        /// SIP P-Asserted-Identity headers. ARRAY when multiple identities present (RFC 3325).
124        PAssertedIdentity => "sip_i_p_asserted_identity",
125        /// SIP P-Preferred-Identity headers. ARRAY when multiple identities present.
126        PPreferredIdentity => "sip_i_p_preferred_identity",
127        /// SIP Remote-Party-ID headers. ARRAY when multiple identities present.
128        RemotePartyId => "sip_i_remote_party_id",
129        /// SIP Reply-To headers. ARRAY when multiple reply addresses present.
130        ReplyTo => "sip_i_reply_to",
131    }
132}
133
134impl SipInviteHeader {
135    /// Headers that may contain multiple values in ARRAY format.
136    pub const ARRAY_HEADERS: &[SipInviteHeader] = &[
137        SipInviteHeader::Via,
138        SipInviteHeader::RecordRoute,
139        SipInviteHeader::ProxyAuthorization,
140        SipInviteHeader::CallInfo,
141        SipInviteHeader::Accept,
142        SipInviteHeader::Authorization,
143        SipInviteHeader::AlertInfo,
144        SipInviteHeader::PAssertedIdentity,
145        SipInviteHeader::PPreferredIdentity,
146        SipInviteHeader::RemotePartyId,
147        SipInviteHeader::ReplyTo,
148    ];
149
150    /// Whether this header may contain multiple values in ARRAY format.
151    pub fn is_array_header(&self) -> bool {
152        Self::ARRAY_HEADERS.contains(self)
153    }
154
155    /// Canonical SIP header name (e.g. `"From"`, `"Call-ID"`).
156    pub fn header_name(&self) -> &'static str {
157        match self {
158            Self::From => "From",
159            Self::To => "To",
160            Self::CallId => "Call-ID",
161            Self::Cseq => "CSeq",
162            Self::Identity => "Identity",
163            Self::Route => "Route",
164            Self::MaxForwards => "Max-Forwards",
165            Self::ProxyRequire => "Proxy-Require",
166            Self::Contact => "Contact",
167            Self::UserAgent => "User-Agent",
168            Self::Subject => "Subject",
169            Self::Priority => "Priority",
170            Self::Organization => "Organization",
171            Self::InReplyTo => "In-Reply-To",
172            Self::AcceptEncoding => "Accept-Encoding",
173            Self::AcceptLanguage => "Accept-Language",
174            Self::Allow => "Allow",
175            Self::Require => "Require",
176            Self::Supported => "Supported",
177            Self::Date => "Date",
178            Self::Timestamp => "Timestamp",
179            Self::Expires => "Expires",
180            Self::MinExpires => "Min-Expires",
181            Self::SessionExpires => "Session-Expires",
182            Self::MinSe => "Min-SE",
183            Self::Privacy => "Privacy",
184            Self::MimeVersion => "MIME-Version",
185            Self::ContentType => "Content-Type",
186            Self::ContentEncoding => "Content-Encoding",
187            Self::ContentLanguage => "Content-Language",
188            Self::ContentDisposition => "Content-Disposition",
189            Self::ContentLength => "Content-Length",
190            Self::Via => "Via",
191            Self::RecordRoute => "Record-Route",
192            Self::ProxyAuthorization => "Proxy-Authorization",
193            Self::CallInfo => "Call-Info",
194            Self::Accept => "Accept",
195            Self::Authorization => "Authorization",
196            Self::AlertInfo => "Alert-Info",
197            Self::PAssertedIdentity => "P-Asserted-Identity",
198            Self::PPreferredIdentity => "P-Preferred-Identity",
199            Self::RemotePartyId => "Remote-Party-ID",
200            Self::ReplyTo => "Reply-To",
201        }
202    }
203
204    /// Extract this header's value from a raw SIP message.
205    ///
206    /// Delegates to [`extract_header`](sip_header::extract_header)
207    /// using the canonical name from [`header_name()`](Self::header_name).
208    pub fn extract_from(&self, message: &str) -> Option<String> {
209        sip_header::extract_header(message, self.header_name())
210    }
211}
212
213#[cfg(test)]
214mod tests {
215    use super::*;
216
217    #[test]
218    fn display_round_trip() {
219        assert_eq!(
220            SipInviteHeader::PAssertedIdentity.to_string(),
221            "sip_i_p_asserted_identity"
222        );
223        assert_eq!(SipInviteHeader::From.to_string(), "sip_i_from");
224        assert_eq!(SipInviteHeader::Via.to_string(), "sip_i_via");
225    }
226
227    #[test]
228    fn as_ref_str() {
229        let v: &str = SipInviteHeader::CallId.as_ref();
230        assert_eq!(v, "sip_i_call_id");
231    }
232
233    #[test]
234    fn from_str_case_insensitive() {
235        assert_eq!(
236            "sip_i_p_asserted_identity".parse::<SipInviteHeader>(),
237            Ok(SipInviteHeader::PAssertedIdentity)
238        );
239        assert_eq!(
240            "SIP_I_P_ASSERTED_IDENTITY".parse::<SipInviteHeader>(),
241            Ok(SipInviteHeader::PAssertedIdentity)
242        );
243    }
244
245    #[test]
246    fn from_str_unknown() {
247        assert!("sip_i_nonexistent"
248            .parse::<SipInviteHeader>()
249            .is_err());
250    }
251
252    #[test]
253    fn from_str_round_trip_all() {
254        let variants = [
255            SipInviteHeader::From,
256            SipInviteHeader::To,
257            SipInviteHeader::CallId,
258            SipInviteHeader::Via,
259            SipInviteHeader::RecordRoute,
260            SipInviteHeader::PAssertedIdentity,
261            SipInviteHeader::PPreferredIdentity,
262            SipInviteHeader::RemotePartyId,
263            SipInviteHeader::AlertInfo,
264            SipInviteHeader::Privacy,
265            SipInviteHeader::ContentType,
266        ];
267        for v in variants {
268            let wire = v.to_string();
269            let parsed: SipInviteHeader = wire
270                .parse()
271                .unwrap();
272            assert_eq!(parsed, v, "round-trip failed for {wire}");
273        }
274    }
275
276    #[test]
277    fn header_name_mapping() {
278        assert_eq!(SipInviteHeader::From.header_name(), "From");
279        assert_eq!(SipInviteHeader::CallId.header_name(), "Call-ID");
280        assert_eq!(SipInviteHeader::Cseq.header_name(), "CSeq");
281        assert_eq!(
282            SipInviteHeader::PAssertedIdentity.header_name(),
283            "P-Asserted-Identity"
284        );
285        assert_eq!(SipInviteHeader::MinSe.header_name(), "Min-SE");
286        assert_eq!(SipInviteHeader::MimeVersion.header_name(), "MIME-Version");
287        assert_eq!(SipInviteHeader::UserAgent.header_name(), "User-Agent");
288        assert_eq!(SipInviteHeader::RecordRoute.header_name(), "Record-Route");
289    }
290
291    #[test]
292    fn extract_from_sip_message() {
293        let msg = "INVITE sip:bob@host SIP/2.0\r\n\
294                   From: Alice <sip:alice@host>;tag=abc\r\n\
295                   To: Bob <sip:bob@host>\r\n\
296                   Call-ID: 12345@host\r\n\
297                   \r\n";
298        assert_eq!(
299            SipInviteHeader::From.extract_from(msg),
300            Some("Alice <sip:alice@host>;tag=abc".into())
301        );
302        assert_eq!(
303            SipInviteHeader::CallId.extract_from(msg),
304            Some("12345@host".into())
305        );
306    }
307
308    #[test]
309    fn extract_from_array_header() {
310        let msg = "INVITE sip:bob@host SIP/2.0\r\n\
311                   Via: SIP/2.0/UDP first.example.com\r\n\
312                   Via: SIP/2.0/UDP second.example.com\r\n\
313                   \r\n";
314        assert_eq!(
315            SipInviteHeader::Via.extract_from(msg),
316            Some("SIP/2.0/UDP first.example.com, SIP/2.0/UDP second.example.com".into())
317        );
318    }
319
320    #[test]
321    fn extract_from_missing() {
322        let msg = "INVITE sip:bob@host SIP/2.0\r\n\
323                   From: Alice <sip:alice@host>\r\n\
324                   \r\n";
325        assert_eq!(SipInviteHeader::Identity.extract_from(msg), None);
326    }
327
328    #[test]
329    fn array_headers_classification() {
330        assert!(SipInviteHeader::Via.is_array_header());
331        assert!(SipInviteHeader::PAssertedIdentity.is_array_header());
332        assert!(SipInviteHeader::RecordRoute.is_array_header());
333        assert!(!SipInviteHeader::From.is_array_header());
334        assert!(!SipInviteHeader::CallId.is_array_header());
335        assert!(!SipInviteHeader::ContentType.is_array_header());
336    }
337}