use crate::protocol::opcode::{
CATCH_ID_ANY, CLIP_CFG_F_FINALIZED, CLIP_CFG_F_LOOP, CLIP_CFG_F_RETAIN, CLIP_CFG_F_RIDE,
CLIP_OP_PAUSE, CLIP_OP_RESTART, CLIP_OP_RESUME, CLIP_OP_START, CLIP_OP_STOP, CLIP_OP_TOGGLE,
CLIP_PKT_TRIG_ENTRY, CLIP_PKT_TRIG_MAX, CLIP_TRIG_F_CONSUME, CLIP_TRIG_F_RUN, CLIP_TRIG_MAX,
LOCK_DIR_BOTH, LOCK_DIR_NEG, LOCK_DIR_POS, MAX_PAYLOAD, PKT_MATCH_MAX, RESP_CLIP_HDR,
};
use crate::types::lock::blanket_from_scope;
use crate::types::{Action, Blanket, CatchClass, Class, Direction, Setup, TrafficClass, Usage};
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Edge {
Both = LOCK_DIR_BOTH,
Press = LOCK_DIR_POS,
Release = LOCK_DIR_NEG,
}
impl Edge {
pub(crate) fn as_u8(self) -> u8 {
self as u8
}
pub(crate) fn from_u8(v: u8) -> Option<Edge> {
Some(match v {
LOCK_DIR_BOTH => Edge::Both,
LOCK_DIR_POS => Edge::Press,
LOCK_DIR_NEG => Edge::Release,
_ => return None,
})
}
}
impl From<Edge> for Direction {
fn from(e: Edge) -> Direction {
match e {
Edge::Both => Direction::Both,
Edge::Press => Direction::Positive,
Edge::Release => Direction::Negative,
}
}
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ClipAction {
Start = CLIP_OP_START,
Stop = CLIP_OP_STOP,
Pause = CLIP_OP_PAUSE,
Resume = CLIP_OP_RESUME,
Restart = CLIP_OP_RESTART,
Toggle = CLIP_OP_TOGGLE,
}
impl ClipAction {
pub(crate) fn as_u8(self) -> u8 {
self as u8
}
pub(crate) fn from_u8(v: u8) -> Option<ClipAction> {
Some(match v {
CLIP_OP_START => ClipAction::Start,
CLIP_OP_STOP => ClipAction::Stop,
CLIP_OP_PAUSE => ClipAction::Pause,
CLIP_OP_RESUME => ClipAction::Resume,
CLIP_OP_RESTART => ClipAction::Restart,
CLIP_OP_TOGGLE => ClipAction::Toggle,
_ => return None,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ClipTrigger {
pub on: Usage,
pub edge: Edge,
pub action: ClipAction,
pub consume: bool,
}
impl ClipTrigger {
pub fn new(on: impl Into<Usage>, edge: Edge, action: ClipAction) -> ClipTrigger {
ClipTrigger {
on: on.into(),
edge,
action,
consume: false,
}
}
pub fn consume(mut self) -> ClipTrigger {
self.consume = true;
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ClipPacketTrigger {
pub class: TrafficClass,
pub id: u16,
pub direction: Direction,
pub action: ClipAction,
pub match_bytes: Vec<u8>,
pub mask: Vec<u8>,
pub consume: bool,
pub once_per_run: bool,
pub selector_len: u8,
}
impl ClipPacketTrigger {
pub const ANY_ID: u16 = CATCH_ID_ANY;
pub fn new(
class: TrafficClass,
id: u16,
direction: Direction,
action: ClipAction,
) -> ClipPacketTrigger {
ClipPacketTrigger {
class,
id,
direction,
action,
match_bytes: Vec::new(),
mask: Vec::new(),
consume: false,
once_per_run: false,
selector_len: 0,
}
}
pub fn matching(mut self, match_bytes: impl Into<Vec<u8>>, mask: impl Into<Vec<u8>>) -> Self {
self.match_bytes = match_bytes.into();
self.mask = mask.into();
self
}
pub fn consume(mut self) -> Self {
self.consume = true;
self
}
pub fn once_per_run(mut self, selector_len: u8) -> Self {
self.once_per_run = true;
self.selector_len = selector_len;
self
}
pub(crate) fn is_surface(class: TrafficClass) -> bool {
!matches!(class, TrafficClass::Bus | TrafficClass::ClipTransfer)
}
pub(crate) fn class_carries(class: TrafficClass, direction: Direction) -> bool {
match class {
TrafficClass::HidIn | TrafficClass::Emit => direction == Direction::IN,
TrafficClass::HidOut => direction == Direction::OUT,
TrafficClass::VendorInterrupt | TrafficClass::VendorBulk | TrafficClass::Control => {
matches!(direction, Direction::IN | Direction::OUT)
}
TrafficClass::Bus | TrafficClass::ClipTransfer => false,
}
}
pub(crate) fn flags(&self) -> u8 {
(if self.consume { CLIP_TRIG_F_CONSUME } else { 0 })
| (if self.once_per_run {
CLIP_TRIG_F_RUN
} else {
0
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ClipPacketTriggerEntry {
pub trigger: ClipPacketTrigger,
pub hits: u16,
}
impl ClipPacketTriggerEntry {
fn from_wire(e: &[u8]) -> Option<ClipPacketTriggerEntry> {
let class = TrafficClass::try_from(CatchClass::from_u8(e[0])?).ok()?;
let direction = Direction::from_u8(e[3]).filter(|d| !d.is_relative())?;
let action = ClipAction::from_u8(e[4])?;
if !ClipPacketTrigger::is_surface(class) {
return None;
}
let mlen = e[7] as usize;
let body = &e[CLIP_PKT_TRIG_ENTRY..];
Some(ClipPacketTriggerEntry {
trigger: ClipPacketTrigger {
class,
id: u16::from_le_bytes([e[1], e[2]]),
direction,
action,
match_bytes: body[..mlen].to_vec(),
mask: body[mlen..].to_vec(),
consume: e[5] & CLIP_TRIG_F_CONSUME != 0,
once_per_run: e[5] & CLIP_TRIG_F_RUN != 0,
selector_len: e[6],
},
hits: u16::from_le_bytes([e[8], e[9]]),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum ClipState {
#[default]
Idle,
Playing,
Paused,
Faulted,
}
impl ClipState {
pub(crate) fn from_u8(v: u8) -> Option<ClipState> {
Some(match v {
0 => ClipState::Idle,
1 => ClipState::Playing,
2 => ClipState::Paused,
3 => ClipState::Faulted,
_ => return None,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
pub struct ClipStatus {
pub state: ClipState,
pub free: u32,
pub total: u32,
pub played: u32,
pub ticks: u32,
pub underruns: u16,
pub overruns: u16,
pub seq_gaps: u16,
pub xfers: u16,
pub xfer_errs: u16,
pub gated: u16,
pub held: Vec<Usage>,
}
fn clip_config_offset(p: &[u8]) -> Option<usize> {
if p.len() < RESP_CLIP_HDR {
return None;
}
let cfg = RESP_CLIP_HDR + p[RESP_CLIP_HDR - 1] as usize * 3;
let n_trig = *p.get(cfg + 2)? as usize;
if n_trig > CLIP_TRIG_MAX {
return None;
}
let mut end = cfg + 3 + n_trig * 6;
let n_pkt = *p.get(end)? as usize;
if n_pkt > CLIP_PKT_TRIG_MAX {
return None;
}
end += 1;
for _ in 0..n_pkt {
let mlen = *p.get(end + 7)? as usize;
if mlen > PKT_MATCH_MAX {
return None;
}
end += CLIP_PKT_TRIG_ENTRY + 2 * mlen;
}
(p.len() == end).then_some(cfg)
}
impl ClipStatus {
pub fn is_held(&self, usage: impl Into<Usage>) -> bool {
let u = usage.into();
self.held.contains(&u)
}
pub(crate) fn from_payload(p: &[u8]) -> Option<ClipStatus> {
clip_config_offset(p)?;
let held = Usage::decode_list(&p[RESP_CLIP_HDR - 1..])?;
Some(ClipStatus {
state: ClipState::from_u8(p[1])?,
free: u32::from_le_bytes([p[2], p[3], p[4], p[5]]),
total: u32::from_le_bytes([p[6], p[7], p[8], p[9]]),
played: u32::from_le_bytes([p[10], p[11], p[12], p[13]]),
ticks: u32::from_le_bytes([p[14], p[15], p[16], p[17]]),
underruns: u16::from_le_bytes([p[18], p[19]]),
overruns: u16::from_le_bytes([p[20], p[21]]),
seq_gaps: u16::from_le_bytes([p[22], p[23]]),
xfers: u16::from_le_bytes([p[24], p[25]]),
xfer_errs: u16::from_le_bytes([p[26], p[27]]),
gated: u16::from_le_bytes([p[28], p[29]]),
held,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
pub struct ClipSettings {
pub autolock: Vec<Blanket>,
pub loop_: bool,
pub retain: bool,
pub finalized: bool,
pub ride: bool,
pub triggers: Vec<ClipTrigger>,
pub packet_triggers: Vec<ClipPacketTriggerEntry>,
}
impl ClipSettings {
pub(crate) fn from_payload(p: &[u8]) -> Option<ClipSettings> {
let mut off = clip_config_offset(p)?;
let autolock = blanket_from_scope(p[off]);
let flags = p[off + 1];
let n_trig = p[off + 2] as usize;
off += 3;
let mut triggers = Vec::with_capacity(n_trig);
for _ in 0..n_trig {
if let (Some(class), Some(edge), Some(action)) = (
Class::from_u8(p[off]),
Edge::from_u8(p[off + 3]),
ClipAction::from_u8(p[off + 4]),
) {
let id = u16::from_le_bytes([p[off + 1], p[off + 2]]);
triggers.push(ClipTrigger {
on: Usage::new(class, id),
edge,
action,
consume: p[off + 5] != 0,
});
}
off += 6;
}
let n_pkt = p[off] as usize;
off += 1;
let mut packet_triggers = Vec::with_capacity(n_pkt);
for _ in 0..n_pkt {
let end = off + CLIP_PKT_TRIG_ENTRY + 2 * p[off + 7] as usize;
packet_triggers.extend(ClipPacketTriggerEntry::from_wire(&p[off..end]));
off = end;
}
Some(ClipSettings {
autolock,
loop_: flags & CLIP_CFG_F_LOOP != 0,
retain: flags & CLIP_CFG_F_RETAIN != 0,
finalized: flags & CLIP_CFG_F_FINALIZED != 0,
ride: flags & CLIP_CFG_F_RIDE != 0,
triggers,
packet_triggers,
})
}
}
pub const CLIP_EDGES_MAX: usize = 8;
pub const CLIP_RAW_MAX: usize = 8;
pub const CLIP_ENTRY_MAX: usize = MAX_PAYLOAD;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct RawItem {
pub(crate) ep: u8,
pub(crate) direction: Direction,
pub(crate) bytes: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct TransferItem {
pub(crate) ep: u8,
pub(crate) setup: Setup,
pub(crate) out: Vec<u8>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ClipFrame {
pub(crate) dx: i16,
pub(crate) dy: i16,
pub(crate) wheel: i16,
pub(crate) pan: i16,
pub(crate) edges: Vec<(Usage, Action)>,
pub(crate) raw: Vec<RawItem>,
pub(crate) transfers: Vec<TransferItem>,
}
impl ClipFrame {
pub fn new() -> ClipFrame {
ClipFrame::default()
}
pub fn move_by(mut self, dx: i16, dy: i16) -> ClipFrame {
self.dx = dx;
self.dy = dy;
self
}
pub fn wheel(mut self, dz: i16) -> ClipFrame {
self.wheel = dz;
self
}
pub fn pan(mut self, dpan: i16) -> ClipFrame {
self.pan = dpan;
self
}
pub fn edge(mut self, usage: impl Into<Usage>, action: Action) -> ClipFrame {
self.edges.push((usage.into(), action));
self
}
pub fn press(self, usage: impl Into<Usage>) -> ClipFrame {
self.edge(usage, Action::Press)
}
pub fn release(self, usage: impl Into<Usage>) -> ClipFrame {
self.edge(usage, Action::SoftRelease)
}
pub fn force_release(self, usage: impl Into<Usage>) -> ClipFrame {
self.edge(usage, Action::ForceRelease)
}
pub fn raw(mut self, ep: u8, direction: Direction, bytes: impl Into<Vec<u8>>) -> ClipFrame {
self.raw.push(RawItem {
ep,
direction,
bytes: bytes.into(),
});
self
}
pub fn transfer(mut self, ep: u8, setup: Setup, out: impl Into<Vec<u8>>) -> ClipFrame {
self.transfers.push(TransferItem {
ep,
setup,
out: out.into(),
});
self
}
pub fn byte_len(&self) -> usize {
let motion = (self.wheel != 0) as usize * 2 + (self.pan != 0) as usize * 2;
let items = !self.edges.is_empty() as usize * (1 + self.edges.len() * 4)
+ self.raw.iter().map(|r| 4 + r.bytes.len()).sum::<usize>()
+ !self.raw.is_empty() as usize
+ self
.transfers
.iter()
.map(|t| 9 + t.out.len())
.sum::<usize>()
+ !self.transfers.is_empty() as usize;
let xy = (self.dx != 0 || self.dy != 0 || motion + items == 0) as usize * 4;
1 + xy + motion + items
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum ClipEntry {
Gap(u16),
Frame(ClipFrame),
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct ClipBuilder {
pub(crate) entries: Vec<ClipEntry>,
}
impl ClipBuilder {
pub fn new() -> ClipBuilder {
ClipBuilder::default()
}
pub fn gap(&mut self, frames: u16) -> &mut Self {
if frames != 0 {
self.entries.push(ClipEntry::Gap(frames));
}
self
}
pub fn frame(&mut self, frame: ClipFrame) -> &mut Self {
self.entries.push(ClipEntry::Frame(frame));
self
}
pub fn move_by(&mut self, dx: i16, dy: i16) -> &mut Self {
self.frame(ClipFrame::new().move_by(dx, dy))
}
pub fn wheel(&mut self, dz: i16) -> &mut Self {
self.frame(ClipFrame::new().wheel(dz))
}
pub fn pan(&mut self, dpan: i16) -> &mut Self {
self.frame(ClipFrame::new().pan(dpan))
}
pub fn edge(&mut self, input: impl Into<Usage>, action: Action) -> &mut Self {
self.frame(ClipFrame::new().edge(input, action))
}
pub fn press(&mut self, usage: impl Into<Usage>) -> &mut Self {
self.edge(usage, Action::Press)
}
pub fn release(&mut self, usage: impl Into<Usage>) -> &mut Self {
self.edge(usage, Action::SoftRelease)
}
pub fn force_release(&mut self, usage: impl Into<Usage>) -> &mut Self {
self.edge(usage, Action::ForceRelease)
}
pub fn raw(&mut self, ep: u8, direction: Direction, bytes: impl Into<Vec<u8>>) -> &mut Self {
self.frame(ClipFrame::new().raw(ep, direction, bytes))
}
pub fn transfer(&mut self, ep: u8, setup: Setup, out: impl Into<Vec<u8>>) -> &mut Self {
self.frame(ClipFrame::new().transfer(ep, setup, out))
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn byte_len(&self) -> usize {
self.entries
.iter()
.map(|e| match e {
ClipEntry::Gap(_) => 3,
ClipEntry::Frame(f) => f.byte_len(),
})
.sum()
}
pub fn clear(&mut self) {
self.entries.clear();
}
}