lesavka/server/src/main.rs

96 lines
3.4 KiB
Rust

// Server binary for relay RPCs, HID/audio/video gadget IO, and capture leasing.
#[allow(clippy::useless_attribute)]
#[forbid(unsafe_code)]
use anyhow::Context;
use futures_util::{Stream, StreamExt};
use std::collections::HashMap;
use std::collections::HashSet;
use std::net::SocketAddr;
use std::os::unix::fs::FileTypeExt;
use std::process::Command;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::{backtrace::Backtrace, panic, pin::Pin, sync::Arc, time::Duration};
use tokio::sync::{Mutex, broadcast};
use tokio_stream::wrappers::ReceiverStream;
use tonic::transport::Server;
use tonic::{Request, Response, Status};
use tonic_reflection::server::Builder as ReflBuilder;
use tracing::{debug, error, info, warn};
use lesavka_common::lesavka::{
AudioPacket, CapturePowerCommand, CapturePowerState, Empty, KeyboardReport, MonitorRequest,
MouseReport, PasteReply, PasteRequest, ResetUsbReply, SetCapturePowerRequest, VideoPacket,
relay_server::{Relay, RelayServer},
};
use lesavka_server::{
camera, camera_runtime::CameraRuntime, capture_power::CapturePowerManager, gadget::UsbGadget,
handshake::HandshakeSvc, paste, runtime_support, runtime_support::init_tracing, uvc_runtime,
video,
};
/*──────────────── constants ────────────────*/
const PKG_NAME: &str = env!("CARGO_PKG_NAME");
type VideoStream = Pin<Box<dyn Stream<Item = Result<VideoPacket, Status>> + Send>>;
type AudioStream = Pin<Box<dyn Stream<Item = Result<AudioPacket, Status>> + Send>>;
fn hid_endpoint(index: u8) -> String {
std::env::var("LESAVKA_HID_DIR")
.map(|dir| format!("{dir}/hidg{index}"))
.unwrap_or_else(|_| format!("/dev/hidg{index}"))
}
fn live_keyboard_report_delay() -> Duration {
std::env::var("LESAVKA_LIVE_KEYBOARD_REPORT_DELAY_MS")
.ok()
.and_then(|value| value.parse::<u64>().ok())
.map(Duration::from_millis)
.unwrap_or_else(|| Duration::from_millis(8))
}
fn server_bind_addr() -> anyhow::Result<SocketAddr> {
// LESAVKA_SERVER_BIND_ADDR lets tests and constrained lab benches avoid
// colliding with another live relay while keeping the field default stable.
let raw =
std::env::var("LESAVKA_SERVER_BIND_ADDR").unwrap_or_else(|_| "0.0.0.0:50051".to_string());
raw.parse::<SocketAddr>()
.with_context(|| format!("parsing LESAVKA_SERVER_BIND_ADDR={raw:?}"))
}
/*──────────────── Handler ───────────────────*/
struct Handler {
kb: Arc<Mutex<Option<tokio::fs::File>>>,
ms: Arc<Mutex<Option<tokio::fs::File>>>,
gadget: UsbGadget,
did_cycle: Arc<AtomicBool>,
camera_rt: Arc<CameraRuntime>,
capture_power: CapturePowerManager,
eye_hubs: Arc<Mutex<HashMap<EyeHubKey, Arc<EyeHub>>>>,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
struct EyeHubKey {
source_id: u32,
requested_width: u32,
requested_height: u32,
requested_fps: u32,
}
struct EyeHub {
tx: broadcast::Sender<VideoPacket>,
running: Arc<AtomicBool>,
subscribers: Arc<AtomicUsize>,
abort: tokio::task::AbortHandle,
}
include!("main/handler_startup.rs");
include!("main/eye_video.rs");
include!("main/rpc_helpers.rs");
include!("main/usb_recovery_helpers.rs");
include!("main/eye_hub.rs");
include!("main/relay_service.rs");
include!("main/relay_service_coverage.rs");
include!("main/entrypoint.rs");