viceroy_lib/
wiggle_abi.rs

1// a few things to ignore from the `from_witx!` macro-generated code:
2#![allow(clippy::too_many_arguments)]
3#![allow(clippy::derive_partial_eq_without_eq)]
4
5//! Wiggle implementations for the Compute ABI.
6//
7// Future maintainers wishing to peer into the code generated by theses macros can do so by running
8// `cargo expand --package viceroy-lib wiggle_abi` in their shell, from the root of the `Viceroy`
9// project. Alternatively, you can run `make doc-dev` from the root of the `Viceroy` project to
10// build documentation which includes the code generated by Wiggle, and then open it in your
11// browser.
12
13pub use self::dictionary_impl::DictionaryError;
14pub use self::secret_store_impl::SecretStoreError;
15
16pub use self::device_detection_impl::DeviceDetectionError;
17
18use {
19    self::{
20        fastly_abi::FastlyAbi,
21        types::{FastlyStatus, UserErrorConversion},
22    },
23    crate::{error::Error, session::Session},
24    tracing::{event, Level},
25    wiggle::{GuestErrorType, GuestMemory, GuestPtr},
26};
27
28pub const ABI_VERSION: u64 = 1;
29
30/// Wrapper macro to recover the pre-Wiggle behavior where multi-value hostcalls would write default
31/// outputs in case of failure.
32///
33/// This definition must appear above `mod req_impl` and `mod resp_impl` so that the macro
34/// is in scope in those modules.
35//
36// TODO ACF 2020-06-29: this lets us avoid ABI breakage for the moment, but the next time we need
37// to break the ABI, we should revisit whether we want to keep this behavior.
38macro_rules! multi_value_result {
39    ( $memory:ident, $expr:expr, $ending_cursor_out:expr ) => {{
40        let res = $expr;
41        let ec = res.as_ref().unwrap_or(&(-1));
42        // the previous implementation would only write these if they were null
43        if $ending_cursor_out.offset() != 0 {
44            $memory.write($ending_cursor_out, *ec)?;
45        }
46        let _ = res?;
47        Ok(())
48    }};
49}
50
51mod acl;
52mod backend_impl;
53mod body_impl;
54mod cache;
55mod compute_runtime;
56mod config_store;
57mod device_detection_impl;
58mod dictionary_impl;
59mod entity;
60mod erl_impl;
61mod fastly_purge_impl;
62mod geo_impl;
63mod headers;
64mod http_cache;
65mod http_downstream;
66mod image_optimizer;
67mod kv_store_impl;
68mod log_impl;
69mod obj_store_impl;
70mod req_impl;
71mod resp_impl;
72mod secret_store_impl;
73mod shielding;
74mod uap_impl;
75
76// Expand the `.witx` interface definition into a collection of modules. The `types` module will
77// contain all of the `typename`'s defined in the `witx` file, and other modules will export traits
78// that *must* be implemented by our `ctx` type. See the `from_witx` documentation for more.
79wiggle::from_witx!({
80    witx: ["wasm_abi/compute-at-edge-abi/compute-at-edge.witx"],
81    errors: { fastly_status => Error },
82    async: {
83        fastly_acl::lookup,
84        fastly_async_io::{select},
85        fastly_object_store::{delete_async, pending_delete_wait, insert, insert_async, pending_insert_wait, lookup_async, pending_lookup_wait, list},
86        fastly_kv_store::{lookup, lookup_wait, lookup_wait_v2, insert, insert_wait, delete, delete_wait, list, list_wait},
87        fastly_http_body::{append, read, write},
88        fastly_http_cache::{lookup, transaction_lookup, insert, transaction_insert, transaction_insert_and_stream_back, transaction_update, transaction_update_and_return_fresh, transaction_record_not_cacheable, transaction_abandon, found, close, get_suggested_backend_request, get_suggested_cache_options, prepare_response_for_storage, get_found_response, get_state, get_length, get_max_age_ns, get_stale_while_revalidate_ns, get_age_ns, get_hits, get_sensitive_data, get_surrogate_keys, get_vary_rule},
89        fastly_cache::{ cache_busy_handle_wait, close, close_busy, get_age_ns, get_body, get_hits, get_length, get_max_age_ns, get_stale_while_revalidate_ns, get_state, get_user_metadata, insert, lookup, replace, replace_get_age_ns, replace_get_body, replace_get_hits, replace_get_length, replace_get_max_age_ns, replace_get_stale_while_revalidate_ns, replace_get_state, replace_get_user_metadata, replace_insert, transaction_cancel, transaction_insert, transaction_insert_and_stream_back, transaction_lookup, transaction_lookup_async, transaction_update },
90        fastly_http_downstream::{next_request, next_request_abandon, next_request_wait},
91        fastly_http_req::{
92            pending_req_select, pending_req_select_v2, pending_req_poll, pending_req_poll_v2,
93            pending_req_wait, pending_req_wait_v2, send, send_v2, send_v3, send_async, send_async_v2, send_async_streaming,
94        },
95        fastly_image_optimizer::transform_image_optimizer_request,
96    }
97});
98
99impl From<types::ObjectStoreHandle> for types::KvStoreHandle {
100    fn from(h: types::ObjectStoreHandle) -> types::KvStoreHandle {
101        let s = unsafe { h.inner() };
102        s.into()
103    }
104}
105
106impl From<types::KvStoreHandle> for types::ObjectStoreHandle {
107    fn from(h: types::KvStoreHandle) -> types::ObjectStoreHandle {
108        let s = unsafe { h.inner() };
109        s.into()
110    }
111}
112
113impl From<types::KvStoreLookupHandle> for types::PendingKvLookupHandle {
114    fn from(h: types::KvStoreLookupHandle) -> types::PendingKvLookupHandle {
115        let s = unsafe { h.inner() };
116        s.into()
117    }
118}
119
120impl From<types::PendingKvLookupHandle> for types::KvStoreLookupHandle {
121    fn from(h: types::PendingKvLookupHandle) -> types::KvStoreLookupHandle {
122        let s = unsafe { h.inner() };
123        s.into()
124    }
125}
126
127impl From<types::KvStoreInsertHandle> for types::PendingKvInsertHandle {
128    fn from(h: types::KvStoreInsertHandle) -> types::PendingKvInsertHandle {
129        let s = unsafe { h.inner() };
130        s.into()
131    }
132}
133
134impl From<types::PendingKvInsertHandle> for types::KvStoreInsertHandle {
135    fn from(h: types::PendingKvInsertHandle) -> types::KvStoreInsertHandle {
136        let s = unsafe { h.inner() };
137        s.into()
138    }
139}
140
141impl From<types::KvStoreDeleteHandle> for types::PendingKvDeleteHandle {
142    fn from(h: types::KvStoreDeleteHandle) -> types::PendingKvDeleteHandle {
143        let s = unsafe { h.inner() };
144        s.into()
145    }
146}
147
148impl From<types::PendingKvDeleteHandle> for types::KvStoreDeleteHandle {
149    fn from(h: types::PendingKvDeleteHandle) -> types::KvStoreDeleteHandle {
150        let s = unsafe { h.inner() };
151        s.into()
152    }
153}
154
155impl From<types::KvStoreListHandle> for types::PendingKvListHandle {
156    fn from(h: types::KvStoreListHandle) -> types::PendingKvListHandle {
157        let s = unsafe { h.inner() };
158        s.into()
159    }
160}
161
162impl From<types::PendingKvListHandle> for types::KvStoreListHandle {
163    fn from(h: types::PendingKvListHandle) -> types::KvStoreListHandle {
164        let s = unsafe { h.inner() };
165        s.into()
166    }
167}
168
169impl From<types::HttpVersion> for http::version::Version {
170    fn from(v: types::HttpVersion) -> http::version::Version {
171        match v {
172            types::HttpVersion::Http09 => http::version::Version::HTTP_09,
173            types::HttpVersion::Http10 => http::version::Version::HTTP_10,
174            types::HttpVersion::Http11 => http::version::Version::HTTP_11,
175            types::HttpVersion::H2 => http::version::Version::HTTP_2,
176            types::HttpVersion::H3 => http::version::Version::HTTP_3,
177        }
178    }
179}
180
181// The http crate's `Version` is a struct that has a bunch of
182// associated constants, not an enum; this is only a partial conversion.
183impl TryFrom<http::version::Version> for types::HttpVersion {
184    type Error = &'static str;
185    fn try_from(v: http::version::Version) -> Result<Self, Self::Error> {
186        match v {
187            http::version::Version::HTTP_09 => Ok(types::HttpVersion::Http09),
188            http::version::Version::HTTP_10 => Ok(types::HttpVersion::Http10),
189            http::version::Version::HTTP_11 => Ok(types::HttpVersion::Http11),
190            http::version::Version::HTTP_2 => Ok(types::HttpVersion::H2),
191            http::version::Version::HTTP_3 => Ok(types::HttpVersion::H3),
192            _ => Err("unknown http::version::Version"),
193        }
194    }
195}
196
197impl FastlyAbi for Session {
198    fn init(&mut self, _memory: &mut GuestMemory<'_>, abi_version: u64) -> Result<(), Error> {
199        if abi_version != ABI_VERSION {
200            Err(Error::AbiVersionMismatch)
201        } else {
202            Ok(())
203        }
204    }
205}
206
207impl UserErrorConversion for Session {
208    fn fastly_status_from_error(&mut self, e: Error) -> Result<FastlyStatus, anyhow::Error> {
209        match e {
210            Error::UnknownBackend(ref backend) => {
211                let config_path = self.config_path();
212                let backends_buffer = itertools::join(self.backend_names(), ",");
213                let backends_len = self.backend_names().count();
214
215                match (backends_len, config_path) {
216                    (_, None) => event!(
217                        Level::WARN,
218                        "Attempted to access backend '{}', but no manifest file was provided to define backends. \
219                        Specify a file with -C <TOML_FILE>.",
220                        backend,
221                    ),
222                    (0, Some(config_path)) => event!(
223                        Level::WARN,
224                        "Attempted to access backend '{}', but no backends were defined in the {} manifest file.",
225                        backend,
226                        config_path.display()
227                    ),
228                    (_, Some(config_path)) => event!(
229                        Level::WARN,
230                        "Backend '{}' does not exist. Currently defined backends are: {}. \
231                        To define additional backends, add them to your {} file.",
232                        backend,
233                        backends_buffer,
234                        config_path.display(),
235                    ),
236                }
237            }
238            Error::DictionaryError(ref err) => match err {
239                DictionaryError::UnknownDictionaryItem(_) => {
240                    event!(Level::DEBUG, "Hostcall yielded an error: {}", err);
241                }
242                DictionaryError::UnknownDictionary(_) => {
243                    event!(Level::DEBUG, "Hostcall yielded an error: {}", err);
244                }
245            },
246            _ => event!(Level::DEBUG, "Hostcall yielded an error: {}", e),
247        }
248
249        match e {
250            // If a Fatal Error was encountered, propagate the error message out.
251            Error::FatalError(msg) => Err(anyhow::Error::new(Error::FatalError(msg))),
252            // Propagate the actionable error to the guest.
253            _ => Ok(e.to_fastly_status()),
254        }
255    }
256}
257
258impl GuestErrorType for FastlyStatus {
259    fn success() -> Self {
260        FastlyStatus::Ok
261    }
262}
263
264pub(crate) trait MultiValueWriter {
265    fn write_values(
266        &mut self,
267        memory: &mut GuestMemory<'_>,
268        terminator: u8,
269        buf: GuestPtr<[u8]>,
270        cursor: types::MultiValueCursor,
271        nwritten_out: GuestPtr<u32>,
272    ) -> Result<types::MultiValueCursorResult, Error>;
273}
274
275impl<I, T> MultiValueWriter for I
276where
277    I: Iterator<Item = T>,
278    T: AsRef<[u8]>,
279{
280    #[allow(clippy::useless_conversion)] // numeric conversations that may vary by platform
281    fn write_values(
282        &mut self,
283        memory: &mut GuestMemory<'_>,
284        terminator: u8,
285        buf: GuestPtr<[u8]>,
286        cursor: types::MultiValueCursor,
287        nwritten_out: GuestPtr<u32>,
288    ) -> Result<types::MultiValueCursorResult, Error> {
289        let buf = memory.as_slice_mut(buf)?.ok_or(Error::SharedMemory)?;
290
291        // Note: the prior implementation multi_value_writer would first
292        // terminate the buffer, write -1 to the ending cursor, and zero the nwritten
293        // pointer. The latter two aren't possible under the wiggle model, and the
294        // guest code doesn't inspect any if the Result is not OK. Therefore,
295        // those steps are elided in this implementation.
296
297        let mut cursor = u32::from(cursor) as usize;
298
299        let mut buf_offset = 0;
300        let mut finished = true;
301
302        for value in self.skip(cursor) {
303            let value_bytes = value.as_ref();
304            let value_len = value_bytes.len();
305            let value_len_with_term = value_len + 1;
306            match buf.get_mut(buf_offset..buf_offset + value_len_with_term) {
307                None => {
308                    if buf_offset == 0 {
309                        // If there's not enough room to write even a single value, that's an error.
310                        // Write out the number of bytes necessary to fit this header value, or zero
311                        // on overflow to signal an error condition.
312                        memory.write(nwritten_out, value_len_with_term.try_into().unwrap_or(0))?;
313                        return Err(Error::BufferLengthError {
314                            buf: "buf",
315                            len: "buf.len()",
316                        });
317                    }
318                    // out of room, stop copying
319                    finished = false;
320                    break;
321                }
322                Some(dest) => {
323                    if dest.len() < value_len_with_term {
324                        // out of room, stop copying
325                        finished = false;
326                        break;
327                    }
328                    // copy the header bytes first
329                    dest[..value_len].copy_from_slice(value_bytes);
330                    // then add the terminating byte
331                    dest[value_len] = terminator;
332                    // now that the copy has succeeded, we update the cursor and the offset.
333                    cursor = if let Some(cursor) = cursor.checked_add(1) {
334                        cursor
335                    } else {
336                        return Err(Error::FatalError(
337                            "multi_value_writer cursor overflowed".to_owned(),
338                        ));
339                    };
340                    buf_offset += value_len_with_term;
341                }
342            }
343        }
344
345        let ending_cursor = if finished {
346            types::MultiValueCursorResult::from(-1i64)
347        } else {
348            types::MultiValueCursorResult::from(cursor as i64)
349        };
350
351        memory.write(nwritten_out, buf_offset.try_into().unwrap_or(0))?;
352
353        Ok(ending_cursor)
354    }
355}