use std::sync::atomic::Ordering;
use std::time::{Duration, Instant};
use parking_lot::Mutex;
use crate::protocol::command::query_payload;
use crate::protocol::opcode::{Q_CAPS, Q_CLIP, Q_DEVICE_INFO, Q_STATS};
use crate::protocol::{FrameType, PROTO_VER, Resp, parse_resp};
use crate::types::{ClipStatus, Stats};
use super::correlation;
use super::keepalive::KeepaliveCtx;
use super::reconnect::reapply_locked;
use super::slot::TransportSlot;
use super::write_frame;
const CLONE_POLL: Duration = Duration::from_millis(50);
const CLONE_POLL_SLOW: Duration = Duration::from_millis(500);
const CLONE_POLL_CLOSE: Duration = Duration::from_secs(5);
const QUERY_TIMEOUT: Duration = Duration::from_millis(250);
const QUERY_SLICE: Duration = Duration::from_millis(20);
const REPRESENT_WATCH: Duration = Duration::from_secs(5);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Cause {
Restart,
Released,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Outcome {
Done,
Wait,
Again,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct Pending {
pub(crate) cause: Cause,
since: Instant,
pub(crate) next: Instant,
}
impl Pending {
pub(crate) fn new(cause: Cause, now: Instant) -> Pending {
Pending {
cause,
since: now,
next: now,
}
}
pub(crate) fn wait(&mut self, now: Instant) {
let gap = if now - self.since < CLONE_POLL_CLOSE {
CLONE_POLL
} else {
CLONE_POLL_SLOW
};
self.next = now + gap;
}
}
#[derive(Debug, Default)]
pub(crate) struct RestartWatch {
state: Mutex<Watch>,
}
#[derive(Debug, Default)]
struct Watch {
armed: bool,
recovering: bool,
owed: bool,
hellos: u64,
booted: bool,
session: Option<u16>,
represent_until: Option<Instant>,
}
impl RestartWatch {
pub(crate) fn note_reply(&self) {
self.state.lock().armed = true;
}
pub(crate) fn note_hello(&self, payload: &[u8], transport: &TransportSlot) {
let proto = match parse_resp(payload) {
Some(Resp::Version(v)) => v.proto_ver,
_ => return,
};
if proto != PROTO_VER {
trace_event!(
target: "medius::device",
tracing::Level::WARN,
got = proto,
expected = PROTO_VER,
"restart: the box came back on another protocol",
);
transport.refuse(proto);
return;
}
let was_refused = transport.refused().is_some();
transport.accept();
let mut w = self.state.lock();
w.armed |= was_refused;
if w.recovering {
w.hellos += 1;
w.booted = true;
return;
}
if !w.armed {
return;
}
w.armed = false;
w.recovering = true;
w.owed = true;
w.hellos += 1;
trace_event!(
target: "medius::device",
tracing::Level::INFO,
"device chip restarted",
);
}
pub(crate) fn begin(&self) {
let mut w = self.state.lock();
w.recovering = true;
w.owed = true;
w.hellos += 1;
}
pub(crate) fn take_owed(&self) -> bool {
let mut w = self.state.lock();
if w.owed {
w.recovering = true;
}
std::mem::take(&mut w.owed)
}
fn hellos(&self) -> u64 {
self.state.lock().hellos
}
fn finish(&self, seen: u64) -> bool {
let mut w = self.state.lock();
if w.hellos != seen {
return false;
}
w.recovering = false;
true
}
pub(crate) fn note_session(&self, session: u16) {
self.state.lock().session = Some(session);
}
fn session_is(&self, session: u16) -> bool {
self.state.lock().session == Some(session)
}
fn session_moved(&self, session: u16) -> bool {
let mut w = self.state.lock();
w.session.replace(session).is_some_and(|was| was != session)
}
pub(crate) fn expect_represent(&self) {
self.state.lock().represent_until = Some(Instant::now() + REPRESENT_WATCH);
}
pub(crate) fn expecting_represent(&self, now: Instant) -> bool {
self.state.lock().represent_until.is_some_and(|t| now < t)
}
pub(crate) fn begin_release(&self) {
self.state.lock().recovering = true;
}
fn take_booted(&self) -> bool {
std::mem::take(&mut self.state.lock().booted)
}
}
pub(crate) fn session_released(ctx: &KeepaliveCtx) -> bool {
let Some(stats) = read_stats(ctx) else {
return false;
};
if !ctx.restart.session_moved(stats.session) {
return false;
}
trace_event!(
target: "medius::device",
tracing::Level::INFO,
session = stats.session,
"the box released the session",
);
true
}
fn read_stats(ctx: &KeepaliveCtx) -> Option<Stats> {
match query(ctx, Q_STATS).as_deref().and_then(parse_resp) {
Some(Resp::Stats(s)) => Some(s),
_ => None,
}
}
fn clone_up(ctx: &KeepaliveCtx) -> Option<u8> {
match query(ctx, Q_CAPS).as_deref().and_then(parse_resp) {
Some(Resp::Caps(c)) if c.mouse.n_hid > 0 => Some(c.mouse.n_buttons),
Some(Resp::Caps(_)) => match query(ctx, Q_DEVICE_INFO).as_deref().and_then(parse_resp) {
Some(Resp::DeviceInfo(d)) if d.is_cloned() => Some(0),
_ => None,
},
_ => None,
}
}
pub(crate) fn read_ring(ctx: &KeepaliveCtx) {
let seen = ctx.desired.lock().clip_ring_gen();
let hellos = ctx.restart.hellos();
let Some(status) = query(ctx, Q_CLIP).and_then(|p| ClipStatus::from_payload(&p)) else {
return;
};
let Some(stats) = read_stats(ctx) else {
return;
};
if ctx.restart.hellos() != hellos || !ctx.restart.session_is(stats.session) {
return;
}
ctx.desired.lock().clip_note_ring(seen, &status, false);
}
pub(crate) fn step(ctx: &KeepaliveCtx, cause: Cause) -> Outcome {
let Some(buttons) = clone_up(ctx) else {
return Outcome::Wait;
};
let seen = ctx.restart.hellos();
if buttons > 0 {
ctx.desired.lock().note_declared_buttons(buttons);
}
let Some(before) = read_stats(ctx).map(|s| s.session) else {
return Outcome::Wait;
};
let sent = {
let _serial = ctx.catch_lock.lock();
reapply_locked(
&ctx.transport,
&ctx.write_lock,
&ctx.seq,
&ctx.counters,
&ctx.desired,
true,
)
};
if sent.is_err() {
return Outcome::Wait;
}
let up = clone_up(ctx).is_some();
let ring_seen = ctx.desired.lock().clip_ring_gen();
let ring = query(ctx, Q_CLIP).and_then(|p| ClipStatus::from_payload(&p));
let settled = up && read_stats(ctx).map(|s| s.session) == Some(before);
if !settled {
return Outcome::Wait;
}
if let Some(status) = ring {
ctx.desired.lock().clip_note_ring(ring_seen, &status, true);
}
ctx.restart.note_session(before);
if ctx.restart.take_booted() || cause == Cause::Restart {
ctx.counters.inc_restarts();
}
if ctx.restart.finish(seen) {
Outcome::Done
} else {
Outcome::Again
}
}
fn query(ctx: &KeepaliveCtx, what: u8) -> Option<Vec<u8>> {
query_sent(ctx, what).flatten()
}
fn query_sent(ctx: &KeepaliveCtx, what: u8) -> Option<Option<Vec<u8>>> {
let (seq, gen_id, rx) = correlation::register(
&ctx.pending,
&ctx.query_gen,
&ctx.seq,
FrameType::Resp,
what,
);
let sent = write_frame(
&ctx.transport,
&ctx.write_lock,
&ctx.counters,
seq,
FrameType::Query,
&query_payload(what),
);
if sent.is_err() {
correlation::cancel(&ctx.pending, seq, gen_id);
return None;
}
let deadline = Instant::now() + QUERY_TIMEOUT;
let reply = loop {
match rx.recv_timeout(QUERY_SLICE) {
Ok(reply) => break Some(reply),
Err(_) if ctx.stop.load(Ordering::SeqCst) || Instant::now() >= deadline => break None,
Err(_) => {}
}
};
if reply.is_none() {
correlation::cancel(&ctx.pending, seq, gen_id);
}
Some(reply)
}