Skip to main content

freeswitch_types/variables/
sofia.rs

1//! Typed mod_sofia / SIP channel variable names.
2
3use sip_header::SipHeader;
4
5sip_header::define_header_enum! {
6    tests_mod: sofia_variable_generated_tests,
7    error_type: ParseSofiaVariableError => "unknown sofia variable",
8    /// mod_sofia / SIP channel variable names (the part after the `variable_` prefix).
9    ///
10    /// Use with [`HeaderLookup::variable()`](crate::HeaderLookup::variable) for type-safe lookups.
11    /// Core FreeSWITCH variables belong in [`ChannelVariable`](super::ChannelVariable).
12    pub enum SofiaVariable {
13        // --- SIP From ---
14        SipFromUser => "sip_from_user",
15        SipFromHost => "sip_from_host",
16        SipFromPort => "sip_from_port",
17        SipFromUri => "sip_from_uri",
18        SipFromDisplay => "sip_from_display",
19        SipFromTag => "sip_from_tag",
20        SipFromComment => "sip_from_comment",
21        SipFromUserStripped => "sip_from_user_stripped",
22        SipFullFrom => "sip_full_from",
23
24        // --- SIP To ---
25        SipToUser => "sip_to_user",
26        SipToHost => "sip_to_host",
27        SipToPort => "sip_to_port",
28        SipToUri => "sip_to_uri",
29        SipToDisplay => "sip_to_display",
30        SipToTag => "sip_to_tag",
31        SipToComment => "sip_to_comment",
32        SipFullTo => "sip_full_to",
33
34        // --- SIP Contact ---
35        SipContactUser => "sip_contact_user",
36        SipContactHost => "sip_contact_host",
37        SipContactPort => "sip_contact_port",
38        SipContactUri => "sip_contact_uri",
39        SipContactParams => "sip_contact_params",
40
41        // --- SIP Request ---
42        SipReqUser => "sip_req_user",
43        SipReqHost => "sip_req_host",
44        SipReqPort => "sip_req_port",
45        SipReqUri => "sip_req_uri",
46
47        // --- SIP Via ---
48        SipViaHost => "sip_via_host",
49        SipViaPort => "sip_via_port",
50        SipViaRport => "sip_via_rport",
51        SipViaProtocol => "sip_via_protocol",
52        SipFullVia => "sip_full_via",
53        SipFullRoute => "sip_full_route",
54
55        // --- SIP Session ---
56        SipCallId => "sip_call_id",
57        SipCseq => "sip_cseq",
58        SipUserAgent => "sip_user_agent",
59        SipSubject => "sip_subject",
60        SipAllow => "sip_allow",
61        SipAcceptLanguage => "sip_accept_language",
62        SipCallInfo => "sip_call_info",
63        SipDateEpochTime => "sip_date_epoch_time",
64
65        // --- SIP Network ---
66        SipReceivedIp => "sip_received_ip",
67        SipReceivedPort => "sip_received_port",
68        SipNetworkIp => "sip_network_ip",
69        SipNetworkPort => "sip_network_port",
70        SipNatDetected => "sip_nat_detected",
71        SipTransport => "sip_transport",
72        SipReplyHost => "sip_reply_host",
73
74        // --- SIP Auth ---
75        SipAuthUsername => "sip_auth_username",
76        SipAuthPassword => "sip_auth_password",
77        SipAuthorized => "sip_authorized",
78        SipAclAuthedBy => "sip_acl_authed_by",
79        SipAclToken => "sip_acl_token",
80        SipChallengeRealm => "sip_challenge_realm",
81
82        // --- SIP Failure / Hangup ---
83        SipInviteFailureStatus => "sip_invite_failure_status",
84        SipInviteFailurePhrase => "sip_invite_failure_phrase",
85        SipHangupDisposition => "sip_hangup_disposition",
86        SipTermStatus => "sip_term_status",
87        SipTermCause => "sip_term_cause",
88        SipReason => "sip_reason",
89
90        // --- SIP Identity / Privacy ---
91        SipPAssertedIdentity => "sip_P-Asserted-Identity",
92        SipPPreferredIdentity => "sip_P-Preferred-Identity",
93        SipPrivacy => "sip_Privacy",
94        SipRemotePartyId => "sip_Remote-Party-ID",
95        SipStirShakenAttest => "sip_stir_shaken_attest",
96        SipVerstat => "sip_verstat",
97        SipVerstatDetailed => "sip_verstat_detailed",
98
99        // --- SIP Invite Details ---
100        SipInviteCallId => "sip_invite_call_id",
101        SipInviteCseq => "sip_invite_cseq",
102        SipInviteFullFrom => "sip_invite_full_from",
103        SipInviteFullTo => "sip_invite_full_to",
104        SipInviteFullVia => "sip_invite_full_via",
105        SipInviteFromUri => "sip_invite_from_uri",
106        SipInviteToUri => "sip_invite_to_uri",
107        SipInviteReqUri => "sip_invite_req_uri",
108        SipInviteRecordRoute => "sip_invite_record_route",
109        SipInviteRouteUri => "sip_invite_route_uri",
110        SipInviteDomain => "sip_invite_domain",
111        SipInviteParams => "sip_invite_params",
112
113        // --- SIP Features ---
114        SipAutoAnswer => "sip_auto_answer",
115        SipAutoSimplify => "sip_auto_simplify",
116        SipEnableSoa => "sip_enable_soa",
117        SipCopyCustomHeaders => "sip_copy_custom_headers",
118        SipCopyMultipart => "sip_copy_multipart",
119        SipLoopedCall => "sip_looped_call",
120
121        // --- SIP Redirect / Transfer ---
122        SipRedirectedTo => "sip_redirected_to",
123        SipRedirectedBy => "sip_redirected_by",
124        SipRedirectDialstring => "sip_redirect_dialstring",
125        SipReferReply => "sip_refer_reply",
126        SipReferStatusCode => "sip_refer_status_code",
127        SipReferredByFull => "sip_referred_by_full",
128        SipReferredByCid => "sip_referred_by_cid",
129        SipReinviteSdp => "sip_reinvite_sdp",
130
131        // --- SIP Gateway ---
132        SipGateway => "sip_gateway",
133        SipGatewayName => "sip_gateway_name",
134        SipUseGateway => "sip_use_gateway",
135        SipDestinationUrl => "sip_destination_url",
136
137        // --- Sofia Profile ---
138        SipProfileName => "sip_profile_name",
139        SofiaProfileName => "sofia_profile_name",
140        SofiaProfileUrl => "sofia_profile_url",
141        SofiaProfileDomainName => "sofia_profile_domain_name",
142
143        // --- RTP / SRTP (set via mod_sofia / switch_core_media) ---
144        RtpSecureMediaConfirmed => "rtp_secure_media_confirmed",
145        Rtp2833SendPayload => "rtp_2833_send_payload",
146        Rtp2833RecvPayload => "rtp_2833_recv_payload",
147        RtpDisableHold => "rtp_disable_hold",
148        RtpJitterBufferPlc => "rtp_jitter_buffer_plc",
149        RtpVideoMaxBandwidthIn => "rtp_video_max_bandwidth_in",
150        RtpVideoMaxBandwidthOut => "rtp_video_max_bandwidth_out",
151
152        // --- SIP Callee / Display ---
153        SipCalleeIdName => "sip_callee_id_name",
154        SipCalleeIdNumber => "sip_callee_id_number",
155        SipCidType => "sip_cid_type",
156
157        // --- SIP RTP Stats ---
158        SipRtpRxstat => "sip_rtp_rxstat",
159        SipRtpTxstat => "sip_rtp_txstat",
160        SipPRtpStat => "sip_p_rtp_stat",
161
162        // --- SIP History ---
163        SipHistoryInfo => "sip_history_info",
164        SipGeolocation => "sip_geolocation",
165
166        // --- SIP Body ---
167        SipMultipart => "sip_multipart",
168
169        // --- SIP Referred-By ---
170        SipReferredByUser => "sip_referred_by_user",
171        SipReferredByUserStripped => "sip_referred_by_user_stripped",
172        SipReferredByHost => "sip_referred_by_host",
173        SipReferredByPort => "sip_referred_by_port",
174        SipReferredByUri => "sip_referred_by_uri",
175        SipReferredByParams => "sip_referred_by_params",
176
177        // --- Parsed sub-fields ---
178        SipFromParams => "sip_from_params",
179        SipToParams => "sip_to_params",
180        SipReqParams => "sip_req_params",
181        SipNameParams => "sip_name_params",
182        SipAcceptLanguageCount => "sip_accept_language_count",
183
184        // --- Auth / directory ---
185        SipAuthRealm => "sip_auth_realm",
186        SipNumberAlias => "sip_number_alias",
187
188        // --- Recovery / outbound Contact ---
189        SipRecoverVia => "sip_recover_via",
190        SipRecoverContact => "sip_recover_contact",
191        SipOutgoingContactUri => "sip_outgoing_contact_uri",
192
193        // --- Local transport / timing ---
194        SipInviteStamp => "sip_invite_stamp",
195        SipReplyPort => "sip_reply_port",
196        SipLocalNetworkAddr => "sip_local_network_addr",
197    }
198}
199
200/// Relationship between a [`SofiaVariable`] and the SIP header behind it.
201#[non_exhaustive]
202#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
203pub enum CarriedHeader {
204    /// The variable holds the header's field-value verbatim.
205    Verbatim(SipHeader),
206    /// The variable holds a field mod_sofia parsed out of the header. Lossy: the
207    /// header is the source of truth and the parts cannot rebuild it.
208    Derived(SipHeader),
209    /// As [`Derived`](Self::Derived), for a variable mod_sofia fills from whichever
210    /// of these headers the message carried.
211    DerivedOneOf(&'static [SipHeader]),
212}
213
214impl SofiaVariable {
215    /// The SIP header this variable's value came from, if any.
216    ///
217    /// `None` covers everything carrying no header field-value: switch-side config
218    /// and knobs, transport addresses, the Request-URI, values computed rather than
219    /// read off the wire, and values the caller sets for mod_sofia to build a header
220    /// from.
221    ///
222    /// The match is exhaustive by design. A new variant has to be classified here,
223    /// and a catch-all arm would answer `None` for it in silence.
224    pub fn carried_header(&self) -> Option<CarriedHeader> {
225        use CarriedHeader::{Derived, DerivedOneOf, Verbatim};
226        use SipHeader as H;
227
228        match self {
229            Self::SipFromUser
230            | Self::SipFromHost
231            | Self::SipFromPort
232            | Self::SipFromUri
233            | Self::SipFromDisplay
234            | Self::SipFromTag
235            | Self::SipFromComment
236            | Self::SipFromUserStripped
237            | Self::SipFromParams => Some(Derived(H::From)),
238            Self::SipFullFrom => Some(Verbatim(H::From)),
239
240            Self::SipToUser
241            | Self::SipToHost
242            | Self::SipToPort
243            | Self::SipToUri
244            | Self::SipToDisplay
245            | Self::SipToTag
246            | Self::SipToComment
247            | Self::SipToParams => Some(Derived(H::To)),
248            Self::SipFullTo => Some(Verbatim(H::To)),
249
250            Self::SipContactUser
251            | Self::SipContactHost
252            | Self::SipContactPort
253            | Self::SipContactUri
254            | Self::SipContactParams => Some(Derived(H::Contact)),
255
256            // The Request-URI is on the request line, not in a header.
257            Self::SipReqUser
258            | Self::SipReqHost
259            | Self::SipReqPort
260            | Self::SipReqUri
261            | Self::SipReqParams
262            | Self::SipInviteReqUri => None,
263
264            Self::SipViaHost | Self::SipViaPort | Self::SipViaRport | Self::SipViaProtocol => {
265                Some(Derived(H::Via))
266            }
267            Self::SipFullVia | Self::SipRecoverVia => Some(Verbatim(H::Via)),
268            Self::SipFullRoute => Some(Verbatim(H::Route)),
269
270            Self::SipCallId => Some(Verbatim(H::CallId)),
271            Self::SipCseq => Some(Verbatim(H::Cseq)),
272            // Falls back to Server when the message that carried it was a response.
273            Self::SipUserAgent => Some(Verbatim(H::UserAgent)),
274            Self::SipSubject => Some(Verbatim(H::Subject)),
275            Self::SipAllow => Some(Verbatim(H::Allow)),
276            // Only the first entry of the list.
277            Self::SipAcceptLanguage | Self::SipAcceptLanguageCount => {
278                Some(Derived(H::AcceptLanguage))
279            }
280            Self::SipCallInfo => Some(Verbatim(H::CallInfo)),
281            Self::SipDateEpochTime => Some(Derived(H::Date)),
282
283            Self::SipReceivedIp
284            | Self::SipReceivedPort
285            | Self::SipNetworkIp
286            | Self::SipNetworkPort
287            | Self::SipNatDetected
288            | Self::SipTransport
289            | Self::SipReplyHost
290            | Self::SipReplyPort
291            | Self::SipLocalNetworkAddr => None,
292
293            Self::SipAuthUsername
294            | Self::SipAuthPassword
295            | Self::SipAuthorized
296            | Self::SipAclAuthedBy
297            | Self::SipAclToken
298            | Self::SipChallengeRealm
299            | Self::SipNumberAlias => None,
300            Self::SipAuthRealm => Some(DerivedOneOf(&[H::Authorization, H::ProxyAuthorization])),
301
302            Self::SipInviteFailureStatus
303            | Self::SipInviteFailurePhrase
304            | Self::SipHangupDisposition
305            | Self::SipTermStatus
306            | Self::SipTermCause => None,
307            Self::SipReason => Some(Verbatim(H::Reason)),
308
309            Self::SipPAssertedIdentity => Some(Verbatim(H::PAssertedIdentity)),
310            Self::SipPPreferredIdentity => Some(Verbatim(H::PPreferredIdentity)),
311            Self::SipPrivacy => Some(Verbatim(H::Privacy)),
312            Self::SipRemotePartyId => Some(Verbatim(H::RemotePartyId)),
313            // A verification verdict and the attestation level asked of us, neither
314            // of them a field of the Identity header they concern.
315            Self::SipStirShakenAttest | Self::SipVerstat | Self::SipVerstatDetailed => None,
316
317            Self::SipInviteCallId => Some(Verbatim(H::CallId)),
318            Self::SipInviteCseq => Some(Verbatim(H::Cseq)),
319            Self::SipInviteFullFrom => Some(Verbatim(H::From)),
320            Self::SipInviteFullTo => Some(Verbatim(H::To)),
321            Self::SipInviteFullVia => Some(Verbatim(H::Via)),
322            Self::SipInviteFromUri => Some(Derived(H::From)),
323            Self::SipInviteToUri => Some(Derived(H::To)),
324            Self::SipInviteRecordRoute => Some(Verbatim(H::RecordRoute)),
325            Self::SipInviteRouteUri => Some(Derived(H::Route)),
326            Self::SipInviteDomain | Self::SipInviteParams | Self::SipInviteStamp => None,
327
328            Self::SipAutoAnswer
329            | Self::SipAutoSimplify
330            | Self::SipEnableSoa
331            | Self::SipCopyCustomHeaders
332            | Self::SipCopyMultipart
333            | Self::SipLoopedCall => None,
334
335            Self::SipRedirectedTo | Self::SipRecoverContact | Self::SipOutgoingContactUri => {
336                Some(Verbatim(H::Contact))
337            }
338            Self::SipRedirectedBy => Some(Verbatim(H::Diversion)),
339            Self::SipReferredByFull => Some(Verbatim(H::ReferredBy)),
340            Self::SipReferredByCid
341            | Self::SipReferredByUser
342            | Self::SipReferredByUserStripped
343            | Self::SipReferredByHost
344            | Self::SipReferredByPort
345            | Self::SipReferredByUri
346            | Self::SipReferredByParams => Some(Derived(H::ReferredBy)),
347            // The user part comes from whichever identity header mod_sofia resolved
348            // the caller from.
349            Self::SipNameParams => Some(DerivedOneOf(&[
350                H::From,
351                H::PAssertedIdentity,
352                H::PPreferredIdentity,
353                H::RemotePartyId,
354            ])),
355            Self::SipRedirectDialstring
356            | Self::SipReferReply
357            | Self::SipReferStatusCode
358            | Self::SipReinviteSdp => None,
359
360            Self::SipGateway
361            | Self::SipGatewayName
362            | Self::SipUseGateway
363            | Self::SipDestinationUrl
364            | Self::SipProfileName
365            | Self::SofiaProfileName
366            | Self::SofiaProfileUrl
367            | Self::SofiaProfileDomainName => None,
368
369            Self::RtpSecureMediaConfirmed
370            | Self::Rtp2833SendPayload
371            | Self::Rtp2833RecvPayload
372            | Self::RtpDisableHold
373            | Self::RtpJitterBufferPlc
374            | Self::RtpVideoMaxBandwidthIn
375            | Self::RtpVideoMaxBandwidthOut => None,
376
377            // Caller-supplied identity mod_sofia builds an outbound header from; which
378            // header depends on sip_cid_type, so none of them carries one.
379            Self::SipCalleeIdName | Self::SipCalleeIdNumber | Self::SipCidType => None,
380
381            // P-RTP-Stat has no SipHeader variant to name.
382            Self::SipRtpRxstat | Self::SipRtpTxstat | Self::SipPRtpStat => None,
383
384            Self::SipHistoryInfo => Some(Verbatim(H::HistoryInfo)),
385            Self::SipGeolocation => Some(Verbatim(H::Geolocation)),
386
387            Self::SipMultipart => None,
388        }
389    }
390}
391
392#[cfg(test)]
393mod tests {
394    use super::*;
395
396    #[test]
397    fn display_round_trip() {
398        assert_eq!(SofiaVariable::SipCallId.to_string(), "sip_call_id");
399        assert_eq!(
400            SofiaVariable::SipFromDisplay.to_string(),
401            "sip_from_display"
402        );
403        assert_eq!(
404            SofiaVariable::SofiaProfileName.to_string(),
405            "sofia_profile_name"
406        );
407    }
408
409    #[test]
410    fn as_ref_str() {
411        let v: &str = SofiaVariable::SipNetworkIp.as_ref();
412        assert_eq!(v, "sip_network_ip");
413    }
414
415    #[test]
416    fn from_str_case_insensitive() {
417        assert_eq!(
418            "sip_call_id".parse::<SofiaVariable>(),
419            Ok(SofiaVariable::SipCallId)
420        );
421        assert_eq!(
422            "SIP_CALL_ID".parse::<SofiaVariable>(),
423            Ok(SofiaVariable::SipCallId)
424        );
425    }
426
427    #[test]
428    fn from_str_unknown() {
429        let err = "nonexistent_sip_var".parse::<SofiaVariable>();
430        assert!(err.is_err());
431    }
432
433    #[test]
434    fn carried_header_verbatim() {
435        assert_eq!(
436            SofiaVariable::SipPrivacy.carried_header(),
437            Some(CarriedHeader::Verbatim(SipHeader::Privacy))
438        );
439        assert_eq!(
440            SofiaVariable::SipPAssertedIdentity.carried_header(),
441            Some(CarriedHeader::Verbatim(SipHeader::PAssertedIdentity))
442        );
443        assert_eq!(
444            SofiaVariable::SipRemotePartyId.carried_header(),
445            Some(CarriedHeader::Verbatim(SipHeader::RemotePartyId))
446        );
447        assert_eq!(
448            SofiaVariable::SipFullFrom.carried_header(),
449            Some(CarriedHeader::Verbatim(SipHeader::From))
450        );
451    }
452
453    #[test]
454    fn carried_header_derived() {
455        assert_eq!(
456            SofiaVariable::SipFromUser.carried_header(),
457            Some(CarriedHeader::Derived(SipHeader::From))
458        );
459    }
460
461    #[test]
462    fn recovery_and_outbound_contact_are_verbatim() {
463        assert_eq!(
464            SofiaVariable::SipRecoverVia.carried_header(),
465            Some(CarriedHeader::Verbatim(SipHeader::Via))
466        );
467        assert_eq!(
468            SofiaVariable::SipRecoverContact.carried_header(),
469            Some(CarriedHeader::Verbatim(SipHeader::Contact))
470        );
471        assert_eq!(
472            SofiaVariable::SipOutgoingContactUri.carried_header(),
473            Some(CarriedHeader::Verbatim(SipHeader::Contact))
474        );
475    }
476
477    #[test]
478    fn parsed_sub_fields_are_derived() {
479        assert_eq!(
480            SofiaVariable::SipFromParams.carried_header(),
481            Some(CarriedHeader::Derived(SipHeader::From))
482        );
483        assert_eq!(
484            SofiaVariable::SipToParams.carried_header(),
485            Some(CarriedHeader::Derived(SipHeader::To))
486        );
487        assert_eq!(
488            SofiaVariable::SipReferredByParams.carried_header(),
489            Some(CarriedHeader::Derived(SipHeader::ReferredBy))
490        );
491        assert_eq!(
492            SofiaVariable::SipAcceptLanguageCount.carried_header(),
493            Some(CarriedHeader::Derived(SipHeader::AcceptLanguage))
494        );
495    }
496
497    /// mod_sofia fills these from whichever of several headers was present, so
498    /// neither a single header nor `None` is a true answer.
499    #[test]
500    fn runtime_chosen_source_names_the_candidates() {
501        assert_eq!(
502            SofiaVariable::SipNameParams.carried_header(),
503            Some(CarriedHeader::DerivedOneOf(&[
504                SipHeader::From,
505                SipHeader::PAssertedIdentity,
506                SipHeader::PPreferredIdentity,
507                SipHeader::RemotePartyId,
508            ]))
509        );
510        assert_eq!(
511            SofiaVariable::SipAuthRealm.carried_header(),
512            Some(CarriedHeader::DerivedOneOf(&[
513                SipHeader::Authorization,
514                SipHeader::ProxyAuthorization,
515            ]))
516        );
517    }
518
519    #[test]
520    fn switch_side_variables_carry_no_header() {
521        for var in [
522            SofiaVariable::SipInviteStamp,
523            SofiaVariable::SipReplyPort,
524            SofiaVariable::SipLocalNetworkAddr,
525            SofiaVariable::SipNumberAlias,
526            SofiaVariable::SipReqParams,
527        ] {
528            assert_eq!(var.carried_header(), None, "{}", var.as_str());
529        }
530    }
531
532    #[test]
533    fn carried_header_none() {
534        // The Request-URI is not a header, and a gateway name is switch config.
535        assert_eq!(SofiaVariable::SipReqUri.carried_header(), None);
536        assert_eq!(SofiaVariable::SipGateway.carried_header(), None);
537    }
538
539    /// A wire name that already spells the header keeps its exact canonical
540    /// casing, so any typo in either table shows up as a mismatch here.
541    #[test]
542    fn hyphenated_wire_names_match_their_header() {
543        for var in SofiaVariable::ALL {
544            let Some(suffix) = var
545                .as_str()
546                .strip_prefix("sip_")
547            else {
548                continue;
549            };
550            if !suffix.contains('-') {
551                continue;
552            }
553            let Some(carried) = var.carried_header() else {
554                panic!("{} spells a header but carries none", var.as_str());
555            };
556            let CarriedHeader::Verbatim(header) = carried else {
557                panic!("{} spells a header but is not verbatim", var.as_str());
558            };
559            assert_eq!(header.as_str(), suffix, "for {}", var.as_str());
560        }
561    }
562}