Skip to main content

varnish_sys/vcl/
convert.rs

1//! Convert Rust types into their VCL_* equivalent, and back
2//!
3//! # Type conversion
4//!
5//! The proc macro will generate the wrappers for each user function, relying on
6//! the type conversions defined here. The values need to be converted from Varnish's internal types
7//! to Rust's types, and vice versa.
8//!
9//! Most conversions from VCL to Rust are straightforward, using either `From` or `TryFrom` traits.
10//! The `IntoVCL` trait take care of converting a Rust type into VCL. It requires a `&mut `[`Workspace`]
11//! to possibly store the returned value into the task request. This allows vmod writes to just return
12//! easy-to-work-with strings, and let the boilerplate handle the allocation, copy and error handling.
13//!
14//! If one wants to handle things manually, all `VCL_*` types implement [`IntoVCL`] as a no-op. It
15//! can be useful to avoid extra memory allocations by the boilerplate, if that is a worry.
16//!
17//! Here's a table of the type correspondences:
18//!
19//! | Rust | direction | VCL |
20//! | :--: | :-------: | :-:
21//! | `()` | -> | `VCL_VOID` |
22//! | `f64`  | <-> | `VCL_REAL` |
23//! | `i64`  | <-> | `VCL_INT` |
24//! | `bool` | <-> | `VCL_BOOL` |
25//! | `std::time::Duration` | <-> | `VCL_DURATION` |
26//! | `std::time::SystemTime` | <-> | `VCL_TIME` |
27//! | `&str` | <-> | `VCL_STRING` |
28//! | `String` | -> | `VCL_STRING` |
29//! | `&[u8]` | <- | `VCL_BLOB` |
30//! | `Option<CowProbe>` | <-> | `VCL_PROBE` |
31//! | `Option<Probe>` | <-> | `VCL_PROBE` |
32//! | `Option<std::net::SocketAddr>` | -> | `VCL_IP` |
33//! | `Subroutine` | <-> | `VCL_SUB` |
34//!
35//! For all the other types, which are pointers, you will need to use the native types.
36//!
37//! *Note:* It is possible to simply return a `VCL_*` type (or a Result<VCL_*, _>), in which case
38//! the boilerplate will just skip the conversion.
39//!
40//! # Result
41//!
42//! It's possible for a vmod writer to return a bare value, or a `Result<_, E: AsRef<str>>` to
43//! potentially abort VCL processing in case the vmod hit an unrecoverable error.
44//!
45//! If a vmod function returns `Err(msg)`, the boilerplate will log `msg`, mark the current task as
46//! failed and will return a default value to the VCL. In turn, the VCL will stop its processing
47//! and will create a synthetic error object.
48
49use std::borrow::Cow;
50use std::ffi::CStr;
51use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
52use std::ptr::{null, null_mut};
53use std::time::{Duration, SystemTime, UNIX_EPOCH};
54
55use crate::ffi::{
56    http, sa_family_t, vsa_suckaddr_len, vtim_dur, vtim_real, VSA_BuildFAP, VSA_GetPtr, VSA_Port,
57    PF_INET, PF_INET6, VCL_ACL, VCL_BACKEND, VCL_BLOB, VCL_BODY, VCL_BOOL, VCL_DURATION, VCL_ENUM,
58    VCL_HEADER, VCL_HTTP, VCL_INT, VCL_IP, VCL_PROBE, VCL_REAL, VCL_REGEX, VCL_STEVEDORE,
59    VCL_STRANDS, VCL_STRING, VCL_SUB, VCL_TIME, VCL_VCL,
60};
61
62use crate::vcl::{
63    from_vcl_probe, into_vcl_probe, subroutine::Subroutine, Acl, BackendRef, CowProbe, Probe,
64    VclError, Workspace,
65};
66
67/// Convert a Rust type into a VCL one
68///
69/// It will use the [`Workspace`] to persist the data during the VCL task if necessary
70pub trait IntoVCL<T> {
71    fn into_vcl(self, ws: &mut Workspace) -> Result<T, VclError>;
72}
73
74macro_rules! default_null_ptr {
75    ($ident:ident) => {
76        default_null_ptr!($ident, null);
77    };
78    (mut $ident:ident) => {
79        default_null_ptr!($ident, null_mut);
80    };
81    ($ident:ident, $func:ident) => {
82        impl Default for $ident {
83            fn default() -> Self {
84                $ident($func())
85            }
86        }
87    };
88}
89
90macro_rules! into_vcl_using_from {
91    ($rust_ty:ty, $vcl_ty:ident) => {
92        impl IntoVCL<$vcl_ty> for $rust_ty {
93            fn into_vcl(self, _: &mut Workspace) -> Result<$vcl_ty, VclError> {
94                Ok(self.into())
95            }
96        }
97    };
98}
99
100macro_rules! from_rust_to_vcl {
101    ($rust_ty:ty, $vcl_ty:ident) => {
102        impl From<$rust_ty> for $vcl_ty {
103            fn from(b: $rust_ty) -> Self {
104                Self(b.into())
105            }
106        }
107    };
108}
109
110macro_rules! from_vcl_to_opt_rust {
111    ($vcl_ty:ident, $rust_ty:ty) => {
112        impl From<$vcl_ty> for Option<$rust_ty> {
113            fn from(b: $vcl_ty) -> Self {
114                Some(b.into())
115            }
116        }
117    };
118}
119
120// VCL_ACL
121
122// this is deceiving: VCL_ACL must never be null, but we may need to return a NULL to the C layer if
123// the vmod returns Return<Acl, _>
124default_null_ptr!(VCL_ACL);
125
126from_vcl_to_opt_rust!(VCL_ACL, Acl);
127impl From<VCL_ACL> for Acl {
128    fn from(value: VCL_ACL) -> Acl {
129        assert!(!value.0.is_null());
130        Acl { raw: value }
131    }
132}
133
134impl IntoVCL<VCL_ACL> for Acl {
135    fn into_vcl(self, _: &mut Workspace) -> Result<VCL_ACL, VclError> {
136        unsafe {
137            assert!(!self.vcl_ptr().0.is_null());
138            Ok(self.vcl_ptr())
139        }
140    }
141}
142
143// VCL_BLOB
144default_null_ptr!(VCL_BLOB);
145impl From<VCL_BLOB> for &[u8] {
146    fn from(value: VCL_BLOB) -> Self {
147        if value.0.is_null() {
148            return &[];
149        }
150
151        unsafe {
152            let blob = &*value.0;
153            if blob.blob.is_null() || blob.len == 0 {
154                &[]
155            } else {
156                std::slice::from_raw_parts(blob.blob.cast::<u8>(), blob.len)
157            }
158        }
159    }
160}
161from_vcl_to_opt_rust!(VCL_BLOB, &[u8]);
162
163// VCL_BODY
164default_null_ptr!(VCL_BODY);
165
166//
167// VCL_BOOL
168//
169into_vcl_using_from!(bool, VCL_BOOL);
170from_rust_to_vcl!(bool, VCL_BOOL);
171from_vcl_to_opt_rust!(VCL_BOOL, bool);
172impl From<VCL_BOOL> for bool {
173    fn from(b: VCL_BOOL) -> Self {
174        b.0 != 0
175    }
176}
177
178//
179// VCL_DURATION
180//
181into_vcl_using_from!(Duration, VCL_DURATION);
182from_vcl_to_opt_rust!(VCL_DURATION, Duration);
183impl From<VCL_DURATION> for Duration {
184    fn from(value: VCL_DURATION) -> Self {
185        value.0.into()
186    }
187}
188impl From<Duration> for VCL_DURATION {
189    fn from(value: Duration) -> Self {
190        Self(value.into())
191    }
192}
193
194//
195// vtim_dur -- this is a sub-structure of VCL_DURATION, equal to f64
196//
197impl From<vtim_dur> for Duration {
198    fn from(value: vtim_dur) -> Self {
199        Self::from_secs_f64(value.0)
200    }
201}
202impl From<Duration> for vtim_dur {
203    fn from(value: Duration) -> Self {
204        Self(value.as_secs_f64())
205    }
206}
207
208// VCL_ENUM
209default_null_ptr!(VCL_ENUM);
210// VCL_HEADER
211default_null_ptr!(VCL_HEADER);
212// VCL_HTTP
213default_null_ptr!(mut VCL_HTTP);
214impl From<*mut http> for VCL_HTTP {
215    // This is needed because pre-v7 vrt_ctx used http instead of VCL_HTTP
216    fn from(value: *mut http) -> Self {
217        Self(value)
218    }
219}
220
221//
222// VCL_INT
223//
224into_vcl_using_from!(i64, VCL_INT);
225from_rust_to_vcl!(i64, VCL_INT);
226from_vcl_to_opt_rust!(VCL_INT, i64);
227impl From<VCL_INT> for i64 {
228    fn from(b: VCL_INT) -> Self {
229        b.0
230    }
231}
232
233//
234// VCL_IP
235//
236default_null_ptr!(VCL_IP);
237impl From<VCL_IP> for Option<SocketAddr> {
238    fn from(value: VCL_IP) -> Self {
239        let value = value.0;
240        if value.is_null() {
241            return None;
242        }
243        unsafe {
244            let mut ptr = null();
245            let fam = VSA_GetPtr(value, &raw mut ptr) as u32;
246            let port = VSA_Port(value) as u16;
247
248            match fam {
249                PF_INET => {
250                    let buf: &[u8; 4] = std::slice::from_raw_parts(ptr.cast::<u8>(), 4)
251                        .try_into()
252                        .expect("IPv4 address bytes slice must always be 4 bytes");
253                    Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::from(*buf)), port))
254                }
255                PF_INET6 => {
256                    let buf: &[u8; 16] = std::slice::from_raw_parts(ptr.cast::<u8>(), 16)
257                        .try_into()
258                        .expect("IPv6 address bytes slice must always be 16 bytes");
259                    Some(SocketAddr::new(IpAddr::V6(Ipv6Addr::from(*buf)), port))
260                }
261                _ => None,
262            }
263        }
264    }
265}
266
267//
268// VCL_PROBE
269//
270default_null_ptr!(VCL_PROBE);
271impl IntoVCL<VCL_PROBE> for CowProbe<'_> {
272    fn into_vcl(self, ws: &mut Workspace) -> Result<VCL_PROBE, VclError> {
273        into_vcl_probe(self, ws)
274    }
275}
276impl IntoVCL<VCL_PROBE> for Probe {
277    fn into_vcl(self, ws: &mut Workspace) -> Result<VCL_PROBE, VclError> {
278        into_vcl_probe(self, ws)
279    }
280}
281impl From<VCL_PROBE> for Option<CowProbe<'_>> {
282    fn from(value: VCL_PROBE) -> Self {
283        from_vcl_probe(value)
284    }
285}
286impl From<VCL_PROBE> for Option<Probe> {
287    fn from(value: VCL_PROBE) -> Self {
288        from_vcl_probe(value)
289    }
290}
291
292//
293// VCL_REAL
294//
295into_vcl_using_from!(f64, VCL_REAL);
296from_rust_to_vcl!(f64, VCL_REAL);
297from_vcl_to_opt_rust!(VCL_REAL, f64);
298impl From<VCL_REAL> for f64 {
299    fn from(b: VCL_REAL) -> Self {
300        b.0
301    }
302}
303
304//
305// VCL_STRING
306//
307default_null_ptr!(VCL_STRING);
308impl IntoVCL<VCL_STRING> for &str {
309    fn into_vcl(self, ws: &mut Workspace) -> Result<VCL_STRING, VclError> {
310        Ok(VCL_STRING(ws.copy_bytes_with_null(self.as_bytes())?.b))
311    }
312}
313impl IntoVCL<VCL_STRING> for &CStr {
314    fn into_vcl(self, ws: &mut Workspace) -> Result<VCL_STRING, VclError> {
315        ws.copy_cstr(self)
316    }
317}
318impl IntoVCL<VCL_STRING> for &Cow<'_, str> {
319    fn into_vcl(self, ws: &mut Workspace) -> Result<VCL_STRING, VclError> {
320        Ok(VCL_STRING(ws.copy_bytes_with_null(self.as_bytes())?.b))
321    }
322}
323impl IntoVCL<VCL_STRING> for String {
324    fn into_vcl(self, ws: &mut Workspace) -> Result<VCL_STRING, VclError> {
325        self.as_str().into_vcl(ws)
326    }
327}
328impl<T: IntoVCL<VCL_STRING>> IntoVCL<VCL_STRING> for Option<T> {
329    fn into_vcl(self, ws: &mut Workspace) -> Result<VCL_STRING, VclError> {
330        match self {
331            None => Ok(VCL_STRING(null())),
332            Some(t) => t.into_vcl(ws),
333        }
334    }
335}
336impl From<VCL_STRING> for Option<&CStr> {
337    fn from(value: VCL_STRING) -> Self {
338        if value.0.is_null() {
339            None
340        } else {
341            Some(unsafe { CStr::from_ptr(value.0) })
342        }
343    }
344}
345impl From<VCL_STRING> for &CStr {
346    fn from(value: VCL_STRING) -> Self {
347        // Treat a null pointer as an empty string
348        <Option<&CStr>>::from(value).unwrap_or_default()
349    }
350}
351impl TryFrom<VCL_STRING> for Option<&str> {
352    type Error = VclError;
353    fn try_from(value: VCL_STRING) -> Result<Self, Self::Error> {
354        Ok(<Option<&CStr>>::from(value).map(CStr::to_str).transpose()?)
355    }
356}
357impl<'a> TryFrom<VCL_STRING> for &'a str {
358    type Error = VclError;
359    fn try_from(value: VCL_STRING) -> Result<Self, Self::Error> {
360        Ok(<Option<&'a str>>::try_from(value)?.unwrap_or(""))
361    }
362}
363
364// VCL_STEVEDORE
365default_null_ptr!(VCL_STEVEDORE);
366// VCL_STRANDS
367default_null_ptr!(VCL_STRANDS);
368
369//
370// VCL_TIME
371//
372impl From<VCL_TIME> for SystemTime {
373    fn from(value: VCL_TIME) -> Self {
374        // seconds are stored in `VCL_TIME(vtim_real(f64))`
375        let secs = value.0 .0;
376
377        // Reject NaN/Inf and out-of-range values by falling back to UNIX_EPOCH.
378        if !secs.is_finite() {
379            return UNIX_EPOCH;
380        }
381
382        if secs >= 0.0 {
383            Duration::try_from_secs_f64(secs)
384                .ok()
385                .and_then(|dur| UNIX_EPOCH.checked_add(dur))
386                .unwrap_or(UNIX_EPOCH)
387        } else {
388            // Allow times before UNIX_EPOCH by subtracting the positive duration.
389            Duration::try_from_secs_f64(-secs)
390                .ok()
391                .and_then(|dur| UNIX_EPOCH.checked_sub(dur))
392                .unwrap_or(UNIX_EPOCH)
393        }
394    }
395}
396
397impl IntoVCL<VCL_TIME> for SystemTime {
398    fn into_vcl(self, _: &mut Workspace) -> Result<VCL_TIME, VclError> {
399        self.try_into()
400    }
401}
402
403impl TryFrom<SystemTime> for VCL_TIME {
404    type Error = VclError;
405
406    fn try_from(value: SystemTime) -> Result<Self, Self::Error> {
407        Ok(VCL_TIME(vtim_real(
408            value
409                .duration_since(SystemTime::UNIX_EPOCH)
410                .map_err(|e| VclError::new(e.to_string()))?
411                .as_secs_f64(),
412        )))
413    }
414}
415
416// VCL_VCL
417default_null_ptr!(mut VCL_VCL);
418
419// VCL_BACKEND
420default_null_ptr!(VCL_BACKEND);
421
422use std::ffi::c_void;
423use std::num::NonZeroUsize;
424use std::ptr;
425
426impl IntoVCL<VCL_BACKEND> for BackendRef {
427    fn into_vcl(self, _: &mut Workspace) -> Result<VCL_BACKEND, VclError> {
428        unsafe { Ok(self.vcl_ptr()) }
429    }
430}
431
432impl IntoVCL<VCL_BACKEND> for Option<BackendRef> {
433    fn into_vcl(self, _: &mut Workspace) -> Result<VCL_BACKEND, VclError> {
434        unsafe { Ok(self.map_or(VCL_BACKEND(null()), |b: BackendRef| b.vcl_ptr())) }
435    }
436}
437
438impl From<VCL_BACKEND> for Option<BackendRef> {
439    fn from(value: VCL_BACKEND) -> Self {
440        unsafe { BackendRef::new(value) }
441    }
442}
443
444// VCL_SUB
445default_null_ptr!(VCL_SUB);
446impl From<VCL_SUB> for Subroutine {
447    fn from(value: VCL_SUB) -> Self {
448        assert!(!value.0.is_null(), "VCL_SUB must not be null");
449        Subroutine(value)
450    }
451}
452
453impl IntoVCL<VCL_SUB> for Subroutine {
454    fn into_vcl(self, _: &mut Workspace) -> Result<VCL_SUB, VclError> {
455        Ok(self.vcl_ptr())
456    }
457}
458
459default_null_ptr!(VCL_REGEX);
460
461unsafe fn write_ip_to_ptr(ip: SocketAddr, p: *mut c_void) {
462    match ip {
463        SocketAddr::V4(sa) => {
464            assert!(!VSA_BuildFAP(
465                p,
466                PF_INET as sa_family_t,
467                sa.ip().octets().as_slice().as_ptr().cast::<c_void>(),
468                4,
469                ptr::from_ref::<u16>(&sa.port().to_be()).cast::<c_void>(),
470                2
471            )
472            .is_null());
473        }
474        SocketAddr::V6(sa) => {
475            assert!(!VSA_BuildFAP(
476                p,
477                PF_INET6 as sa_family_t,
478                sa.ip().octets().as_slice().as_ptr().cast::<c_void>(),
479                16,
480                ptr::from_ref::<u16>(&sa.port().to_be()).cast::<c_void>(),
481                2
482            )
483            .is_null());
484        }
485    }
486}
487
488pub(crate) unsafe fn write_ip_to_buf(ip: SocketAddr, buf: &mut [u8]) {
489    assert_eq!(buf.len(), vsa_suckaddr_len);
490    write_ip_to_ptr(ip, buf.as_mut_ptr().cast::<c_void>());
491}
492impl IntoVCL<VCL_IP> for SocketAddr {
493    fn into_vcl(self, ws: &mut Workspace) -> Result<VCL_IP, VclError> {
494        unsafe {
495            // We cannot use sizeof::<suckaddr>() because suckaddr is a zero-sized
496            // struct from Rust's perspective
497            let size =
498                NonZeroUsize::new(vsa_suckaddr_len).expect("vsa_suckaddr_len must be non-zero");
499            let p = ws.alloc(size);
500            if p.is_null() {
501                Err(VclError::WsOutOfMemory(size))?;
502            }
503
504            let buf = std::slice::from_raw_parts_mut(p.cast::<u8>(), vsa_suckaddr_len);
505            write_ip_to_buf(self, buf);
506
507            Ok(VCL_IP(p.cast()))
508        }
509    }
510}