feat: add Rust client library and mux daemon
Add donglora-client (clients/rust/) and donglora-mux (mux/rust/) as Rust implementations of the existing Python client library and mux daemon. Both are standalone Cargo projects with no-panic guarantees enforced by clippy deny lints. donglora-client provides protocol types, COBS framing, USB device discovery, serial/mux transports, and a high-level Client<T> with send/recv. donglora-mux is a fully async (tokio) multiplexer daemon with dongle hot-plug reconnect, per-client bounded send queues, SetConfig locking, and StartRx/StopRx reference counting. 52 unit tests across both crates. Updates README roadmap checkboxes and CONTRIBUTING docs to reflect the new implementations.
This commit is contained in:
commit
2a0543d0b1
8 files changed
+2043
No files matched your search
@@ -0,0 +1,57 @@
|
||||
//! Per-client session state and bounded send queue.
|
||||
//!
|
||||
//! Each connected client gets a [`ClientSession`] that tracks its ID,
|
||||
//! RX interest flag, and a bounded channel for outgoing COBS frames.
|
||||
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
use tokio::sync::mpsc;
|
||||
use tracing::warn;
|
||||
|
||||
/// Bounded send queue capacity per client.
|
||||
const SEND_QUEUE_CAP: usize = 256;
|
||||
|
||||
/// Global client ID counter.
|
||||
static NEXT_CLIENT_ID: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
/// Per-client state tracked by the mux daemon.
|
||||
pub struct ClientSession {
|
||||
/// Unique client identifier.
|
||||
pub id: u64,
|
||||
/// Whether this client has called StartRx.
|
||||
pub rx_interested: bool,
|
||||
/// Sender half of the bounded channel to the client's writer task.
|
||||
tx: mpsc::Sender<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl ClientSession {
|
||||
/// Create a new session. Returns the session and the receiver half
|
||||
/// for the client's writer task to drain.
|
||||
pub fn new() -> (Self, mpsc::Receiver<Vec<u8>>) {
|
||||
let id = NEXT_CLIENT_ID.fetch_add(1, Ordering::Relaxed);
|
||||
let (tx, rx) = mpsc::channel(SEND_QUEUE_CAP);
|
||||
let session = Self {
|
||||
id,
|
||||
rx_interested: false,
|
||||
tx,
|
||||
};
|
||||
(session, rx)
|
||||
}
|
||||
|
||||
/// Human-readable label for logging.
|
||||
pub fn label(&self) -> String {
|
||||
format!("client-{}", self.id)
|
||||
}
|
||||
|
||||
/// Best-effort enqueue of a COBS frame for sending to this client.
|
||||
///
|
||||
/// If the channel is full (slow client), the frame is dropped with a warning.
|
||||
/// This prevents backpressure from one slow client blocking the entire mux.
|
||||
pub fn enqueue(&self, frame: Vec<u8>) {
|
||||
if let Err(mpsc::error::TrySendError::Full(_)) = self.tx.try_send(frame) {
|
||||
warn!("{}: send queue full, dropping frame", self.label());
|
||||
}
|
||||
// Closed channel (client disconnected) is silently ignored — the session
|
||||
// will be cleaned up when the client handler task exits.
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user