<!-- Source: https://medius.k4tech.net/native/commands/rewrite -->
# Rewrite

_Match traffic in flight and change it_

[`REWRITE`](/native/commands/rewrite.md#rewrite) manages up to 32 rules on the device chip. A rule matches packets at one surface and passes, drops, patches, replaces, answers or refuses them before they reach the game PC or the real device.

```
  real device                                                        game PC

  report      --> [ HID_IN ] --> input pipeline --> [ EMIT ] -------------->
                                                        ^
                        inject, render, clip entries ---+
  vendor IN   --> [ VEND_INTR, VEND_BULK ] -------------------------------->
  IN reply    --> [ CONTROL, reply side ] --------------------------------->

  OUT packet  <-- [ HID_OUT, VEND_INTR, VEND_BULK ] <-----------------------
  request     <-- [ CONTROL, request side ] <-------------------------------
                    |
                    +-- ANSWER, STALL and NAK end the request here
```

A surface is a [`CATCH`](/native/commands/catch.md#catch) traffic class, addressed by the same `(class, id, dir)`.

| Name | Value | `id` is | Carries | Head |
| --- | --- | --- | --- | --- |
| `HID_IN` | `4` | an interface number; `0xFFFF` = every HID interface | IN | the device's report as it arrived, report ID first |
| `HID_OUT` | `5` | an endpoint number; `0xFFFF` = every HID interrupt-OUT endpoint | OUT | a report the PC writes to the device |
| `VEND_INTR` | `6` | an endpoint number; `0xFFFF` = every vendor interrupt endpoint | IN, OUT | a relayed vendor interrupt packet |
| `VEND_BULK` | `7` | an endpoint number; `0xFFFF` = every vendor bulk endpoint | IN, OUT | a relayed vendor bulk packet |
| `CONTROL` | `8` | an endpoint number, `0` = EP0; `0xFFFF` = every control endpoint | IN, OUT | the 8 SETUP bytes, then the first 8 OUT data bytes |
| `EMIT` | `9` | an endpoint number; `0xFFFF` = every cloned HID interrupt-IN endpoint | IN | the report going on the wire |
| `ANY` | `0xFF` | not compared | IN, OUT | the head at each surface a packet crosses, so a mouse report meets the rule at `HID_IN` and again at `EMIT` |

> **Warning**
>
> Rules need [`OPTION(IMPERFECT)`](/native/commands/option.md#imperfect). Otherwise the box discards every `REWRITE` but the whole-table clear; turning it off empties the table.

## REWRITE

_Install, overwrite, or remove one rule_

A rule is keyed by `(cls, id, dir, mlen, match, mask)`; setting one whose key exists overwrites it. [Opcode](/native/frame.md#opcodes) `0x1C`.

```text
REWRITE 0x1C · payload 9 + 2 x mlen + plen bytes
```

_Fire-and-forget_

#### PAYLOAD

| Offset | Field | Type | Notes |
| --- | --- | --- | --- |
| 0 | `cls` | `u8` | surface, `4`\-`9` or `0xFF`, as the [table above](/native/commands/rewrite.md) |
| 1 | `id` | `u16` | class address, little-endian; `0xFFFF` = every id in the class |
| 3 | `dir` | `u8` | `0` both, `1` IN, `2` OUT; on `CONTROL`, the request's direction |
| 4 | `state` | `u8` | `1` set (add or overwrite), `0` remove the keyed rule |
| 5 | `action` | `u8` | rule action, [`0`\-`8`](/native/commands/rewrite.md#actions) |
| 6 | `off` | `u16` | byte offset a `PATCH` or `REPLY_PATCH` writes at, little-endian |
| 8 | `mlen` | `u8` | match length, `0`\-`16`; `0` takes every packet on the address |
| 9 | `match` | `u8[]` | `mlen` bytes compared against the [packet head](/native/commands/rewrite.md) |
| 9+mlen | `mask` | `u8[]` | `mlen` bytes: a head byte ANDed with its mask byte must equal the match byte |
| 9+2 x mlen | `payload` | `u8[]` | rest of the frame, `plen` bytes: what the action writes or replies with |

`state = 0` removes the rule under that key and ignores `action`, `off` and the payload. `cls = 0xFF`, `id = 0xFFFF`, `state = 0` clears the whole table.

#### REFUSALS

| Refused when | Why |
| --- | --- |
| `OPTION(IMPERFECT)` off, for any frame but the whole-table clear | the advanced layer needs the opt-in |
| frame shorter than `9 + 2 x mlen` | malformed |
| `11 + 2 x mlen + plen` above 512 | a rule must fit its own [readback](/native/commands/requests.md#rewrite-entry) in one frame |
| `cls` is not `4`\-`9` or `0xFF` | other classes carry no packet |
| `dir` above `2` | a packet travels IN or OUT |
| `mlen` above `16` | the head compare reads at most 16 bytes |
| action invalid on `cls` | the [action table](/native/commands/rewrite.md#actions) lists each action's classes |
| `off + plen` of a `PATCH` or `REPLY_PATCH` passes 64 on a report class or `ANY`, or 2056 on `CONTROL` | the write lands past the largest packet the surface carries |
| a `REPLACE` payload above 64 bytes on a report class or `ANY` | a report is at most 64 bytes |
| all rule payloads would pass 2048 bytes | one shared pool; an overwrite is costed with its old payload returned, and a refused one keeps the old rule; [`RESP(REWRITE)`](/native/commands/requests.md#rewrite) sets its full flag |
| a 33rd rule | nothing is evicted; [`RESP(REWRITE)`](/native/commands/requests.md#rewrite) sets its full flag |
| `state = 0` with no rule under that key | nothing to remove |

#### EFFECT

A rule applies from the next packet at its surface. A refused frame changes nothing, so compare [`QUERY(REWRITE)`](/native/commands/requests.md#rewrite) with what was sent.

#### EXAMPLE

Drop every report the clone emits on endpoint 1: `cls = 9`, `id = 1`, `dir = 1`, `action = 1` (`DROP`), `mlen = 0`:

```
+--------+--------+--------+--------+--------+--------+
| A5     | 1C     | 00     | 09 00  | 09     | 01 00  |
+--------+--------+--------+--------+--------+--------+
| SOF    | TYPE   | SEQ    | LEN    | cls    | id     |
+--------+--------+--------+--------+--------+--------+

+--------+--------+--------+--------+--------+--------+
| 01     | 01     | 01     | 00 00  | 00     | lo hi  |
+--------+--------+--------+--------+--------+--------+
| dir    | state  | action | off    | mlen   | CRC16  |
+--------+--------+--------+--------+--------+--------+
```

Answer every HID `GET_REPORT` on EP0 with report ID 7's four bytes: `cls = 8`, `dir = 1`, `action = 4`, and a two-byte match on `bmRequestType` and `bRequest` under mask `FF FF`:

```
+--------+--------+--------+--------+--------+--------+
| A5     | 1C     | 01     | 11 00  | 08     | 00 00  |
+--------+--------+--------+--------+--------+--------+
| SOF    | TYPE   | SEQ    | LEN    | cls    | id     |
+--------+--------+--------+--------+--------+--------+

+--------+--------+--------+--------+--------+--------+
| 01     | 01     | 04     | 00 00  | 02     | A1 01  |
+--------+--------+--------+--------+--------+--------+
| dir    | state  | action | off    | mlen   | match  |
+--------+--------+--------+--------+--------+--------+

+--------+--------------+--------+
| FF FF  | 07 01 00 00  | lo hi  |
+--------+--------------+--------+
| mask   | payload      | CRC16  |
+--------+--------------+--------+
```

Library bindings: [`set_rewrite`](/library/advanced/rewrite.md#set-rewrite), [`remove_rewrite`](/library/advanced/rewrite.md#remove-rewrite), and [`clear_rewrite`](/library/advanced/rewrite.md#clear-rewrite).

## Actions

_Top-ranked rule's effect_

Report classes are `4`\-`7` and `9`.

| Name | Value | Effect |
| --- | --- | --- |
| `PASS` | `0` | Any class. The packet passes unchanged, and a broader rule it outranks doesn't act. |
| `DROP` | `1` | Report classes. The packet is dropped. |
| `PATCH` | `2` | Any class. Writes the payload at `off` and keeps the length; a write past the packet's end is left unapplied. |
| `REPLACE` | `3` | Any class. A report becomes the payload, length included. |
| `ANSWER` | `4` | `CONTROL`. The box completes the request itself. |
| `STALL` | `5` | `CONTROL`. The request ends in a STALL handshake. |
| `NAK` | `6` | `CONTROL`. EP0 NAKs until the PC times out; a control endpoint above 0 STALLs. |
| `REPLY_PATCH` | `7` | `CONTROL`. Writes the payload into the device's IN reply at `off`, unapplied past the reply's end. |
| `REPLY_REPLACE` | `8` | `CONTROL`. The IN reply becomes the payload, cut to `wLength`. |

#### CONTROL

On `CONTROL` an action depends on the request's direction. The reply actions apply only when the device completed the request.

| Action | IN request | OUT request |
| --- | --- | --- |
| `PASS` | proxied unchanged | proxied unchanged |
| `PATCH` | proxied unchanged | the data stage is patched at `off` before the device gets it |
| `REPLACE` | proxied unchanged | the payload overwrites the start of the data stage; `wLength` is kept |
| `ANSWER` | the payload is the reply, cut to `wLength` | the status stage is ACKed and the data goes no further |
| `STALL`, `NAK` | refused | refused |
| `REPLY_PATCH`, `REPLY_REPLACE` | the device's reply is rewritten | proxied unchanged |

> **Note**
>
> On EP0 the table matches class and vendor requests. The clone serves standard requests such as `GET_DESCRIPTOR` itself; change a descriptor with [`PATCH`](/native/commands/patch.md). A control endpoint above 0 passes every request to the table.

## Matching

_Which rule a packet reaches_

A packet shorter than `mlen` does not match. A match bit outside its mask, or a `dir` the class never carries, is stored and matches nothing.

#### RANK

Only the highest-ranked match applies to a packet.

```
  rank   1  exact (cls, id)       over  id = 0xFFFF   over  cls = 0xFF
         2  more mask bits set    over  fewer
         3  dir 1 or 2            over  dir 0
         4  lower table index

  table (every rule dir 1)                      action
    #0  EMIT  id 0xFFFF  mlen 0                 PASS
    #1  EMIT  id 1       mlen 0                 DROP
    #2  EMIT  id 1       match 02  mask FF      PATCH

  report 02 ... on endpoint 1
    +- #2  exact id, 8 mask bits      top-ranked  --> patched
    +- #1  exact id, 0 mask bits      outranked
    +- #0  id 0xFFFF                  outranked

  report 01 ... on endpoint 1
    +- #2  01 AND FF is not 02        no match
    +- #1  exact id                   top-ranked  --> dropped
    +- #0  id 0xFFFF                  outranked

  report on endpoint 2
    +- #0  id 0xFFFF                  top-ranked  --> passed
```

> **Note**
>
> An overwrite moves the rule to the end of the table with its hits at 0, so an equal-ranked rule installed earlier now outranks it. A remove or an overwrite shifts every later rule down one index; re-read the list before a [`QUERY(REWRITE_ENTRY)`](/native/commands/requests.md#rewrite-entry).

#### HITS

A rule counts one hit per packet it matches as top-ranked, `PASS` included. A `cls = 0xFF` rule counts, and its `PATCH` applies, at each surface it matches. [`RESP(REWRITE)`](/native/commands/requests.md#rewrite) reports hits saturated at 65535.

## Order

_Clip triggers, pipeline, RAW_

Each surface runs its packets through two tables before delivery.

```
  packet at a surface
        |
        v
  [ clip packet triggers ]   a consuming trigger stops the packet here
        |
        v
  [ rewrite table ]          the top-ranked match counts a hit and applies
        |
        v
  delivered                  to the game PC (IN) or the real device (OUT)
```

| Name | Behaviour |
| --- | --- |
| [packet triggers](/native/commands/clip.md#packet-triggers) | A packet can fire a trigger and then match a rule. A report a trigger consumes reaches no rule and counts no hit. |
| `HID_IN` | Buttons, keys, media and a secondary mouse's report are read from the rewritten bytes by [`LOCK`](/native/commands/lock.md), [`TRANSFORM`](/native/commands/transform.md#order) and injection. A `DROP` removes the native report; injection still emits on the frame clock. |
| `EMIT` | Acts last, on native, injected and rendered reports and a clip's entries. A rewritten report that carries no event against the last one sent is suppressed, unless the device reports every poll. |
| [`RAW`](/native/commands/raw.md) | Goes straight to the endpoint, past every rule and trigger, as a clip's [raw items](/native/commands/clip.md#items) do. |
| [`TRANSFER`](/native/commands/transfer.md) | Runs on its own messages to the device, past every rule. |

> **Warning**
>
> The host chip weighs the bound mouse's relative axes from the report as it arrived. Whenever a scale, injection or rendering changes them, the host chip's values replace a `HID_IN` rewrite of those bytes. Rewrite motion at `EMIT`.

#### CATCH

Each [`CATCH`](/native/commands/catch.md#traffic-event) tap sits at a fixed side of the table, and flags bit 7 marks a packet a rule acted on: changed, dropped, answered or refused. A `PASS` leaves it clear. A `CONTROL` event is the transaction the game PC received, on every control endpoint; each class is in [rules and taps](/native/commands/catch.md#rules).

## Lifecycle

_Rules are PC-owned session state_

The table holds until one of these empties it, on the same terms as [injection and locks](/native/injection.md#safety).

#### CLEARS ON

```
remove      a REWRITE with state = 0 and the rule's key
clear       state 0 with cls = 0xFF and id = 0xFFFF
silence     ~1 s with no control-PC frame
RESET       a RESET command
link loss   the inter-chip link drops
detach      the real device goes away
re-clone    the box clones the device again: a replug, or a patch presentation
opt-in off  OPTION(IMPERFECT) turned off
```

Any valid frame resets the silence timer, so a keepalive holds the table. The library re-asserts its held rules on keepalive and across a control-link reconnect, as for [`LOCK`](/native/commands/lock.md).

Every clear here but remove and clear moves the [`session`](/native/commands/requests.md#stats) count.

#### GEN

| Event | Effect |
| --- | --- |
| a rule added, overwritten or removed | `gen` goes up by one, wrapping at 255. |
| a re-send identical to the stored rule | `gen` stays. |
| a clear or silence that empties a non-empty table | `gen` goes up by one. |
| [`RESET`](/native/commands/admin.md#reset), link loss, detach, re-clone, opt-in off | The table and `gen` both return to `0`. |

#### READBACK

[`QUERY(REWRITE)`](/native/commands/requests.md#rewrite) returns `gen`, the full flag and a line per rule; [`QUERY(REWRITE_ENTRY)`](/native/commands/requests.md#rewrite-entry) returns one rule in this frame's shape. [`HEALTH`](/native/commands/requests.md#health) sets `REWRITE_ON` (`0x0100`) while the table is non-empty.

#### EXAMPLE

Clear the whole table:

```
+--------+--------+--------+--------+--------+--------+
| A5     | 1C     | 02     | 09 00  | FF     | FF FF  |
+--------+--------+--------+--------+--------+--------+
| SOF    | TYPE   | SEQ    | LEN    | cls    | id     |
+--------+--------+--------+--------+--------+--------+

+--------+--------+--------+--------+--------+--------+
| 00     | 00     | 00     | 00 00  | 00     | lo hi  |
+--------+--------+--------+--------+--------+--------+
| dir    | state  | action | off    | mlen   | CRC16  |
+--------+--------+--------+--------+--------+--------+
```

Library bindings: [`query_rewrite`](/library/advanced/rewrite.md#query-rewrite) and [`query_rewrite_entry`](/library/advanced/rewrite.md#query-rewrite-entry).
