use std::sync::Arc;
use std::sync::atomic::{AtomicU8, Ordering};
use crate::device::raw::validate_raw_direction;
use crate::error::{Error, Result};
use crate::link::Link;
use crate::link::reconcile::clip_packet_key;
use crate::protocol::command::{
clip_op_payload, clip_packet_trigger_payload, clip_set_payload, clip_trigger_payload,
};
use crate::protocol::opcode::{
CLIP_COND_ANY_CLASS, CLIP_COND_ANY_ID, CLIP_OP_CLEAR, CLIP_OP_FINALIZE, CLIP_OP_PAUSE,
CLIP_OP_RESTART, CLIP_OP_RESUME, CLIP_OP_START, CLIP_OP_STOP, CLIP_OP_TOGGLE,
CLIP_SET_AUTOLOCK, CLIP_SET_LOOP, CLIP_SET_RETAIN, CLIP_SET_RIDE, CLIP_TRIG_F_CONSUME,
CLIP_TRIG_F_PRESENT, LOCK_DIR_BOTH, MAX_PAYLOAD, PKT_MATCH_MAX, Q_CLIP,
};
use crate::protocol::opcode::{
CLIP_F_EDGES, CLIP_F_PAN, CLIP_F_RAW, CLIP_F_WHEEL, CLIP_F_XFER, CLIP_F_XY, CLIP_TAG_GAP,
};
use crate::protocol::{FrameType, Resp, parse_resp};
use crate::types::clip::ClipEntry;
use crate::types::lock::blanket_scope;
use crate::types::{
Blanket, CLIP_EDGES_MAX, CLIP_ENTRY_MAX, CLIP_RAW_MAX, ClipBuilder, ClipFrame,
ClipPacketTrigger, ClipSettings, ClipStatus, ClipTrigger, Direction, Edge, TrafficClass, Usage,
};
use super::Device;
fn encode_frame(f: &ClipFrame, out: &mut Vec<u8>) -> Result<()> {
if f.edges.len() > CLIP_EDGES_MAX {
return Err(Error::ClipFrameCount {
what: "edges",
count: f.edges.len(),
limit: CLIP_EDGES_MAX,
});
}
if f.raw.len() > CLIP_RAW_MAX {
return Err(Error::ClipFrameCount {
what: "raw reports",
count: f.raw.len(),
limit: CLIP_RAW_MAX,
});
}
for item in &f.raw {
validate_raw_direction(item.direction)?;
}
for item in &f.transfers {
let want = if item.setup.is_in() {
0
} else {
item.setup.length as usize
};
if item.out.len() != want {
return Err(Error::ClipTransferData {
want,
got: item.out.len(),
});
}
}
let len = f.byte_len();
if len > CLIP_ENTRY_MAX {
return Err(Error::ClipFrameTooLong { len });
}
let mut flags = 0u8;
if f.dx != 0 || f.dy != 0 {
flags |= CLIP_F_XY;
}
if f.wheel != 0 {
flags |= CLIP_F_WHEEL;
}
if f.pan != 0 {
flags |= CLIP_F_PAN;
}
if !f.edges.is_empty() {
flags |= CLIP_F_EDGES;
}
if !f.raw.is_empty() {
flags |= CLIP_F_RAW;
}
if !f.transfers.is_empty() {
flags |= CLIP_F_XFER;
}
if flags == 0 {
flags = CLIP_F_XY; }
out.push(flags);
if flags & CLIP_F_XY != 0 {
out.extend_from_slice(&f.dx.to_le_bytes());
out.extend_from_slice(&f.dy.to_le_bytes());
}
if flags & CLIP_F_WHEEL != 0 {
out.extend_from_slice(&f.wheel.to_le_bytes());
}
if flags & CLIP_F_PAN != 0 {
out.extend_from_slice(&f.pan.to_le_bytes());
}
if flags & CLIP_F_EDGES != 0 {
out.push(f.edges.len() as u8);
for &(usage, action) in &f.edges {
let (class, id) = usage.class_id();
out.push(class);
out.extend_from_slice(&id.to_le_bytes());
out.push(action.as_u8());
}
}
if flags & CLIP_F_RAW != 0 {
out.push(f.raw.len() as u8);
for item in &f.raw {
out.push(item.ep & 0x0f);
out.push(item.direction.as_u8());
out.extend_from_slice(&(item.bytes.len() as u16).to_le_bytes());
out.extend_from_slice(&item.bytes);
}
}
if flags & CLIP_F_XFER != 0 {
out.push(f.transfers.len() as u8);
for item in &f.transfers {
out.push(item.ep);
out.extend_from_slice(&item.setup.to_bytes());
out.extend_from_slice(&item.out);
}
}
Ok(())
}
pub(crate) fn encode_chunks(clip: &ClipBuilder, limit: usize) -> Result<Vec<Vec<u8>>> {
let mut out: Vec<Vec<u8>> = Vec::new();
let mut cur: Vec<u8> = Vec::new();
let mut entry: Vec<u8> = Vec::new();
for e in &clip.entries {
entry.clear();
match e {
ClipEntry::Gap(n) => {
entry.push(CLIP_TAG_GAP);
entry.extend_from_slice(&n.to_le_bytes());
}
ClipEntry::Frame(f) => encode_frame(f, &mut entry)?,
}
if !cur.is_empty() && cur.len() + entry.len() > limit {
out.push(std::mem::take(&mut cur));
}
cur.extend_from_slice(&entry);
}
if !cur.is_empty() {
out.push(cur);
}
Ok(out)
}
pub(crate) fn validate_packet_key(t: &ClipPacketTrigger) -> Result<()> {
let refuse = |reason| Err(Error::ClipPacketTrigger { reason });
if !ClipPacketTrigger::is_surface(t.class) {
return refuse(
"names a surface packets cross: HidIn, HidOut, VendorInterrupt, VendorBulk, Control or Emit",
);
}
if t.direction.is_relative() {
return Err(Error::RelativeDirection {
direction: t.direction,
what: "clip packet trigger",
});
}
if t.match_bytes.len() != t.mask.len() {
return refuse("takes a match and a mask of one length");
}
if t.match_bytes.len() > PKT_MATCH_MAX {
return refuse("compares at most 16 match bytes");
}
if t.direction != Direction::Both && !ClipPacketTrigger::class_carries(t.class, t.direction) {
return refuse(
"names a direction its class never carries: HidIn and Emit flow IN, HidOut flows OUT",
);
}
if t.match_bytes.iter().zip(&t.mask).any(|(m, k)| m & !k != 0) {
return refuse(
"has a match bit outside its mask, which no packet can equal: a packet byte is masked before it is compared",
);
}
Ok(())
}
pub(crate) fn validate_packet_trigger(t: &ClipPacketTrigger) -> Result<()> {
let refuse = |reason| Err(Error::ClipPacketTrigger { reason });
validate_packet_key(t)?;
if t.consume && t.class == TrafficClass::Control {
return refuse(
"takes consume on a report or vendor class; a Control transfer always runs to completion",
);
}
if !t.once_per_run {
return if t.selector_len == 0 {
Ok(())
} else {
refuse("has a selector length only with once_per_run")
};
}
let one_stream = t.class != TrafficClass::Control
&& t.id != ClipPacketTrigger::ANY_ID
&& t.direction != Direction::Both;
if !one_stream {
return refuse(
"needs one stream for once_per_run to have a run over: a report class, a concrete id, and IN or OUT",
);
}
if t.selector_len as usize >= t.match_bytes.len() {
return refuse(
"needs match bytes past its selector: they are the condition the run is over",
);
}
if t.mask[t.selector_len as usize..].iter().all(|&k| k == 0) {
return refuse(
"needs a masked bit past its selector: a condition every packet of the stream meets is a run that never ends",
);
}
Ok(())
}
impl Device {
pub fn clip(&self) -> ClipHandle {
ClipHandle {
link: self.link.clone(),
seq: Arc::new(AtomicU8::new(0)),
}
}
}
#[derive(Clone, Debug)]
pub struct ClipHandle {
link: Link,
seq: Arc<AtomicU8>,
}
impl ClipHandle {
#[cfg_attr(not(feature = "async"), allow(dead_code))]
pub(crate) fn link(&self) -> &Link {
&self.link
}
fn send_chunk(&self, chunk: &[u8]) -> Result<()> {
let seq = self.seq.fetch_add(1, Ordering::Relaxed);
self.link.send_with_seq(seq, FrameType::ClipAppend, chunk)
}
pub fn append(&self, clip: &ClipBuilder) -> Result<()> {
let chunks = encode_chunks(clip, MAX_PAYLOAD)?;
let _serial = self.link.reassert_guard();
for chunk in chunks {
self.send_chunk(&chunk)?;
self.link.desired().lock().clip_loaded(true);
}
Ok(())
}
fn ctrl(&self, op: u8) -> Result<()> {
self.link.send(FrameType::ClipCtrl, &clip_op_payload(op))
}
fn set(&self, id: u8, value: u8) -> Result<()> {
let _serial = self.link.reassert_guard();
self.link
.send(FrameType::ClipSet, &clip_set_payload(id, value))?;
self.link.desired().lock().clip_setting(id, value);
Ok(())
}
pub fn set_autolock(&self, scope: &[Blanket]) -> Result<()> {
self.set(CLIP_SET_AUTOLOCK, blanket_scope(scope))
}
pub fn set_loop(&self, on: bool) -> Result<()> {
self.set(CLIP_SET_LOOP, on as u8)
}
pub fn set_retain(&self, on: bool) -> Result<()> {
self.set(CLIP_SET_RETAIN, on as u8)
}
pub fn set_ride(&self, on: bool) -> Result<()> {
self.set(CLIP_SET_RIDE, on as u8)
}
pub fn bind(&self, trigger: ClipTrigger) -> Result<()> {
let (class, id) = trigger.on.class_id();
let flags = CLIP_TRIG_F_PRESENT
| if trigger.consume {
CLIP_TRIG_F_CONSUME
} else {
0
};
let _serial = self.link.reassert_guard();
self.link.send(
FrameType::ClipTrigger,
&clip_trigger_payload(
class,
id,
trigger.edge.as_u8(),
trigger.action.as_u8(),
flags,
),
)?;
self.link
.desired()
.lock()
.clip_trigger((class, id, trigger.edge.as_u8()), true);
Ok(())
}
pub fn unbind(&self, usage: impl Into<Usage>, edge: Edge) -> Result<()> {
let (class, id) = usage.into().class_id();
let _serial = self.link.reassert_guard();
self.link.send(
FrameType::ClipTrigger,
&clip_trigger_payload(class, id, edge.as_u8(), 0, 0),
)?;
self.link
.desired()
.lock()
.clip_trigger((class, id, edge.as_u8()), false);
Ok(())
}
pub fn bind_packet(&self, trigger: &ClipPacketTrigger) -> Result<()> {
validate_packet_trigger(trigger)?;
let _serial = self.link.reassert_guard();
self.link.send(
FrameType::ClipTrigger,
&clip_packet_trigger_payload(
trigger.class.as_u8(),
trigger.id,
trigger.direction.as_u8(),
trigger.action.as_u8(),
CLIP_TRIG_F_PRESENT | trigger.flags(),
trigger.selector_len,
&trigger.match_bytes,
&trigger.mask,
),
)?;
self.link
.desired()
.lock()
.clip_packet_bind(clip_packet_key(trigger), trigger.consume);
Ok(())
}
pub fn unbind_packet(&self, trigger: &ClipPacketTrigger) -> Result<()> {
validate_packet_key(trigger)?;
let _serial = self.link.reassert_guard();
self.link.send(
FrameType::ClipTrigger,
&clip_packet_trigger_payload(
trigger.class.as_u8(),
trigger.id,
trigger.direction.as_u8(),
0,
0,
0,
&trigger.match_bytes,
&trigger.mask,
),
)?;
self.link
.desired()
.lock()
.clip_packet_unbind(&clip_packet_key(trigger));
Ok(())
}
pub fn clear_triggers(&self) -> Result<()> {
let _serial = self.link.reassert_guard();
self.link.send(
FrameType::ClipTrigger,
&clip_trigger_payload(CLIP_COND_ANY_CLASS, CLIP_COND_ANY_ID, LOCK_DIR_BOTH, 0, 0),
)?;
self.link.desired().lock().clip_triggers_clear();
Ok(())
}
pub fn start(&self) -> Result<()> {
self.ctrl(CLIP_OP_START)
}
pub fn stop(&self) -> Result<()> {
self.ctrl(CLIP_OP_STOP)
}
pub fn pause(&self) -> Result<()> {
self.ctrl(CLIP_OP_PAUSE)
}
pub fn resume(&self) -> Result<()> {
self.ctrl(CLIP_OP_RESUME)
}
pub fn restart(&self) -> Result<()> {
self.ctrl(CLIP_OP_RESTART)
}
pub fn toggle(&self) -> Result<()> {
self.ctrl(CLIP_OP_TOGGLE)
}
pub fn clear(&self) -> Result<()> {
let _serial = self.link.reassert_guard();
self.ctrl(CLIP_OP_CLEAR)?;
self.link.desired().lock().clip_loaded(false);
Ok(())
}
pub fn finalize(&self) -> Result<()> {
self.ctrl(CLIP_OP_FINALIZE)
}
pub fn query_status(&self) -> Result<ClipStatus> {
let payload = self.link.query(Q_CLIP)?;
match parse_resp(&payload) {
Some(Resp::Clip(s)) => Ok(s),
_ => Err(Error::NoReply),
}
}
pub fn query_config(&self) -> Result<ClipSettings> {
let payload = self.link.query(Q_CLIP)?;
ClipSettings::from_payload(&payload).ok_or(Error::NoReply)
}
}