redevplugin-worker-sdk 2.0.6

Rust SDK for ReDevPlugin WASM workers
Documentation
use crate::error::{Error, Result};
use serde::Deserialize;

pub const IO_FLAG_EOF: u32 = 1 << 0;
pub const IO_FLAG_TEXT: u32 = 1 << 1;
pub const IO_FLAG_BINARY: u32 = 1 << 2;
pub const IO_FLAG_MESSAGE_END: u32 = 1 << 3;
pub const IO_FLAG_DATAGRAM_END: u32 = 1 << 4;
pub const MAX_IO_CHUNK_BYTES: usize = 64 * 1024;

#[link(wasm_import_module = "redevplugin.io")]
unsafe extern "C" {
    #[link_name = "rdp_call_v1"]
    fn rdp_call_v1(
        request_ptr: i32,
        request_len: i32,
        response_ptr: i32,
        response_capacity: i32,
    ) -> i32;
    #[link_name = "rdp_read_v1"]
    fn rdp_read_v1(
        handle: i64,
        destination_ptr: i32,
        destination_capacity: i32,
        flags_ptr: i32,
    ) -> i32;
    #[link_name = "rdp_write_v1"]
    fn rdp_write_v1(handle: i64, source_ptr: i32, source_len: i32, flags: i32) -> i32;
    #[link_name = "rdp_seek_v1"]
    fn rdp_seek_v1(handle: i64, offset: i64, whence: i32) -> i64;
    #[link_name = "rdp_close_v1"]
    fn rdp_close_v1(handle: i64) -> i32;
    #[link_name = "rdp_last_error_v1"]
    fn rdp_last_error_v1(response_ptr: i32, response_capacity: i32) -> i32;
}

#[derive(Debug)]
pub(crate) struct Handle {
    value: Option<u64>,
}

impl Handle {
    pub(crate) fn new(value: u64) -> Result<Self> {
        if value == 0 || value > i64::MAX as u64 {
            return Err(Error::internal("Host returned an invalid resource handle"));
        }
        Ok(Self { value: Some(value) })
    }

    pub fn id(&self) -> u64 {
        self.value.unwrap_or(0)
    }

    pub(crate) fn disarm(&mut self) {
        self.value = None;
    }

    pub(crate) fn read(&mut self, capacity: usize) -> Result<(Vec<u8>, u32)> {
        if capacity == 0 || capacity > MAX_IO_CHUNK_BYTES {
            return Err(Error::from_abi_status(-1));
        }
        let handle = self.require_open()?;
        let mut bytes = vec![0_u8; capacity];
        let mut flags = 0_u32;
        let status = unsafe {
            rdp_read_v1(
                handle as i64,
                bytes.as_mut_ptr() as i32,
                bytes.len() as i32,
                (&mut flags as *mut u32) as i32,
            )
        };
        let written = status_result(status)?;
        if written > bytes.len() {
            return Err(Error::internal("Host returned an oversized read result"));
        }
        bytes.truncate(written);
        Ok((bytes, flags))
    }

    pub(crate) fn write(&mut self, source: &[u8], flags: u32) -> Result<()> {
        if source.len() > MAX_IO_CHUNK_BYTES {
            return Err(Error::from_abi_status(-11));
        }
        let handle = self.require_open()?;
        let status = unsafe {
            rdp_write_v1(
                handle as i64,
                source.as_ptr() as i32,
                source.len() as i32,
                flags as i32,
            )
        };
        let written = status_result(status)?;
        if written != source.len() {
            return Err(Error::internal("Host returned a forbidden partial write"));
        }
        Ok(())
    }

    pub(crate) fn seek(&mut self, offset: i64, whence: u32) -> Result<u64> {
        let handle = self.require_open()?;
        let status = unsafe { rdp_seek_v1(handle as i64, offset, whence as i32) };
        if status < 0 {
            return Err(last_error(status as i32));
        }
        Ok(status as u64)
    }

    pub(crate) fn close(&mut self) -> Result<()> {
        let Some(handle) = self.value.take() else {
            return Ok(());
        };
        let status = unsafe { rdp_close_v1(handle as i64) };
        status_result(status).map(|_| ())
    }

    fn require_open(&self) -> Result<u64> {
        self.value.ok_or_else(|| Error::from_abi_status(-5))
    }
}

impl Drop for Handle {
    fn drop(&mut self) {
        let _ = self.close();
    }
}

pub(crate) fn call_control_raw(request: &[u8]) -> Result<Vec<u8>> {
    if request.is_empty() || request.len() > MAX_IO_CHUNK_BYTES {
        return Err(Error::from_abi_status(-1));
    }
    let mut response = vec![0_u8; MAX_IO_CHUNK_BYTES];
    let status = unsafe {
        rdp_call_v1(
            request.as_ptr() as i32,
            request.len() as i32,
            response.as_mut_ptr() as i32,
            response.len() as i32,
        )
    };
    let written = status_result(status)?;
    if written > response.len() {
        return Err(Error::internal(
            "Host returned an oversized control response",
        ));
    }
    response.truncate(written);
    Ok(response)
}

fn status_result(status: i32) -> Result<usize> {
    if status < 0 {
        return Err(last_error(status));
    }
    usize::try_from(status).map_err(|_| Error::internal("ABI result does not fit usize"))
}

fn last_error(status: i32) -> Error {
    let mut response = vec![0_u8; MAX_IO_CHUNK_BYTES];
    let written = unsafe { rdp_last_error_v1(response.as_mut_ptr() as i32, response.len() as i32) };
    if written > 0 && (written as usize) <= response.len() {
        response.truncate(written as usize);
        #[derive(Deserialize)]
        struct LastError {
            code: crate::error::ErrorCode,
            message: String,
            #[serde(default)]
            retryable: bool,
            #[serde(default)]
            details: serde_json::Value,
        }
        if let Ok(error) = serde_json::from_slice::<LastError>(&response) {
            return Error {
                code: error.code,
                message: error.message,
                retryable: error.retryable,
                details: error.details,
            };
        }
    }
    Error::from_abi_status(status)
}