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, BackendRef, CowProbe, Probe, VclError,
64    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
121default_null_ptr!(VCL_ACL);
122
123// VCL_BLOB
124default_null_ptr!(VCL_BLOB);
125impl From<VCL_BLOB> for &[u8] {
126    fn from(value: VCL_BLOB) -> Self {
127        if value.0.is_null() {
128            return &[];
129        }
130
131        unsafe {
132            let blob = &*value.0;
133            if blob.blob.is_null() || blob.len == 0 {
134                &[]
135            } else {
136                std::slice::from_raw_parts(blob.blob.cast::<u8>(), blob.len)
137            }
138        }
139    }
140}
141from_vcl_to_opt_rust!(VCL_BLOB, &[u8]);
142
143// VCL_BODY
144default_null_ptr!(VCL_BODY);
145
146//
147// VCL_BOOL
148//
149into_vcl_using_from!(bool, VCL_BOOL);
150from_rust_to_vcl!(bool, VCL_BOOL);
151from_vcl_to_opt_rust!(VCL_BOOL, bool);
152impl From<VCL_BOOL> for bool {
153    fn from(b: VCL_BOOL) -> Self {
154        b.0 != 0
155    }
156}
157
158//
159// VCL_DURATION
160//
161into_vcl_using_from!(Duration, VCL_DURATION);
162from_vcl_to_opt_rust!(VCL_DURATION, Duration);
163impl From<VCL_DURATION> for Duration {
164    fn from(value: VCL_DURATION) -> Self {
165        value.0.into()
166    }
167}
168impl From<Duration> for VCL_DURATION {
169    fn from(value: Duration) -> Self {
170        Self(value.into())
171    }
172}
173
174//
175// vtim_dur -- this is a sub-structure of VCL_DURATION, equal to f64
176//
177impl From<vtim_dur> for Duration {
178    fn from(value: vtim_dur) -> Self {
179        Self::from_secs_f64(value.0)
180    }
181}
182impl From<Duration> for vtim_dur {
183    fn from(value: Duration) -> Self {
184        Self(value.as_secs_f64())
185    }
186}
187
188// VCL_ENUM
189default_null_ptr!(VCL_ENUM);
190// VCL_HEADER
191default_null_ptr!(VCL_HEADER);
192// VCL_HTTP
193default_null_ptr!(mut VCL_HTTP);
194impl From<*mut http> for VCL_HTTP {
195    // This is needed because pre-v7 vrt_ctx used http instead of VCL_HTTP
196    fn from(value: *mut http) -> Self {
197        Self(value)
198    }
199}
200
201//
202// VCL_INT
203//
204into_vcl_using_from!(i64, VCL_INT);
205from_rust_to_vcl!(i64, VCL_INT);
206from_vcl_to_opt_rust!(VCL_INT, i64);
207impl From<VCL_INT> for i64 {
208    fn from(b: VCL_INT) -> Self {
209        b.0
210    }
211}
212
213//
214// VCL_IP
215//
216default_null_ptr!(VCL_IP);
217impl From<VCL_IP> for Option<SocketAddr> {
218    fn from(value: VCL_IP) -> Self {
219        let value = value.0;
220        if value.is_null() {
221            return None;
222        }
223        unsafe {
224            let mut ptr = null();
225            let fam = VSA_GetPtr(value, &raw mut ptr) as u32;
226            let port = VSA_Port(value) as u16;
227
228            match fam {
229                PF_INET => {
230                    let buf: &[u8; 4] = std::slice::from_raw_parts(ptr.cast::<u8>(), 4)
231                        .try_into()
232                        .expect("IPv4 address bytes slice must always be 4 bytes");
233                    Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::from(*buf)), port))
234                }
235                PF_INET6 => {
236                    let buf: &[u8; 16] = std::slice::from_raw_parts(ptr.cast::<u8>(), 16)
237                        .try_into()
238                        .expect("IPv6 address bytes slice must always be 16 bytes");
239                    Some(SocketAddr::new(IpAddr::V6(Ipv6Addr::from(*buf)), port))
240                }
241                _ => None,
242            }
243        }
244    }
245}
246
247//
248// VCL_PROBE
249//
250default_null_ptr!(VCL_PROBE);
251impl IntoVCL<VCL_PROBE> for CowProbe<'_> {
252    fn into_vcl(self, ws: &mut Workspace) -> Result<VCL_PROBE, VclError> {
253        into_vcl_probe(self, ws)
254    }
255}
256impl IntoVCL<VCL_PROBE> for Probe {
257    fn into_vcl(self, ws: &mut Workspace) -> Result<VCL_PROBE, VclError> {
258        into_vcl_probe(self, ws)
259    }
260}
261impl From<VCL_PROBE> for Option<CowProbe<'_>> {
262    fn from(value: VCL_PROBE) -> Self {
263        from_vcl_probe(value)
264    }
265}
266impl From<VCL_PROBE> for Option<Probe> {
267    fn from(value: VCL_PROBE) -> Self {
268        from_vcl_probe(value)
269    }
270}
271
272//
273// VCL_REAL
274//
275into_vcl_using_from!(f64, VCL_REAL);
276from_rust_to_vcl!(f64, VCL_REAL);
277from_vcl_to_opt_rust!(VCL_REAL, f64);
278impl From<VCL_REAL> for f64 {
279    fn from(b: VCL_REAL) -> Self {
280        b.0
281    }
282}
283
284//
285// VCL_STRING
286//
287default_null_ptr!(VCL_STRING);
288impl IntoVCL<VCL_STRING> for &str {
289    fn into_vcl(self, ws: &mut Workspace) -> Result<VCL_STRING, VclError> {
290        Ok(VCL_STRING(ws.copy_bytes_with_null(self.as_bytes())?.b))
291    }
292}
293impl IntoVCL<VCL_STRING> for &CStr {
294    fn into_vcl(self, ws: &mut Workspace) -> Result<VCL_STRING, VclError> {
295        ws.copy_cstr(self)
296    }
297}
298impl IntoVCL<VCL_STRING> for &Cow<'_, str> {
299    fn into_vcl(self, ws: &mut Workspace) -> Result<VCL_STRING, VclError> {
300        Ok(VCL_STRING(ws.copy_bytes_with_null(self.as_bytes())?.b))
301    }
302}
303impl IntoVCL<VCL_STRING> for String {
304    fn into_vcl(self, ws: &mut Workspace) -> Result<VCL_STRING, VclError> {
305        self.as_str().into_vcl(ws)
306    }
307}
308impl<T: IntoVCL<VCL_STRING>> IntoVCL<VCL_STRING> for Option<T> {
309    fn into_vcl(self, ws: &mut Workspace) -> Result<VCL_STRING, VclError> {
310        match self {
311            None => Ok(VCL_STRING(null())),
312            Some(t) => t.into_vcl(ws),
313        }
314    }
315}
316impl From<VCL_STRING> for Option<&CStr> {
317    fn from(value: VCL_STRING) -> Self {
318        if value.0.is_null() {
319            None
320        } else {
321            Some(unsafe { CStr::from_ptr(value.0) })
322        }
323    }
324}
325impl From<VCL_STRING> for &CStr {
326    fn from(value: VCL_STRING) -> Self {
327        // Treat a null pointer as an empty string
328        <Option<&CStr>>::from(value).unwrap_or_default()
329    }
330}
331impl TryFrom<VCL_STRING> for Option<&str> {
332    type Error = VclError;
333    fn try_from(value: VCL_STRING) -> Result<Self, Self::Error> {
334        Ok(<Option<&CStr>>::from(value).map(CStr::to_str).transpose()?)
335    }
336}
337impl<'a> TryFrom<VCL_STRING> for &'a str {
338    type Error = VclError;
339    fn try_from(value: VCL_STRING) -> Result<Self, Self::Error> {
340        Ok(<Option<&'a str>>::try_from(value)?.unwrap_or(""))
341    }
342}
343
344// VCL_STEVEDORE
345default_null_ptr!(VCL_STEVEDORE);
346// VCL_STRANDS
347default_null_ptr!(VCL_STRANDS);
348
349//
350// VCL_TIME
351//
352impl From<VCL_TIME> for SystemTime {
353    fn from(value: VCL_TIME) -> Self {
354        // seconds are stored in `VCL_TIME(vtim_real(f64))`
355        let secs = value.0 .0;
356
357        // Reject NaN/Inf and out-of-range values by falling back to UNIX_EPOCH.
358        if !secs.is_finite() {
359            return UNIX_EPOCH;
360        }
361
362        if secs >= 0.0 {
363            Duration::try_from_secs_f64(secs)
364                .ok()
365                .and_then(|dur| UNIX_EPOCH.checked_add(dur))
366                .unwrap_or(UNIX_EPOCH)
367        } else {
368            // Allow times before UNIX_EPOCH by subtracting the positive duration.
369            Duration::try_from_secs_f64(-secs)
370                .ok()
371                .and_then(|dur| UNIX_EPOCH.checked_sub(dur))
372                .unwrap_or(UNIX_EPOCH)
373        }
374    }
375}
376
377impl IntoVCL<VCL_TIME> for SystemTime {
378    fn into_vcl(self, _: &mut Workspace) -> Result<VCL_TIME, VclError> {
379        self.try_into()
380    }
381}
382
383impl TryFrom<SystemTime> for VCL_TIME {
384    type Error = VclError;
385
386    fn try_from(value: SystemTime) -> Result<Self, Self::Error> {
387        Ok(VCL_TIME(vtim_real(
388            value
389                .duration_since(SystemTime::UNIX_EPOCH)
390                .map_err(|e| VclError::new(e.to_string()))?
391                .as_secs_f64(),
392        )))
393    }
394}
395
396// VCL_VCL
397default_null_ptr!(mut VCL_VCL);
398
399// VCL_BACKEND
400default_null_ptr!(VCL_BACKEND);
401
402use std::ffi::c_void;
403use std::num::NonZeroUsize;
404use std::ptr;
405
406impl IntoVCL<VCL_BACKEND> for BackendRef {
407    fn into_vcl(self, _: &mut Workspace) -> Result<VCL_BACKEND, VclError> {
408        unsafe { Ok(self.vcl_ptr()) }
409    }
410}
411
412impl IntoVCL<VCL_BACKEND> for Option<BackendRef> {
413    fn into_vcl(self, _: &mut Workspace) -> Result<VCL_BACKEND, VclError> {
414        unsafe { Ok(self.map_or(VCL_BACKEND(null()), |b: BackendRef| b.vcl_ptr())) }
415    }
416}
417
418impl From<VCL_BACKEND> for Option<BackendRef> {
419    fn from(value: VCL_BACKEND) -> Self {
420        unsafe { BackendRef::new(value) }
421    }
422}
423
424// VCL_SUB
425default_null_ptr!(VCL_SUB);
426impl From<VCL_SUB> for Subroutine {
427    fn from(value: VCL_SUB) -> Self {
428        assert!(!value.0.is_null(), "VCL_SUB must not be null");
429        Subroutine(value)
430    }
431}
432
433impl IntoVCL<VCL_SUB> for Subroutine {
434    fn into_vcl(self, _: &mut Workspace) -> Result<VCL_SUB, VclError> {
435        Ok(self.vcl_ptr())
436    }
437}
438
439default_null_ptr!(VCL_REGEX);
440
441unsafe fn write_ip_to_ptr(ip: SocketAddr, p: *mut c_void) {
442    match ip {
443        SocketAddr::V4(sa) => {
444            assert!(!VSA_BuildFAP(
445                p,
446                PF_INET as sa_family_t,
447                sa.ip().octets().as_slice().as_ptr().cast::<c_void>(),
448                4,
449                ptr::from_ref::<u16>(&sa.port().to_be()).cast::<c_void>(),
450                2
451            )
452            .is_null());
453        }
454        SocketAddr::V6(sa) => {
455            assert!(!VSA_BuildFAP(
456                p,
457                PF_INET6 as sa_family_t,
458                sa.ip().octets().as_slice().as_ptr().cast::<c_void>(),
459                16,
460                ptr::from_ref::<u16>(&sa.port().to_be()).cast::<c_void>(),
461                2
462            )
463            .is_null());
464        }
465    }
466}
467
468pub(crate) unsafe fn write_ip_to_buf(ip: SocketAddr, buf: &mut [u8]) {
469    assert_eq!(buf.len(), vsa_suckaddr_len);
470    write_ip_to_ptr(ip, buf.as_mut_ptr().cast::<c_void>());
471}
472impl IntoVCL<VCL_IP> for SocketAddr {
473    fn into_vcl(self, ws: &mut Workspace) -> Result<VCL_IP, VclError> {
474        unsafe {
475            // We cannot use sizeof::<suckaddr>() because suckaddr is a zero-sized
476            // struct from Rust's perspective
477            let size =
478                NonZeroUsize::new(vsa_suckaddr_len).expect("vsa_suckaddr_len must be non-zero");
479            let p = ws.alloc(size);
480            if p.is_null() {
481                Err(VclError::WsOutOfMemory(size))?;
482            }
483
484            let buf = std::slice::from_raw_parts_mut(p.cast::<u8>(), vsa_suckaddr_len);
485            write_ip_to_buf(self, buf);
486
487            Ok(VCL_IP(p.cast()))
488        }
489    }
490}