1use serde::Serialize;
19use serde::de::DeserializeOwned;
20use std::alloc::{GlobalAlloc, Layout, System};
21use std::sync::Once;
22
23pub mod action;
24pub mod base64;
25pub mod diagnose;
26pub mod http;
27pub mod ops;
28#[cfg(test)]
29mod ops_tests;
30
31pub const API_VERSION: u32 = 1;
33
34pub const CACHE_MAX_KEY: usize = 1024;
35pub const CACHE_MAX_VALUE: usize = 1024 * 1024;
36
37#[link(wasm_import_module = "extism:host/env")]
40unsafe extern "C" {
41 fn input_length() -> u64;
42 fn input_load_u8(index: u64) -> u8;
43 fn input_load_u64(index: u64) -> u64;
44 fn alloc(length: u64) -> u64;
45 fn free(offset: u64);
46 fn length(offset: u64) -> u64;
47 fn load_u8(offset: u64) -> u8;
48 fn load_u64(offset: u64) -> u64;
49 fn store_u8(offset: u64, value: u8);
50 fn store_u64(offset: u64, value: u64);
51 fn output_set(offset: u64, length: u64);
52 fn error_set(offset: u64);
53 fn log_debug(offset: u64);
54 fn log_info(offset: u64);
55 fn log_warn(offset: u64);
56 fn log_error(offset: u64);
57}
58
59#[link(wasm_import_module = "extism:host/user")]
60unsafe extern "C" {
61 fn hammer_dispatch(request: u64) -> u64;
62 fn hammer_cache_get(key: u64) -> u64;
63 fn hammer_cache_set(key: u64, value: u64);
64 fn hammer_progress(report: u64);
65 fn hammer_resource(name: u64) -> u64;
66}
67
68fn to_kernel(bytes: &[u8]) -> u64 {
69 unsafe {
70 let offset = alloc(bytes.len() as u64);
71 let mut chunks = bytes.chunks_exact(8);
72 let mut at = offset;
73 for chunk in &mut chunks {
74 store_u64(at, u64::from_le_bytes(chunk.try_into().unwrap()));
75 at += 8;
76 }
77 for &byte in chunks.remainder() {
78 store_u8(at, byte);
79 at += 1;
80 }
81 offset
82 }
83}
84
85fn from_kernel(offset: u64) -> Vec<u8> {
86 unsafe {
87 let size = length(offset) as usize;
88 let mut bytes = Vec::with_capacity(size);
89 let mut at = 0;
90 while at + 8 <= size {
91 bytes.extend_from_slice(&load_u64(offset + at as u64).to_le_bytes());
92 at += 8;
93 }
94 while at < size {
95 bytes.push(load_u8(offset + at as u64));
96 at += 1;
97 }
98 bytes
99 }
100}
101
102pub type Error = Box<dyn std::error::Error>;
104
105pub type Result<T> = std::result::Result<T, Error>;
106
107pub fn run<O: AsRef<[u8]>>(body: impl FnOnce() -> Result<O>) -> i32 {
109 static PANIC_HOOK: Once = Once::new();
110 PANIC_HOOK.call_once(|| std::panic::set_hook(Box::new(|info| set_error(&info.to_string()))));
112 match body() {
113 Ok(output) => {
114 let output = output.as_ref();
115 unsafe { output_set(to_kernel(output), output.len() as u64) };
116 0
117 }
118 Err(error) => {
119 set_error(&error.to_string());
120 1
121 }
122 }
123}
124
125fn set_error(message: &str) {
126 unsafe { error_set(to_kernel(message.as_bytes())) };
127}
128
129pub fn input() -> Vec<u8> {
130 unsafe {
131 let size = input_length() as usize;
132 let mut bytes = Vec::with_capacity(size);
133 let mut at = 0;
134 while at + 8 <= size {
135 bytes.extend_from_slice(&input_load_u64(at as u64).to_le_bytes());
136 at += 8;
137 }
138 while at < size {
139 bytes.push(input_load_u8(at as u64));
140 at += 1;
141 }
142 bytes
143 }
144}
145
146pub fn input_json<T: DeserializeOwned>() -> Result<T> {
147 Ok(serde_json::from_slice(&input())?)
148}
149
150#[derive(serde::Deserialize)]
151struct Reply {
152 #[serde(default)]
153 output: serde_json::Value,
154 error: Option<ReplyError>,
155}
156
157#[derive(serde::Deserialize)]
158struct ReplyError {
159 kind: String,
160 #[serde(default)]
161 message: String,
162}
163
164pub fn call<T: DeserializeOwned>(operation: &str, input: &impl Serialize) -> Result<T> {
168 #[derive(Serialize)]
169 struct Request<'a, I> {
170 operation: &'a str,
171 input: &'a I,
172 }
173 let request = to_kernel(&serde_json::to_vec(&Request { operation, input })?);
174 let reply = unsafe { hammer_dispatch(request) };
175 unsafe { free(request) };
176 let bytes = from_kernel(reply);
177 unsafe { free(reply) };
178 let reply: Reply = serde_json::from_slice(&bytes)?;
179 match reply.error {
180 Some(error) => Err(format!("{}: {}", error.kind, error.message).into()),
181 None => Ok(serde_json::from_value(reply.output)?),
183 }
184}
185
186pub fn cache_get(key: &str) -> Option<Vec<u8>> {
193 if key.len() > CACHE_MAX_KEY {
194 return None;
195 }
196 let key = to_kernel(key.as_bytes());
197 let value = unsafe { hammer_cache_get(key) };
198 unsafe { free(key) };
199 if value == 0 {
200 return None;
201 }
202 let bytes = from_kernel(value);
203 unsafe { free(value) };
204 Some(bytes)
205}
206
207pub fn cache_set(key: &str, value: &[u8]) -> bool {
210 if key.len() > CACHE_MAX_KEY || value.len() > CACHE_MAX_VALUE {
211 return false;
212 }
213 let key = to_kernel(key.as_bytes());
214 let value = if value.is_empty() { 0 } else { to_kernel(value) };
215 unsafe {
216 hammer_cache_set(key, value);
217 free(key);
218 if value != 0 {
219 free(value);
220 }
221 }
222 true
223}
224
225pub fn resource(name: &str) -> Option<Vec<u8>> {
229 let name = to_kernel(name.as_bytes());
230 let value = unsafe { hammer_resource(name) };
231 unsafe { free(name) };
232 if value == 0 {
233 return None;
234 }
235 let bytes = from_kernel(value);
236 unsafe { free(value) };
237 Some(bytes)
238}
239
240pub fn progress(fraction: Option<f64>, message: Option<&str>) {
244 #[derive(Serialize)]
245 struct Report<'a> {
246 #[serde(skip_serializing_if = "Option::is_none")]
247 fraction: Option<f64>,
248 #[serde(skip_serializing_if = "Option::is_none")]
249 message: Option<&'a str>,
250 }
251 let fraction = fraction.map(|it| it.clamp(0.0, 1.0));
252 let Ok(report) = serde_json::to_vec(&Report { fraction, message }) else { return };
253 let handle = to_kernel(&report);
254 unsafe {
255 hammer_progress(handle);
256 free(handle);
257 }
258}
259
260pub fn char_byte_offsets(text: &str) -> Vec<usize> {
263 text.char_indices().map(|(at, _)| at).chain(std::iter::once(text.len())).collect()
264}
265
266pub struct ZeroingAllocator;
274
275unsafe impl GlobalAlloc for ZeroingAllocator {
276 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
277 unsafe { System.alloc(layout) }
278 }
279
280 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
281 unsafe {
282 ptr.write_bytes(0, layout.size());
283 System.dealloc(ptr, layout)
284 }
285 }
286}
287
288#[cfg(feature = "getrandom")]
289mod random {
290 #[link(wasm_import_module = "wasi_snapshot_preview1")]
291 unsafe extern "C" {
292 fn random_get(buf: *mut u8, len: usize) -> u16;
293 }
294
295 #[unsafe(no_mangle)]
298 unsafe extern "Rust" fn __getrandom_v03_custom(dest: *mut u8, len: usize) -> Result<(), getrandom::Error> {
299 match unsafe { random_get(dest, len) } {
300 0 => Ok(()),
301 _ => Err(getrandom::Error::UNSUPPORTED),
302 }
303 }
304}
305
306pub mod log {
307 fn send(to: unsafe extern "C" fn(u64), message: &str) {
308 let handle = super::to_kernel(message.as_bytes());
309 unsafe {
310 to(handle);
311 super::free(handle);
312 }
313 }
314
315 pub fn debug(message: &str) {
316 send(super::log_debug, message)
317 }
318
319 pub fn info(message: &str) {
320 send(super::log_info, message)
321 }
322
323 pub fn warn(message: &str) {
324 send(super::log_warn, message)
325 }
326
327 pub fn error(message: &str) {
328 send(super::log_error, message)
329 }
330}