1 use crate::FieldMap;
2 use crate::p3::bindings::clocks::monotonic_clock::Duration;
3 use crate::p3::bindings::http::types::{
4     ErrorCode, FieldName, FieldValue, Fields, HeaderError, Headers, Host, HostFields, HostRequest,
5     HostRequestOptions, HostRequestWithStore, HostResponse, HostResponseWithStore, Method, Request,
6     RequestOptions, RequestOptionsError, Response, Scheme, StatusCode, Trailers,
7 };
8 use crate::p3::body::{Body, HostBodyStreamProducer};
9 use crate::p3::{HeaderResult, HttpError, RequestOptionsResult, WasiHttp, WasiHttpCtxView};
10 use core::mem;
11 use core::pin::Pin;
12 use core::task::{Context, Poll, ready};
13 use http::header::CONTENT_LENGTH;
14 use std::sync::Arc;
15 use tokio::sync::oneshot;
16 use wasmtime::component::{
17     Access, FutureProducer, FutureReader, Resource, ResourceTable, StreamReader,
18 };
19 use wasmtime::error::Context as _;
20 use wasmtime::{AsContextMut, StoreContextMut};
21 
get_fields<'a>( table: &'a ResourceTable, fields: &Resource<Fields>, ) -> wasmtime::Result<&'a Fields>22 fn get_fields<'a>(
23     table: &'a ResourceTable,
24     fields: &Resource<Fields>,
25 ) -> wasmtime::Result<&'a Fields> {
26     table
27         .get(&fields)
28         .context("failed to get fields from table")
29 }
30 
get_fields_mut<'a>( table: &'a mut ResourceTable, fields: &Resource<Fields>, ) -> HeaderResult<&'a mut Fields>31 fn get_fields_mut<'a>(
32     table: &'a mut ResourceTable,
33     fields: &Resource<Fields>,
34 ) -> HeaderResult<&'a mut Fields> {
35     table
36         .get_mut(&fields)
37         .context("failed to get fields from table")
38         .map_err(crate::p3::HeaderError::trap)
39 }
40 
push_fields(table: &mut ResourceTable, fields: Fields) -> wasmtime::Result<Resource<Fields>>41 fn push_fields(table: &mut ResourceTable, fields: Fields) -> wasmtime::Result<Resource<Fields>> {
42     table.push(fields).context("failed to push fields to table")
43 }
44 
delete_fields(table: &mut ResourceTable, fields: Resource<Fields>) -> wasmtime::Result<Fields>45 fn delete_fields(table: &mut ResourceTable, fields: Resource<Fields>) -> wasmtime::Result<Fields> {
46     let mut fields = table
47         .delete(fields)
48         .context("failed to delete fields from table")?;
49     // When fields are passed by ownership to the host that flags them as
50     // immutable within `wasi:http`, and this semantically means that putting
51     // fields in a request, then getting them back out, will return an immutable
52     // view of the headers rather than mutable for example.
53     fields.set_immutable();
54     Ok(fields)
55 }
56 
get_request<'a>( table: &'a ResourceTable, req: &Resource<Request>, ) -> wasmtime::Result<&'a Request>57 fn get_request<'a>(
58     table: &'a ResourceTable,
59     req: &Resource<Request>,
60 ) -> wasmtime::Result<&'a Request> {
61     table.get(req).context("failed to get request from table")
62 }
63 
get_request_mut<'a>( table: &'a mut ResourceTable, req: &Resource<Request>, ) -> wasmtime::Result<&'a mut Request>64 fn get_request_mut<'a>(
65     table: &'a mut ResourceTable,
66     req: &Resource<Request>,
67 ) -> wasmtime::Result<&'a mut Request> {
68     table
69         .get_mut(req)
70         .context("failed to get request from table")
71 }
72 
get_response<'a>( table: &'a ResourceTable, res: &Resource<Response>, ) -> wasmtime::Result<&'a Response>73 fn get_response<'a>(
74     table: &'a ResourceTable,
75     res: &Resource<Response>,
76 ) -> wasmtime::Result<&'a Response> {
77     table.get(res).context("failed to get response from table")
78 }
79 
get_response_mut<'a>( table: &'a mut ResourceTable, res: &Resource<Response>, ) -> wasmtime::Result<&'a mut Response>80 fn get_response_mut<'a>(
81     table: &'a mut ResourceTable,
82     res: &Resource<Response>,
83 ) -> wasmtime::Result<&'a mut Response> {
84     table
85         .get_mut(res)
86         .context("failed to get response from table")
87 }
88 
get_request_options<'a>( table: &'a ResourceTable, opts: &Resource<RequestOptions>, ) -> wasmtime::Result<&'a RequestOptions>89 fn get_request_options<'a>(
90     table: &'a ResourceTable,
91     opts: &Resource<RequestOptions>,
92 ) -> wasmtime::Result<&'a RequestOptions> {
93     table
94         .get(opts)
95         .context("failed to get request options from table")
96 }
97 
get_request_options_mut<'a>( table: &'a mut ResourceTable, opts: &Resource<RequestOptions>, ) -> RequestOptionsResult<&'a mut RequestOptions>98 fn get_request_options_mut<'a>(
99     table: &'a mut ResourceTable,
100     opts: &Resource<RequestOptions>,
101 ) -> RequestOptionsResult<&'a mut RequestOptions> {
102     table
103         .get_mut(opts)
104         .context("failed to get request options from table")
105         .map_err(crate::p3::RequestOptionsError::trap)
106 }
107 
push_request_options( table: &mut ResourceTable, opts: RequestOptions, ) -> wasmtime::Result<Resource<RequestOptions>>108 fn push_request_options(
109     table: &mut ResourceTable,
110     opts: RequestOptions,
111 ) -> wasmtime::Result<Resource<RequestOptions>> {
112     table
113         .push(opts)
114         .context("failed to push request options to table")
115 }
116 
delete_request_options( table: &mut ResourceTable, opts: Resource<RequestOptions>, ) -> wasmtime::Result<RequestOptions>117 fn delete_request_options(
118     table: &mut ResourceTable,
119     opts: Resource<RequestOptions>,
120 ) -> wasmtime::Result<RequestOptions> {
121     table
122         .delete(opts)
123         .context("failed to delete request options from table")
124 }
125 
parse_header_value( name: &http::HeaderName, value: impl AsRef<[u8]>, ) -> Result<http::HeaderValue, HeaderError>126 fn parse_header_value(
127     name: &http::HeaderName,
128     value: impl AsRef<[u8]>,
129 ) -> Result<http::HeaderValue, HeaderError> {
130     if name == CONTENT_LENGTH {
131         let s = str::from_utf8(value.as_ref()).or(Err(HeaderError::InvalidSyntax))?;
132         let v: u64 = s.parse().or(Err(HeaderError::InvalidSyntax))?;
133         Ok(v.into())
134     } else {
135         http::HeaderValue::from_bytes(value.as_ref()).or(Err(HeaderError::InvalidSyntax))
136     }
137 }
138 
139 enum GuestBodyResultProducer {
140     Receiver(oneshot::Receiver<Box<dyn Future<Output = Result<(), ErrorCode>> + Send>>),
141     Future(Pin<Box<dyn Future<Output = Result<(), ErrorCode>> + Send>>),
142 }
143 
poll_future<T>( cx: &mut Context<'_>, fut: Pin<&mut (impl Future<Output = T> + ?Sized)>, finish: bool, ) -> Poll<Option<T>>144 fn poll_future<T>(
145     cx: &mut Context<'_>,
146     fut: Pin<&mut (impl Future<Output = T> + ?Sized)>,
147     finish: bool,
148 ) -> Poll<Option<T>> {
149     match fut.poll(cx) {
150         Poll::Ready(v) => Poll::Ready(Some(v)),
151         Poll::Pending if finish => Poll::Ready(None),
152         Poll::Pending => Poll::Pending,
153     }
154 }
155 
156 impl<D> FutureProducer<D> for GuestBodyResultProducer {
157     type Item = Result<(), ErrorCode>;
158 
poll_produce( mut self: Pin<&mut Self>, cx: &mut Context<'_>, _: StoreContextMut<D>, finish: bool, ) -> Poll<wasmtime::Result<Option<Self::Item>>>159     fn poll_produce(
160         mut self: Pin<&mut Self>,
161         cx: &mut Context<'_>,
162         _: StoreContextMut<D>,
163         finish: bool,
164     ) -> Poll<wasmtime::Result<Option<Self::Item>>> {
165         match &mut *self {
166             Self::Receiver(rx) => {
167                 match ready!(poll_future(cx, Pin::new(rx), finish)) {
168                     Some(Ok(fut)) => {
169                         let mut fut = Box::into_pin(fut);
170                         // poll the received future once and update state
171                         let res = poll_future(cx, fut.as_mut(), finish);
172                         *self = Self::Future(fut);
173                         res.map(Ok)
174                     }
175                     Some(Err(..)) => {
176                         // oneshot sender dropped, treat as success
177                         Poll::Ready(Ok(Some(Ok(()))))
178                     }
179                     None => Poll::Ready(Ok(None)),
180                 }
181             }
182             Self::Future(fut) => poll_future(cx, fut.as_mut(), finish).map(Ok),
183         }
184     }
185 }
186 
187 impl HostFields for WasiHttpCtxView<'_> {
new(&mut self) -> wasmtime::Result<Resource<Fields>>188     fn new(&mut self) -> wasmtime::Result<Resource<Fields>> {
189         push_fields(self.table, FieldMap::new_mutable(self.ctx.field_size_limit))
190     }
191 
from_list( &mut self, entries: Vec<(FieldName, FieldValue)>, ) -> HeaderResult<Resource<Fields>>192     fn from_list(
193         &mut self,
194         entries: Vec<(FieldName, FieldValue)>,
195     ) -> HeaderResult<Resource<Fields>> {
196         let mut fields = FieldMap::new_mutable(self.ctx.field_size_limit);
197         for (name, value) in entries {
198             let name = name.parse().or(Err(HeaderError::InvalidSyntax))?;
199             if self.hooks.is_forbidden_header(&name) {
200                 return Err(HeaderError::Forbidden.into());
201             }
202             let value = parse_header_value(&name, value)?;
203             fields.append(name, value)?;
204         }
205         let fields = push_fields(self.table, fields).map_err(crate::p3::HeaderError::trap)?;
206         Ok(fields)
207     }
208 
get( &mut self, fields: Resource<Fields>, name: FieldName, ) -> wasmtime::Result<Vec<FieldValue>>209     fn get(
210         &mut self,
211         fields: Resource<Fields>,
212         name: FieldName,
213     ) -> wasmtime::Result<Vec<FieldValue>> {
214         let fields = get_fields(self.table, &fields)?;
215         Ok(fields
216             .get_all(name)
217             .into_iter()
218             .map(|val| val.as_bytes().into())
219             .collect())
220     }
221 
has(&mut self, fields: Resource<Fields>, name: FieldName) -> wasmtime::Result<bool>222     fn has(&mut self, fields: Resource<Fields>, name: FieldName) -> wasmtime::Result<bool> {
223         let fields = get_fields(self.table, &fields)?;
224         Ok(fields.contains_key(name))
225     }
226 
set( &mut self, fields: Resource<Fields>, name: FieldName, value: Vec<FieldValue>, ) -> HeaderResult<()>227     fn set(
228         &mut self,
229         fields: Resource<Fields>,
230         name: FieldName,
231         value: Vec<FieldValue>,
232     ) -> HeaderResult<()> {
233         let name = name.parse().map_err(|_| HeaderError::InvalidSyntax)?;
234         if self.hooks.is_forbidden_header(&name) {
235             return Err(HeaderError::Forbidden.into());
236         }
237         let mut values = Vec::with_capacity(value.len());
238         for value in value {
239             let value = parse_header_value(&name, value)?;
240             values.push(value);
241         }
242         get_fields_mut(self.table, &fields)?.set(name, values)?;
243         Ok(())
244     }
245 
delete(&mut self, fields: Resource<Fields>, name: FieldName) -> HeaderResult<()>246     fn delete(&mut self, fields: Resource<Fields>, name: FieldName) -> HeaderResult<()> {
247         let name = name.parse().map_err(|_| HeaderError::InvalidSyntax)?;
248         if self.hooks.is_forbidden_header(&name) {
249             return Err(HeaderError::Forbidden.into());
250         }
251         get_fields_mut(self.table, &fields)?.remove_all(name)?;
252         Ok(())
253     }
254 
get_and_delete( &mut self, fields: Resource<Fields>, name: FieldName, ) -> HeaderResult<Vec<FieldValue>>255     fn get_and_delete(
256         &mut self,
257         fields: Resource<Fields>,
258         name: FieldName,
259     ) -> HeaderResult<Vec<FieldValue>> {
260         let name = name.parse().or(Err(HeaderError::InvalidSyntax))?;
261         if self.hooks.is_forbidden_header(&name) {
262             return Err(HeaderError::Forbidden.into());
263         }
264         let values = get_fields_mut(self.table, &fields)?
265             .remove_all(name)?
266             .into_iter();
267         Ok(values.map(|value| value.as_bytes().into()).collect())
268     }
269 
append( &mut self, fields: Resource<Fields>, name: FieldName, value: FieldValue, ) -> HeaderResult<()>270     fn append(
271         &mut self,
272         fields: Resource<Fields>,
273         name: FieldName,
274         value: FieldValue,
275     ) -> HeaderResult<()> {
276         let name = name.parse().or(Err(HeaderError::InvalidSyntax))?;
277         if self.hooks.is_forbidden_header(&name) {
278             return Err(HeaderError::Forbidden.into());
279         }
280         let value = parse_header_value(&name, value)?;
281         get_fields_mut(self.table, &fields)?.append(name, value)?;
282         Ok(())
283     }
284 
copy_all( &mut self, fields: Resource<Fields>, ) -> wasmtime::Result<Vec<(FieldName, FieldValue)>>285     fn copy_all(
286         &mut self,
287         fields: Resource<Fields>,
288     ) -> wasmtime::Result<Vec<(FieldName, FieldValue)>> {
289         let fields = get_fields(self.table, &fields)?;
290         let fields = fields
291             .iter()
292             .map(|(name, value)| (name.as_str().into(), value.as_bytes().into()))
293             .collect();
294         Ok(fields)
295     }
296 
clone(&mut self, fields: Resource<Fields>) -> wasmtime::Result<Resource<Fields>>297     fn clone(&mut self, fields: Resource<Fields>) -> wasmtime::Result<Resource<Fields>> {
298         let mut fields = get_fields(self.table, &fields)?.clone();
299         fields.set_mutable(self.ctx.field_size_limit);
300         push_fields(self.table, fields)
301     }
302 
drop(&mut self, fields: Resource<Fields>) -> wasmtime::Result<()>303     fn drop(&mut self, fields: Resource<Fields>) -> wasmtime::Result<()> {
304         delete_fields(self.table, fields)?;
305         Ok(())
306     }
307 }
308 
309 impl HostRequestWithStore for WasiHttp {
new<T>( mut store: Access<T, Self>, headers: Resource<Headers>, contents: Option<StreamReader<u8>>, trailers: FutureReader<Result<Option<Resource<Trailers>>, ErrorCode>>, options: Option<Resource<RequestOptions>>, ) -> wasmtime::Result<(Resource<Request>, FutureReader<Result<(), ErrorCode>>)>310     fn new<T>(
311         mut store: Access<T, Self>,
312         headers: Resource<Headers>,
313         contents: Option<StreamReader<u8>>,
314         trailers: FutureReader<Result<Option<Resource<Trailers>>, ErrorCode>>,
315         options: Option<Resource<RequestOptions>>,
316     ) -> wasmtime::Result<(Resource<Request>, FutureReader<Result<(), ErrorCode>>)> {
317         let (result_tx, result_rx) = oneshot::channel();
318         let body = match contents
319             .map(|rx| rx.try_into::<HostBodyStreamProducer<T>>(store.as_context_mut()))
320         {
321             Some(Ok(mut producer)) => Body::Host {
322                 body: mem::take(&mut producer.body),
323                 result_tx,
324             },
325             Some(Err(rx)) => Body::Guest {
326                 contents_rx: Some(rx),
327                 trailers_rx: trailers,
328                 result_tx,
329             },
330             None => Body::Guest {
331                 contents_rx: None,
332                 trailers_rx: trailers,
333                 result_tx,
334             },
335         };
336         let WasiHttpCtxView { table, .. } = store.get();
337         let headers = delete_fields(table, headers)?;
338         let options = options
339             .map(|options| delete_request_options(table, options))
340             .transpose()?;
341         let req = Request {
342             method: http::Method::GET,
343             scheme: None,
344             authority: None,
345             path_with_query: None,
346             headers,
347             options: options.map(Into::into),
348             body,
349         };
350         let req = table.push(req).context("failed to push request to table")?;
351         Ok((
352             req,
353             FutureReader::new(&mut store, GuestBodyResultProducer::Receiver(result_rx))?,
354         ))
355     }
356 
consume_body<T>( mut store: Access<T, Self>, req: Resource<Request>, fut: FutureReader<Result<(), ErrorCode>>, ) -> wasmtime::Result<( StreamReader<u8>, FutureReader<Result<Option<Resource<Trailers>>, ErrorCode>>, )>357     fn consume_body<T>(
358         mut store: Access<T, Self>,
359         req: Resource<Request>,
360         fut: FutureReader<Result<(), ErrorCode>>,
361     ) -> wasmtime::Result<(
362         StreamReader<u8>,
363         FutureReader<Result<Option<Resource<Trailers>>, ErrorCode>>,
364     )> {
365         let getter = store.getter();
366         let Request { body, .. } = store
367             .get()
368             .table
369             .delete(req)
370             .context("failed to delete request from table")?;
371         body.consume(store, fut, getter)
372     }
373 
drop<T>(mut store: Access<'_, T, Self>, req: Resource<Request>) -> wasmtime::Result<()>374     fn drop<T>(mut store: Access<'_, T, Self>, req: Resource<Request>) -> wasmtime::Result<()> {
375         let Request { body, .. } = store
376             .get()
377             .table
378             .delete(req)
379             .context("failed to delete request from table")?;
380         body.drop(store)?;
381         Ok(())
382     }
383 }
384 
385 impl HostRequest for WasiHttpCtxView<'_> {
get_method(&mut self, req: Resource<Request>) -> wasmtime::Result<Method>386     fn get_method(&mut self, req: Resource<Request>) -> wasmtime::Result<Method> {
387         let Request { method, .. } = get_request(self.table, &req)?;
388         Ok(method.into())
389     }
390 
set_method( &mut self, req: Resource<Request>, method: Method, ) -> wasmtime::Result<Result<(), ()>>391     fn set_method(
392         &mut self,
393         req: Resource<Request>,
394         method: Method,
395     ) -> wasmtime::Result<Result<(), ()>> {
396         let req = get_request_mut(self.table, &req)?;
397         let Ok(method) = method.try_into() else {
398             return Ok(Err(()));
399         };
400         req.method = method;
401         Ok(Ok(()))
402     }
403 
get_path_with_query(&mut self, req: Resource<Request>) -> wasmtime::Result<Option<String>>404     fn get_path_with_query(&mut self, req: Resource<Request>) -> wasmtime::Result<Option<String>> {
405         let Request {
406             path_with_query, ..
407         } = get_request(self.table, &req)?;
408         Ok(path_with_query.as_ref().map(|pq| pq.as_str().into()))
409     }
410 
set_path_with_query( &mut self, req: Resource<Request>, path_with_query: Option<String>, ) -> wasmtime::Result<Result<(), ()>>411     fn set_path_with_query(
412         &mut self,
413         req: Resource<Request>,
414         path_with_query: Option<String>,
415     ) -> wasmtime::Result<Result<(), ()>> {
416         let req = get_request_mut(self.table, &req)?;
417         let Some(path_with_query) = path_with_query else {
418             req.path_with_query = None;
419             return Ok(Ok(()));
420         };
421         let Ok(path_with_query) = path_with_query.try_into() else {
422             return Ok(Err(()));
423         };
424         req.path_with_query = Some(path_with_query);
425         Ok(Ok(()))
426     }
427 
get_scheme(&mut self, req: Resource<Request>) -> wasmtime::Result<Option<Scheme>>428     fn get_scheme(&mut self, req: Resource<Request>) -> wasmtime::Result<Option<Scheme>> {
429         let Request { scheme, .. } = get_request(self.table, &req)?;
430         Ok(scheme.as_ref().map(Into::into))
431     }
432 
set_scheme( &mut self, req: Resource<Request>, scheme: Option<Scheme>, ) -> wasmtime::Result<Result<(), ()>>433     fn set_scheme(
434         &mut self,
435         req: Resource<Request>,
436         scheme: Option<Scheme>,
437     ) -> wasmtime::Result<Result<(), ()>> {
438         let req = get_request_mut(self.table, &req)?;
439         let Some(scheme) = scheme else {
440             req.scheme = None;
441             return Ok(Ok(()));
442         };
443         let Ok(scheme) = scheme.try_into() else {
444             return Ok(Err(()));
445         };
446         req.scheme = Some(scheme);
447         Ok(Ok(()))
448     }
449 
get_authority(&mut self, req: Resource<Request>) -> wasmtime::Result<Option<String>>450     fn get_authority(&mut self, req: Resource<Request>) -> wasmtime::Result<Option<String>> {
451         let Request { authority, .. } = get_request(self.table, &req)?;
452         Ok(authority.as_ref().map(|auth| auth.as_str().into()))
453     }
454 
set_authority( &mut self, req: Resource<Request>, authority: Option<String>, ) -> wasmtime::Result<Result<(), ()>>455     fn set_authority(
456         &mut self,
457         req: Resource<Request>,
458         authority: Option<String>,
459     ) -> wasmtime::Result<Result<(), ()>> {
460         let req = get_request_mut(self.table, &req)?;
461         let Some(authority) = authority else {
462             req.authority = None;
463             return Ok(Ok(()));
464         };
465         let has_port = authority.contains(':');
466         let Ok(authority) = http::uri::Authority::try_from(authority) else {
467             return Ok(Err(()));
468         };
469         if has_port && authority.port_u16().is_none() {
470             return Ok(Err(()));
471         }
472         req.authority = Some(authority);
473         Ok(Ok(()))
474     }
475 
get_options( &mut self, req: Resource<Request>, ) -> wasmtime::Result<Option<Resource<RequestOptions>>>476     fn get_options(
477         &mut self,
478         req: Resource<Request>,
479     ) -> wasmtime::Result<Option<Resource<RequestOptions>>> {
480         let Request { options, .. } = get_request(self.table, &req)?;
481         if let Some(options) = options {
482             let options = push_request_options(
483                 self.table,
484                 RequestOptions::new_immutable(Arc::clone(options)),
485             )?;
486             Ok(Some(options))
487         } else {
488             Ok(None)
489         }
490     }
491 
get_headers(&mut self, req: Resource<Request>) -> wasmtime::Result<Resource<Headers>>492     fn get_headers(&mut self, req: Resource<Request>) -> wasmtime::Result<Resource<Headers>> {
493         let Request { headers, .. } = get_request(self.table, &req)?;
494         push_fields(self.table, headers.clone())
495     }
496 }
497 
498 impl HostRequestOptions for WasiHttpCtxView<'_> {
new(&mut self) -> wasmtime::Result<Resource<RequestOptions>>499     fn new(&mut self) -> wasmtime::Result<Resource<RequestOptions>> {
500         push_request_options(self.table, RequestOptions::new_mutable_default())
501     }
502 
get_connect_timeout( &mut self, opts: Resource<RequestOptions>, ) -> wasmtime::Result<Option<Duration>>503     fn get_connect_timeout(
504         &mut self,
505         opts: Resource<RequestOptions>,
506     ) -> wasmtime::Result<Option<Duration>> {
507         let opts = get_request_options(self.table, &opts)?;
508         let Some(connect_timeout) = opts.connect_timeout else {
509             return Ok(None);
510         };
511         let ns = connect_timeout.as_nanos();
512         let ns = Duration::try_from(ns)
513             .context("connect timeout duration nanoseconds do not fit in u64")?;
514         Ok(Some(ns))
515     }
516 
set_connect_timeout( &mut self, opts: Resource<RequestOptions>, duration: Option<Duration>, ) -> RequestOptionsResult<()>517     fn set_connect_timeout(
518         &mut self,
519         opts: Resource<RequestOptions>,
520         duration: Option<Duration>,
521     ) -> RequestOptionsResult<()> {
522         let opts = get_request_options_mut(self.table, &opts)?;
523         let opts = opts.get_mut().ok_or(RequestOptionsError::Immutable)?;
524         opts.connect_timeout = duration.map(core::time::Duration::from_nanos);
525         Ok(())
526     }
527 
get_first_byte_timeout( &mut self, opts: Resource<RequestOptions>, ) -> wasmtime::Result<Option<Duration>>528     fn get_first_byte_timeout(
529         &mut self,
530         opts: Resource<RequestOptions>,
531     ) -> wasmtime::Result<Option<Duration>> {
532         let opts = get_request_options(self.table, &opts)?;
533         let Some(first_byte_timeout) = opts.first_byte_timeout else {
534             return Ok(None);
535         };
536         let ns = first_byte_timeout.as_nanos();
537         let ns = Duration::try_from(ns)
538             .context("first byte timeout duration nanoseconds do not fit in u64")?;
539         Ok(Some(ns))
540     }
541 
set_first_byte_timeout( &mut self, opts: Resource<RequestOptions>, duration: Option<Duration>, ) -> RequestOptionsResult<()>542     fn set_first_byte_timeout(
543         &mut self,
544         opts: Resource<RequestOptions>,
545         duration: Option<Duration>,
546     ) -> RequestOptionsResult<()> {
547         let opts = get_request_options_mut(self.table, &opts)?;
548         let opts = opts.get_mut().ok_or(RequestOptionsError::Immutable)?;
549         opts.first_byte_timeout = duration.map(core::time::Duration::from_nanos);
550         Ok(())
551     }
552 
get_between_bytes_timeout( &mut self, opts: Resource<RequestOptions>, ) -> wasmtime::Result<Option<Duration>>553     fn get_between_bytes_timeout(
554         &mut self,
555         opts: Resource<RequestOptions>,
556     ) -> wasmtime::Result<Option<Duration>> {
557         let opts = get_request_options(self.table, &opts)?;
558         let Some(between_bytes_timeout) = opts.between_bytes_timeout else {
559             return Ok(None);
560         };
561         let ns = between_bytes_timeout.as_nanos();
562         let ns = Duration::try_from(ns)
563             .context("between bytes timeout duration nanoseconds do not fit in u64")?;
564         Ok(Some(ns))
565     }
566 
set_between_bytes_timeout( &mut self, opts: Resource<RequestOptions>, duration: Option<Duration>, ) -> RequestOptionsResult<()>567     fn set_between_bytes_timeout(
568         &mut self,
569         opts: Resource<RequestOptions>,
570         duration: Option<Duration>,
571     ) -> RequestOptionsResult<()> {
572         let opts = get_request_options_mut(self.table, &opts)?;
573         let opts = opts.get_mut().ok_or(RequestOptionsError::Immutable)?;
574         opts.between_bytes_timeout = duration.map(core::time::Duration::from_nanos);
575         Ok(())
576     }
577 
clone( &mut self, opts: Resource<RequestOptions>, ) -> wasmtime::Result<Resource<RequestOptions>>578     fn clone(
579         &mut self,
580         opts: Resource<RequestOptions>,
581     ) -> wasmtime::Result<Resource<RequestOptions>> {
582         let opts = get_request_options(self.table, &opts)?;
583         push_request_options(self.table, RequestOptions::new_mutable(Arc::clone(opts)))
584     }
585 
drop(&mut self, opts: Resource<RequestOptions>) -> wasmtime::Result<()>586     fn drop(&mut self, opts: Resource<RequestOptions>) -> wasmtime::Result<()> {
587         delete_request_options(self.table, opts)?;
588         Ok(())
589     }
590 }
591 
592 impl HostResponseWithStore for WasiHttp {
new<T>( mut store: Access<T, Self>, headers: Resource<Headers>, contents: Option<StreamReader<u8>>, trailers: FutureReader<Result<Option<Resource<Trailers>>, ErrorCode>>, ) -> wasmtime::Result<(Resource<Response>, FutureReader<Result<(), ErrorCode>>)>593     fn new<T>(
594         mut store: Access<T, Self>,
595         headers: Resource<Headers>,
596         contents: Option<StreamReader<u8>>,
597         trailers: FutureReader<Result<Option<Resource<Trailers>>, ErrorCode>>,
598     ) -> wasmtime::Result<(Resource<Response>, FutureReader<Result<(), ErrorCode>>)> {
599         let (result_tx, result_rx) = oneshot::channel();
600         let body = match contents
601             .map(|rx| rx.try_into::<HostBodyStreamProducer<T>>(store.as_context_mut()))
602         {
603             Some(Ok(mut producer)) => Body::Host {
604                 body: mem::take(&mut producer.body),
605                 result_tx,
606             },
607             Some(Err(rx)) => Body::Guest {
608                 contents_rx: Some(rx),
609                 trailers_rx: trailers,
610                 result_tx,
611             },
612             None => Body::Guest {
613                 contents_rx: None,
614                 trailers_rx: trailers,
615                 result_tx,
616             },
617         };
618         let WasiHttpCtxView { table, .. } = store.get();
619         let headers = delete_fields(table, headers)?;
620         let res = Response {
621             status: http::StatusCode::OK,
622             headers,
623             body,
624         };
625         let res = table
626             .push(res)
627             .context("failed to push response to table")?;
628         Ok((
629             res,
630             FutureReader::new(&mut store, GuestBodyResultProducer::Receiver(result_rx))?,
631         ))
632     }
633 
consume_body<T>( mut store: Access<T, Self>, res: Resource<Response>, fut: FutureReader<Result<(), ErrorCode>>, ) -> wasmtime::Result<( StreamReader<u8>, FutureReader<Result<Option<Resource<Trailers>>, ErrorCode>>, )>634     fn consume_body<T>(
635         mut store: Access<T, Self>,
636         res: Resource<Response>,
637         fut: FutureReader<Result<(), ErrorCode>>,
638     ) -> wasmtime::Result<(
639         StreamReader<u8>,
640         FutureReader<Result<Option<Resource<Trailers>>, ErrorCode>>,
641     )> {
642         let getter = store.getter();
643         let Response { body, .. } = store
644             .get()
645             .table
646             .delete(res)
647             .context("failed to delete response from table")?;
648         body.consume(store, fut, getter)
649     }
650 
drop<T>(mut store: Access<'_, T, Self>, res: Resource<Response>) -> wasmtime::Result<()>651     fn drop<T>(mut store: Access<'_, T, Self>, res: Resource<Response>) -> wasmtime::Result<()> {
652         let Response { body, .. } = store
653             .get()
654             .table
655             .delete(res)
656             .context("failed to delete response from table")?;
657         body.drop(store)?;
658         Ok(())
659     }
660 }
661 
662 impl HostResponse for WasiHttpCtxView<'_> {
get_status_code(&mut self, res: Resource<Response>) -> wasmtime::Result<StatusCode>663     fn get_status_code(&mut self, res: Resource<Response>) -> wasmtime::Result<StatusCode> {
664         let res = get_response(self.table, &res)?;
665         Ok(res.status.into())
666     }
667 
set_status_code( &mut self, res: Resource<Response>, status_code: StatusCode, ) -> wasmtime::Result<Result<(), ()>>668     fn set_status_code(
669         &mut self,
670         res: Resource<Response>,
671         status_code: StatusCode,
672     ) -> wasmtime::Result<Result<(), ()>> {
673         let res = get_response_mut(self.table, &res)?;
674         match http::StatusCode::from_u16(status_code) {
675             Ok(status) if matches!(status_code, 100..=599) => {
676                 res.status = status;
677                 Ok(Ok(()))
678             }
679             _ => Ok(Err(())),
680         }
681     }
682 
get_headers(&mut self, res: Resource<Response>) -> wasmtime::Result<Resource<Headers>>683     fn get_headers(&mut self, res: Resource<Response>) -> wasmtime::Result<Resource<Headers>> {
684         let Response { headers, .. } = get_response(self.table, &res)?;
685         push_fields(self.table, headers.clone())
686     }
687 }
688 
689 impl Host for WasiHttpCtxView<'_> {
convert_error_code(&mut self, error: HttpError) -> wasmtime::Result<ErrorCode>690     fn convert_error_code(&mut self, error: HttpError) -> wasmtime::Result<ErrorCode> {
691         error.downcast()
692     }
693 
convert_header_error( &mut self, error: crate::p3::HeaderError, ) -> wasmtime::Result<HeaderError>694     fn convert_header_error(
695         &mut self,
696         error: crate::p3::HeaderError,
697     ) -> wasmtime::Result<HeaderError> {
698         error.downcast()
699     }
700 
convert_request_options_error( &mut self, error: crate::p3::RequestOptionsError, ) -> wasmtime::Result<RequestOptionsError>701     fn convert_request_options_error(
702         &mut self,
703         error: crate::p3::RequestOptionsError,
704     ) -> wasmtime::Result<RequestOptionsError> {
705         error.downcast()
706     }
707 }
708