use crate::protocol::opcode::{
CATCH_CLS_CONTROL, CATCH_CLS_EMIT, CATCH_CLS_HID_IN, CATCH_CLS_HID_OUT, CATCH_CLS_VEND_BULK,
CATCH_CLS_VEND_INTR, CATCH_ID_ANY, RW_ANSWER, RW_DROP, RW_NAK, RW_PASS, RW_PATCH, RW_REPLACE,
RW_REPLY_PATCH, RW_REPLY_REPLACE, RW_STALL,
};
use crate::types::Direction;
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum RewriteClass {
HidIn = CATCH_CLS_HID_IN,
HidOut = CATCH_CLS_HID_OUT,
VendorInterrupt = CATCH_CLS_VEND_INTR,
VendorBulk = CATCH_CLS_VEND_BULK,
Control = CATCH_CLS_CONTROL,
Emit = CATCH_CLS_EMIT,
Any = 0xFF,
}
impl RewriteClass {
pub fn as_u8(self) -> u8 {
self as u8
}
pub fn from_u8(v: u8) -> Option<RewriteClass> {
Some(match v {
CATCH_CLS_HID_IN => RewriteClass::HidIn,
CATCH_CLS_HID_OUT => RewriteClass::HidOut,
CATCH_CLS_VEND_INTR => RewriteClass::VendorInterrupt,
CATCH_CLS_VEND_BULK => RewriteClass::VendorBulk,
CATCH_CLS_CONTROL => RewriteClass::Control,
CATCH_CLS_EMIT => RewriteClass::Emit,
0xFF => RewriteClass::Any,
_ => return None,
})
}
pub fn is_control(self) -> bool {
matches!(self, RewriteClass::Control)
}
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
pub enum RewriteAction {
#[default]
Pass = RW_PASS,
Drop = RW_DROP,
Patch = RW_PATCH,
Replace = RW_REPLACE,
Answer = RW_ANSWER,
Stall = RW_STALL,
Nak = RW_NAK,
ReplyPatch = RW_REPLY_PATCH,
ReplyReplace = RW_REPLY_REPLACE,
}
impl RewriteAction {
pub fn as_u8(self) -> u8 {
self as u8
}
pub fn from_u8(v: u8) -> Option<RewriteAction> {
Some(match v {
RW_PASS => RewriteAction::Pass,
RW_DROP => RewriteAction::Drop,
RW_PATCH => RewriteAction::Patch,
RW_REPLACE => RewriteAction::Replace,
RW_ANSWER => RewriteAction::Answer,
RW_STALL => RewriteAction::Stall,
RW_NAK => RewriteAction::Nak,
RW_REPLY_PATCH => RewriteAction::ReplyPatch,
RW_REPLY_REPLACE => RewriteAction::ReplyReplace,
_ => return None,
})
}
pub fn carries_payload(self) -> bool {
matches!(
self,
RewriteAction::Patch
| RewriteAction::Replace
| RewriteAction::Answer
| RewriteAction::ReplyPatch
| RewriteAction::ReplyReplace
)
}
pub fn is_valid_for(self, class: RewriteClass) -> bool {
let ctl = class.is_control();
let any = matches!(class, RewriteClass::Any);
match self {
RewriteAction::Pass | RewriteAction::Patch | RewriteAction::Replace => true,
RewriteAction::Drop => !ctl && !any,
RewriteAction::Answer
| RewriteAction::Stall
| RewriteAction::Nak
| RewriteAction::ReplyPatch
| RewriteAction::ReplyReplace => ctl,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct RewriteRule {
pub class: RewriteClass,
pub id: u16,
pub direction: Direction,
pub action: RewriteAction,
pub offset: u16,
pub match_bytes: Vec<u8>,
pub mask: Vec<u8>,
pub payload: Vec<u8>,
}
impl Default for RewriteClass {
fn default() -> Self {
RewriteClass::Emit
}
}
impl RewriteRule {
pub fn new(
class: RewriteClass,
id: u16,
direction: Direction,
action: RewriteAction,
) -> RewriteRule {
RewriteRule {
class,
id,
direction,
action,
..RewriteRule::default()
}
}
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 at_offset(mut self, offset: u16) -> Self {
self.offset = offset;
self
}
pub fn with_payload(mut self, payload: impl Into<Vec<u8>>) -> Self {
self.payload = payload.into();
self
}
pub fn key(&self) -> RewriteKey {
RewriteKey {
class: self.class,
id: self.id,
direction: self.direction,
match_bytes: self.match_bytes.clone(),
mask: self.mask.clone(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RewriteKey {
pub class: RewriteClass,
pub id: u16,
pub direction: Direction,
pub match_bytes: Vec<u8>,
pub mask: Vec<u8>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RewriteEntry {
pub class: RewriteClass,
pub id: u16,
pub direction: Direction,
pub action: RewriteAction,
pub match_len: u8,
pub offset: u16,
pub payload_len: u16,
pub hits: u16,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct RewriteTable {
pub table_full: bool,
pub generation: u8,
pub entries: Vec<RewriteEntry>,
}
impl RewriteTable {
pub(crate) fn from_payload(p: &[u8]) -> Option<RewriteTable> {
if p.len() < 4 {
return None;
}
let table_full = p[1] & 0x01 != 0;
let generation = p[2];
let n = p[3] as usize;
let mut entries = Vec::with_capacity(n);
for i in 0..n {
let o = 4 + 12 * i;
let row = p.get(o..o + 12)?;
let (Some(class), Some(action)) = (
RewriteClass::from_u8(row[0]),
RewriteAction::from_u8(row[4]),
) else {
continue;
};
let Some(direction) = Direction::from_u8(row[3]) else {
continue;
};
entries.push(RewriteEntry {
class,
id: u16::from_le_bytes([row[1], row[2]]),
direction,
action,
match_len: row[5],
offset: u16::from_le_bytes([row[6], row[7]]),
payload_len: u16::from_le_bytes([row[8], row[9]]),
hits: u16::from_le_bytes([row[10], row[11]]),
});
}
Some(RewriteTable {
table_full,
generation,
entries,
})
}
}
pub(crate) fn rewrite_entry_from_payload(p: &[u8]) -> Option<RewriteRule> {
let hdr = p.get(0..11)?;
let class = RewriteClass::from_u8(hdr[2])?;
let id = u16::from_le_bytes([hdr[3], hdr[4]]);
let direction = Direction::from_u8(hdr[5])?;
let action = RewriteAction::from_u8(hdr[7])?;
let offset = u16::from_le_bytes([hdr[8], hdr[9]]);
let mlen = hdr[10] as usize;
let match_bytes = p.get(11..11 + mlen)?.to_vec();
let mask = p.get(11 + mlen..11 + 2 * mlen)?.to_vec();
let payload = p.get(11 + 2 * mlen..)?.to_vec();
Some(RewriteRule {
class,
id,
direction,
action,
offset,
match_bytes,
mask,
payload,
})
}
pub(crate) const REWRITE_CLEAR_ID: u16 = CATCH_ID_ANY;