oc2_hlapi_v2 0.2.0

A client for the new version of OpenComputers II's HLAPI
Documentation
//! The low-level structures representing HLAPI messages

use crc::{CRC_32_ISO_HDLC, Crc};
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use thiserror::Error;
use uuid::Uuid;

/// The description of a blob in a method invocation or result message
#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
pub struct BlobDescriptor {
    /// The length of the blob's data
    pub length: u32,
    /// The ISO-HDLC CRC32 of the blob's data
    pub checksum: u32,
}

/// The error returned by [`BlobDescriptor::describe`] when a blob greater than the maximum size is
/// passed in
#[derive(Clone, Copy, Debug, Error)]
#[error("Blob too big")]
pub struct BlobTooBig;

impl BlobDescriptor {
    const CRC: Crc<u32> = Crc::<u32>::new(&CRC_32_ISO_HDLC);

    /// Describes the given blob. The maximum size is 4GiB, as the size is a u32 per the spec.
    pub fn describe(blob: &[u8]) -> Result<Self, BlobTooBig> {
        let crc = Self::CRC.checksum(blob);
        let len = blob.len().try_into().map_err(|_| BlobTooBig)?;
        Ok(Self {
            length: len,
            checksum: crc,
        })
    }

    #[must_use]
    /// Verifies the checksum for the given blob
    pub fn verify_checksum(&self, blob: &[u8]) -> bool {
        let crc = Self::CRC.checksum(blob);
        crc == self.checksum
    }
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
/// A blob marker in an HLAPI data value (method parameter or return value)
pub struct BlobMarker {
    #[serde(rename = "$blob")]
    /// The index of the blob in the array of blobs associated with the call or return.
    pub blob_idx: u32,
}

/// An HLAPI request
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Request {
    /// The request's ID, if present
    #[serde(skip_serializing_if = "Option::is_none")]
    pub id: Option<u32>,
    #[serde(flatten)]
    /// The specific request
    pub data: RequestData,
}

/// The specific fields of each HLAPI request type
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(tag = "type")]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub enum RequestData {
    /// A request to list the devices on the bus
    List,
    /// A request to list the methods of a device
    Methods {
        #[serde(rename = "data")]
        /// The UUID of the device to get the methods of
        uuid: Uuid,
    },
    /// A request to invoke a method
    Invoke {
        #[serde(rename = "data")]
        /// The details of the method invocation
        invocation: MethodInvocation,
        /// The descriptors for the invocation's blobs, if present
        #[serde(skip_serializing_if = "Option::is_none")]
        blobs: Option<Vec<BlobDescriptor>>,
    },
}

/// An HLAPI method invocation
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct MethodInvocation {
    /// The device to invoke the method on
    pub device_id: Uuid,
    /// The name of the method to invoke
    pub name: String,
    /// The method's parameters, if present
    #[serde(skip_serializing_if = "Option::is_none")]
    pub parameters: Option<Vec<JsonValue>>,
}

/// An HLAPI response
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Response {
    /// The ID of the request this response was for, or 0 if the request had no ID. Also 0 for
    /// errors that cannot be associated with a specific message.
    // busd will send no ID field if the request had none, not 0. Deserialize that
    // as 0 like the host sends.
    #[serde(default)]
    pub id: u32,
    #[serde(rename = "gen")]
    /// The current bus generation. Cached replies to `list` and `methods` should be invalidated if
    /// this value changes
    pub generation: Option<u32>,
    #[serde(flatten)]
    /// The specific response
    pub data: ResponseData,
}

/// The specific fields of each HLAPI response type
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(tag = "type")]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub enum ResponseData {
    /// A list of the devices on the bus. The response to this can be cached until the `generation`
    /// field of the responses change.
    List {
        #[serde(rename = "data")]
        /// The list of devices
        device_list: Vec<DeviceInfo>,
    },
    /// A list of the methods for a device. The response to this can be cached until the `generation`
    /// field of the responses change.
    Methods {
        #[serde(rename = "data")]
        /// The list of methods for the device
        device_methods: Vec<MethodInfo>,
    },
    /// The result of an invoked method
    Result {
        #[serde(rename = "data")]
        /// The return value, if present
        #[serde(skip_serializing_if = "Option::is_none")]
        return_value: Option<JsonValue>,
        /// The descriptors for the return value's blobs, if present
        #[serde(skip_serializing_if = "Option::is_none")]
        blobs: Option<Vec<BlobDescriptor>>,
    },
    /// An HLAPI error
    Error {
        #[serde(rename = "data")]
        /// The HLAPI error message. The exact text of the message is not stable, do not attempt to
        /// determine error type programmatically.
        msg: String,
    },
}

/// Info about an HLAPI device
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct DeviceInfo {
    /// The ID of this device
    pub device_id: Uuid,
    /// The names of this device
    pub type_names: Vec<String>,
}

/// Info about a method of an HLAPI device
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct MethodInfo {
    /// The name of this method
    pub name: String,
    /// The return type of this method, if present. `byte[]` indicates a blob.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub return_type: Option<String>,
    /// The description of this method, if present
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
    /// The description of this method's return value, if present
    #[serde(skip_serializing_if = "Option::is_none")]
    pub return_value_description: Option<String>,
    /// The parameters of this method, if present
    #[serde(skip_serializing_if = "Option::is_none")]
    pub parameters: Option<Vec<MethodParameterInfo>>,
}

/// Info about a parameter of a method of an HLAPI device
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct MethodParameterInfo {
    /// The name of this parameter, if present
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    /// The description of this parameter, if present
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
    #[serde(rename = "type")]
    /// The type of this parameter, if present. `byte[]` indicates a blob.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub typ: Option<String>,
}

/// An HLAPI event fired by a device
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct DeviceEvent {
    #[serde(rename = "type")]
    /// The type of this event
    pub typ: String,
    /// The UUID of the device that sent this event
    pub device_id: Uuid,
    /// The data of this event, if present
    #[serde(skip_serializing_if = "Option::is_none")]
    pub data: Option<JsonValue>,
}

/// An HLAPI event
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Event {
    /// The current bus generation. Cached replies to `list` and `methods` should be invalidated if
    /// this value changes
    #[serde(rename = "gen")]
    pub generation: u32,
    #[serde(flatten)]
    /// The specific event
    pub data: EventData,
}

/// The specific fields of each HLAPI event type
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(tag = "type")]
#[serde(rename_all = "camelCase")]
#[serde(rename_all_fields = "camelCase")]
#[non_exhaustive]
pub enum EventData {
    /// Sent on a bus rebuild to notify of a generation change
    DevicesChanged,
    /// Sent when the event queue fills up and events get dropped. As a dropped event may have been
    /// a devices changed event, the `list` and `methods` caches should be considered outdated until
    /// either another event or a response is received.
    EventsDropped {
        /// The total number of events dropped since either the last `EventsDropped` event, or if this
        /// is the first `EventsDropped` event, the start of the session.
        #[serde(rename = "data")]
        events_dropped: u32,
    },
    #[serde(untagged)]
    /// Sent when a device fires an event
    Device(DeviceEvent),
    #[serde(untagged)]
    /// Some other, unknown, event
    Other(JsonValue),
}