auc-tool 0.1.0

Machine-local software passkey authenticator exposed through Linux UHID.
Documentation
use std::io::{Read, Write};
use std::os::unix::net::UnixStream;
use std::path::{Path, PathBuf};
use std::time::Duration;

use super::protocol::{
    ApplicationError, ApplicationRequest, ApplicationResponse, PROTOCOL_MAJOR, RequestEnvelope,
    RequestId, ResponseBody, ResponseEnvelope, decode, encode,
};

#[derive(Clone, Debug)]
pub struct ApplicationClient {
    socket_path: PathBuf,
    timeout: Duration,
}

impl ApplicationClient {
    pub fn new(socket_path: impl Into<PathBuf>) -> Self {
        Self {
            socket_path: socket_path.into(),
            timeout: Duration::from_secs(30),
        }
    }

    pub fn socket_path(&self) -> &Path {
        &self.socket_path
    }

    pub fn request(
        &self,
        request: ApplicationRequest,
    ) -> Result<ApplicationResponse, ApplicationError> {
        let request_id = RequestId::random();
        let payload = encode(&RequestEnvelope {
            request_id,
            minimum_protocol_major: PROTOCOL_MAJOR,
            maximum_protocol_major: PROTOCOL_MAJOR,
            request,
        })?;
        let mut stream = UnixStream::connect(&self.socket_path)?;
        stream.set_read_timeout(Some(self.timeout))?;
        stream.set_write_timeout(Some(self.timeout))?;
        stream.write_all(&(payload.len() as u32).to_be_bytes())?;
        stream.write_all(&payload)?;
        stream.flush()?;
        let response: ResponseEnvelope = decode(&read_frame(&mut stream)?)?;
        if response.request_id != request_id {
            return Err(ApplicationError::MismatchedRequestId);
        }
        if response.protocol_major != PROTOCOL_MAJOR {
            return Err(ApplicationError::UnsupportedProtocol(
                response.protocol_major,
            ));
        }
        match response.body {
            ResponseBody::Ok(response) => Ok(response),
            ResponseBody::Error(error) => Err(ApplicationError::Remote {
                code: error.code,
                message: error.message,
            }),
        }
    }
}

fn read_frame(stream: &mut UnixStream) -> Result<Vec<u8>, ApplicationError> {
    let mut header = [0_u8; 4];
    read_exact(stream, &mut header)?;
    let length = u32::from_be_bytes(header) as usize;
    if length > super::protocol::MAX_FRAME_BYTES {
        return Err(ApplicationError::FrameTooLarge);
    }
    let mut payload = vec![0_u8; length];
    read_exact(stream, &mut payload)?;
    Ok(payload)
}

fn read_exact(stream: &mut UnixStream, buffer: &mut [u8]) -> Result<(), ApplicationError> {
    match stream.read_exact(buffer) {
        Ok(()) => Ok(()),
        Err(error) if error.kind() == std::io::ErrorKind::UnexpectedEof => {
            Err(ApplicationError::EarlyEof)
        }
        Err(error) => Err(error.into()),
    }
}