// a few things to ignore from the `from_witx!` macro-generated code:
#![allow(clippy::too_many_arguments)]
#![allow(clippy::derive_partial_eq_without_eq)]
//! Wiggle implementations for the Compute ABI.
//
// Future maintainers wishing to peer into the code generated by theses macros can do so by running
// `cargo expand --package viceroy-lib wiggle_abi` in their shell, from the root of the `Viceroy`
// project. Alternatively, you can run `make doc-dev` from the root of the `Viceroy` project to
// build documentation which includes the code generated by Wiggle, and then open it in your
// browser.
pub use self::dictionary_impl::DictionaryError;
pub use self::secret_store_impl::SecretStoreError;
pub use self::device_detection_impl::DeviceDetectionError;
use {
self::{
fastly_abi::FastlyAbi,
types::{FastlyStatus, UserErrorConversion},
},
crate::{error::Error, session::Session},
tracing::{Level, event},
wiggle::{GuestErrorType, GuestMemory, GuestPtr},
};
pub const ABI_VERSION: u64 = 1;
/// Wrapper macro to recover the pre-Wiggle behavior where multi-value hostcalls would write default
/// outputs in case of failure.
///
/// This definition must appear above `mod req_impl` and `mod resp_impl` so that the macro
/// is in scope in those modules.
//
// TODO ACF 2020-06-29: this lets us avoid ABI breakage for the moment, but the next time we need
// to break the ABI, we should revisit whether we want to keep this behavior.
macro_rules! multi_value_result {
( $memory:ident, $expr:expr, $ending_cursor_out:expr ) => {{
let res = $expr;
let ec = res.as_ref().unwrap_or(&(-1));
// the previous implementation would only write these if they were null
if $ending_cursor_out.offset() != 0 {
$memory.write($ending_cursor_out, *ec)?;
}
let _ = res?;
Ok(())
}};
}
mod acl;
mod backend_impl;
mod body_impl;
mod cache;
mod compute_runtime;
mod config_store;
mod device_detection_impl;
mod dictionary_impl;
mod entity;
mod erl_impl;
mod fastly_purge_impl;
mod geo_impl;
mod headers;
mod http_cache;
mod http_downstream;
mod image_optimizer;
mod kv_store_impl;
mod log_impl;
mod obj_store_impl;
mod req_impl;
mod resp_impl;
mod secret_store_impl;
mod shielding;
mod uap_impl;
// Expand the `.witx` interface definition into a collection of modules. The `types` module will
// contain all of the `typename`'s defined in the `witx` file, and other modules will export traits
// that *must* be implemented by our `ctx` type. See the `from_witx` documentation for more.
wiggle::from_witx!({
witx: ["wasm_abi/compute-at-edge-abi/compute-at-edge.witx"],
errors: { fastly_status => Error },
async: {
fastly_acl::lookup,
fastly_async_io::{select},
fastly_object_store::{delete_async, pending_delete_wait, insert, insert_async, pending_insert_wait, lookup_async, pending_lookup_wait, list},
fastly_kv_store::{lookup, lookup_wait, lookup_wait_v2, insert, insert_wait, delete, delete_wait, list, list_wait},
fastly_http_body::{append, read, write},
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, transaction_choose_stale, 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_stale_if_error_ns, get_age_ns, get_hits, get_sensitive_data, get_surrogate_keys, get_vary_rule},
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 },
fastly_http_downstream::{next_request, next_request_abandon, next_request_wait},
fastly_http_req::{
pending_req_select, pending_req_select_v2, pending_req_poll, pending_req_poll_v2,
pending_req_wait, pending_req_wait_v2, send, send_v2, send_v3, send_async, send_async_v2, send_async_streaming,
},
fastly_image_optimizer::transform_image_optimizer_request,
}
});
impl From<types::ObjectStoreHandle> for types::KvStoreHandle {
fn from(h: types::ObjectStoreHandle) -> types::KvStoreHandle {
let s = unsafe { h.inner() };
s.into()
}
}
impl From<types::KvStoreHandle> for types::ObjectStoreHandle {
fn from(h: types::KvStoreHandle) -> types::ObjectStoreHandle {
let s = unsafe { h.inner() };
s.into()
}
}
impl From<types::KvStoreLookupHandle> for types::PendingKvLookupHandle {
fn from(h: types::KvStoreLookupHandle) -> types::PendingKvLookupHandle {
let s = unsafe { h.inner() };
s.into()
}
}
impl From<types::PendingKvLookupHandle> for types::KvStoreLookupHandle {
fn from(h: types::PendingKvLookupHandle) -> types::KvStoreLookupHandle {
let s = unsafe { h.inner() };
s.into()
}
}
impl From<types::KvStoreInsertHandle> for types::PendingKvInsertHandle {
fn from(h: types::KvStoreInsertHandle) -> types::PendingKvInsertHandle {
let s = unsafe { h.inner() };
s.into()
}
}
impl From<types::PendingKvInsertHandle> for types::KvStoreInsertHandle {
fn from(h: types::PendingKvInsertHandle) -> types::KvStoreInsertHandle {
let s = unsafe { h.inner() };
s.into()
}
}
impl From<types::KvStoreDeleteHandle> for types::PendingKvDeleteHandle {
fn from(h: types::KvStoreDeleteHandle) -> types::PendingKvDeleteHandle {
let s = unsafe { h.inner() };
s.into()
}
}
impl From<types::PendingKvDeleteHandle> for types::KvStoreDeleteHandle {
fn from(h: types::PendingKvDeleteHandle) -> types::KvStoreDeleteHandle {
let s = unsafe { h.inner() };
s.into()
}
}
impl From<types::KvStoreListHandle> for types::PendingKvListHandle {
fn from(h: types::KvStoreListHandle) -> types::PendingKvListHandle {
let s = unsafe { h.inner() };
s.into()
}
}
impl From<types::PendingKvListHandle> for types::KvStoreListHandle {
fn from(h: types::PendingKvListHandle) -> types::KvStoreListHandle {
let s = unsafe { h.inner() };
s.into()
}
}
impl From<types::HttpVersion> for http::version::Version {
fn from(v: types::HttpVersion) -> http::version::Version {
match v {
types::HttpVersion::Http09 => http::version::Version::HTTP_09,
types::HttpVersion::Http10 => http::version::Version::HTTP_10,
types::HttpVersion::Http11 => http::version::Version::HTTP_11,
types::HttpVersion::H2 => http::version::Version::HTTP_2,
types::HttpVersion::H3 => http::version::Version::HTTP_3,
}
}
}
// The http crate's `Version` is a struct that has a bunch of
// associated constants, not an enum; this is only a partial conversion.
impl TryFrom<http::version::Version> for types::HttpVersion {
type Error = &'static str;
fn try_from(v: http::version::Version) -> Result<Self, Self::Error> {
match v {
http::version::Version::HTTP_09 => Ok(types::HttpVersion::Http09),
http::version::Version::HTTP_10 => Ok(types::HttpVersion::Http10),
http::version::Version::HTTP_11 => Ok(types::HttpVersion::Http11),
http::version::Version::HTTP_2 => Ok(types::HttpVersion::H2),
http::version::Version::HTTP_3 => Ok(types::HttpVersion::H3),
_ => Err("unknown http::version::Version"),
}
}
}
impl FastlyAbi for Session {
fn init(&mut self, _memory: &mut GuestMemory<'_>, abi_version: u64) -> Result<(), Error> {
if abi_version != ABI_VERSION {
Err(Error::AbiVersionMismatch)
} else {
Ok(())
}
}
}
impl UserErrorConversion for Session {
fn fastly_status_from_error(&mut self, e: Error) -> Result<FastlyStatus, anyhow::Error> {
match e {
Error::UnknownBackend(ref backend) => {
let config_path = self.config_path();
let backends_buffer = itertools::join(self.backend_names(), ",");
let backends_len = self.backend_names().count();
match (backends_len, config_path) {
(_, None) => event!(
Level::WARN,
"Attempted to access backend '{}', but no manifest file was provided to define backends. \
Specify a file with -C <TOML_FILE>.",
backend,
),
(0, Some(config_path)) => event!(
Level::WARN,
"Attempted to access backend '{}', but no backends were defined in the {} manifest file.",
backend,
config_path.display()
),
(_, Some(config_path)) => event!(
Level::WARN,
"Backend '{}' does not exist. Currently defined backends are: {}. \
To define additional backends, add them to your {} file.",
backend,
backends_buffer,
config_path.display(),
),
}
}
Error::DictionaryError(ref err) => match err {
DictionaryError::UnknownDictionaryItem(_) => {
event!(Level::DEBUG, "Hostcall yielded an error: {}", err);
}
DictionaryError::UnknownDictionary(_) => {
event!(Level::DEBUG, "Hostcall yielded an error: {}", err);
}
},
_ => event!(Level::DEBUG, "Hostcall yielded an error: {}", e),
}
match e {
// If a Fatal Error was encountered, propagate the error message out.
Error::FatalError(msg) => Err(anyhow::Error::new(Error::FatalError(msg))),
// Propagate the actionable error to the guest.
_ => Ok(e.to_fastly_status()),
}
}
}
impl GuestErrorType for FastlyStatus {
fn success() -> Self {
FastlyStatus::Ok
}
}
pub(crate) trait MultiValueWriter {
fn write_values(
&mut self,
memory: &mut GuestMemory<'_>,
terminator: u8,
buf: GuestPtr<[u8]>,
cursor: types::MultiValueCursor,
nwritten_out: GuestPtr<u32>,
) -> Result<types::MultiValueCursorResult, Error>;
}
impl<I, T> MultiValueWriter for I
where
I: Iterator<Item = T>,
T: AsRef<[u8]>,
{
#[allow(clippy::useless_conversion)] // numeric conversations that may vary by platform
fn write_values(
&mut self,
memory: &mut GuestMemory<'_>,
terminator: u8,
buf: GuestPtr<[u8]>,
cursor: types::MultiValueCursor,
nwritten_out: GuestPtr<u32>,
) -> Result<types::MultiValueCursorResult, Error> {
let buf = memory.as_slice_mut(buf)?.ok_or(Error::SharedMemory)?;
// Note: the prior implementation multi_value_writer would first
// terminate the buffer, write -1 to the ending cursor, and zero the nwritten
// pointer. The latter two aren't possible under the wiggle model, and the
// guest code doesn't inspect any if the Result is not OK. Therefore,
// those steps are elided in this implementation.
let mut cursor = u32::from(cursor) as usize;
let mut buf_offset = 0;
let mut finished = true;
for value in self.skip(cursor) {
let value_bytes = value.as_ref();
let value_len = value_bytes.len();
let value_len_with_term = value_len + 1;
match buf.get_mut(buf_offset..buf_offset + value_len_with_term) {
None => {
if buf_offset == 0 {
// If there's not enough room to write even a single value, that's an error.
// Write out the number of bytes necessary to fit this header value, or zero
// on overflow to signal an error condition.
memory.write(nwritten_out, value_len_with_term.try_into().unwrap_or(0))?;
return Err(Error::BufferLengthError {
buf: "buf",
len: "buf.len()",
});
}
// out of room, stop copying
finished = false;
break;
}
Some(dest) => {
if dest.len() < value_len_with_term {
// out of room, stop copying
finished = false;
break;
}
// copy the header bytes first
dest[..value_len].copy_from_slice(value_bytes);
// then add the terminating byte
dest[value_len] = terminator;
// now that the copy has succeeded, we update the cursor and the offset.
cursor = if let Some(cursor) = cursor.checked_add(1) {
cursor
} else {
return Err(Error::FatalError(
"multi_value_writer cursor overflowed".to_owned(),
));
};
buf_offset += value_len_with_term;
}
}
}
let ending_cursor = if finished {
types::MultiValueCursorResult::from(-1i64)
} else {
types::MultiValueCursorResult::from(cursor as i64)
};
memory.write(nwritten_out, buf_offset.try_into().unwrap_or(0))?;
Ok(ending_cursor)
}
}