lesavka/server/src/main.rs

292 lines
10 KiB
Rust
Raw Normal View History

2025-06-25 20:36:42 -05:00
//! lesavka-server
2025-06-16 21:47:01 -05:00
// sever/src/main.rs
2025-06-01 16:04:00 -05:00
#![forbid(unsafe_code)]
2025-06-01 13:31:22 -05:00
2025-06-25 15:13:49 -05:00
use std::sync::atomic::{AtomicBool, Ordering};
2025-06-25 20:00:34 -05:00
use std::{panic, backtrace::Backtrace, pin::Pin, sync::Arc, time::Duration};
2025-06-25 16:23:50 -05:00
use futures_util::{Stream, StreamExt};
2025-06-23 21:22:02 -05:00
use tokio::{fs::{OpenOptions}, io::AsyncWriteExt, sync::Mutex};
2025-06-21 05:21:57 -05:00
use tokio_stream::{wrappers::ReceiverStream};
2025-06-25 18:23:38 -05:00
use tonic::{transport::Server, Request, Response, Status};
use anyhow::Context as _;
2025-06-25 19:04:11 -05:00
use tracing::{info, trace, warn, error};
2025-06-25 16:23:50 -05:00
use tracing_subscriber::{filter::EnvFilter, fmt, prelude::*};
use tracing_appender::non_blocking;
2025-06-25 16:52:26 -05:00
use tracing_appender::non_blocking::WorkerGuard;
2025-06-23 21:22:02 -05:00
use udev::{MonitorBuilder};
2025-06-21 05:21:57 -05:00
2025-06-25 15:13:49 -05:00
use lesavka_server::{usb_gadget::UsbGadget, video};
2025-06-01 21:26:57 -05:00
2025-06-23 07:18:26 -05:00
use lesavka_common::lesavka::{
2025-06-01 21:26:57 -05:00
relay_server::{Relay, RelayServer},
2025-06-17 20:54:31 -05:00
KeyboardReport, MouseReport,
2025-06-21 05:21:57 -05:00
MonitorRequest, VideoPacket,
2025-06-01 16:04:00 -05:00
};
2025-06-02 20:24:00 -05:00
2025-06-25 16:23:50 -05:00
// ───────────────── helper ─────────────────────
2025-06-25 16:52:26 -05:00
fn init_tracing() -> anyhow::Result<WorkerGuard> {
// 1. create file writer + guard
2025-06-25 16:23:50 -05:00
let file = std::fs::OpenOptions::new()
2025-06-25 20:36:42 -05:00
.create(true).write(true).append(true)
2025-06-25 16:23:50 -05:00
.open("/tmp/lesavka-server.log")?;
2025-06-25 16:52:26 -05:00
let (file_writer, guard) = non_blocking(file);
2025-06-25 16:23:50 -05:00
2025-06-25 16:52:26 -05:00
// 2. build subscriber once
let env = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::new("lesavka_server=info"));
2025-06-25 16:23:50 -05:00
let console_layer = fmt::layer()
.with_target(true)
.with_thread_ids(true);
2025-06-25 16:52:26 -05:00
let file_layer = fmt::layer()
2025-06-25 16:23:50 -05:00
.with_writer(file_writer)
.with_ansi(false);
tracing_subscriber::registry()
2025-06-25 16:52:26 -05:00
.with(env)
2025-06-25 16:23:50 -05:00
.with(console_layer)
.with(file_layer)
.init();
2025-06-25 16:52:26 -05:00
Ok(guard)
2025-06-25 16:23:50 -05:00
}
2025-06-25 21:15:24 -05:00
async fn open_with_retry(path: &str) -> anyhow::Result<tokio::fs::File> {
2025-06-25 23:27:51 -05:00
const MAX_ATTEMPTS: usize = 200; // ≈ 10s (@50ms)
2025-06-25 21:15:24 -05:00
for attempt in 1..=MAX_ATTEMPTS {
2025-06-25 19:04:11 -05:00
match OpenOptions::new()
.write(true)
.custom_flags(libc::O_NONBLOCK)
.open(path)
.await
{
Ok(f) => {
2025-06-25 21:15:24 -05:00
info!("✅ {path} opened on attempt #{attempt}");
2025-06-25 19:04:11 -05:00
return Ok(f);
}
2025-06-25 22:24:58 -05:00
Err(e) if e.raw_os_error() == Some(libc::EBUSY) => {
2025-06-25 21:15:24 -05:00
trace!("⏳ {path} busy, retry #{attempt}");
tokio::time::sleep(Duration::from_millis(50)).await;
2025-06-25 20:00:34 -05:00
}
2025-06-25 21:15:24 -05:00
Err(e) => return Err(e).with_context(|| format!("💥 opening {path}")),
2025-06-25 19:04:11 -05:00
}
}
2025-06-25 21:15:24 -05:00
Err(anyhow::anyhow!("💥 timeout waiting for {path} to become available"))
2025-06-25 19:04:11 -05:00
}
2025-06-25 20:36:42 -05:00
fn owners_of(path: &str) -> String {
use std::{fs, os::unix::fs::MetadataExt};
let Ok(target_ino) = fs::metadata(path).map(|m| m.ino()) else { return "-".into() };
let mut pids = Vec::new();
if let Ok(entries) = fs::read_dir("/proc") {
for e in entries.flatten() {
let file_name = e.file_name();
let pid = file_name.to_string_lossy().into_owned();
if !pid.chars().all(|c| c.is_ascii_digit()) { continue }
let fd_dir = e.path().join("fd");
for fd in fs::read_dir(fd_dir).into_iter().flatten().flatten() {
if let Ok(meta) = fd.metadata() {
if meta.ino() == target_ino {
pids.push(pid.to_string());
break;
}
}
}
}
}
if pids.is_empty() { "-".into() } else { pids.join(",") }
}
2025-06-21 05:21:57 -05:00
/*─────────────────── tonic service ─────────────────────*/
2025-06-02 20:41:36 -05:00
struct Handler {
2025-06-17 20:54:31 -05:00
kb: Arc<Mutex<tokio::fs::File>>,
ms: Arc<Mutex<tokio::fs::File>>,
2025-06-25 07:46:50 -05:00
gadget: UsbGadget,
2025-06-25 15:13:49 -05:00
did_cycle: AtomicBool,
2025-06-24 23:48:06 -05:00
}
impl Handler {
2025-06-25 07:46:50 -05:00
async fn make(gadget: UsbGadget) -> anyhow::Result<Self> {
2025-06-25 20:00:34 -05:00
info!("🛠️ Handler::make - cycling gadget ...");
2025-06-25 16:23:50 -05:00
gadget.cycle()?;
2025-06-25 20:36:42 -05:00
tokio::time::sleep(Duration::from_millis(1000)).await;
2025-06-25 16:23:50 -05:00
2025-06-25 20:00:34 -05:00
info!("🛠️ opening HID endpoints ...");
2025-06-25 21:15:24 -05:00
let kb = open_with_retry("/dev/hidg0").await?;
let ms = open_with_retry("/dev/hidg1").await?;
2025-06-25 16:23:50 -05:00
2025-06-25 20:00:34 -05:00
info!("✅ HID endpoints ready");
Ok(Self {
kb: Arc::new(Mutex::new(kb)),
ms: Arc::new(Mutex::new(ms)),
gadget,
did_cycle: AtomicBool::new(true),
2025-06-25 15:13:49 -05:00
})
2025-06-24 23:48:06 -05:00
}
2025-06-01 16:04:00 -05:00
}
2025-06-01 13:31:22 -05:00
2025-06-02 20:41:36 -05:00
#[tonic::async_trait]
2025-06-16 22:14:52 -05:00
impl Relay for Handler {
2025-06-17 08:17:23 -05:00
type StreamKeyboardStream = ReceiverStream<Result<KeyboardReport, Status>>;
type StreamMouseStream = ReceiverStream<Result<MouseReport, Status>>;
2025-06-21 05:21:57 -05:00
type CaptureVideoStream = Pin<Box<dyn Stream<Item = Result<VideoPacket, Status>> + Send + Sync + 'static>>;
2025-06-01 13:31:22 -05:00
2025-06-17 08:17:23 -05:00
async fn stream_keyboard(
2025-06-01 13:31:22 -05:00
&self,
2025-06-17 08:17:23 -05:00
req: Request<tonic::Streaming<KeyboardReport>>,
) -> Result<Response<Self::StreamKeyboardStream>, Status> {
2025-06-25 12:31:48 -05:00
// self.gadget.cycle().map_err(|e| Status::internal(e.to_string()))?;
2025-06-25 15:13:49 -05:00
if !self.did_cycle.swap(true, Ordering::SeqCst) {
self.gadget
.cycle()
.map_err(|e| Status::internal(e.to_string()))?;
tokio::time::sleep(Duration::from_millis(500)).await;
}
2025-06-17 08:17:23 -05:00
let (tx, rx) = tokio::sync::mpsc::channel(32);
2025-06-12 01:57:08 -05:00
let kb = self.kb.clone();
2025-06-02 20:41:36 -05:00
tokio::spawn(async move {
2025-06-17 08:17:23 -05:00
let mut s = req.into_inner();
2025-06-17 20:54:31 -05:00
while let Some(pkt) = s.next().await.transpose()? {
2025-06-25 15:13:49 -05:00
// kb.lock().await.write_all(&pkt.data).await?;
2025-06-25 16:23:50 -05:00
loop {
2025-06-25 15:13:49 -05:00
match kb.lock().await.write_all(&pkt.data).await {
Ok(()) => {
trace!("⌨️ wrote {}", pkt.data.iter()
.map(|b| format!("{b:02X}")).collect::<Vec<_>>().join(" "));
break;
},
2025-06-25 16:23:50 -05:00
Err(e) if matches!(e.raw_os_error(), Some(libc::EBUSY) | Some(libc::EAGAIN)) => {
tokio::time::sleep(Duration::from_millis(10)).await;
continue;
2025-06-25 15:13:49 -05:00
}
Err(e) => return Err(Status::internal(e.to_string())),
}
}
2025-06-17 20:54:31 -05:00
tx.send(Ok(pkt)).await;//.ok(); // best-effort echo
2025-06-17 08:17:23 -05:00
}
Ok::<(), Status>(())
});
Ok(Response::new(ReceiverStream::new(rx)))
}
2025-06-16 19:19:14 -05:00
2025-06-17 08:17:23 -05:00
async fn stream_mouse(
&self,
req: Request<tonic::Streaming<MouseReport>>,
) -> Result<Response<Self::StreamMouseStream>, Status> {
2025-06-17 22:36:23 -05:00
let (tx, rx) = tokio::sync::mpsc::channel(4096); // higher burst
2025-06-17 08:17:23 -05:00
let ms = self.ms.clone();
2025-06-16 19:19:14 -05:00
2025-06-17 08:17:23 -05:00
tokio::spawn(async move {
let mut s = req.into_inner();
2025-06-18 02:06:11 -05:00
let mut boot_mode = true;
2025-06-17 20:54:31 -05:00
while let Some(pkt) = s.next().await.transpose()? {
2025-06-17 23:13:13 -05:00
loop {
match ms.lock().await.write_all(&pkt.data).await {
2025-06-18 02:06:11 -05:00
Ok(()) => {
trace!("🖱️ wrote {}", pkt.data.iter()
.map(|b| format!("{b:02X}")).collect::<Vec<_>>().join(" "));
break;
}
2025-06-25 16:23:50 -05:00
Err(e) if matches!(e.raw_os_error(), Some(libc::EBUSY) | Some(libc::EAGAIN)) => {
tokio::time::sleep(Duration::from_millis(10)).await;
continue;
2025-06-17 23:13:13 -05:00
}
2025-06-25 16:23:50 -05:00
Err(e) => return Err(Status::internal(e.to_string())),
2025-06-17 23:13:13 -05:00
}
}
let _ = tx.send(Ok(pkt)).await;
2025-06-01 13:31:22 -05:00
}
2025-06-17 08:17:23 -05:00
Ok::<(), Status>(())
2025-06-01 13:31:22 -05:00
});
2025-06-17 08:17:23 -05:00
Ok(Response::new(ReceiverStream::new(rx)))
2025-06-01 13:31:22 -05:00
}
2025-06-21 05:21:57 -05:00
async fn capture_video(
&self,
req: Request<MonitorRequest>,
) -> Result<Response<Self::CaptureVideoStream>, Status> {
let r = req.into_inner();
2025-06-25 12:31:48 -05:00
// let devs = loop {
// let list = list_gc311_devices()
// .map_err(|e| Status::internal(format!("enum v4l2: {e}")))?;
// if !list.is_empty() { break list; }
// tokio::time::sleep(Duration::from_secs(1)).await;
// };
// let dev = devs
// .get(r.id as usize)
// .ok_or_else(|| Status::invalid_argument(format!("monitor id {} absent", r.id)))?
// .to_owned();
let dev = match r.id {
0 => "/dev/lesavka_l_eye",
1 => "/dev/lesavka_r_eye",
_ => return Err(Status::invalid_argument("monitor id must be 0 or 1")),
}
.to_string();
2025-06-21 05:21:57 -05:00
2025-06-25 20:36:42 -05:00
info!("🎥 streaming {dev} at ≤{} kb/s", r.max_bitrate);
2025-06-21 05:21:57 -05:00
let s = video::spawn_camera(&dev, r.id, r.max_bitrate)
.await
.map_err(|e| Status::internal(format!("{e:#?}")))?;
Ok(Response::new(Box::pin(s) as _))
}
2025-06-01 13:31:22 -05:00
}
2025-06-21 05:21:57 -05:00
/*─────────────────── main ──────────────────────────────*/
#[tokio::main(flavor = "multi_thread", worker_threads = 4)]
2025-06-02 20:41:36 -05:00
async fn main() -> anyhow::Result<()> {
2025-06-21 05:21:57 -05:00
/* logging */
2025-06-25 18:23:38 -05:00
let _log_guard: WorkerGuard = init_tracing()?;
2025-06-05 22:44:27 -05:00
2025-06-25 20:00:34 -05:00
panic::set_hook(Box::new(|p| {
let bt = Backtrace::force_capture();
error!("💥 panic: {p}\n{bt}");
}));
2025-06-21 05:21:57 -05:00
/* autocycle task */
2025-06-23 21:52:57 -05:00
// tokio::spawn(async { monitor_gc311_disconnect().await.ok(); });
2025-06-21 05:21:57 -05:00
2025-06-25 07:46:50 -05:00
let gadget = UsbGadget::new("lesavka");
2025-06-25 20:00:34 -05:00
let handler = match Handler::make(gadget.clone()).await {
Ok(h) => h,
Err(e) => {
error!("💥 failed to create Handler: {e:#}");
std::process::exit(1);
}
};
2025-06-12 01:57:08 -05:00
2025-06-25 09:21:39 -05:00
// tokio::spawn({
// let gadget = gadget.clone();
// async move {
// loop {
// tokio::time::sleep(Duration::from_secs(4)).await;
// if LAST_HID_WRITE.elapsed().as_secs() > 3 {
2025-06-25 20:36:42 -05:00
// warn!("no HID traffic in 3 s - cycling UDC");
2025-06-25 09:21:39 -05:00
// let _ = gadget.cycle();
// }
// }
// }
// });
2025-06-01 16:04:00 -05:00
2025-06-25 19:04:11 -05:00
info!("🌐 lesavkaserver listening on 0.0.0.0:50051");
if let Err(e) = Server::builder()
.add_service(RelayServer::new(handler))
.serve(([0, 0, 0, 0], 50051).into())
.await
{
error!("💥 gRPC server exited: {e:#}");
std::process::exit(1);
}
2025-06-01 13:31:22 -05:00
Ok(())
}