use std::sync::Arc;
use parking_lot::Mutex;
use crate::protocol::opcode::{
CAP_PAN, CAP_REPORT_ID, CAP_WHEEL, CAP_X, CAP_Y, CAPS_CD_KBD, CAPS_CD_MOUSE, DI_HAS_BOS,
DI_HAS_SERIAL, KBC_CONSUMER, KBC_NKRO, KBC_REPORT_ID, KBC_SYSTEM, LOCK_AXIS_PAN, LOCK_CLS_AXIS,
LOCK_CLS_BTN, LOCK_CLS_KEY, LOCK_CLS_MEDIA, LOCK_DIR_AGAINST, LOCK_DIR_BOTH, LOCK_DIR_NEG,
LOCK_DIR_POS, LOCK_DIR_WITH, LOCK_ID_ALL, LOCK_SCALE_BLOCK, LOCK_SCALE_PASS, MAX_BUTTONS,
OPT_BEARING, OPT_EMIT, OPT_IMPERFECT, OPT_MOVE_RIDE, OPT_NAME, OPT_RENDER, OPT_SPREAD,
Q_FIRMWARE, RATE_CONFIDENT,
};
use crate::protocol::opcode::{
CATCH_CLS_AXIS, CATCH_CLS_BTN, CATCH_CLS_KEY, CATCH_CLS_MEDIA, Q_TRANSFORMS, TF_F_FULL,
TF_OP_COUNT, TF_REMAP, TF_SWAP, TRANSFORM_MAX_ENTRIES,
};
use crate::protocol::opcode::{
CLIP_CFG_F_FINALIZED, CLIP_CFG_F_LOOP, CLIP_CFG_F_RETAIN, CLIP_CFG_F_RIDE, CLIP_TRIG_MAX,
CLK_RATE_NONE, PATCH_APPLY, PATCH_CLEAR, PATCH_MAX_ENTRIES, Q_PATCH_ENTRY, Q_PATCHES,
Q_REWRITE, Q_REWRITE_ENTRY, REWRITE_MATCH_MAX, REWRITE_MAX_ENTRIES,
};
use crate::protocol::{DecodedFrame, FrameType, encode};
use crate::types::PatchSection;
use sha2::{Digest, Sha256};
use crate::transport::mock::MockTransport;
use crate::types::lock::blanket_scope;
use crate::types::{
Axis, Bearing, BearingMode, Caps, CatchClass, CatchState, Class, ClipSettings, ClipState,
ClipStatus, ClockDomain, DeviceInfo, DeviceKind, Direction, EmitPace, Health, ImperfectStatus,
KbdCaps, LockEntry, LockScope, LockTarget, Locks, LogLevel, MouseCaps, Rate, RenderMode, Stats,
Usage, Version,
};
#[derive(Debug)]
struct State {
update: MockUpdate,
replied: Vec<Vec<u8>>,
version: Version,
health: Health,
device_info: DeviceInfo,
caps: Caps,
rate: Rate,
stats: Stats,
table: LockTable,
locks: Option<Locks>,
catch: CatchState,
imperfect: ImperfectStatus,
move_ride_ms: u16,
bearing: Bearing,
emit_pace: EmitPace,
render_mode: RenderMode,
render_full: bool,
render_ready: bool,
spread_percent: u16,
spread_learned_us: u32,
emit_force_hz: Option<u16>,
advertised_hz: u16,
clip: ClipStatus,
clip_settings: ClipSettings,
rewrites: Vec<MockRewrite>,
rewrite_gen: u8,
rewrite_full: bool,
patches: Vec<MockPatch>,
patch_applied: bool,
patch_refused: bool,
transforms: Vec<MockTransform>,
transform_full: bool,
transfer_reply: (u8, Vec<u8>),
recorded: Vec<DecodedFrame>,
respond: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct MockRewrite {
class: u8,
id: u16,
dir: u8,
action: u8,
offset: u16,
match_bytes: Vec<u8>,
mask: Vec<u8>,
payload: Vec<u8>,
hits: u16,
}
impl MockRewrite {
fn key(&self) -> (u8, u16, u8, Vec<u8>, Vec<u8>) {
(
self.class,
self.id,
self.dir,
self.match_bytes.clone(),
self.mask.clone(),
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct MockTransform {
op: u8,
sclass: u8,
sid: u16,
dclass: u8,
did: u16,
}
impl MockTransform {
fn key(&self) -> (u8, u16, u8, u16) {
(self.sclass, self.sid, self.dclass, self.did)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct MockPatch {
section: u8,
cfg: u8,
index: u8,
offset: u16,
bytes: Vec<u8>,
}
impl MockPatch {
fn key(&self) -> (u8, u8, u8, u16) {
(self.section, self.cfg, self.index, self.offset)
}
}
impl Default for State {
fn default() -> Self {
State {
update: MockUpdate::default(),
replied: Vec::new(),
version: Version {
proto_ver: crate::protocol::PROTO_VER,
fw_major: 0,
fw_minor: 0,
fw_patch: 0,
mac: [0; 6],
name: String::new(),
},
health: Health::from_flags(0),
device_info: DeviceInfo::default(),
caps: Caps {
mouse: MouseCaps {
n_buttons: 5,
has_x: true,
has_y: true,
has_wheel: true,
pan: false,
has_report_id: false,
n_hid: 1,
},
..Caps::default()
},
rate: Rate::from_payload(&[4, 0, 0, 0, 0, 0]).unwrap(),
stats: Stats::from_payload(&[5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
.unwrap(),
table: LockTable::default(),
locks: None,
catch: CatchState::from_payload(&[
7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 0,
])
.unwrap(),
imperfect: ImperfectStatus::default(),
move_ride_ms: 0,
bearing: Bearing::default(),
emit_pace: EmitPace::Learned,
render_mode: RenderMode::Despiked,
render_full: false,
render_ready: false,
spread_percent: 100,
spread_learned_us: 0,
emit_force_hz: None,
advertised_hz: 0,
clip: ClipStatus::default(),
clip_settings: ClipSettings::default(),
rewrites: Vec::new(),
rewrite_gen: 0,
rewrite_full: false,
patches: Vec::new(),
patch_applied: false,
patch_refused: false,
transforms: Vec::new(),
transform_full: false,
transfer_reply: (0x00, Vec::new()),
recorded: Vec::new(),
respond: true,
}
}
}
const LOCK_TGT_BTN_BASE: usize = 4; const LOCK_TGT_COUNT: usize = LOCK_TGT_BTN_BASE + MAX_BUTTONS as usize; const LOCK_SLOT_WITH: usize = 2;
const SLOT_DIRS: [u8; 4] = [LOCK_DIR_POS, LOCK_DIR_NEG, LOCK_DIR_WITH, LOCK_DIR_AGAINST];
const RESP_LOCKS_MAXN: usize = 85;
const MEDIA_LOCK_MAX: usize = 8;
const NAME_MAX: usize = 32; const DEVICE_INFO_PRODUCT_MAX: usize = 127; const CATCH_MAXN: usize = 32; const USAGE_EVENT_MAX: usize = 40; const CLIP_HELD_MAX: usize = USAGE_EVENT_MAX; const TRAFFIC_DATA_MAX: usize = 180;
#[derive(Debug, Clone)]
pub(crate) struct LockTable {
mouse: [[i16; 4]; LOCK_TGT_COUNT],
key_blanket: u8,
key_press: [bool; 256],
key_release: [bool; 256],
media: [u16; MEDIA_LOCK_MAX], media_blanket: bool,
}
impl Default for LockTable {
fn default() -> LockTable {
LockTable {
mouse: [[LOCK_SCALE_PASS; 4]; LOCK_TGT_COUNT],
key_blanket: 0,
key_press: [false; 256],
key_release: [false; 256],
media: [0; MEDIA_LOCK_MAX],
media_blanket: false,
}
}
}
fn slot_mask(dir: u8) -> u8 {
match dir {
LOCK_DIR_BOTH => 0x0F,
LOCK_DIR_POS => 0x01,
LOCK_DIR_NEG => 0x02,
LOCK_DIR_WITH => 0x04,
LOCK_DIR_AGAINST => 0x08,
_ => 0,
}
}
impl LockTable {
fn set_mouse(&mut self, target: usize, dir: u8, scale: i16) {
let slots = slot_mask(dir);
for i in 0..4 {
if slots & (1 << i) == 0 {
continue;
}
let mut v = scale;
if target >= LOCK_TGT_BTN_BASE {
v = if v < LOCK_SCALE_PASS {
LOCK_SCALE_BLOCK
} else {
LOCK_SCALE_PASS
};
}
if i >= LOCK_SLOT_WITH {
if target >= LOCK_TGT_BTN_BASE && dir != LOCK_DIR_BOTH {
continue;
}
if dir == LOCK_DIR_BOTH {
v = LOCK_SCALE_PASS;
}
}
self.mouse[target][i] = v;
}
}
pub(crate) fn apply(&mut self, class: u8, id: u16, dir: u8, scale: i16, n_buttons: u8) {
if scale < 0 && class != LOCK_CLS_AXIS {
return;
}
let on = scale < LOCK_SCALE_PASS;
match class {
LOCK_CLS_AXIS => {
if id == LOCK_ID_ALL {
for t in 0..=LOCK_AXIS_PAN as usize {
self.set_mouse(t, dir, scale);
}
} else if id <= LOCK_AXIS_PAN {
self.set_mouse(id as usize, dir, scale);
}
}
LOCK_CLS_BTN => {
let nbtn = (n_buttons as usize).min(MAX_BUTTONS as usize);
if id == LOCK_ID_ALL {
for b in 0..nbtn {
self.set_mouse(LOCK_TGT_BTN_BASE + b, dir, scale);
}
} else if (id as usize) < nbtn {
self.set_mouse(LOCK_TGT_BTN_BASE + id as usize, dir, scale);
}
}
LOCK_CLS_KEY => {
if id == LOCK_ID_ALL {
let m = slot_mask(dir) & 0x03;
if m == 0 {
return;
}
if on {
self.key_blanket |= m;
} else {
self.key_blanket &= !m;
}
} else {
let u = (id & 0xFF) as usize;
if u < 0x04 {
return;
}
if dir == LOCK_DIR_BOTH || dir == LOCK_DIR_POS {
self.key_press[u] = on;
}
if dir == LOCK_DIR_BOTH || dir == LOCK_DIR_NEG {
self.key_release[u] = on;
}
}
}
LOCK_CLS_MEDIA => {
if id == LOCK_ID_ALL {
self.media_blanket = on;
} else if id != 0 {
if !on {
for slot in self.media.iter_mut().filter(|s| **s == id) {
*slot = 0;
}
} else if !self.media.contains(&id)
&& let Some(slot) = self.media.iter_mut().find(|s| **s == 0)
{
*slot = id;
}
}
}
_ => {}
}
}
fn reported(&self, t: usize, slot: usize, vector: bool) -> i16 {
let sc = self.mouse[t][slot];
if !vector || slot < LOCK_SLOT_WITH || t > Axis::Y.as_u16() as usize {
return sc;
}
sc.min(self.mouse[1 - t][slot])
}
pub(crate) fn pack(&self, mode: BearingMode) -> Locks {
let vector = mode == BearingMode::Vector;
let mut out: Vec<LockEntry> = Vec::new();
let mut push = |scope: LockScope, direction: u8, scale: i16| {
if out.len() < RESP_LOCKS_MAXN {
out.push(LockEntry {
scope,
direction: Direction::from_u8(direction).expect("slot direction"),
scale,
});
}
};
for t in 0..LOCK_TGT_COUNT {
for (slot, &dir) in SLOT_DIRS.iter().enumerate() {
let scale = self.reported(t, slot, vector);
if scale == LOCK_SCALE_PASS {
continue;
}
let target = if t < LOCK_TGT_BTN_BASE {
LockTarget::Axis(match t {
0 => Axis::X,
1 => Axis::Y,
2 => Axis::Wheel,
_ => Axis::Pan,
})
} else {
LockTarget::Usage(Usage::new(Class::Button, (t - LOCK_TGT_BTN_BASE) as u16))
};
push(LockScope::Target(target), dir, scale);
}
}
for (bit, dir) in [(0x01, LOCK_DIR_POS), (0x02, LOCK_DIR_NEG)] {
if self.key_blanket & bit != 0 {
push(LockScope::Blanket(Class::Key), dir, LOCK_SCALE_BLOCK);
}
}
if self.media_blanket {
push(
LockScope::Blanket(Class::Media),
LOCK_DIR_BOTH,
LOCK_SCALE_BLOCK,
);
}
for &id in self.media.iter().filter(|&&id| id != 0) {
push(
LockScope::Target(LockTarget::Usage(Usage::new(Class::Media, id))),
LOCK_DIR_BOTH,
LOCK_SCALE_BLOCK,
);
}
for u in 0..256u16 {
let usage = LockScope::Target(LockTarget::Usage(Usage::new(Class::Key, u)));
if self.key_press[u as usize] {
push(usage, LOCK_DIR_POS, LOCK_SCALE_BLOCK);
}
if self.key_release[u as usize] {
push(usage, LOCK_DIR_NEG, LOCK_SCALE_BLOCK);
}
}
Locks::from_entries(out)
}
}
impl State {
fn apply_lock_frame(&mut self, p: &[u8]) {
if p.len() < 6 {
return;
}
let n_buttons = self.caps.mouse.n_buttons;
self.table.apply(
p[0],
u16::from_le_bytes([p[1], p[2]]),
p[3],
i16::from_le_bytes([p[4], p[5]]),
n_buttons,
);
}
fn apply_rewrite_frame(&mut self, p: &[u8]) {
if !self.imperfect.allowed {
return; }
if p.len() < 9 {
return;
}
let cls = p[0];
let id = u16::from_le_bytes([p[1], p[2]]);
let dir = p[3];
let state = p[4];
let action = p[5];
let offset = u16::from_le_bytes([p[6], p[7]]);
let mlen = p[8] as usize;
if state == 0 && cls == 0xFF && id == 0xFFFF {
if !self.rewrites.is_empty() {
self.rewrites.clear();
self.rewrite_gen = self.rewrite_gen.wrapping_add(1);
}
self.rewrite_full = false;
return;
}
if mlen > REWRITE_MATCH_MAX || p.len() < 9 + 2 * mlen {
return; }
let match_bytes = p[9..9 + mlen].to_vec();
let mask = p[9 + mlen..9 + 2 * mlen].to_vec();
let payload = p[9 + 2 * mlen..].to_vec();
let key = (cls, id, dir, match_bytes.clone(), mask.clone());
let pos = self.rewrites.iter().position(|r| r.key() == key);
if state == 0 {
if let Some(i) = pos {
self.rewrites.remove(i);
self.rewrite_gen = self.rewrite_gen.wrapping_add(1);
}
return;
}
match pos {
Some(i) => {
let same = self.rewrites[i].action == action
&& self.rewrites[i].offset == offset
&& self.rewrites[i].payload == payload;
if same {
return;
}
let hits = self.rewrites[i].hits;
self.rewrites[i] = MockRewrite {
class: cls,
id,
dir,
action,
offset,
match_bytes,
mask,
payload,
hits,
};
self.rewrite_gen = self.rewrite_gen.wrapping_add(1);
}
None => {
if self.rewrites.len() >= REWRITE_MAX_ENTRIES {
self.rewrite_full = true;
return;
}
self.rewrites.push(MockRewrite {
class: cls,
id,
dir,
action,
offset,
match_bytes,
mask,
payload,
hits: 0,
});
self.rewrite_gen = self.rewrite_gen.wrapping_add(1);
}
}
}
fn apply_transform_frame(&mut self, p: &[u8]) {
if p.len() < 8 {
return;
}
let op = p[0];
let sclass = p[1];
let sid = u16::from_le_bytes([p[2], p[3]]);
let dclass = p[4];
let did = u16::from_le_bytes([p[5], p[6]]);
let state = p[7];
if state == 0 && sclass == 0xFF && dclass == 0xFF && sid == 0xFFFF && did == 0xFFFF {
self.transforms.clear();
self.transform_full = false;
return;
}
let key = (sclass, sid, dclass, did);
let pos = self.transforms.iter().position(|t| t.key() == key);
if state == 0 {
if let Some(i) = pos {
self.transforms.remove(i);
self.transform_full = false;
}
return;
}
if op >= TF_OP_COUNT || !transform_pair_ok(op, sclass, sid, dclass, did)
|| !self.transform_field_present(sclass, sid)
|| !self.transform_field_present(dclass, did)
{
return;
}
match pos {
Some(i) => {
self.transforms[i].op = op;
}
None => {
if self.transforms.len() >= TRANSFORM_MAX_ENTRIES {
self.transform_full = true;
return;
}
self.transforms.push(MockTransform {
op,
sclass,
sid,
dclass,
did,
});
}
}
}
fn transform_field_present(&self, cls: u8, id: u16) -> bool {
match cls {
CATCH_CLS_AXIS => match id {
0 => self.caps.mouse.has_x,
1 => self.caps.mouse.has_y,
2 => self.caps.mouse.has_wheel,
3 => self.caps.mouse.pan,
_ => false,
},
CATCH_CLS_BTN => id < self.caps.mouse.n_buttons as u16 && id < MAX_BUTTONS as u16,
CATCH_CLS_KEY => self.caps.keyboard.n_keys > 0 && (0x04..=0xFF).contains(&id),
CATCH_CLS_MEDIA => self.caps.keyboard.has_consumer && id != 0,
_ => false,
}
}
fn apply_patch_frame(&mut self, p: &[u8]) {
let Some(§ion) = p.first() else {
return;
};
match section {
PATCH_APPLY => {
if self.imperfect.allowed {
self.patch_applied = true;
self.patch_refused = false;
}
return;
}
PATCH_CLEAR => {
self.patches.clear();
self.patch_applied = false;
self.patch_refused = false;
return;
}
_ => {}
}
if p.len() < 5 || PatchSection::from_u8(section).is_none() {
return;
}
let cfg = p[1];
let index = p[2];
let offset = u16::from_le_bytes([p[3], p[4]]);
let bytes = p[5..].to_vec();
let key = (section, cfg, index, offset);
let pos = self.patches.iter().position(|q| q.key() == key);
if bytes.is_empty() {
if let Some(i) = pos {
self.patches.remove(i);
}
return;
}
match pos {
Some(i) => {
self.patches[i] = MockPatch {
section,
cfg,
index,
offset,
bytes,
}
}
None => {
if self.patches.len() >= PATCH_MAX_ENTRIES {
return; }
self.patches.push(MockPatch {
section,
cfg,
index,
offset,
bytes,
});
}
}
}
fn apply_option_frame(&mut self, p: &[u8]) {
if p.is_empty() {
return;
}
match (p.first().copied(), &p[1..]) {
(Some(OPT_IMPERFECT), [allow, ..]) => {
self.imperfect.allowed = *allow != 0;
if !self.imperfect.allowed {
if !self.rewrites.is_empty() {
self.rewrites.clear();
self.rewrite_gen = self.rewrite_gen.wrapping_add(1);
}
self.rewrite_full = false;
self.patch_applied = false;
}
}
(Some(OPT_MOVE_RIDE), [lo, hi, ..]) => {
self.move_ride_ms = u16::from_le_bytes([*lo, *hi])
}
(Some(OPT_EMIT), [mode, lo, hi, flo, fhi, ..]) => {
if let Some(p) = match *mode {
0 => Some(EmitPace::Learned),
1 => Some(EmitPace::Interval),
2 => Some(EmitPace::Fixed(u16::from_le_bytes([*lo, *hi]))),
_ => None,
} {
let force = u16::from_le_bytes([*flo, *fhi]);
self.emit_pace = p;
self.emit_force_hz = (force != 0).then_some(force);
}
}
(Some(OPT_RENDER), [mode, full, ..]) => {
if let (Some(m), true) = (RenderMode::from_u8(*mode), *full <= 1) {
self.render_mode = m;
self.render_full = *full != 0;
}
}
(Some(OPT_SPREAD), [lo, hi, ..]) => {
self.spread_percent = u16::from_le_bytes([*lo, *hi]);
}
(Some(OPT_NAME), name) => {
self.version.name = String::from_utf8_lossy(name).into_owned()
}
(Some(OPT_BEARING), [lo, hi, mode, ..]) => {
let Some(mode) = BearingMode::from_u8(*mode) else {
return;
};
let ms = u16::from_le_bytes([*lo, *hi]);
self.bearing = Bearing {
window: (ms != 0).then(|| std::time::Duration::from_millis(ms as u64)),
mode,
};
}
_ => {}
}
}
}
fn version_payload(v: &Version) -> Vec<u8> {
let mut p = vec![0u8, v.proto_ver, v.fw_major, v.fw_minor, v.fw_patch];
p.extend_from_slice(&v.mac);
p.extend_from_slice(&v.name.as_bytes()[..v.name.len().min(NAME_MAX)]);
p
}
fn device_info_payload(m: &DeviceInfo) -> Vec<u8> {
let mut flags = 0u8;
if m.has_serial {
flags |= DI_HAS_SERIAL;
}
if m.has_bos {
flags |= DI_HAS_BOS;
}
let kind = match m.kind {
DeviceKind::Unknown => 0,
DeviceKind::Keyboard => 1,
DeviceKind::Mouse => 2,
};
let mut p = vec![2u8];
p.extend_from_slice(&m.vid.to_le_bytes());
p.extend_from_slice(&m.pid.to_le_bytes());
p.extend_from_slice(&m.bcd_device.to_le_bytes());
p.extend_from_slice(&m.bcd_usb.to_le_bytes());
p.push(flags);
p.push(kind);
p.extend_from_slice(&m.product.as_bytes()[..m.product.len().min(DEVICE_INFO_PRODUCT_MAX)]);
p
}
fn caps_payload(c: Caps) -> Vec<u8> {
let mut axis = 0u8;
if c.mouse.has_x {
axis |= CAP_X;
}
if c.mouse.has_y {
axis |= CAP_Y;
}
if c.mouse.has_wheel {
axis |= CAP_WHEEL;
}
if c.mouse.pan {
axis |= CAP_PAN;
}
if c.mouse.has_report_id {
axis |= CAP_REPORT_ID;
}
let mut kf = 0u8;
if c.keyboard.nkro {
kf |= KBC_NKRO;
}
if c.keyboard.has_consumer {
kf |= KBC_CONSUMER;
}
if c.keyboard.has_system {
kf |= KBC_SYSTEM;
}
if c.keyboard.has_report_id {
kf |= KBC_REPORT_ID;
}
let mut cd = 0u8;
if c.mouse_change_driven {
cd |= CAPS_CD_MOUSE;
}
if c.kbd_change_driven {
cd |= CAPS_CD_KBD;
}
vec![
3u8,
c.mouse.n_buttons,
axis,
c.mouse.n_hid,
c.keyboard.n_keys,
kf,
cd,
]
}
fn rate_payload(r: Rate) -> Vec<u8> {
let flags = if r.confident { RATE_CONFIDENT } else { 0 };
let mut p = vec![4u8];
p.extend_from_slice(&r.native_period_us.to_le_bytes());
p.extend_from_slice(&r.poll_period_us.to_le_bytes());
p.push(flags);
p
}
fn stats_payload(s: Stats) -> Vec<u8> {
let mut p = vec![5u8];
p.extend_from_slice(&s.inject_emits.to_le_bytes());
p.extend_from_slice(&s.tx_drops.to_le_bytes());
p.extend_from_slice(&s.tx_merges.to_le_bytes());
p.push(s.tx_maxdepth);
p.push(s.tx_wedges);
p.extend_from_slice(&s.wakeups.to_le_bytes());
p.extend_from_slice(&s.reset_count.to_le_bytes());
p.extend_from_slice(&s.config_count.to_le_bytes());
p
}
fn locks_payload(l: &Locks) -> Vec<u8> {
use crate::protocol::opcode::{LOCK_CLS_AXIS, LOCK_ID_ALL};
use crate::types::{LockScope, LockTarget};
let entries = &l.entries()[..l.entries().len().min(RESP_LOCKS_MAXN)];
let mut p = vec![6u8, entries.len() as u8];
for e in entries {
let (class, id) = match e.scope {
LockScope::Blanket(class) => (class.as_u8(), LOCK_ID_ALL),
LockScope::Target(LockTarget::Axis(a)) => (LOCK_CLS_AXIS, a.as_u16()),
LockScope::Target(LockTarget::Usage(u)) => u.class_id(),
};
p.push(class);
p.extend_from_slice(&id.to_le_bytes());
p.push(e.direction.as_u8());
p.extend_from_slice(&e.scale.to_le_bytes());
}
p
}
fn catch_resp_payload(c: &CatchState) -> Vec<u8> {
let mut p = vec![7u8, c.table_full as u8];
p.extend_from_slice(&c.dropped.to_le_bytes());
p.extend_from_slice(&c.clock.offset_us.to_le_bytes());
p.extend_from_slice(&c.clock.rate_ppb.unwrap_or(CLK_RATE_NONE).to_le_bytes());
p.extend_from_slice(&c.clock.delay_us.to_le_bytes());
let age = c
.clock
.age
.map_or(u16::MAX, |d| d.as_millis().min(u16::MAX as u128 - 1) as u16);
p.extend_from_slice(&age.to_le_bytes());
let entries = &c.entries[..c.entries.len().min(CATCH_MAXN)];
p.push(entries.len() as u8);
for e in entries {
let (class, id) = e.filter.wire();
p.push(class);
p.extend_from_slice(&id.to_le_bytes());
p.push(e.filter.direction().as_u8());
p.push(e.filter.capture().as_u8());
p.extend_from_slice(&e.dropped.to_le_bytes());
}
p
}
fn options_imperfect_payload(i: ImperfectStatus) -> Vec<u8> {
vec![
9u8,
OPT_IMPERFECT,
i.allowed as u8,
i.over_capacity as u8,
i.clone_imperfect as u8,
]
}
fn options_move_ride_payload(ms: u16) -> Vec<u8> {
let mut p = vec![9u8, OPT_MOVE_RIDE];
p.extend_from_slice(&ms.to_le_bytes());
p
}
fn options_bearing_payload(b: Bearing) -> Vec<u8> {
let mut p = vec![9u8, OPT_BEARING];
p.extend_from_slice(&crate::device::options::ride_window_ms(b.window).to_le_bytes());
p.push(b.mode.as_u8());
p
}
fn options_emit_payload(
pace: EmitPace,
render: RenderMode,
render_ready: bool,
force_hz: Option<u16>,
native_hz: u16,
allowed: bool,
) -> Vec<u8> {
let (mode, fixed_hz, mut resolved) = match pace {
EmitPace::Learned => (0u8, 0u16, 0u16),
EmitPace::Interval => (1, 0, 0),
EmitPace::Fixed(h) => {
let hz = if h == 0 { 1000 } else { h.min(1000) };
let n = (((1_000_000u32 / hz as u32) + 500) / 1000).max(1);
(2, hz, (1000 / n) as u16)
}
};
if render != RenderMode::Off && render_ready && resolved == 0 {
resolved = 1000;
}
let (advertised, active) = match force_hz.filter(|hz| *hz != 0 && allowed) {
None => (native_hz, false),
Some(hz) => {
let n = ((1000u32 + hz as u32 / 2) / hz as u32).min(128);
let mut p = 1u32;
while p * 2 <= n {
p *= 2;
}
((1000u32 / p) as u16, true)
}
};
let mut p = vec![9u8, OPT_EMIT, mode];
p.extend_from_slice(&fixed_hz.to_le_bytes());
p.extend_from_slice(&resolved.to_le_bytes());
p.extend_from_slice(&force_hz.unwrap_or(0).to_le_bytes());
p.extend_from_slice(&advertised.to_le_bytes());
p.push(active as u8);
p
}
fn options_render_payload(mode: RenderMode, full: bool, ready: bool) -> Vec<u8> {
vec![9u8, OPT_RENDER, mode.to_wire(), full as u8, ready as u8]
}
fn options_spread_payload(percent: u16, learned_us: u32) -> Vec<u8> {
let span = if percent == 0 || learned_us == 0 {
0
} else {
((learned_us as u64 * percent as u64) / 100) as u32
};
let mut p = vec![9u8, OPT_SPREAD];
p.extend_from_slice(&percent.to_le_bytes());
p.extend_from_slice(&span.to_le_bytes());
p
}
fn clip_status_payload(c: &ClipStatus, cfg: &ClipSettings) -> Vec<u8> {
let state = match c.state {
ClipState::Idle => 0u8,
ClipState::Playing => 1,
ClipState::Paused => 2,
ClipState::Faulted => 3,
};
let mut p = vec![10u8, state];
p.extend_from_slice(&c.free.to_le_bytes());
p.extend_from_slice(&c.total.to_le_bytes());
p.extend_from_slice(&c.played.to_le_bytes());
p.extend_from_slice(&c.ticks.to_le_bytes());
p.extend_from_slice(&c.underruns.to_le_bytes());
p.extend_from_slice(&c.overruns.to_le_bytes());
p.extend_from_slice(&c.seq_gaps.to_le_bytes());
let held = &c.held[..c.held.len().min(CLIP_HELD_MAX)];
p.push(held.len() as u8);
for u in held {
u.push_le(&mut p);
}
p.push(blanket_scope(&cfg.autolock));
let flags = (if cfg.loop_ { CLIP_CFG_F_LOOP } else { 0 })
| (if cfg.retain { CLIP_CFG_F_RETAIN } else { 0 })
| (if cfg.finalized {
CLIP_CFG_F_FINALIZED
} else {
0
})
| (if cfg.ride { CLIP_CFG_F_RIDE } else { 0 });
p.push(flags);
let triggers = &cfg.triggers[..cfg.triggers.len().min(CLIP_TRIG_MAX)];
p.push(triggers.len() as u8);
for t in triggers {
let (class, id) = t.on.class_id();
p.push(class);
p.extend_from_slice(&id.to_le_bytes());
p.push(t.edge.as_u8());
p.push(t.action.as_u8());
p.push(t.consume as u8);
}
p
}
fn transform_pair_ok(op: u8, sc: u8, si: u16, dc: u8, di: u16) -> bool {
if sc == dc && si == di {
return false;
}
match op {
TF_SWAP => sc == CATCH_CLS_AXIS && dc == CATCH_CLS_AXIS,
TF_REMAP => {
(sc == CATCH_CLS_AXIS && dc == CATCH_CLS_AXIS)
|| (sc == CATCH_CLS_BTN && dc == CATCH_CLS_BTN)
|| (sc == CATCH_CLS_BTN && dc == CATCH_CLS_KEY)
|| (sc == CATCH_CLS_BTN && dc == CATCH_CLS_MEDIA)
}
_ => false,
}
}
fn transforms_resp_payload(st: &State) -> Vec<u8> {
let mut p = vec![
Q_TRANSFORMS,
if st.transform_full { TF_F_FULL } else { 0x00 },
st.transforms.len() as u8,
];
for t in &st.transforms {
p.push(t.op);
p.push(t.sclass);
p.extend_from_slice(&t.sid.to_le_bytes());
p.push(t.dclass);
p.extend_from_slice(&t.did.to_le_bytes());
}
p
}
fn rewrite_resp_payload(st: &State) -> Vec<u8> {
let mut p = vec![
Q_REWRITE,
if st.rewrite_full { 0x01 } else { 0x00 },
st.rewrite_gen,
st.rewrites.len() as u8,
];
for r in &st.rewrites {
p.push(r.class);
p.extend_from_slice(&r.id.to_le_bytes());
p.push(r.dir);
p.push(r.action);
p.push(r.match_bytes.len() as u8);
p.extend_from_slice(&r.offset.to_le_bytes());
p.extend_from_slice(&(r.payload.len() as u16).to_le_bytes());
p.extend_from_slice(&r.hits.to_le_bytes());
}
p
}
fn rewrite_entry_resp_payload(st: &State, index: u8) -> Vec<u8> {
let mut p = vec![Q_REWRITE_ENTRY, index];
if let Some(r) = st.rewrites.get(index as usize) {
p.push(r.class);
p.extend_from_slice(&r.id.to_le_bytes());
p.push(r.dir);
p.push(1); p.push(r.action);
p.extend_from_slice(&r.offset.to_le_bytes());
p.push(r.match_bytes.len() as u8);
p.extend_from_slice(&r.match_bytes);
p.extend_from_slice(&r.mask);
p.extend_from_slice(&r.payload);
}
p
}
fn patches_resp_payload(st: &State) -> Vec<u8> {
let mut flags = 0u8;
if st.patch_applied {
flags |= 0x01;
}
if !st.patches.is_empty() && !st.patch_applied {
flags |= 0x02;
}
if st.patch_refused {
flags |= 0x04;
}
if st.patches.len() >= PATCH_MAX_ENTRIES {
flags |= 0x08;
}
let mut p = vec![Q_PATCHES, flags, st.patches.len() as u8];
for q in &st.patches {
p.push(q.section);
p.push(q.cfg);
p.push(q.index);
p.extend_from_slice(&q.offset.to_le_bytes());
p.extend_from_slice(&(q.bytes.len() as u16).to_le_bytes());
}
p
}
fn patch_entry_resp_payload(st: &State, index: u8) -> Vec<u8> {
let mut p = vec![Q_PATCH_ENTRY, index];
if let Some(q) = st.patches.get(index as usize) {
p.push(q.section);
p.push(q.cfg);
p.push(q.index);
p.extend_from_slice(&q.offset.to_le_bytes());
p.extend_from_slice(&q.bytes);
}
p
}
fn motion_event_payload(ts_us: u32, dx: i16, dy: i16, dz: i16, dpan: i16) -> Vec<u8> {
let mut p = Vec::with_capacity(13);
p.extend_from_slice(&ts_us.to_le_bytes());
p.push(0);
p.extend_from_slice(&dx.to_le_bytes());
p.extend_from_slice(&dy.to_le_bytes());
p.extend_from_slice(&dz.to_le_bytes());
p.extend_from_slice(&dpan.to_le_bytes());
p
}
fn usage_event_payload(
ts_us: u32,
class: Class,
direction: Direction,
usages: &[Usage],
) -> Vec<u8> {
let usages = &usages[..usages.len().min(USAGE_EVENT_MAX)];
let mut p = Vec::with_capacity(8 + 3 * usages.len());
p.extend_from_slice(&ts_us.to_le_bytes());
p.push(0); p.push(class.as_u8());
p.push(direction.as_u8());
p.push(usages.len() as u8);
for u in usages {
u.push_le(&mut p);
}
p
}
#[derive(Clone, Debug)]
pub struct MockBox {
state: Arc<Mutex<State>>,
transport: Arc<MockTransport>,
}
#[derive(Debug, Clone, Default)]
pub(crate) struct MockUpdate {
pub(crate) active: bool,
pub(crate) size: u32,
pub(crate) got: u32,
pub(crate) next_seq: u16,
pub(crate) since_ack: u16,
pub(crate) sha: [u8; 32],
pub(crate) hasher: Vec<u8>,
pub(crate) staged: bool,
pub(crate) data_seq: u8,
}
impl MockUpdate {
#[cfg(test)]
pub(crate) fn begin_for_test(&mut self, body: &[u8]) -> (u8, u32) {
self.begin(body)
}
#[cfg(test)]
pub(crate) fn data_for_test(&mut self, body: &[u8]) -> Option<(u8, u32)> {
self.data(body)
}
fn begin(&mut self, body: &[u8]) -> (u8, u32) {
if body.len() < 36 {
return (0x1A, 0);
}
if self.active {
return (0x10, 0);
}
let size = u32::from_le_bytes([body[0], body[1], body[2], body[3]]);
if size == 0 || size > MOCK_SLOT_SIZE {
return (0x12, MOCK_SLOT_SIZE);
}
self.active = true;
self.staged = false;
self.size = size;
self.got = 0;
self.next_seq = 0;
self.since_ack = 0;
self.hasher.clear();
self.sha.copy_from_slice(&body[4..36]);
(0x01, 16)
}
fn data(&mut self, body: &[u8]) -> Option<(u8, u32)> {
if body.len() < 3 {
return Some((0x1A, 1));
}
if !self.active {
return Some((0x1A, 0));
}
let seq = u16::from_le_bytes([body[0], body[1]]);
let bytes = &body[2..];
if self.next_seq > 0 && seq == self.next_seq.wrapping_sub(1) {
return Some((0x02, u32::from(self.next_seq)));
}
if seq != self.next_seq {
let want = u32::from(self.next_seq);
self.active = false;
return Some((0x13, want));
}
if bytes.is_empty() || bytes.len() > 504 {
self.active = false;
return Some((0x1A, 504));
}
if self.got + bytes.len() as u32 > self.size {
self.active = false;
return Some((0x12, self.size));
}
self.hasher.extend_from_slice(bytes);
self.got += bytes.len() as u32;
self.next_seq = self.next_seq.wrapping_add(1);
self.since_ack += 1;
if self.since_ack >= 16 || self.got == self.size {
self.since_ack = 0;
return Some((0x02, u32::from(self.next_seq)));
}
None
}
fn end(&mut self) -> (u8, u32) {
if !self.active {
return (0x1A, 0);
}
if self.got != self.size {
self.active = false;
return (0x1A, self.size - self.got);
}
let digest: [u8; 32] = Sha256::digest(&self.hasher).into();
self.active = false;
if digest != self.sha {
return (0x15, 0);
}
self.staged = true;
(0x03, self.size)
}
}
pub(crate) const MOCK_SLOT_SIZE: u32 = 0xF_0000;
fn firmware_payload(dev: &Version, staged: bool) -> Vec<u8> {
let mut p = vec![Q_FIRMWARE, dev.fw_major, dev.fw_minor, dev.fw_patch, 0, 2];
p.extend_from_slice(&[1, dev.fw_major, dev.fw_minor, dev.fw_patch, 0, 2]);
p.extend_from_slice(&MOCK_SLOT_SIZE.to_le_bytes());
p.push(u8::from(staged));
p
}
impl Default for MockBox {
fn default() -> Self {
Self::new()
}
}
impl MockBox {
pub fn new() -> Self {
let state = Arc::new(Mutex::new(State::default()));
let responder_state = Arc::clone(&state);
let transport = Arc::new(MockTransport::with_responder(move |ty, seq, payload| {
let mut st = responder_state.lock();
st.recorded.push(DecodedFrame {
ty,
seq,
payload: payload.to_vec(),
});
match ty {
FrameType::Lock => st.apply_lock_frame(payload),
FrameType::Option => st.apply_option_frame(payload),
FrameType::Rewrite => st.apply_rewrite_frame(payload),
FrameType::Patch => st.apply_patch_frame(payload),
FrameType::Transform => st.apply_transform_frame(payload),
FrameType::Reset => {
st.table = LockTable::default();
if !st.rewrites.is_empty() {
st.rewrites.clear();
st.rewrite_gen = st.rewrite_gen.wrapping_add(1);
}
st.rewrite_full = false;
st.transforms.clear();
st.transform_full = false;
}
_ => {}
}
let out: Vec<u8> = 'reply: {
if ty == FrameType::Update && st.respond {
let Some(&op) = payload.first() else {
return Vec::new();
};
let body = if payload.len() > 2 {
&payload[2..]
} else {
&[][..]
};
let answer = match op {
0 => Some(st.update.begin(body)),
1 => st.update.data(body),
2 => Some(st.update.end()),
3 => {
let seq = st.update.data_seq;
st.update = MockUpdate::default();
st.update.data_seq = seq;
Some((0x00, 0))
}
4 => {
if st.update.staged {
let seq = st.update.data_seq;
st.update = MockUpdate::default();
st.update.data_seq = seq;
Some((0x00, 0))
} else {
Some((0x19, 0))
}
}
_ => None,
};
break 'reply match answer {
Some((status, arg)) => {
let mut p = vec![op, payload.get(1).copied().unwrap_or(0), status];
p.extend_from_slice(&arg.to_le_bytes());
let rseq = if op == 1 {
let v = st.update.data_seq;
st.update.data_seq = st.update.data_seq.wrapping_add(1);
v
} else {
seq
};
encode(FrameType::UpdateResp, rseq, &p).expect("resp fits")
}
None => Vec::new(),
};
}
if ty == FrameType::Transfer && st.respond {
let ep = payload.first().copied().unwrap_or(0);
let (status, mut data) = if st.imperfect.allowed {
st.transfer_reply.clone()
} else {
(0xFC, Vec::new())
};
if status != 0 {
data.clear();
}
let mut p = vec![ep, status];
p.extend_from_slice(&data);
break 'reply encode(FrameType::TransferResp, seq, &p).expect("resp fits");
}
if ty == FrameType::Query && st.respond {
match payload.first().copied() {
Some(0) => encode(FrameType::Resp, seq, &version_payload(&st.version))
.expect("resp fits"),
Some(1) => {
let mut h = st.health;
h.rewrite_on |= !st.rewrites.is_empty();
h.patch_on |= st.patch_applied;
h.transform_on |= !st.transforms.is_empty();
let f = h.to_flags().to_le_bytes();
encode(FrameType::Resp, seq, &[1, f[0], f[1]]).expect("resp fits")
}
Some(2) => {
encode(FrameType::Resp, seq, &device_info_payload(&st.device_info))
.expect("resp fits")
}
Some(3) => {
encode(FrameType::Resp, seq, &caps_payload(st.caps)).expect("resp fits")
}
Some(4) => {
encode(FrameType::Resp, seq, &rate_payload(st.rate)).expect("resp fits")
}
Some(5) => encode(FrameType::Resp, seq, &stats_payload(st.stats))
.expect("resp fits"),
Some(6) => {
let locks = st
.locks
.clone()
.unwrap_or_else(|| st.table.pack(st.bearing.mode));
encode(FrameType::Resp, seq, &locks_payload(&locks)).expect("resp fits")
}
Some(7) => encode(FrameType::Resp, seq, &catch_resp_payload(&st.catch))
.expect("resp fits"),
Some(9) => match payload.get(1).copied() {
Some(OPT_IMPERFECT) => encode(
FrameType::Resp,
seq,
&options_imperfect_payload(st.imperfect),
)
.expect("resp fits"),
Some(OPT_MOVE_RIDE) => encode(
FrameType::Resp,
seq,
&options_move_ride_payload(st.move_ride_ms),
)
.expect("resp fits"),
Some(OPT_BEARING) => {
encode(FrameType::Resp, seq, &options_bearing_payload(st.bearing))
.expect("resp fits")
}
Some(OPT_EMIT) => encode(
FrameType::Resp,
seq,
&options_emit_payload(
st.emit_pace,
st.render_mode,
st.render_ready,
st.emit_force_hz,
st.advertised_hz,
st.imperfect.allowed,
),
)
.expect("resp fits"),
Some(OPT_RENDER) => encode(
FrameType::Resp,
seq,
&options_render_payload(
st.render_mode,
st.render_full,
st.render_ready,
),
)
.expect("resp fits"),
Some(OPT_SPREAD) => encode(
FrameType::Resp,
seq,
&options_spread_payload(st.spread_percent, st.spread_learned_us),
)
.expect("resp fits"),
_ => Vec::new(),
},
Some(11) => encode(
FrameType::Resp,
seq,
&firmware_payload(&st.version, st.update.staged),
)
.expect("resp fits"),
Some(10) => encode(
FrameType::Resp,
seq,
&clip_status_payload(&st.clip, &st.clip_settings),
)
.expect("resp fits"),
Some(12) => encode(FrameType::Resp, seq, &rewrite_resp_payload(&st))
.expect("resp fits"),
Some(13) => encode(
FrameType::Resp,
seq,
&rewrite_entry_resp_payload(&st, payload.get(1).copied().unwrap_or(0)),
)
.expect("resp fits"),
Some(14) => encode(FrameType::Resp, seq, &patches_resp_payload(&st))
.expect("resp fits"),
Some(15) => encode(
FrameType::Resp,
seq,
&patch_entry_resp_payload(&st, payload.get(1).copied().unwrap_or(0)),
)
.expect("resp fits"),
Some(16) => encode(FrameType::Resp, seq, &transforms_resp_payload(&st))
.expect("resp fits"),
_ => Vec::new(),
}
} else {
Vec::new()
}
};
if !out.is_empty() {
st.replied.push(out.clone());
}
out
}));
MockBox { state, transport }
}
#[must_use]
pub fn with_version(self, version: Version) -> Self {
self.state.lock().version = version;
self
}
#[must_use]
pub fn with_health(self, health: Health) -> Self {
self.state.lock().health = health;
self
}
#[must_use]
pub fn with_device_info(self, device_info: DeviceInfo) -> Self {
self.state.lock().device_info = device_info;
self
}
#[must_use]
pub fn with_caps(self, caps: Caps) -> Self {
self.state.lock().caps = caps;
self
}
#[must_use]
pub fn with_mouse_caps(self, mouse: MouseCaps) -> Self {
self.state.lock().caps.mouse = mouse;
self
}
#[must_use]
pub fn with_kbd_caps(self, keyboard: KbdCaps) -> Self {
let mut st = self.state.lock();
st.caps.keyboard = keyboard;
st.caps.kbd_change_driven = true;
drop(st);
self
}
#[must_use]
pub fn with_rate(self, rate: Rate) -> Self {
self.state.lock().rate = rate;
self
}
#[must_use]
pub fn with_stats(self, stats: Stats) -> Self {
self.state.lock().stats = stats;
self
}
#[must_use]
pub fn with_locks(self, locks: Locks) -> Self {
self.state.lock().locks = Some(locks);
self
}
pub fn set_locks(&self, locks: Locks) {
self.state.lock().locks = Some(locks);
}
#[must_use]
pub fn with_catch_state(self, catch: CatchState) -> Self {
self.state.lock().catch = catch;
self
}
#[must_use]
pub fn with_imperfect_status(self, imperfect: ImperfectStatus) -> Self {
self.state.lock().imperfect = imperfect;
self
}
#[must_use]
pub fn with_movement_riding(self, window: Option<std::time::Duration>) -> Self {
self.state.lock().move_ride_ms = crate::device::options::ride_window_ms(window);
self
}
pub fn set_version(&self, version: Version) {
self.state.lock().version = version;
}
pub fn set_health(&self, health: Health) {
self.state.lock().health = health;
}
pub fn set_imperfect_status(&self, imperfect: ImperfectStatus) {
self.state.lock().imperfect = imperfect;
}
pub fn with_imperfect(self, allow: bool) -> Self {
{
let mut st = self.state.lock();
st.imperfect.allowed = allow;
}
self
}
pub fn with_transfer_reply(self, status: u8, data: &[u8]) -> Self {
{
let mut st = self.state.lock();
st.transfer_reply = (status, data.to_vec());
}
self
}
pub fn set_transfer_reply(&self, status: u8, data: &[u8]) {
self.state.lock().transfer_reply = (status, data.to_vec());
}
pub fn set_movement_riding(&self, window: Option<std::time::Duration>) {
self.state.lock().move_ride_ms = crate::device::options::ride_window_ms(window);
}
#[must_use]
pub fn with_bearing(self, bearing: Bearing) -> Self {
self.state.lock().bearing = bearing;
self
}
pub fn set_bearing(&self, bearing: Bearing) {
self.state.lock().bearing = bearing;
}
#[must_use]
pub fn with_emit_pace(self, pace: EmitPace) -> Self {
self.state.lock().emit_pace = pace;
self
}
pub fn set_emit_pace(&self, pace: EmitPace) {
self.state.lock().emit_pace = pace;
}
#[must_use]
pub fn with_render(self, mode: RenderMode, full: bool) -> Self {
self.set_render(mode, full);
self
}
#[must_use]
pub fn with_render_ready(self, ready: bool) -> Self {
self.set_render_ready(ready);
self
}
pub fn set_render(&self, mode: RenderMode, full: bool) {
let mut st = self.state.lock();
st.render_mode = mode;
st.render_full = full;
}
pub fn set_render_ready(&self, ready: bool) {
self.state.lock().render_ready = ready;
}
#[must_use]
pub fn with_spread_learned(self, period_us: u32) -> Self {
self.set_spread_learned(period_us);
self
}
pub fn set_spread_learned(&self, period_us: u32) {
self.state.lock().spread_learned_us = period_us;
}
#[must_use]
pub fn with_rate_force(self, force_hz: Option<u16>) -> Self {
self.set_rate_force(force_hz);
self
}
pub fn set_rate_force(&self, force_hz: Option<u16>) {
self.state.lock().emit_force_hz = force_hz.filter(|hz| *hz != 0);
}
#[must_use]
pub fn with_advertised_hz(self, hz: u16) -> Self {
self.state.lock().advertised_hz = hz;
self
}
#[must_use]
pub fn with_clip_status(self, clip: ClipStatus) -> Self {
self.state.lock().clip = clip;
self
}
pub fn set_clip_status(&self, clip: ClipStatus) {
self.state.lock().clip = clip;
}
#[must_use]
pub fn with_clip_settings(self, settings: ClipSettings) -> Self {
self.state.lock().clip_settings = settings;
self
}
pub fn set_clip_settings(&self, settings: ClipSettings) {
self.state.lock().clip_settings = settings;
}
#[must_use]
pub fn silent(self) -> Self {
self.state.lock().respond = false;
self
}
pub fn push_raw(&self, bytes: &[u8]) {
self.transport.push_bytes(bytes);
}
pub fn push_log(&self, level: LogLevel, text: &str) {
let n = text.len().min(crate::protocol::opcode::MAX_PAYLOAD - 1);
let mut payload = Vec::with_capacity(1 + n);
payload.push(level.as_u8());
payload.extend_from_slice(&text.as_bytes()[..n]);
self.transport.push_frame(FrameType::Log, 0, &payload);
}
pub fn push_motion(&self, seq: u8, ts_us: u32, dx: i16, dy: i16, dz: i16, dpan: i16) {
self.transport.push_frame(
FrameType::MotionEvent,
seq,
&motion_event_payload(ts_us, dx, dy, dz, dpan),
);
}
#[allow(clippy::too_many_arguments)]
pub fn push_traffic(
&self,
seq: u8,
ts_us: u32,
clock: ClockDomain,
class: CatchClass,
id: u16,
direction: Direction,
flags: u8,
true_len: u16,
bytes: &[u8],
) {
let mut p = Vec::with_capacity(12 + bytes.len());
p.extend_from_slice(&ts_us.to_le_bytes());
p.push(match clock {
ClockDomain::HostChip => 0,
ClockDomain::DeviceChip => 1,
});
p.push(class.as_u8());
p.extend_from_slice(&id.to_le_bytes());
p.push(direction.as_u8());
p.push(flags);
p.extend_from_slice(&true_len.to_le_bytes());
p.extend_from_slice(&bytes[..bytes.len().min(TRAFFIC_DATA_MAX)]);
self.transport.push_frame(FrameType::TrafficEvent, seq, &p);
}
pub fn push_usages(
&self,
seq: u8,
ts_us: u32,
class: Class,
direction: Direction,
usages: &[Usage],
) {
self.transport.push_frame(
FrameType::UsageEvent,
seq,
&usage_event_payload(ts_us, class, direction, usages),
);
}
pub fn replied_frames(&self) -> Vec<DecodedFrame> {
let mut out = Vec::new();
let mut dec = crate::protocol::FrameDecoder::new();
for bytes in self.state.lock().replied.iter() {
dec.feed(bytes, |f| out.push(f));
}
out
}
pub fn recorded_frames(&self) -> Vec<DecodedFrame> {
self.state.lock().recorded.clone()
}
pub fn recorded(&self) -> usize {
self.state.lock().recorded.len()
}
pub fn saw(&self, ty: FrameType) -> bool {
self.state.lock().recorded.iter().any(|f| f.ty == ty)
}
pub fn clear_recorded(&self) {
self.state.lock().recorded.clear();
}
pub(crate) fn transport(&self) -> Arc<dyn crate::transport::Transport> {
Arc::clone(&self.transport) as Arc<dyn crate::transport::Transport>
}
}
impl crate::Device {
pub fn with_mock(mock: MockBox) -> crate::Device {
crate::Device::from_transport(mock.transport())
}
pub fn open_mock(mock: MockBox) -> crate::Result<crate::Device> {
crate::Device::open_transport(mock.transport())
}
}