viceroy-lib 0.17.0

Viceroy implementation details.
Documentation
use std::time::Duration;

use crate::component::bindings::fastly::compute::{http_body, http_downstream, http_req, types};
use crate::component::compute::headers::get_names;
use crate::error::Error;
use crate::linking::{ComponentCtx, SessionView};
use crate::session::Session;
use crate::wiggle_abi::types::RequestPromiseHandle;
use wasmtime::component::Resource;
use wasmtime_wasi_io::IoView;

impl http_downstream::Host for ComponentCtx {
    async fn next_request(
        &mut self,
        options: http_downstream::NextRequestOptions,
    ) -> Result<Resource<http_downstream::PendingRequest>, types::Error> {
        let timeout = options.timeout_ms.map(Duration::from_millis);
        let handle = self
            .session_mut()
            .register_pending_downstream_req(timeout)
            .await?;

        let handle = RequestPromiseHandle::from(handle);

        Ok(handle.into())
    }

    async fn await_request(
        &mut self,
        handle: Resource<http_downstream::PendingRequest>,
    ) -> Result<Option<(Resource<http_req::Request>, Resource<http_body::Body>)>, types::Error>
    {
        let handle = RequestPromiseHandle::from(handle).into();
        let Some((req, body)) = self.session_mut().await_downstream_req(handle).await? else {
            return Ok(None);
        };

        Ok(Some((req.into(), body.into())))
    }

    fn downstream_client_ip_addr(
        &mut self,
        h: Resource<http_req::Request>,
    ) -> Option<types::IpAddress> {
        self.session()
            .downstream_client_ip(h.into())
            .ok()?
            .map(|ip| ip.into())
    }

    fn downstream_server_ip_addr(
        &mut self,
        h: Resource<http_req::Request>,
    ) -> Option<types::IpAddress> {
        self.session()
            .downstream_server_ip(h.into())
            .ok()?
            .map(|ip| ip.into())
    }

    fn downstream_client_ddos_detected(
        &mut self,
        _h: Resource<http_req::Request>,
    ) -> Result<bool, types::Error> {
        Ok(false)
    }

    fn downstream_tls_cipher_openssl_name(
        &mut self,
        h: Resource<http_req::Request>,
        _max_len: u64,
    ) -> Result<Option<Vec<u8>>, types::Error> {
        Ok(self.session().absent_metadata_value(h)?)
    }

    fn downstream_tls_protocol(
        &mut self,
        h: Resource<http_req::Request>,
        _max_len: u64,
    ) -> Result<Option<Vec<u8>>, types::Error> {
        Ok(self.session().absent_metadata_value(h)?)
    }

    fn downstream_tls_client_servername(
        &mut self,
        h: Resource<http_req::Request>,
        _max_len: u64,
    ) -> Result<Option<String>, types::Error> {
        self.session().absent_metadata_value(h).map_err(Into::into)
    }

    fn downstream_tls_client_hello(
        &mut self,
        h: Resource<http_req::Request>,
        _max_len: u64,
    ) -> Result<Option<Vec<u8>>, types::Error> {
        Ok(self.session().absent_metadata_value(h)?)
    }

    fn downstream_tls_raw_client_certificate(
        &mut self,
        h: Resource<http_req::Request>,
        _max_len: u64,
    ) -> Result<Option<Vec<u8>>, types::Error> {
        Ok(self.session().absent_metadata_value(h)?)
    }

    fn downstream_tls_client_cert_verify_result(
        &mut self,
        h: Resource<http_req::Request>,
    ) -> Result<Option<http_req::ClientCertVerifyResult>, types::Error> {
        Ok(self.session().absent_metadata_value(h)?)
    }

    fn downstream_tls_ja3_md5(
        &mut self,
        h: Resource<http_req::Request>,
    ) -> Result<Option<Vec<u8>>, types::Error> {
        Ok(self.session().absent_metadata_value(h)?)
    }

