//! navka-server – bridge HID reports between client (gRPC) and /dev/hidg0 #![forbid(unsafe_code)] use anyhow::Result; use navka_common::navka::{ hid_report::*, relay_server::Relay, HidReport, RelayServer, }; use std::{path::Path, sync::Arc}; use tokio::{ fs::OpenOptions, io::AsyncWriteExt, signal, sync::{mpsc, Mutex}, }; use tonic::{transport::Server, Request, Response, Status}; /// Implementation of the gRPC service generated by tonic-build. #[derive(Debug)] pub struct RelaySvc { /// Shared handle to /dev/hidg0 (protected by a mutex because several /// gRPC streams may write concurrently). hidg: Arc>, } #[tonic::async_trait] impl Relay for RelaySvc { type StreamStream = tokio_stream::wrappers::ReceiverStream>; async fn stream( &self, request: Request>, ) -> Result, Status> { let mut inbound = request.into_inner(); // Channel we’ll use if one day we need to send something back. let (tx, rx) = mpsc::channel(32); let hidg = self.hidg.clone(); tokio::spawn(async move { while let Some(report) = inbound.message().await.transpose()? { // Write raw 8-byte packet to the gadget device hidg.lock().await.write_all(&report.data).await?; } Ok::<(), anyhow::Error>(()) }); Ok(Response::new(tokio_stream::wrappers::ReceiverStream::new(rx))) } } #[tokio::main] async fn main() -> Result<()> { // Open /dev/hidg0 once and share it. let hid_dev = if Path::new("/dev/hidg0").exists() { OpenOptions::new().write(true).open("/dev/hidg0").await? } else { anyhow::bail!("/dev/hidg0 not found – is navka-gadget running?"); }; let svc = RelayServer::new(RelaySvc { hidg: Arc::new(Mutex::new(hid_dev)), }); println!("🛰️ navka-server listening on 0.0.0.0:50051"); Server::builder() .add_service(svc) .serve_with_shutdown("0.0.0.0:50051".parse()?, async { signal::ctrl_c().await.ok(); }) .await?; Ok(()) }