diff --git a/lib/compute-at-edge-abi/compute-at-edge.witx b/lib/compute-at-edge-abi/compute-at-edge.witx index 45395d28..38128c98 100644 --- a/lib/compute-at-edge-abi/compute-at-edge.witx +++ b/lib/compute-at-edge-abi/compute-at-edge.witx @@ -58,10 +58,21 @@ (result $err (expected $num_bytes (error $fastly_status))) ) + ;;; Frees the body on the host. + ;;; + ;;; For streaming bodies, this is a _successful_ stream termination, which will signal + ;;; via framing that the body transfer is complete. (@interface func (export "close") (param $h $body_handle) (result $err (expected (error $fastly_status))) ) + + ;;; Frees a streaming body on the host _unsuccessfully_, so that framing makes clear that + ;;; the body is incomplete. + (@interface func (export "abandon") + (param $h $body_handle) + (result $err (expected (error $fastly_status))) + ) ) (module $fastly_log @@ -81,8 +92,7 @@ (@interface func (export "body_downstream_get") (result $err (expected (tuple $request_handle $body_handle) - (error $fastly_status)) - ) + (error $fastly_status))) ) (@interface func (export "cache_override_set") @@ -103,7 +113,7 @@ ) (@interface func (export "downstream_client_ip_addr") - ;;; must be a 16-byte array + ;; must be a 16-byte array (param $addr_octets_out (@witx pointer (@witx char8))) (result $err (expected $num_bytes (error $fastly_status))) ) @@ -122,6 +132,13 @@ (result $err (expected (error $fastly_status))) ) + (@interface func (export "downstream_client_oh_fingerprint") + (param $ohfp_out (@witx pointer (@witx char8))) + (param $ohfp_max_len (@witx usize)) + (param $nwritten_out (@witx pointer (@witx usize))) + (result $err (expected (error $fastly_status))) + ) + (@interface func (export "downstream_tls_cipher_openssl_name") (param $cipher_out (@witx pointer (@witx char8))) (param $cipher_max_len (@witx usize)) @@ -280,8 +297,29 @@ (param $backend string) (result $err (expected (tuple $response_handle $body_handle) - (error $fastly_status)) - ) + (error $fastly_status))) + ) + + ;; The behavior of this method is identical to the original except for the `$error_detail` + ;; out-parameter. + ;; + ;; If the returned `$fastly_status` is OK, `$error_detail` will not be read. Otherwise, + ;; the status is returned identically to the original `send`, but `$error_detail` is populated. + ;; Since `$send_error_detail` provides much more granular information about failures, it should + ;; be used by SDKs as the primary source of error information in favor of `$fastly_status`. + ;; + ;; Make sure to initialize `$error_detail` with the full complement of mask values that the + ;; guest supports. If the corresponding bits in the mask are not set, the host will not populate + ;; fields in the `$error_detail` struct even if there are values available for those fields. + ;; This allows forward compatibility when new fields are added. + (@interface func (export "send_v2") + (param $h $request_handle) + (param $b $body_handle) + (param $backend string) + (param $error_detail (@witx pointer $send_error_detail)) + (result $err (expected + (tuple $response_handle $body_handle) + (error $fastly_status))) ) (@interface func (export "send_async") @@ -289,8 +327,7 @@ (param $b $body_handle) (param $backend string) (result $err (expected $pending_request_handle - (error $fastly_status)) - ) + (error $fastly_status))) ) (@interface func (export "send_async_streaming") @@ -306,27 +343,60 @@ (tuple $is_done $response_handle $body_handle) - (error $fastly_status)) - ) + (error $fastly_status))) + ) + + ;; See `send_v2` for an explanation of the `$error_detail` out-parameter. + (@interface func (export "pending_req_poll_v2") + (param $h $pending_request_handle) + (param $error_detail (@witx pointer $send_error_detail)) + (result $err (expected + (tuple $is_done + $response_handle + $body_handle) + (error $fastly_status))) ) (@interface func (export "pending_req_wait") (param $h $pending_request_handle) (result $err (expected (tuple $response_handle $body_handle) - (error $fastly_status)) - ) + (error $fastly_status))) + ) + + ;; See `send_v2` for an explanation of the `$error_detail` out-parameter. + (@interface func (export "pending_req_wait_v2") + (param $h $pending_request_handle) + (param $error_detail (@witx pointer $send_error_detail)) + (result $err (expected + (tuple $response_handle $body_handle) + (error $fastly_status))) ) (@interface func (export "pending_req_select") (param $hs (list $pending_request_handle)) (result $err (expected (tuple $done_idx $response_handle $body_handle) - (error $fastly_status)) - ) + (error $fastly_status))) + ) + + ;; See `send_v2` for an explanation of the `$error_detail` out-parameter. + (@interface func (export "pending_req_select_v2") + (param $hs (list $pending_request_handle)) + (param $error_detail (@witx pointer $send_error_detail)) + (result $err (expected + (tuple $done_idx $response_handle $body_handle) + (error $fastly_status))) + ) + + ;;; Returns whether or not the original client request arrived with a + ;;; Fastly-Key belonging to a user with the rights to purge content on this + ;;; service. + (@interface func (export "fastly_key_is_valid") + (result $err (expected $is_valid (error $fastly_status))) ) - (@interface func(export "close") + (@interface func (export "close") (param $h $request_handle) (result $err (expected (error $fastly_status))) ) @@ -352,6 +422,18 @@ (result $err (expected (error $fastly_status))) ) + (@interface func (export "redirect_to_websocket_proxy_v2") + (param $h $request_handle) + (param $backend_name string) + (result $err (expected (error $fastly_status))) + ) + + (@interface func (export "redirect_to_grip_proxy_v2") + (param $h $request_handle) + (param $backend_name string) + (result $err (expected (error $fastly_status))) + ) + ;;; Adjust how this requests's framing headers are determined. (@interface func (export "framing_headers_mode_set") (param $h $request_handle) @@ -464,7 +546,7 @@ (result $err (expected (error $fastly_status))) ) - (@interface func(export "close") + (@interface func (export "close") (param $h $response_handle) (result $err (expected (error $fastly_status))) ) diff --git a/lib/compute-at-edge-abi/typenames.witx b/lib/compute-at-edge-abi/typenames.witx index 71a53f13..dedc2258 100644 --- a/lib/compute-at-edge-abi/typenames.witx +++ b/lib/compute-at-edge-abi/typenames.witx @@ -53,11 +53,9 @@ $httpinvalidstatus ;;; Limit exceeded ;;; - ;;; This is returned when an attempt to allocate a resource has exceeded the maximum number of + ;;; This is returned when an attempt to allocate a resource has exceeded the maximum number of ;;; resources permitted. For example, creating too many response handles. - $limitexceeded - ) -) + $limitexceeded)) ;;; A tag indicating HTTP protocol versions. (typename $http_version @@ -66,9 +64,7 @@ $http_10 $http_11 $h2 - $h3 - ) -) + $h3)) ;;; HTTP status codes. (typename $http_status u16) @@ -76,9 +72,7 @@ (typename $body_write_end (enum (@witx tag u32) $back - $front - ) -) + $front)) ;;; A handle to an HTTP request or response body. (typename $body_handle (handle)) @@ -126,13 +120,12 @@ $pass $ttl $stale_while_revalidate - $pci - ) -) + $pci)) (typename $num_bytes (@witx usize)) (typename $header_count u32) (typename $is_done u32) (typename $done_idx u32) +(typename $is_valid u32) (typename $inserted u32) (typename $ready_idx u32) @@ -184,6 +177,7 @@ $sni_hostname $dont_pool $client_cert + $grpc )) (typename $dynamic_backend_config @@ -260,3 +254,91 @@ (field $ret_buf_nwritten_out (@witx pointer (@witx usize))) ) ) + +(typename $send_error_detail_tag + (enum (@witx tag u32) + ;;; The $send_error_detail struct has not been populated. + $uninitialized + ;;; There was no send error. + $ok + ;;; The system encountered a timeout when trying to find an IP address for the backend + ;;; hostname. + $dns_timeout + ;;; The system encountered a DNS error when trying to find an IP address for the backend + ;;; hostname. The fields $dns_error_rcode and $dns_error_info_code may be set in the + ;;; $send_error_detail. + $dns_error + ;;; The system cannot determine which backend to use, or the specified backend was invalid. + $destination_not_found + ;;; The system considers the backend to be unavailable; e.g., recent attempts to communicate + ;;; with it may have failed, or a health check may indicate that it is down. + $destination_unavailable + ;;; The system cannot find a route to the next-hop IP address. + $destination_ip_unroutable + ;;; The system's connection to the backend was refused. + $connection_refused + ;;; The system's connection to the backend was closed before a complete response was + ;;; received. + $connection_terminated + ;;; The system's attempt to open a connection to the backend timed out. + $connection_timeout + ;;; The system is configured to limit the number of connections it has to the backend, and + ;;; that limit has been exceeded. + $connection_limit_reached + ;;; The system encountered an error when verifying the certificate presented by the backend. + $tls_certificate_error + ;;; The system encountered an error with the backend TLS configuration. + $tls_configuration_error + ;;; The system received an incomplete response to the request from the backend. + $http_incomplete_response + ;;; The system received a response to the request whose header section was considered too + ;;; large. + $http_response_header_section_too_large + ;;; The system received a response to the request whose body was considered too large. + $http_response_body_too_large + ;;; The system reached a configured time limit waiting for the complete response. + $http_response_timeout + ;;; The system received a response to the request whose status code or reason phrase was + ;;; invalid. + $http_response_status_invalid + ;;; The process of negotiating an upgrade of the HTTP version between the system and the + ;;; backend failed. + $http_upgrade_failed + ;;; The system encountered an HTTP protocol error when communicating with the backend. This + ;;; error will only be used when a more specific one is not defined. + $http_protocol_error + ;;; An invalid cache key was provided for the request. + $http_request_cache_key_invalid + ;;; An invalid URI was provided for the request. + $http_request_uri_invalid + ;;; The system encountered an unexpected internal error. + $internal_error + ;;; The system received a TLS alert from the backend. The field $tls_alert_id may be set in + ;;; the $send_error_detail. + $tls_alert_received + ;;; The system encountered a TLS error when communicating with the backend, either during + ;;; the handshake or afterwards. + $tls_protocol_error + )) + +;;; Mask representing which fields are understood by the guest, and which have been set by the host. +;;; +;;; When the guest calls hostcalls with a mask, it should set every bit in the mask that corresponds +;;; to a defined flag. This signals the host to write only to fields with a set bit, allowing +;;; forward compatibility for existing guest programs even after new fields are added to the struct. +(typename $send_error_detail_mask + (flags (@witx repr u32) + $reserved + $dns_error_rcode + $dns_error_info_code + $tls_alert_id + )) + +(typename $send_error_detail + (record + (field $tag $send_error_detail_tag) + (field $mask $send_error_detail_mask) + (field $dns_error_rcode u16) + (field $dns_error_info_code u16) + (field $tls_alert_id u8) + )) diff --git a/lib/src/wiggle_abi.rs b/lib/src/wiggle_abi.rs index d7b3e429..a0596643 100644 --- a/lib/src/wiggle_abi.rs +++ b/lib/src/wiggle_abi.rs @@ -74,7 +74,10 @@ wiggle::from_witx!({ fastly_async_io::{select}, fastly_object_store::{insert, lookup_async, pending_lookup_wait}, fastly_http_body::{append, read, write}, - fastly_http_req::{pending_req_select, pending_req_poll, pending_req_wait, send, send_async, send_async_streaming}, + fastly_http_req::{ + pending_req_select, pending_req_select_v2, pending_req_poll, pending_req_poll_v2, + pending_req_wait, pending_req_wait_v2, send, send_v2, send_async, send_async_streaming + }, } }); diff --git a/lib/src/wiggle_abi/body_impl.rs b/lib/src/wiggle_abi/body_impl.rs index 0ced72aa..f427dcdb 100644 --- a/lib/src/wiggle_abi/body_impl.rs +++ b/lib/src/wiggle_abi/body_impl.rs @@ -111,4 +111,9 @@ impl FastlyHttpBody for Session { Ok(self.drop_body(body_handle)?) } } + + fn abandon(&mut self, body_handle: BodyHandle) -> Result<(), Error> { + // Drop the body without a `finish` message + Ok(self.drop_body(body_handle)?) + } } diff --git a/lib/src/wiggle_abi/req_impl.rs b/lib/src/wiggle_abi/req_impl.rs index 173680be..e17e4ef0 100644 --- a/lib/src/wiggle_abi/req_impl.rs +++ b/lib/src/wiggle_abi/req_impl.rs @@ -1,5 +1,6 @@ //! fastly_req` hostcall implementations. +use super::types::SendErrorDetail; use super::SecretStoreError; use crate::config::ClientCertInfo; use crate::secret_store::SecretLookup; @@ -133,6 +134,15 @@ impl FastlyHttpReq for Session { Ok(()) } + fn downstream_client_oh_fingerprint<'a>( + &mut self, + _ohfp_out: &GuestPtr<'a, u8>, + _ohfp_max_len: u32, + _nwritten_out: &GuestPtr, + ) -> Result<(), Error> { + Err(Error::NotAvailable("Client original header fingerprint")) + } + #[allow(unused_variables)] // FIXME KTM 2020-06-25: Remove this directive once implemented. fn downstream_tls_cipher_openssl_name<'a>( &mut self, @@ -158,6 +168,22 @@ impl FastlyHttpReq for Session { Err(Error::NotAvailable("Redirect to Fanout/GRIP proxy")) } + fn redirect_to_websocket_proxy_v2<'a>( + &mut self, + _req_handle: RequestHandle, + _backend: &GuestPtr<'a, str>, + ) -> Result<(), Error> { + Err(Error::NotAvailable("Redirect to WebSocket proxy")) + } + + fn redirect_to_grip_proxy_v2<'a>( + &mut self, + _req_handle: RequestHandle, + _backend: &GuestPtr<'a, str>, + ) -> Result<(), Error> { + Err(Error::NotAvailable("Redirect to Fanout/GRIP proxy")) + } + #[allow(unused_variables)] // FIXME KTM 2020-06-25: Remove this directive once implemented. fn downstream_tls_protocol<'a>( &mut self, @@ -614,6 +640,17 @@ impl FastlyHttpReq for Session { Ok(self.insert_response(resp)) } + async fn send_v2<'a>( + &mut self, + req_handle: RequestHandle, + body_handle: BodyHandle, + backend_bytes: &GuestPtr<'a, str>, + _error_detail: &GuestPtr<'a, SendErrorDetail>, + ) -> Result<(ResponseHandle, BodyHandle), Error> { + // This initial implementation ignores the error detail field + self.send(req_handle, body_handle, backend_bytes).await + } + async fn send_async<'a>( &mut self, req_handle: RequestHandle, @@ -688,6 +725,15 @@ impl FastlyHttpReq for Session { } } + async fn pending_req_poll_v2<'a>( + &mut self, + pending_req_handle: PendingRequestHandle, + _error_detail: &GuestPtr<'a, SendErrorDetail>, + ) -> Result<(u32, ResponseHandle, BodyHandle), Error> { + // This initial implementation ignores the error detail field + self.pending_req_poll(pending_req_handle).await + } + async fn pending_req_wait( &mut self, pending_req_handle: PendingRequestHandle, @@ -699,6 +745,15 @@ impl FastlyHttpReq for Session { Ok(self.insert_response(pending_req)) } + async fn pending_req_wait_v2<'a>( + &mut self, + pending_req_handle: PendingRequestHandle, + _error_detail: &GuestPtr<'a, SendErrorDetail>, + ) -> Result<(ResponseHandle, BodyHandle), Error> { + // This initial implementation ignores the error detail field + self.pending_req_wait(pending_req_handle).await + } + // First element of return tuple is the "done index" async fn pending_req_select<'a>( &mut self, @@ -746,6 +801,19 @@ impl FastlyHttpReq for Session { Ok(outcome) } + async fn pending_req_select_v2<'a>( + &mut self, + pending_req_handles: &GuestPtr<'a, [PendingRequestHandle]>, + _error_detail: &GuestPtr<'a, SendErrorDetail>, + ) -> Result<(u32, ResponseHandle, BodyHandle), Error> { + // This initial implementation ignores the error detail field + self.pending_req_select(pending_req_handles).await + } + + fn fastly_key_is_valid(&mut self) -> Result { + Err(Error::NotAvailable("FASTLY_KEY is valid")) + } + fn close(&mut self, req_handle: RequestHandle) -> Result<(), Error> { // We don't do anything with the parts, but we do pass the error up if // the handle given doesn't exist