    fn downstream_client_h2_fingerprint(
        &mut self,
        h: Resource<http_req::Request>,
        _max_len: u64,
    ) -> Result<String, types::Error> {
        Ok(self
            .session()
            .absent_metadata_value(h)?
            .ok_or(Error::MissingDownstreamMetadata)?)
    }

    fn downstream_client_request_id(
        &mut self,
        h: Resource<http_req::Request>,
        max_len: u64,
    ) -> Result<String, types::Error> {
        let reqid = self
            .session()
            .downstream_request_id(h.into())?
            .ok_or(Error::MissingDownstreamMetadata)?;
        let result = format!("{:032x}", reqid);

        if result.len() > usize::try_from(max_len).unwrap() {
            return Err(types::Error::BufferLen(
                u64::try_from(result.len()).unwrap(),
            ));
        }

        Ok(result)
    }

    fn downstream_client_oh_fingerprint(
        &mut self,
        h: Resource<http_req::Request>,
        _max_len: u64,
    ) -> Result<String, types::Error> {
        Ok(self
            .session()
            .absent_metadata_value(h)?
            .ok_or(Error::MissingDownstreamMetadata)?)
    }

    fn downstream_tls_ja4(
        &mut self,
        h: Resource<http_req::Request>,
        _max_len: u64,
    ) -> Result<Option<String>, types::Error> {
        Ok(self.session().absent_metadata_value(h)?)
    }

    fn downstream_compliance_region(
        &mut self,
        h: Resource<http_req::Request>,
        region_max_len: u64,
    ) -> Result<Option<String>, types::Error> {
        let region = Session::downstream_compliance_region(self.session(), h.into())?
            .ok_or(Error::MissingDownstreamMetadata)?;
        let region_len = region.len();

        match u64::try_from(region_len) {
            Ok(region_len) if region_len <= region_max_len => Ok(Some(region.to_owned())),
            too_large => Err(types::Error::BufferLen(too_large.unwrap_or(0))),
        }
    }

    fn downstream_original_header_names(
        &mut self,
        h: Resource<http_req::Request>,
        max_len: u64,
        cursor: u32,
    ) -> Result<(String, Option<u32>), types::Error> {
        let headers = self
            .session()
            .downstream_original_headers(h.into())?
            .ok_or(Error::MissingDownstreamMetadata)?;
        let res = get_names(headers.keys(), max_len, cursor)?;

        Ok(res)
    }

    fn downstream_original_header_count(
        &mut self,
        h: Resource<http_req::Request>,
    ) -> Result<u32, types::Error> {
        Ok(self
            .session()
            .downstream_original_headers(h.into())?
            .ok_or(Error::MissingDownstreamMetadata)?
            .len()
            .try_into()
            .expect("More than u32::MAX headers"))
    }

    fn fastly_key_is_valid(
        &mut self,
        h: Resource<http_req::Request>,
    ) -> Result<bool, types::Error> {
        self.session()
            .check_fastly_key(h.into())
            .map_err(Into::into)
    }

    fn downstream_bot_analyzed(
        &mut self,
        _h: Resource<http_req::Request>,
    ) -> Result<bool, types::Error> {
        Ok(false)
    }

    fn downstream_bot_detected(
        &mut self,
        _h: Resource<http_req::Request>,
    ) -> Result<bool, types::Error> {
        Ok(false)
    }

    fn downstream_bot_name(
        &mut self,
        _h: Resource<http_req::Request>,
        _max_len: u64,
    ) -> Result<Option<String>, types::Error> {
        Ok(None)
    }

    fn downstream_bot_category(
        &mut self,
        _h: Resource<http_req::Request>,
        _max_len: u64,
    ) -> Result<Option<String>, types::Error> {
        Ok(None)
    }

    fn downstream_bot_category_kind(
        &mut self,
        _h: Resource<http_req::Request>,
    ) -> Result<Option<http_downstream::BotCategory>, types::Error> {
        Ok(None)
    }

