Rewrite rules
Rewrite a matched packet in flightThe box holds a table of rules that match traffic and rewrite, answer, refuse, or drop it. A rule is addressed in the same (class, id, direction) space catch reads, in the write direction, narrowed by a masked head compare.
Rules are session state: re-asserted on reconnect like a lock, and cleared on control-PC silence, reset, a re-clone, or the opt-in going off.
native device the box (host chip | device chip = the clone) game PC
HID report ---IN---> [ HID_IN ]--> renderer --> [ EMIT ]---interrupt-IN---> reads report
^ pre-render ^ post-render, the wire
relayed <--OUT--- [ HID_OUT ]<-- relay <---------------- interrupt-OUT <-- writes report
^ VEND_INTR / VEND_BULK
control <-- EP0 -> [ CONTROL ]<-- proxy ------------------- EP0 <--------> GET_DESCRIPTOR, SET_*
^ id = endpoint number
a rule acts at any [ bracketed ] stageThe advanced control layer is gated on the imperfect-clone opt-in. With allow_imperfect_clones off, set_rewrite returns Error::ImperfectRequired.
set_rewrite, remove_rewrite, and clear_rewrite are fire-and-forget; query_rewrite reads back what the box actually holds.
set_rewrite
Install or overwrite one rulefn set_rewrite(&self, rule: &RewriteRule) -> Result<()>
Fire-and-forget
| Parameter | Type | Description |
|---|---|---|
rule | RewriteRule | The rule to install: its address, its action, and any masked match or payload. Installing one with the whole table already in use is Error::RewriteTableFull. |
A rule is keyed by (class, id, direction, match, mask); setting one whose key exists overwrites it. The crate validates a rule before sending, so a bad one is a real error rather than a frame the box drops.
use medius::{Device, Direction, RewriteRule, RewriteClass, RewriteAction};
let device = Device::find()?;
device.allow_imperfect_clones(true)?;
// Mute the clone's own wire on interrupt-IN endpoint 1.
device.set_rewrite(&RewriteRule::new(RewriteClass::Emit, 1, Direction::IN, RewriteAction::Drop))?;
// Overwrite byte 2 of the device's report on interface 0, when byte 0 is the report id 0x01.
device.set_rewrite(
&RewriteRule::new(RewriteClass::HidIn, 0, Direction::Both, RewriteAction::Patch)
.matching([0x01], [0xFF])
.at_offset(2)
.with_payload([0x00]),
)?;remove_rewrite
Drop one rulefn remove_rewrite(&self, rule: &RewriteRule) -> Result<()>
Fire-and-forget
Drops the rule keyed by this rule's (class, id, direction, match, mask); its action and payload are ignored. A no-op when no such rule is held.
let rule = RewriteRule::new(RewriteClass::Emit, 1, Direction::IN, RewriteAction::Drop);
device.set_rewrite(&rule)?;
device.remove_rewrite(&rule)?; // the same key, droppedclear_rewrite
Drop every rulefn clear_rewrite(&self) -> Result<()>
Fire-and-forget
Drops the whole table. It always clears the crate's held rules, whatever the opt-in, so a reconnect never re-asserts a rule you cleared.
device.clear_rewrite()?;query_rewrite
Read the table's summaryfn query_rewrite(&self) -> Result<RewriteTable>
Blocks
Returns a RewriteTable: a full flag, a generation counter, and a row per rule without its match, mask, or payload bytes.
let table = device.query_rewrite()?;
println!("{} rules, gen {}", table.entries.len(), table.generation);
for e in &table.entries {
println!(" {:?} id {:#04x} -> {:?}, {} hits", e.class, e.id, e.action, e.hits);
}query_rewrite_entry
Read one rule in fullfn query_rewrite_entry(&self, index: u8) -> Result<RewriteRule>
Blocks
| Parameter | Type | Description |
|---|---|---|
index | u8 | The row in the query_rewrite summary. |
Returns one RewriteRule in full, in the shape set_rewrite takes.
let table = device.query_rewrite()?;
for i in 0..table.entries.len() as u8 {
let rule = device.query_rewrite_entry(i)?; // replayable as a set
let _ = rule;
}On AsyncDevice
set_rewrite and the queries await; remove and clear fireAsyncDevice makes set_rewrite a future: it awaits the imperfect-clone opt-in check before it sends, as do query_rewrite and query_rewrite_entry. remove_rewrite and clear_rewrite carry no opt-in check and stay synchronous.
use futures::executor::block_on;
use medius::{AsyncDevice, Direction, RewriteRule, RewriteClass, RewriteAction};
let device = AsyncDevice::open("/dev/ttyACM0")?;
device.allow_imperfect_clones(true)?;
let rule = RewriteRule::new(RewriteClass::Emit, 1, Direction::IN, RewriteAction::Drop);
block_on(device.set_rewrite(&rule))?; // awaits the opt-in gate
device.remove_rewrite(&rule)?; // sync: no gate
let table = block_on(device.query_rewrite())?; // query awaits