oc2_hlapi_v2 0.2.0

A client for the new version of OpenComputers II's HLAPI
Documentation
//! Base HLAPI transports
//!
//! These transports provide the standard HLAPI connections as provided by base OC2.
//!
//! Most programs will want to use the [`BusdClient`] transport to connect to the HLAPI.
//!
//! The [`LinuxVirtio`] transport (Linux only) connects directly to the HLAPI virtio ports, providing lower
//! latency and removing the requirement that the bus daemon be running. However, if that transport
//! is used, no other program can access the HLAPI while your program is running.

#[cfg(target_os = "linux")]
mod linux;

#[cfg(target_os = "linux")]
pub use linux::*;

use std::{
    error::Error,
    fmt::Debug,
    io::{self, Read, Write},
};

use serde::{Deserialize, Serialize};
use thiserror::Error;

use crate::Transport;
use crate::codecs::BaseStreams;

/// Errors produced by the OC2 bus multiplexer daemon client
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum BusdClientError<E: Error> {
    /// An I/O error
    #[error("I/O error: {0}")]
    Io(#[from] io::Error),
    /// An error produced by the transport
    #[error("Transport error: {0}")]
    Transport(#[source] E),
    /// Read an invalid hello message from the daemon
    #[error("Bad hello message ({0})")]
    BadHello(serde_json::Error),
    /// Got an error after sending the hello message
    #[error("Error connecting to the bus daemon: {0}")]
    ErrorOnHello(String),
}

#[derive(Debug, Serialize)]
struct BusdClientHello<'a> {
    #[serde(rename = "type")]
    typ: &'a str,
    version: u32,
    roles: &'a [&'a str],
}

#[derive(Debug, Deserialize)]
#[allow(unused)]
struct BusdServerMessage {
    #[serde(rename = "gen")]
    generation: Option<u32>,
    #[serde(flatten)]
    data: BusdServerMessageData,
}

#[derive(Debug, Deserialize)]
#[allow(unused)]
#[serde(tag = "type")]
#[serde(rename_all = "camelCase")]
enum BusdServerMessageData {
    Hello {
        version: u32,
        token: String,
    },
    Error {
        #[serde(rename = "data")]
        msg: String,
    },
}

/// The stream to the OC2 bus multiplexer daemon
#[derive(Debug)]
#[repr(transparent)]
pub struct BusdStream<T>(pub T);

/// The client for the OC2 bus multiplexer
///
/// This transport connects to the OC2 bus multiplexer daemon to access the HLAPI, allowing the bus
/// to be shared with other programs.
#[derive(Debug)]
pub struct BusdClient<S, T: Transport<Streams = BusdStream<S>>> {
    transport: T,
}

impl<S, T: Transport<Streams = BusdStream<S>>> BusdClient<S, T> {
    #[must_use]
    /// Creates a new client using the given transport to connect to the daemon
    pub fn new(transport: T) -> Self {
        Self { transport }
    }
}

impl<S: Read + Write, T: Transport<Streams = BusdStream<S>>> Transport for BusdClient<S, T> {
    type Streams = BaseStreams<S>;
    type Error = BusdClientError<T::Error>;
    fn open_streams(&mut self) -> Result<Self::Streams, Self::Error> {
        let mut rpc_stream = self
            .transport
            .open_streams()
            .map_err(BusdClientError::Transport)?
            .0;
        rpc_stream.write_all(b"R\0")?;
        rpc_stream.write_all(
            &serde_json::to_vec(&BusdClientHello {
                typ: "hello",
                version: 1,
                roles: &["rpc", "blob", "event"],
            })
            .unwrap(),
        )?;
        rpc_stream.write_all(b"\0")?;
        let mut null_buf = [0; 1];
        // Discard the null byte at the start of the reply frame
        rpc_stream.read_exact(&mut null_buf)?;
        let mut de = serde_json::Deserializer::from_reader(&mut rpc_stream);
        let server_msg =
            BusdServerMessage::deserialize(&mut de).map_err(BusdClientError::BadHello)?;
        // Discard the null byte at the end of the reply frame
        rpc_stream.read_exact(&mut null_buf)?;
        match server_msg.data {
            BusdServerMessageData::Hello { version: _, token } => {
                let mut blob_stream = self
                    .transport
                    .open_streams()
                    .map_err(BusdClientError::Transport)?
                    .0;
                blob_stream.write_all(b"B")?;
                blob_stream.write_all(token.as_bytes())?;
                let mut event_stream = self
                    .transport
                    .open_streams()
                    .map_err(BusdClientError::Transport)?
                    .0;
                event_stream.write_all(b"E")?;
                event_stream.write_all(token.as_bytes())?;
                Ok(BaseStreams {
                    rpc: rpc_stream,
                    blob: blob_stream,
                    event: event_stream,
                })
            }
            BusdServerMessageData::Error { msg } => Err(BusdClientError::ErrorOnHello(msg)),
        }
    }
}