    fn downstream_bot_verified(
        &mut self,
        _h: Resource<http_req::Request>,
    ) -> Result<Option<bool>, types::Error> {
        Ok(None)
    }

    fn downstream_resvpnproxy_is_anonymous(
        &mut self,
        _h: Resource<http_req::Request>,
    ) -> Result<Option<bool>, types::Error> {
        Ok(None)
    }

    fn downstream_resvpnproxy_is_anonymous_vpn(
        &mut self,
        _h: Resource<http_req::Request>,
    ) -> Result<Option<bool>, types::Error> {
        Ok(None)
    }

    fn downstream_resvpnproxy_is_hosting_provider(
        &mut self,
        _h: Resource<http_req::Request>,
    ) -> Result<Option<bool>, types::Error> {
        Ok(None)
    }

    fn downstream_resvpnproxy_is_proxy_over_vpn(
        &mut self,
        _h: Resource<http_req::Request>,
    ) -> Result<Option<bool>, types::Error> {
        Ok(None)
    }

    fn downstream_resvpnproxy_is_public_proxy(
        &mut self,
        _h: Resource<http_req::Request>,
    ) -> Result<Option<bool>, types::Error> {
        Ok(None)
    }

    fn downstream_resvpnproxy_is_relay_proxy(
        &mut self,
        _h: Resource<http_req::Request>,
    ) -> Result<Option<bool>, types::Error> {
        Ok(None)
    }

    fn downstream_resvpnproxy_is_residential_proxy(
        &mut self,
        _h: Resource<http_req::Request>,
    ) -> Result<Option<bool>, types::Error> {
        Ok(None)
    }

    fn downstream_resvpnproxy_is_smart_dns_proxy(
        &mut self,
        _h: Resource<http_req::Request>,
    ) -> Result<Option<bool>, types::Error> {
        Ok(None)
    }

    fn downstream_resvpnproxy_is_tor_exit_node(
        &mut self,
        _h: Resource<http_req::Request>,
    ) -> Result<Option<bool>, types::Error> {
        Ok(None)
    }

    fn downstream_resvpnproxy_is_vpn_datacenter(
        &mut self,
        _h: Resource<http_req::Request>,
    ) -> Result<Option<bool>, types::Error> {
        Ok(None)
    }

    fn downstream_resvpnproxy_vpn_service_name(
        &mut self,
        _h: Resource<http_req::Request>,
        _max_len: u64,
    ) -> Result<Option<String>, types::Error> {
        Ok(None)
    }
}

impl http_downstream::HostExtraNextRequestOptions for ComponentCtx {
    fn drop(
        &mut self,
        _options: Resource<http_downstream::ExtraNextRequestOptions>,
    ) -> wasmtime::Result<()> {
        Ok(())
    }
}

pub struct ExtraBotCategory {
    raw: u32,
}

impl http_downstream::HostExtraBotCategory for ComponentCtx {
    fn as_raw(&mut self, h: wasmtime::component::Resource<ExtraBotCategory>) -> u32 {
        self.table().get(&h).map(|r| r.raw).unwrap_or_default()
    }

    fn drop(&mut self, h: wasmtime::component::Resource<ExtraBotCategory>) -> wasmtime::Result<()> {
        self.table().delete(h)?;
        Ok(())
    }
}

pub(in super::super) trait MetadataView {
    /// Stub for metadata that Viceroy does not support.
    ///
    /// Validates the handle normally, but always returns `Ok(None)` rather than a meaningful value.
    fn absent_metadata_value<T>(
        &self,
        handle: Resource<http_req::Request>,
    ) -> Result<Option<T>, Error>;
}
impl MetadataView for Session {
    fn absent_metadata_value<T>(
        &self,
        handle: Resource<http_req::Request>,
    ) -> Result<Option<T>, Error> {
        let _ = self
            .downstream_metadata(handle.into())?
            .ok_or(Error::MissingDownstreamMetadata)?;
        Ok(None)
    }
}