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//! | `&str` | <-> | `VCL_STRING` |
27//! | `String` | -> | `VCL_STRING` |
28//! | `Option<CowProbe>` | <-> | `VCL_PROBE` |
29//! | `Option<Probe>` | <-> | `VCL_PROBE` |
30//! | `Option<std::net::SockAdd>` | -> | `VCL_IP` |
31//!
32//! For all the other types, which are pointers, you will need to use the native types.
33//!
34//! *Note:* It is possible to simply return a `VCL_*` type (or a Result<VCL_*, _>), in which case
35//! the boilerplate will just skip the conversion.
36//!
37//! # Result
38//!
39//! It's possible for a vmod writer to return a bare value, or a `Result<_, E: AsRef<str>>` to
40//! potentially abort VCL processing in case the vmod hit an unrecoverable error.
41//!
42//! If a vmod function returns `Err(msg)`, the boilerplate will log `msg`, mark the current task as
43//! failed and will return a default value to the VCL. In turn, the VCL will stop its processing
44//! and will create a synthetic error object.
45
46use std::borrow::Cow;
47use std::ffi::{c_char, CStr};
48use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
49use std::ptr::{null, null_mut};
50use std::time::{Duration, SystemTime};
51
52use crate::ffi::{
53    http, vtim_dur, vtim_real, VSA_GetPtr, VSA_Port, PF_INET, PF_INET6, VCL_ACL, VCL_BACKEND,
54    VCL_BLOB, VCL_BODY, VCL_BOOL, VCL_DURATION, VCL_ENUM, VCL_HEADER, VCL_HTTP, VCL_INT, VCL_IP,
55    VCL_PROBE, VCL_REAL, VCL_STEVEDORE, VCL_STRANDS, VCL_STRING, VCL_TIME, VCL_VCL,
56};
57use crate::vcl::{from_vcl_probe, into_vcl_probe, CowProbe, Probe, VclError, Workspace};
58
59/// Convert a Rust type into a VCL one
60///
61/// It will use the [`Workspace`] to persist the data during the VCL task if necessary
62pub trait IntoVCL<T> {
63    fn into_vcl(self, ws: &mut Workspace) -> Result<T, VclError>;
64}
65
66macro_rules! default_null_ptr {
67    ($ident:ident) => {
68        default_null_ptr!($ident, null);
69    };
70    (mut $ident:ident) => {
71        default_null_ptr!($ident, null_mut);
72    };
73    ($ident:ident, $func:ident) => {
74        impl Default for $ident {
75            fn default() -> Self {
76                $ident($func())
77            }
78        }
79    };
80}
81
82macro_rules! into_vcl_using_from {
83    ($rust_ty:ty, $vcl_ty:ident) => {
84        impl IntoVCL<$vcl_ty> for $rust_ty {
85            fn into_vcl(self, _: &mut Workspace) -> Result<$vcl_ty, VclError> {
86                Ok(self.into())
87            }
88        }
89    };
90}
91
92macro_rules! from_rust_to_vcl {
93    ($rust_ty:ty, $vcl_ty:ident) => {
94        impl From<$rust_ty> for $vcl_ty {
95            fn from(b: $rust_ty) -> Self {
96                Self(b.into())
97            }
98        }
99    };
100}
101
102macro_rules! from_vcl_to_opt_rust {
103    ($vcl_ty:ident, $rust_ty:ty) => {
104        impl From<$vcl_ty> for Option<$rust_ty> {
105            fn from(b: $vcl_ty) -> Self {
106                Some(b.into())
107            }
108        }
109    };
110}
111
112// VCL_ACL
113default_null_ptr!(VCL_ACL);
114
115// VCL_BACKEND
116default_null_ptr!(VCL_BACKEND);
117
118// VCL_BLOB
119default_null_ptr!(VCL_BLOB);
120
121// VCL_BODY
122default_null_ptr!(VCL_BODY);
123
124//
125// VCL_BOOL
126//
127into_vcl_using_from!(bool, VCL_BOOL);
128from_rust_to_vcl!(bool, VCL_BOOL);
129from_vcl_to_opt_rust!(VCL_BOOL, bool);
130impl From<VCL_BOOL> for bool {
131    fn from(b: VCL_BOOL) -> Self {
132        b.0 != 0
133    }
134}
135
136//
137// VCL_DURATION
138//
139into_vcl_using_from!(Duration, VCL_DURATION);
140from_vcl_to_opt_rust!(VCL_DURATION, Duration);
141impl From<VCL_DURATION> for Duration {
142    fn from(value: VCL_DURATION) -> Self {
143        value.0.into()
144    }
145}
146impl From<Duration> for VCL_DURATION {
147    fn from(value: Duration) -> Self {
148        Self(value.into())
149    }
150}
151
152//
153// vtim_dur -- this is a sub-structure of VCL_DURATION, equal to f64
154//
155impl From<vtim_dur> for Duration {
156    fn from(value: vtim_dur) -> Self {
157        Self::from_secs_f64(value.0)
158    }
159}
160impl From<Duration> for vtim_dur {
161    fn from(value: Duration) -> Self {
162        Self(value.as_secs_f64())
163    }
164}
165
166// VCL_ENUM
167default_null_ptr!(VCL_ENUM);
168// VCL_HEADER
169default_null_ptr!(VCL_HEADER);
170// VCL_HTTP
171default_null_ptr!(mut VCL_HTTP);
172impl From<*mut http> for VCL_HTTP {
173    // This is needed because pre-v7 vrt_ctx used http instead of VCL_HTTP
174    fn from(value: *mut http) -> Self {
175        Self(value)
176    }
177}
178
179//
180// VCL_INT
181//
182into_vcl_using_from!(i64, VCL_INT);
183from_rust_to_vcl!(i64, VCL_INT);
184from_vcl_to_opt_rust!(VCL_INT, i64);
185impl From<VCL_INT> for i64 {
186    fn from(b: VCL_INT) -> Self {
187        b.0
188    }
189}
190
191//
192// VCL_IP
193//
194default_null_ptr!(VCL_IP);
195impl From<VCL_IP> for Option<SocketAddr> {
196    fn from(value: VCL_IP) -> Self {
197        let value = value.0;
198        if value.is_null() {
199            return None;
200        }
201        unsafe {
202            let mut ptr = null();
203            let fam = VSA_GetPtr(value, &raw mut ptr) as u32;
204            let port = VSA_Port(value) as u16;
205
206            match fam {
207                PF_INET => {
208                    let buf: &[u8; 4] = std::slice::from_raw_parts(ptr.cast::<u8>(), 4)
209                        .try_into()
210                        .unwrap();
211                    Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::from(*buf)), port))
212                }
213                PF_INET6 => {
214                    let buf: &[u8; 16] = std::slice::from_raw_parts(ptr.cast::<u8>(), 16)
215                        .try_into()
216                        .unwrap();
217                    Some(SocketAddr::new(IpAddr::V6(Ipv6Addr::from(*buf)), port))
218                }
219                _ => None,
220            }
221        }
222    }
223}
224
225//
226// VCL_PROBE
227//
228default_null_ptr!(VCL_PROBE);
229impl IntoVCL<VCL_PROBE> for CowProbe<'_> {
230    fn into_vcl(self, ws: &mut Workspace) -> Result<VCL_PROBE, VclError> {
231        into_vcl_probe(self, ws)
232    }
233}
234impl IntoVCL<VCL_PROBE> for Probe {
235    fn into_vcl(self, ws: &mut Workspace) -> Result<VCL_PROBE, VclError> {
236        into_vcl_probe(self, ws)
237    }
238}
239impl From<VCL_PROBE> for Option<CowProbe<'_>> {
240    fn from(value: VCL_PROBE) -> Self {
241        from_vcl_probe(value)
242    }
243}
244impl From<VCL_PROBE> for Option<Probe> {
245    fn from(value: VCL_PROBE) -> Self {
246        from_vcl_probe(value)
247    }
248}
249
250//
251// VCL_REAL
252//
253into_vcl_using_from!(f64, VCL_REAL);
254from_rust_to_vcl!(f64, VCL_REAL);
255from_vcl_to_opt_rust!(VCL_REAL, f64);
256impl From<VCL_REAL> for f64 {
257    fn from(b: VCL_REAL) -> Self {
258        b.0
259    }
260}
261
262//
263// VCL_STRING
264//
265default_null_ptr!(VCL_STRING);
266impl IntoVCL<VCL_STRING> for &[u8] {
267    fn into_vcl(self, ws: &mut Workspace) -> Result<VCL_STRING, VclError> {
268        // Try to save some work if the buffer is already in the workspace.
269        // We assume that &[u8] has always been readonly, so workspace data is valid.
270        // However, we need to make sure that the buffer is null-terminated, either by its own last
271        // byte, or by the one after it
272        let with_extra_byte = unsafe { std::slice::from_raw_parts(self.as_ptr(), self.len() + 1) };
273        if (ws.contains(self) && self.ends_with(b"\0"))
274            || (ws.contains(with_extra_byte) && with_extra_byte.last() == Some(&b'\0'))
275        {
276            Ok(VCL_STRING(self.as_ptr().cast::<c_char>()))
277        } else {
278            Ok(VCL_STRING(ws.copy_bytes_with_null(self)?.b))
279        }
280    }
281}
282
283impl IntoVCL<VCL_STRING> for &str {
284    fn into_vcl(self, ws: &mut Workspace) -> Result<VCL_STRING, VclError> {
285        self.as_bytes().into_vcl(ws)
286    }
287}
288impl IntoVCL<VCL_STRING> for &CStr {
289    fn into_vcl(self, ws: &mut Workspace) -> Result<VCL_STRING, VclError> {
290        ws.copy_cstr(self)
291    }
292}
293impl IntoVCL<VCL_STRING> for &Cow<'_, str> {
294    fn into_vcl(self, ws: &mut Workspace) -> Result<VCL_STRING, VclError> {
295        self.as_bytes().into_vcl(ws)
296    }
297}
298impl IntoVCL<VCL_STRING> for String {
299    fn into_vcl(self, ws: &mut Workspace) -> Result<VCL_STRING, VclError> {
300        self.as_str().into_vcl(ws)
301    }
302}
303impl<T: IntoVCL<VCL_STRING> + AsRef<[u8]>> IntoVCL<VCL_STRING> for Option<T> {
304    fn into_vcl(self, ws: &mut Workspace) -> Result<VCL_STRING, VclError> {
305        match self {
306            None => Ok(VCL_STRING(null())),
307            Some(t) => t.as_ref().into_vcl(ws),
308        }
309    }
310}
311impl From<VCL_STRING> for Option<&CStr> {
312    fn from(value: VCL_STRING) -> Self {
313        if value.0.is_null() {
314            None
315        } else {
316            Some(unsafe { CStr::from_ptr(value.0) })
317        }
318    }
319}
320impl From<VCL_STRING> for &CStr {
321    fn from(value: VCL_STRING) -> Self {
322        // Treat a null pointer as an empty string
323        <Option<&CStr>>::from(value).unwrap_or_default()
324    }
325}
326impl TryFrom<VCL_STRING> for Option<&str> {
327    type Error = VclError;
328    fn try_from(value: VCL_STRING) -> Result<Self, Self::Error> {
329        Ok(<Option<&CStr>>::from(value).map(CStr::to_str).transpose()?)
330    }
331}
332impl<'a> TryFrom<VCL_STRING> for &'a str {
333    type Error = VclError;
334    fn try_from(value: VCL_STRING) -> Result<Self, Self::Error> {
335        Ok(<Option<&'a str>>::try_from(value)?.unwrap_or(""))
336    }
337}
338
339// VCL_STEVEDORE
340default_null_ptr!(VCL_STEVEDORE);
341// VCL_STRANDS
342default_null_ptr!(VCL_STRANDS);
343
344//
345// VCL_TIME
346//
347impl IntoVCL<VCL_TIME> for SystemTime {
348    fn into_vcl(self, _: &mut Workspace) -> Result<VCL_TIME, VclError> {
349        self.try_into()
350    }
351}
352impl TryFrom<SystemTime> for VCL_TIME {
353    type Error = VclError;
354
355    fn try_from(value: SystemTime) -> Result<Self, Self::Error> {
356        Ok(VCL_TIME(vtim_real(
357            value
358                .duration_since(SystemTime::UNIX_EPOCH)
359                .map_err(|e| VclError::new(e.to_string()))?
360                .as_secs_f64(),
361        )))
362    }
363}
364
365// VCL_VCL
366default_null_ptr!(mut VCL_VCL);
367
368#[cfg(not(varnishsys_6))]
369mod version_after_v6 {
370    use std::ffi::c_void;
371    use std::net::SocketAddr;
372    use std::num::NonZeroUsize;
373    use std::ptr;
374    use std::ptr::null;
375
376    use super::IntoVCL;
377    use crate::ffi::{
378        sa_family_t, vsa_suckaddr_len, VSA_BuildFAP, PF_INET, PF_INET6, VCL_IP, VCL_REGEX, VCL_SUB,
379    };
380    use crate::vcl::{VclError, Workspace};
381    default_null_ptr!(VCL_SUB);
382
383    default_null_ptr!(VCL_REGEX);
384
385    impl IntoVCL<VCL_IP> for SocketAddr {
386        fn into_vcl(self, ws: &mut Workspace) -> Result<VCL_IP, VclError> {
387            unsafe {
388                // We cannot use sizeof::<suckaddr>() because suckaddr is a zero-sized
389                // struct from Rust's perspective
390                let size = NonZeroUsize::new(vsa_suckaddr_len).unwrap();
391                let p = ws.alloc(size);
392                if p.is_null() {
393                    Err(VclError::WsOutOfMemory(size))?;
394                }
395                match self {
396                    SocketAddr::V4(sa) => {
397                        assert!(!VSA_BuildFAP(
398                            p,
399                            PF_INET as sa_family_t,
400                            sa.ip().octets().as_slice().as_ptr().cast::<c_void>(),
401                            4,
402                            ptr::from_ref::<u16>(&sa.port().to_be()).cast::<c_void>(),
403                            2
404                        )
405                        .is_null());
406                    }
407                    SocketAddr::V6(sa) => {
408                        assert!(!VSA_BuildFAP(
409                            p,
410                            PF_INET6 as sa_family_t,
411                            sa.ip().octets().as_slice().as_ptr().cast::<c_void>(),
412                            16,
413                            ptr::from_ref::<u16>(&sa.port().to_be()).cast::<c_void>(),
414                            2
415                        )
416                        .is_null());
417                    }
418                }
419                Ok(VCL_IP(p.cast()))
420            }
421        }
422    }
423}