Medius - Rust LibraryRewrite rules

Rewrite rules

Rewrite a matched packet in flight

The 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 ] stage

The 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 rule
fn set_rewrite(&self, rule: &RewriteRule) -> Result<()>

Fire-and-forget

ParameterTypeDescription
ruleRewriteRuleThe 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.

EXAMPLE
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 rule
fn 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.

EXAMPLE
let rule = RewriteRule::new(RewriteClass::Emit, 1, Direction::IN, RewriteAction::Drop);
device.set_rewrite(&rule)?;
device.remove_rewrite(&rule)?; // the same key, dropped

clear_rewrite

Drop every rule
fn 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.

EXAMPLE
device.clear_rewrite()?;

query_rewrite

Read the table's summary
fn 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.

EXAMPLE
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 full
fn query_rewrite_entry(&self, index: u8) -> Result<RewriteRule>

Blocks

ParameterTypeDescription
indexu8The row in the query_rewrite summary.

Returns one RewriteRule in full, in the shape set_rewrite takes.

EXAMPLE
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 fire

AsyncDevice 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.

EXAMPLE
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