extism_runtime/
internal.rs

1use std::collections::BTreeMap;
2
3use crate::*;
4
5/// WASI context
6pub struct Wasi {
7    /// wasi
8    pub ctx: wasmtime_wasi::WasiCtx,
9
10    /// wasi-nn
11    #[cfg(feature = "nn")]
12    pub nn: wasmtime_wasi_nn::WasiNnCtx,
13}
14
15/// Internal stores data that is available to the caller in PDK functions
16pub struct Internal {
17    /// Store
18    pub store: *mut Store<Internal>,
19
20    /// Linker
21    pub linker: *mut wasmtime::Linker<Internal>,
22
23    /// WASI context
24    pub wasi: Option<Wasi>,
25
26    /// Keep track of the status from the last HTTP request
27    pub http_status: u16,
28
29    /// Plugin variables
30    pub vars: BTreeMap<String, Vec<u8>>,
31
32    pub manifest: Manifest,
33
34    pub available_pages: Option<u32>,
35
36    pub(crate) memory_limiter: Option<MemoryLimiter>,
37}
38
39/// InternalExt provides a unified way of acessing `memory`, `store` and `internal` values
40pub trait InternalExt {
41    fn store(&self) -> &Store<Internal>;
42
43    fn store_mut(&mut self) -> &mut Store<Internal>;
44
45    fn linker(&self) -> &Linker<Internal>;
46
47    fn linker_mut(&mut self) -> &mut Linker<Internal>;
48
49    fn linker_and_store(&mut self) -> (&mut Linker<Internal>, &mut Store<Internal>);
50
51    fn internal(&self) -> &Internal {
52        self.store().data()
53    }
54
55    fn internal_mut(&mut self) -> &mut Internal {
56        self.store_mut().data_mut()
57    }
58
59    fn memory_ptr(&mut self) -> *mut u8 {
60        let (linker, mut store) = self.linker_and_store();
61        if let Some(mem) = linker.get(&mut store, "env", "memory") {
62            if let Some(mem) = mem.into_memory() {
63                return mem.data_ptr(&mut store);
64            }
65        }
66
67        std::ptr::null_mut()
68    }
69
70    fn memory(&mut self) -> &mut [u8] {
71        let (linker, mut store) = self.linker_and_store();
72        let mem = linker
73            .get(&mut store, "env", "memory")
74            .unwrap()
75            .into_memory()
76            .unwrap();
77        let ptr = mem.data_ptr(&store);
78        if ptr.is_null() {
79            return &mut [];
80        }
81        let size = mem.data_size(&store);
82        unsafe { std::slice::from_raw_parts_mut(ptr, size) }
83    }
84
85    fn memory_read(&mut self, offs: u64, len: Size) -> &[u8] {
86        trace!("memory_read: {}, {}", offs, len);
87        let offs = offs as usize;
88        let len = len as usize;
89        let mem = self.memory();
90        &mem[offs..offs + len]
91    }
92
93    fn memory_read_str(&mut self, offs: u64) -> Result<&str, std::str::Utf8Error> {
94        let len = self.memory_length(offs);
95        std::str::from_utf8(self.memory_read(offs, len))
96    }
97
98    fn memory_write(&mut self, offs: u64, bytes: impl AsRef<[u8]>) {
99        trace!("memory_write: {}", offs);
100        let b = bytes.as_ref();
101        let offs = offs as usize;
102        let len = b.len();
103        self.memory()[offs..offs + len].copy_from_slice(bytes.as_ref());
104    }
105
106    fn memory_alloc(&mut self, n: Size) -> Result<u64, Error> {
107        if n == 0 {
108            return Ok(0);
109        }
110        let (linker, mut store) = self.linker_and_store();
111        let output = &mut [Val::I64(0)];
112        linker
113            .get(&mut store, "env", "extism_alloc")
114            .unwrap()
115            .into_func()
116            .unwrap()
117            .call(&mut store, &[Val::I64(n as i64)], output)?;
118        let offs = output[0].unwrap_i64() as u64;
119        if offs == 0 && n > 0 {
120            anyhow::bail!("out of memory")
121        }
122        trace!("memory_alloc: {}, {}", offs, n);
123        Ok(offs)
124    }
125
126    fn memory_alloc_bytes(&mut self, bytes: impl AsRef<[u8]>) -> Result<u64, Error> {
127        let b = bytes.as_ref();
128        let offs = self.memory_alloc(b.len() as Size)?;
129        self.memory_write(offs, b);
130        Ok(offs)
131    }
132
133    fn memory_free(&mut self, offs: u64) {
134        let (linker, mut store) = self.linker_and_store();
135        linker
136            .get(&mut store, "env", "extism_free")
137            .unwrap()
138            .into_func()
139            .unwrap()
140            .call(&mut store, &[Val::I64(offs as i64)], &mut [])
141            .unwrap();
142    }
143
144    fn memory_length(&mut self, offs: u64) -> u64 {
145        let (linker, mut store) = self.linker_and_store();
146        let output = &mut [Val::I64(0)];
147        linker
148            .get(&mut store, "env", "extism_length")
149            .unwrap()
150            .into_func()
151            .unwrap()
152            .call(&mut store, &[Val::I64(offs as i64)], output)
153            .unwrap();
154        let len = output[0].unwrap_i64() as u64;
155        trace!("memory_length: {}, {}", offs, len);
156        len
157    }
158
159    // A convenience method to set the plugin error and return a value
160    fn error<E>(&mut self, e: impl std::fmt::Debug, x: E) -> E {
161        let s = format!("{e:?}");
162        debug!("Set error: {:?}", s);
163        if let Ok(offs) = self.memory_alloc_bytes(&s) {
164            let (linker, mut store) = self.linker_and_store();
165            if let Some(f) = linker.get(&mut store, "env", "extism_error_set") {
166                f.into_func()
167                    .unwrap()
168                    .call(&mut store, &[Val::I64(offs as i64)], &mut [])
169                    .unwrap();
170            }
171        }
172        x
173    }
174
175    fn clear_error(&mut self) {
176        let (linker, mut store) = self.linker_and_store();
177        if let Some(f) = linker.get(&mut store, "env", "extism_error_set") {
178            f.into_func()
179                .unwrap()
180                .call(&mut store, &[Val::I64(0)], &mut [])
181                .unwrap();
182        }
183    }
184
185    fn has_error(&mut self) -> bool {
186        let (linker, mut store) = self.linker_and_store();
187        let output = &mut [Val::I64(0)];
188        linker
189            .get(&mut store, "env", "extism_error_get")
190            .unwrap()
191            .into_func()
192            .unwrap()
193            .call(&mut store, &[], output)
194            .unwrap();
195        output[0].unwrap_i64() != 0
196    }
197
198    fn get_error(&mut self) -> Option<&str> {
199        let (linker, mut store) = self.linker_and_store();
200        let output = &mut [Val::I64(0)];
201        linker
202            .get(&mut store, "env", "extism_error_get")
203            .unwrap()
204            .into_func()
205            .unwrap()
206            .call(&mut store, &[], output)
207            .unwrap();
208        let offs = output[0].unwrap_i64() as u64;
209        if offs == 0 {
210            return None;
211        }
212
213        let length = self.memory_length(offs);
214        let data = self.memory_read(offs, length);
215        let s = std::str::from_utf8(data);
216        match s {
217            Ok(s) => Some(s),
218            Err(_) => None,
219        }
220    }
221}
222
223impl Internal {
224    pub(crate) fn new(
225        manifest: Manifest,
226        wasi: bool,
227        available_pages: Option<u32>,
228    ) -> Result<Self, Error> {
229        let wasi = if wasi {
230            let auth = wasmtime_wasi::ambient_authority();
231            let mut ctx = wasmtime_wasi::WasiCtxBuilder::new();
232            for (k, v) in manifest.as_ref().config.iter() {
233                ctx = ctx.env(k, v)?;
234            }
235
236            if let Some(a) = &manifest.as_ref().allowed_paths {
237                for (k, v) in a.iter() {
238                    let d = wasmtime_wasi::Dir::open_ambient_dir(k, auth)?;
239                    ctx = ctx.preopened_dir(d, v)?;
240                }
241            }
242
243            #[cfg(feature = "nn")]
244            let nn = wasmtime_wasi_nn::WasiNnCtx::new()?;
245
246            Some(Wasi {
247                ctx: ctx.build(),
248                #[cfg(feature = "nn")]
249                nn,
250            })
251        } else {
252            None
253        };
254
255        let memory_limiter = if let Some(pgs) = available_pages {
256            let n = pgs as usize * 65536;
257            Some(MemoryLimiter {
258                max_bytes: n,
259                bytes_left: n,
260            })
261        } else {
262            None
263        };
264
265        Ok(Internal {
266            wasi,
267            manifest,
268            http_status: 0,
269            vars: BTreeMap::new(),
270            linker: std::ptr::null_mut(),
271            store: std::ptr::null_mut(),
272            available_pages,
273            memory_limiter,
274        })
275    }
276
277    pub fn linker(&self) -> &wasmtime::Linker<Internal> {
278        unsafe { &*self.linker }
279    }
280
281    pub fn linker_mut(&mut self) -> &mut wasmtime::Linker<Internal> {
282        unsafe { &mut *self.linker }
283    }
284}
285
286impl InternalExt for Internal {
287    fn store(&self) -> &Store<Internal> {
288        unsafe { &*self.store }
289    }
290
291    fn store_mut(&mut self) -> &mut Store<Internal> {
292        unsafe { &mut *self.store }
293    }
294
295    fn linker(&self) -> &Linker<Internal> {
296        unsafe { &*self.linker }
297    }
298
299    fn linker_mut(&mut self) -> &mut Linker<Internal> {
300        unsafe { &mut *self.linker }
301    }
302
303    fn linker_and_store(&mut self) -> (&mut Linker<Internal>, &mut Store<Internal>) {
304        unsafe { (&mut *self.linker, &mut *self.store) }
305    }
306}
307
308pub(crate) struct MemoryLimiter {
309    bytes_left: usize,
310    max_bytes: usize,
311}
312
313impl MemoryLimiter {
314    pub(crate) fn reset(&mut self) {
315        self.bytes_left = self.max_bytes;
316    }
317}
318
319impl wasmtime::ResourceLimiter for MemoryLimiter {
320    fn memory_growing(
321        &mut self,
322        current: usize,
323        desired: usize,
324        maximum: Option<usize>,
325    ) -> Result<bool> {
326        if let Some(max) = maximum {
327            if desired > max {
328                return Ok(false);
329            }
330        }
331
332        let d = desired - current;
333        if d > self.bytes_left {
334            return Ok(false);
335        }
336
337        self.bytes_left -= d;
338        Ok(true)
339    }
340
341    fn table_growing(&mut self, _current: u32, desired: u32, maximum: Option<u32>) -> Result<bool> {
342        if let Some(max) = maximum {
343            return Ok(desired <= max);
344        }
345
346        Ok(true)
347    }
348}