use super::ffi::FidoDevIo;
use super::hid::{ReportChannel, REPORT_LEN};
use anyhow::{bail, Context as _, Result};
use std::ffi::{c_char, c_int, c_void, CStr};
use std::io::{Read, Write};
use std::os::unix::net::UnixStream;
use std::time::Duration;
pub const SOCKET_ENV: &str = "YB_FIDO2_SOCKET";
pub fn socket_path() -> Option<String> {
std::env::var(SOCKET_ENV).ok().filter(|p| !p.is_empty())
}
unsafe extern "C" fn io_open(path: *const c_char) -> *mut c_void {
let Ok(path) = CStr::from_ptr(path).to_str() else {
return std::ptr::null_mut();
};
match UnixStream::connect(path) {
Ok(stream) => Box::into_raw(Box::new(stream)).cast(),
Err(_) => std::ptr::null_mut(),
}
}
unsafe extern "C" fn io_close(handle: *mut c_void) {
if !handle.is_null() {
drop(Box::from_raw(handle.cast::<UnixStream>()));
}
}
unsafe extern "C" fn io_write(handle: *mut c_void, buf: *const u8, len: usize) -> c_int {
let stream = &mut *handle.cast::<UnixStream>();
let data = std::slice::from_raw_parts(buf, len);
match stream.write_all(data) {
Ok(()) => len as c_int,
Err(_) => -1,
}
}
unsafe extern "C" fn io_read(handle: *mut c_void, buf: *mut u8, len: usize, ms: c_int) -> c_int {
let stream = &mut *handle.cast::<UnixStream>();
let timeout = (ms > 0).then(|| Duration::from_millis(ms as u64));
if stream.set_read_timeout(timeout).is_err() {
return -1;
}
let mut report = [0u8; REPORT_LEN];
match stream.read_exact(&mut report) {
Ok(()) => {
let n = len.min(REPORT_LEN);
std::ptr::copy_nonoverlapping(report.as_ptr(), buf, n);
n as c_int
}
Err(_) => -1,
}
}
pub const SOCKET_IO: FidoDevIo = FidoDevIo {
open: io_open,
close: io_close,
read: io_read,
write: io_write,
};
pub struct SocketChannel(UnixStream);
impl SocketChannel {
pub fn open(path: &str) -> Result<Self> {
let stream = UnixStream::connect(path).with_context(|| format!("connecting to {path}"))?;
stream.set_read_timeout(Some(Duration::from_secs(5)))?;
Ok(Self(stream))
}
}
impl ReportChannel for SocketChannel {
fn write_report(&mut self, report: &[u8; REPORT_LEN]) -> Result<()> {
let mut buf = [0u8; REPORT_LEN + 1];
buf[1..].copy_from_slice(report);
self.0.write_all(&buf).context("writing to the emulator")
}
fn read_report(&mut self) -> Result<[u8; REPORT_LEN]> {
let mut report = [0u8; REPORT_LEN];
if self.0.read_exact(&mut report).is_err() {
bail!("no reply from the emulator");
}
Ok(report)
}
}