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}
170
171/// Relationship between a [`SofiaVariable`] and the SIP header behind it.
172#[non_exhaustive]
173#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
174pub enum CarriedHeader {
175    /// The variable holds the header's field-value verbatim.
176    Verbatim(SipHeader),
177    /// The variable holds a field mod_sofia parsed out of the header. Lossy: the
178    /// header is the source of truth and the parts cannot rebuild it.
179    Derived(SipHeader),
180}
181
182impl SofiaVariable {
183    /// The SIP header this variable's value came from, if any.
184    ///
185    /// `None` covers everything carrying no header field-value: switch-side config
186    /// and knobs, transport addresses, the Request-URI, values computed rather than
187    /// read off the wire, and values the caller sets for mod_sofia to build a header
188    /// from.
189    ///
190    /// The match is exhaustive by design. A new variant has to be classified here,
191    /// and a catch-all arm would answer `None` for it in silence.
192    pub fn carried_header(&self) -> Option<CarriedHeader> {
193        use CarriedHeader::{Derived, Verbatim};
194        use SipHeader as H;
195
196        match self {
197            Self::SipFromUser
198            | Self::SipFromHost
199            | Self::SipFromPort
200            | Self::SipFromUri
201            | Self::SipFromDisplay
202            | Self::SipFromTag
203            | Self::SipFromComment
204            | Self::SipFromUserStripped => Some(Derived(H::From)),
205            Self::SipFullFrom => Some(Verbatim(H::From)),
206
207            Self::SipToUser
208            | Self::SipToHost
209            | Self::SipToPort
210            | Self::SipToUri
211            | Self::SipToDisplay
212            | Self::SipToTag
213            | Self::SipToComment => Some(Derived(H::To)),
214            Self::SipFullTo => Some(Verbatim(H::To)),
215
216            Self::SipContactUser
217            | Self::SipContactHost
218            | Self::SipContactPort
219            | Self::SipContactUri
220            | Self::SipContactParams => Some(Derived(H::Contact)),
221
222            // The Request-URI is on the request line, not in a header.
223            Self::SipReqUser
224            | Self::SipReqHost
225            | Self::SipReqPort
226            | Self::SipReqUri
227            | Self::SipInviteReqUri => None,
228
229            Self::SipViaHost | Self::SipViaPort | Self::SipViaRport | Self::SipViaProtocol => {
230                Some(Derived(H::Via))
231            }
232            Self::SipFullVia => Some(Verbatim(H::Via)),
233            Self::SipFullRoute => Some(Verbatim(H::Route)),
234
235            Self::SipCallId => Some(Verbatim(H::CallId)),
236            Self::SipCseq => Some(Verbatim(H::Cseq)),
237            // Falls back to Server when the message that carried it was a response.
238            Self::SipUserAgent => Some(Verbatim(H::UserAgent)),
239            Self::SipSubject => Some(Verbatim(H::Subject)),
240            Self::SipAllow => Some(Verbatim(H::Allow)),
241            // Only the first entry of the list.
242            Self::SipAcceptLanguage => Some(Derived(H::AcceptLanguage)),
243            Self::SipCallInfo => Some(Verbatim(H::CallInfo)),
244            Self::SipDateEpochTime => Some(Derived(H::Date)),
245
246            Self::SipReceivedIp
247            | Self::SipReceivedPort
248            | Self::SipNetworkIp
249            | Self::SipNetworkPort
250            | Self::SipNatDetected
251            | Self::SipTransport
252            | Self::SipReplyHost => None,
253
254            Self::SipAuthUsername
255            | Self::SipAuthPassword
256            | Self::SipAuthorized
257            | Self::SipAclAuthedBy
258            | Self::SipAclToken
259            | Self::SipChallengeRealm => None,
260
261            Self::SipInviteFailureStatus
262            | Self::SipInviteFailurePhrase
263            | Self::SipHangupDisposition
264            | Self::SipTermStatus
265            | Self::SipTermCause => None,
266            Self::SipReason => Some(Verbatim(H::Reason)),
267
268            Self::SipPAssertedIdentity => Some(Verbatim(H::PAssertedIdentity)),
269            Self::SipPPreferredIdentity => Some(Verbatim(H::PPreferredIdentity)),
270            Self::SipPrivacy => Some(Verbatim(H::Privacy)),
271            Self::SipRemotePartyId => Some(Verbatim(H::RemotePartyId)),
272            // A verification verdict and the attestation level asked of us, neither
273            // of them a field of the Identity header they concern.
274            Self::SipStirShakenAttest | Self::SipVerstat | Self::SipVerstatDetailed => None,
275
276            Self::SipInviteCallId => Some(Verbatim(H::CallId)),
277            Self::SipInviteCseq => Some(Verbatim(H::Cseq)),
278            Self::SipInviteFullFrom => Some(Verbatim(H::From)),
279            Self::SipInviteFullTo => Some(Verbatim(H::To)),
280            Self::SipInviteFullVia => Some(Verbatim(H::Via)),
281            Self::SipInviteFromUri => Some(Derived(H::From)),
282            Self::SipInviteToUri => Some(Derived(H::To)),
283            Self::SipInviteRecordRoute => Some(Verbatim(H::RecordRoute)),
284            Self::SipInviteRouteUri => Some(Derived(H::Route)),
285            Self::SipInviteDomain | Self::SipInviteParams => None,
286
287            Self::SipAutoAnswer
288            | Self::SipAutoSimplify
289            | Self::SipEnableSoa
290            | Self::SipCopyCustomHeaders
291            | Self::SipCopyMultipart
292            | Self::SipLoopedCall => None,
293
294            Self::SipRedirectedTo => Some(Verbatim(H::Contact)),
295            Self::SipRedirectedBy => Some(Verbatim(H::Diversion)),
296            Self::SipReferredByFull => Some(Verbatim(H::ReferredBy)),
297            Self::SipReferredByCid => Some(Derived(H::ReferredBy)),
298            Self::SipRedirectDialstring
299            | Self::SipReferReply
300            | Self::SipReferStatusCode
301            | Self::SipReinviteSdp => None,
302
303            Self::SipGateway
304            | Self::SipGatewayName
305            | Self::SipUseGateway
306            | Self::SipDestinationUrl
307            | Self::SipProfileName
308            | Self::SofiaProfileName
309            | Self::SofiaProfileUrl
310            | Self::SofiaProfileDomainName => None,
311
312            Self::RtpSecureMediaConfirmed
313            | Self::Rtp2833SendPayload
314            | Self::Rtp2833RecvPayload
315            | Self::RtpDisableHold
316            | Self::RtpJitterBufferPlc
317            | Self::RtpVideoMaxBandwidthIn
318            | Self::RtpVideoMaxBandwidthOut => None,
319
320            // Caller-supplied identity mod_sofia builds an outbound header from; which
321            // header depends on sip_cid_type, so none of them carries one.
322            Self::SipCalleeIdName | Self::SipCalleeIdNumber | Self::SipCidType => None,
323
324            // P-RTP-Stat has no SipHeader variant to name.
325            Self::SipRtpRxstat | Self::SipRtpTxstat | Self::SipPRtpStat => None,
326
327            Self::SipHistoryInfo => Some(Verbatim(H::HistoryInfo)),
328            Self::SipGeolocation => Some(Verbatim(H::Geolocation)),
329
330            Self::SipMultipart => None,
331        }
332    }
333}
334
335#[cfg(test)]
336mod tests {
337    use super::*;
338
339    #[test]
340    fn display_round_trip() {
341        assert_eq!(SofiaVariable::SipCallId.to_string(), "sip_call_id");
342        assert_eq!(
343            SofiaVariable::SipFromDisplay.to_string(),
344            "sip_from_display"
345        );
346        assert_eq!(
347            SofiaVariable::SofiaProfileName.to_string(),
348            "sofia_profile_name"
349        );
350    }
351
352    #[test]
353    fn as_ref_str() {
354        let v: &str = SofiaVariable::SipNetworkIp.as_ref();
355        assert_eq!(v, "sip_network_ip");
356    }
357
358    #[test]
359    fn from_str_case_insensitive() {
360        assert_eq!(
361            "sip_call_id".parse::<SofiaVariable>(),
362            Ok(SofiaVariable::SipCallId)
363        );
364        assert_eq!(
365            "SIP_CALL_ID".parse::<SofiaVariable>(),
366            Ok(SofiaVariable::SipCallId)
367        );
368    }
369
370    #[test]
371    fn from_str_unknown() {
372        let err = "nonexistent_sip_var".parse::<SofiaVariable>();
373        assert!(err.is_err());
374    }
375
376    #[test]
377    fn carried_header_verbatim() {
378        assert_eq!(
379            SofiaVariable::SipPrivacy.carried_header(),
380            Some(CarriedHeader::Verbatim(SipHeader::Privacy))
381        );
382        assert_eq!(
383            SofiaVariable::SipPAssertedIdentity.carried_header(),
384            Some(CarriedHeader::Verbatim(SipHeader::PAssertedIdentity))
385        );
386        assert_eq!(
387            SofiaVariable::SipRemotePartyId.carried_header(),
388            Some(CarriedHeader::Verbatim(SipHeader::RemotePartyId))
389        );
390        assert_eq!(
391            SofiaVariable::SipFullFrom.carried_header(),
392            Some(CarriedHeader::Verbatim(SipHeader::From))
393        );
394    }
395
396    #[test]
397    fn carried_header_derived() {
398        assert_eq!(
399            SofiaVariable::SipFromUser.carried_header(),
400            Some(CarriedHeader::Derived(SipHeader::From))
401        );
402    }
403
404    #[test]
405    fn carried_header_none() {
406        // The Request-URI is not a header, and a gateway name is switch config.
407        assert_eq!(SofiaVariable::SipReqUri.carried_header(), None);
408        assert_eq!(SofiaVariable::SipGateway.carried_header(), None);
409    }
410
411    /// A wire name that already spells the header keeps its exact canonical
412    /// casing, so any typo in either table shows up as a mismatch here.
413    #[test]
414    fn hyphenated_wire_names_match_their_header() {
415        for var in SofiaVariable::ALL {
416            let Some(suffix) = var
417                .as_str()
418                .strip_prefix("sip_")
419            else {
420                continue;
421            };
422            if !suffix.contains('-') {
423                continue;
424            }
425            let Some(carried) = var.carried_header() else {
426                panic!("{} spells a header but carries none", var.as_str());
427            };
428            let CarriedHeader::Verbatim(header) = carried else {
429                panic!("{} spells a header but is not verbatim", var.as_str());
430            };
431            assert_eq!(header.as_str(), suffix, "for {}", var.as_str());
432        }
433    }
434}