From f918a70fb7a577c9f5a3462cc8cd406e9a70516e Mon Sep 17 00:00:00 2001 From: Trevor Elliott Date: Thu, 23 May 2024 10:42:38 -0700 Subject: [PATCH 01/15] Add the component adapter --- Cargo.lock | 130 +- Cargo.toml | 26 + crates/adapter/Cargo.toml | 20 + crates/adapter/build.rs | 318 ++ crates/adapter/byte-array-literals/Cargo.toml | 8 + crates/adapter/byte-array-literals/README.md | 15 + crates/adapter/byte-array-literals/src/lib.rs | 98 + crates/adapter/src/descriptors.rs | 238 ++ crates/adapter/src/fastly/cache.rs | 465 +++ crates/adapter/src/fastly/config_store.rs | 25 + crates/adapter/src/fastly/core.rs | 2808 +++++++++++++++++ crates/adapter/src/fastly/error.rs | 48 + crates/adapter/src/fastly/macros.rs | 23 + crates/adapter/src/fastly/mod.rs | 11 + crates/adapter/src/lib.rs | 1427 +++++++++ crates/adapter/src/macros.rs | 94 + 16 files changed, 5752 insertions(+), 2 deletions(-) create mode 100644 crates/adapter/Cargo.toml create mode 100644 crates/adapter/build.rs create mode 100644 crates/adapter/byte-array-literals/Cargo.toml create mode 100644 crates/adapter/byte-array-literals/README.md create mode 100644 crates/adapter/byte-array-literals/src/lib.rs create mode 100644 crates/adapter/src/descriptors.rs create mode 100644 crates/adapter/src/fastly/cache.rs create mode 100644 crates/adapter/src/fastly/config_store.rs create mode 100644 crates/adapter/src/fastly/core.rs create mode 100644 crates/adapter/src/fastly/error.rs create mode 100644 crates/adapter/src/fastly/macros.rs create mode 100644 crates/adapter/src/fastly/mod.rs create mode 100644 crates/adapter/src/lib.rs create mode 100644 crates/adapter/src/macros.rs diff --git a/Cargo.lock b/Cargo.lock index d1471e5c..8f0d63f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -179,6 +179,10 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "byte-array-literals" +version = "0.0.0" + [[package]] name = "byteorder" version = "1.5.0" @@ -1929,6 +1933,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "spdx" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ef1a0fa1e39ac22972c8db23ff89aea700ab96aa87114e1fb55937a631a0c9" +dependencies = [ + "smallvec", +] + [[package]] name = "spin" version = "0.9.8" @@ -2353,6 +2366,18 @@ dependencies = [ "wat", ] +[[package]] +name = "viceroy-component-adapter" +version = "0.1.0" +dependencies = [ + "bitflags 2.5.0", + "byte-array-literals", + "object 0.33.0", + "wasi", + "wasm-encoder 0.205.0", + "wit-bindgen-rust-macro", +] + [[package]] name = "viceroy-lib" version = "0.9.8" @@ -2474,6 +2499,15 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +[[package]] +name = "wasm-encoder" +version = "0.205.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e95b3563d164f33c1cfb0a7efbd5940c37710019be10cd09f800fdec8b0e5c" +dependencies = [ + "leb128", +] + [[package]] name = "wasm-encoder" version = "0.207.0" @@ -2492,6 +2526,22 @@ dependencies = [ "leb128", ] +[[package]] +name = "wasm-metadata" +version = "0.208.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a2c4280ad374a6db3d76d4bb61e2ec4b3b9ce5469cc4f2bbc5708047a2bbff" +dependencies = [ + "anyhow", + "indexmap", + "serde", + "serde_derive", + "serde_json", + "spdx", + "wasm-encoder 0.208.1", + "wasmparser 0.208.1", +] + [[package]] name = "wasmparser" version = "0.207.0" @@ -2626,7 +2676,7 @@ dependencies = [ "syn", "wasmtime-component-util", "wasmtime-wit-bindgen", - "wit-parser", + "wit-parser 0.207.0", ] [[package]] @@ -2825,7 +2875,7 @@ dependencies = [ "anyhow", "heck 0.4.1", "indexmap", - "wit-parser", + "wit-parser 0.207.0", ] [[package]] @@ -3117,6 +3167,64 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "wit-bindgen-core" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7076a12e69af6e1f6093bd16657d7ae61c30cfd3c5f62321046eb863b17ab1e2" +dependencies = [ + "anyhow", + "heck 0.5.0", + "wit-parser 0.208.1", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8ca0dd2aa75452450da1906391aba9d3a43d95fa920e872361ea00acc452a5" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d734e18bdf439ed86afe9d85fc5bfa38db4b676fc0e0a0dae95bd8f14de039" +dependencies = [ + "anyhow", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.208.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fef7dd0e47f5135dd8739ccc5b188ab8b7e27e1d64df668aa36680f0b8646db8" +dependencies = [ + "anyhow", + "bitflags 2.5.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder 0.208.1", + "wasm-metadata", + "wasmparser 0.208.1", + "wit-parser 0.208.1", +] + [[package]] name = "wit-parser" version = "0.207.0" @@ -3135,6 +3243,24 @@ dependencies = [ "wasmparser 0.207.0", ] +[[package]] +name = "wit-parser" +version = "0.208.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516417a730725fe3e6c9e2efc8d86697480f80496d32b24e62736950704c047c" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver 1.0.23", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser 0.208.1", +] + [[package]] name = "witx" version = "0.9.1" diff --git a/Cargo.toml b/Cargo.toml index d471c4df..ad69ad8d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,8 @@ members = [ "cli", "lib", + "crates/adapter", + "crates/adapter/byte-array-literals", ] resolver = "2" @@ -43,3 +45,27 @@ wasmtime-wasi = "21.0.0" wasmtime-wasi-nn = "21.0.0" wiggle = "21.0.0" wasmparser = "0.208.0" + +# Adapter dependencies +byte-array-literals = { path = "crates/adapter/byte-array-literals" } +bitflags = { version = "2.5.0", default-features = false } +object = { version = "0.33", default-features = false, features = ["archive"] } +wasi = { version = "0.11.0", default-features = false } +wasm-encoder = "0.205.0" +wit-bindgen-rust-macro = { version = "0.25.0", default-features = false } + +[profile.release.package.viceroy-component-adapter] +opt-level = 's' +strip = 'debuginfo' + +[profile.dev.package.viceroy-component-adapter] +# Make dev look like a release build since this adapter module won't work with +# a debug build that uses data segments and such. +incremental = false +opt-level = 's' +# Omit assertions, which include failure messages which require string +# initializers. +debug-assertions = false +# Omit integer overflow checks, which include failure messages which require +# string initializers. +overflow-checks = false diff --git a/crates/adapter/Cargo.toml b/crates/adapter/Cargo.toml new file mode 100644 index 00000000..186e6755 --- /dev/null +++ b/crates/adapter/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "viceroy-component-adapter" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +wasi = { workspace = true } +wit-bindgen-rust-macro = { workspace = true } +byte-array-literals = { workspace = true } +bitflags = { workspace = true } + +[build-dependencies] +wasm-encoder = { workspace = true } +object = { workspace = true } + +[lib] +test = false +crate-type = ["cdylib"] +name = "wasi_snapshot_preview1" diff --git a/crates/adapter/build.rs b/crates/adapter/build.rs new file mode 100644 index 00000000..2762dd6b --- /dev/null +++ b/crates/adapter/build.rs @@ -0,0 +1,318 @@ +use std::env; +use std::path::PathBuf; + +fn main() { + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + + let wasm = build_raw_intrinsics(); + let archive = build_archive(&wasm); + + std::fs::write(out_dir.join("libwasm-raw-intrinsics.a"), &archive).unwrap(); + println!("cargo:rustc-link-lib=static=wasm-raw-intrinsics"); + println!( + "cargo:rustc-link-search=native={}", + out_dir.to_str().unwrap() + ); + + // Some specific flags to `wasm-ld` to inform the shape of this adapter. + // Notably we're importing memory from the main module and additionally our + // own module has no stack at all since it's specifically allocated at + // startup. + println!("cargo:rustc-link-arg=--import-memory"); + println!("cargo:rustc-link-arg=-zstack-size=0"); +} + +/// This function will produce a wasm module which is itself an object file +/// that is the basic equivalent of: +/// +/// ```rust +/// std::arch::global_asm!( +/// " +/// .globaltype internal_state_ptr, i32 +/// internal_state_ptr: +/// " +/// ); +/// +/// #[no_mangle] +/// extern "C" fn get_state_ptr() -> *mut u8 { +/// unsafe { +/// let ret: *mut u8; +/// std::arch::asm!( +/// " +/// global.get internal_state_ptr +/// ", +/// out(local) ret, +/// options(nostack, readonly) +/// ); +/// ret +/// } +/// } +/// +/// #[no_mangle] +/// extern "C" fn set_state_ptr(val: *mut u8) { +/// unsafe { +/// std::arch::asm!( +/// " +/// local.get {} +/// global.set internal_state_ptr +/// ", +/// in(local) val, +/// options(nostack, readonly) +/// ); +/// } +/// } +/// +/// // And likewise for `allocation_state`, `get_allocation_state`, and `set_allocation_state` +/// ``` +/// +/// The main trickiness here is getting the `reloc.CODE` and `linking` sections +/// right. +fn build_raw_intrinsics() -> Vec { + use wasm_encoder::Instruction::*; + use wasm_encoder::*; + + let mut module = Module::new(); + + let mut types = TypeSection::new(); + types.function([], [ValType::I32]); + types.function([ValType::I32], []); + module.section(&types); + + // Declare the functions, using the type we just added. + let mut funcs = FunctionSection::new(); + funcs.function(0); + funcs.function(1); + funcs.function(0); + funcs.function(1); + module.section(&funcs); + + // Declare the globals. + let mut globals = GlobalSection::new(); + // internal_state_ptr + globals.global( + GlobalType { + val_type: ValType::I32, + mutable: true, + shared: false, + }, + &ConstExpr::i32_const(0), + ); + // allocation_state + globals.global( + GlobalType { + val_type: ValType::I32, + mutable: true, + shared: false, + }, + &ConstExpr::i32_const(0), + ); + module.section(&globals); + + // Here the `code` section is defined. This is tricky because an offset is + // needed within the code section itself for the `reloc.CODE` section + // later. At this time `wasm-encoder` doesn't give enough functionality to + // use the high-level APIs. so everything is done manually here. + // + // First the function bodies are created and then they're appended into a + // code section. + + let mut code = Vec::new(); + 4u32.encode(&mut code); // number of functions + + let global_get = 0x23; + let global_set = 0x24; + + let encode = |code: &mut _, global, instruction| { + assert!(global < 0x7F); + + let mut body = Vec::new(); + 0u32.encode(&mut body); // no locals + if instruction == global_set { + LocalGet(0).encode(&mut body); + } + let global_offset = body.len() + 1; + // global.get $global ;; but with maximal encoding of $global + body.extend_from_slice(&[instruction, 0x80u8 + global, 0x80, 0x80, 0x80, 0x00]); + End.encode(&mut body); + body.len().encode(code); // length of the function + let offset = code.len() + global_offset; + code.extend_from_slice(&body); // the function itself + offset + }; + + let internal_state_ptr_ref1 = encode(&mut code, 0, global_get); // get_state_ptr + let internal_state_ptr_ref2 = encode(&mut code, 0, global_set); // set_state_ptr + let allocation_state_ref1 = encode(&mut code, 1, global_get); // get_allocation_state + let allocation_state_ref2 = encode(&mut code, 1, global_set); // set_allocation_state + + module.section(&RawSection { + id: SectionId::Code as u8, + data: &code, + }); + + // Here the linking section is constructed. There is one symbol for each function and global. The injected + // globals here are referenced in the relocations below. + // + // More information about this format is at + // https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md + { + let mut linking = Vec::new(); + linking.push(0x02); // version + + linking.push(0x08); // `WASM_SYMBOL_TABLE` + let mut subsection = Vec::new(); + 6u32.encode(&mut subsection); // 6 symbols (4 functions + 2 globals) + + subsection.push(0x00); // SYMTAB_FUNCTION + 0x00.encode(&mut subsection); // flags + 0u32.encode(&mut subsection); // function index + "get_state_ptr".encode(&mut subsection); // symbol name + + subsection.push(0x00); // SYMTAB_FUNCTION + 0x00.encode(&mut subsection); // flags + 1u32.encode(&mut subsection); // function index + "set_state_ptr".encode(&mut subsection); // symbol name + + subsection.push(0x00); // SYMTAB_FUNCTION + 0x00.encode(&mut subsection); // flags + 2u32.encode(&mut subsection); // function index + "get_allocation_state".encode(&mut subsection); // symbol name + + subsection.push(0x00); // SYMTAB_FUNCTION + 0x00.encode(&mut subsection); // flags + 3u32.encode(&mut subsection); // function index + "set_allocation_state".encode(&mut subsection); // symbol name + + subsection.push(0x02); // SYMTAB_GLOBAL + 0x02.encode(&mut subsection); // flags (WASM_SYM_BINDING_LOCAL) + 0u32.encode(&mut subsection); // global index + "internal_state_ptr".encode(&mut subsection); // symbol name + + subsection.push(0x02); // SYMTAB_GLOBAL + 0x00.encode(&mut subsection); // flags + 1u32.encode(&mut subsection); // global index + "allocation_state".encode(&mut subsection); // symbol name + + subsection.encode(&mut linking); + module.section(&CustomSection { + name: "linking".into(), + data: linking.into(), + }); + } + + // A `reloc.CODE` section is appended here with relocations for the + // `global`-referencing instructions that were added. + { + let mut reloc = Vec::new(); + 3u32.encode(&mut reloc); // target section (code is the 4th section, 3 when 0-indexed) + 4u32.encode(&mut reloc); // 4 relocations + + reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB + internal_state_ptr_ref1.encode(&mut reloc); // offset + 4u32.encode(&mut reloc); // symbol index + + reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB + internal_state_ptr_ref2.encode(&mut reloc); // offset + 4u32.encode(&mut reloc); // symbol index + + reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB + allocation_state_ref1.encode(&mut reloc); // offset + 5u32.encode(&mut reloc); // symbol index + + reloc.push(0x07); // R_WASM_GLOBAL_INDEX_LEB + allocation_state_ref2.encode(&mut reloc); // offset + 5u32.encode(&mut reloc); // symbol index + + module.section(&CustomSection { + name: "reloc.CODE".into(), + data: reloc.into(), + }); + } + + module.finish() +} + +/// This function produces the output of `llvm-ar crus libfoo.a foo.o` given +/// the object file above as input. The archive is what's eventually fed to +/// LLD. +/// +/// Like above this is still tricky, mainly around the production of the symbol +/// table. +fn build_archive(wasm: &[u8]) -> Vec { + use object::{bytes_of, endian::BigEndian, U32Bytes}; + + let mut archive = Vec::new(); + archive.extend_from_slice(&object::archive::MAGIC); + + // The symbol table is in the "GNU" format which means it has a structure + // that looks like: + // + // * a big-endian 32-bit integer for the number of symbols + // * N big-endian 32-bit integers for the offset to the object file, within + // the entire archive, for which object has the symbol + // * N nul-delimited strings for each symbol + // + // Here we're building an archive with just a few symbols so it's a bit + // easier. Note though we don't know the offset of our `intrinsics.o` up + // front so it's left as 0 for now and filled in later. + + let syms = [ + "get_state_ptr", + "set_state_ptr", + "get_allocation_state", + "set_allocation_state", + "allocation_state", + ]; + + let mut symbol_table = Vec::new(); + symbol_table.extend_from_slice(bytes_of(&U32Bytes::new(BigEndian, syms.len() as u32))); + for _ in syms.iter() { + symbol_table.extend_from_slice(bytes_of(&U32Bytes::new(BigEndian, 0))); + } + for s in syms.iter() { + symbol_table.extend_from_slice(&std::ffi::CString::new(*s).unwrap().into_bytes_with_nul()); + } + + archive.extend_from_slice(bytes_of(&object::archive::Header { + name: *b"/ ", + date: *b"0 ", + uid: *b"0 ", + gid: *b"0 ", + mode: *b"0 ", + size: format!("{:<10}", symbol_table.len()) + .as_bytes() + .try_into() + .unwrap(), + terminator: object::archive::TERMINATOR, + })); + let symtab_offset = archive.len(); + archive.extend_from_slice(&symbol_table); + + // All archive members must start on even offsets + if archive.len() & 1 == 1 { + archive.push(0x00); + } + + // Now that we have the starting offset of the `intrinsics.o` file go back + // and fill in the offset within the symbol table generated earlier. + let member_offset = archive.len(); + for (index, _) in syms.iter().enumerate() { + let index = index + 1; + archive[symtab_offset + (index * 4)..][..4].copy_from_slice(bytes_of(&U32Bytes::new( + BigEndian, + member_offset.try_into().unwrap(), + ))); + } + + archive.extend_from_slice(object::bytes_of(&object::archive::Header { + name: *b"intrinsics.o ", + date: *b"0 ", + uid: *b"0 ", + gid: *b"0 ", + mode: *b"644 ", + size: format!("{:<10}", wasm.len()).as_bytes().try_into().unwrap(), + terminator: object::archive::TERMINATOR, + })); + archive.extend_from_slice(&wasm); + archive +} diff --git a/crates/adapter/byte-array-literals/Cargo.toml b/crates/adapter/byte-array-literals/Cargo.toml new file mode 100644 index 00000000..fa451017 --- /dev/null +++ b/crates/adapter/byte-array-literals/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "byte-array-literals" +publish = false + +[lib] +proc-macro = true +test = false +doctest = false diff --git a/crates/adapter/byte-array-literals/README.md b/crates/adapter/byte-array-literals/README.md new file mode 100644 index 00000000..15685494 --- /dev/null +++ b/crates/adapter/byte-array-literals/README.md @@ -0,0 +1,15 @@ +# byte-array-literals + +This crate exists to solve a very peculiar problem for the +`wasi-preview1-component-adapter`: we want to use string literals in our +source code, but the resulting binary (when compiled for +wasm32-unknown-unknown) cannot contain any data sections. + +The answer that @sunfishcode discovered is that these string literals, if +represented as an array of u8 literals, these will somehow not end up in the +data section, at least when compiled with opt-level='s' on today's rustc +(1.69.0). So, this crate exists to transform these literals using a proc +macro. + +It is very possible this cheat code will abruptly stop working in some future +compiler, but we'll cross that bridge when we get to it. diff --git a/crates/adapter/byte-array-literals/src/lib.rs b/crates/adapter/byte-array-literals/src/lib.rs new file mode 100644 index 00000000..ea9f013d --- /dev/null +++ b/crates/adapter/byte-array-literals/src/lib.rs @@ -0,0 +1,98 @@ +extern crate proc_macro; + +use proc_macro::{Delimiter, Group, Literal, Punct, Spacing, TokenStream, TokenTree}; + +/// Expand a `str` literal into a byte array. +#[proc_macro] +pub fn str(input: TokenStream) -> TokenStream { + let rv = convert_str(input); + + vec![TokenTree::Group(Group::new( + Delimiter::Bracket, + rv.into_iter().collect(), + ))] + .into_iter() + .collect() +} + +/// The same as `str` but appends a `'\n'`. +#[proc_macro] +pub fn str_nl(input: TokenStream) -> TokenStream { + let mut rv = convert_str(input); + + rv.push(TokenTree::Literal(Literal::u8_suffixed(b'\n'))); + + vec![TokenTree::Group(Group::new( + Delimiter::Bracket, + rv.into_iter().collect(), + ))] + .into_iter() + .collect() +} + +fn convert_str(input: TokenStream) -> Vec { + let mut it = input.into_iter(); + + let mut tokens = Vec::new(); + match it.next() { + Some(TokenTree::Literal(l)) => { + for b in to_string(l).into_bytes() { + tokens.push(TokenTree::Literal(Literal::u8_suffixed(b))); + tokens.push(TokenTree::Punct(Punct::new(',', Spacing::Alone))); + } + } + _ => panic!(), + } + + assert!(it.next().is_none()); + tokens +} + +fn to_string(lit: Literal) -> String { + let formatted = lit.to_string(); + + let mut it = formatted.chars(); + assert_eq!(it.next(), Some('"')); + + let mut rv = String::new(); + loop { + match it.next() { + Some('"') => match it.next() { + Some(_) => panic!(), + None => break, + }, + Some('\\') => match it.next() { + Some('x') => { + let hi = it.next().unwrap().to_digit(16).unwrap(); + let lo = it.next().unwrap().to_digit(16).unwrap(); + let v = (hi << 16) | lo; + rv.push(v as u8 as char); + } + Some('u') => { + assert_eq!(it.next(), Some('{')); + let mut c = it.next().unwrap(); + let mut ch = 0; + while let Some(v) = c.to_digit(16) { + ch *= 16; + ch |= v; + c = it.next().unwrap(); + } + assert_eq!(c, '}'); + rv.push(::std::char::from_u32(ch).unwrap()); + } + Some('0') => rv.push('\0'), + Some('\\') => rv.push('\\'), + Some('\"') => rv.push('\"'), + Some('r') => rv.push('\r'), + Some('n') => rv.push('\n'), + Some('t') => rv.push('\t'), + Some(_) => panic!(), + None => panic!(), + }, + Some(c) => rv.push(c), + None => panic!(), + } + } + + rv +} diff --git a/crates/adapter/src/descriptors.rs b/crates/adapter/src/descriptors.rs new file mode 100644 index 00000000..d013877f --- /dev/null +++ b/crates/adapter/src/descriptors.rs @@ -0,0 +1,238 @@ +use crate::bindings::wasi::cli::{stderr, stdin, stdout}; +use crate::bindings::wasi::io::streams::{InputStream, OutputStream}; +use crate::{BumpArena, ImportAlloc, TrappingUnwrap}; +use core::cell::{Cell, OnceCell, UnsafeCell}; +use core::mem::MaybeUninit; +use wasi::{Errno, Fd}; + +pub const MAX_DESCRIPTORS: usize = 128; + +#[repr(C)] +pub enum Descriptor { + /// A closed descriptor, holding a reference to the previous closed + /// descriptor to support reusing them. + Closed(Option), + + /// Input and/or output wasi-streams, along with stream metadata. + Streams(Streams), + + Bad, +} + +/// Input and/or output wasi-streams, along with a stream type that +/// identifies what kind of stream they are and possibly supporting +/// type-specific operations like seeking. +pub struct Streams { + /// The input stream, if present. + pub input: OnceCell, + + /// The output stream, if present. + pub output: OnceCell, + + /// Information about the source of the stream. + pub type_: StreamType, +} + +impl Streams { + /// Return the input stream, initializing it on the fly if needed. + pub fn get_read_stream(&self) -> Result<&InputStream, Errno> { + match self.input.get() { + Some(wasi_stream) => Ok(wasi_stream), + + // proxy worlds don't have filesystem access + None => Err(wasi::ERRNO_BADF), + } + } + + /// Return the output stream, initializing it on the fly if needed. + pub fn get_write_stream(&self) -> Result<&OutputStream, Errno> { + match self.output.get() { + Some(wasi_stream) => Ok(wasi_stream), + + // proxy worlds don't have filesystem access + None => Err(wasi::ERRNO_BADF), + } + } +} + +pub enum StreamType { + /// Streams for implementing stdio. + Stdio(Stdio), +} + +pub enum Stdio { + Stdin, + Stdout, + Stderr, +} + +#[repr(C)] +pub struct Descriptors { + /// Storage of mapping from preview1 file descriptors to preview2 file + /// descriptors. + table: UnsafeCell>, + table_len: Cell, + + /// Points to the head of a free-list of closed file descriptors. + closed: Option, +} + +impl Descriptors { + pub fn new(_import_alloc: &ImportAlloc, _arena: &BumpArena) -> Self { + let d = Descriptors { + table: UnsafeCell::new(MaybeUninit::uninit()), + table_len: Cell::new(0), + closed: None, + }; + + fn new_once(val: T) -> OnceCell { + let cell = OnceCell::new(); + let _ = cell.set(val); + cell + } + + d.push(Descriptor::Streams(Streams { + input: new_once(stdin::get_stdin()), + output: OnceCell::new(), + type_: StreamType::Stdio(Stdio::Stdin), + })) + .trapping_unwrap(); + d.push(Descriptor::Streams(Streams { + input: OnceCell::new(), + output: new_once(stdout::get_stdout()), + type_: StreamType::Stdio(Stdio::Stdout), + })) + .trapping_unwrap(); + d.push(Descriptor::Streams(Streams { + input: OnceCell::new(), + output: new_once(stderr::get_stderr()), + type_: StreamType::Stdio(Stdio::Stderr), + })) + .trapping_unwrap(); + + d + } + + fn push(&self, desc: Descriptor) -> Result { + unsafe { + let table = (*self.table.get()).as_mut_ptr(); + let len = usize::try_from(self.table_len.get()).trapping_unwrap(); + if len >= (*table).len() { + return Err(wasi::ERRNO_NOMEM); + } + core::ptr::addr_of_mut!((*table)[len]).write(desc); + self.table_len.set(u16::try_from(len + 1).trapping_unwrap()); + Ok(Fd::from(u32::try_from(len).trapping_unwrap())) + } + } + + fn table(&self) -> &[Descriptor] { + unsafe { + std::slice::from_raw_parts( + (*self.table.get()).as_ptr().cast(), + usize::try_from(self.table_len.get()).trapping_unwrap(), + ) + } + } + + fn table_mut(&mut self) -> &mut [Descriptor] { + unsafe { + std::slice::from_raw_parts_mut( + (*self.table.get()).as_mut_ptr().cast(), + usize::try_from(self.table_len.get()).trapping_unwrap(), + ) + } + } + + pub fn open(&mut self, d: Descriptor) -> Result { + match self.closed { + // No closed descriptors: expand table + None => self.push(d), + Some(freelist_head) => { + // Pop an item off the freelist + let freelist_desc = self.get_mut(freelist_head).trapping_unwrap(); + let next_closed = match freelist_desc { + Descriptor::Closed(next) => *next, + _ => unreachable!("impossible: freelist points to a closed descriptor"), + }; + // Write descriptor to the entry at the nead of the list + *freelist_desc = d; + // Point closed to the following item + self.closed = next_closed; + Ok(freelist_head) + } + } + } + + pub fn get(&self, fd: Fd) -> Result<&Descriptor, Errno> { + self.table() + .get(usize::try_from(fd).trapping_unwrap()) + .ok_or(wasi::ERRNO_BADF) + } + + pub fn get_mut(&mut self, fd: Fd) -> Result<&mut Descriptor, Errno> { + self.table_mut() + .get_mut(usize::try_from(fd).trapping_unwrap()) + .ok_or(wasi::ERRNO_BADF) + } + + // Internal: close a fd, returning the descriptor. + fn close_(&mut self, fd: Fd) -> Result { + // Throw an error if closing an fd which is already closed + match self.get(fd)? { + Descriptor::Closed(_) => Err(wasi::ERRNO_BADF)?, + _ => {} + } + // Mutate the descriptor to be closed, and push the closed fd onto the head of the linked list: + let last_closed = self.closed; + let prev = std::mem::replace(self.get_mut(fd)?, Descriptor::Closed(last_closed)); + self.closed = Some(fd); + Ok(prev) + } + + // Close an fd. + pub fn close(&mut self, fd: Fd) -> Result<(), Errno> { + drop(self.close_(fd)?); + Ok(()) + } + + // Expand the table by pushing a closed descriptor to the end. Used for renumbering. + fn push_closed(&mut self) -> Result<(), Errno> { + let old_closed = self.closed; + let new_closed = self.push(Descriptor::Closed(old_closed))?; + self.closed = Some(new_closed); + Ok(()) + } + + // Implementation of fd_renumber + pub fn renumber(&mut self, from_fd: Fd, to_fd: Fd) -> Result<(), Errno> { + // First, ensure from_fd is in bounds: + let _ = self.get(from_fd)?; + // Expand table until to_fd is in bounds as well: + while self.table_len.get() as u32 <= to_fd { + self.push_closed()?; + } + // Then, close from_fd and put its contents into to_fd: + let desc = self.close_(from_fd)?; + // TODO FIXME if this overwrites a preopen, do we need to clear it from the preopen table? + *self.get_mut(to_fd)? = desc; + + Ok(()) + } + + // A bunch of helper functions implemented in terms of the above pub functions: + + pub fn get_read_stream(&self, fd: Fd) -> Result<&InputStream, Errno> { + match self.get(fd)? { + Descriptor::Streams(streams) => streams.get_read_stream(), + Descriptor::Closed(_) | Descriptor::Bad => Err(wasi::ERRNO_BADF), + } + } + + pub fn get_write_stream(&self, fd: Fd) -> Result<&OutputStream, Errno> { + match self.get(fd)? { + Descriptor::Streams(streams) => streams.get_write_stream(), + Descriptor::Closed(_) | Descriptor::Bad => Err(wasi::ERRNO_BADF), + } + } +} diff --git a/crates/adapter/src/fastly/cache.rs b/crates/adapter/src/fastly/cache.rs new file mode 100644 index 00000000..16d86488 --- /dev/null +++ b/crates/adapter/src/fastly/cache.rs @@ -0,0 +1,465 @@ +use super::{convert_result, BodyHandle, FastlyStatus, RequestHandle}; +use crate::{alloc_result, with_buffer, TrappingUnwrap}; + +pub type CacheHandle = u32; + +pub type CacheObjectLength = u64; +pub type CacheDurationNs = u64; +pub type CacheHitCount = u64; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(C)] +pub struct CacheLookupOptions { + pub request_headers: RequestHandle, +} + +bitflags::bitflags! { + #[repr(transparent)] + pub struct CacheLookupOptionsMask: u32 { + const _RESERVED = 1 << 0; + const REQUEST_HEADERS = 1 << 1; + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(C)] +pub struct CacheWriteOptions { + pub max_age_ns: u64, + pub request_headers: RequestHandle, + pub vary_rule_ptr: *const u8, + pub vary_rule_len: usize, + pub initial_age_ns: u64, + pub stale_while_revalidate_ns: u64, + pub surrogate_keys_ptr: *const u8, + pub surrogate_keys_len: usize, + pub length: CacheObjectLength, + pub user_metadata_ptr: *const u8, + pub user_metadata_len: usize, +} + +bitflags::bitflags! { + #[repr(transparent)] + pub struct CacheWriteOptionsMask: u32 { + const _RESERVED = 1 << 0; + const REQUEST_HEADERS = 1 << 1; + const VARY_RULE = 1 << 2; + const INITIAL_AGE_NS = 1 << 3; + const STALE_WHILE_REVALIDATE_NS = 1 << 4; + const SURROGATE_KEYS = 1 << 5; + const LENGTH = 1 << 6; + const USER_METADATA = 1 << 7; + const SENSITIVE_DATA = 1 << 8; + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(C)] +pub struct CacheGetBodyOptions { + pub from: u64, + pub to: u64, +} + +bitflags::bitflags! { + #[repr(transparent)] + pub struct CacheGetBodyOptionsMask: u32 { + const _RESERVED = 1 << 0; + const FROM = 1 << 1; + const TO = 1 << 2; + } +} + +bitflags::bitflags! { + #[repr(transparent)] + pub struct CacheLookupState: u32 { + const FOUND = 1 << 0; + const USABLE = 1 << 1; + const STALE = 1 << 2; + const MUST_INSERT_OR_UPDATE = 1 << 3; + } +} + +mod cache { + use super::*; + use crate::bindings::fastly::api::cache; + use core::slice; + + impl From for cache::LookupOptionsMask { + fn from(value: CacheLookupOptionsMask) -> Self { + let mut flags = Self::empty(); + flags.set( + Self::REQUEST_HEADERS, + value.contains(CacheLookupOptionsMask::REQUEST_HEADERS), + ); + flags + } + } + + impl From for cache::LookupOptions { + fn from(value: CacheLookupOptions) -> Self { + Self { + request_headers: value.request_headers, + } + } + } + + impl From for cache::WriteOptionsMask { + fn from(value: CacheWriteOptionsMask) -> Self { + let mut flags = Self::empty(); + flags.set( + Self::RESERVED, + value.contains(CacheWriteOptionsMask::_RESERVED), + ); + flags.set( + Self::REQUEST_HEADERS, + value.contains(CacheWriteOptionsMask::REQUEST_HEADERS), + ); + flags.set( + Self::VARY_RULE, + value.contains(CacheWriteOptionsMask::VARY_RULE), + ); + flags.set( + Self::INITIAL_AGE_NS, + value.contains(CacheWriteOptionsMask::INITIAL_AGE_NS), + ); + flags.set( + Self::STALE_WHILE_REVALIDATE_NS, + value.contains(CacheWriteOptionsMask::STALE_WHILE_REVALIDATE_NS), + ); + flags.set( + Self::SURROGATE_KEYS, + value.contains(CacheWriteOptionsMask::SURROGATE_KEYS), + ); + flags.set(Self::LENGTH, value.contains(CacheWriteOptionsMask::LENGTH)); + flags.set( + Self::USER_METADATA, + value.contains(CacheWriteOptionsMask::USER_METADATA), + ); + flags.set( + Self::SENSITIVE_DATA, + value.contains(CacheWriteOptionsMask::SENSITIVE_DATA), + ); + flags + } + } + + impl From for CacheLookupState { + fn from(value: cache::LookupState) -> Self { + let mut flags = Self::empty(); + flags.set(Self::FOUND, value.contains(cache::LookupState::FOUND)); + flags.set(Self::USABLE, value.contains(cache::LookupState::USABLE)); + flags.set(Self::STALE, value.contains(cache::LookupState::STALE)); + flags.set( + Self::MUST_INSERT_OR_UPDATE, + value.contains(cache::LookupState::MUST_INSERT_OR_UPDATE), + ); + flags + } + } + + #[export_name = "fastly_cache#lookup"] + pub fn lookup( + cache_key_ptr: *const u8, + cache_key_len: usize, + options_mask: CacheLookupOptionsMask, + options: *const CacheLookupOptions, + cache_handle_out: *mut CacheHandle, + ) -> FastlyStatus { + let cache_key = unsafe { slice::from_raw_parts(cache_key_ptr, cache_key_len) }; + let options_mask = cache::LookupOptionsMask::from(options_mask); + let options = unsafe { cache::LookupOptions::from(*options) }; + match cache::lookup(cache_key, options_mask, options) { + Ok(res) => { + unsafe { + *cache_handle_out = res; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + unsafe fn write_options(options: *const CacheWriteOptions) -> cache::WriteOptions { + // NOTE: this is only really safe because we never mutate the vectors -- we only need + // vectors to satisfy the interface produced by the DynamicBackendConfig record, + // `register_dynamic_backend` will never mutate the vectors it's given. + macro_rules! make_vec { + ($ptr_field:ident, $len_field:ident) => {{ + let len = usize::try_from((*options).$len_field).trapping_unwrap(); + Vec::from_raw_parts((*options).$ptr_field as *mut _, len, len) + }}; + } + + cache::WriteOptions { + max_age_ns: (*options).max_age_ns, + request_headers: (*options).request_headers, + vary_rule: make_vec!(vary_rule_ptr, vary_rule_len), + initial_age_ns: (*options).initial_age_ns, + stale_while_revalidate_ns: (*options).stale_while_revalidate_ns, + surrogate_keys: make_vec!(surrogate_keys_ptr, surrogate_keys_len), + length: (*options).length, + user_metadata: make_vec!(user_metadata_ptr, user_metadata_len), + } + } + + #[export_name = "fastly_cache#insert"] + pub fn insert( + cache_key_ptr: *const u8, + cache_key_len: usize, + options_mask: CacheWriteOptionsMask, + options: *const CacheWriteOptions, + body_handle_out: *mut BodyHandle, + ) -> FastlyStatus { + let cache_key = unsafe { slice::from_raw_parts(cache_key_ptr, cache_key_len) }; + let options_mask = cache::WriteOptionsMask::from(options_mask); + + let options = unsafe { write_options(options) }; + + let res = cache::insert(cache_key, options_mask, &options); + + std::mem::forget(options); + + match res { + Ok(res) => { + unsafe { + *body_handle_out = res; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_cache#transaction_lookup"] + pub fn transaction_lookup( + cache_key_ptr: *const u8, + cache_key_len: usize, + options_mask: CacheLookupOptionsMask, + options: *const CacheLookupOptions, + cache_handle_out: *mut CacheHandle, + ) -> FastlyStatus { + let cache_key = unsafe { slice::from_raw_parts(cache_key_ptr, cache_key_len) }; + let options_mask = cache::LookupOptionsMask::from(options_mask); + let options = unsafe { cache::LookupOptions::from(*options) }; + match cache::transaction_lookup(cache_key, options_mask, options) { + Ok(res) => { + unsafe { + *cache_handle_out = res; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_cache#transaction_insert"] + pub fn transaction_insert( + handle: CacheHandle, + options_mask: CacheWriteOptionsMask, + options: *const CacheWriteOptions, + body_handle_out: *mut BodyHandle, + ) -> FastlyStatus { + let options_mask = cache::WriteOptionsMask::from(options_mask); + let options = unsafe { write_options(options) }; + let res = cache::transaction_insert(handle, options_mask, &options); + + std::mem::forget(options); + + match res { + Ok(res) => { + unsafe { + *body_handle_out = res; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_cache#transaction_insert_and_stream_back"] + pub fn transaction_insert_and_stream_back( + handle: CacheHandle, + options_mask: CacheWriteOptionsMask, + options: *const CacheWriteOptions, + body_handle_out: *mut BodyHandle, + cache_handle_out: *mut CacheHandle, + ) -> FastlyStatus { + let options_mask = cache::WriteOptionsMask::from(options_mask); + let options = unsafe { write_options(options) }; + let res = cache::transaction_insert_and_stream_back(handle, options_mask, &options); + std::mem::forget(options); + match res { + Ok((body_handle, cache_handle)) => { + unsafe { + *body_handle_out = body_handle; + *cache_handle_out = cache_handle; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_cache#transaction_update"] + pub fn transaction_update( + handle: CacheHandle, + options_mask: CacheWriteOptionsMask, + options: *const CacheWriteOptions, + ) -> FastlyStatus { + let options_mask = cache::WriteOptionsMask::from(options_mask); + let options = unsafe { write_options(options) }; + let res = cache::transaction_update(handle, options_mask, &options); + std::mem::forget(options); + convert_result(res) + } + + #[export_name = "fastly_cache#transaction_cancel"] + pub fn transaction_cancel(handle: CacheHandle) -> FastlyStatus { + convert_result(cache::transaction_cancel(handle)) + } + + #[export_name = "fastly_cache#close"] + pub fn close(handle: CacheHandle) -> FastlyStatus { + convert_result(cache::close(handle)) + } + + #[export_name = "fastly_cache#get_state"] + pub fn get_state( + handle: CacheHandle, + cache_lookup_state_out: *mut CacheLookupState, + ) -> FastlyStatus { + match cache::get_state(handle) { + Ok(res) => { + unsafe { + *cache_lookup_state_out = res.into(); + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_cache#get_user_metadata"] + pub fn get_user_metadata( + handle: CacheHandle, + user_metadata_out_ptr: *mut u8, + user_metadata_out_len: usize, + nwritten_out: *mut usize, + ) -> FastlyStatus { + alloc_result!( + user_metadata_out_ptr, + user_metadata_out_len, + nwritten_out, + { cache::get_user_metadata(handle) } + ) + } + + impl From for cache::GetBodyOptionsMask { + fn from(value: CacheGetBodyOptionsMask) -> Self { + let mut flags = Self::empty(); + flags.set( + Self::RESERVED, + value.contains(CacheGetBodyOptionsMask::_RESERVED), + ); + flags.set(Self::FROM, value.contains(CacheGetBodyOptionsMask::FROM)); + flags.set(Self::TO, value.contains(CacheGetBodyOptionsMask::TO)); + flags + } + } + + impl From for cache::GetBodyOptions { + fn from(value: CacheGetBodyOptions) -> Self { + Self { + from: value.from, + to: value.to, + } + } + } + + #[export_name = "fastly_cache#get_body"] + pub fn get_body( + handle: CacheHandle, + options_mask: CacheGetBodyOptionsMask, + options: *const CacheGetBodyOptions, + body_handle_out: *mut BodyHandle, + ) -> FastlyStatus { + let options_mask = cache::GetBodyOptionsMask::from(options_mask); + let options = unsafe { cache::GetBodyOptions::from(*options) }; + match cache::get_body(handle, options_mask, options) { + Ok(res) => { + unsafe { + *body_handle_out = res; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_cache#get_length"] + pub fn get_length(handle: CacheHandle, length_out: *mut CacheObjectLength) -> FastlyStatus { + match cache::get_length(handle) { + Ok(res) => { + unsafe { + *length_out = res; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_cache#get_max_age_ns"] + pub fn get_max_age_ns(handle: CacheHandle, duration_out: *mut CacheDurationNs) -> FastlyStatus { + match cache::get_max_age_ns(handle) { + Ok(res) => { + unsafe { + *duration_out = res; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_cache#get_stale_while_revalidate_ns"] + pub fn get_stale_while_revalidate_ns( + handle: CacheHandle, + duration_out: *mut CacheDurationNs, + ) -> FastlyStatus { + match cache::get_stale_while_revalidate_ns(handle) { + Ok(res) => { + unsafe { + *duration_out = res; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_cache#get_age_ns"] + pub fn get_age_ns(handle: CacheHandle, duration_out: *mut CacheDurationNs) -> FastlyStatus { + match cache::get_age_ns(handle) { + Ok(res) => { + unsafe { + *duration_out = res; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_cache#get_hits"] + pub fn get_hits(handle: CacheHandle, hits_out: *mut CacheHitCount) -> FastlyStatus { + match cache::get_hits(handle) { + Ok(res) => { + unsafe { + *hits_out = res; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } +} diff --git a/crates/adapter/src/fastly/config_store.rs b/crates/adapter/src/fastly/config_store.rs new file mode 100644 index 00000000..62033b35 --- /dev/null +++ b/crates/adapter/src/fastly/config_store.rs @@ -0,0 +1,25 @@ +use super::fastly_dictionary; +use super::FastlyStatus; + +pub type ConfigStoreHandle = u32; + +#[export_name = "fastly_config_store#open"] +pub fn open( + name: *const u8, + name_len: usize, + store_handle_out: *mut ConfigStoreHandle, +) -> FastlyStatus { + fastly_dictionary::open(name, name_len, store_handle_out) +} + +#[export_name = "fastly_config_store#get"] +pub fn get( + store_handle: ConfigStoreHandle, + key: *const u8, + key_len: usize, + value: *mut u8, + value_max_len: usize, + nwritten: *mut usize, +) -> FastlyStatus { + fastly_dictionary::get(store_handle, key, key_len, value, value_max_len, nwritten) +} diff --git a/crates/adapter/src/fastly/core.rs b/crates/adapter/src/fastly/core.rs new file mode 100644 index 00000000..375529e4 --- /dev/null +++ b/crates/adapter/src/fastly/core.rs @@ -0,0 +1,2808 @@ +// The following type aliases are used for readability of definitions in this module. They should +// not be confused with types of similar names in the `fastly` crate which are used to provide safe +// wrappers around these definitions. + +use super::{convert_result, FastlyStatus}; +use crate::{alloc_result, with_buffer, TrappingUnwrap}; + +impl From for u32 { + fn from(value: crate::bindings::fastly::api::http_types::HttpVersion) -> Self { + use crate::bindings::fastly::api::http_types::HttpVersion; + match value { + HttpVersion::Http09 => 0, + HttpVersion::Http10 => 1, + HttpVersion::Http11 => 2, + HttpVersion::H2 => 3, + HttpVersion::H3 => 4, + } + } +} + +impl TryFrom for crate::bindings::fastly::api::http_types::HttpVersion { + type Error = u32; + + fn try_from(value: u32) -> Result { + use crate::bindings::fastly::api::http_types::HttpVersion; + match value { + 0 => Ok(HttpVersion::Http09), + 1 => Ok(HttpVersion::Http10), + 2 => Ok(HttpVersion::Http11), + 3 => Ok(HttpVersion::H2), + 4 => Ok(HttpVersion::H3), + _ => Err(value), + } + } +} + +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[repr(u32)] +pub enum BodyWriteEnd { + Back = 0, + Front = 1, +} + +/// Determines how the framing headers (`Content-Length`/`Transfer-Encoding`) are set for a +/// request or response. +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[repr(u32)] +pub enum FramingHeadersMode { + /// Determine the framing headers automatically based on the message body, and discard any framing + /// headers already set in the message. This is the default behavior. + /// + /// In automatic mode, a `Content-Length` is used when the size of the body can be determined + /// before it is sent. Requests/responses sent in streaming mode, where headers are sent immediately + /// but the content of the body is streamed later, will receive a `Transfer-Encoding: chunked` + /// to accommodate the dynamic generation of the body. + Automatic = 0, + + /// Use the exact framing headers set in the message, falling back to [`Automatic`][`Self::Automatic`] + /// if invalid. + /// + /// In "from headers" mode, any `Content-Length` or `Transfer-Encoding` headers will be honored. + /// You must ensure that those headers have correct values permitted by the + /// [HTTP/1.1 specification][spec]. If the provided headers are not permitted by the spec, + /// the headers will revert to automatic mode and a log diagnostic will be issued about what was + /// wrong. If a `Content-Length` is permitted by the spec, but the value doesn't match the size of + /// the actual body, the body will either be truncated (if it is too long), or the connection will + /// be hung up early (if it is too short). + /// + /// [spec]: https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.1 + ManuallyFromHeaders = 1, +} + +/// Determines whether the client is encouraged to stop using the current connection and to open a +/// new one for the next request. +/// +/// Most applications do not need to change this setting. +#[doc(hidden)] +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[repr(u32)] +pub enum HttpKeepaliveMode { + /// This is the default behavor. + Automatic = 0, + + /// Send `Connection: close` in HTTP/1 and a GOAWAY frame in HTTP/2 and HTTP/3. This prompts + /// the client to close the current connection and to open a new one for the next request. + NoKeepalive = 1, +} + +pub type PendingObjectStoreLookupHandle = u32; +pub type PendingObjectStoreInsertHandle = u32; +pub type PendingObjectStoreDeleteHandle = u32; +pub type BodyHandle = u32; +pub type PendingRequestHandle = u32; +pub type RequestHandle = u32; +pub type ResponseHandle = u32; +pub type DictionaryHandle = u32; +pub type KVStoreHandle = u32; +pub type SecretStoreHandle = u32; +pub type SecretHandle = u32; +pub type AsyncItemHandle = u32; + +const INVALID_HANDLE: u32 = u32::MAX - 1; + +#[repr(C)] +pub struct DynamicBackendConfig { + pub host_override: *const u8, + pub host_override_len: u32, + pub connect_timeout_ms: u32, + pub first_byte_timeout_ms: u32, + pub between_bytes_timeout_ms: u32, + pub ssl_min_version: u32, + pub ssl_max_version: u32, + pub cert_hostname: *const u8, + pub cert_hostname_len: u32, + pub ca_cert: *const u8, + pub ca_cert_len: u32, + pub ciphers: *const u8, + pub ciphers_len: u32, + pub sni_hostname: *const u8, + pub sni_hostname_len: u32, + pub client_certificate: *const u8, + pub client_certificate_len: u32, + pub client_key: SecretHandle, +} + +impl Default for DynamicBackendConfig { + fn default() -> Self { + DynamicBackendConfig { + host_override: std::ptr::null(), + host_override_len: 0, + connect_timeout_ms: 0, + first_byte_timeout_ms: 0, + between_bytes_timeout_ms: 0, + ssl_min_version: 0, + ssl_max_version: 0, + cert_hostname: std::ptr::null(), + cert_hostname_len: 0, + ca_cert: std::ptr::null(), + ca_cert_len: 0, + ciphers: std::ptr::null(), + ciphers_len: 0, + sni_hostname: std::ptr::null(), + sni_hostname_len: 0, + client_certificate: std::ptr::null(), + client_certificate_len: 0, + client_key: 0, + } + } +} + +bitflags::bitflags! { + /// `Content-Encoding` codings. + #[derive(Default)] + #[repr(transparent)] + pub struct ContentEncodings: u32 { + const GZIP = 1 << 0; + } +} + +impl From for crate::bindings::fastly::api::http_req::ContentEncodings { + fn from(value: ContentEncodings) -> Self { + let mut flags = Self::empty(); + flags.set(Self::GZIP, value.contains(ContentEncodings::GZIP)); + flags + } +} + +bitflags::bitflags! { + /// `BackendConfigOptions` codings. + #[derive(Default)] + #[repr(transparent)] + pub struct BackendConfigOptions: u32 { + const RESERVED = 1 << 0; + const HOST_OVERRIDE = 1 << 1; + const CONNECT_TIMEOUT = 1 << 2; + const FIRST_BYTE_TIMEOUT = 1 << 3; + const BETWEEN_BYTES_TIMEOUT = 1 << 4; + const USE_SSL = 1 << 5; + const SSL_MIN_VERSION = 1 << 6; + const SSL_MAX_VERSION = 1 << 7; + const CERT_HOSTNAME = 1 << 8; + const CA_CERT = 1 << 9; + const CIPHERS = 1 << 10; + const SNI_HOSTNAME = 1 << 11; + const DONT_POOL = 1 << 12; + const CLIENT_CERT = 1 << 13; + const GRPC = 1 << 14; + } +} + +impl From for crate::bindings::fastly::api::http_types::BackendConfigOptions { + fn from(options: BackendConfigOptions) -> Self { + let mut flags = Self::empty(); + flags.set( + Self::RESERVED, + options.contains(BackendConfigOptions::RESERVED), + ); + flags.set( + Self::HOST_OVERRIDE, + options.contains(BackendConfigOptions::HOST_OVERRIDE), + ); + flags.set( + Self::CONNECT_TIMEOUT, + options.contains(BackendConfigOptions::CONNECT_TIMEOUT), + ); + flags.set( + Self::FIRST_BYTE_TIMEOUT, + options.contains(BackendConfigOptions::FIRST_BYTE_TIMEOUT), + ); + flags.set( + Self::BETWEEN_BYTES_TIMEOUT, + options.contains(BackendConfigOptions::BETWEEN_BYTES_TIMEOUT), + ); + flags.set( + Self::USE_SSL, + options.contains(BackendConfigOptions::USE_SSL), + ); + flags.set( + Self::SSL_MIN_VERSION, + options.contains(BackendConfigOptions::SSL_MIN_VERSION), + ); + flags.set( + Self::SSL_MAX_VERSION, + options.contains(BackendConfigOptions::SSL_MAX_VERSION), + ); + flags.set( + Self::CERT_HOSTNAME, + options.contains(BackendConfigOptions::CERT_HOSTNAME), + ); + flags.set( + Self::CA_CERT, + options.contains(BackendConfigOptions::CA_CERT), + ); + flags.set( + Self::CIPHERS, + options.contains(BackendConfigOptions::CIPHERS), + ); + flags.set( + Self::SNI_HOSTNAME, + options.contains(BackendConfigOptions::SNI_HOSTNAME), + ); + flags.set( + Self::DONT_POOL, + options.contains(BackendConfigOptions::DONT_POOL), + ); + flags.set( + Self::CLIENT_CERT, + options.contains(BackendConfigOptions::CLIENT_CERT), + ); + flags.set(Self::GRPC, options.contains(BackendConfigOptions::GRPC)); + flags + } +} + +pub mod fastly_abi { + use super::*; + + pub const ABI_VERSION: u64 = 1; + + #[export_name = "fastly_abi#init"] + /// Tell the runtime what ABI version this program is using (FASTLY_ABI_VERSION) + pub fn init(abi_version: u64) -> FastlyStatus { + if abi_version != ABI_VERSION { + FastlyStatus::UNKNOWN_ERROR + } else { + FastlyStatus::OK + } + } +} + +pub mod fastly_uap { + use super::*; + use crate::bindings::fastly::api::uap; + use core::slice; + + #[export_name = "fastly_uap#parse"] + pub fn parse( + user_agent: *const u8, + user_agent_max_len: usize, + family: *mut u8, + family_max_len: usize, + family_written: *mut usize, + major: *mut u8, + major_max_len: usize, + major_written: *mut usize, + minor: *mut u8, + minor_max_len: usize, + minor_written: *mut usize, + patch: *mut u8, + patch_max_len: usize, + patch_written: *mut usize, + ) -> FastlyStatus { + let user_agent = unsafe { slice::from_raw_parts(user_agent, user_agent_max_len) }; + let ua = match uap::parse(user_agent) { + Ok(ua) => ua, + Err(e) => return e.into(), + }; + + alloc_result!(family, family_max_len, family_written, { + ua.family(u64::try_from(family_max_len).trapping_unwrap()) + }); + + alloc_result!(major, major_max_len, major_written, { + ua.major(u64::try_from(major_max_len).trapping_unwrap()) + }); + + alloc_result!(minor, minor_max_len, minor_written, { + ua.minor(u64::try_from(minor_max_len).trapping_unwrap()) + }); + + alloc_result!(patch, patch_max_len, patch_written, { + ua.patch(u64::try_from(patch_max_len).trapping_unwrap()) + }); + + FastlyStatus::OK + } +} + +pub mod fastly_http_body { + use super::*; + use crate::bindings::fastly::api::http_body; + use core::slice; + + #[export_name = "fastly_http_body#append"] + pub fn append(dst_handle: BodyHandle, src_handle: BodyHandle) -> FastlyStatus { + convert_result(http_body::append(dst_handle, src_handle)) + } + + #[export_name = "fastly_http_body#new"] + pub fn new(handle_out: *mut BodyHandle) -> FastlyStatus { + match http_body::new() { + Ok(handle) => { + unsafe { + *handle_out = handle; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_http_body#read"] + pub fn read( + body_handle: BodyHandle, + buf: *mut u8, + buf_len: usize, + nread_out: *mut usize, + ) -> FastlyStatus { + alloc_result!(buf, buf_len, nread_out, { + http_body::read(body_handle, u32::try_from(buf_len).trapping_unwrap()) + }) + } + + // overeager warning for extern declarations is a rustc bug: https://github.com/rust-lang/rust/issues/79581 + #[allow(clashing_extern_declarations)] + #[export_name = "fastly_http_body#write"] + pub fn write( + body_handle: BodyHandle, + buf: *const u8, + buf_len: usize, + end: BodyWriteEnd, + nwritten_out: *mut usize, + ) -> FastlyStatus { + let end = match end { + BodyWriteEnd::Back => http_body::WriteEnd::Back, + BodyWriteEnd::Front => http_body::WriteEnd::Front, + }; + match http_body::write( + body_handle, + unsafe { slice::from_raw_parts(buf, buf_len) }, + end, + ) { + Ok(len) => { + unsafe { + *nwritten_out = usize::try_from(len).trapping_unwrap(); + } + FastlyStatus::OK + } + + Err(e) => e.into(), + } + } + + /// Close a body, freeing its resources and causing any sends to finish. + #[export_name = "fastly_http_body#close"] + pub fn close(body_handle: BodyHandle) -> FastlyStatus { + convert_result(http_body::close(body_handle)) + } + + #[export_name = "fastly_http_body#trailer_append"] + pub fn trailer_append( + body_handle: BodyHandle, + name: *const u8, + name_len: usize, + value: *const u8, + value_len: usize, + ) -> FastlyStatus { + let name = unsafe { slice::from_raw_parts(name, name_len) }; + let value = unsafe { slice::from_raw_parts(value, value_len) }; + convert_result(http_body::trailer_append(body_handle, name, value)) + } + + #[export_name = "fastly_http_body#trailer_names_get"] + pub fn trailer_names_get( + body_handle: BodyHandle, + buf: *mut u8, + buf_len: usize, + cursor: u32, + ending_cursor: *mut i64, + nwritten: *mut usize, + ) -> FastlyStatus { + with_buffer!( + buf, + buf_len, + { + http_body::trailer_names_get( + body_handle, + u64::try_from(buf_len).trapping_unwrap(), + cursor, + ) + }, + |res| { + let (written, end) = match res { + Some((bytes, next)) => { + let written = bytes.len(); + let end = match next { + Some(next) => i64::from(next), + None => -1, + }; + + std::mem::forget(bytes); + + (written, end) + } + None => (0, -1), + }; + + unsafe { + *nwritten = written; + *ending_cursor = end; + } + } + ) + } + + #[export_name = "fastly_http_body#trailer_value_get"] + pub fn trailer_value_get( + body_handle: BodyHandle, + name: *const u8, + name_len: usize, + value: *mut u8, + value_max_len: usize, + nwritten: *mut usize, + ) -> FastlyStatus { + let name = unsafe { slice::from_raw_parts(name, name_len) }; + with_buffer!( + value, + value_max_len, + { + http_body::trailer_value_get( + body_handle, + name, + u64::try_from(value_max_len).trapping_unwrap(), + ) + }, + |res| { + let res = res.ok_or(FastlyStatus::NONE)?; + unsafe { + *nwritten = res.len(); + } + std::mem::forget(res); + } + ) + } + + #[export_name = "fastly_http_body#trailer_values_get"] + pub fn trailer_values_get( + body_handle: BodyHandle, + name: *const u8, + name_len: usize, + buf: *mut u8, + buf_len: usize, + cursor: u32, + ending_cursor: *mut i64, + nwritten: *mut usize, + ) -> FastlyStatus { + let name = unsafe { slice::from_raw_parts(name, name_len) }; + with_buffer!( + buf, + buf_len, + { + http_body::trailer_values_get( + body_handle, + name, + u64::try_from(buf_len).trapping_unwrap(), + cursor, + ) + }, + |res| { + let (written, end) = match res { + Some((bytes, next)) => { + let written = bytes.len(); + let end = match next { + Some(next) => i64::from(next), + None => -1, + }; + + std::mem::forget(bytes); + + (written, end) + } + None => (0, -1), + }; + + unsafe { + *nwritten = written; + *ending_cursor = end; + } + } + ) + } + + #[export_name = "fastly_http_body#known_length"] + pub fn known_length(body_handle: BodyHandle, length_out: *mut u64) -> FastlyStatus { + match http_body::known_length(body_handle) { + Ok(len) => { + unsafe { + *length_out = len; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } +} + +pub mod fastly_log { + use core::slice; + + use super::*; + use crate::bindings::fastly; + + #[export_name = "fastly_log#endpoint_get"] + pub fn endpoint_get( + name: *const u8, + name_len: usize, + endpoint_handle_out: *mut u32, + ) -> FastlyStatus { + let name = unsafe { slice::from_raw_parts(name, name_len) }; + match fastly::api::log::endpoint_get(name) { + Ok(res) => { + unsafe { + *endpoint_handle_out = res; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + // overeager warning for extern declarations is a rustc bug: https://github.com/rust-lang/rust/issues/79581 + #[allow(clashing_extern_declarations)] + #[export_name = "fastly_log#write"] + pub fn write( + endpoint_handle: u32, + msg: *const u8, + msg_len: usize, + nwritten_out: *mut usize, + ) -> FastlyStatus { + let msg = unsafe { slice::from_raw_parts(msg, msg_len) }; + match fastly::api::log::write(endpoint_handle, msg) { + Ok(res) => { + unsafe { + *nwritten_out = usize::try_from(res).trapping_unwrap(); + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } +} + +pub mod fastly_http_req { + use core::slice; + + use super::*; + use crate::{ + bindings::fastly::{ + self, + api::{http_req, http_types}, + }, + TrappingUnwrap, + }; + + bitflags::bitflags! { + #[derive(Default, Clone, Debug, PartialEq, Eq)] + #[repr(transparent)] + pub struct SendErrorDetailMask: u32 { + const RESERVED = 1 << 0; + const DNS_ERROR_RCODE = 1 << 1; + const DNS_ERROR_INFO_CODE = 1 << 2; + const TLS_ALERT_ID = 1 << 3; + } + } + + #[repr(u32)] + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub enum SendErrorDetailTag { + Uninitialized, + Ok, + DnsTimeout, + DnsError, + DestinationNotFound, + DestinationUnavailable, + DestinationIpUnroutable, + ConnectionRefused, + ConnectionTerminated, + ConnectionTimeout, + ConnectionLimitReached, + TlsCertificateError, + TlsConfigurationError, + HttpIncompleteResponse, + HttpResponseHeaderSectionTooLarge, + HttpResponseBodyTooLarge, + HttpResponseTimeout, + HttpResponseStatusInvalid, + HttpUpgradeFailed, + HttpProtocolError, + HttpRequestCacheKeyInvalid, + HttpRequestUriInvalid, + InternalError, + TlsAlertReceived, + TlsProtocolError, + } + + #[repr(C)] + #[derive(Clone, Debug, PartialEq, Eq)] + pub struct SendErrorDetail { + pub tag: SendErrorDetailTag, + pub mask: SendErrorDetailMask, + pub dns_error_rcode: u16, + pub dns_error_info_code: u16, + pub tls_alert_id: u8, + } + + impl SendErrorDetail { + pub fn uninitialized_all() -> Self { + Self { + tag: SendErrorDetailTag::Uninitialized, + mask: SendErrorDetailMask::all(), + dns_error_rcode: Default::default(), + dns_error_info_code: Default::default(), + tls_alert_id: Default::default(), + } + } + } + + impl From for fastly::api::http_req::CacheOverrideTag { + fn from(tag: u32) -> Self { + let flag_present = |n: u32| tag & (1 << n) != 0; + + let mut flags = Self::empty(); + flags.set(Self::PASS, flag_present(0)); + flags.set(Self::TTL, flag_present(1)); + flags.set(Self::STALE_WHILE_REVALIDATE, flag_present(2)); + flags.set(Self::PCI, flag_present(3)); + flags + } + } + + impl Into for fastly::api::http_req::ClientCertVerifyResult { + fn into(self) -> u32 { + use fastly::api::http_req::ClientCertVerifyResult; + match self { + ClientCertVerifyResult::Ok => 0, + ClientCertVerifyResult::BadCertificate => 1, + ClientCertVerifyResult::CertificateRevoked => 2, + ClientCertVerifyResult::CertificateExpired => 3, + ClientCertVerifyResult::UnknownCa => 4, + ClientCertVerifyResult::CertificateMissing => 5, + ClientCertVerifyResult::CertificateUnknown => 6, + } + } + } + + impl Into for fastly::api::http_req::SendErrorDetailTag { + fn into(self) -> SendErrorDetailTag { + match self { + http_req::SendErrorDetailTag::Uninitialized => SendErrorDetailTag::Uninitialized, + http_req::SendErrorDetailTag::Ok => SendErrorDetailTag::Ok, + http_req::SendErrorDetailTag::DnsTimeout => SendErrorDetailTag::DnsTimeout, + http_req::SendErrorDetailTag::DnsError => SendErrorDetailTag::DnsError, + http_req::SendErrorDetailTag::DestinationNotFound => { + SendErrorDetailTag::DestinationNotFound + } + http_req::SendErrorDetailTag::DestinationUnavailable => { + SendErrorDetailTag::DestinationUnavailable + } + http_req::SendErrorDetailTag::DestinationIpUnroutable => { + SendErrorDetailTag::DestinationIpUnroutable + } + http_req::SendErrorDetailTag::ConnectionRefused => { + SendErrorDetailTag::ConnectionRefused + } + http_req::SendErrorDetailTag::ConnectionTerminated => { + SendErrorDetailTag::ConnectionTerminated + } + http_req::SendErrorDetailTag::ConnectionTimeout => { + SendErrorDetailTag::ConnectionTimeout + } + http_req::SendErrorDetailTag::ConnectionLimitReached => { + SendErrorDetailTag::ConnectionLimitReached + } + http_req::SendErrorDetailTag::TlsCertificateError => { + SendErrorDetailTag::TlsCertificateError + } + http_req::SendErrorDetailTag::TlsConfigurationError => { + SendErrorDetailTag::TlsConfigurationError + } + http_req::SendErrorDetailTag::HttpIncompleteResponse => { + SendErrorDetailTag::HttpIncompleteResponse + } + http_req::SendErrorDetailTag::HttpResponseHeaderSectionTooLarge => { + SendErrorDetailTag::HttpResponseHeaderSectionTooLarge + } + http_req::SendErrorDetailTag::HttpResponseBodyTooLarge => { + SendErrorDetailTag::HttpResponseBodyTooLarge + } + http_req::SendErrorDetailTag::HttpResponseTimeout => { + SendErrorDetailTag::HttpResponseTimeout + } + http_req::SendErrorDetailTag::HttpResponseStatusInvalid => { + SendErrorDetailTag::HttpResponseStatusInvalid + } + http_req::SendErrorDetailTag::HttpUpgradeFailed => { + SendErrorDetailTag::HttpUpgradeFailed + } + http_req::SendErrorDetailTag::HttpProtocolError => { + SendErrorDetailTag::HttpProtocolError + } + http_req::SendErrorDetailTag::HttpRequestCacheKeyInvalid => { + SendErrorDetailTag::HttpRequestCacheKeyInvalid + } + http_req::SendErrorDetailTag::HttpRequestUriInvalid => { + SendErrorDetailTag::HttpRequestUriInvalid + } + http_req::SendErrorDetailTag::InternalError => SendErrorDetailTag::InternalError, + http_req::SendErrorDetailTag::TlsAlertReceived => { + SendErrorDetailTag::TlsAlertReceived + } + http_req::SendErrorDetailTag::TlsProtocolError => { + SendErrorDetailTag::TlsProtocolError + } + } + } + } + + impl Into for http_req::SendErrorDetailMask { + fn into(self) -> SendErrorDetailMask { + let mut flags = SendErrorDetailMask::empty(); + flags.set( + SendErrorDetailMask::RESERVED, + self.contains(http_req::SendErrorDetailMask::RESERVED), + ); + flags.set( + SendErrorDetailMask::DNS_ERROR_RCODE, + self.contains(http_req::SendErrorDetailMask::DNS_ERROR_RCODE), + ); + flags.set( + SendErrorDetailMask::DNS_ERROR_INFO_CODE, + self.contains(http_req::SendErrorDetailMask::DNS_ERROR_INFO_CODE), + ); + flags.set( + SendErrorDetailMask::TLS_ALERT_ID, + self.contains(http_req::SendErrorDetailMask::TLS_ALERT_ID), + ); + flags + } + } + + impl Default for http_req::SendErrorDetail { + fn default() -> Self { + Self { + tag: http_req::SendErrorDetailTag::Uninitialized, + mask: http_req::SendErrorDetailMask::empty(), + dns_error_rcode: Default::default(), + dns_error_info_code: Default::default(), + tls_alert_id: Default::default(), + } + } + } + + impl Into for http_req::SendErrorDetail { + fn into(self) -> SendErrorDetail { + SendErrorDetail { + tag: self.tag.into(), + mask: self.mask.into(), + dns_error_rcode: self.dns_error_rcode, + dns_error_info_code: self.dns_error_info_code, + tls_alert_id: self.tls_alert_id, + } + } + } + + impl From for http_req::SendErrorDetail { + fn from(tag: http_req::SendErrorDetailTag) -> Self { + Self { + tag, + ..Self::default() + } + } + } + + impl From for SendErrorDetail { + fn from(tag: http_req::SendErrorDetailTag) -> Self { + http_req::SendErrorDetail::from(tag).into() + } + } + + #[export_name = "fastly_http_req#body_downstream_get"] + pub fn body_downstream_get( + req_handle_out: *mut RequestHandle, + body_handle_out: *mut BodyHandle, + ) -> FastlyStatus { + crate::State::with::(|state| { + unsafe { + *req_handle_out = state.request.get().trapping_unwrap(); + *body_handle_out = state.request_body.get().trapping_unwrap(); + } + Ok(()) + }) + } + + #[export_name = "fastly_http_req#cache_override_set"] + pub fn cache_override_set( + req_handle: RequestHandle, + tag: u32, + ttl: u32, + swr: u32, + ) -> FastlyStatus { + use fastly::api::http_req::CacheOverrideTag; + let tag = CacheOverrideTag::from(tag); + convert_result(fastly::api::http_req::cache_override_set( + req_handle, tag, ttl, swr, + )) + } + + #[export_name = "fastly_http_req#cache_override_v2_set"] + pub fn cache_override_v2_set( + req_handle: RequestHandle, + tag: u32, + ttl: u32, + swr: u32, + sk: *const u8, + sk_len: usize, + ) -> FastlyStatus { + use fastly::api::http_req::CacheOverrideTag; + let tag = CacheOverrideTag::from(tag); + let sk = unsafe { slice::from_raw_parts(sk, sk_len) }; + convert_result(fastly::api::http_req::cache_override_v2_set( + req_handle, + tag, + ttl, + swr, + (!sk.is_empty()).then_some(sk), + )) + } + + #[export_name = "fastly_http_req#framing_headers_mode_set"] + pub fn framing_headers_mode_set( + req_handle: RequestHandle, + mode: FramingHeadersMode, + ) -> FastlyStatus { + let mode = match mode { + FramingHeadersMode::Automatic => fastly::api::http_types::FramingHeadersMode::Automatic, + FramingHeadersMode::ManuallyFromHeaders => { + fastly::api::http_types::FramingHeadersMode::ManuallyFromHeaders + } + }; + + convert_result(fastly::api::http_req::framing_headers_mode_set( + req_handle, mode, + )) + } + + #[export_name = "fastly_http_req#downstream_client_ip_addr"] + pub fn downstream_client_ip_addr( + addr_octets_out: *mut u8, + nwritten_out: *mut usize, + ) -> FastlyStatus { + alloc_result!(addr_octets_out, 16, nwritten_out, { + fastly::api::http_req::downstream_client_ip_addr() + }) + } + + #[export_name = "fastly_http_req#downstream_server_ip_addr"] + pub fn downstream_server_ip_addr( + addr_octets_out: *mut u8, + nwritten_out: *mut usize, + ) -> FastlyStatus { + alloc_result!(addr_octets_out, 16, nwritten_out, { + fastly::api::http_req::downstream_server_ip_addr() + }) + } + + #[export_name = "fastly_http_req#downstream_client_h2_fingerprint"] + pub fn downstream_client_h2_fingerprint( + h2fp_out: *mut u8, + h2fp_max_len: usize, + nwritten: *mut usize, + ) -> FastlyStatus { + alloc_result!(h2fp_out, h2fp_max_len, nwritten, { + fastly::api::http_req::downstream_client_h2_fingerprint( + u64::try_from(h2fp_max_len).trapping_unwrap(), + ) + }) + } + + #[export_name = "fastly_http_req#downstream_client_request_id"] + pub fn downstream_client_request_id( + reqid_out: *mut u8, + reqid_max_len: usize, + nwritten: *mut usize, + ) -> FastlyStatus { + alloc_result!(reqid_out, reqid_max_len, nwritten, { + fastly::api::http_req::downstream_client_request_id( + u64::try_from(reqid_max_len).trapping_unwrap(), + ) + }) + } + + #[export_name = "fastly_http_req#downstream_client_oh_fingerprint"] + pub fn downstream_client_oh_fingerprint( + ohfp_out: *mut u8, + ohfp_max_len: usize, + nwritten: *mut usize, + ) -> FastlyStatus { + alloc_result!(ohfp_out, ohfp_max_len, nwritten, { + fastly::api::http_req::downstream_client_oh_fingerprint( + u64::try_from(ohfp_max_len).trapping_unwrap(), + ) + }) + } + + #[export_name = "fastly_http_req#downstream_tls_cipher_openssl_name"] + pub fn downstream_tls_cipher_openssl_name( + cipher_out: *mut u8, + cipher_max_len: usize, + nwritten: *mut usize, + ) -> FastlyStatus { + alloc_result!(cipher_out, cipher_max_len, nwritten, { + fastly::api::http_req::downstream_tls_cipher_openssl_name( + u64::try_from(cipher_max_len).trapping_unwrap(), + ) + }) + } + + #[export_name = "fastly_http_req#downstream_tls_protocol"] + pub fn downstream_tls_protocol( + protocol_out: *mut u8, + protocol_max_len: usize, + nwritten: *mut usize, + ) -> FastlyStatus { + alloc_result!(protocol_out, protocol_max_len, nwritten, { + fastly::api::http_req::downstream_tls_protocol( + u64::try_from(protocol_max_len).trapping_unwrap(), + ) + }) + } + + #[export_name = "fastly_http_req#downstream_tls_client_hello"] + pub fn downstream_tls_client_hello( + client_hello_out: *mut u8, + client_hello_max_len: usize, + nwritten: *mut usize, + ) -> FastlyStatus { + alloc_result!(client_hello_out, client_hello_max_len, nwritten, { + fastly::api::http_req::downstream_tls_client_hello( + u64::try_from(client_hello_max_len).trapping_unwrap(), + ) + }) + } + + #[export_name = "fastly_http_req#downstream_tls_ja3_md5"] + pub fn downstream_tls_ja3_md5(ja3_md5_out: *mut u8, nwritten_out: *mut usize) -> FastlyStatus { + alloc_result!(ja3_md5_out, 16, nwritten_out, { + fastly::api::http_req::downstream_tls_ja3_md5() + }) + } + + #[export_name = "fastly_http_req#downstream_tls_ja4"] + pub fn downstream_tls_ja4( + ja4_out: *mut u8, + ja4_max_len: usize, + nwritten: *mut usize, + ) -> FastlyStatus { + alloc_result!(ja4_out, ja4_max_len, nwritten, { + fastly::api::http_req::downstream_tls_ja4(u64::try_from(ja4_max_len).trapping_unwrap()) + }) + } + + #[export_name = "fastly_http_req#downstream_compliance_region"] + pub fn downstream_compliance_region( + region_out: *mut u8, + region_max_len: usize, + nwritten: *mut usize, + ) -> FastlyStatus { + alloc_result!(region_out, region_max_len, nwritten, { + fastly::api::http_req::downstream_compliance_region( + u64::try_from(region_max_len).trapping_unwrap(), + ) + }) + } + + #[export_name = "fastly_http_req#downstream_tls_raw_client_certificate"] + pub fn downstream_tls_raw_client_certificate( + client_certificate_out: *mut u8, + client_certificate_max_len: usize, + nwritten: *mut usize, + ) -> FastlyStatus { + alloc_result!( + client_certificate_out, + client_certificate_max_len, + nwritten, + { + fastly::api::http_req::downstream_tls_raw_client_certificate( + u64::try_from(client_certificate_max_len).trapping_unwrap(), + ) + } + ) + } + + #[export_name = "fastly_http_req#downstream_tls_client_cert_verify_result"] + pub fn downstream_tls_client_cert_verify_result(verify_result_out: *mut u32) -> FastlyStatus { + match fastly::api::http_req::downstream_tls_client_cert_verify_result() { + Ok(res) => { + unsafe { + *verify_result_out = res.into(); + } + + FastlyStatus::OK + } + + Err(e) => e.into(), + } + } + + #[export_name = "fastly_http_req#header_append"] + pub fn header_append( + req_handle: RequestHandle, + name: *const u8, + name_len: usize, + value: *const u8, + value_len: usize, + ) -> FastlyStatus { + let name = unsafe { slice::from_raw_parts(name, name_len) }; + let value = unsafe { slice::from_raw_parts(value, value_len) }; + convert_result(fastly::api::http_req::header_append( + req_handle, name, value, + )) + } + + #[export_name = "fastly_http_req#header_insert"] + pub fn header_insert( + req_handle: RequestHandle, + name: *const u8, + name_len: usize, + value: *const u8, + value_len: usize, + ) -> FastlyStatus { + let name = unsafe { slice::from_raw_parts(name, name_len) }; + let value = unsafe { slice::from_raw_parts(value, value_len) }; + convert_result(fastly::api::http_req::header_insert( + req_handle, name, value, + )) + } + + #[export_name = "fastly_http_req#original_header_names_get"] + pub fn original_header_names_get( + buf: *mut u8, + buf_len: usize, + cursor: u32, + ending_cursor: *mut i64, + nwritten: *mut usize, + ) -> FastlyStatus { + with_buffer!( + buf, + buf_len, + { + fastly::api::http_req::original_header_names_get( + u64::try_from(buf_len).trapping_unwrap(), + cursor, + ) + }, + |res| { + let (written, end) = match res { + Some((bytes, next)) => { + let written = bytes.len(); + let end = match next { + Some(next) => i64::from(next), + None => -1, + }; + + std::mem::forget(bytes); + + (written, end) + } + None => (0, -1), + }; + + unsafe { + *nwritten = written; + *ending_cursor = end; + } + } + ) + } + + #[export_name = "fastly_http_req#original_header_count"] + pub fn original_header_count(count_out: *mut u32) -> FastlyStatus { + match fastly::api::http_req::original_header_count() { + Ok(count) => { + unsafe { + *count_out = count; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_http_req#header_names_get"] + pub fn header_names_get( + req_handle: RequestHandle, + buf: *mut u8, + buf_len: usize, + cursor: u32, + ending_cursor: *mut i64, + nwritten: *mut usize, + ) -> FastlyStatus { + with_buffer!( + buf, + buf_len, + { + fastly::api::http_req::header_names_get( + req_handle, + u64::try_from(buf_len).trapping_unwrap(), + cursor, + ) + }, + |res| { + let (written, end) = match res { + Some((bytes, next)) => { + let written = bytes.len(); + let end = match next { + Some(next) => i64::from(next), + None => -1, + }; + + std::mem::forget(bytes); + + (written, end) + } + None => (0, -1), + }; + + unsafe { + *nwritten = written; + *ending_cursor = end; + } + } + ) + } + + #[export_name = "fastly_http_req#header_values_get"] + pub fn header_values_get( + req_handle: RequestHandle, + name: *const u8, + name_len: usize, + buf: *mut u8, + buf_len: usize, + cursor: u32, + ending_cursor: *mut i64, + nwritten: *mut usize, + ) -> FastlyStatus { + with_buffer!( + buf, + buf_len, + { + fastly::api::http_req::header_values_get( + req_handle, + unsafe { slice::from_raw_parts(name, name_len) }, + u64::try_from(buf_len).trapping_unwrap(), + cursor, + ) + }, + |res| { + let (written, end) = match res { + Some((bytes, next)) => { + let written = bytes.len(); + let end = match next { + Some(next) => i64::from(next), + None => -1, + }; + + std::mem::forget(bytes); + + (written, end) + } + None => (0, -1), + }; + + unsafe { + *nwritten = written; + *ending_cursor = end; + } + } + ) + } + + #[export_name = "fastly_http_req#header_values_set"] + pub fn header_values_set( + req_handle: RequestHandle, + name: *const u8, + name_len: usize, + values: *const u8, + values_len: usize, + ) -> FastlyStatus { + convert_result(fastly::api::http_req::header_values_set( + req_handle, + unsafe { slice::from_raw_parts(name, name_len) }, + unsafe { slice::from_raw_parts(values, values_len) }, + )) + } + + #[export_name = "fastly_http_req#header_value_get"] + pub fn header_value_get( + req_handle: RequestHandle, + name: *const u8, + name_len: usize, + value: *mut u8, + value_max_len: usize, + nwritten: *mut usize, + ) -> FastlyStatus { + let name = unsafe { slice::from_raw_parts(name, name_len) }; + with_buffer!( + value, + value_max_len, + { + fastly::api::http_req::header_value_get( + req_handle, + name, + u64::try_from(value_max_len).trapping_unwrap(), + ) + }, + |res| { + let res = res.ok_or(FastlyStatus::NONE)?; + unsafe { + *nwritten = res.len(); + } + std::mem::forget(res); + } + ) + } + + #[export_name = "fastly_http_req#header_remove"] + pub fn header_remove( + req_handle: RequestHandle, + name: *const u8, + name_len: usize, + ) -> FastlyStatus { + let name = unsafe { slice::from_raw_parts(name, name_len) }; + convert_result(fastly::api::http_req::header_remove(req_handle, name)) + } + + #[export_name = "fastly_http_req#method_get"] + pub fn method_get( + req_handle: RequestHandle, + method: *mut u8, + method_max_len: usize, + nwritten: *mut usize, + ) -> FastlyStatus { + alloc_result!(method, method_max_len, nwritten, { + fastly::api::http_req::method_get( + req_handle, + u64::try_from(method_max_len).trapping_unwrap(), + ) + }) + } + + #[export_name = "fastly_http_req#method_set"] + pub fn method_set( + req_handle: RequestHandle, + method: *const u8, + method_len: usize, + ) -> FastlyStatus { + let method = unsafe { slice::from_raw_parts(method, method_len) }; + convert_result(fastly::api::http_req::method_set(req_handle, method)) + } + + #[export_name = "fastly_http_req#new"] + pub fn new(req_handle_out: *mut RequestHandle) -> FastlyStatus { + match fastly::api::http_req::new() { + Ok(res) => { + unsafe { + *req_handle_out = res; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_http_req#send"] + pub fn send( + req_handle: RequestHandle, + body_handle: BodyHandle, + backend: *const u8, + backend_len: usize, + resp_handle_out: *mut ResponseHandle, + resp_body_handle_out: *mut BodyHandle, + ) -> FastlyStatus { + let backend = unsafe { slice::from_raw_parts(backend, backend_len) }; + match fastly::api::http_req::send(req_handle, body_handle, backend) { + Ok((resp_handle, resp_body_handle)) => { + unsafe { + *resp_handle_out = resp_handle; + *resp_body_handle_out = resp_body_handle; + } + + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_http_req#send_v2"] + pub fn send_v2( + req_handle: RequestHandle, + body_handle: BodyHandle, + backend: *const u8, + backend_len: usize, + error_detail: *mut SendErrorDetail, + resp_handle_out: *mut ResponseHandle, + resp_body_handle_out: *mut BodyHandle, + ) -> FastlyStatus { + let backend = unsafe { slice::from_raw_parts(backend, backend_len) }; + match fastly::api::http_req::send_v2(req_handle, body_handle, backend) { + Ok((resp_handle, resp_body_handle)) => { + unsafe { + *error_detail = http_req::SendErrorDetailTag::Ok.into(); + *resp_handle_out = resp_handle; + *resp_body_handle_out = resp_body_handle; + } + + FastlyStatus::OK + } + Err((detail, e)) => { + unsafe { + *error_detail = detail + .unwrap_or_else(|| http_req::SendErrorDetailTag::Ok.into()) + .into(); + *resp_handle_out = INVALID_HANDLE; + *resp_body_handle_out = INVALID_HANDLE; + } + + e.into() + } + } + } + + #[export_name = "fastly_http_req#send_async"] + pub fn send_async( + req_handle: RequestHandle, + body_handle: BodyHandle, + backend: *const u8, + backend_len: usize, + pending_req_handle_out: *mut PendingRequestHandle, + ) -> FastlyStatus { + let backend = unsafe { slice::from_raw_parts(backend, backend_len) }; + match http_req::send_async(req_handle, body_handle, backend) { + Ok(res) => { + unsafe { + *pending_req_handle_out = res; + } + + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_http_req#send_async_streaming"] + pub fn send_async_streaming( + req_handle: RequestHandle, + body_handle: BodyHandle, + backend: *const u8, + backend_len: usize, + pending_req_handle_out: *mut PendingRequestHandle, + ) -> FastlyStatus { + let backend = unsafe { slice::from_raw_parts(backend, backend_len) }; + match http_req::send_async_streaming(req_handle, body_handle, backend) { + Ok(res) => { + unsafe { + *pending_req_handle_out = res; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_http_req#upgrade_websocket"] + pub fn upgrade_websocket(backend: *const u8, backend_len: usize) -> FastlyStatus { + let backend = unsafe { slice::from_raw_parts(backend, backend_len) }; + convert_result(http_req::upgrade_websocket(backend)) + } + + #[export_name = "fastly_http_req#redirect_to_websocket_proxy"] + pub fn redirect_to_websocket_proxy(backend: *const u8, backend_len: usize) -> FastlyStatus { + let backend = unsafe { slice::from_raw_parts(backend, backend_len) }; + convert_result(http_req::redirect_to_websocket_proxy(backend)) + } + + #[export_name = "fastly_http_req#redirect_to_websocket_proxy_v2"] + pub fn redirect_to_websocket_proxy_v2( + req: RequestHandle, + backend: *const u8, + backend_len: usize, + ) -> FastlyStatus { + let backend = unsafe { slice::from_raw_parts(backend, backend_len) }; + convert_result(http_req::redirect_to_websocket_proxy_v2(req, backend)) + } + + #[export_name = "fastly_http_req#redirect_to_grip_proxy"] + pub fn redirect_to_grip_proxy(backend: *const u8, backend_len: usize) -> FastlyStatus { + let backend = unsafe { slice::from_raw_parts(backend, backend_len) }; + convert_result(http_req::redirect_to_grip_proxy(backend)) + } + + #[export_name = "fastly_http_req#redirect_to_grip_proxy_v2"] + pub fn redirect_to_grip_proxy_v2( + req: RequestHandle, + backend: *const u8, + backend_len: usize, + ) -> FastlyStatus { + let backend = unsafe { slice::from_raw_parts(backend, backend_len) }; + convert_result(http_req::redirect_to_grip_proxy_v2(req, backend)) + } + + #[export_name = "fastly_http_req#register_dynamic_backend"] + pub fn register_dynamic_backend( + name_prefix: *const u8, + name_prefix_len: usize, + target: *const u8, + target_len: usize, + config_mask: BackendConfigOptions, + config: *const DynamicBackendConfig, + ) -> FastlyStatus { + let name_prefix = unsafe { slice::from_raw_parts(name_prefix, name_prefix_len) }; + let target = unsafe { slice::from_raw_parts(target, target_len) }; + + let options = http_types::BackendConfigOptions::from(config_mask); + + // NOTE: this is only really safe because we never mutate the vectors -- we only need + // vectors to satisfy the interface produced by the DynamicBackendConfig record, + // `register_dynamic_backend` will never mutate the vectors it's given. + macro_rules! make_vec { + ($ptr_field:ident, $len_field:ident) => { + unsafe { + let len = usize::try_from((*config).$len_field).trapping_unwrap(); + Vec::from_raw_parts((*config).$ptr_field as *mut _, len, len) + } + }; + } + + let config = http_req::DynamicBackendConfig { + host_override: make_vec!(host_override, host_override_len), + connect_timeout: unsafe { (*config).connect_timeout_ms }, + first_byte_timeout: unsafe { (*config).first_byte_timeout_ms }, + between_bytes_timeout: unsafe { (*config).between_bytes_timeout_ms }, + ssl_min_version: unsafe { (*config).ssl_min_version }.try_into().ok(), + ssl_max_version: unsafe { (*config).ssl_max_version }.try_into().ok(), + cert_hostname: make_vec!(cert_hostname, cert_hostname_len), + ca_cert: make_vec!(ca_cert, ca_cert_len), + ciphers: make_vec!(ciphers, ciphers_len), + sni_hostname: make_vec!(sni_hostname, sni_hostname_len), + client_cert: make_vec!(client_certificate, client_certificate_len), + client_key: unsafe { (*config).client_key }, + }; + + let res = http_req::register_dynamic_backend(name_prefix, target, options, &config); + + std::mem::forget(config); + + convert_result(res) + } + + #[export_name = "fastly_http_req#uri_get"] + pub fn uri_get( + req_handle: RequestHandle, + uri: *mut u8, + uri_max_len: usize, + nwritten: *mut usize, + ) -> FastlyStatus { + alloc_result!(uri, uri_max_len, nwritten, { + fastly::api::http_req::uri_get(req_handle, u64::try_from(uri_max_len).trapping_unwrap()) + }) + } + + #[export_name = "fastly_http_req#uri_set"] + pub fn uri_set(req_handle: RequestHandle, uri: *const u8, uri_len: usize) -> FastlyStatus { + let uri = unsafe { slice::from_raw_parts(uri, uri_len) }; + convert_result(http_req::uri_set(req_handle, uri)) + } + + #[export_name = "fastly_http_req#version_get"] + pub fn version_get(req_handle: RequestHandle, version: *mut u32) -> FastlyStatus { + match fastly::api::http_req::version_get(req_handle) { + Ok(res) => { + unsafe { + *version = res.into(); + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_http_req#version_set"] + pub fn version_set(req_handle: RequestHandle, version: u32) -> FastlyStatus { + match http_types::HttpVersion::try_from(version) { + Ok(version) => convert_result(crate::bindings::fastly::api::http_req::version_set( + req_handle, version, + )), + + Err(_) => FastlyStatus::INVALID_ARGUMENT, + } + } + + #[export_name = "fastly_http_req#pending_req_poll_v2"] + pub fn pending_req_poll_v2( + pending_req_handle: PendingRequestHandle, + error_detail: *mut SendErrorDetail, + is_done_out: *mut i32, + resp_handle_out: *mut ResponseHandle, + resp_body_handle_out: *mut BodyHandle, + ) -> FastlyStatus { + match http_req::pending_req_poll_v2(pending_req_handle) { + Ok(res) => unsafe { + *error_detail = http_req::SendErrorDetailTag::Ok.into(); + match res { + Some((resp_handle, resp_body_handle)) => { + *is_done_out = 1; + *resp_handle_out = resp_handle; + *resp_body_handle_out = resp_body_handle; + } + + None => { + *is_done_out = 0; + *resp_handle_out = INVALID_HANDLE; + *resp_body_handle_out = INVALID_HANDLE; + } + } + + FastlyStatus::OK + }, + Err((detail, e)) => { + unsafe { + *error_detail = detail + .unwrap_or_else(|| http_req::SendErrorDetailTag::Ok.into()) + .into(); + *is_done_out = 0; + *resp_handle_out = INVALID_HANDLE; + *resp_body_handle_out = INVALID_HANDLE; + } + e.into() + } + } + } + + #[export_name = "fastly_http_req#pending_req_select"] + pub fn pending_req_select( + pending_req_handles: *const PendingRequestHandle, + pending_req_handles_len: usize, + done_index_out: *mut i32, + resp_handle_out: *mut ResponseHandle, + resp_body_handle_out: *mut BodyHandle, + ) -> FastlyStatus { + let pending_req_handles = + unsafe { slice::from_raw_parts(pending_req_handles, pending_req_handles_len) }; + match http_req::pending_req_select(pending_req_handles) { + Ok((idx, (resp_handle, resp_body_handle))) => { + unsafe { + *done_index_out = i32::try_from(idx).trapping_unwrap(); + *resp_handle_out = resp_handle; + *resp_body_handle_out = resp_body_handle; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_http_req#pending_req_select_v2"] + pub fn pending_req_select_v2( + pending_req_handles: *const PendingRequestHandle, + pending_req_handles_len: usize, + error_detail: *mut SendErrorDetail, + done_index_out: *mut i32, + resp_handle_out: *mut ResponseHandle, + resp_body_handle_out: *mut BodyHandle, + ) -> FastlyStatus { + let pending_req_handles = + unsafe { slice::from_raw_parts(pending_req_handles, pending_req_handles_len) }; + match http_req::pending_req_select_v2(pending_req_handles) { + Ok((idx, (resp_handle, resp_body_handle))) => { + unsafe { + *done_index_out = i32::try_from(idx).trapping_unwrap(); + *error_detail = http_req::SendErrorDetailTag::Ok.into(); + *resp_handle_out = resp_handle; + *resp_body_handle_out = resp_body_handle; + } + FastlyStatus::OK + } + Err((detail, e)) => { + unsafe { + *error_detail = detail + .unwrap_or_else(|| http_req::SendErrorDetailTag::Ok.into()) + .into(); + *resp_handle_out = INVALID_HANDLE; + *resp_body_handle_out = INVALID_HANDLE; + } + e.into() + } + } + } + + #[export_name = "fastly_http_req#pending_req_wait_v2"] + pub fn pending_req_wait_v2( + pending_req_handle: PendingRequestHandle, + error_detail: *mut SendErrorDetail, + resp_handle_out: *mut ResponseHandle, + resp_body_handle_out: *mut BodyHandle, + ) -> FastlyStatus { + match http_req::pending_req_wait_v2(pending_req_handle) { + Ok((resp_handle, resp_body_handle)) => { + unsafe { + *error_detail = http_req::SendErrorDetailTag::Ok.into(); + *resp_handle_out = resp_handle; + *resp_body_handle_out = resp_body_handle; + } + + FastlyStatus::OK + } + Err((detail, e)) => { + unsafe { + *error_detail = detail + .unwrap_or_else(|| http_req::SendErrorDetailTag::Ok.into()) + .into(); + *resp_handle_out = INVALID_HANDLE; + *resp_body_handle_out = INVALID_HANDLE; + } + e.into() + } + } + } + + #[export_name = "fastly_http_req#fastly_key_is_valid"] + pub fn fastly_key_is_valid(is_valid_out: *mut u32) -> FastlyStatus { + match http_req::fastly_key_is_valid() { + Ok(res) => { + unsafe { + *is_valid_out = u32::from(res); + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_http_req#close"] + pub fn close(req_handle: RequestHandle) -> FastlyStatus { + convert_result(http_req::close(req_handle)) + } + + #[export_name = "fastly_http_req#auto_decompress_response_set"] + pub fn auto_decompress_response_set( + req_handle: RequestHandle, + encodings: ContentEncodings, + ) -> FastlyStatus { + convert_result(http_req::auto_decompress_response_set( + req_handle, + encodings.into(), + )) + } +} + +pub mod fastly_http_resp { + use core::slice; + + use super::*; + use crate::bindings::fastly::{self, api::http_resp}; + + #[export_name = "fastly_http_resp#header_append"] + pub fn header_append( + resp_handle: ResponseHandle, + name: *const u8, + name_len: usize, + value: *const u8, + value_len: usize, + ) -> FastlyStatus { + let name = unsafe { slice::from_raw_parts(name, name_len) }; + let value = unsafe { slice::from_raw_parts(value, value_len) }; + convert_result(http_resp::header_append(resp_handle, name, value)) + } + + #[export_name = "fastly_http_resp#header_insert"] + pub fn header_insert( + resp_handle: ResponseHandle, + name: *const u8, + name_len: usize, + value: *const u8, + value_len: usize, + ) -> FastlyStatus { + let name = unsafe { slice::from_raw_parts(name, name_len) }; + let value = unsafe { slice::from_raw_parts(value, value_len) }; + convert_result(http_resp::header_insert(resp_handle, name, value)) + } + + #[export_name = "fastly_http_resp#header_names_get"] + pub fn header_names_get( + resp_handle: ResponseHandle, + buf: *mut u8, + buf_len: usize, + cursor: u32, + ending_cursor: *mut i64, + nwritten: *mut usize, + ) -> FastlyStatus { + with_buffer!( + buf, + buf_len, + { + http_resp::header_names_get( + resp_handle, + u64::try_from(buf_len).trapping_unwrap(), + cursor, + ) + }, + |res| { + let (written, end) = match res { + Some((bytes, next)) => { + let written = bytes.len(); + let end = match next { + Some(next) => i64::from(next), + None => -1, + }; + + std::mem::forget(bytes); + + (written, end) + } + None => (0, -1), + }; + + unsafe { + *nwritten = written; + *ending_cursor = end; + } + } + ) + } + + #[export_name = "fastly_http_resp#header_value_get"] + pub fn header_value_get( + resp_handle: ResponseHandle, + name: *const u8, + name_len: usize, + value: *mut u8, + value_max_len: usize, + nwritten: *mut usize, + ) -> FastlyStatus { + let name = unsafe { slice::from_raw_parts(name, name_len) }; + with_buffer!( + value, + value_max_len, + { + http_resp::header_value_get( + resp_handle, + name, + u64::try_from(value_max_len).trapping_unwrap(), + ) + }, + |res| { + let res = res.ok_or(FastlyStatus::NONE)?; + unsafe { + *nwritten = res.len(); + } + std::mem::forget(res); + } + ) + } + + #[export_name = "fastly_http_resp#header_values_get"] + pub fn header_values_get( + resp_handle: ResponseHandle, + name: *const u8, + name_len: usize, + buf: *mut u8, + buf_len: usize, + cursor: u32, + ending_cursor: *mut i64, + nwritten: *mut usize, + ) -> FastlyStatus { + let name = unsafe { slice::from_raw_parts(name, name_len) }; + with_buffer!( + buf, + buf_len, + { + http_resp::header_values_get( + resp_handle, + name, + u64::try_from(buf_len).trapping_unwrap(), + cursor, + ) + }, + |res| { + let (written, end) = match res { + Some((bytes, next)) => { + let written = bytes.len(); + let end = match next { + Some(next) => i64::from(next), + None => -1, + }; + + std::mem::forget(bytes); + + (written, end) + } + None => (0, -1), + }; + + unsafe { + *nwritten = written; + *ending_cursor = end; + } + } + ) + } + + #[export_name = "fastly_http_resp#header_values_set"] + pub fn header_values_set( + resp_handle: ResponseHandle, + name: *const u8, + name_len: usize, + values: *const u8, + values_len: usize, + ) -> FastlyStatus { + let name = unsafe { slice::from_raw_parts(name, name_len) }; + let values = unsafe { slice::from_raw_parts(values, values_len) }; + convert_result(http_resp::header_values_set(resp_handle, name, values)) + } + + #[export_name = "fastly_http_resp#header_remove"] + pub fn header_remove( + resp_handle: ResponseHandle, + name: *const u8, + name_len: usize, + ) -> FastlyStatus { + let name = unsafe { slice::from_raw_parts(name, name_len) }; + convert_result(http_resp::header_remove(resp_handle, name)) + } + + #[export_name = "fastly_http_resp#new"] + pub fn new(handle_out: *mut ResponseHandle) -> FastlyStatus { + match fastly::api::http_resp::new() { + Ok(handle) => { + unsafe { + *handle_out = handle; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_http_resp#send_downstream"] + pub fn send_downstream( + resp_handle: ResponseHandle, + body_handle: BodyHandle, + streaming: u32, + ) -> FastlyStatus { + convert_result(fastly::api::http_resp::send_downstream( + resp_handle, + body_handle, + streaming != 0, + )) + } + + #[export_name = "fastly_http_resp#status_get"] + pub fn status_get(resp_handle: ResponseHandle, status: *mut u16) -> FastlyStatus { + match http_resp::status_get(resp_handle) { + Ok(res) => { + unsafe { + *status = res; + } + + FastlyStatus::OK + } + + Err(e) => e.into(), + } + } + + #[export_name = "fastly_http_resp#status_set"] + pub fn status_set(resp_handle: ResponseHandle, status: u16) -> FastlyStatus { + convert_result(fastly::api::http_resp::status_set(resp_handle, status)) + } + + #[export_name = "fastly_http_resp#version_get"] + pub fn version_get(resp_handle: ResponseHandle, version: *mut u32) -> FastlyStatus { + match fastly::api::http_resp::version_get(resp_handle) { + Ok(res) => { + unsafe { + *version = res.into(); + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_http_resp#version_set"] + pub fn version_set(resp_handle: ResponseHandle, version: u32) -> FastlyStatus { + match crate::bindings::fastly::api::http_types::HttpVersion::try_from(version) { + Ok(version) => convert_result(crate::bindings::fastly::api::http_resp::version_set( + resp_handle, + version, + )), + + Err(_) => FastlyStatus::INVALID_ARGUMENT, + } + } + + #[export_name = "fastly_http_resp#framing_headers_mode_set"] + pub fn framing_headers_mode_set( + resp_handle: ResponseHandle, + mode: FramingHeadersMode, + ) -> FastlyStatus { + let mode = match mode { + FramingHeadersMode::Automatic => fastly::api::http_types::FramingHeadersMode::Automatic, + FramingHeadersMode::ManuallyFromHeaders => { + fastly::api::http_types::FramingHeadersMode::ManuallyFromHeaders + } + }; + + convert_result(fastly::api::http_resp::framing_headers_mode_set( + resp_handle, + mode, + )) + } + + #[doc(hidden)] + #[export_name = "fastly_http_resp#http_keepalive_mode_set"] + pub fn http_keepalive_mode_set( + resp_handle: ResponseHandle, + mode: HttpKeepaliveMode, + ) -> FastlyStatus { + let mode = match mode { + HttpKeepaliveMode::Automatic => fastly::api::http_resp::KeepaliveMode::Automatic, + HttpKeepaliveMode::NoKeepalive => fastly::api::http_resp::KeepaliveMode::NoKeepalive, + }; + + convert_result(fastly::api::http_resp::http_keepalive_mode_set( + resp_handle, + mode, + )) + } + + #[export_name = "fastly_http_resp#close"] + pub fn close(resp_handle: ResponseHandle) -> FastlyStatus { + convert_result(fastly::api::http_resp::close(resp_handle)) + } +} + +pub mod fastly_dictionary { + use core::slice; + + use super::*; + use crate::bindings::fastly::api::dictionary; + + #[export_name = "fastly_dictionary#open"] + pub fn open( + name: *const u8, + name_len: usize, + dict_handle_out: *mut DictionaryHandle, + ) -> FastlyStatus { + let name = unsafe { slice::from_raw_parts(name, name_len) }; + match dictionary::open(name) { + Ok(res) => { + unsafe { + *dict_handle_out = res; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_dictionary#get"] + pub fn get( + dict_handle: DictionaryHandle, + key: *const u8, + key_len: usize, + value: *mut u8, + value_max_len: usize, + nwritten: *mut usize, + ) -> FastlyStatus { + let key = unsafe { slice::from_raw_parts(key, key_len) }; + with_buffer!( + value, + value_max_len, + { + dictionary::get( + dict_handle, + key, + u64::try_from(value_max_len).trapping_unwrap(), + ) + }, + |res| { + let res = res.ok_or(FastlyStatus::NONE)?; + unsafe { + *nwritten = res.len(); + } + std::mem::forget(res) + } + ) + } +} + +pub mod fastly_geo { + use super::*; + use crate::bindings::fastly::api::geo; + use core::slice; + + #[export_name = "fastly_geo#lookup"] + pub fn lookup( + addr_octets: *const u8, + addr_len: usize, + buf: *mut u8, + buf_len: usize, + nwritten_out: *mut usize, + ) -> FastlyStatus { + let addr = unsafe { slice::from_raw_parts(addr_octets, addr_len) }; + alloc_result!(buf, buf_len, nwritten_out, { + geo::lookup(addr, u64::try_from(buf_len).trapping_unwrap()) + }) + } +} + +pub mod fastly_device_detection { + use super::*; + use crate::bindings::fastly::api::device_detection; + use core::slice; + + #[export_name = "fastly_device_detection#lookup"] + pub fn lookup( + user_agent: *const u8, + user_agent_max_len: usize, + buf: *mut u8, + buf_len: usize, + nwritten_out: *mut usize, + ) -> FastlyStatus { + let user_agent = unsafe { slice::from_raw_parts(user_agent, user_agent_max_len) }; + alloc_result!(buf, buf_len, nwritten_out, { + device_detection::lookup(user_agent, u64::try_from(buf_len).trapping_unwrap()) + }) + } +} + +pub mod fastly_erl { + use super::*; + use crate::bindings::fastly::api::erl; + use core::slice; + + #[export_name = "fastly_erl#check_rate"] + pub fn check_rate( + rc: *const u8, + rc_max_len: usize, + entry: *const u8, + entry_max_len: usize, + delta: u32, + window: u32, + limit: u32, + pb: *const u8, + pb_max_len: usize, + ttl: u32, + value: *mut u32, + ) -> FastlyStatus { + let rc = unsafe { slice::from_raw_parts(rc, rc_max_len) }; + let entry = unsafe { slice::from_raw_parts(entry, entry_max_len) }; + let pb = unsafe { slice::from_raw_parts(pb, pb_max_len) }; + match erl::check_rate(rc, entry, delta, window, limit, pb, ttl) { + Ok(res) => { + unsafe { + *value = res; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_erl#ratecounter_increment"] + pub fn ratecounter_increment( + rc: *const u8, + rc_max_len: usize, + entry: *const u8, + entry_max_len: usize, + delta: u32, + ) -> FastlyStatus { + let rc = unsafe { slice::from_raw_parts(rc, rc_max_len) }; + let entry = unsafe { slice::from_raw_parts(entry, entry_max_len) }; + convert_result(erl::ratecounter_increment(rc, entry, delta)) + } + + #[export_name = "fastly_erl#ratecounter_lookup_rate"] + pub fn ratecounter_lookup_rate( + rc: *const u8, + rc_max_len: usize, + entry: *const u8, + entry_max_len: usize, + window: u32, + value: *mut u32, + ) -> FastlyStatus { + let rc = unsafe { slice::from_raw_parts(rc, rc_max_len) }; + let entry = unsafe { slice::from_raw_parts(entry, entry_max_len) }; + match erl::ratecounter_lookup_rate(rc, entry, window) { + Ok(res) => { + unsafe { + *value = res; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_erl#ratecounter_lookup_count"] + pub fn ratecounter_lookup_count( + rc: *const u8, + rc_max_len: usize, + entry: *const u8, + entry_max_len: usize, + duration: u32, + value: *mut u32, + ) -> FastlyStatus { + let rc = unsafe { slice::from_raw_parts(rc, rc_max_len) }; + let entry = unsafe { slice::from_raw_parts(entry, entry_max_len) }; + match erl::ratecounter_lookup_count(rc, entry, duration) { + Ok(res) => { + unsafe { + *value = res; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_erl#penaltybox_add"] + pub fn penaltybox_add( + pb: *const u8, + pb_max_len: usize, + entry: *const u8, + entry_max_len: usize, + ttl: u32, + ) -> FastlyStatus { + let pb = unsafe { slice::from_raw_parts(pb, pb_max_len) }; + let entry = unsafe { slice::from_raw_parts(entry, entry_max_len) }; + convert_result(erl::penaltybox_add(pb, entry, ttl)) + } + + #[export_name = "fastly_erl#penaltybox_has"] + pub fn penaltybox_has( + pb: *const u8, + pb_max_len: usize, + entry: *const u8, + entry_max_len: usize, + value: *mut u32, + ) -> FastlyStatus { + let pb = unsafe { slice::from_raw_parts(pb, pb_max_len) }; + let entry = unsafe { slice::from_raw_parts(entry, entry_max_len) }; + match erl::penaltybox_has(pb, entry) { + Ok(res) => { + unsafe { + *value = res; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } +} + +pub mod fastly_kv_store { + use super::*; + use crate::bindings::fastly::api::kv_store; + use core::slice; + + #[export_name = "fastly_object_store#open"] + pub fn open( + name_ptr: *const u8, + name_len: usize, + kv_store_handle_out: *mut KVStoreHandle, + ) -> FastlyStatus { + let name = unsafe { slice::from_raw_parts(name_ptr, name_len) }; + match kv_store::open(name) { + Ok(None) => { + unsafe { + *kv_store_handle_out = INVALID_HANDLE; + } + + FastlyStatus::NONE + } + Ok(Some(res)) => { + unsafe { + *kv_store_handle_out = res; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_object_store#lookup"] + pub fn lookup( + kv_store_handle: KVStoreHandle, + key_ptr: *const u8, + key_len: usize, + body_handle_out: *mut BodyHandle, + ) -> FastlyStatus { + let key = unsafe { slice::from_raw_parts(key_ptr, key_len) }; + match kv_store::lookup(kv_store_handle, key) { + Ok(res) => { + unsafe { + *body_handle_out = res.unwrap_or(INVALID_HANDLE); + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_object_store#lookup_async"] + pub fn lookup_async( + kv_store_handle: KVStoreHandle, + key_ptr: *const u8, + key_len: usize, + pending_body_handle_out: *mut PendingObjectStoreLookupHandle, + ) -> FastlyStatus { + let key = unsafe { slice::from_raw_parts(key_ptr, key_len) }; + match kv_store::lookup_async(kv_store_handle, key) { + Ok(res) => { + unsafe { + *pending_body_handle_out = res; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_object_store#pending_lookup_wait"] + pub fn pending_lookup_wait( + pending_body_handle: PendingObjectStoreLookupHandle, + body_handle_out: *mut BodyHandle, + ) -> FastlyStatus { + match kv_store::pending_lookup_wait(pending_body_handle) { + Ok(res) => { + unsafe { + *body_handle_out = res.unwrap_or(INVALID_HANDLE); + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_object_store#insert"] + pub fn insert( + kv_store_handle: KVStoreHandle, + key_ptr: *const u8, + key_len: usize, + body_handle: BodyHandle, + ) -> FastlyStatus { + let key = unsafe { slice::from_raw_parts(key_ptr, key_len) }; + convert_result(kv_store::insert(kv_store_handle, key, body_handle)) + } + + #[export_name = "fastly_object_store#insert_async"] + pub fn insert_async( + kv_store_handle: KVStoreHandle, + key_ptr: *const u8, + key_len: usize, + body_handle: BodyHandle, + pending_body_handle_out: *mut PendingObjectStoreInsertHandle, + ) -> FastlyStatus { + let key = unsafe { slice::from_raw_parts(key_ptr, key_len) }; + match kv_store::insert_async(kv_store_handle, key, body_handle) { + Ok(res) => { + unsafe { + *pending_body_handle_out = res; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_object_store#pending_insert_wait"] + pub fn pending_insert_wait( + pending_body_handle: PendingObjectStoreInsertHandle, + ) -> FastlyStatus { + convert_result(kv_store::pending_insert_wait(pending_body_handle)) + } + + #[export_name = "fastly_object_store#delete_async"] + pub fn delete_async( + kv_store_handle: KVStoreHandle, + key_ptr: *const u8, + key_len: usize, + pending_body_handle_out: *mut PendingObjectStoreDeleteHandle, + ) -> FastlyStatus { + let key = unsafe { slice::from_raw_parts(key_ptr, key_len) }; + match kv_store::delete_async(kv_store_handle, key) { + Ok(res) => { + unsafe { + *pending_body_handle_out = res; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_object_store#pending_delete_wait"] + pub fn pending_delete_wait( + pending_body_handle: PendingObjectStoreDeleteHandle, + ) -> FastlyStatus { + convert_result(kv_store::pending_delete_wait(pending_body_handle)) + } +} + +pub mod fastly_secret_store { + use super::*; + use crate::bindings::fastly::api::secret_store; + use core::slice; + + #[export_name = "fastly_secret_store#open"] + pub fn open( + secret_store_name_ptr: *const u8, + secret_store_name_len: usize, + secret_store_handle_out: *mut SecretStoreHandle, + ) -> FastlyStatus { + let secret_store_name = + unsafe { slice::from_raw_parts(secret_store_name_ptr, secret_store_name_len) }; + match secret_store::open(secret_store_name) { + Ok(res) => { + unsafe { + *secret_store_handle_out = res; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_secret_store#get"] + pub fn get( + secret_store_handle: SecretStoreHandle, + secret_name_ptr: *const u8, + secret_name_len: usize, + secret_handle_out: *mut SecretHandle, + ) -> FastlyStatus { + let secret_name = unsafe { slice::from_raw_parts(secret_name_ptr, secret_name_len) }; + match secret_store::get(secret_store_handle, secret_name) { + Ok(Some(res)) => { + unsafe { + *secret_handle_out = res; + } + FastlyStatus::OK + } + + Ok(None) => FastlyStatus::NONE, + + Err(e) => e.into(), + } + } + + #[export_name = "fastly_secret_store#plaintext"] + pub fn plaintext( + secret_handle: SecretHandle, + plaintext_buf: *mut u8, + plaintext_max_len: usize, + nwritten_out: *mut usize, + ) -> FastlyStatus { + with_buffer!( + plaintext_buf, + plaintext_max_len, + { + secret_store::plaintext( + secret_handle, + u64::try_from(plaintext_max_len).trapping_unwrap(), + ) + }, + |res| { + let res = res.ok_or(FastlyStatus::NONE)?; + unsafe { + *nwritten_out = res.len(); + } + std::mem::forget(res); + } + ) + } + + #[export_name = "fastly_secret_store#from_bytes"] + pub fn from_bytes( + plaintext_buf: *const u8, + plaintext_len: usize, + secret_handle_out: *mut SecretHandle, + ) -> FastlyStatus { + let plaintext = unsafe { slice::from_raw_parts(plaintext_buf, plaintext_len) }; + match secret_store::from_bytes(plaintext) { + Ok(res) => { + unsafe { + *secret_handle_out = res; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } +} + +pub mod fastly_backend { + use super::*; + use crate::bindings::fastly::api::{backend, http_types}; + use core::slice; + + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + #[repr(u32)] + pub enum BackendHealth { + Unknown, + Healthy, + Unhealthy, + } + + impl From for BackendHealth { + fn from(value: backend::BackendHealth) -> Self { + match value { + backend::BackendHealth::Unknown => BackendHealth::Unknown, + backend::BackendHealth::Healthy => BackendHealth::Healthy, + backend::BackendHealth::Unhealthy => BackendHealth::Unhealthy, + } + } + } + + impl From for u32 { + fn from(val: http_types::TlsVersion) -> Self { + match val { + http_types::TlsVersion::Tls1 => 0, + http_types::TlsVersion::Tls11 => 1, + http_types::TlsVersion::Tls12 => 2, + http_types::TlsVersion::Tls13 => 3, + } + } + } + + impl TryFrom for http_types::TlsVersion { + type Error = u32; + + fn try_from(val: u32) -> Result { + match val { + 0 => Ok(http_types::TlsVersion::Tls1), + 1 => Ok(http_types::TlsVersion::Tls11), + 2 => Ok(http_types::TlsVersion::Tls12), + 3 => Ok(http_types::TlsVersion::Tls13), + _ => Err(val), + } + } + } + + #[export_name = "fastly_backend#exists"] + pub fn exists( + backend_ptr: *const u8, + backend_len: usize, + backend_exists_out: *mut u32, + ) -> FastlyStatus { + let backend = unsafe { slice::from_raw_parts(backend_ptr, backend_len) }; + match backend::exists(backend) { + Ok(res) => { + unsafe { + *backend_exists_out = u32::from(res); + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_backend#is_healthy"] + pub fn is_healthy( + backend_ptr: *const u8, + backend_len: usize, + backend_health_out: *mut BackendHealth, + ) -> FastlyStatus { + let backend = unsafe { slice::from_raw_parts(backend_ptr, backend_len) }; + match backend::is_healthy(backend) { + Ok(res) => { + unsafe { + *backend_health_out = BackendHealth::from(res); + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_backend#is_dynamic"] + pub fn is_dynamic(backend_ptr: *const u8, backend_len: usize, value: *mut u32) -> FastlyStatus { + let backend = unsafe { slice::from_raw_parts(backend_ptr, backend_len) }; + match backend::is_dynamic(backend) { + Ok(res) => { + unsafe { + *value = u32::from(res); + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_backend#get_host"] + pub fn get_host( + backend_ptr: *const u8, + backend_len: usize, + value: *mut u8, + value_max_len: usize, + nwritten: *mut usize, + ) -> FastlyStatus { + let backend = unsafe { slice::from_raw_parts(backend_ptr, backend_len) }; + alloc_result!(value, value_max_len, nwritten, { + backend::get_host(backend, u64::try_from(value_max_len).trapping_unwrap()) + }) + } + + #[export_name = "fastly_backend#get_override_host"] + pub fn get_override_host( + backend_ptr: *const u8, + backend_len: usize, + value: *mut u8, + value_max_len: usize, + nwritten: *mut usize, + ) -> FastlyStatus { + let backend = unsafe { slice::from_raw_parts(backend_ptr, backend_len) }; + with_buffer!( + value, + value_max_len, + { backend::get_override_host(backend, u64::try_from(value_max_len).trapping_unwrap()) }, + |res| { + let res = res.ok_or(FastlyStatus::NONE)?; + unsafe { + *nwritten = res.len(); + } + std::mem::forget(res); + } + ) + } + + #[export_name = "fastly_backend#get_port"] + pub fn get_port(backend_ptr: *const u8, backend_len: usize, value: *mut u16) -> FastlyStatus { + let backend = unsafe { slice::from_raw_parts(backend_ptr, backend_len) }; + match backend::get_port(backend) { + Ok(res) => { + unsafe { + *value = res; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_backend#get_connect_timeout_ms"] + pub fn get_connect_timeout_ms( + backend_ptr: *const u8, + backend_len: usize, + value: *mut u32, + ) -> FastlyStatus { + let backend = unsafe { slice::from_raw_parts(backend_ptr, backend_len) }; + match backend::get_connect_timeout_ms(backend) { + Ok(res) => { + unsafe { + *value = res; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_backend#get_first_byte_timeout_ms"] + pub fn get_first_byte_timeout_ms( + backend_ptr: *const u8, + backend_len: usize, + value: *mut u32, + ) -> FastlyStatus { + let backend = unsafe { slice::from_raw_parts(backend_ptr, backend_len) }; + match backend::get_first_byte_timeout_ms(backend) { + Ok(res) => { + unsafe { + *value = res; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_backend#get_between_bytes_timeout_ms"] + pub fn get_between_bytes_timeout_ms( + backend_ptr: *const u8, + backend_len: usize, + value: *mut u32, + ) -> FastlyStatus { + let backend = unsafe { slice::from_raw_parts(backend_ptr, backend_len) }; + match backend::get_between_bytes_timeout_ms(backend) { + Ok(res) => { + unsafe { + *value = res; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_backend#is_ssl"] + pub fn is_ssl(backend_ptr: *const u8, backend_len: usize, value: *mut u32) -> FastlyStatus { + let backend = unsafe { slice::from_raw_parts(backend_ptr, backend_len) }; + match backend::is_ssl(backend) { + Ok(res) => { + unsafe { + *value = u32::from(res); + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_backend#get_ssl_min_version"] + pub fn get_ssl_min_version( + backend_ptr: *const u8, + backend_len: usize, + value: *mut u32, + ) -> FastlyStatus { + let backend = unsafe { slice::from_raw_parts(backend_ptr, backend_len) }; + match backend::get_ssl_min_version(backend) { + Ok(res) => { + unsafe { + *value = u32::from(res); + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_backend#get_ssl_max_version"] + pub fn get_ssl_max_version( + backend_ptr: *const u8, + backend_len: usize, + value: *mut u32, + ) -> FastlyStatus { + let backend = unsafe { slice::from_raw_parts(backend_ptr, backend_len) }; + match backend::get_ssl_max_version(backend) { + Ok(res) => { + unsafe { + *value = u32::from(res); + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } +} + +pub mod fastly_async_io { + use super::*; + use crate::bindings::fastly::api::async_io; + use core::slice; + + #[export_name = "fastly_async_io#select"] + pub fn select( + async_item_handles: *const AsyncItemHandle, + async_item_handles_len: usize, + timeout_ms: u32, + done_index_out: *mut u32, + ) -> FastlyStatus { + let async_item_handles = + unsafe { slice::from_raw_parts(async_item_handles, async_item_handles_len) }; + match async_io::select(async_item_handles, timeout_ms) { + Ok(Some(res)) => { + unsafe { + *done_index_out = res; + } + FastlyStatus::OK + } + + Ok(None) => { + unsafe { + *done_index_out = u32::MAX; + } + FastlyStatus::NONE + } + Err(e) => e.into(), + } + } + + #[export_name = "fastly_async_io#is_ready"] + pub fn is_ready(async_item_handle: AsyncItemHandle, ready_out: *mut u32) -> FastlyStatus { + match async_io::is_ready(async_item_handle) { + Ok(res) => { + unsafe { + *ready_out = u32::from(res); + } + FastlyStatus::OK + } + Err(e) => e.into(), + } + } +} + +pub mod fastly_purge { + use super::*; + use crate::bindings::fastly::api::purge; + use core::slice; + + bitflags::bitflags! { + #[derive(Default)] + #[repr(transparent)] + pub struct PurgeOptionsMask: u32 { + const SOFT_PURGE = 1 << 0; + const RET_BUF = 1 << 1; + } + } + + impl From for purge::PurgeOptionsMask { + fn from(value: PurgeOptionsMask) -> Self { + let mut flags = Self::empty(); + flags.set( + Self::SOFT_PURGE, + value.contains(PurgeOptionsMask::SOFT_PURGE), + ); + flags.set(Self::RET_BUF, value.contains(PurgeOptionsMask::RET_BUF)); + flags + } + } + + #[derive(Debug)] + #[repr(C)] + pub struct PurgeOptions { + pub ret_buf_ptr: *mut u8, + pub ret_buf_len: usize, + pub ret_buf_nwritten_out: *mut usize, + } + + #[export_name = "fastly_purge#purge_surrogate_key"] + pub fn purge_surrogate_key( + surrogate_key_ptr: *const u8, + surrogate_key_len: usize, + options_mask: PurgeOptionsMask, + options: *mut PurgeOptions, + ) -> FastlyStatus { + let surrogate_key = unsafe { slice::from_raw_parts(surrogate_key_ptr, surrogate_key_len) }; + with_buffer!( + unsafe { (*options).ret_buf_ptr }, + unsafe { (*options).ret_buf_len }, + { + purge::purge_surrogate_key( + surrogate_key, + options_mask.into(), + u64::try_from(unsafe { (*options).ret_buf_len }).trapping_unwrap(), + ) + }, + |res| { + if let Some(res) = res { + unsafe { + *(*options).ret_buf_nwritten_out = res.len(); + } + std::mem::forget(res); + } + } + ) + } +} diff --git a/crates/adapter/src/fastly/error.rs b/crates/adapter/src/fastly/error.rs new file mode 100644 index 00000000..0c7fecf3 --- /dev/null +++ b/crates/adapter/src/fastly/error.rs @@ -0,0 +1,48 @@ +use crate::StateError; + +#[derive(PartialEq, Eq)] +#[repr(C)] +pub struct FastlyStatus(i32); + +impl StateError for FastlyStatus { + const SUCCESS: Self = Self::OK; +} + +impl FastlyStatus { + pub const OK: Self = FastlyStatus(0); + pub const UNKNOWN_ERROR: Self = FastlyStatus(1); + pub const INVALID_ARGUMENT: Self = Self(2); + pub const NONE: Self = FastlyStatus(10); +} + +impl From for FastlyStatus { + fn from(err: crate::bindings::fastly::api::types::Error) -> Self { + use crate::bindings::fastly::api::types::Error; + FastlyStatus(match err { + // use black_box here to prevent rustc/llvm from generating a switch table + Error::UnknownError => std::hint::black_box(100), + Error::GenericError => 1, + Error::InvalidArgument => Self::INVALID_ARGUMENT.0, + Error::BadHandle => 3, + Error::BufferLen => 4, + Error::Unsupported => 5, + Error::BadAlign => 6, + Error::HttpInvalid => 7, + Error::HttpUser => 8, + Error::HttpIncomplete => 9, + Error::OptionalNone => 10, + Error::HttpHeadTooLarge => 11, + Error::HttpInvalidStatus => 12, + Error::LimitExceeded => 13, + }) + } +} + +pub(crate) fn convert_result( + res: Result<(), crate::bindings::fastly::api::types::Error>, +) -> FastlyStatus { + match res { + Ok(()) => FastlyStatus::OK, + Err(e) => FastlyStatus::from(e), + } +} diff --git a/crates/adapter/src/fastly/macros.rs b/crates/adapter/src/fastly/macros.rs new file mode 100644 index 00000000..8047f992 --- /dev/null +++ b/crates/adapter/src/fastly/macros.rs @@ -0,0 +1,23 @@ +#[macro_export] +macro_rules! with_buffer { + ($buf:expr, $len:expr, $alloc:block, |$res:ident| $free:block) => { + crate::State::with::(|state| { + let $res = state.import_alloc.with_buffer($buf, $len, || $alloc)?; + $free; + Ok(()) + }) + }; +} + +#[macro_export] +macro_rules! alloc_result { + ($buf:expr, $len:expr, $nwritten:expr, $block:block) => { + with_buffer!($buf, $len, $block, |res| { + unsafe { + *$nwritten = res.len(); + } + + std::mem::forget(res); + }) + }; +} diff --git a/crates/adapter/src/fastly/mod.rs b/crates/adapter/src/fastly/mod.rs new file mode 100644 index 00000000..34998690 --- /dev/null +++ b/crates/adapter/src/fastly/mod.rs @@ -0,0 +1,11 @@ +mod cache; +mod config_store; +mod core; +mod error; +mod macros; + +pub(crate) use error::*; + +pub use cache::*; +pub use config_store::*; +pub use core::*; diff --git a/crates/adapter/src/lib.rs b/crates/adapter/src/lib.rs new file mode 100644 index 00000000..73e27d15 --- /dev/null +++ b/crates/adapter/src/lib.rs @@ -0,0 +1,1427 @@ +use crate::bindings::wasi::clocks::{monotonic_clock, wall_clock}; +use crate::bindings::wasi::io::poll; +use crate::bindings::wasi::io::streams; +use crate::bindings::wasi::random::random; +use core::cell::{Cell, RefCell, RefMut, UnsafeCell}; +use core::ffi::c_void; +use core::mem::{self, align_of, forget, size_of, MaybeUninit}; +use core::ops::{Deref, DerefMut}; +use core::ptr::{self, null_mut}; +use core::slice; +use poll::Pollable; +use wasi::*; + +#[macro_use] +mod macros; + +pub mod fastly; + +mod descriptors; +use crate::descriptors::{Descriptor, Descriptors, StreamType}; + +pub mod bindings { + wit_bindgen_rust_macro::generate!({ + path: "../../lib/wit", + world: "fastly:api/compute", + std_feature, + raw_strings, + runtime_path: "crate::bindings::wit_bindgen_rt_shim", + disable_run_ctors_once_workaround: true, + skip: ["poll"], + }); + + pub mod wit_bindgen_rt_shim { + pub use bitflags; + + pub fn maybe_link_cabi_realloc() {} + } + + pub struct ComponentAdapter; + + impl exports::fastly::api::reactor::Guest for ComponentAdapter { + fn serve( + req: fastly::api::http_types::RequestHandle, + body: fastly::api::http_types::BodyHandle, + ) -> Result<(), ()> { + #[link(wasm_import_module = "__main_module__")] + extern "C" { + fn _start(); + } + + let res = crate::State::with::(|state| { + let old = state.request.replace(Some(req)); + assert!(old.is_none()); + let old = state.request_body.replace(Some(body)); + assert!(old.is_none()); + Ok(()) + }); + + unsafe { + _start(); + } + + if res == crate::fastly::FastlyStatus::OK { + Ok(()) + } else { + Err(()) + } + } + } + + export!(ComponentAdapter); +} + +// The unwrap/expect methods in std pull panic when they fail, which pulls +// in unwinding machinery that we can't use in the adapter. Instead, use this +// extension trait to get postfixed upwrap on Option and Result. +trait TrappingUnwrap { + fn trapping_unwrap(self) -> T; +} + +impl TrappingUnwrap for Option { + fn trapping_unwrap(self) -> T { + match self { + Some(t) => t, + None => unreachable!(), + } + } +} + +impl TrappingUnwrap for Result { + fn trapping_unwrap(self) -> T { + match self { + Ok(t) => t, + Err(_) => unreachable!(), + } + } +} + +/// Allocate a file descriptor which will generate an `ERRNO_BADF` if passed to +/// any WASI Preview 1 function implemented by this adapter. +/// +/// This is intended for use by `wasi-libc` during its incremental transition +/// from WASI Preview 1 to Preview 2. It will use this function to reserve +/// descriptors for its own use, valid only for use with libc functions. +#[no_mangle] +pub unsafe extern "C" fn adapter_open_badfd(fd: *mut u32) -> Errno { + State::with::(|state| { + *fd = state.descriptors_mut().open(Descriptor::Bad)?; + Ok(()) + }) +} + +/// Close a descriptor previously opened using `adapter_open_badfd`. +#[no_mangle] +pub unsafe extern "C" fn adapter_close_badfd(fd: u32) -> Errno { + State::with::(|state| state.descriptors_mut().close(fd)) +} + +#[no_mangle] +pub unsafe extern "C" fn reset_adapter_state() { + let state = get_state_ptr(); + if !state.is_null() { + State::init(state) + } +} + +#[no_mangle] +pub unsafe extern "C" fn cabi_import_realloc( + old_ptr: *mut u8, + old_size: usize, + align: usize, + new_size: usize, +) -> *mut u8 { + if !old_ptr.is_null() || old_size != 0 { + unreachable!(); + } + let mut ptr = null_mut::(); + State::with::(|state| { + ptr = state.import_alloc.alloc(align, new_size); + Ok(()) + }); + ptr +} + +/// Bump-allocated memory arena. This is a singleton - the +/// memory will be sized according to `bump_arena_size()`. +pub struct BumpArena { + data: MaybeUninit<[u8; bump_arena_size()]>, + position: Cell, +} + +impl BumpArena { + fn new() -> Self { + BumpArena { + data: MaybeUninit::uninit(), + position: Cell::new(0), + } + } + fn alloc(&self, align: usize, size: usize) -> *mut u8 { + let start = self.data.as_ptr() as usize; + let next = start + self.position.get(); + let alloc = align_to(next, align); + let offset = alloc - start; + if offset + size > bump_arena_size() { + unreachable!("out of memory"); + } + self.position.set(offset + size); + alloc as *mut u8 + } +} +fn align_to(ptr: usize, align: usize) -> usize { + (ptr + (align - 1)) & !(align - 1) +} + +// Invariant: buffer not-null and arena is-some are never true at the same +// time. We did not use an enum to make this invalid behavior unrepresentable +// because we can't use RefCell to borrow() the variants of the enum - only +// Cell provides mutability without pulling in panic machinery - so it would +// make the accessors a lot more awkward to write. +pub struct ImportAlloc { + // When not-null, allocator should use this buffer/len pair at most once + // to satisfy allocations. + buffer: Cell<*mut u8>, + len: Cell, + // When not-empty, allocator should use this arena to satisfy allocations. + arena: Cell>, +} + +impl ImportAlloc { + fn new() -> Self { + ImportAlloc { + buffer: Cell::new(std::ptr::null_mut()), + len: Cell::new(0), + arena: Cell::new(None), + } + } + + /// Expect at most one import allocation during execution of the provided closure. + /// Use the provided buffer to satisfy that import allocation. The user is responsible + /// for making sure allocated imports are not used beyond the lifetime of the buffer. + pub fn with_buffer(&self, buffer: *mut u8, len: usize, f: impl FnOnce() -> T) -> T { + if self.arena.get().is_some() { + unreachable!("arena mode") + } + let prev = self.buffer.replace(buffer); + if !prev.is_null() { + unreachable!("overwrote another buffer") + } + self.len.set(len); + let r = f(); + self.buffer.set(std::ptr::null_mut()); + r + } + + /// To be used by cabi_import_realloc only! + fn alloc(&self, align: usize, size: usize) -> *mut u8 { + if let Some(arena) = self.arena.get() { + arena.alloc(align, size) + } else { + let buffer = self.buffer.get(); + if buffer.is_null() { + unreachable!("buffer not provided, or already used") + } + let buffer = buffer as usize; + let alloc = align_to(buffer, align); + if alloc.checked_add(size).trapping_unwrap() + > buffer.checked_add(self.len.get()).trapping_unwrap() + { + unreachable!("out of memory") + } + self.buffer.set(std::ptr::null_mut()); + alloc as *mut u8 + } + } +} + +/// This allocator is only used for the `run` entrypoint. +/// +/// The implementation here is a bump allocator into `State::long_lived_arena` which +/// traps when it runs out of data. This means that the total size of +/// arguments/env/etc coming into a component is bounded by the current 64k +/// (ish) limit. That's just an implementation limit though which can be lifted +/// by dynamically calling the main module's allocator as necessary for more data. +#[no_mangle] +pub unsafe extern "C" fn cabi_export_realloc( + old_ptr: *mut u8, + old_size: usize, + align: usize, + new_size: usize, +) -> *mut u8 { + if !old_ptr.is_null() || old_size != 0 { + unreachable!(); + } + let mut ret = null_mut::(); + State::with::(|state| { + ret = state.long_lived_arena.alloc(align, new_size); + Ok(()) + }); + ret +} + +/// Read command-line argument data. +/// The size of the array should match that returned by `args_sizes_get` +#[no_mangle] +pub unsafe extern "C" fn args_get(_argv: *mut *mut u8, _argv_buf: *mut u8) -> Errno { + ERRNO_SUCCESS +} + +/// Return command-line argument data sizes. +#[no_mangle] +pub unsafe extern "C" fn args_sizes_get(argc: *mut Size, argv_buf_size: *mut Size) -> Errno { + *argc = 0; + *argv_buf_size = 0; + ERRNO_SUCCESS +} + +/// Read environment variable data. +/// The sizes of the buffers should match that returned by `environ_sizes_get`. +#[no_mangle] +pub unsafe extern "C" fn environ_get(_environ: *mut *mut u8, _nviron_buf: *mut u8) -> Errno { + ERRNO_SUCCESS +} + +/// Return environment variable data sizes. +#[no_mangle] +pub unsafe extern "C" fn environ_sizes_get( + environc: *mut Size, + environ_buf_size: *mut Size, +) -> Errno { + *environc = 0; + *environ_buf_size = 0; + + return ERRNO_SUCCESS; +} + +/// Return the resolution of a clock. +/// Implementations are required to provide a non-zero value for supported clocks. For unsupported clocks, +/// return `errno::inval`. +/// Note: This is similar to `clock_getres` in POSIX. +#[no_mangle] +pub extern "C" fn clock_res_get(id: Clockid, resolution: &mut Timestamp) -> Errno { + match id { + CLOCKID_MONOTONIC => { + *resolution = monotonic_clock::resolution(); + ERRNO_SUCCESS + } + CLOCKID_REALTIME => { + let res = wall_clock::resolution(); + *resolution = match Timestamp::from(res.seconds) + .checked_mul(1_000_000_000) + .and_then(|ns| ns.checked_add(res.nanoseconds.into())) + { + Some(ns) => ns, + None => return ERRNO_OVERFLOW, + }; + ERRNO_SUCCESS + } + _ => ERRNO_BADF, + } +} + +/// Return the time value of a clock. +/// Note: This is similar to `clock_gettime` in POSIX. +#[no_mangle] +pub unsafe extern "C" fn clock_time_get( + id: Clockid, + _precision: Timestamp, + time: &mut Timestamp, +) -> Errno { + match id { + CLOCKID_MONOTONIC => { + *time = monotonic_clock::now(); + ERRNO_SUCCESS + } + CLOCKID_REALTIME => { + let res = wall_clock::now(); + *time = match Timestamp::from(res.seconds) + .checked_mul(1_000_000_000) + .and_then(|ns| ns.checked_add(res.nanoseconds.into())) + { + Some(ns) => ns, + None => return ERRNO_OVERFLOW, + }; + ERRNO_SUCCESS + } + _ => ERRNO_BADF, + } +} + +/// Provide file advisory information on a file descriptor. +/// Note: This is similar to `posix_fadvise` in POSIX. +#[no_mangle] +pub unsafe extern "C" fn fd_advise( + _fd: Fd, + _offset: Filesize, + _len: Filesize, + _advice: Advice, +) -> Errno { + wasi::ERRNO_NOTSUP +} + +/// Force the allocation of space in a file. +/// Note: This is similar to `posix_fallocate` in POSIX. +#[no_mangle] +pub unsafe extern "C" fn fd_allocate(_fd: Fd, _offset: Filesize, _len: Filesize) -> Errno { + wasi::ERRNO_NOTSUP +} + +/// Close a file descriptor. +/// Note: This is similar to `close` in POSIX. +#[no_mangle] +pub unsafe extern "C" fn fd_close(fd: Fd) -> Errno { + State::with::(|state| { + if let Descriptor::Bad = state.descriptors().get(fd)? { + return Err(wasi::ERRNO_BADF); + } + + state.descriptors_mut().close(fd)?; + Ok(()) + }) +} + +/// Synchronize the data of a file to disk. +/// Note: This is similar to `fdatasync` in POSIX. +#[no_mangle] +pub unsafe extern "C" fn fd_datasync(_fd: Fd) -> Errno { + wasi::ERRNO_NOTSUP +} + +/// Get the attributes of a file descriptor. +/// Note: This returns similar flags to `fsync(fd, F_GETFL)` in POSIX, as well as additional fields. +#[no_mangle] +pub unsafe extern "C" fn fd_fdstat_get(_fd: Fd, _stat: *mut Fdstat) -> Errno { + wasi::ERRNO_NOTSUP +} + +/// Adjust the flags associated with a file descriptor. +/// Note: This is similar to `fcntl(fd, F_SETFL, flags)` in POSIX. +#[no_mangle] +pub unsafe extern "C" fn fd_fdstat_set_flags(_fd: Fd, _flags: Fdflags) -> Errno { + wasi::ERRNO_NOTSUP +} + +/// Does not do anything if `fd` corresponds to a valid descriptor and returns [`wasi::ERRNO_BADF`] otherwise. +#[no_mangle] +pub unsafe extern "C" fn fd_fdstat_set_rights( + fd: Fd, + _fs_rights_base: Rights, + _fs_rights_inheriting: Rights, +) -> Errno { + State::with::(|state| { + let ds = state.descriptors(); + match ds.get(fd)? { + Descriptor::Streams(..) => Ok(()), + Descriptor::Closed(..) | Descriptor::Bad => Err(wasi::ERRNO_BADF), + } + }) +} + +/// Return the attributes of an open file. +#[no_mangle] +pub unsafe extern "C" fn fd_filestat_get(_fd: Fd, _buf: *mut Filestat) -> Errno { + wasi::ERRNO_NOTSUP +} + +/// Adjust the size of an open file. If this increases the file's size, the extra bytes are filled with zeros. +/// Note: This is similar to `ftruncate` in POSIX. +#[no_mangle] +pub unsafe extern "C" fn fd_filestat_set_size(_fd: Fd, _size: Filesize) -> Errno { + wasi::ERRNO_NOTSUP +} + +/// Adjust the timestamps of an open file or directory. +/// Note: This is similar to `futimens` in POSIX. +#[no_mangle] +pub unsafe extern "C" fn fd_filestat_set_times( + _fd: Fd, + _atim: Timestamp, + _mtim: Timestamp, + _fst_flags: Fstflags, +) -> Errno { + wasi::ERRNO_NOTSUP +} + +/// Read from a file descriptor, without using and updating the file descriptor's offset. +/// Note: This is similar to `preadv` in POSIX. +#[no_mangle] +pub unsafe extern "C" fn fd_pread( + _fd: Fd, + _iovs_ptr: *const Iovec, + _iovs_len: usize, + _offset: Filesize, + _nread: *mut Size, +) -> Errno { + wasi::ERRNO_NOTSUP +} + +/// Return a description of the given preopened file descriptor. +#[no_mangle] +pub unsafe extern "C" fn fd_prestat_get(_fd: Fd, _buf: *mut Prestat) -> Errno { + return ERRNO_BADF; +} + +/// Return a description of the given preopened file descriptor. +#[no_mangle] +pub unsafe extern "C" fn fd_prestat_dir_name( + _fd: Fd, + _path: *mut u8, + _path_max_len: Size, +) -> Errno { + wasi::ERRNO_NOTSUP +} + +/// Write to a file descriptor, without using and updating the file descriptor's offset. +/// Note: This is similar to `pwritev` in POSIX. +#[no_mangle] +pub unsafe extern "C" fn fd_pwrite( + _fd: Fd, + _iovs_ptr: *const Ciovec, + _iovs_len: usize, + _offset: Filesize, + _nwritten: *mut Size, +) -> Errno { + wasi::ERRNO_NOTSUP +} + +/// Read from a file descriptor. +/// Note: This is similar to `readv` in POSIX. +#[no_mangle] +pub unsafe extern "C" fn fd_read( + fd: Fd, + mut iovs_ptr: *const Iovec, + mut iovs_len: usize, + nread: *mut Size, +) -> Errno { + // Advance to the first non-empty buffer. + while iovs_len != 0 && (*iovs_ptr).buf_len == 0 { + iovs_ptr = iovs_ptr.add(1); + iovs_len -= 1; + } + if iovs_len == 0 { + *nread = 0; + return ERRNO_SUCCESS; + } + + let ptr = (*iovs_ptr).buf; + let len = (*iovs_ptr).buf_len; + + State::with::(|state| { + let ds = state.descriptors(); + match ds.get(fd)? { + Descriptor::Streams(streams) => { + let blocking_mode = BlockingMode::Blocking; + + let read_len = u64::try_from(len).trapping_unwrap(); + let wasi_stream = streams.get_read_stream()?; + let data = match state + .import_alloc + .with_buffer(ptr, len, || blocking_mode.read(wasi_stream, read_len)) + { + Ok(data) => data, + Err(streams::StreamError::Closed) => { + *nread = 0; + return Ok(()); + } + Err(streams::StreamError::LastOperationFailed(e)) => { + Err(stream_error_to_errno(e))? + } + }; + + assert_eq!(data.as_ptr(), ptr); + assert!(data.len() <= len); + + let len = data.len(); + *nread = len; + forget(data); + Ok(()) + } + Descriptor::Closed(_) | Descriptor::Bad => Err(ERRNO_BADF), + } + }) +} + +fn stream_error_to_errno(_err: streams::Error) -> Errno { + return ERRNO_IO; +} + +/// Read directory entries from a directory. +/// When successful, the contents of the output buffer consist of a sequence of +/// directory entries. Each directory entry consists of a `dirent` object, +/// followed by `dirent::d_namlen` bytes holding the name of the directory +/// entry. +/// This function fills the output buffer as much as possible, potentially +/// truncating the last directory entry. This allows the caller to grow its +/// read buffer size in case it's too small to fit a single large directory +/// entry, or skip the oversized directory entry. +#[no_mangle] +pub unsafe extern "C" fn fd_readdir( + _fd: Fd, + _buf: *mut u8, + _buf_len: Size, + _cookie: Dircookie, + _bufused: *mut Size, +) -> Errno { + wasi::ERRNO_NOTSUP +} + +/// Atomically replace a file descriptor by renumbering another file descriptor. +/// Due to the strong focus on thread safety, this environment does not provide +/// a mechanism to duplicate or renumber a file descriptor to an arbitrary +/// number, like `dup2()`. This would be prone to race conditions, as an actual +/// file descriptor with the same number could be allocated by a different +/// thread at the same time. +/// This function provides a way to atomically renumber file descriptors, which +/// would disappear if `dup2()` were to be removed entirely. +#[no_mangle] +pub unsafe extern "C" fn fd_renumber(fd: Fd, to: Fd) -> Errno { + State::with::(|state| state.descriptors_mut().renumber(fd, to)) +} + +/// Move the offset of a file descriptor. +/// Note: This is similar to `lseek` in POSIX. +#[no_mangle] +pub unsafe extern "C" fn fd_seek( + _fd: Fd, + _offset: Filedelta, + _whence: Whence, + _newoffset: *mut Filesize, +) -> Errno { + wasi::ERRNO_NOTSUP +} + +/// Synchronize the data and metadata of a file to disk. +/// Note: This is similar to `fsync` in POSIX. +#[no_mangle] +pub unsafe extern "C" fn fd_sync(_fd: Fd) -> Errno { + wasi::ERRNO_NOTSUP +} + +/// Return the current offset of a file descriptor. +/// Note: This is similar to `lseek(fd, 0, SEEK_CUR)` in POSIX. +#[no_mangle] +pub unsafe extern "C" fn fd_tell(_fd: Fd, _offset: *mut Filesize) -> Errno { + wasi::ERRNO_NOTSUP +} + +/// Write to a file descriptor. +/// Note: This is similar to `writev` in POSIX. +#[no_mangle] +pub unsafe extern "C" fn fd_write( + fd: Fd, + mut iovs_ptr: *const Ciovec, + mut iovs_len: usize, + nwritten: *mut Size, +) -> Errno { + if !matches!( + get_allocation_state(), + AllocationState::StackAllocated | AllocationState::StateAllocated + ) { + *nwritten = 0; + return ERRNO_IO; + } + + // Advance to the first non-empty buffer. + while iovs_len != 0 && (*iovs_ptr).buf_len == 0 { + iovs_ptr = iovs_ptr.add(1); + iovs_len -= 1; + } + if iovs_len == 0 { + *nwritten = 0; + return ERRNO_SUCCESS; + } + + let ptr = (*iovs_ptr).buf; + let len = (*iovs_ptr).buf_len; + let bytes = slice::from_raw_parts(ptr, len); + + State::with::(|state| { + let ds = state.descriptors(); + match ds.get(fd)? { + Descriptor::Streams(streams) => { + let wasi_stream = streams.get_write_stream()?; + + let nbytes = BlockingMode::Blocking.write(wasi_stream, bytes)?; + + *nwritten = nbytes; + Ok(()) + } + Descriptor::Closed(_) | Descriptor::Bad => Err(ERRNO_BADF), + } + }) +} + +/// Create a directory. +/// Note: This is similar to `mkdirat` in POSIX. +#[no_mangle] +pub unsafe extern "C" fn path_create_directory( + _fd: Fd, + _path_ptr: *const u8, + _path_len: usize, +) -> Errno { + wasi::ERRNO_NOTSUP +} + +/// Return the attributes of a file or directory. +/// Note: This is similar to `stat` in POSIX. +#[no_mangle] +pub unsafe extern "C" fn path_filestat_get( + _fd: Fd, + _flags: Lookupflags, + _path_ptr: *const u8, + _path_len: usize, + _buf: *mut Filestat, +) -> Errno { + wasi::ERRNO_NOTSUP +} + +/// Adjust the timestamps of a file or directory. +/// Note: This is similar to `utimensat` in POSIX. +#[no_mangle] +pub unsafe extern "C" fn path_filestat_set_times( + _fd: Fd, + _flags: Lookupflags, + _path_ptr: *const u8, + _path_len: usize, + _atim: Timestamp, + _mtim: Timestamp, + _fst_flags: Fstflags, +) -> Errno { + wasi::ERRNO_NOTSUP +} + +/// Create a hard link. +/// Note: This is similar to `linkat` in POSIX. +#[no_mangle] +pub unsafe extern "C" fn path_link( + _old_fd: Fd, + _old_flags: Lookupflags, + _old_path_ptr: *const u8, + _old_path_len: usize, + _new_fd: Fd, + _new_path_ptr: *const u8, + _new_path_len: usize, +) -> Errno { + wasi::ERRNO_NOTSUP +} + +/// Open a file or directory. +/// The returned file descriptor is not guaranteed to be the lowest-numbered +/// file descriptor not currently open; it is randomized to prevent +/// applications from depending on making assumptions about indexes, since this +/// is error-prone in multi-threaded contexts. The returned file descriptor is +/// guaranteed to be less than 2**31. +/// Note: This is similar to `openat` in POSIX. +#[no_mangle] +pub unsafe extern "C" fn path_open( + _fd: Fd, + _dirflags: Lookupflags, + _path_ptr: *const u8, + _path_len: usize, + _oflags: Oflags, + _fs_rights_base: Rights, + _fs_rights_inheriting: Rights, + _fdflags: Fdflags, + _opened_fd: *mut Fd, +) -> Errno { + wasi::ERRNO_NOTSUP +} + +/// Read the contents of a symbolic link. +/// Note: This is similar to `readlinkat` in POSIX. +#[no_mangle] +pub unsafe extern "C" fn path_readlink( + _fd: Fd, + _path_ptr: *const u8, + _path_len: usize, + _buf: *mut u8, + _buf_len: Size, + _bufused: *mut Size, +) -> Errno { + wasi::ERRNO_NOTSUP +} + +/// Remove a directory. +/// Return `errno::notempty` if the directory is not empty. +/// Note: This is similar to `unlinkat(fd, path, AT_REMOVEDIR)` in POSIX. +#[no_mangle] +pub unsafe extern "C" fn path_remove_directory( + _fd: Fd, + _path_ptr: *const u8, + _path_len: usize, +) -> Errno { + wasi::ERRNO_NOTSUP +} + +/// Rename a file or directory. +/// Note: This is similar to `renameat` in POSIX. +#[no_mangle] +pub unsafe extern "C" fn path_rename( + _old_fd: Fd, + _old_path_ptr: *const u8, + _old_path_len: usize, + _new_fd: Fd, + _new_path_ptr: *const u8, + _new_path_len: usize, +) -> Errno { + wasi::ERRNO_NOTSUP +} + +/// Create a symbolic link. +/// Note: This is similar to `symlinkat` in POSIX. +#[no_mangle] +pub unsafe extern "C" fn path_symlink( + _old_path_ptr: *const u8, + _old_path_len: usize, + _fd: Fd, + _new_path_ptr: *const u8, + _new_path_len: usize, +) -> Errno { + wasi::ERRNO_NOTSUP +} + +/// Unlink a file. +/// Return `errno::isdir` if the path refers to a directory. +/// Note: This is similar to `unlinkat(fd, path, 0)` in POSIX. +#[no_mangle] +pub unsafe extern "C" fn path_unlink_file( + _fd: Fd, + _path_ptr: *const u8, + _path_len: usize, +) -> Errno { + wasi::ERRNO_NOTSUP +} + +struct Pollables { + pointer: *mut Pollable, + index: usize, + length: usize, +} + +impl Pollables { + unsafe fn push(&mut self, pollable: Pollable) { + assert!(self.index < self.length); + // Use `ptr::write` instead of `*... = pollable` because `ptr::write` + // doesn't call drop on the old memory. + self.pointer.add(self.index).write(pollable); + self.index += 1; + } +} + +// We create new pollable handles for each `poll_oneoff` call, so drop them all +// after the call. +impl Drop for Pollables { + fn drop(&mut self) { + while self.index != 0 { + self.index -= 1; + unsafe { + core::ptr::drop_in_place(self.pointer.add(self.index)); + } + } + } +} + +/// Concurrently poll for the occurrence of a set of events. +#[no_mangle] +pub unsafe extern "C" fn poll_oneoff( + r#in: *const Subscription, + out: *mut Event, + nsubscriptions: Size, + nevents: *mut Size, +) -> Errno { + *nevents = 0; + + let subscriptions = slice::from_raw_parts(r#in, nsubscriptions); + + // We're going to split the `nevents` buffer into two non-overlapping + // buffers: one to store the pollable handles, and the other to store + // the bool results. + // + // First, we assert that this is possible: + assert!(align_of::() >= align_of::()); + assert!(align_of::() >= align_of::()); + assert!( + nsubscriptions + .checked_mul(size_of::()) + .trapping_unwrap() + >= nsubscriptions + .checked_mul(size_of::()) + .trapping_unwrap() + .checked_add( + nsubscriptions + .checked_mul(size_of::()) + .trapping_unwrap() + ) + .trapping_unwrap() + ); + // Store the pollable handles at the beginning, and the bool results at the + // end, so that we don't clobber the bool results when writting the events. + let pollables = out as *mut c_void as *mut Pollable; + let results = out.add(nsubscriptions).cast::().sub(nsubscriptions); + + // Indefinite sleeping is not supported in preview1. + if nsubscriptions == 0 { + return ERRNO_INVAL; + } + + State::with::(|state| { + const EVENTTYPE_CLOCK: u8 = wasi::EVENTTYPE_CLOCK.raw(); + const EVENTTYPE_FD_READ: u8 = wasi::EVENTTYPE_FD_READ.raw(); + const EVENTTYPE_FD_WRITE: u8 = wasi::EVENTTYPE_FD_WRITE.raw(); + + let mut pollables = Pollables { + pointer: pollables, + index: 0, + length: nsubscriptions, + }; + + for subscription in subscriptions { + pollables.push(match subscription.u.tag { + EVENTTYPE_CLOCK => { + let clock = &subscription.u.u.clock; + let absolute = (clock.flags & SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME) + == SUBCLOCKFLAGS_SUBSCRIPTION_CLOCK_ABSTIME; + match clock.id { + CLOCKID_REALTIME => { + let timeout = if absolute { + // Convert `clock.timeout` to `Datetime`. + let mut datetime = wall_clock::Datetime { + seconds: clock.timeout / 1_000_000_000, + nanoseconds: (clock.timeout % 1_000_000_000) as _, + }; + + // Subtract `now`. + let now = wall_clock::now(); + datetime.seconds -= now.seconds; + if datetime.nanoseconds < now.nanoseconds { + datetime.seconds -= 1; + datetime.nanoseconds += 1_000_000_000; + } + datetime.nanoseconds -= now.nanoseconds; + + // Convert to nanoseconds. + let nanos = datetime + .seconds + .checked_mul(1_000_000_000) + .ok_or(ERRNO_OVERFLOW)?; + nanos + .checked_add(datetime.nanoseconds.into()) + .ok_or(ERRNO_OVERFLOW)? + } else { + clock.timeout + }; + + monotonic_clock::subscribe_duration(timeout) + } + + CLOCKID_MONOTONIC => { + if absolute { + monotonic_clock::subscribe_instant(clock.timeout) + } else { + monotonic_clock::subscribe_duration(clock.timeout) + } + } + + _ => return Err(ERRNO_INVAL), + } + } + + EVENTTYPE_FD_READ => state + .descriptors() + .get_read_stream(subscription.u.u.fd_read.file_descriptor) + .map(|stream| stream.subscribe())?, + + EVENTTYPE_FD_WRITE => state + .descriptors() + .get_write_stream(subscription.u.u.fd_write.file_descriptor) + .map(|stream| stream.subscribe())?, + + _ => return Err(ERRNO_INVAL), + }); + } + + #[link(wasm_import_module = "wasi:io/poll@0.2.0")] + #[allow(improper_ctypes)] // FIXME(bytecodealliance/wit-bindgen#684) + extern "C" { + #[link_name = "poll"] + fn poll_import(pollables: *const Pollable, len: usize, rval: *mut ReadyList); + } + let mut ready_list = ReadyList { + base: std::ptr::null(), + len: 0, + }; + + state.import_alloc.with_buffer( + results.cast(), + nsubscriptions + .checked_mul(size_of::()) + .trapping_unwrap(), + || { + poll_import( + pollables.pointer, + pollables.length, + &mut ready_list as *mut _, + ); + }, + ); + + assert!(ready_list.len <= nsubscriptions); + assert_eq!(ready_list.base, results as *const u32); + + drop(pollables); + + let ready = std::slice::from_raw_parts(ready_list.base, ready_list.len); + + let mut count = 0; + + for subscription in ready { + let subscription = *subscriptions.as_ptr().add(*subscription as usize); + + let type_; + + let (error, nbytes, flags) = match subscription.u.tag { + EVENTTYPE_CLOCK => { + type_ = wasi::EVENTTYPE_CLOCK; + (ERRNO_SUCCESS, 0, 0) + } + + EVENTTYPE_FD_READ => { + type_ = wasi::EVENTTYPE_FD_READ; + let ds = state.descriptors(); + let desc = ds + .get(subscription.u.u.fd_read.file_descriptor) + .trapping_unwrap(); + match desc { + Descriptor::Streams(streams) => match &streams.type_ { + StreamType::Stdio(_) => (ERRNO_SUCCESS, 1, 0), + }, + _ => unreachable!(), + } + } + EVENTTYPE_FD_WRITE => { + type_ = wasi::EVENTTYPE_FD_WRITE; + let ds = state.descriptors(); + let desc = ds + .get(subscription.u.u.fd_write.file_descriptor) + .trapping_unwrap(); + match desc { + Descriptor::Streams(streams) => match &streams.type_ { + StreamType::Stdio(_) => (ERRNO_SUCCESS, 1, 0), + }, + _ => unreachable!(), + } + } + + _ => unreachable!(), + }; + + *out.add(count) = Event { + userdata: subscription.userdata, + error, + type_, + fd_readwrite: EventFdReadwrite { nbytes, flags }, + }; + + count += 1; + } + + *nevents = count; + + Ok(()) + }) +} + +/// Terminate the process normally. An exit code of 0 indicates successful +/// termination of the program. The meanings of other values is dependent on +/// the environment. +#[no_mangle] +pub unsafe extern "C" fn proc_exit(_rval: Exitcode) -> ! { + unreachable!("no other implementation available in proxy world"); +} + +/// Send a signal to the process of the calling thread. +/// Note: This is similar to `raise` in POSIX. +#[no_mangle] +pub unsafe extern "C" fn proc_raise(_sig: Signal) -> Errno { + unreachable!() +} + +/// Temporarily yield execution of the calling thread. +/// Note: This is similar to `sched_yield` in POSIX. +#[no_mangle] +pub unsafe extern "C" fn sched_yield() -> Errno { + // TODO: This is not yet covered in Preview2. + + ERRNO_SUCCESS +} + +/// Write high-quality random data into a buffer. +/// This function blocks when the implementation is unable to immediately +/// provide sufficient high-quality random data. +/// This function may execute slowly, so when large mounts of random data are +/// required, it's advisable to use this function to seed a pseudo-random +/// number generator, rather than to provide the random data directly. +#[no_mangle] +pub unsafe extern "C" fn random_get(buf: *mut u8, buf_len: Size) -> Errno { + if matches!( + get_allocation_state(), + AllocationState::StackAllocated | AllocationState::StateAllocated + ) { + State::with::(|state| { + assert_eq!(buf_len as u32 as Size, buf_len); + let result = state + .import_alloc + .with_buffer(buf, buf_len, || random::get_random_bytes(buf_len as u64)); + assert_eq!(result.as_ptr(), buf); + + // The returned buffer's memory was allocated in `buf`, so don't separately + // free it. + forget(result); + + Ok(()) + }) + } else { + ERRNO_SUCCESS + } +} + +/// Accept a new incoming connection. +/// Note: This is similar to `accept` in POSIX. +#[no_mangle] +pub unsafe extern "C" fn sock_accept(_fd: Fd, _flags: Fdflags, _connection: *mut Fd) -> Errno { + unreachable!() +} + +/// Receive a message from a socket. +/// Note: This is similar to `recv` in POSIX, though it also supports reading +/// the data into multiple buffers in the manner of `readv`. +#[no_mangle] +pub unsafe extern "C" fn sock_recv( + _fd: Fd, + _ri_data_ptr: *const Iovec, + _ri_data_len: usize, + _ri_flags: Riflags, + _ro_datalen: *mut Size, + _ro_flags: *mut Roflags, +) -> Errno { + unreachable!() +} + +/// Send a message on a socket. +/// Note: This is similar to `send` in POSIX, though it also supports writing +/// the data from multiple buffers in the manner of `writev`. +#[no_mangle] +pub unsafe extern "C" fn sock_send( + _fd: Fd, + _si_data_ptr: *const Ciovec, + _si_data_len: usize, + _si_flags: Siflags, + _so_datalen: *mut Size, +) -> Errno { + unreachable!() +} + +/// Shut down socket send and receive channels. +/// Note: This is similar to `shutdown` in POSIX. +#[no_mangle] +pub unsafe extern "C" fn sock_shutdown(_fd: Fd, _how: Sdflags) -> Errno { + unreachable!() +} + +#[derive(Clone, Copy)] +pub enum BlockingMode { + NonBlocking, + Blocking, +} + +impl BlockingMode { + // note: these methods must take self, not &self, to avoid rustc creating a constant + // out of a BlockingMode literal that it places in .romem, creating a data section and + // breaking our fragile linking scheme + fn read( + self, + input_stream: &streams::InputStream, + read_len: u64, + ) -> Result, streams::StreamError> { + match self { + BlockingMode::NonBlocking => input_stream.read(read_len), + BlockingMode::Blocking => input_stream.blocking_read(read_len), + } + } + fn write( + self, + output_stream: &streams::OutputStream, + mut bytes: &[u8], + ) -> Result { + match self { + BlockingMode::Blocking => { + let total = bytes.len(); + while !bytes.is_empty() { + let len = bytes.len().min(4096); + let (chunk, rest) = bytes.split_at(len); + bytes = rest; + match output_stream.blocking_write_and_flush(chunk) { + Ok(()) => {} + Err(streams::StreamError::Closed) => return Err(ERRNO_IO), + Err(streams::StreamError::LastOperationFailed(e)) => { + return Err(stream_error_to_errno(e)) + } + } + } + Ok(total) + } + + BlockingMode::NonBlocking => { + let permit = match output_stream.check_write() { + Ok(n) => n, + Err(streams::StreamError::Closed) => 0, + Err(streams::StreamError::LastOperationFailed(e)) => { + return Err(stream_error_to_errno(e)) + } + }; + + let len = bytes.len().min(permit as usize); + if len == 0 { + return Ok(0); + } + + match output_stream.write(&bytes[..len]) { + Ok(_) => {} + Err(streams::StreamError::Closed) => return Ok(0), + Err(streams::StreamError::LastOperationFailed(e)) => { + return Err(stream_error_to_errno(e)) + } + } + + match output_stream.blocking_flush() { + Ok(_) => {} + Err(streams::StreamError::Closed) => return Ok(0), + Err(streams::StreamError::LastOperationFailed(e)) => { + return Err(stream_error_to_errno(e)) + } + } + + Ok(len) + } + } + } +} + +const PAGE_SIZE: usize = 65536; + +/// A canary value to detect memory corruption within `State`. +const MAGIC: u32 = u32::from_le_bytes(*b"ugh!"); + +#[repr(C)] // used for now to keep magic1 and magic2 at the start and end +pub(crate) struct State { + /// A canary constant value located at the beginning of this structure to + /// try to catch memory corruption coming from the bottom. + magic1: u32, + + /// Used to coordinate allocations of `cabi_import_realloc` + import_alloc: ImportAlloc, + + /// Storage of mapping from preview1 file descriptors to preview2 file + /// descriptors. + /// + /// Do not use this member directly - use State::descriptors() to ensure + /// lazy initialization happens. + descriptors: RefCell>, + + /// Long-lived bump allocated memory arena. + /// + /// This is used for the cabi_export_realloc to allocate data passed to the + /// `run` entrypoint. Allocations in this arena are safe to use for + /// the lifetime of the State struct. It may also be used for import allocations + /// which need to be long-lived, by using `import_alloc.with_arena`. + long_lived_arena: BumpArena, + + /// The incoming request, if the entry-point was through the reactor. + pub(crate) request: Cell>, + + /// The incoming request body, if the entry-point was through the reactor. + pub(crate) request_body: Cell>, + + /// Another canary constant located at the end of the structure to catch + /// memory corruption coming from the bottom. + magic2: u32, +} + +#[repr(C)] +pub struct WasmStr { + ptr: *const u8, + len: usize, +} + +#[repr(C)] +pub struct WasmStrList { + base: *const WasmStr, + len: usize, +} + +#[repr(C)] +pub struct StrTuple { + key: WasmStr, + value: WasmStr, +} + +#[derive(Copy, Clone)] +#[repr(C)] +pub struct StrTupleList { + base: *const StrTuple, + len: usize, +} + +#[derive(Copy, Clone)] +#[repr(C)] +pub struct ReadyList { + base: *const u32, + len: usize, +} + +const fn bump_arena_size() -> usize { + // The total size of the struct should be a page, so start there + let mut start = PAGE_SIZE; + + // Remove big chunks of the struct for its various fields. + start -= size_of::(); + + // Remove miscellaneous metadata also stored in state. + let misc = 11; + start -= misc * size_of::(); + + // Everything else is the `command_data` allocation. + start +} + +// Statically assert that the `State` structure is the size of a wasm page. This +// mostly guarantees that it's not larger than one page which is relied upon +// below. +#[cfg(target_arch = "wasm32")] +const _: () = { + let _size_assert: [(); PAGE_SIZE] = [(); size_of::()]; +}; + +#[allow(unused)] +#[repr(i32)] +enum AllocationState { + StackUnallocated, + StackAllocating, + StackAllocated, + StateAllocating, + StateAllocated, +} + +#[allow(improper_ctypes)] +extern "C" { + fn get_state_ptr() -> *mut State; + fn set_state_ptr(state: *mut State); + fn get_allocation_state() -> AllocationState; + fn set_allocation_state(state: AllocationState); +} + +pub(crate) trait StateError { + const SUCCESS: Self; +} + +impl StateError for Errno { + const SUCCESS: Self = ERRNO_SUCCESS; +} + +impl State { + pub(crate) fn with(f: impl FnOnce(&State) -> Result<(), E>) -> E { + let state_ref = State::ptr(); + assert_eq!(state_ref.magic1, MAGIC); + assert_eq!(state_ref.magic2, MAGIC); + let ret = f(state_ref); + match ret { + Ok(()) => E::SUCCESS, + Err(err) => err, + } + } + + fn ptr() -> &'static State { + unsafe { + let mut ptr = get_state_ptr(); + if ptr.is_null() { + ptr = State::new(); + set_state_ptr(ptr); + } + &*ptr + } + } + + #[cold] + fn new() -> *mut State { + #[link(wasm_import_module = "__main_module__")] + extern "C" { + fn cabi_realloc( + old_ptr: *mut u8, + old_len: usize, + align: usize, + new_len: usize, + ) -> *mut u8; + } + + assert!(matches!( + unsafe { get_allocation_state() }, + AllocationState::StackAllocated + )); + + unsafe { set_allocation_state(AllocationState::StateAllocating) }; + + let ret = unsafe { + cabi_realloc( + ptr::null_mut(), + 0, + mem::align_of::>(), + mem::size_of::>(), + ) as *mut State + }; + + unsafe { set_allocation_state(AllocationState::StateAllocated) }; + + unsafe { + Self::init(ret); + } + + ret + } + + #[cold] + unsafe fn init(state: *mut State) { + state.write(State { + magic1: MAGIC, + magic2: MAGIC, + import_alloc: ImportAlloc::new(), + descriptors: RefCell::new(None), + request: Cell::new(None), + request_body: Cell::new(None), + long_lived_arena: BumpArena::new(), + }); + } + + /// Accessor for the descriptors member that ensures it is properly initialized + fn descriptors<'a>(&'a self) -> impl Deref + 'a { + let mut d = self + .descriptors + .try_borrow_mut() + .unwrap_or_else(|_| unreachable!()); + if d.is_none() { + *d = Some(Descriptors::new(&self.import_alloc, &self.long_lived_arena)); + } + RefMut::map(d, |d| d.as_mut().unwrap_or_else(|| unreachable!())) + } + + /// Mut accessor for the descriptors member that ensures it is properly initialized + fn descriptors_mut<'a>(&'a self) -> impl DerefMut + Deref + 'a { + let mut d = self + .descriptors + .try_borrow_mut() + .unwrap_or_else(|_| unreachable!()); + if d.is_none() { + *d = Some(Descriptors::new(&self.import_alloc, &self.long_lived_arena)); + } + RefMut::map(d, |d| d.as_mut().unwrap_or_else(|| unreachable!())) + } +} diff --git a/crates/adapter/src/macros.rs b/crates/adapter/src/macros.rs new file mode 100644 index 00000000..c1ec4a51 --- /dev/null +++ b/crates/adapter/src/macros.rs @@ -0,0 +1,94 @@ +//! Minimal versions of standard-library panicking and printing macros. +//! +//! We're avoiding static initializers, so we can't have things like string +//! literals. Replace the standard assert macros with simpler implementations. + +use crate::bindings::wasi::cli::stderr::get_stderr; + +#[allow(dead_code)] +#[doc(hidden)] +pub fn print(message: &[u8]) { + let _ = get_stderr().blocking_write_and_flush(message); +} + +/// A minimal `eprint` for debugging. +#[allow(unused_macros)] +macro_rules! eprint { + ($arg:tt) => {{ + // We have to expand string literals into byte arrays to prevent them + // from getting statically initialized. + let message = byte_array_literals::str!($arg); + $crate::macros::print(&message); + }}; +} + +/// A minimal `eprintln` for debugging. +#[allow(unused_macros)] +macro_rules! eprintln { + ($arg:tt) => {{ + // We have to expand string literals into byte arrays to prevent them + // from getting statically initialized. + let message = byte_array_literals::str_nl!($arg); + $crate::macros::print(&message); + }}; +} + +pub(crate) fn eprint_u32(x: u32) { + if x == 0 { + eprint!("0"); + } else { + eprint_u32_impl(x) + } + + fn eprint_u32_impl(x: u32) { + if x != 0 { + eprint_u32_impl(x / 10); + + let digit = [b'0' + ((x % 10) as u8)]; + crate::macros::print(&digit); + } + } +} + +/// A minimal `unreachable`. +macro_rules! unreachable { + () => {{ + eprint!("unreachable executed at adapter line "); + crate::macros::eprint_u32(line!()); + eprint!("\n"); + #[cfg(target_arch = "wasm32")] + core::arch::wasm32::unreachable(); + // This is here to keep rust-analyzer happy when building for native: + #[cfg(not(target_arch = "wasm32"))] + std::process::abort(); + }}; + + ($arg:tt) => {{ + eprint!("unreachable executed at adapter line "); + crate::macros::eprint_u32(line!()); + eprint!(": "); + eprintln!($arg); + eprint!("\n"); + #[cfg(target_arch = "wasm32")] + core::arch::wasm32::unreachable(); + // This is here to keep rust-analyzer happy when building for native: + #[cfg(not(target_arch = "wasm32"))] + std::process::abort(); + }}; +} + +/// A minimal `assert`. +macro_rules! assert { + ($cond:expr $(,)?) => { + if !$cond { + unreachable!("assertion failed") + } + }; +} + +/// A minimal `assert_eq`. +macro_rules! assert_eq { + ($left:expr, $right:expr $(,)?) => { + assert!($left == $right); + }; +} From af6b63a47418aa29faf6e277535a1d2573da0d30 Mon Sep 17 00:00:00 2001 From: Trevor Elliott Date: Thu, 23 May 2024 15:38:32 -0700 Subject: [PATCH 02/15] Add a meta package for wasm artifacts that viceroy depends on --- Cargo.lock | 5 +++++ Cargo.toml | 2 ++ crates/artifacts/Cargo.toml | 6 +++++ crates/artifacts/build.rs | 44 +++++++++++++++++++++++++++++++++++++ crates/artifacts/src/lib.rs | 1 + lib/Cargo.toml | 2 ++ 6 files changed, 60 insertions(+) create mode 100644 crates/artifacts/Cargo.toml create mode 100644 crates/artifacts/build.rs create mode 100644 crates/artifacts/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 8f0d63f4..97c27aac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2366,6 +2366,10 @@ dependencies = [ "wat", ] +[[package]] +name = "viceroy-artifacts" +version = "0.1.0" + [[package]] name = "viceroy-component-adapter" version = "0.1.0" @@ -2413,6 +2417,7 @@ dependencies = [ "tracing", "tracing-futures", "url", + "viceroy-artifacts", "wasmparser 0.208.1", "wasmtime", "wasmtime-wasi", diff --git a/Cargo.toml b/Cargo.toml index ad69ad8d..21f71cbc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "lib", "crates/adapter", "crates/adapter/byte-array-literals", + "crates/artifacts", ] resolver = "2" @@ -38,6 +39,7 @@ tracing = "0.1.37" tracing-futures = "0.2.5" futures = "0.3.24" url = "2.3.1" +viceroy-artifacts = { path = "crates/artifacts" } # Wasmtime dependencies wasmtime = "21.0.0" diff --git a/crates/artifacts/Cargo.toml b/crates/artifacts/Cargo.toml new file mode 100644 index 00000000..1d91ebbf --- /dev/null +++ b/crates/artifacts/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "viceroy-artifacts" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/crates/artifacts/build.rs b/crates/artifacts/build.rs new file mode 100644 index 00000000..61c8d37c --- /dev/null +++ b/crates/artifacts/build.rs @@ -0,0 +1,44 @@ +use std::{path::PathBuf, process::Command}; + +fn main() { + build_adapter() +} + +fn build_adapter() { + let out_dir = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + + // ensure that we rebuild the artifacts if the adapter is changed + println!("cargo:rerun-if-changed=../adapter/src"); + println!("cargo:rerun-if-changed=../../lib/wit"); + + let mut cmd = Command::new("cargo"); + + cmd.arg("build") + .arg("--release") + .arg("--package=viceroy-component-adapter") + .arg("--target=wasm32-unknown-unknown") + .env("CARGO_TARGET_DIR", &out_dir) + .env_remove("CARGO_ENCODED_RUSTFLAGS"); + + eprintln!("running: {cmd:?}"); + let status = cmd.status().unwrap(); + assert!(status.success()); + + let adapter = out_dir.join(format!("wasi_snapshot_preview1.wasm")); + + std::fs::copy( + out_dir + .join("wasm32-unknown-unknown") + .join("release") + .join("wasi_snapshot_preview1.wasm"), + &adapter, + ) + .unwrap(); + + let mut generated_code = String::new(); + + generated_code += + &format!("pub const ADAPTER_BYTES: &'static [u8] = include_bytes!({adapter:?});\n"); + + std::fs::write(out_dir.join("gen.rs"), generated_code).unwrap(); +} diff --git a/crates/artifacts/src/lib.rs b/crates/artifacts/src/lib.rs new file mode 100644 index 00000000..26a930a6 --- /dev/null +++ b/crates/artifacts/src/lib.rs @@ -0,0 +1 @@ +include!(concat!(env!("OUT_DIR"), "/gen.rs")); diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 253c7779..d436506f 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -60,6 +60,8 @@ wasmtime-wasi = { workspace = true } wasmtime-wasi-nn = { workspace = true } wiggle = { workspace = true } +viceroy-artifacts = { workspace = true } + [dev-dependencies] tempfile = "3.6.0" From 5c0001b9730b4e5941f1650d18060698f0b89206 Mon Sep 17 00:00:00 2001 From: Trevor Elliott Date: Thu, 23 May 2024 16:48:22 -0700 Subject: [PATCH 03/15] Add the `adapt` command for adapting core wasm to a component --- Cargo.lock | 14 +++------- Cargo.toml | 3 ++- cli/src/main.rs | 28 ++++++++++++++++++++ cli/src/opts.rs | 34 +++++++++++++++++++++++++ lib/Cargo.toml | 2 ++ lib/src/adapt.rs | 66 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/src/lib.rs | 1 + 7 files changed, 137 insertions(+), 11 deletions(-) create mode 100644 lib/src/adapt.rs diff --git a/Cargo.lock b/Cargo.lock index 97c27aac..b724b410 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2378,7 +2378,7 @@ dependencies = [ "byte-array-literals", "object 0.33.0", "wasi", - "wasm-encoder 0.205.0", + "wasm-encoder 0.208.1", "wit-bindgen-rust-macro", ] @@ -2418,11 +2418,13 @@ dependencies = [ "tracing-futures", "url", "viceroy-artifacts", + "wasm-encoder 0.208.1", "wasmparser 0.208.1", "wasmtime", "wasmtime-wasi", "wasmtime-wasi-nn", "wiggle", + "wit-component", ] [[package]] @@ -2504,15 +2506,6 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" -[[package]] -name = "wasm-encoder" -version = "0.205.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e95b3563d164f33c1cfb0a7efbd5940c37710019be10cd09f800fdec8b0e5c" -dependencies = [ - "leb128", -] - [[package]] name = "wasm-encoder" version = "0.207.0" @@ -2529,6 +2522,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6425e84e42f7f558478e40ecc2287912cb319f2ca68e5c0bb93c61d4fc63fa17" dependencies = [ "leb128", + "wasmparser 0.208.1", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 21f71cbc..d92c9d08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,13 +47,14 @@ wasmtime-wasi = "21.0.0" wasmtime-wasi-nn = "21.0.0" wiggle = "21.0.0" wasmparser = "0.208.0" +wasm-encoder = { version = "0.208.0", features = ["wasmparser"] } +wit-component = "0.208.0" # Adapter dependencies byte-array-literals = { path = "crates/adapter/byte-array-literals" } bitflags = { version = "2.5.0", default-features = false } object = { version = "0.33", default-features = false, features = ["archive"] } wasi = { version = "0.11.0", default-features = false } -wasm-encoder = "0.205.0" wit-bindgen-rust-macro = { version = "0.25.0", default-features = false } [profile.release.package.viceroy-component-adapter] diff --git a/cli/src/main.rs b/cli/src/main.rs index 5e63336f..a1e87d32 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -93,6 +93,34 @@ pub async fn main() -> ExitCode { Err(_) => ExitCode::FAILURE, } } + Commands::Adapt(adapt_args) => { + let input = adapt_args.input(); + let output = adapt_args.output(); + let bytes = match std::fs::read(&input) { + Ok(bytes) => bytes, + Err(_) => { + eprintln!("Failed to read module from: {}", input.display()); + return ExitCode::FAILURE; + } + }; + + let module = match viceroy_lib::adapt::adapt_bytes(&bytes) { + Ok(module) => module, + Err(e) => { + eprintln!("Failed to adapt module: {e}"); + return ExitCode::FAILURE; + } + }; + + println!("Writing component to: {}", output.display()); + match std::fs::write(output, module) { + Ok(_) => ExitCode::SUCCESS, + Err(e) => { + eprintln!("Failed to write component: {e}"); + return ExitCode::FAILURE; + } + } + } } } diff --git a/cli/src/opts.rs b/cli/src/opts.rs index e4295728..e1d8828b 100644 --- a/cli/src/opts.rs +++ b/cli/src/opts.rs @@ -40,6 +40,9 @@ pub enum Commands { /// Run the input wasm once and then exit. Run(RunArgs), + + /// Adapt core wasm to a component. + Adapt(AdaptArgs), } #[derive(Debug, Args, Clone)] @@ -212,6 +215,37 @@ impl SharedArgs { } } +#[derive(Args, Debug, Clone)] +pub struct AdaptArgs { + /// The path to the service's Wasm module. + #[arg(value_parser = check_module, required=true)] + input: Option, + + /// The output name + #[arg(short = 'o', long = "output")] + output: Option, +} + +impl AdaptArgs { + pub(crate) fn input(&self) -> PathBuf { + PathBuf::from(self.input.as_ref().expect("input wasm name")) + } + + pub(crate) fn output(&self) -> PathBuf { + if let Some(output) = self.output.as_ref() { + return output.clone(); + } + + let mut output = PathBuf::from( + PathBuf::from(self.input.as_ref().expect("input wasm name")) + .file_name() + .expect("input filename"), + ); + output.set_extension("component.wasm"); + output + } +} + /// Enum of available (experimental) wasi modules #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Hash)] pub enum ExperimentalModuleArg { diff --git a/lib/Cargo.toml b/lib/Cargo.toml index d436506f..c4c81d4c 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -55,6 +55,8 @@ tracing = { workspace = true } tracing-futures = { workspace = true } url = { workspace = true } wasmparser = { workspace = true } +wasm-encoder = { workspace = true } +wit-component = { workspace = true } wasmtime = { workspace = true } wasmtime-wasi = { workspace = true } wasmtime-wasi-nn = { workspace = true } diff --git a/lib/src/adapt.rs b/lib/src/adapt.rs new file mode 100644 index 00000000..96ea5d9f --- /dev/null +++ b/lib/src/adapt.rs @@ -0,0 +1,66 @@ +fn mangle_imports(bytes: &[u8]) -> anyhow::Result { + let mut module = wasm_encoder::Module::new(); + + for payload in wasmparser::Parser::new(0).parse_all(&bytes) { + let payload = payload?; + match payload { + wasmparser::Payload::Version { + encoding: wasmparser::Encoding::Component, + .. + } => { + anyhow::bail!("Mangling only supports core-wasm modules, not components"); + } + + wasmparser::Payload::ImportSection(section) => { + let mut imports = wasm_encoder::ImportSection::new(); + + for import in section { + let import = import?; + let entity = wasm_encoder::EntityType::try_from(import.ty).map_err(|_| { + anyhow::anyhow!( + "Failed to translate type for import {}:{}", + import.module, + import.name + ) + })?; + + // Leave the existing preview1 imports alone + if import.module == "wasi_snapshot_preview1" { + imports.import(import.module, import.name, entity); + } else { + let module = "wasi_snapshot_preview1"; + let name = format!("{}#{}", import.module, import.name); + imports.import(module, &name, entity); + } + } + + module.section(&imports); + } + + payload => { + if let Some((id, range)) = payload.as_section() { + module.section(&wasm_encoder::RawSection { + id, + data: &bytes[range], + }); + } + } + } + } + + Ok(module) +} + +/// Given bytes that represent a core wasm module, adapt it to a component using the viceroy +/// adapter. +pub fn adapt_bytes(bytes: &[u8]) -> anyhow::Result> { + let module = mangle_imports(bytes)?; + + let component = wit_component::ComponentEncoder::default() + .module(module.as_slice())? + .adapter("wasi_snapshot_preview1", viceroy_artifacts::ADAPTER_BYTES)? + .validate(true) + .encode()?; + + Ok(component) +} diff --git a/lib/src/lib.rs b/lib/src/lib.rs index db972744..5f03d7ae 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -13,6 +13,7 @@ #![cfg_attr(not(debug_assertions), doc(test(attr(allow(dead_code)))))] #![cfg_attr(not(debug_assertions), doc(test(attr(allow(unused_variables)))))] +pub mod adapt; pub mod body; pub mod config; pub mod error; From 995c3e8bd4d45f5ecfd6ec1b882b5e016aba727b Mon Sep 17 00:00:00 2001 From: Trevor Elliott Date: Thu, 23 May 2024 16:53:42 -0700 Subject: [PATCH 04/15] Update the lockfiles --- cli/tests/trap-test/Cargo.lock | 74 +++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/cli/tests/trap-test/Cargo.lock b/cli/tests/trap-test/Cargo.lock index 286948ab..e65fb732 100644 --- a/cli/tests/trap-test/Cargo.lock +++ b/cli/tests/trap-test/Cargo.lock @@ -1905,6 +1905,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "spdx" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ef1a0fa1e39ac22972c8db23ff89aea700ab96aa87114e1fb55937a631a0c9" +dependencies = [ + "smallvec", +] + [[package]] name = "spin" version = "0.9.8" @@ -2302,6 +2311,10 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "viceroy-artifacts" +version = "0.1.0" + [[package]] name = "viceroy-lib" version = "0.9.8" @@ -2336,11 +2349,14 @@ dependencies = [ "tracing", "tracing-futures", "url", + "viceroy-artifacts", + "wasm-encoder 0.208.1", "wasmparser 0.208.1", "wasmtime", "wasmtime-wasi", "wasmtime-wasi-nn", "wiggle", + "wit-component", ] [[package]] @@ -2438,6 +2454,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6425e84e42f7f558478e40ecc2287912cb319f2ca68e5c0bb93c61d4fc63fa17" dependencies = [ "leb128", + "wasmparser 0.208.1", +] + +[[package]] +name = "wasm-metadata" +version = "0.208.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42a2c4280ad374a6db3d76d4bb61e2ec4b3b9ce5469cc4f2bbc5708047a2bbff" +dependencies = [ + "anyhow", + "indexmap", + "serde", + "serde_derive", + "serde_json", + "spdx", + "wasm-encoder 0.208.1", + "wasmparser 0.208.1", ] [[package]] @@ -2574,7 +2607,7 @@ dependencies = [ "syn", "wasmtime-component-util", "wasmtime-wit-bindgen", - "wit-parser", + "wit-parser 0.207.0", ] [[package]] @@ -2773,7 +2806,7 @@ dependencies = [ "anyhow", "heck 0.4.1", "indexmap", - "wit-parser", + "wit-parser 0.207.0", ] [[package]] @@ -3065,6 +3098,25 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "wit-component" +version = "0.208.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fef7dd0e47f5135dd8739ccc5b188ab8b7e27e1d64df668aa36680f0b8646db8" +dependencies = [ + "anyhow", + "bitflags 2.5.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder 0.208.1", + "wasm-metadata", + "wasmparser 0.208.1", + "wit-parser 0.208.1", +] + [[package]] name = "wit-parser" version = "0.207.0" @@ -3083,6 +3135,24 @@ dependencies = [ "wasmparser 0.207.0", ] +[[package]] +name = "wit-parser" +version = "0.208.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516417a730725fe3e6c9e2efc8d86697480f80496d32b24e62736950704c047c" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver 1.0.23", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser 0.208.1", +] + [[package]] name = "witx" version = "0.9.1" From 8cff699ffbdf1c7e7d4d5ff3fb4b00226cb3f5bf Mon Sep 17 00:00:00 2001 From: Trevor Elliott Date: Thu, 23 May 2024 16:57:38 -0700 Subject: [PATCH 05/15] Add the wasm32-unknown-unknown target while building --- .github/workflows/test.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3ea233b1..9bd96cdf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,6 +21,8 @@ jobs: shell: bash - name: Add wasm32-wasi Rust target run: rustup target add wasm32-wasi + - name: Add wasm32-unknown-unknown Rust target + run: rustup target add wasm32-unknown-unknown - name: Cache cargo uses: actions/cache@v3 with: @@ -60,6 +62,8 @@ jobs: shell: bash - name: Add wasm32-wasi Rust target run: rustup target add wasm32-wasi + - name: Add wasm32-unknown-unknown Rust target + run: rustup target add wasm32-unknown-unknown - name: Cache cargo uses: actions/cache@v3 with: From b2cb65bdabbf993fb4e6e593093c1d36a992e257 Mon Sep 17 00:00:00 2001 From: Trevor Elliott Date: Thu, 23 May 2024 16:58:21 -0700 Subject: [PATCH 06/15] Fix the publish script --- Cargo.lock | 4 ++-- cli/tests/trap-test/Cargo.lock | 2 +- crates/adapter/Cargo.toml | 2 +- crates/adapter/byte-array-literals/Cargo.toml | 1 + crates/artifacts/Cargo.toml | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b724b410..3c9c99bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2368,11 +2368,11 @@ dependencies = [ [[package]] name = "viceroy-artifacts" -version = "0.1.0" +version = "0.9.7" [[package]] name = "viceroy-component-adapter" -version = "0.1.0" +version = "0.0.0" dependencies = [ "bitflags 2.5.0", "byte-array-literals", diff --git a/cli/tests/trap-test/Cargo.lock b/cli/tests/trap-test/Cargo.lock index e65fb732..e43dbbfb 100644 --- a/cli/tests/trap-test/Cargo.lock +++ b/cli/tests/trap-test/Cargo.lock @@ -2313,7 +2313,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "viceroy-artifacts" -version = "0.1.0" +version = "0.9.7" [[package]] name = "viceroy-lib" diff --git a/crates/adapter/Cargo.toml b/crates/adapter/Cargo.toml index 186e6755..fd9919f4 100644 --- a/crates/adapter/Cargo.toml +++ b/crates/adapter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "viceroy-component-adapter" -version = "0.1.0" +version = "0.0.0" edition = "2021" publish = false diff --git a/crates/adapter/byte-array-literals/Cargo.toml b/crates/adapter/byte-array-literals/Cargo.toml index fa451017..6f27edb0 100644 --- a/crates/adapter/byte-array-literals/Cargo.toml +++ b/crates/adapter/byte-array-literals/Cargo.toml @@ -1,5 +1,6 @@ [package] name = "byte-array-literals" +version = "0.0.0" publish = false [lib] diff --git a/crates/artifacts/Cargo.toml b/crates/artifacts/Cargo.toml index 1d91ebbf..3d45b281 100644 --- a/crates/artifacts/Cargo.toml +++ b/crates/artifacts/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "viceroy-artifacts" -version = "0.1.0" +version = "0.9.7" edition = "2021" [dependencies] From 91f2766c22d993918972b3d72fbb301c64453f16 Mon Sep 17 00:00:00 2001 From: Trevor Elliott Date: Thu, 30 May 2024 10:52:46 -0700 Subject: [PATCH 07/15] Commit the built adapter --- Cargo.lock | 99 ++++++++++++++++----------- Cargo.toml | 2 - Makefile | 10 +++ cli/tests/trap-test/Cargo.lock | 99 ++++++++++++++++----------- crates/artifacts/Cargo.toml | 6 -- crates/artifacts/build.rs | 44 ------------ crates/artifacts/src/lib.rs | 1 - lib/Cargo.toml | 3 +- lib/data/wasi_snapshot_preview1.wasm | Bin 0 -> 134217 bytes lib/src/adapt.rs | 4 +- test-fixtures/Cargo.lock | 12 ++-- 11 files changed, 138 insertions(+), 142 deletions(-) delete mode 100644 crates/artifacts/Cargo.toml delete mode 100644 crates/artifacts/build.rs delete mode 100644 crates/artifacts/src/lib.rs create mode 100755 lib/data/wasi_snapshot_preview1.wasm diff --git a/Cargo.lock b/Cargo.lock index 3c9c99bd..a67a1bbc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,16 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ - "gimli", + "gimli 0.28.1", +] + +[[package]] +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli 0.29.0", ] [[package]] @@ -133,16 +142,16 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" -version = "0.3.71" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +checksum = "17c6a35df3749d2e8bb1b7b21a976d82b15548788d2735b9d82f329268f71a11" dependencies = [ - "addr2line", + "addr2line 0.22.0", "cc", "cfg-if", "libc", "miniz_oxide", - "object 0.32.2", + "object 0.35.0", "rustc-demangle", ] @@ -403,7 +412,7 @@ dependencies = [ "cranelift-control", "cranelift-entity 0.108.1", "cranelift-isle", - "gimli", + "gimli 0.28.1", "hashbrown 0.14.5", "log", "regalloc2", @@ -876,6 +885,12 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + [[package]] name = "h2" version = "0.3.26" @@ -1303,22 +1318,22 @@ dependencies = [ [[package]] name = "object" -version = "0.32.2" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "d8dd6c0cdf9429bce006e1362bfce61fa1bfd8c898a643ed8d2b471934701d3d" dependencies = [ + "crc32fast", + "hashbrown 0.14.5", + "indexmap", "memchr", ] [[package]] name = "object" -version = "0.33.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8dd6c0cdf9429bce006e1362bfce61fa1bfd8c898a643ed8d2b471934701d3d" +checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e" dependencies = [ - "crc32fast", - "hashbrown 0.14.5", - "indexmap", "memchr", ] @@ -1375,9 +1390,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "parking_lot" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -1465,9 +1480,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.83" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" +checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" dependencies = [ "unicode-ident", ] @@ -1788,18 +1803,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.202" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.202" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", @@ -2366,10 +2381,6 @@ dependencies = [ "wat", ] -[[package]] -name = "viceroy-artifacts" -version = "0.9.7" - [[package]] name = "viceroy-component-adapter" version = "0.0.0" @@ -2417,7 +2428,6 @@ dependencies = [ "tracing", "tracing-futures", "url", - "viceroy-artifacts", "wasm-encoder 0.208.1", "wasmparser 0.208.1", "wasmtime", @@ -2525,6 +2535,15 @@ dependencies = [ "wasmparser 0.208.1", ] +[[package]] +name = "wasm-encoder" +version = "0.209.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4a05336882dae732ce6bd48b7e11fe597293cb72c13da4f35d7d5f8d53b2a7" +dependencies = [ + "leb128", +] + [[package]] name = "wasm-metadata" version = "0.208.1" @@ -2584,7 +2603,7 @@ version = "21.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f92a1370c66a0022e6d92dcc277e2c84f5dece19569670b8ce7db8162560d8b6" dependencies = [ - "addr2line", + "addr2line 0.21.0", "anyhow", "async-trait", "bumpalo", @@ -2592,7 +2611,7 @@ dependencies = [ "cfg-if", "encoding_rs", "fxprof-processed-profile", - "gimli", + "gimli 0.28.1", "hashbrown 0.14.5", "indexmap", "ittapi", @@ -2698,7 +2717,7 @@ dependencies = [ "cranelift-frontend", "cranelift-native", "cranelift-wasm", - "gimli", + "gimli 0.28.1", "log", "object 0.33.0", "target-lexicon", @@ -2717,7 +2736,7 @@ dependencies = [ "anyhow", "cpp_demangle", "cranelift-entity 0.108.1", - "gimli", + "gimli 0.28.1", "indexmap", "log", "object 0.33.0", @@ -2856,7 +2875,7 @@ checksum = "97b27054fed6be4f3800aba5766f7ef435d4220ce290788f021a08d4fa573108" dependencies = [ "anyhow", "cranelift-codegen", - "gimli", + "gimli 0.28.1", "object 0.33.0", "target-lexicon", "wasmparser 0.207.0", @@ -2888,24 +2907,24 @@ dependencies = [ [[package]] name = "wast" -version = "208.0.1" +version = "209.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc00b3f023b4e2ccd2e054e240294263db52ae962892e6523e550783c83a67f1" +checksum = "8fffef2ff6147e4d12e972765fd75332c6a11c722571d4ab7a780d81ffc8f0a4" dependencies = [ "bumpalo", "leb128", "memchr", "unicode-width", - "wasm-encoder 0.208.1", + "wasm-encoder 0.209.1", ] [[package]] name = "wat" -version = "1.208.1" +version = "1.209.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ed38e59176550214c025ea2bd0eeefd8e86b92d0af6698d5ba95020ec2e07b" +checksum = "42203ec0271d113f8eb1f77ebc624886530cecb35915a7f63a497131f16e4d24" dependencies = [ - "wast 208.0.1", + "wast 209.0.1", ] [[package]] @@ -2990,7 +3009,7 @@ checksum = "1dc69899ccb2da7daa4df31426dcfd284b104d1a85e1dae35806df0c46187f87" dependencies = [ "anyhow", "cranelift-codegen", - "gimli", + "gimli 0.28.1", "regalloc2", "smallvec", "target-lexicon", @@ -3149,9 +3168,9 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d" +checksum = "86c949fede1d13936a99f14fafd3e76fd642b556dd2ce96287fbe2e0151bfac6" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index d92c9d08..3d9e7093 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,6 @@ members = [ "lib", "crates/adapter", "crates/adapter/byte-array-literals", - "crates/artifacts", ] resolver = "2" @@ -39,7 +38,6 @@ tracing = "0.1.37" tracing-futures = "0.2.5" futures = "0.3.24" url = "2.3.1" -viceroy-artifacts = { path = "crates/artifacts" } # Wasmtime dependencies wasmtime = "21.0.0" diff --git a/Makefile b/Makefile index f727a2f0..6970f4b8 100644 --- a/Makefile +++ b/Makefile @@ -73,3 +73,13 @@ package-check: rm publish rm -rf .cargo/ rm -rf verify-publishable/ + +# Re-generate the adapter, and move it into `lib/adapter` +.PHONY: adapter +adapter: + cargo build --release \ + -p viceroy-component-adapter \ + --target wasm32-unknown-unknown + mkdir -p lib/adapter + cp target/wasm32-unknown-unknown/release/wasi_snapshot_preview1.wasm \ + lib/adapter/ diff --git a/cli/tests/trap-test/Cargo.lock b/cli/tests/trap-test/Cargo.lock index e43dbbfb..098b8fcf 100644 --- a/cli/tests/trap-test/Cargo.lock +++ b/cli/tests/trap-test/Cargo.lock @@ -8,7 +8,16 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ - "gimli", + "gimli 0.28.1", +] + +[[package]] +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli 0.29.0", ] [[package]] @@ -148,16 +157,16 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" -version = "0.3.71" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +checksum = "17c6a35df3749d2e8bb1b7b21a976d82b15548788d2735b9d82f329268f71a11" dependencies = [ - "addr2line", + "addr2line 0.22.0", "cc", "cfg-if", "libc", "miniz_oxide", - "object 0.32.2", + "object 0.35.0", "rustc-demangle", ] @@ -426,7 +435,7 @@ dependencies = [ "cranelift-control", "cranelift-entity 0.108.1", "cranelift-isle", - "gimli", + "gimli 0.28.1", "hashbrown 0.14.5", "log", "regalloc2", @@ -880,6 +889,12 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + [[package]] name = "h2" version = "0.3.26" @@ -1306,22 +1321,22 @@ dependencies = [ [[package]] name = "object" -version = "0.32.2" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "d8dd6c0cdf9429bce006e1362bfce61fa1bfd8c898a643ed8d2b471934701d3d" dependencies = [ + "crc32fast", + "hashbrown 0.14.5", + "indexmap", "memchr", ] [[package]] name = "object" -version = "0.33.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8dd6c0cdf9429bce006e1362bfce61fa1bfd8c898a643ed8d2b471934701d3d" +checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e" dependencies = [ - "crc32fast", - "hashbrown 0.14.5", - "indexmap", "memchr", ] @@ -1372,9 +1387,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -1462,9 +1477,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.83" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" +checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" dependencies = [ "unicode-ident", ] @@ -1785,18 +1800,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.202" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.202" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", @@ -2311,10 +2326,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "viceroy-artifacts" -version = "0.9.7" - [[package]] name = "viceroy-lib" version = "0.9.8" @@ -2349,7 +2360,6 @@ dependencies = [ "tracing", "tracing-futures", "url", - "viceroy-artifacts", "wasm-encoder 0.208.1", "wasmparser 0.208.1", "wasmtime", @@ -2457,6 +2467,15 @@ dependencies = [ "wasmparser 0.208.1", ] +[[package]] +name = "wasm-encoder" +version = "0.209.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4a05336882dae732ce6bd48b7e11fe597293cb72c13da4f35d7d5f8d53b2a7" +dependencies = [ + "leb128", +] + [[package]] name = "wasm-metadata" version = "0.208.1" @@ -2516,7 +2535,7 @@ version = "21.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f92a1370c66a0022e6d92dcc277e2c84f5dece19569670b8ce7db8162560d8b6" dependencies = [ - "addr2line", + "addr2line 0.21.0", "anyhow", "async-trait", "bumpalo", @@ -2524,7 +2543,7 @@ dependencies = [ "cfg-if", "encoding_rs", "fxprof-processed-profile", - "gimli", + "gimli 0.28.1", "hashbrown 0.14.5", "indexmap", "ittapi", @@ -2630,7 +2649,7 @@ dependencies = [ "cranelift-frontend", "cranelift-native", "cranelift-wasm", - "gimli", + "gimli 0.28.1", "log", "object 0.33.0", "target-lexicon", @@ -2649,7 +2668,7 @@ dependencies = [ "anyhow", "cpp_demangle", "cranelift-entity 0.108.1", - "gimli", + "gimli 0.28.1", "indexmap", "log", "object 0.33.0", @@ -2788,7 +2807,7 @@ checksum = "97b27054fed6be4f3800aba5766f7ef435d4220ce290788f021a08d4fa573108" dependencies = [ "anyhow", "cranelift-codegen", - "gimli", + "gimli 0.28.1", "object 0.33.0", "target-lexicon", "wasmparser 0.207.0", @@ -2820,24 +2839,24 @@ dependencies = [ [[package]] name = "wast" -version = "208.0.1" +version = "209.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc00b3f023b4e2ccd2e054e240294263db52ae962892e6523e550783c83a67f1" +checksum = "8fffef2ff6147e4d12e972765fd75332c6a11c722571d4ab7a780d81ffc8f0a4" dependencies = [ "bumpalo", "leb128", "memchr", "unicode-width", - "wasm-encoder 0.208.1", + "wasm-encoder 0.209.1", ] [[package]] name = "wat" -version = "1.208.1" +version = "1.209.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ed38e59176550214c025ea2bd0eeefd8e86b92d0af6698d5ba95020ec2e07b" +checksum = "42203ec0271d113f8eb1f77ebc624886530cecb35915a7f63a497131f16e4d24" dependencies = [ - "wast 208.0.1", + "wast 209.0.1", ] [[package]] @@ -2922,7 +2941,7 @@ checksum = "1dc69899ccb2da7daa4df31426dcfd284b104d1a85e1dae35806df0c46187f87" dependencies = [ "anyhow", "cranelift-codegen", - "gimli", + "gimli 0.28.1", "regalloc2", "smallvec", "target-lexicon", @@ -3081,9 +3100,9 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d" +checksum = "86c949fede1d13936a99f14fafd3e76fd642b556dd2ce96287fbe2e0151bfac6" dependencies = [ "memchr", ] diff --git a/crates/artifacts/Cargo.toml b/crates/artifacts/Cargo.toml deleted file mode 100644 index 3d45b281..00000000 --- a/crates/artifacts/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "viceroy-artifacts" -version = "0.9.7" -edition = "2021" - -[dependencies] diff --git a/crates/artifacts/build.rs b/crates/artifacts/build.rs deleted file mode 100644 index 61c8d37c..00000000 --- a/crates/artifacts/build.rs +++ /dev/null @@ -1,44 +0,0 @@ -use std::{path::PathBuf, process::Command}; - -fn main() { - build_adapter() -} - -fn build_adapter() { - let out_dir = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); - - // ensure that we rebuild the artifacts if the adapter is changed - println!("cargo:rerun-if-changed=../adapter/src"); - println!("cargo:rerun-if-changed=../../lib/wit"); - - let mut cmd = Command::new("cargo"); - - cmd.arg("build") - .arg("--release") - .arg("--package=viceroy-component-adapter") - .arg("--target=wasm32-unknown-unknown") - .env("CARGO_TARGET_DIR", &out_dir) - .env_remove("CARGO_ENCODED_RUSTFLAGS"); - - eprintln!("running: {cmd:?}"); - let status = cmd.status().unwrap(); - assert!(status.success()); - - let adapter = out_dir.join(format!("wasi_snapshot_preview1.wasm")); - - std::fs::copy( - out_dir - .join("wasm32-unknown-unknown") - .join("release") - .join("wasi_snapshot_preview1.wasm"), - &adapter, - ) - .unwrap(); - - let mut generated_code = String::new(); - - generated_code += - &format!("pub const ADAPTER_BYTES: &'static [u8] = include_bytes!({adapter:?});\n"); - - std::fs::write(out_dir.join("gen.rs"), generated_code).unwrap(); -} diff --git a/crates/artifacts/src/lib.rs b/crates/artifacts/src/lib.rs deleted file mode 100644 index 26a930a6..00000000 --- a/crates/artifacts/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -include!(concat!(env!("OUT_DIR"), "/gen.rs")); diff --git a/lib/Cargo.toml b/lib/Cargo.toml index c4c81d4c..5def6f37 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -21,6 +21,7 @@ include = [ "src/**/*", "wit/**/*", "compute-at-edge-abi/**/*.witx", + "data/*.wasm", ] [dependencies] @@ -62,8 +63,6 @@ wasmtime-wasi = { workspace = true } wasmtime-wasi-nn = { workspace = true } wiggle = { workspace = true } -viceroy-artifacts = { workspace = true } - [dev-dependencies] tempfile = "3.6.0" diff --git a/lib/data/wasi_snapshot_preview1.wasm b/lib/data/wasi_snapshot_preview1.wasm new file mode 100755 index 0000000000000000000000000000000000000000..7cea2921b8515c601bd5dfa2f0e70541153c5038 GIT binary patch literal 134217 zcmeFa37lJ3bwB>@dy+;oniYmPiT4a zXclQAY3xi(z!`We=s4@&igKd!bE3%Th|A&F_28y>Hc< z&GuyZpn&JSch_^zJ@@SINh`INgr;fY$9n2n;+*y@O=_*HeQ8p6ve!l5@|}v}f%= z35KB-eAS*c94sIx(HKB6$k)>a5bzaX(?axf^jAV6YdxT&ppWL2J@{6R*iWTAGnx?i z_)iUU?s~Z(UKt?r{)Z;srQp#)4ekLg`V#VRiDLBYuh;Y+9sf;XSdBBWC2OhKTGch} z;Mr2UexkD2tSq#TpDiseruhD3`q;#=^gzpMHy2kr^=3oU#=I?;nvG_s*{E06YA)6t z*l0A*Ze~PoCwBA9_2zM_)oQjRXd_Q2*j8EZD{r zs=V5<+FEpXt<>%;uAV3@*N@M2I?Jh+b?yFY^DI_E=uNe)))}jnsxPNX)oM%AMuP3j z&FX3dO{-cYY^=VORy~DjwOClKcc#i>-D-5)nRa<*Dwiq?R-+mx_~)AKj@B18n(2XC zjW#!ts?{5(t=4j@j*SUj?xmh?oy9f~bQUzvz*MYOr(Ua9N*zm!4toGvDJ_rMySV}! zrAns({IR8aquJ78ftLG2TGmQS^~Ke2tn3Q_wA{i;tTY$H!QT~VJztvg!+P3k#ul5+ zg_Y$^qpdl&G1?9U=Cv_yEiN{-XwrkMN~tnu?WDb9RNzyp2N zI#VYeWOb|x@dO8#fn5%xhvibIG8ewdKF}q#;pnYcZ5)BBe*@Pdw$eNiTNqvjBsY`? zZ%47XRH`?MOU>%aqE##oR!Zf15l0pqRnhvx`tRZ`OEq!yTlK01F3`~i!W!-gG(0oG zZ4+T_j|4IAp0N}rs@GPraqX4GPWWCM4GR(_Y~bV^*bnxQ_O8uq9t>-?zuBsvt~W}H zsjl`zVeR(>+gF+^;34aRaHh1lVlfEAVMFdzGjlTu>W)pcBlIW3+6}4kAb7y3)^Af< z28mbKZ_|R(aK_ShhRtQu6af?3fLZ;TwFzu-8^?!{6MFKU7BohvFp0Ej1)+r4M^&ja z6XCT3Tw7|dHY(u_M%)HUcYxFgZ@Qb%h+2}ADH6CeYv_V}Ezg$foi$tT@{ZZI7Qt!4 zF|en*Aq^UeEwzcM1#2}`Z>KJ80 z(1~}hET3-SM5fMK%c|B}SZfM1IBio9ch{|kFX`dy^dLNh(&b)nr_(K{ zGV9E+KfLjpgOAo)kmR5Sc@obz^E)@p=Q4*%piYq_>Y9m3#GFAk;fEuzV-26QlI9EPXeGSVprnHtw zsNE@5!q6&p_6EW$mAnhB90qd**MUi=6sFKtg9~tQG#obP-SCAoV4KaBr4D#lmCf3H zv|#reRA*MIE1^vs%|bOL<-#w%?ZIM7HS{S>cIBX~Oob>$b`xhN)pYHd2CL9pK!KY9 zTQ!w~b>WvZj5W5FszTKL&>`%09-Tb2U6@k7nTD*Dhc*nV(C}USOs=%G;L>SvjI{GR;29`K9}8M?Wt^?BXojwG^)_{``B%@U|Gvh zaL-sC1#F*3?zSz6OVVfDgSl2Uv)Y|&)%Gth!kF$@*TJQ=R-1uZtGT2+Y>{wgIBxrd ze7?Xkd1<9>0f$;Fyz4Q$tB-*mbNaH@k%IRxRa;zX&q4pN-HJY!>45+{_5z}5UP;3? zzRgJXcGdW_QN?cdb+sDz>Z~^30gTGeNNTXw-OD%j$}-$WmbNcMd5BrpL9Gr~7D(|> z+IFZ2XU~-5>X7cmkSSP?Ty8?Xg=sG_OalLr7A~lE+A_uCqUa)KQm37U>oF~ANQhx6 z#SN7!!FY{+i>{Bv^*#Ng{rmUtzvRF{J+ANHfAC=k9=;zXkLWkY#t$7nZ1!vV$o|p8 z=KlS^Q_UrxVd-(8WfCV+C-mmvv`pCHc=tpb&`w#O#)CuE_VhU3~ ze3^N8w0~4TJZ=I~lppRN-M`;75BFp4NF=gzVCT-!NaXVU5e$Q3v>(;cg8st*m}GSS zh&G}f9UYBC#j+40c1iS{C};`KAB`UAPv31v|4i(HXL1?lS&@|UV!2eURkhy`yX{ie z8&vTd#cptDt5dWqrLFW^VmJHO>Pti`j+^aGs@S^DSNs+cFSSm$i^RF#Du(&KUH>IZ zmb^_2ajXWT$b#P%!?J)BUheceqQ6!J2xscBWPVp9=z;utbWm;=aXd<0=l8gZ+^*~d zdAk@wRjo=e^EmGiyWIj>Rc*0!x~=`b81Wawg*JzC_D)$23gi)}1A(pGDfUo%Z#jY5 z*51Xiz=Ht3_HHqZM&wPfXa579-7bMspvZpM_lQ0;Q6*JS1r~X3)-Km2e!-pObl@iP0qD{E)uGM zLAy%~@?(2-iL2j_YWRYR&4$&i)wI9BSgmHI2)5MG{!$F^15PY9=>sCshLKk-uGX!^ zs`gi6Kt^PE8$O6ih=5|LQn8jh+J{6xKf+0JM*FaOAUUmlL=16Fdu|0P{@I51QISB{ zuik1l6fy1aZUo`s9gMRM{PVcoX*c##8ZX13R{dR|^>m!YWFy4Z@4s zdsKzgPka?g?y=G!^zeqDF>R-!KcyBIS-2chPK^6N2ON)5#Y&pos6WD2>=7kJ)$=*) zG*P46q#s{%6xpN*p$QuXi(JAXhd{b{ol%uEdhR++l+)#Tx)LX9Rq{kWU*GEk2U|q; z6VB}m^oMuVkQ1WlL{5HHS5~4Lk@~VX=GSzklXxnWE9Ql|ifVYO;D;%~aZ-dI=0*CW zyCwxV2pF&v2B*+=!cglMhs>h}dPxXubL4(`sXl7Y&}`J|r?FZnV~6;-z05&I)p3x> znwPsXd3zB-ze3-k21G#PP`!bp_DWqT9z_IA4oUyctDF(wlR0F2Z1|Mi;xwl!N%^mK z;P-$+)4j&2fG_3{`)P{or@2)(6&zIIajFzuC(Ud1@ip2=0+u7aPCvAME9suOP2cZn z=K5wJ2ddiZ*QzAP%&$9ZP=3clN^$xPr9Q4{fi5gjrdZU9F zZ5Z+-wcGy4v~y1x-JIZ zjKB5oEHz`Dpy==k=xf@i9bUj&7Sig>M3EZ)ox=;9hAKou>Ol-BK{oi#P%RCvU@n`k)#v{*7KreEWQ$D93-M9~6q#S`FLV2Ix%p6Tp zhTS>-UY}TJ4&*}=Wj+MkKg7&f+5LjE=?+dkc%oj=Z)J+gKRE1lt)>dCbDSsgMg5T* zz=QmcBISSlV>g)uV4%d8FRhP5Cq)D-lv6~$tWR$+KeB;fLQytQ(VGqQ6+OQ}M^7hW z^hj^Oq@3RHRX_V7vXi7~v&65h*+3oquXod;ovfg!oHtGyjo;`#I>E+Lt@tPB{5yN6 z;Bxu!H}$dLx>XwwfBn`*jp(5M^P01{{QO_mM1*pP-sA8`2V|N0!mQ&u+GChr)|W zlVp>NY}+4pV;Wh6yrcgWUPasaBgejRwo~a>-1En4S5xo*?iNJ@ zd7CM%?Y{04*WHS+a)V*(`wqXx-zC>Hf$e%U2g6rK2Ka9qw%YEiasx8IHX2*H7vvlfQW*O2_phx>or9C*Jr%{i-3&#M6)OGwf$!ib8ze z)RS+ZrlvTe502?{iH{km(zALJm;Ag*Ps#b@^KL>j8ru}dMIOU(pDVMNK^rp$MS{vt zj2Q`d)p=eqk2>_dOPiN~nMUEh`;2RMpk={4p-tg(&!^sZdn~J^O>yeWAAaslAA0+1 z-)H8uBY6D&H*a~<^WJ>xAK}r&<5xa=%T1qp(HHOhs+QA6srm~ax&77m-1_b>|L@-GZgD9Mn1T!)}W)O`W(Qh3sNU|$L=26do-Y`0!d@ckv481|5qH|d4(h|2|Zhy@&aFb`9Jfk@$_d4!KJ@#GOcx@-@bGcE`` z0Wvd^Zzjewrjsl|^9WVp{F!3DaBd#NrY~GOj%M@YAj&79`5ltWQ`+DBjh)#rTeHd2Ua+rItH8*hBuy|?}Gx1gTTwteNF zU;D1leEjtitiBU*BlGKODJ2+Xv9T{`o(BWnY$;@a=#8o14CK_iJDF zukI3tmBtmmgj;nV2Qq99L<++^@qrKjlcAV}0J8wsSupkaalL@@ClD7j!PIaMP1Iou z0!+b>Ou=xOg5hThhMy@I0j7XzA5$>AOyMmyW(@mWazW{Ak!(xWDM-`2%t)TnPTlaT zfBAxON=yDesiTFTA|>=PBYsMI@(?%C#-SBV>!Z&ekD8IE4T(WLk%Z;|)gcO-%#7v? zK*WY70Bst8pmsBotN8iS#VnNbB5;0IJOGKpgn zDkvmiHa1j|1aNpl{`s$n=(Dw}^Rwnzx4*--=9A;badE@wScH`Ok+CS&8PiWfjna~D zC&g*J&x||?Jkc=w3Zskjq^#+W!*kS((!FiH2VDeFU>F0&z1D(`QDH4;YzrKq^#%rU zT>!D3z2Na=kd5M%9LgZDO;JY`Ql5-$vlo7%4E!6{x3R&Iw$Z!r-1R4S*#Y0ZBO;X6N2Fjbqmq#2IHkxhIgzHblmB}vL z9u?3z$lJY)0R7-GE~U%+2Sv=LB$6&Z1<@9NbTxI@Hj0yG?5d%dYyh?62}6B2aRRCk zjgmlCdcSjMNS|_rAekJj$vy@b$6x02LrlWL^Y|eyCxg!?IQ)JH7>J?%GG#-nYTM*s z<(h`{x&p}L^d1}4$38aPN0qr|$M!D6IW`aNWqiyvYS-g&)xYQfKAl(^}BBV?ti@a*Wdkh zJQH<&`<`1~`GZe<;l1B;m$m-^T-GZijx~JJ6reH&kq|y6L4Wlm4ak0NEL~NsUm}h$v{W=$1A%)3V`=jgtAj&x91#{E zi&Pw?XvEF9b3`zV>#lS3jH4)32jd8dc{kK<;?bjLjN;LIFp^a5acz=P6?fwuj(V;H zw5qlb2I~wor1xOZklur#`q&49`=~O{!C-Z1yO!Y`jO|`Vw|cIeKW@dFFVTlw=h>HB zRb8DAa3)e)Baa`867>ZCC#&z$9*>)7XFR5YWbntJK<|wA$pZOz9F#LZ-fzaAWcJIF z$FnjFHNS9hY+x{oW2jD3!gHE55HX%T=LrOB#z6B6V+gJn(u-q5rasmOH8_TU{l-Ze ztqW3|r}^<=kW&g@GBGrv4<*du!jXA$B#CRscHkjNZmD5iHwKBW&3QB79$Ire$%l3a z9h}2s0|m|Qp!{3I=8h)~4VXz3!=4=<<}-2?N^ln$G6n}|%p<-rk9fyCy4INeU1Rps zm|z5aO(vg9M#9*zIbaSr2XhdfKL9chN8#8`AaDn%ZyxR zhc5-}&xzpSwYp(y5w~Y@!VLW-quY|Zs2dL91xk>phiBX0N>`R?Dht1>xbH~CR z&mPBE*9>9pJCo0oU=G3y>p(_u3DiAb)q(e5Y4PL>!j=Rh#~B5rV)oIRgxQbj$RXSZ zlMzg@A9)^Q{pOGv17{kKJ#MHE{GIpng!T-eKryDL5?^o`)6Ws-adUvK#-o=pfy??B zQ=W0@f>Wfsw~Kuwrd$d#`F+fMo>&VM3?cg`;Dl) zj04IDPHR6MOHp|C{8-EY(}yb3PrPv)c~??j_*FWfXon`_Xsi#yQv-rV=0+O4KQ7t( zSd19=AiO%bL~u3}<{&vu`i2bL!haIS@r9oT2Q~R?G@uD@4>bTEnlkk#z}temv^jVc zyete&A47vf&d|gpG_j-)8t5w!17OhPaO9!JW6b?96c__kq0ZPa)k1gDq|+n~h8=@h z06`KXj=Ad;b4^=@n6Z=w9ctKsNrFYQa^uiBJY8k@S=p+ezfjvP77>5KaRHjv_DU3PE=yJ!RRz zycs*8Q5iHyisG%VQuzox zdX>t3fpF{&479_wc3&D-l0b!HRkf{Da$-4BM^F9dcW!*&5AJ^bdnmCyhv%=~_2%zB z@8;LPkKDNo&^^!n*az>p^_}N-)sz|KHsvW*nX8k&T)4lM<3CK4f@MFgLG#wSuj zNvA=c2I(}&$}pwM0%e86F>zCe8e~AVxdX;&d_MU?CKZSlM4Y5m4R4UCNs;4tPYRUm zq!TYYii_ht@wpXE4DG?$C?37^l%#48&HxF?Bs;wCsKv@3r>bpwVizF|=`~VlNUxEi z`q)Ma_fcgo_P2K#j*;@vUWOwX*IC9cV3H*xZ-YlNDwNxj@qyR|M>4JhKhdjKG7@!p zB_l1%D;d?YY{~dQEbCo1V_9ce7v+!yxTear0DsuVynqkbiwjRm2!Iq*LM|pjirGx* zax>;<LPa=QJ3(}WukfIVKu3Qr&B~~QQJC2KD-4vkli8~+bMkpo#lAm>xDDWiuf71qu zkA$jpLHco@h}hW{;gT*yu60MuSgx^nQ*sT~5L@FU>@q$iwE zd5R-rl;lKiA;l4*;0SQW5Jv#hfQJd%IW*lDE5;xcW59Kgx;l&jhn@;>NsfUf(CCse z7%pSL9D_6_y@*M?K}g=<*b(%$u7|OjemjC**7b@XyhZEd^-#-0bz)^>scKs%W`l=@ z^y|a`z!Pd$eQceW`=~OPMQ`si9NzHIUWTI+ud|F@z@*ZNu?;?*n0MCJi64k%?TSyO~QwuzsDGmu2h3Yb-0Ocmtw-!sQK-#BC1(-f#yWx#UZF%z+$6a98x0 z1Ak{>i>$iG9Qe}{7m+#K8ej7jAX|6XqUJCtH3z)n#0tbXz1o8yAXE#A{`pi0iUD#C zHJt_|PLua6I<0HTm&&X>sTYvKFy2?V5xnC%SPg{%#DMy2T_H-7BuQ65vk|BZ&?t0A zW!SE_>K!+lV)EPemJIL$U%+-PBd9VwM3)id3SrB*6qr=W&VH3aHTJ0tsw}9vZPzvg zRfaJ5>0=gNm4Q6zUX_7d>0XsVjp0)nLY9@%l1ghp){XbOIeOxLb|}2G=TMM(fljuG zhxE2J9^i0*?RIs-$lJ!ShQk5YSw_#1PFiP>y4G?iyu@QPc%q%QPC(Z!>IGZ!D6l#p z-3oUkKk0doip2cjc*L&_M10x+dk%Wq0KE|7_k(b_)#LM4@}SGP_|R5r9A2=_GJ4to zy~E1iyYfgHxa!u_2HxVa6Wjp*1+atjGZSnJ0kFf@__8o}0=?lwAqzGF$k&L7Joy?U zunb^-NSngp7;L1l{Qys9&(~;w?{EW`siQWmxJ-krLOEr8p5LCYG0a(ID60W4 z>U(^ReTkQP)P{?~CO80rBQ5i6eBJ}RPyz1&LIemvDF5Qi!x(}cU~m`$2Oecq(q^}a(1mS* z^qdv8!qIb9tl_M%Sxi^x;-SW4?hZIOdpw3dLd5PVUq)4k%c>V1r4JqX^V+9*@~7k9iPfRlPFI$mykAY8tX+x7B&mbAwVkb}!{}ESPfn=D3L4FJoLB69>7{ zWI)h67P!Qvi4&m@7I0L9Y#W$3R7d|O+a}Jo4PG`^$yJnx#TiyH%CNdKpmGe3$Ne$Y zxa|;SV`_vt_hPEOn5z9XI#$klD#U!@0y3-~L@LCqdMZTEPula75)JUjFv23i+v6u) z>#dD%KKT8ltWeM&*IW9ZH{JlZmF^^N_dFRrPloqRy$kzIzMdyTI(yD5(R)pA=_liQ z-^Gr|oL_bq|6(U)eDGT!GXCUypHL6u}qhI84K4O}rO> z!DoOj?`FF$)prr)x4Rb_;dv&jT!~xT23LI)+5Pxk%fWWP_H+15V@mpkjmFgbT`0?K_NZl z_bYneu6WTG@BAv>IN2bpsb^X^mikZpw<|c$$NiZ$?SX#lin}7{Xe5!vh?ncx{g%sBKrW!TYtM z?ffQqPeb?#f3ssPhHzJ0k6!F#sV#mP5AUf*?vTk*|FPM0OZq0BlN?2vQvT#9epBB5 zZl0YS#b3pW^^&7{$x*#8>Ams?pZLOizXz|his4*9a?~AtLbu-y>;9ph&HYFZzvq?J z|Ls2h1-g6gGZ1Dl3L>lH7NH4z6^on(2vq#1Z1Fv}1#V^V!ug2+_gCk(>!*vH&tUj7TPa>IA8uZ?qC>-#8aGyUJv0cCQ5acFdIYGUQXos6@ z*ER%04`JJ|Z9lnT`xd&sTXjGG4ErRe>JXYVIBa@rPuN!&?p?EU11;~Y>wuVFg} z*7)se`}hZc!}GO~wSKF9b54OHZ2jX$GvF3T-a+Yy@?$ah>I=Q@hzhd(ae-4n)k`-4 z|I8*#XIr=FIRtFefSeHiUy1hpB(o#Tx7(C{@Mp&VAfRscGUI!h@x9FW`}Y^y9^hd0 z>%%-$z3i1<_KGb{H^|C!G|F`xZM_%jw(>&V1I!S9y~hxG*((>2rrTo(=lg5Vwu$+W zNQ3{4FgCGXWUKcpZa01C?XP_w@=_=nvX=%gR4BNoH+Vcz{8eV#6UCXc-6@o7rNKYA zBU`TsyoBHTBAAnwWM{|leyf;9FlBbTLMo0kY@KYeUS#VhG_v&|`qc4nhN&BNvW)ZA zT~xkxXW$Kf+l7G)J9PCY(%{!8dp{bxN&7y?Ai`ev>Lt=R4B{x>ce&?N@4NkCzx)^s zeTQws_Pk`X)~AjiU^m4Z!We>m=pBa8`_b5oPKiB+(0j>-!_+}H#e+y&cw-oI=xGbR zpvESZo}Lf$e0`W}X$ub`=I~oR=FkgpTz~_%#~dy;bNGVpd$~@t-_}XKot%d9_nM95 zJNN-F#1`n?m`L(XbdJaH>jSa|PIWDZZ~TeE_doH*7wVKeCklh}R7iKK@W)7|6ZB3k zWz}(yZ*mFR-#Th)PBxt>b|mn~@1<$BMR&QWdB3uK2#+2={sBCG#q);BZmRZV)6jT{ z`NE~ick@REKQo{l;F|pL8dYt-UZ)iX=o^b#;PpBH@Qu4~f9p&B;oaZHdyT4(?Lg!{ zs?2pDrtqT)rbubA6WSo%9T8Y8eprFZfCyD93OlaFClV7SA?z2Yk_+Q8xfRs7aG$sq zpHejNW|DRlzMB{=AVu+j87mCWC*LAjs(#XprL^H8tuQL=y@=rKM-FH8cCAyLrBqJv1qM!^Y9D;Q%& zVQ@@P3EGVULVOPrze#ZrKfs5}$e5r@aty!gji0H=>Wm19g&2Pr(!^_+S|K?Gpt06@ znOqqumXfL zWFVP9j?zVej9gFx8IaS)DUk6*Ama@e)XbYq@`elYO~SX8Q7_1}f63b!=4eWbNHm+c z)c`L>!*3RjD=NV~erla8Yc#*%I=KM{#thnD#(qS89diu!0G^4UqRw_F-s=GZc(dtA zZMGQ&X#jh_$B=+pfDi|JcqHsa9qj26dr^tKD6nTx3EDCCboz?tL7)v6`fYh!)P7L-4hoB~KJ&3zZkAMjk;D03i0HHVu zc9Y1Ec544vj4P%^%#2Qofm7!5ZZ@MCZSYk3c{fY)jGWN$v-4&wC-j1#(}SaMh45kZ zcA@J&;B;`M;YnBb{#E4Nj@OpoXEIG{5yD4%GY6rh7ZLaSj#2VBFz${GSz zw}I7dU>P!;J_0r(ftAQ4V0D)SZV6)23+x{`IP^lPa3~zOoP|gnGV$r*U@;6rf@Mgs z299GHLy zbIi(g0?}|W_cWJLf&hiR$HZ~}ls6e1dVRPJVYV!uMatsV9~_1z+RbHG{Of>hw{PX4YuFJ zWss(kc!}^%D$gvP+pzyZg@E`Q*M^6+A)yZKad#YZ>fF_dp3wo9-JS;USs|p ziGq%Q2L46xFN%LL{OiNNIR5qH-vIt4@Q>OK(e7x8ANrWfCqW!G>yr{`9OCCFIJZ0w zM~H_L32*@XjeEZqm(_ zB||TK>mldc@v8z8H>+2N0YxwN+My7Fn+bY6Hg>w@9xCoy*u$q zXZwxw-Tn_>$oBuB3*ELx1epsFHV(;j6a7ukB^X5A~qhzYrL#tR485V6RG6TK+HLje{}geMmsmyMXth(b`0 zg%gn|!jlUJxljizJV{rgGqjX+h8te^6J@8Xsi~wo(3uH3`?RDpNo928U+AnIgmw+D z9Y_ADq(uOCNAzb%l4C5O#Zwf?0Si$N$I%nt=-Pf;4k4L}wjZkB6cCGhY=4?iO!UA7 zj0N2B_T%tSqodT}q`YS=aECk<@b-_&I~NO#uRP+V++AUTJ6%mhgavRpU;zh+L=8?G zW5FOSyoj&>CJ&}m7$!b0+;tkh-F}jn$&HJ{I5IboswqgCeZ)CHB*AQW zWDaMGFL0@K$NT8k$9tv^K3hd0MF#MFjDgij+DhhvTYzZ_@MAKY9Dp?!T2})Cw9D@3 z(1P6q4)GcRy|5X^XDyB=aJWHSB4FfM{UF_CbsRnv#8EK9cM;qrQIecbe*H#ZatsEJ zqr93Tu?p=~zy1*5z5!aUJ0*yKeukV#1_wXlNVGy9K7tRbr#X@zchF}WU1U9h{Aiu_ zxwgd_pKE7h^u+&mmx_5kh)mw?3=8yWj6MPfg2LW!l*iY=;M&7}Vta=D>+Ko#liM@w z-(1MB!+a1;J^6A{P!eU|4m#|}p%v(pAd=5GGeYMjorld5C< zVH1q_FfRQB;IKJBU=5oIV%NhaFfxV95DDdB!r@8cdggH9E^R*fDF=zeDeWYXIBX6d z(H~3F3wON2C-U8uSl88jM=N|R-w}H_sRsHiE%6dAHc=exbkONE>gn`vZl|LmosKx2 z4tqL%#_g03=``hZN_#rp%bf^XR_F@nNLCohzoq3TaQazC1_~alPCu-GVUPGQHc6A8 zcF=hqG+A}&z{4>co0w4~+HMt{LO_*QM%qHh{UbYNU!ddU})x=22fere{? ztrQ&s*0j_wSajHGWKeEABXoQsB!fuk9|(3DbcZ`W>1n!j?`rT6lUAviOmOfGfPcw_ z0>9(}4<*pq1Clc^8A#4(xZvoMK?5mA1`9O95nO0zNaE0)RQ+&AatU70`Q+aacO?gf zs{tB`ZJX^AVY406z}#tzU#2VqIO!Shh2$A{wfN%%NNztPQNKxc{zZor9XgRt2T~X} zQZY8C#r)&;Pw}M-nPLN)eEEga9=r*1$~WYg(2fN z6+y6wH%j*%``k6+M7FsIb;n~a8i}X4Yx9|ltR%3VNM<3k2sR$2O-TBnvI)Pa&Zx&G zG!kzS?yyk0m1zcikS(Tf=QlY2PIJEdh^VIeD5x8u#N=t)2P+b9T^~%hB_g!j3iTJ) zhx+&ZaEim&dGbA)+HpY`#V^;>ug?j0Z5kjJ63}`{nOkC|-m#~jMhNF{@BY#5Mfy)l(^hs|2UiPn`!h!Y4K3U=nWNNd-K?(znaXrbs zOaUW2iuWQ9w_m zg6(I`J9)3OJe*sfO+@>OGJ9#rFYy?tvGu*w-*!d?a^z0fsIqq=h^kGQhyUh`3&=KS zNWa3vFrXSZmNXGQff>;YNFT{e$ScA`=6fan9WUK)}w5~Y$ z1`px_vo4Jb5P1}rIQ)_1-J?5l%H{cv@HNGEgq|aON7y{fccj;uXm8FZKdqpQ7vMcq zz!>Ye-2dGn1src?P2~FDq|5y}-?6uic{&>gvC!|(*lfYO#&(3*mSbPzaDXGKY!Yyv z>!r3W*^P=Hvndzw4lnp7wi&oQl&so+m!~EU?NRM%YDZ_J}(CQ6CZQt`6SrtMtKnH zM)dyE8F|yZab(EWFoPzxQRjWH zM})y>wnZ2Va?*Dpap2wjABKcHy$_m9L%%L*GY7y;$Mq&+_%b(7n0&KC=5z^_)8#5^ ziKicp!#-TnrbI9Vg~KT!`bLASj8!f@*V5{HA6SEUa zUCrB#9b$gN&cg+ZbLLmdJ@M0*I?E$Sh*M+2F$gV&L1urUMk@Cb2FiXwtxBbpV6x_e zgGOFi&-_mWD+lIa+yg;Zu{((j{aIS3&s=#qfdShAjT>!t}hZ- zVqg$Yj24SMaz=xX`T(rQ?-4NLoSZ8&KkPSupaRDdKj)(oMbj9~;IW`qGjacwgo??V zaO5#Tf*!H0Vd}5LFtxQIJ{C4a*+n*3j7@xS?9u7M+K6&EXC{dY5e=z;bVn*2W!_Na zoRmZZq21)C)D(v6wijOG^&(91?d_l9%iBN2zij^$U)cUBz7jUY)`;?-)DS)naB+$9 zAH$%$xKsS|_D}J*VN*QS$CQ+O1Zx9-d@FGx5{H~l4VEVrHgGG7%8?8ugTk>cd}2}G zTzLB(4h!D2RlX@}|Lt2J_h#YpFxhIJ>oJ0>9CN51-p&pu6y6+0;%;K#^7N2ePCF`R%o}A zKP23;mfUw-kYUtEZ<6VB!JA|>4ce#X3)&SL_0+Q3Ww^j%IfBdRJpLS+PkxsMfhrBH zoeFWiOy_q{BWB-a`Y64P%8Zs?iXq`bf~7B|?N#qy^<5^0$K$8&6E~gO|6~FoFb(ET zaTgO_yd&ruoYS|-_xHTl*&!TxpI_YG6O`Kw8hhgL zjc;X|KfI7>HVB8@<%|vM!($nFY!QNZ#wH@Tw=O=o1 zZI}W8F-icYWMw>2WCThKg+-7fOnJ+AqNL>k4v0`1G37wxiE^MJx#{2I51i1RiN`)X z;vYRe1BWcCvw5y8{rf1kOOci2{}2}%?{iy@|djK1AXP>cnlZyS=)om(fP zr}?C~?L&k1PRjkeXgqc|7Bo6SZ^n^C z;=?gGM(^{Y!Vye(F`ooeR0<~2(sAzyCi=oGp1p!e&=Fw4q?2|C!2k-o-i%X_frPDn zPhtoBe3!yv^fECp-tS7P;Jmh^ikkf_sk$I>rVW8fCyymL-dIvaOBC>hldOnDIA(~C}vD!ek`&wMYlVIyreMqXzVkR*TXY-LPtC6t&E0Duc^CBLsuX$^owIB?L+*Z^DS zh{>&u$*rY#8&K(NEqe;7(A7EcPQ2P$a1B~llxd!J657%(6}XGhPI`2X;57D5f*J8n zc1`Te5(0wSJEeL!u|ZPM(sqnVB;KOyhVLNQo=kcR3J2jw4)!8aHA9*PeIfxDd?Ep8 zB9+TV6W+h46A+PTiV&J2E}EFysL(|dIFxe&5WS@nfLJcG1$apJDuR!YYS!lxd~g~Y zO+YoHscT|qmJkqRbkrtz52Z(3G--*a`v_eqWVgnh5n7#&xzf@4ySH%5YsrW z-M#M$@zH%Ka!2CiCFmqZ;e)b!KPm8P0I2lc1ANdo07AHEJc8GcU>6L6IYjQjXCO|A zo9Xj48iAmpP|mIGLCHRt5^4fepU{q?I_mT}EC)I$yW%4@=n9ZwC!pjwIE2#`pB#8B zs^d=AD`nRy*)@&Jhwz?jzukcb`XC0HMFqWRO-LyG;lAXZly!Uw3W4OC>8m-$72*cC zw+o-#r%rU@(-H4^CSQm0mXk1(|07?{&7)w*2tVC0I*xFVc>IvSXz4424qw}+Xx|C~ ztk+1e2t@lAjE=*2qV|tRM|jh(K;#M^$smSC`L&QSi*QOM!4U8Y2TVmt4A{GO&}ide z7upbt@c{#R{ZY;V=8&WiOVY8_h-VWkb1XM}IfHFD;GMKU@Z=m|r&&q4!JGigZbNdx zI~t2}&uG|hIR^OCJRy!=fWk;Xg#7-4liGoFQG?P&)3~UQ%HqvmZ@TDVJbFGsgkXa& zT@--^U%DtA3~ZtO38L5A-{3doCx}$F{Rtw2K0)-;=<@Bai7T`#G1k{UdiQN_ef#VF z@O$|75MD|?^@b0=^GEM_`)lvSj~WnQ_kQ#4oBrb7+g|o%R1;+PeCqBuJ^$t}y!Tsp zCeZHr=(leC%E!NO=hyK}pndD}@Bh(z?)}2Mzk+8q_oVzf5+=pms?1Gb{glw7u9R@& zBSZoPyw|Fql*D1`KSOkdU7)RjgoP?h0VYV^2!saYT@2?JuM%E{T~yy|fjp9uANvli z5s$<3mkBK=n9k-4d#PyeJaT(!m%zlB_Ay5mR0&dpc%|S^JSXtxpc$2f2foZ?OuLIB z9T)zV3%1ohDc?5qKHr)mlB;7)pd60B$peDd)v+o*2~ZbE@8ko4h44Xwb<{UDV4Q@; z1|<%8;ABS(f;zFPxH(X`4w`dYXa^G0$Mm*5vQ#fm_TMzpdJP@G0!qJzYw8qCHST!h+o#)If z@c_>ZQV9r9d~pcRK!OA%@f&^!6+zl>ivvO?CjQC?#m!CnbA1(SSi%ZEl-uypC%(|lf$ufp@Lf1yFq~iI1uS9%#)rfO zEUpk21T2tuZ3l=!aS~(n+CAlQne^uf{W;3{c=QJ!r67#qPo5s}!@h(u{CO;>6;|vm z4q(c;f=>>y*yEs8zys}f-r8_T+QL|`rm^H}Jvwa*RJw5i65&KSB$oKT9wbzziRC-;mqdEolREZFX4B{@n?|NN0=1M)1Lj1Z zHuCxrO{29DB42lbhkJ7L><3Ra zncov;3m2wVIy_+WIR7^i+)T5;A{6Myv9pT#8wzZLF8H?friQ-ZyoP>&gSDS0pvTD5 z;&DlMxscbPW2e8fUgdnMf^)w)qC@Uo@E~&UZs8jhf6pKUf|K4i_WbeU2Ppsi?-Z2Y z@UW5C6k*&)_V<=)&i~mLThb_UCQ^QmLvJL-Y*Kb4qw;zOBeZ_`C~QB9Qo%@p`jG=J z93ETEJ-EgvJrvXh@=r*yfdn{~>CctGvjFC7LYB!2W-?$MoyUwMjskn|g#9%evM`M4 z1#1Cw*?qxUb6>FjMW2qTFgV06$SRb1>JF; zfIC!ysBLu+H>2!mR)Egy>jPgRaDcz4W8#?G`1OH%!e%S`sNLoghCI$hef-6u7dm(Y zt01aG%-=~TVzNiM9jyk5dQP86T{)}&}``Ya3#hZoW4{MMlIs*{2`OSEtEAdo%1 zYffhnpG%|UVUoxW1Z-8>HqJI@k$jS|7zoU54vR0j5G+2yhed~Z$OKwlCY~`XVb%I; z63XO16EF60Asp01Tswq%-j>2cmr&yM7#= zic{tb#^DXR9v)$;!&CqD&mND$C;RO20s4$3rDXMAkIL9J!1B>81RYr>mrT}aUHy`kbDyM7-xeSUIDiY1vp_M^X#dTIpa7uVk`!? z4McxRI*}3HnTL1MATJA!6+E4Aq~L^_5#+5Do((U~y#1kGGXftD#w0q67sS=iS%K&9}DQ23jsq)+_l2se>a28v9lMMe-}nrM#GjG^Hz$L#QT zz(Yf$!%Z|MxFhumZ%s}gKt!{f0F++=(+NJH2n$DVtXQoSRuRqRU#XUGzDM>>ax^Ej zcRn*$X)Y}{8&;!}>Z~qXC(hP8sdBwhJ#95kq>oKZA4{L07Aqa=gw?1tt5(%K+iWdX zwNiU&NE4d2e=l`$N8wiW#_5RG_oJudYvQ7CwKx?OExoU8Rho@z`)s07YBbfuYD9}y zOC76IU$P8M6DRR6q8W|mS)oU?ftJ;7F0ORy%?7^7Dh{44wd*G;i_OYH`}o8K`74Pdn8=Hg^DYXWC7UwX9NgRp?GD zp@p?7(}cnhi03b_qOD$UM6_r`i_kbCW{6!3SG{?h3KTSeR}&5};?xe^&}m6d5v}$t zkW_E9JEca)&~$o?S65mkTCbr+2pNV#MvNXXrxzoAqAm#ZvvU@j$0OR#_DZ>3Y1PYC zN{uS|?FLb5u*+>=&m#s5G3LeVQnS(QG#mAb54nao6~UsdR;$?xV74969%f^f>yLGs zsj5|8Ih|^ETA&2ly4^O4xP`(IA{m+?#}ucI5Q4BmHHW5kBTT0SU#ss~EVVnS=CUO* zm8zBMi&hn669YN4t*VAy9K_UCX(=TECd>`i8=ztW3FQV_4>eah-V!m_CzgLj%!>g5 zZ0k%n{l=ozIIATh+J1Y9-eH)4#9%}l53Oaw0RmD)XApz);%wK@Lu(D)UZ^jN;SGnD z%|zXxjV4m>*LFm-hlj%B?7m2fxwze7rv@0t><&Jp9IMP(m4(#VR=s11U1DBy!QUCj zVeWu!v+q8$sbcQC{aQ>8{VRa;zX&xsKiy0u4_P4`5!!yAmQ8oFZw zMUM#YlI6kMzl-A8#wdXEe)~9K8ErbwADe35r+otpA>SbI6%qMRwvcMhBTK_ z<<$-r2416SU{S-Knw9Jno$gT{R<=GaFKXmI9Px<%q+u>P+F(cia$<=V+34 zrwT+1!-l)ESU)by)addPFf&2D9E! zBM*XiilyP9mBs?(T0`=rAqZHj1un}RDp_xwDJ|BksZ#6o$`Vf1K)F;+%>n(3)fmmuG*>nfH7;qT$k0H1tdGC;Zk z=peP!Ue#h~3@&F?I-(D)&QUAjbO%kv>uqMYtD+xL#{)~Lz1pax>dh!5vDWVgT%39s zftBoDkVfDo%FXJkyGAM+Zduo^SP(-t}yKFV8lHvPnV^q5X+6+{!w&iq+i>4lh zi0@OQ8QK6!`cV?M+ZwPwm!dJ+t@I3EGHH3q=yMbL+$5-zxAKhDYO~%jEE*zm`j_g< zPzV%{PT5LX+t89?N$)SMbec=hGb($QN{tn0Qmd(23n!iE0pmopky@*?L@QAxsU@g> zhPF#AMIxQWb|y-H16`-u?ZqAVmjZb9Z1TBuo&7viv0B&~%-JX{S$&mK ziVFHF_2oINZm`{`JC*%ah_Tdiv$>c6dm%NLnnq5ymMhrLa;dU_Eroj9sMSwH=b#e= zB<>Xpdqiu{zpQp}U8^ArTMb{?YH&%T!8MKg7uB{`g&Vmn|GL`V<+KOX+7_%;Xg+(Z ztH29*kAHcFwqN`*wvow6o_<3+0OspCMO-J3^-i3lgm=%ysj=YUrsrKj%FxA<(WjVU zALwhbGq>8m(olCeTQ*p-gfwCMr1z<+HZub2x_Z0QW=6p5Lb%o71Owm%YE02DAJw|K z2MaXa>Cv|Y@?m9m|Kn4yCuN?ZjZ(=AmyQzQR zPzk7QZn=B7#0V@N4@sykiSA~4@UTjH4)95F>gq5O!At=jORb>T1-cU{fRQ@<_SKnxVOy> zcP}=Z3oFa^`A;pC+6%-kGFYc z0mS_{fs0n^>>P|6NHiAiFqBa<++KkhcA6e=p4&0G${~{ZDOk8A*wdxqw$*53xHDFY z9%1@PB~@`SwSo~+`daY;qJ25*P!PVT3t&3{;n3P34h3MeflT5R9!DVRsM8j~9 z6(OOdcbXAx4-+_vVgCyD!?D1v&Y~H1l+{+IL5Ei#5RFK*)?8^+V=Ha)3`7~TdzMxp zb6_WvsA#rQE6e1EfHYOxM+^`W?_oHpeRN<9gODAet>4+F2H&kc0@Nrw2>iGioCHqG zK`)?t>>i^CRcJ9es~E?&bf7M`OBIqUDbGOt?m)pQ4@I=m?p`U@Z^U6&9qK3G_DTx( z9Ff%7QoSQC#e&o^>f;Ig5cCu(b=&k)8%RI+1yY0rk%H>Uk7zY79!?)4taIm`QK5;76H8=ZRq(n}Xs?}DiS%FWm zt-Aw5C$H1L|7r8=YLrcJAXpuN?jZ=@d#F(be5(V#UT?t8BEt|HaMJK}uqUIz_j!9LHpVl-pXKf2I2jqF z(U4v|E%+X~0eZDOxd=cu3_^g`wpwmNY*ecPt|Z;ljzIA?nJcx)81wF#)mn^RB2GOr zh*f$X=~4z@u|$YzL?I3u9h^}qGoYBWZpeH@k5plf8RR)JSUD^$lQHHhJQ_JbjfQm= zK2hc7!`atC!UL5#Xrrli{gmdEQZVl#6S%1l>=p|n7;%q%)*RxE zRaRQk@G?;y~v#_D)4of#Kv% zZSDp8V8nBF*gha$7d=4gpU*U<0>`eUhc(P)s_M2}sWj9o<&l&U8ZgwjD%{u+r(P~L zM}5>PR28L)gI(q~Gq;C<1-lk}oLW8{!gwwB0DMonfVA#jH565MOIZTdudP>VWMPtCW1zOZ9de{s&)~Y6@e>W2%$QU#~%hw%RL;(BSuq zrTuWVDP>?_g|_jyZK8l^L!c~Tqqd&}#~ zsg*_xUMZ^CrA#)`;aXM=;-1qS_fsm-~A#aKoi45{S zE@f|o3KYqujPk*Qlu*t-LI?Yr9?2E*uT+o+x{$uS z(uSa7OGUaDSC&t=;80Q#8dTn#M^E=ga|v+ZHzvTS>7rB$~pV2S{( z>{+lT!Jd!vMLtLo+=mlCxc9dSyYu>NWNodQ%uC8I0KH=vBM#dREr{>yWQt{ zqt*nEsak{XgmpMY_jZpfZNy7ND{5eh{j6jk2oC+rVPpTYZ}49MTj(0y&<-l)0W6y* z*u&C!jpMtjf%^ncZyS>05v0wXjBaWSy}e-4IC}zI;LHTbsUyauR2?UwqiHZw_Cp6k zY?H!m2qm&>4X1{*J8{@w%RWNrICd@Msw01BB(r;@xd}K!8mFz+G9rv*&e*ED+wqZz zoOE}zX{lZnj|v^RIp-f4=8R_)K7Qn055Z&L@>;(BY{q9GDUG2v43|rXc6Tu1$)y&wFnnW7f zSZLGz%~t(180ezSyltgIGve5TRJa!Z(FGha?>F-tg^ai;382aN;{lktyN7zgWL5m z5Fnbx30d!;Qo}(QkeLsxv0WkSW3s4CBiPjh*OdrI5ThkUQZ+U3%8vPXT$zBmC zz)|g|p|LB!KX-eKfV6X4rH2FEMcHtjpzlZM5iw9G?x)1U!=MEJCA52L z`~l>0Kza7J{wuUbS_xSCnF;YYv`g4~>>Kn$Xg0vjq?6=oXjAT^a!Y`lh^xgyKSuco zTNGGYLi;GXCOv8!A_d6#c!F$1FmbDoqt)(UD}w1~R0CR;Zy0vM{H&^_W%#;NEp>mA zSWW;3s|3{F*g${d`yS($Mkl4@5b_y;u{4^Tw>2644KLu?$e5Priq4}i}Paee- ze8nup#V;^`QQALc;XGAtZNC#x8luXB6iFa@7cq*v!)U(K)(g&B zqCqt4E zH8$5YMh1Mb#lc1s>wqAjo;)>v4D&dv0I&J z!{KKR4~CzlrqD8RspIqtXJH;&P)IDS&Qq^kp+ zg9F{>KR31cx8nFPDeV%)@eeXRD!%~@Zr3Z0@A#!iX+Ut9 z5-w4W_DgjQtneI7vxAwWs~w0jV3zqdPXwcKKxzFn&mlQaOcwRi0CqLaLY`^zilA8W z68NCW{ldpALFH>7vqyTG4pGy6d`$cl#Ru|HA^r&BM$tziJ&>r^dsH^`c@9FhEH|NH zvRsQ|`K)aa*v3-(G>0LTH>l6=;aY5-jy`Jh9xiWGi(_04*H(T!JI{baUQVTQuG^Jy zT98u-ZzNopPO`y(SfB-PC{xH|LC!4c)*TiLa7T(AbZm44NSZAx$^j4ZV%dJuooK;Q z-`@h!NjqpFZL4`m$7*4M}Si(1$YQ=*c^z-tCGf*xFl}E%W zW}QCbYv=>*W(~;)D-at<0YL*ms3*O<8E2!3mH<* zsy9uCo8Y5Er$&MYgr2d1L%}p3qBEpeVrdR}QIK9n;T3Jcu_^=V$>) zlcMabz3IsBp`0SmGLT@|zr0u?lmEJo7(z=Kn}~8^TAQTD0H?3WM714;*ynLcD%j;l zJX)M$kC}HP(A$j=VB|T9e#l@1r{j4bi9m0#lkMC-$7;x?SX`y-)zOgJrP6$}wPt0# zv3BKhsZ*H)R_g>2hg^gQ)V9_KUbU`urRC`5;uNy)!hM$-RfIU&=aGYV zQa4~EiwOCE3<^X<**?btd|`ibQ-J`YNBza50wP%)B4$Wved-A*bJ|t?P`#=vFlXCh z5VsZRiB=W)J^GL@fxHjd-0A$WOwyByj`(=H6O-xa+PCkO+)F!nfYR-X?M7+2J=g3M zp@yBQTW2%JP`o5wa<6!oq0JSa_OlZc;qCI2=MGudo}&6J=aRQGX-X_F@&TUFbuziRN_A>xrh>e@i5wE+O0`+BwB?zmomA@$r^{PbE;BP(voe*b3IhDI z`m?q?oo8W_$U_V)D4(rlrgM|onekRC;Y@ z;z^ui{`2ewn{jfBgSelua^>tqcA`8}nJK5sv$2&;&t{Wt5ciq9Gh5M4dh0iR->b{k}DtPKa|?A(gJw7Diu@@tELX)s!eCAxrv!fDLYds zy68J?7+-xLLc z1LK!-RykLz=E1x&>FP{vc6d{qIBZclxYdflVPJ-ADQ#6t*;2MTRjFjHiP;?&2W`?l zyaw8GnaSKl&B|3WxtY>Tx;mTO>audmHIpZ*pxnv8vT~(-rZk6w{K8QVI$yRt<9CZW>JJO!j*pgfjo1QeS=(yvw~GPzuCYATPV zTIJI0NcDmy0nL(H8(7Lzxio3jrm&Z}%=AoUYIe`HTb#w&$BD@RE}<53Y%nk-n0I9+ zn+A#IbJG*G{OsOMN#n3Hm&pgFoG(oUW=mV85_FWAT7GhJaw0uByYFdRT;L>9lCsj) z4UC<&GF4E24T@F_bTL ztiY1XIU@baWDdeUQ>{(SUa~2j+%xSo2OKjwVk~5I740O~z)TbQY;__(oiAl4^Ht0? zdtiBs8|PA{m&4F0N4Ttra9Lo+scJ1hlP%R|tV%gsDVJvto;mLsy@bh8d=9rk_))km zFmHaQI#H_3l*%OH-+3pe0tZ6AO;A+P%_7Cx0<&Y<$$UPSOQ)g0Sret% zhi`R9Gg+BqS9Egh!jH>zB{P$+<)+Vlv+KBRJ;r}wp<5|!{ly4 z^_ZJZ=PEO~Y<6lUowL%psoBF9!0DN76zyy!sEt9DWNNCG&DAE-xqPhx3Yea~w7Cx0 zjxOwBO%8IP)=<`tJKLFwkO{&5CexFp>{PyrC6-FFk9-c9BO5v>!e*RG2M?~RgcM~? z6c9@}Q2~-DrKc*Bm_1*@y35(p?4zD>zA*3}`W+)yHj_`ML3C5Ks+EQ!d1U^4N6%zs z;G20+JA~}WS20mZmnQR>OlB%SJ$u;&Xv%i(Kyiafr%RJFrOAm>shTUzKuw>0^aT)2 za8gcK2<#WkfSIYv1SXrT<|j(!*~>2+qa6AQ!RSP$R-K-zVFRZt6}Xyak8a9152H_< zX3p>^$~1_O$*R+7Yi6c4F_F#8WUABU+0;dwtoy7(*UV1Ws!&^}CaU@A>DgoJ8QANd z^~|JNnG7QYwhM+EEW~^^m#fsEkkn?6U-(2a4j7oIG*ipXOjahRCnhtwiE?f>ec=Bo(L+vZPkd&rWRB0(F=%8`j-3l(S{< z-kEYPou8SWp0H*oH|63CCGL!hriPJqEd%EO)Ro#qCOb1}%}!l}X}VVf6AwZ%2Wxg_ zVk$S2ot=K>`7+umit`3GeX3lAp_i{^YL)!7RkCKY=f52^eDTagV1|5U8jk+SQaYQ5 z?3~HZ<}QG&cnS{G1!kEnO=8!}>2%F1rDvwnv-!sPqG=-S&Qf$D<$?KXVAWY`3V{J= zb(#Fk?98SD1EEAVqcV5M16E{@Ul$T9nThFA1`ISkZPnmBo;`7qrlW9X_nyHru36=3 zB{!4L=F^$l>=hTFpLtg&6PN$ufd$#W7fhm-8aL>%Z z!a;muX7;Bp0GrZ{SwuorvEC*r*ku7^C#Evfm0WqEHkHf5lA8VLt@g;1Kz_OtfFW75#iWljYoM0ZJ=2~DF=^7Lk@bigEEjN4e0tCCt z6)QR!VSz8Ui3S+?>Mx!J-6xHasYup-v#Oo+fDQqTg^mNMmh2_#Y~<>6BS ze>`>F`SO)Xgc)V#TgbHCL^@Hckkwe7$|Dq*$<=26zYB14cxR^kxX`s`^YG!rx00TQ z|Q29p83fM3!WVK!}B2C+1B|Ycw$QB?Ti!f%+g0npfLt^&v7h@{9Uob zoG9n3lasY9-u#;V*{$lhimaw+3cT!G#Ot}m24!D(vn=4LuFa5VCO4DLThrOuCv7T1 z-9^0<_FgPnMgE`yq!$ZhY)<#A*;=JMH90XejW>zX`KjsIpSuwJg)S_UD@|A7MNgM7 zO%B1Z|8)T*fX`P15to}ms3e<)aac{yAc!&hSt(aD#4V6pk)6F}t2;S~ zJkHF7odp_V?M=;qZP&^bxDm_XNYk@V*)pav#WcTB9KduL(HhL1BR6{q3h?az-l_?P z2(|O#{SDXVUyqJ0hW7tgbczA5djFlefy1 z^kgsyI=y zPGU#adKg@(N*P+I{BWF-lu89RgLkjB7jpNm6oo8CiV^u+nm1r=C{FdX<%E#Rixsie$=Fq#EU%&k3qz^@#Twu)r}k* zBaS0isG6j^R8wD3WW>L0=kFN>2(^GuvhyJ1%PoSuMA) z0=E?ZrW^URHY-=6l3c5>JhRPuUQWg-Ns^dSYa06^(f!UEvv##CudXZsXOJ}uDPTA8 zB=ewjyohXjLct?UeUWZ~HT;cXYozO}*dcufQiaM;CNkX#F;YpWrR}?d!IXw;WlQ3suLY&*N`rlHEO_#ZniN|jj(#iBa&+>p_2C3M#p*2t;dRv&YZsQk zCWfd;QZI~fk1ac=5MYbvKBOEn&)%Rz_Dmq#N&)=od9oXZnw}SijsuYB)kQ$?Qunj= z>v%CGI(%{QucS(*#Mk<(QVBAU5s^Yr`#~I~i9?75f-z1Dam+oH9~o}2+Z__PM%ykf z_R+R_cdr=NLhpYwg7zOVr4_`NiP=ZfQ7+eLB|5(oo2C;uR8yL4N$}-YUg`ZjA zGTc}kzYn)nG3y3z%O?7Yydw5(H)ExTE%{-nokMkTXnD}qqf=PGzbx|`}0$9K&F zZf-89#4Tg|ZQ>Hw1u0{@;>4{mHMsIgP!$kbA_vkb4tI6ZR+t(DmRN8#(lGE9l)S)m zJ=xHmEqVhu2a@50u0ydP3(4#jR%we@H#X|s5iT>p zi4NvBu|_3!7BO@g>y$+r-WR7Cx>@+1F-w*z)`nnq6e?T?tbgcOj%$I;IejbaOb=d~ z6I4cd>IJzAj5HBv7`lsYcRz!r9p7@Cf*OAgalxUgD9$u=E18{z4ObNlj~aDKwFJ&icR{AhE&dq|9nJ*8EAmMcv=NTKS;;W>+%OVowuvSWO z$(Gz_r3OA>3*2&2CSe&_)FwT=^h*uhvTBo>4ywH+CJew}^vJhE)upcL0*}7T*r(z} zjSQKv5*CG(m9FD62_KI}cn^jB{Fv5@mdb8%w8h@>U0B1%B27EV-%tBlS0{R&JGHi;o6 zGdNK4Eh#h>CFyTW1|gZttGC45tPl#qamvI^MGNmGE^Kkl#973E;5v@6< zKsYxUhbXd9#e|m&q~sjkZ2aY%uEft++p+uUd#IWE4MWM|F_ck3dL!d`M1i=azHXHV zZ?1c*u|HyP%A3B;c#Qf}>Wix%ZZ{61x7jhAtNwk5@tEr0vbreWw>ymk%ADRGyM48| z%Q#T}E&ED&{cdA_{-y5q9^)BvdR{#MT(fQv8Nf13BVJr+NWRNM4#d6c>T-t=xjWW< z#z9=Y%;k8=e%#LnTJ68(fR-!Cw$%p^{2u!ib4-GuF0i(OJny%%5L)m&`8Aqda>~+PLmB@qI%6^S=*5odtR!3UoDDb zUZ$?8+>n?e>9BQwxSdxRzdin&?4#<;!^Q#X$jbJe$Go!sTf!w?WgMh_mqz8W zR~rY`|CV#ZYtXTgHdc?{ArHQmgPkhF>$nUeH%qJ|dtYxnTK&zJw_3cxIB-?!J1-r- zy;#ZLXgs=BP@scv;*fkVk}NBB?|rjzfch7bh{wLA{#yowZ#525zppN@u|M8%JKttJ zZQU2aqad}+3t$y|)Merap%wZi0npoZ8kYzYkfU;K3eF&2#5*(^Q`+zezX-yaHJ#8B z@6>4QSn!{~ND@IgS?ERLT{?}!h|-6h(;6^&5{P%}G>%g8vt^h8RlrxX#d|awgOKwQ z64tQAWZj|=@6~CHT0sF~6L=9IG@_CB=`_xY*dq;Qfq$U-=!^GjG$th-@tVZ&drLnG zZSet(#vrYd6kQq5_8>q3O8B5gW0)Xdjj1aGcnbX@6d%%QTzI~XgBF8NE5TQa4{J0= z0SEAl3n3T&QJQArBRY*y9LXb!g77{v>0f_Tr*WQ3xe_AJ+z(4P7a!AT98nj~Q#W^G zz~BIC#K*N72L*ZxQNbz?UceHc&}p0kVSrjp&5uiDk@%!WtTq_9_r&*(HRT|g+n-C{fR7&7r$jmBWp2!K++ zGZgwV=z5>iYAiXfc_2|YU}8)0d7Z|AmHP-Vls1lJK>qFvI*m&(O&+D`AR&PTs_u&# zjU{uCS0o4Jnw0?D#g}v%dn6naxO5Sa^3=}6mvtJae9o8!_UR|3pNOyMG|v43aCw<2 zMJ8z4U)5-w+vrUZhx&j8qO^#`*EAXv_%VOwHiTG~E3yJ#*JN#srZCW;85J8K_21NJ3`C0=ALFU*Bn3t6Z|OAlGn(7b@G}a@eki`J(>O*^jJXS3T6KTDbMLNs@^}sOrfN2E@x{fX@EVm& z-m}z}7ZQ!5tdzBI-ujApjRA$(ZhAdCH3{Jn~E~MBB(QuxMA8KcxTLX?iN%!wE4B@hr zWQbE-W26(HmHFaFTW~8~P9aK4(xD2FixbUy|AqRts?_>P<{#GMU)^j!`Ci@l$#c%Au5pU_ z)S1^9PT-$9=bBa8{j@n}RD8YD&3cF6`hHZ#>J0O@#=1hrqvFS%Y5u{~fia=4v&?!A z>iTlBrOefyZPr51z27Uo+VjnNN5|fgj%Id_`R7vxNj?p;!_FNrQjxmO8|aoKW1VmQ zPIGy+0Yp_mU62{23)(U0m^tdeg`Jgt8}9TX^WgewXeBMkea$X5YxV9W=u-=dq;*N# z95&HHpRaUjd$K&Dl4F;({qA)FOP9C5!xdTRNIh4Wht@rizxNp{Ef?oX^N(koQe*67 ztERw*#M|bKEvH zPE}Fm_Vy9>J4Uso&(64G_DLP7;?DLZS7~L)5DOI<;;#0?Xb&_bhP&G_RiuE00cyZK z=Gk(Vk1K$9HA`1ptt>7+XSdwXPEONDx9;sadeBGj0slm;{O@)i$6sAj=4sn!s#OP< z*3?nvY+R5`vaoWC+<|My;$HK7wdhzC&c5moNo*9i78j3H|F16k%qNQfr#^*X+`Kd; zd_mSX3o>!vBi|=f5xkrPO;-A-yk@bu|B>$#2xSY2ZR+f?j2CS2z$4!$3e_$GKBa{+ zpN}@Kc+fmb(^;P4>EQiYIo?v{xETPa1Q+}Trfvy*9yv!U^u-|KOVmVAo`EVQ9y0%8 zPPc;{Nw#O#s;{=`<13=7SD*o0P=Ig{lyJlg&CTInHVQ()q%3+#_7I@g+;al)B6D8l ztnRUG6{^omYhd5-coDlTJYf1+iVUrth!>ml0LMcY@w*GkeFRqIXpmPck^Y7ch7uy$ zsEaEr$L?4pQRp$p0QQ7>0Jx7@Z2XdTL22cw7nq`0>0fHr8}L@?1nCzokfV56JI!|u zWF70}?d073uDpU*3>;8?+rzE9*R^P>$W<3!*{%xHXS6ZytLB{4#;LDvU4-c?sgqR2 z^fl%a>IJVmtIx4`ZM)9wN}tP)Vm$}!b>{EYO~(#ZUdrp+MQGq)g+qUX`Da7zn`o@| zc4W}^#$ij9)BUDlON~b^tFdpMZ>c_2T6)Xy#rp91x6Z#<5BdH!bMv6f!4wKr@*;)7 zmwN^2GE(ku-wF%u8zVm>fK)al!6Kk$mXlh4$HunDm_kUfdhh0X1F4O~2@x=N5c;uU z1VyQMr>Uz7-C;QGF{m=BAFfStF~_k$_7rvScbU2(0=&v{oNcUVT>e}sM%1vuq4OPvAsaN z*L)s}j!LGwfJ}l&Zr%YPZ3%2A+j-2{Nu&peSX_Koi;j>Enw;j{45bAx2C{~#BCP&u zLXJ@BaFK}(Bomd)7MYdzjZai$lNRL6+e2$h1jYNu=RA4Vgib6Ngb%b4ftt+*PO8LY zKiEbD?Rm3wjXq>Pxj#Y5OLj2wMy|fzK;gr4&sD6=JNSbGxx*oXhxD@|K z&8Mv2gbqrcC?d%p+lYm6o%4^IkmzQ1N~(7cXj*@woe?2V9Y)QHi4vW>20o!M7E z|CITs>vX1;694q{t1;05r0G91fAHE}d4a`9nefk=la+=3HdOLm#pvHNpEx;C-9h{+TuQ_nwtatktfqwfelYzZ^~K;O^!9#e){OFQ zs+n^(xACLtFR$OAQ@8u^^eZ=fA{YJ1^qe~@UH#MP0j@WA`PuLhp5V{hG-dk)d&086 zn0=JCznory8ZVTG+vtVUKpH<~$P8c;gskR%_Covi)j+zAC;CXeaQ|^= zMmTxYsc7Sr(bRe|PkQpy(X;1-pW^^XyIb#96$>+5c4CK=PLTk%P7qnL#A&0y z+0^wa@-+}k30@-45CkC@Q4n^gkDjxs3)Tdta2Y0^6pIIxl!FGRK7PjN$W2`_Y7mps z(ebFPP_##T!Oms&;mpzB&bjDNsji{~1l2UoB?0H*Wd>g8A=~P*$ths>)%~I`d{z^PUab8 zj_#UKQ&{VIpuDRt61aBM6f%3Kv`pRWW*?%-e#fXuAC4Vqd7*czKaWX`)9t52iU|5RLHsda>xjlj{}ql09bP*NF+ zB~lajjMzN6_wRd0kC(@5LI*Khb?SYi$IH_uf4+aT{}SYvmA%{D2Sz^MrX5NFZbjfp z*S7Qf&Q*6uu_NHWZ1?ee)2?l?lOG$bA|_n3TX20Ww**z^RR anyhow::Result { Ok(module) } +const ADAPTER_BYTES: &[u8] = include_bytes!("../data/wasi_snapshot_preview1.wasm"); + /// Given bytes that represent a core wasm module, adapt it to a component using the viceroy /// adapter. pub fn adapt_bytes(bytes: &[u8]) -> anyhow::Result> { @@ -58,7 +60,7 @@ pub fn adapt_bytes(bytes: &[u8]) -> anyhow::Result> { let component = wit_component::ComponentEncoder::default() .module(module.as_slice())? - .adapter("wasi_snapshot_preview1", viceroy_artifacts::ADAPTER_BYTES)? + .adapter("wasi_snapshot_preview1", ADAPTER_BYTES)? .validate(true) .encode()?; diff --git a/test-fixtures/Cargo.lock b/test-fixtures/Cargo.lock index 183a04a7..27106833 100644 --- a/test-fixtures/Cargo.lock +++ b/test-fixtures/Cargo.lock @@ -197,9 +197,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "proc-macro2" -version = "1.0.83" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" +checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" dependencies = [ "unicode-ident", ] @@ -230,18 +230,18 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "serde" -version = "1.0.202" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.202" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", From 9f9ccb275d938f28e3de0470918d2500e9d62d0f Mon Sep 17 00:00:00 2001 From: Trevor Elliott Date: Thu, 30 May 2024 11:37:51 -0700 Subject: [PATCH 08/15] Add an `--adapt` flag to automatically adapt core wasm before running --- cli/src/main.rs | 1 + cli/src/opts.rs | 8 ++++++++ cli/tests/integration/common.rs | 2 ++ cli/tests/trap-test/src/main.rs | 2 ++ crates/adapter/src/lib.rs | 2 ++ lib/src/execute.rs | 21 ++++++++++++++++++++- lib/src/service.rs | 3 ++- 7 files changed, 37 insertions(+), 2 deletions(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index a1e87d32..12e4d2c9 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -271,6 +271,7 @@ async fn create_execution_context( args.wasi_modules(), guest_profile_path, args.unknown_import_behavior(), + args.adapt(), )? .with_log_stderr(args.log_stderr()) .with_log_stdout(args.log_stdout()); diff --git a/cli/src/opts.rs b/cli/src/opts.rs index e1d8828b..7bfab2c1 100644 --- a/cli/src/opts.rs +++ b/cli/src/opts.rs @@ -111,6 +111,10 @@ pub struct SharedArgs { /// effect if you set RUST_LOG to a value before starting Viceroy #[arg(short = 'v', action = clap::ArgAction::Count)] verbosity: u8, + /// Whether or not to automatically adapt core-wasm modules to + /// components before running them. + #[arg(long = "adapt")] + adapt: bool, } #[derive(Debug, Clone)] @@ -213,6 +217,10 @@ impl SharedArgs { pub fn verbosity(&self) -> u8 { self.verbosity } + + pub fn adapt(&self) -> bool { + self.adapt + } } #[derive(Args, Debug, Clone)] diff --git a/cli/tests/integration/common.rs b/cli/tests/integration/common.rs index b1a4b498..e4849316 100644 --- a/cli/tests/integration/common.rs +++ b/cli/tests/integration/common.rs @@ -276,12 +276,14 @@ impl Test { self.backends.start_servers().await; } + let adapt_core_wasm = false; let ctx = ExecuteCtx::new( &self.module_path, ProfilingStrategy::None, HashSet::new(), None, self.unknown_import_behavior, + adapt_core_wasm, )? .with_backends(self.backends.backend_configs().await) .with_dictionaries(self.dictionaries.clone()) diff --git a/cli/tests/trap-test/src/main.rs b/cli/tests/trap-test/src/main.rs index 1023106c..d8ab745b 100644 --- a/cli/tests/trap-test/src/main.rs +++ b/cli/tests/trap-test/src/main.rs @@ -16,12 +16,14 @@ pub type TestResult = Result<(), Error>; #[tokio::test(flavor = "multi_thread")] async fn fatal_error_traps() -> TestResult { let module_path = format!("{RUST_FIXTURE_PATH}/response.wasm"); + let adapt_core_wasm = false; let ctx = ExecuteCtx::new( module_path, ProfilingStrategy::None, HashSet::new(), None, viceroy_lib::config::UnknownImportBehavior::LinkError, + adapt_core_wasm, )?; let req = Request::get("http://127.0.0.1:7676/").body(Body::from(""))?; let resp = ctx diff --git a/crates/adapter/src/lib.rs b/crates/adapter/src/lib.rs index 73e27d15..f096ab06 100644 --- a/crates/adapter/src/lib.rs +++ b/crates/adapter/src/lib.rs @@ -11,6 +11,8 @@ use core::slice; use poll::Pollable; use wasi::*; +// test + #[macro_use] mod macros; diff --git a/lib/src/execute.rs b/lib/src/execute.rs index c3b1fe19..e1c9b925 100644 --- a/lib/src/execute.rs +++ b/lib/src/execute.rs @@ -8,6 +8,7 @@ use crate::config::UnknownImportBehavior; use { crate::{ + adapt, body::Body, component as compute, config::{Backends, DeviceDetection, Dictionaries, ExperimentalModule, Geolocation}, @@ -106,6 +107,7 @@ impl ExecuteCtx { wasi_modules: HashSet, guest_profile_path: Option, unknown_import_behavior: UnknownImportBehavior, + adapt_components: bool, ) -> Result { let input = fs::read(&module_path)?; @@ -126,6 +128,22 @@ impl ExecuteCtx { }) ); + // When the input wasn't a component, but we're automatically adapting, + // apply the component adapter. + let (is_component, input) = if !is_component && adapt_components { + // It's not possible to adapt a component from WAT, we can't continue at this point. + if is_wat { + return Err(Error::Other(anyhow::anyhow!( + "Wasm components may only be adapted from binary wasm components, not wat" + ))); + } + + let input = adapt::adapt_bytes(&input)?; + (true, input) + } else { + (is_component, input) + }; + let config = &configure_wasmtime(is_component, profiling_strategy); let engine = Engine::new(config)?; let instance_pre = if is_component { @@ -323,7 +341,8 @@ impl ExecuteCtx { /// # use viceroy_lib::{Error, ExecuteCtx, ProfilingStrategy, ViceroyService}; /// # async fn f() -> Result<(), Error> { /// # let req = Request::new(Body::from("")); - /// let ctx = ExecuteCtx::new("path/to/a/file.wasm", ProfilingStrategy::None, HashSet::new(), None, Default::default())?; + /// let adapt_core_wasm = false; + /// let ctx = ExecuteCtx::new("path/to/a/file.wasm", ProfilingStrategy::None, HashSet::new(), None, Default::default(), adapt_core_wasm)?; /// let resp = ctx.handle_request(req, "127.0.0.1".parse().unwrap()).await?; /// # Ok(()) /// # } diff --git a/lib/src/service.rs b/lib/src/service.rs index eb9201be..60411f33 100644 --- a/lib/src/service.rs +++ b/lib/src/service.rs @@ -43,7 +43,8 @@ impl ViceroyService { /// # use std::collections::HashSet; /// use viceroy_lib::{Error, ExecuteCtx, ProfilingStrategy, ViceroyService}; /// # fn f() -> Result<(), Error> { - /// let ctx = ExecuteCtx::new("path/to/a/file.wasm", ProfilingStrategy::None, HashSet::new(), None, Default::default())?; + /// let adapt_core_wasm = false; + /// let ctx = ExecuteCtx::new("path/to/a/file.wasm", ProfilingStrategy::None, HashSet::new(), None, Default::default(), adapt_core_wasm)?; /// let svc = ViceroyService::new(ctx); /// # Ok(()) /// # } From ca403cf55592ab05a3a106e1914a3c63ab3b0f66 Mon Sep 17 00:00:00 2001 From: Trevor Elliott Date: Thu, 30 May 2024 11:54:12 -0700 Subject: [PATCH 09/15] Comment about why we mangle imports when adapting --- lib/src/adapt.rs | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/lib/src/adapt.rs b/lib/src/adapt.rs index 40b468fd..1e307b14 100644 --- a/lib/src/adapt.rs +++ b/lib/src/adapt.rs @@ -1,3 +1,24 @@ +const ADAPTER_BYTES: &[u8] = include_bytes!("../data/wasi_snapshot_preview1.wasm"); + +/// Given bytes that represent a core wasm module, adapt it to a component using the viceroy +/// adapter. +pub fn adapt_bytes(bytes: &[u8]) -> anyhow::Result> { + let module = mangle_imports(bytes)?; + + let component = wit_component::ComponentEncoder::default() + .module(module.as_slice())? + .adapter("wasi_snapshot_preview1", ADAPTER_BYTES)? + .validate(true) + .encode()?; + + Ok(component) +} + +/// We need to ensure that the imports of the core wasm module are all remapped to the single +/// adapter `wasi_snapshot_preview1`, as that allows us to reuse common infrastructure in the +/// adapter's implementation. To accomplish this, we change imports to all come from the +/// `wasi_snapshot_preview1` module, and mangle the function name to +/// `original_module#original_name`. fn mangle_imports(bytes: &[u8]) -> anyhow::Result { let mut module = wasm_encoder::Module::new(); @@ -50,19 +71,3 @@ fn mangle_imports(bytes: &[u8]) -> anyhow::Result { Ok(module) } - -const ADAPTER_BYTES: &[u8] = include_bytes!("../data/wasi_snapshot_preview1.wasm"); - -/// Given bytes that represent a core wasm module, adapt it to a component using the viceroy -/// adapter. -pub fn adapt_bytes(bytes: &[u8]) -> anyhow::Result> { - let module = mangle_imports(bytes)?; - - let component = wit_component::ComponentEncoder::default() - .module(module.as_slice())? - .adapter("wasi_snapshot_preview1", ADAPTER_BYTES)? - .validate(true) - .encode()?; - - Ok(component) -} From 2b03663f1b47116ec402e62e396bfe076cebbdac Mon Sep 17 00:00:00 2001 From: Trevor Elliott Date: Wed, 5 Jun 2024 10:01:44 -0700 Subject: [PATCH 10/15] Add back the `config-store` interface --- crates/adapter/src/fastly/config_store.rs | 34 ++++++++++++++++-- lib/adapter/wasi_snapshot_preview1.wasm | Bin 0 -> 135214 bytes lib/src/component/config_store.rs | 40 ++++++++++++++++++++++ lib/src/component/dictionary.rs | 9 ++--- lib/src/component/mod.rs | 2 ++ lib/wit/deps/fastly/compute.wit | 17 +++++++++ 6 files changed, 95 insertions(+), 7 deletions(-) create mode 100755 lib/adapter/wasi_snapshot_preview1.wasm create mode 100644 lib/src/component/config_store.rs diff --git a/crates/adapter/src/fastly/config_store.rs b/crates/adapter/src/fastly/config_store.rs index 62033b35..5db9ddbc 100644 --- a/crates/adapter/src/fastly/config_store.rs +++ b/crates/adapter/src/fastly/config_store.rs @@ -1,5 +1,6 @@ -use super::fastly_dictionary; use super::FastlyStatus; +use crate::{bindings::fastly::api::config_store, with_buffer, TrappingUnwrap}; +use core::slice; pub type ConfigStoreHandle = u32; @@ -9,7 +10,16 @@ pub fn open( name_len: usize, store_handle_out: *mut ConfigStoreHandle, ) -> FastlyStatus { - fastly_dictionary::open(name, name_len, store_handle_out) + let name = unsafe { slice::from_raw_parts(name, name_len) }; + match config_store::open(name) { + Ok(res) => { + unsafe { + *store_handle_out = res; + } + FastlyStatus::OK + } + Err(e) => e.into(), + } } #[export_name = "fastly_config_store#get"] @@ -21,5 +31,23 @@ pub fn get( value_max_len: usize, nwritten: *mut usize, ) -> FastlyStatus { - fastly_dictionary::get(store_handle, key, key_len, value, value_max_len, nwritten) + let key = unsafe { slice::from_raw_parts(key, key_len) }; + with_buffer!( + value, + value_max_len, + { + config_store::get( + store_handle, + key, + u64::try_from(value_max_len).trapping_unwrap(), + ) + }, + |res| { + let res = res.ok_or(FastlyStatus::NONE)?; + unsafe { + *nwritten = res.len(); + } + std::mem::forget(res) + } + ) } diff --git a/lib/adapter/wasi_snapshot_preview1.wasm b/lib/adapter/wasi_snapshot_preview1.wasm new file mode 100755 index 0000000000000000000000000000000000000000..f3f6641a82eba0d2e093e24b573c0c140278dbae GIT binary patch literal 135214 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 Result { + let handle = self.dictionary_handle(name.as_str())?; + Ok(handle.into()) + } + + async fn get( + &mut self, + store: config_store::Handle, + name: String, + max_len: u64, + ) -> Result, types::Error> { + let dict = self + .dictionary(store.into())? + .contents() + .map_err(|err| error::Error::Other(err.into()))?; + + let item = if let Some(item) = dict.get(&name) { + item + } else { + return Ok(None); + }; + + if item.len() > usize::try_from(max_len).unwrap() { + return Err(error::Error::BufferLengthError { + buf: "item_out", + len: "item_max_len", + } + .into()); + } + + Ok(Some(item.clone())) + } +} diff --git a/lib/src/component/dictionary.rs b/lib/src/component/dictionary.rs index f1a22995..4a371a69 100644 --- a/lib/src/component/dictionary.rs +++ b/lib/src/component/dictionary.rs @@ -21,10 +21,11 @@ impl dictionary::Host for Session { .contents() .map_err(|err| error::Error::Other(err.into()))?; - let key = key.as_str(); - let item = dict - .get(key) - .ok_or_else(|| types::Error::from(types::Error::OptionalNone))?; + let item = if let Some(item) = dict.get(&key) { + item + } else { + return Ok(None); + }; if item.len() > usize::try_from(max_len).unwrap() { return Err(error::Error::BufferLengthError { diff --git a/lib/src/component/mod.rs b/lib/src/component/mod.rs index c1d1d2e6..9991dd52 100644 --- a/lib/src/component/mod.rs +++ b/lib/src/component/mod.rs @@ -57,6 +57,7 @@ pub fn link_host_functions(linker: &mut component::Linker) -> anyh fastly::api::secret_store::add_to_linker(linker, |x| x.session())?; fastly::api::types::add_to_linker(linker, |x| x.session())?; fastly::api::uap::add_to_linker(linker, |x| x.session())?; + fastly::api::config_store::add_to_linker(linker, |x| x.session())?; Ok(()) } @@ -64,6 +65,7 @@ pub fn link_host_functions(linker: &mut component::Linker) -> anyh pub mod async_io; pub mod backend; pub mod cache; +pub mod config_store; pub mod dictionary; pub mod error; pub mod geo; diff --git a/lib/wit/deps/fastly/compute.wit b/lib/wit/deps/fastly/compute.wit index 3367d589..dc5066cf 100644 --- a/lib/wit/deps/fastly/compute.wit +++ b/lib/wit/deps/fastly/compute.wit @@ -1055,6 +1055,22 @@ interface cache { get-hits: func(handle: handle) -> result; } +interface config-store { + use types.{error}; + + type handle = u32; + + /// Attempt to open the named config store. + open: func(name: string) -> result; + + /// Fetch a value from the config store, returning `None` if it doesn't exist. + get: func( + store: handle, + key: string, + max-len: u64, + ) -> result, error>; +} + interface reactor { use http-types.{request-handle, body-handle}; @@ -1089,6 +1105,7 @@ world compute { import kv-store; import purge; import secret-store; + import config-store; import uap; export reactor; From 7a29848869980396a843abca46308443c2993b44 Mon Sep 17 00:00:00 2001 From: Trevor Elliott Date: Wed, 5 Jun 2024 10:13:18 -0700 Subject: [PATCH 11/15] Switch `adapt` command messages to using tracing::event! --- cli/src/main.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index 12e4d2c9..88082fdc 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -99,7 +99,11 @@ pub async fn main() -> ExitCode { let bytes = match std::fs::read(&input) { Ok(bytes) => bytes, Err(_) => { - eprintln!("Failed to read module from: {}", input.display()); + event!( + Level::ERROR, + "Failed to read module from: {}", + input.display() + ); return ExitCode::FAILURE; } }; @@ -107,16 +111,16 @@ pub async fn main() -> ExitCode { let module = match viceroy_lib::adapt::adapt_bytes(&bytes) { Ok(module) => module, Err(e) => { - eprintln!("Failed to adapt module: {e}"); + event!(Level::ERROR, "Failed to adapt module: {e}"); return ExitCode::FAILURE; } }; - println!("Writing component to: {}", output.display()); + event!(Level::INFO, "Writing component to: {}", output.display()); match std::fs::write(output, module) { Ok(_) => ExitCode::SUCCESS, Err(e) => { - eprintln!("Failed to write component: {e}"); + event!(Level::ERROR, "Failed to write component: {e}"); return ExitCode::FAILURE; } } From d2c2079f06593292e4f116d96dc3ab0afac0c6dc Mon Sep 17 00:00:00 2001 From: Trevor Elliott Date: Wed, 5 Jun 2024 10:34:24 -0700 Subject: [PATCH 12/15] Rename the adapter wasm --- Makefile | 4 ++-- crates/adapter/Cargo.toml | 1 - .../viceroy-component-adapter.wasm} | Bin 135214 -> 135993 bytes lib/data/wasi_snapshot_preview1.wasm | Bin 134217 -> 0 bytes lib/src/adapt.rs | 7 ++++++- 5 files changed, 8 insertions(+), 4 deletions(-) rename lib/{adapter/wasi_snapshot_preview1.wasm => data/viceroy-component-adapter.wasm} (63%) delete mode 100755 lib/data/wasi_snapshot_preview1.wasm diff --git a/Makefile b/Makefile index 6970f4b8..428ede2f 100644 --- a/Makefile +++ b/Makefile @@ -81,5 +81,5 @@ adapter: -p viceroy-component-adapter \ --target wasm32-unknown-unknown mkdir -p lib/adapter - cp target/wasm32-unknown-unknown/release/wasi_snapshot_preview1.wasm \ - lib/adapter/ + cp target/wasm32-unknown-unknown/release/viceroy_component_adapter.wasm \ + lib/data/viceroy-component-adapter.wasm diff --git a/crates/adapter/Cargo.toml b/crates/adapter/Cargo.toml index fd9919f4..4c5866b1 100644 --- a/crates/adapter/Cargo.toml +++ b/crates/adapter/Cargo.toml @@ -17,4 +17,3 @@ object = { workspace = true } [lib] test = false crate-type = ["cdylib"] -name = "wasi_snapshot_preview1" diff --git a/lib/adapter/wasi_snapshot_preview1.wasm b/lib/data/viceroy-component-adapter.wasm similarity index 63% rename from lib/adapter/wasi_snapshot_preview1.wasm rename to lib/data/viceroy-component-adapter.wasm index f3f6641a82eba0d2e093e24b573c0c140278dbae..5cf314de9ac3cb436d44df31b8c14a6fd7ef7e80 100755 GIT binary patch delta 18131 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

rz;S`1 zBot*_-B#GR?$~p@Ma!b}Uc=-VArLuK01%|vZu+L{_Hy2Je4rN@m0yp;2%Wmeal-^5 zZ~=thYHd01ARHj`#|?K;8IODrM1gPO?J3uThm=;-CfPAyrfxl~Bo49B(>rbqUhF_c z2}lw`?e62@&K>27>?MvLVkYFsP*w@l3-7GV9#uL>PD13x1|Gx2fx4?Adq6S|6-&$D z5Of6=9uI9SpO&+m0ErqjssJM}{Rm}HY`ZKW#m^sR+( hIJ7JGPmCz{{WK{8bAO5 delta 17202 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 diff --git a/lib/data/wasi_snapshot_preview1.wasm b/lib/data/wasi_snapshot_preview1.wasm deleted file mode 100755 index 7cea2921b8515c601bd5dfa2f0e70541153c5038..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 134217 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 anyhow::Result> { let component = wit_component::ComponentEncoder::default() .module(module.as_slice())? + // NOTE: the adapter uses the module name `wasi_snapshot_preview1` as it was originally a + // fork of the wasi_snapshot_preview1 adapter. The wasm has a different name to make the + // codebase make more sense, but plumbing that name all the way through the adapter would + // require adjusting all preview1 functions to have a mangled name, like + // "wasi_snapshot_preview1#args_get". .adapter("wasi_snapshot_preview1", ADAPTER_BYTES)? .validate(true) .encode()?; From abf9218f32da56b7da1e7c5e071ce5cc6785c1d0 Mon Sep 17 00:00:00 2001 From: Trevor Elliott Date: Wed, 5 Jun 2024 10:34:44 -0700 Subject: [PATCH 13/15] Add the `-v` flag to the `adapt` subcommand --- cli/src/main.rs | 1 + cli/src/opts.rs | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/cli/src/main.rs b/cli/src/main.rs index 88082fdc..1c02d222 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -94,6 +94,7 @@ pub async fn main() -> ExitCode { } } Commands::Adapt(adapt_args) => { + install_tracing_subscriber(adapt_args.verbosity()); let input = adapt_args.input(); let output = adapt_args.output(); let bytes = match std::fs::read(&input) { diff --git a/cli/src/opts.rs b/cli/src/opts.rs index 7bfab2c1..8b809b15 100644 --- a/cli/src/opts.rs +++ b/cli/src/opts.rs @@ -232,6 +232,12 @@ pub struct AdaptArgs { /// The output name #[arg(short = 'o', long = "output")] output: Option, + + /// Verbosity of logs for Viceroy. `-v` sets the log level to INFO, + /// `-vv` to DEBUG, and `-vvv` to TRACE. This option will not take + /// effect if you set RUST_LOG to a value before starting Viceroy + #[arg(short = 'v', action = clap::ArgAction::Count)] + verbosity: u8, } impl AdaptArgs { @@ -252,6 +258,13 @@ impl AdaptArgs { output.set_extension("component.wasm"); output } + + /// Verbosity of logs for Viceroy. `-v` sets the log level to DEBUG and + /// `-vv` to TRACE. This option will not take effect if you set RUST_LOG + /// to a value before starting Viceroy + pub fn verbosity(&self) -> u8 { + self.verbosity + } } /// Enum of available (experimental) wasi modules From 52ebd52d990a1a1e5658ba515d0cc7522b5cb4e5 Mon Sep 17 00:00:00 2001 From: Trevor Elliott Date: Wed, 5 Jun 2024 10:55:37 -0700 Subject: [PATCH 14/15] Remove `Option` from the `input` to the `adapt` subcommand --- cli/src/opts.rs | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/cli/src/opts.rs b/cli/src/opts.rs index 8b809b15..4f34ad78 100644 --- a/cli/src/opts.rs +++ b/cli/src/opts.rs @@ -69,7 +69,7 @@ pub struct RunArgs { pub struct SharedArgs { /// The path to the service's Wasm module. #[arg(value_parser = check_module, required=true)] - input: Option, + input: Option, /// The path to a TOML file containing `local_server` configuration. #[arg(short = 'C', long = "config")] config_path: Option, @@ -175,7 +175,7 @@ impl RunArgs { impl SharedArgs { /// The path to the service's Wasm binary. pub fn input(&self) -> PathBuf { - PathBuf::from(self.input.as_ref().unwrap()) + self.input.as_ref().unwrap().clone() } /// The path to a `local_server` configuration file. @@ -225,9 +225,9 @@ impl SharedArgs { #[derive(Args, Debug, Clone)] pub struct AdaptArgs { - /// The path to the service's Wasm module. + /// The path to the Wasm module to adapt. #[arg(value_parser = check_module, required=true)] - input: Option, + input: PathBuf, /// The output name #[arg(short = 'o', long = "output")] @@ -242,7 +242,7 @@ pub struct AdaptArgs { impl AdaptArgs { pub(crate) fn input(&self) -> PathBuf { - PathBuf::from(self.input.as_ref().expect("input wasm name")) + self.input.clone() } pub(crate) fn output(&self) -> PathBuf { @@ -250,11 +250,7 @@ impl AdaptArgs { return output.clone(); } - let mut output = PathBuf::from( - PathBuf::from(self.input.as_ref().expect("input wasm name")) - .file_name() - .expect("input filename"), - ); + let mut output = PathBuf::from(self.input.file_name().expect("input filename")); output.set_extension("component.wasm"); output } @@ -309,11 +305,11 @@ impl From<&ExperimentalModule> for ExperimentalModuleArg { /// binary or text format. /// /// [opts]: struct.Opts.html -fn check_module(s: &str) -> Result { +fn check_module(s: &str) -> Result { let path = PathBuf::from(s); - let contents = std::fs::read(path)?; + let contents = std::fs::read(&path)?; match wat::parse_bytes(&contents) { - Ok(_) => Ok(s.to_string()), + Ok(_) => Ok(path), _ => Err(Error::FileFormat), } } From b03d4e0885dbca56276bba13261477430df07cf4 Mon Sep 17 00:00:00 2001 From: Trevor Elliott Date: Wed, 5 Jun 2024 11:14:39 -0700 Subject: [PATCH 15/15] Add an adapter README --- crates/adapter/README.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 crates/adapter/README.md diff --git a/crates/adapter/README.md b/crates/adapter/README.md new file mode 100644 index 00000000..7d66179b --- /dev/null +++ b/crates/adapter/README.md @@ -0,0 +1,29 @@ +# The Fastly Component Adapter + +This crate builds a wasm module that adapts both the preview1 api and the fastly +compute host calls to the component model. It started as a fork of the +[wasi_snapshot_preview1] component adapter with the `proxy` feature manually +expanded out, with all of the fastly-specific functionality mostly being added +in the `src/fastly` tree. The exception to this is the reactor export defined +by the compute world of `compute.wit`, whose definition is in `src/lib.rs` +instead of being defined in `src/fastly`, as the `wit-bindgen::generate!` makes +assumptions about relative module paths that make it hard to define elsewhere. + +Changes to the adapter require running the top-level `make adapter` target, and +committing the resulting `lib/data/viceroy-component-adapter.wasm` wasm module. +This is a bit unfortunate, but as there's no way to hook the packaging step with +cargo, committing the binary is the easiest way to ensure that fresh checkouts +of this repository and packaged versions of the crates both build seamlessly. + +## Adding New Host Calls + +When adding new host calls, the adapter will need to be updated to know how they +should be adapted to the component model. In most cases, this will involve +updating the `/lib/wit/deps/fastly/compute.wit` package to describe what the +component imports of the new host call should look like, implementing it in both +`/lib/src/wiggle_abi` and `/lib/src/component`, and then finally adding a +version of the host call to the adapter in `src/fastly`. As the adapter builds +with the same `compute.wit` that `viceroy-lib` does, the imports will +automatically be available through the top-level `bindings` module. + +[wasi_snapshot_preview1]: https://github.com/bytecodealliance/wasmtime/tree/main/crates/wasi-preview1-component-adapter