v1.3.0: Add 14 keyfob protocols (8 ProtoPirate + 6 Flipper-ARF), fix KeeLoq overflow
Deploy to Pages / build (push) Waiting to run
Deploy to Pages / deploy (push) Blocked by required conditions

Brings KAT to 32 protocol decoders. Ported from the ProtoPirate reference and the
D4C1-Labs/Flipper-ARF firmware:

  ProtoPirate (8): Kia V7, Ford V1, Ford V2, Ford V3, Chrysler V0, Honda Static,
  Honda V1, Land Rover V0.
  Flipper-ARF (6): Toyota, Land Rover RKE, Mazda Siemens, BMW CAS4,
  Porsche Cayenne, PSA2.

Each decoder is gated (CRC / checksum / fixed markers / frame structure) so it
cannot false-match existing protocols; every previously-decoding IMPORTS capture
decodes unchanged. Real-capture decodes added for Ford V3 (LDV T80), Honda Static
(Honda), Toyota (Camry NRZ variant), and PSA2 (Groupe PSA, serial 0x99EB25); the
rest are validated by encode/decode round-trip and synthetic-frame tests.

Also fixes a debug-only underflow panic in keeloq_common::keeloq_decrypt
(15 - r underflowed; now wrapping_sub to match the reference's unsigned wrap;
release behavior unchanged).

Adds per-protocol docs, updates the README protocol table, capture metadata
(encoding / RF / encryption), make-suggestion mapping, and CHANGELOG.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JEfaKqzB3T4qsrybwfEi73
This commit is contained in:
KaraZajac
2026-06-23 20:08:54 -04:00
parent 118eca0948
commit 754af4134a
37 changed files with 9157 additions and 7 deletions
+14
View File
@@ -12,18 +12,32 @@ This folder describes how each keyfob protocol supported by KAT works. Each docu
| Kia V3/V4 | `kia_v3_v4.rs` | [kia_v3_v4.md](kia_v3_v4.md) |
| Kia V5 | `kia_v5.rs` | [kia_v5.md](kia_v5.md) |
| Kia V6 | `kia_v6.rs` | [kia_v6.md](kia_v6.md) |
| Kia V7 | `kia_v7.rs` | [kia_v7.md](kia_v7.md) |
| Ford V0 | `ford_v0.rs` | [ford_v0.md](ford_v0.md) |
| Ford V1 | `ford_v1.rs` | [ford_v1.md](ford_v1.md) |
| Ford V2 | `ford_v2.rs` | [ford_v2.md](ford_v2.md) |
| Ford V3 | `ford_v3.rs` | [ford_v3.md](ford_v3.md) |
| Honda Static | `honda_static.rs` | [honda_static.md](honda_static.md) |
| Honda V1 | `honda_v1.rs` | [honda_v1.md](honda_v1.md) |
| Chrysler V0 | `chrysler_v0.rs` | [chrysler_v0.md](chrysler_v0.md) |
| Subaru | `subaru.rs` | [subaru.md](subaru.md) |
| Toyota | `toyota.rs` | [toyota.md](toyota.md) |
| VAG | `vag.rs` | [vag.md](vag.md) |
| Fiat V0 | `fiat_v0.rs` | [fiat_v0.md](fiat_v0.md) |
| Fiat V1 | `fiat_v1.rs` | [fiat_v1.md](fiat_v1.md) |
| Mazda V0 | `mazda_v0.rs` | [mazda_v0.md](mazda_v0.md) |
| Mazda Siemens | `mazda_siemens.rs` | [mazda_siemens.md](mazda_siemens.md) |
| Mitsubishi V0 | `mitsubishi_v0.rs` | [mitsubishi_v0.md](mitsubishi_v0.md) |
| Land Rover V0 | `land_rover_v0.rs` | [land_rover_v0.md](land_rover_v0.md) |
| Land Rover RKE | `land_rover_rke.rs` | [land_rover_rke.md](land_rover_rke.md) |
| BMW CAS4 | `bmw_cas4.rs` | [bmw_cas4.md](bmw_cas4.md) |
| Porsche Touareg | `porsche_touareg.rs` | [porsche_touareg.md](porsche_touareg.md) |
| Porsche Cayenne | `porsche_cayenne.rs` | [porsche_cayenne.md](porsche_cayenne.md) |
| Suzuki | `suzuki.rs` | [suzuki.md](suzuki.md) |
| Scher-Khan | `scher_khan.rs` | [scher_khan.md](scher_khan.md) |
| Star Line | `star_line.rs` | [star_line.md](star_line.md) |
| PSA | `psa.rs` | [psa.md](psa.md) |
| PSA2 (PSA OLD) | `psa2.rs` | [psa2.md](psa2.md) |
| KeeLoq generic (fallback) | `keeloq_generic.rs` | [keeloq_generic.md](keeloq_generic.md) |
**KeeLoq generic** is not a registered decoder; it runs when no protocol matches and tries KeeLoq with every keystore manufacturer key (using `keeloq_common`). Successful decodes appear as **Keeloq (*keystore name*)**.
+64
View File
@@ -0,0 +1,64 @@
---
layout: default
---
# BMW CAS4 Protocol
**Rust module:** `src/protocols/bmw_cas4.rs`
**Reference:** `Flipper-ARF lib/subghz/protocols/bmw_cas4.c`
## Overview
BMW CAS4 uses Manchester encoding at 500/1000 µs. 64 bits (8 bytes), AM/OOK. The CAS4 rolling cipher's
manufacturer key is not available, so the encrypted portion is left as-is — the frame is only framed and
validated, not decrypted. Emission is gated on two fixed marker bytes (`byte[0] == 0x30` and
`byte[6] == 0xC5`), which makes the protocol specific and prevents false matches.
The Manchester decoder uses Flipper's `manchester_decoder.h` transition table, with polarity
`level ? Low : High` (the same mapping as Ford V0 / common).
## Timing
| Parameter | Value | Notes |
|----------------|---------|-----------------------------|
| Short | 500 µs | ±150 µs (te_delta) |
| Long | 1000 µs | ±150 µs |
| Preamble pulse | 300700 µs | |
| Gap | ≥1800 µs | separates preamble from data |
| Min bits | 64 | 8 bytes |
| Preamble | ≥10 pulses | |
## Frame Layout (64 bits / 8 bytes)
- **byte 0:** fixed marker `0x30`
- **bytes 13:** serial (24-bit)
- **byte 5:** counter
- **byte 6:** fixed marker `0xC5`
- **byte 7:** button
The CAS4 rolling cipher is left undecrypted; the two markers serve as the integrity gate.
## RF
- **Encoding:** Manchester (Flipper table, polarity `level ? Low : High`)
- **RF modulation:** AM/OOK
- **Encryption:** CAS4 rolling cipher left undecrypted (no manufacturer key); gated on fixed markers `0x30`/`0xC5`
- **Frequencies:** 433.92 MHz only
## Decoder Steps
1. **Reset** — begin on a HIGH preamble pulse within the 300700 µs window.
2. **Preamble** — count preamble pulses; a long LOW gap (≥1800 µs) with ≥10 pulses enters Data.
3. **Data** — Manchester-decode 64 bits MSB-first; at the 64th bit, require `byte[0]==0x30 && byte[6]==0xC5`, build the signal on success, and reset. An out-of-range pulse aborts.
## Encoder
Not supported (the reference encoder is a non-functional stub: `yield` returns reset, `deserialize`
returns error). Decode-only.
## Validation
Verified by a synthetic-frame check (no local capture available): a unit test Manchester-encodes a frame
by driving the decoder's own transition table (the Flipper table is differential, so there is no
fixed per-bit pattern), confirms it decodes with the right serial/counter/button, and confirms a frame
with wrong marker bytes is rejected.
+66
View File
@@ -0,0 +1,66 @@
---
layout: default
---
# Chrysler V0 Protocol
**Rust module:** `src/protocols/chrysler_v0.rs`
**Reference:** `REFERENCES/ProtoPirate/protocols/chrysler_v0.c`
## Overview
Chrysler/Dodge/Jeep keyfobs. PWM with a short HIGH pulse and two long-LOW symbols: a "1" payload bit is
HIGH ≈600 µs + LOW ≈3400 µs; a "0" payload bit is HIGH ≈300 µs + LOW ≈3700 µs. ~24 preamble pairs
(short HIGH + long_b LOW) precede each frame. 80-bit frame: the first 64 bits are `decode_data` (payload
bytes 07), the last 16 bits are `data_2` (payload bytes 8,9). The 80-bit frame exceeds u64, so `data`
reports the most-significant 64 bits and `data_count_bit = 80`.
Crypto is a proprietary seed-XOR (ported exactly from the reference): `seed = reverse6(key[0] >> 2)` (a
6-bit reversed counter); `transform_block` XORs all 9 transformed bytes with `xor_table[seed & 0x0F]`,
with an extra nibble flip when the Lock button is set. Dual payload: A (seed even, carries serial +
counter) / B (seed odd, carries serial). The frame is gated on a structural `check_ok`, so it does not
false-match; `crc_valid` reflects that check.
## Timing
| Parameter | Value | Notes |
|-------------|---------|------------------------|
| Short HIGH | 300 µs | ±150 µs (te_delta) |
| One-short HIGH | 600 µs | "1" bit HIGH width |
| Long LOW A | 3400 µs | ±400 µs (long_delta) |
| Long LOW B | 3700 µs | ±400 µs |
| Gap | 8000 µs | te_gap |
| Frame gap | 15600 µs| |
| Min bits | 80 | |
| Preamble | 24 pairs| |
## Frame Layout (80 bits / 10 payload bytes)
- **byte 0:** `(reverse6(counter) << 2) | header_low2` — the seed/counter byte
- **bytes 19:** transformed (XOR-masked) payload; after `transform_block`:
- Payload A (even seed): serial = decoded[0..3], rolling counter
- Payload B (odd seed): serial = decoded[0..2] + decoded[7]
- Button: Lock=1, Unlock=2 (derived from structural relationships between key bytes)
## RF
- **Encoding:** PWM (short HIGH + dual long LOW)
- **RF modulation:** AM
- **Encryption:** proprietary seed-XOR (`transform_block` + `xor_table`); gated on structural `check_ok`
- **Frequencies:** 315 MHz, 433.92 MHz
## Decoder Steps
1. **Reset** — a short HIGH pulse begins the seek.
2. **Seek** — count preamble pairs (short HIGH + long LOW); after >15 pairs, a non-short HIGH transitions to Data.
3. **Data** — classify each HIGH/LOW pair into a payload bit (`bit ^ 1`); collect 64 bits into `decode_data` then 16 into `data_2`. At 80 bits (or on a terminating gap with >0x4F bits) run `decode_packet`, check `check_ok`, and emit.
## Encoder
Supported (ENABLE_EMULATE_FEATURE). Rebuilds the 10-byte payload, re-derives seed/plaintext, applies the
requested button, then emits preamble + dual 80-bit PWM frames (A and B) separated by frame gaps.
## Validation
Verified by an encode→decode round-trip / synthetic-frame check (no local capture available). Unit tests
cover `reverse6` involution, `transform_block` invertibility, and a full payload-A round trip.
+68
View File
@@ -0,0 +1,68 @@
---
layout: default
---
# Ford V1 Protocol
**Rust module:** `src/protocols/ford_v1.rs`
**Reference:** `REFERENCES/ProtoPirate/protocols/ford_v1.c`
## Overview
Ford V1 uses Manchester encoding at 65/130 µs. 136 bits / 17 bytes: key1 (bytes 06, 56 bits) + key2
(bytes 714, 64 bits) + CRC16 (bytes 1516). Preamble ≥50 long pulses, then a short-pulse sync window
(`sync_event_count > 2`) replays buffered Manchester events and enters the 17-byte data collection. This
is a ROLLING-code protocol.
Crypto: a proprietary parity-based descrambling cipher operating on the 9-byte air block `raw[6..15]`,
plus CRC16/CCITT (poly `0x1021`, init `0x0000`) over `raw[3..15]`. Emission is gated on CRC16 validity
(with a 17-byte bit-inverted fallback) so it never false-matches. A strict branch
(`decoded[3]==raw[5] && decoded[4]==raw[6]`) yields plaintext serial/button/counter; otherwise it is
classified as encrypted/rolling (device id only). The Manchester transition table is the Flipper
differential-Manchester table (same as Ford V0/V2).
## Timing
| Parameter | Value | Notes |
|-------------|--------|------------------------------|
| Short | 65 µs | ±39 µs (te_delta) |
| Long | 130 µs | ±39 µs (preamble uses ±40) |
| Min bits | 136 | 17 bytes |
| Preamble | ≥50 long pulses | |
## Frame Layout (136 bits / 17 bytes)
- **bytes 06:** key1 (56 bits, big-endian) → `DecodedSignal.data`
- **bytes 614:** air block (9 bytes) — descrambled to plaintext
- **bytes 1516:** CRC16/CCITT over bytes 314
Plaintext fields (when the strict branch matches): serial = `plain[1..3] + plain[0]`, button = `plain[5]>>4`
(Sync=0, Lock=1, Unlock=2, Trunk=4, Panic=8), counter = `((plain[5] & 0x0F) << 16) | plain[6..7]`. The
`extra` word stashes the CRC16, the strict flag, and `plain[4]` so the encoder can rebuild the full frame.
## RF
- **Encoding:** Manchester (Flipper differential table)
- **RF modulation:** FM
- **Encryption:** proprietary parity descramble cipher + CRC16/CCITT gate; rolling code
- **Frequencies:** 315 MHz, 433.92 MHz
## Decoder Steps
1. **Reset** — a long LOW pulse begins the preamble (count = 1).
2. **Preamble** — count long pulses; after ≥50, a short pulse enters Sync.
3. **Sync** — buffer short/long events; once `sync_event_count > 2`, replay the buffered events into Manchester and enter Data.
4. **Data** — Manchester-decode and pack 17 bytes; at the 17th byte run `process_data` (CRC16 gate + descramble + field extraction) and emit on a valid CRC.
## Encoder
Supported (6 bursts). Faithful re-encode is only possible when the original frame's plaintext was
recovered (strict branch); the encoder rebuilds the plaintext from fields + the stashed `plain[4]`,
re-derives the air block + CRC16, and emits 6 bursts of a 400-pair long preamble + sync + 136 Manchester
bits, with a per-burst `pkt[4]` override.
## Validation
Verified by an encode→decode round-trip / synthetic-frame check (no local capture available). Unit tests
cover a CRC16/XMODEM known vector, the descramble round trip, the strict-branch decode, and a full
encode→decode at the on-air-frame level.
+62
View File
@@ -0,0 +1,62 @@
---
layout: default
---
# Ford V2 Protocol
**Rust module:** `src/protocols/ford_v2.rs`
**Reference:** `REFERENCES/ProtoPirate/protocols/ford_v2.c`
## Overview
Ford V2 uses Manchester encoding at 200/400 µs. 104 bits total (13 bytes). The frame begins with a
16-bit Manchester sync that equals `0x7FA7` (the decoder matches the *inverted* shift register against
`!0x7FA7`); those two sync bytes `0x7F 0xA7` head the 13-byte buffer. Decoded data bits are inverted
before packing (`data_bit = !bit`). There is no CRC — structure is validated by the two sync bytes plus
a known button code, so Ford V2 will not false-match.
The Manchester decoder uses Flipper's transition table (same table as Ford V0), with Ford V2 polarity
`level ? High : Low`.
## Timing
| Parameter | Value | Notes |
|-----------|--------|-----------------------------|
| Short | 200 µs | ±260 µs (te_delta) |
| Long | 400 µs | ±260 µs |
| Min bits | 104 | 13 bytes |
| Preamble | ≥64 short pulses | before the sync |
## Frame Layout (104 bits / 13 bytes)
- **bytes 01:** sync `0x7F 0xA7`
- **bytes 25:** serial (32-bit, big-endian)
- **byte 6:** button — valid codes `0x10` (Lock), `0x11` (Unlock), `0x13` (Trunk), `0x14` (Panic), `0x15`
- **bytes 78 + byte 9 MSB:** counter = `((b7 & 0x7F) << 9) | (b8 << 1) | (b9 >> 7)`; byte 7 MSB carries a button parity bit (refreshed by the encoder)
- **bytes 912:** rolling/hop tail (stashed in `extra` so the encoder can rebuild the full frame)
The exported `data` (Key) word is the top 8 bytes; bytes 812 (40 bits) are carried in `extra`.
## RF
- **Encoding:** Manchester (Ford V2 polarity `level ? High : Low`; decoded bits inverted)
- **RF modulation:** FM (per ProtoPirate `SubGhzProtocolFlag_FM`)
- **Encryption:** none — validated by sync bytes + valid button code
- **Frequencies:** 315 MHz, 433.92 MHz
## Decoder Steps
1. **Reset** → a short pulse (~200 µs) begins the preamble.
2. **Preamble** — count short pulses; after ≥64 shorts a long LOW pulse enters Sync.
3. **Sync** — Manchester-decode bits into a 16-bit shift register; when it matches `!0x7FA7`, prime the two sync bytes and enter Data.
4. **Data** — Manchester events feed the state machine; decoded bits are inverted and packed MSB-first into 13 bytes. At 104 bits the sync bytes and button are validated and the frame committed. A non-short/long pulse (gap) ends the attempt.
## Encoder
Supported (matches `subghz_protocol_encoder_ford_v2`). Rebuilds the 13-byte frame, applies the requested
button, refreshes byte-7 parity, then emits 6 bursts of a 70-pair preamble + sync + 104 Manchester bits
(encoder short 240 µs), separated by ~16 ms inter-burst gaps.
## Validation
Verified by an encode→decode round-trip / synthetic-frame check (no local capture available).
+55
View File
@@ -0,0 +1,55 @@
---
layout: default
---
# Ford V3 Protocol
**Rust module:** `src/protocols/ford_v3.rs`
**Reference:** `REFERENCES/ProtoPirate/protocols/ford_v3.c`
## Overview
Ford V3 uses Manchester encoding at 240/480 µs. 104 bits total (13 bytes), transmitted as plaintext —
there is no CRC or encryption. Decode-only: the ProtoPirate reference ships a NULL encoder, so KAT
does not transmit Ford V3 (Replay still works on the raw capture).
The Manchester decoder uses Flipper's `manchester_decoder.h` transition table (same table as Ford V0),
but Ford V3 maps the level the opposite way from Ford V0: `level ? High : Low`.
## Timing
| Parameter | Value | Notes |
|-----------|--------|--------------------|
| Short | 240 µs | ±60 µs (te_delta) |
| Long | 480 µs | ±60 µs |
| Min bits | 104 | 13 bytes |
| Preamble | ≥30 short pulses | before first long |
## Frame Layout (104 bits / 13 bytes)
- **byte 0:** header
- **bytes 14:** serial (32-bit, big-endian)
- **byte 5:** hop/reserved
- **byte 6:** button — bit 0 set → Unlock, else Lock
- **bytes 78:** counter, stored bitwise-inverted (`~b[7]`, `~b[8]`)
- **bytes 912:** rolling/hop tail (not parsed)
## Decoder Steps
1. **Reset** — a short pulse (~240 µs) starts the preamble (count = 1).
2. **Preamble** — count short pulses; once ≥30 shorts are seen, a long pulse begins the data
(Manchester state seeded to Mid1, first bit decoded from the long pulse).
3. **Data** — Manchester events (short/long × level) feed the state machine; bits pack MSB-first into
13 bytes. At 104 bits the fields are parsed and the signal is emitted. A non-short/non-long pulse
(gap) ends the frame.
## RF
- **Encoding:** Manchester
- **RF modulation:** FM (per ProtoPirate `SubGhzProtocolFlag_FM`)
- **Encryption:** none (plaintext)
- **Frequencies:** 315 MHz, 433.92 MHz
## Encoder
Not supported (reference encoder is NULL). Decode-only.
+65
View File
@@ -0,0 +1,65 @@
---
layout: default
---
# Honda Static Protocol
**Rust module:** `src/protocols/honda_static.rs`
**Reference:** `REFERENCES/ProtoPirate/protocols/honda_static.c`
## Overview
Honda/Acura fixed-code keyfobs. 64-bit frame. Unlike most KAT decoders, Honda Static does NOT use the
Flipper `manchester_decoder.h` transition table: it buffers a per-element *symbol stream* (one bit per
~63 µs element) and then performs a custom Manchester unpack over symbol pairs. A short pulse contributes
one symbol equal to the pulse level; a long pulse contributes two symbols of the same level. Anything
outside both ranges (e.g. the 700 µs sync or a trailing gap) terminates the buffer and triggers a parse.
The checksum is an XOR of the first 7 packet bytes. Emission is gated on the checksum, so Honda Static
will not false-match. The parser tries the inverted-Manchester interpretation first (what the encoder
emits), then non-inverted forward, then a bit-reversed-bytes pass.
## Timing
| Parameter | Value | Notes |
|----------------|--------|--------------------------------|
| Element | 63 µs | one symbol per element |
| Short pulse | 2898 µs | base 28 µs, span 70 µs |
| Long pulse | 61191 µs | base 61 µs, span 130 µs (two symbols) |
| Sync | 700 µs | terminates the symbol buffer |
| Min symbols | 36 | before a parse is attempted |
| Min bits | 64 | |
(Reported timing profile: te_short 63 µs, te_long 700 µs, te_delta 120 µs.)
## Frame Layout (64 bits, MSB-first into 8 bytes)
- **bits 03:** button (4-bit) — Lock=1, Unlock=2, Trunk=4, Remote Start=5, Panic=8, Lock×2=9
- **bits 431:** serial (28-bit)
- **bits 3255:** counter (24-bit)
- **bits 5663:** checksum (XOR of bytes 06)
The exported `data` (Key) word is the C `generic.data`: a compact nibble-packed layout, NOT the raw
decoded packet bytes. The KAT counter field is the low 16 bits of the 24-bit counter.
## RF
- **Encoding:** custom symbol-stream Manchester over ~63 µs elements
- **RF modulation:** FM
- **Encryption:** none — gated on the XOR checksum + valid button + valid serial (serial ≠ 0 and ≠ 0x0FFFFFFF)
- **Frequencies:** 315 MHz, 433.92 MHz
## Decoder Steps
1. **feed** — classify each pulse: short → 1 symbol, long → 2 symbols; buffer them.
2. On an out-of-range pulse (700 µs sync or gap), if ≥36 symbols are buffered, walk past the alternating preamble + sync run, Manchester-unpack 64 bits over symbol pairs (inverted first, then non-inverted), validate, and emit. Then clear the buffer.
## Encoder
Supported (matches `honda_static_build_upload`). Emits a 700 µs HIGH sync, a 160-element alternating
preamble at 63 µs, 64 data bits (each as `!value`/`value` at 63 µs), and a trailing 700 µs sync.
## Validation
Decodes REAL IMPORTS captures (IMPORTS/honda Lock and Unlock). Also covered by encode→decode round-trip
and packet-validate unit tests.
+63
View File
@@ -0,0 +1,63 @@
---
layout: default
---
# Honda V1 Protocol
**Rust module:** `src/protocols/honda_v1.rs`
**Reference:** `REFERENCES/ProtoPirate/protocols/honda_v1.c`
## Overview
Honda/Acura fixed-code keyfobs — a DIFFERENT protocol from `honda_static`. The on-wire frame carries 68
bits: 64-bit data + a 4-bit CRC-fold nibble. Encoding is short/long pulse PWM, glued together by a
"pending bit" timing accumulator before classification: sub-`te_delta` runts are summed into `pending`;
a HIGH level extends the running HIGH pulse; a LOW flushes the accumulated HIGH (if it reached
`te_short_min`) as a synthetic symbol, then classifies the LOW.
Validation is a button-code table (Unlock=0, Lock=8, Trunk=9, Panic=10) plus a CRC-fold checksum.
Emission is gated on the button being valid; `crc_valid` reflects whether the received CRC nibble matches
either wire-order checksum. The strong button gate keeps Honda V1 from false-matching.
## Timing
| Parameter | Value | Notes |
|-------------|---------|-----------------------------|
| Short | 1000 µs | ±400 µs (te_delta) |
| Long | 2000 µs | ±400 µs |
| Short min | 600 µs | flush threshold for HIGH |
| End gap | 3500 µs | >te_end commits the frame |
| Min bits | 68 | |
## Frame Layout (68 bits = 64-bit data + 4-bit CRC nibble)
After the end gap, `commit` left-shifts the 12-byte bit buffer to drop leading preamble leakage and align
the trailing 68-bit frame. `data` = first 8 bytes (64 bits); the CRC nibble = byte 8's high nibble.
- **data[63:36]:** serial (28-bit)
- **data[31:28]:** button (nibble) — Unlock=0, Lock=8, Trunk=9, Panic=10
- **data[15:0]:** counter (16-bit)
- **CRC nibble:** CRC-fold checksum (`honda_v1_checksum*`), validated against either wire-order value
## RF
- **Encoding:** short/long pulse PWM with pending-bit timing accumulation
- **RF modulation:** AM (OOK)
- **Encryption:** none — gated on the button-code table + CRC-fold checksum
- **Frequencies:** 315 MHz, 433.92 MHz
## Decoder Steps
1. **feed** — accumulate sub-`te_delta` runts into `pending`; flush HIGH pulses and classify LOW pulses into symbols.
2. **symbol** — Reset → Preamble (short/long pulses, needs a long + >5 count) → Data.
3. **Data** — pending-bit accumulation: a short pulse toggles `data_pending` and emits a bit when paired; a long pulse emits directly. After a >te_end gap, commit: align the 68-bit frame, validate the button, and emit.
## Encoder
Supported (ENABLE_EMULATE_FEATURE). Builds the 64-bit key from serial/button/counter via the button code
table, then emits a 180-element short-pair preamble + 4 PWM frames (2 per checksum wire value).
## Validation
Verified by an encode→decode round-trip / synthetic-frame check (no local capture available). Unit tests
cover the key/field round trip, full encode→decode, and the button-validity mask.
+55
View File
@@ -0,0 +1,55 @@
---
layout: default
---
# Kia V7 Protocol
**Rust module:** `src/protocols/kia_v7.rs`
**Reference:** `REFERENCES/ProtoPirate/protocols/kia_v7.c`
## Overview
Kia V7 uses Manchester encoding at 250/500 µs. 64 bits. The decoded 64-bit word is bit-inverted
(`~data`); a valid frame has a fixed header byte `0x4C` and a CRC8 (poly `0x7F`, init `0x4C`) over bytes
06. Emission is gated on header + CRC, so Kia V7 is strongly validated and will not false-match.
## Timing
| Parameter | Value | Notes |
|-----------|--------|---------------------|
| Short | 250 µs | ±100 µs (te_delta) |
| Long | 500 µs | ±100 µs |
| Min bits | 64 | |
| Preamble | ≥16 short pairs | before sync |
## Frame Layout (64 bits / 8 bytes, after inversion)
- **byte 0:** header `0x4C`
- **bytes 12:** counter (16-bit, big-endian)
- **bytes 36:** serial (28-bit) — `(b3<<20)|(b4<<12)|(b5<<4)|(b6>>4)`, masked to 0x0FFFFFFF
- **byte 6 low nibble:** button (4-bit) — Lock=1, Unlock=2, Trunk=3, aux=8
- **byte 7:** CRC8 over bytes 06 (poly `0x7F`, init `0x4C`)
## RF
- **Encoding:** Manchester (level ? (H,L) : (L,H); decoded word is bit-inverted)
- **RF modulation:** FM (per ProtoPirate `SubGhzProtocolFlag_FM`)
- **Encryption:** none — gated on fixed header `0x4C` + CRC8
- **Frequencies:** 315 MHz, 433.92 MHz
## Decoder Steps
1. **Reset** — a short HIGH pulse begins the preamble.
2. **Preamble** — count short pairs; once >15 pairs are seen, a long HIGH pulse transitions to SyncLow and preloads four seed bits (1,0,1,1 = the inverted header's top nibble 0xB).
3. **SyncLow** — a short LOW after the long sync enters Data.
4. **Data** — Manchester-decode the remaining 60 bits. At 64 bits, invert the word, check the header equals `0x4C` and the CRC8 validates, then emit.
## Encoder
Supported (matches `kia_v7_encode_key`). Rebuilds the 64-bit key from serial/button/counter with the
CRC8, then emits a 32-pair short preamble, a merged long sync pulse, 64 Manchester bits, and a trailing
gap.
## Validation
Verified by an encode→decode round-trip / synthetic-frame check (no local capture available).
+71
View File
@@ -0,0 +1,71 @@
---
layout: default
---
# Land Rover RKE Protocol
**Rust module:** `src/protocols/land_rover_rke.rs`
**Reference:** `Flipper-ARF lib/subghz/protocols/landrover_rke.c`
## Overview
Ported from the Flipper-ARF firmware (D4C1-Labs), itself derived from Pandora DXL 5000 firmware. Land
Rover shares the Ford/Jaguar baseband (firmware protocol ID `0x0E`) but uses a distinct 66-bit frame.
Encoding is fixed-width PWM with a 1000 µs bit period: Bit-1 = 700 µs HIGH + 300 µs LOW, Bit-0 = 300 µs
HIGH + 700 µs LOW. Preamble = 20× (400 µs HIGH + 600 µs LOW); sync = 400 µs HIGH + 9600 µs LOW.
KeeLoq: the hop code is the raw 32-bit KeeLoq ciphertext. Full decryption needs the per-fob manufacturer
key (provisioned, not in firmware), so KAT exposes the framed fields and leaves the hop encrypted —
`crc_valid = false` since no cryptographic check is performed. Emission is still gated tightly on the long
preamble run, the distinctive 9.6 ms sync gap, and exactly 66 PWM bits, so this loose-looking PWM decoder
does not false-match Kia/Subaru/Ford captures.
## Timing
| Parameter | Value | Notes |
|----------------|---------|-----------------------------|
| Bit-1 | 700 µs HIGH + 300 µs LOW | ±20% (relative) |
| Bit-0 | 300 µs HIGH + 700 µs LOW | ±20% |
| Preamble pair | 400 µs HIGH + 600 µs LOW | ×20 |
| Sync | 400 µs HIGH + 9600 µs LOW | |
| Repeat gap | 12000 µs | |
| Min bits | 66 | |
| Preamble min | 16 pairs| |
(Reported timing profile: te_short 300 µs, te_long 700 µs, te_delta 140 µs.)
## Frame Layout (66 bits, MSB-first)
- **[65:34]:** 32-bit KeeLoq encrypted hopping code
- **[33:10]:** 24-bit fixed fob serial
- **[9:6]:** 4-bit button — Lock=0x1, Unlock=0x2, Boot/Tailgate=0x4, Panic=0x8
- **[5:2]:** 4-bit function/repeat flags
- **[1:0]:** 2-bit status — battery-low=0x1, repeat=0x2
66 bits do not fit a u64: `data` holds the low 64 frame bits and `extra` holds the top 2 (the high 2 bits
of the hop code), so encode round-trips the hop losslessly. The KAT counter field surfaces the low 16
bits of the hop ciphertext.
## RF
- **Encoding:** fixed-width PWM
- **RF modulation:** OOK/AM
- **Encryption:** KeeLoq hop left encrypted (no manufacturer key); `crc_valid = false`
- **Frequencies:** 433.92 MHz, 315 MHz
## Decoder Steps
1. **Reset** — wait for the first ~400 µs preamble HIGH.
2. **Preamble** — count 400/600 µs pairs; the distinctive ~9600 µs LOW after a ~400 µs HIGH (with ≥16 pairs) enters DataHigh.
3. **DataHigh/DataLow** — read each bit from its HIGH/LOW half pair (±20% windows). At 66 bits, build the signal and emit.
## Encoder
Supported. Reconstructs the original frame to preserve hop code / func bits / status, overrides only the
button, and emits 4 repetitions of preamble + sync + 66 MSB-first PWM bits, separated by 12 ms gaps.
## Validation
Verified by encode→decode round-trips / synthetic-frame checks (no local capture available). Unit tests
cover pack/unpack round trips, encode→decode across multiple serials/hops/buttons, and rejection of
truncated frames and wrong sync gaps.
+69
View File
@@ -0,0 +1,69 @@
---
layout: default
---
# Land Rover V0 Protocol
**Rust module:** `src/protocols/land_rover_v0.rs`
**Reference:** `REFERENCES/ProtoPirate/protocols/land_rover_v0.c`
## Overview
Land Rover V0 uses **differential** Manchester (NOT the Flipper transition table) at 250/500 µs, with a
~750 µs sync pulse and a ≥64-pair short preamble. The frame is 81 bits = an 80-bit body (`raw[0..10]`)
plus one trailing `extra_bit`. The 64-bit key reported as `data` is `raw[0..8]` big-endian; `raw[8..10]`
is a 16-bit `tail`.
The check is a proprietary 3-bit polynomial over the counter (`calculate_check`); the tail is `0xFFFF`
or `0x7FFF` depending on a 1-bit parity of the counter (`calculate_tail`). Emission is gated on the
reserved bits being zero, the check matching, the tail matching, and `extra_bit` set — so Land Rover V0
is strongly validated and will not false-match.
## Timing
| Parameter | Value | Notes |
|-------------|--------|------------------------|
| Short | 250 µs | ±100 µs (te_delta) |
| Long | 500 µs | ±100 µs |
| Sync | 750 µs | ±120 µs |
| Min bits | 81 | 80-bit body + extra_bit|
| Preamble | ≥64 pairs | |
## Frame Layout (81 bits)
64-bit key (`raw[0..8]`):
- **bytes 02:** 24-bit command signature — Lock = `0xC20363`, Unlock = `0xA285E3`
- **bytes 35:** 24-bit serial
- **byte 6 + byte 7 MSB:** 9-bit counter = `(b6 << 1) | (b7 >> 7)`
- **byte 7 bits 0x78:** 3 reserved bits (must be 0)
- **byte 7 bits 0x07:** 3-bit check (`calculate_check`)
Then `raw[8..10]` = 16-bit tail (`0xFFFF`/`0x7FFF`), and a trailing `extra_bit` (must be 1). The tail is
stashed in `extra` for the encoder.
## RF
- **Encoding:** differential Manchester
- **RF modulation:** FM
- **Encryption:** none — gated on the 3-bit check polynomial + tail parity + reserved-bits + extra_bit
- **Frequencies:** 315 MHz, 433.92 MHz
## Decoder Steps
1. **Reset → PreambleLow/PreambleHigh** — count short pairs (≥64), terminated by a ~750 µs sync HIGH.
2. **SyncLow** — a ~750 µs sync LOW seeds the frame (bit 0 = 1) and enters Data.
3. **Data** — the differential machine (`process_transition`/`add_decoded_bit`) tracks `previous_bit`, skips the initial boundary short-high pad, and completes short half-bits via `pending_short`. At 81 bits, `finish_frame` validates and emits.
## Encoder
Supported. Builds the 64-bit key from signature/serial/counter, emits a 319-pair preamble, the sync,
differential-Manchester body, the 16-bit tail, and a trailing extra bit. Faithful-port note: the
reference encoder forces frame bit 1 = 0, so the Lock signature `0xC20363` (whose bit 1 is 1) is emitted
as `0x820363`; the Unlock signature `0xA285E3` round-trips cleanly. KAT reproduces this behaviour.
## Validation
Verified by an encode→decode round-trip / synthetic-frame check (no local capture available). Unit tests
cover the check polynomial vs. the reference, the tail parity, Unlock round trips across 256 serial/counter
values, the Lock forced-bit quirk, and rejection of a corrupted check.
+70
View File
@@ -0,0 +1,70 @@
---
layout: default
---
# Mazda Siemens Protocol
**Rust module:** `src/protocols/mazda_siemens.rs`
**Reference:** `Flipper-ARF lib/subghz/protocols/mazda_siemens.c`
## Overview
The Siemens/VDO keyfob cipher used on some Mazda vehicles — a DIFFERENT protocol from KAT's existing
"Mazda V0" (Pandora) decoder, even though both ride a 250/500 µs pair-based stream at 433.92 MHz FM. The
decoder is pair-based: `feed()` ignores `level` and interprets raw durations in pairs (`process_pair`),
collecting bits with *inverted* polarity (`state_bit == 0` → stored 1). 64-bit frame.
Siemens obfuscation (`mazda_xor_deobfuscate`): parity-dependent XOR mask, then bit-deinterleave of bytes
5/6. The inner Siemens cipher's plaintext is left as-is (no key); only this obfuscation/interleave layer
is reversed. The gate is an additive checksum `sum(data[0..7]) == data[7]`, plus the structural
preamble/sync/bit-count constraints, which keeps it from false-matching other 250/500 µs Manchester
protocols.
> **Note:** KAT's existing "Mazda V0" decoder implements the same algorithm and keeps first-match
> priority, so on a shared frame Mazda V0 fires first.
## Timing
| Parameter | Value | Notes |
|-------------|--------|-----------------------------|
| Short | 250 µs | ±100 µs (te_delta) |
| Long | 500 µs | ±100 µs |
| Min bits | 64 | |
| Preamble | ≥13 short/short pairs | |
| Completion | 80105 collected bits | |
## Frame Layout (64 bits / 8 bytes, after deobfuscation)
The 14-byte buffer's leading sync byte is discarded, leaving 8 bytes:
- **serial:** `data >> 32` (32-bit)
- **button:** `(data >> 24) & 0xFF` — Lock=0x10, Unlock=0x20, Trunk=0x40
- **counter:** `(data >> 8) & 0xFFFF` (16-bit)
- **byte 7:** additive checksum of bytes 06
## RF
- **Encoding:** pair-based Manchester (inverted polarity), 250/500 µs
- **RF modulation:** FM
- **Encryption:** Siemens obfuscation (parity XOR + bit-interleave); gated on the additive checksum
- **Frequencies:** 433.92 MHz only
## Decoder Steps
1. **Reset** — a short pulse begins the preamble.
2. **PreambleSave/PreambleCheck** — count short/short pairs (≥13); a short→long transition seeds the leading sync bit (a 1) and enters data.
3. **DataSave/DataCheck** — process duration pairs into bits; on a non-matching pair, run `check_completion` (discard sync byte, deobfuscate, validate additive checksum) and emit.
## Encoder
Supported (`subghz_protocol_encoder_mazda_siemens_get_upload`). Increments the counter byte, recomputes
the checksum, obfuscates (interleave + XOR), then emits a 12-byte 0xFF preamble, a 50 ms gap, `0xFF 0xFF
0xD7`, the 8 obfuscated bytes transmitted as `255 byte`, a `0x5A` tail, and a trailing 50 ms gap.
Manchester per byte: bit 1 → (H,L), bit 0 → (L,H) at te_short. As in the C, the decoder and encoder are
not a clean raw-timing round-trip pair (the encoder targets a TX upload).
## Validation
Verified by an encode→decode round-trip / synthetic-frame check (no local capture available). Unit tests
cover the obfuscate/deobfuscate XOR+interleave cipher layer (a true inverse, both parity branches), the
field layout, and the TX upload smoke test.
+67
View File
@@ -0,0 +1,67 @@
---
layout: default
---
# Porsche Cayenne Protocol
**Rust module:** `src/protocols/porsche_cayenne.rs`
**Reference:** `Flipper-ARF lib/subghz/protocols/porsche_cayenne.c`
## Overview
Porsche keyfobs (internal firmware header name "Porsche AG"). PWM at 1680/3370 µs: SHORT LOW + LONG HIGH
= bit 0, LONG LOW + SHORT HIGH = bit 1. 64-bit MSB-first frame, preceded by a 73-pulse 3370 µs sync
preamble + a 5930 µs gap pair. The cipher is a 24-bit rotating-register VAG cipher; the counter is
recovered by brute-forcing 1..=256 and matching the recomputed encrypted bytes (the C's validity signal).
> **Note:** Porsche Cayenne shares the wire protocol with KAT's existing **Porsche Touareg** decoder
> (the Touareg port is itself a port of this same `porsche_cayenne.c` source). The two are the **same
> wire protocol** — identical timing, preamble, 64-bit frame, VAG cipher, and validity check. Cayenne is
> registered AFTER Touareg, so Touareg keeps first-match priority and Cayenne never steals a Touareg
> capture. Emission is additionally gated on `frame_type` being one of the three values the C emits
> (`0b001` Cont, `0b010` First, `0b100` Final) — a strict subset of what Touareg accepts. Cayenne adds an
> encoder (the Touareg port is decode-only).
## Timing
| Parameter | Value | Notes |
|-------------|---------|------------------------|
| Short | 1680 µs | ±500 µs (te_delta) |
| Long | 3370 µs | ±500 µs |
| Sync | 3370 µs | preamble pulse |
| Gap | 5930 µs | preamble→data boundary |
| Min bits | 64 | |
| Sync min | 15 pairs| (firmware emits 73) |
## Frame Layout (64 bits / 8 bytes)
- **byte 0:** `(button << 4) | (frame_type & 0x07)` — button d-pad: Lock=0x01, Unlock=0x02, Trunk=0x04, Open/Panic=0x08; frame_type First=0x02, Cont=0x01, Final=0x04
- **bytes 13:** serial (24-bit, big-endian)
- **bytes 47:** encrypted cipher output (24-bit rotating-register VAG cipher seeded from serial, rotated `4 + counter_low` times)
The `extra` word carries the frame_type; `protocol_display_name` is `Porsche Cayenne [First|Cont|Final]`.
## RF
- **Encoding:** PWM (SHORT LOW + LONG HIGH = 0, LONG LOW + SHORT HIGH = 1)
- **RF modulation:** AM/OOK
- **Encryption:** 24-bit rotating-register VAG cipher; counter recovered by brute force, `counter != 0` is the validity gate
- **Frequencies:** 433.92 MHz, 868.35 MHz
## Decoder Steps
1. **Reset** — wait for a LOW pulse at ~3370 µs.
2. **Sync** — count sync pulses (HIGH and LOW at 3370 µs); a 5930 µs gap with ≥15 sync pulses enters GapHigh/GapLow.
3. **GapHigh/GapLow** — expect the complementary 5930 µs gap pulse, then enter Data.
4. **Data** — decode bit pairs (SHORT LOW + LONG HIGH = 0, LONG LOW + SHORT HIGH = 1); at 64 bits, `parse_data` applies the Cayenne frame_type gate + counter recovery and emits (or stays silent so Touareg owns the frame).
## Encoder
Supported (matches `porsche_cayenne_build_upload`). Emits a 4-frame burst (frame types 0b010/0b001/0b100/
0b100, cipher counters cnt+1..cnt+4), each frame being 73 sync pairs + 1 gap pair + 64 MSB-first PWM bits.
## Validation
Verified by encode→decode round-trips / synthetic-frame checks (no local capture available). Unit tests
cover the full encoder path, all three frame types, rejection of undocumented frame types and truncated
frames, the counter-recovery limit matching the C, and shared cipher vectors with the Touareg port.
+76
View File
@@ -0,0 +1,76 @@
---
layout: default
---
# PSA2 Protocol
**Rust module:** `src/protocols/psa2.rs`
**Reference:** `Flipper-ARF lib/subghz/protocols/psa2.c`
## Overview
PSA2 (Peugeot/Citroën — internal name "PSA OLD") is the OLDER PSA variant, distinct from KAT's existing
`psa` (modified-TEA/XEA) decoder. Manchester encoding: 250/500 µs symbol (Pattern 1, standard rate) or
125/250 µs (Pattern 2, half rate), using the canonical Flipper `manchester_advance` table. 128-bit frame
= key1 (64 bits) + key2/validation word: the decoder collects 64 bits → key1, then 16 more (to 80 bits)
→ the 16-bit validation field / key2_low.
Crypto: TEA (Tiny Encryption Algorithm) with a dual brute-force fallback (BF1 0x230000000x24000000, BF2
0xF30000000xF4000000) and a mode23/mode36 selector, validated via a nibble checksum.
**Performance:** the live decoder only ever runs the cheap O(1) mode23 XOR path
(`direct_xor_decrypt`) per frame — it NEVER runs the TEA brute force. The bounded TEA brute force
(`decrypt_full`, ~16.7M iters per range) is ported faithfully but is reserved for a deferred-decrypt UI
action, mirroring the C exactly. Beyond the C's nibble-checksum gate, KAT adds two false-positive
suppressors required by its feed-all-decoders model: a `key2_high` precondition and a valid-button check
(PSA2 buttons are Lock=0, Unlock=1, Trunk=2 only). Emission is gated strictly on a successful,
field-bearing mode23 decrypt.
## Timing
| Parameter | Value | Notes |
|----------------|--------|-----------------------------|
| Short (P1) | 250 µs | ±100 µs (te_delta) |
| Long (P1) | 500 µs | ±100 µs |
| Short (P2) | 125 µs | ±50 µs (half rate) |
| Long (P2) | 250 µs | ±50 µs |
| End marker | 1000 µs (P1) / 500 µs (P2) | |
| Min bits | 128 | key1 (64) + validation (16) collected |
| Preamble | >0x46 pairs (P1) / >0x45 (P2) | |
## Frame Layout (128 bits = key1 64-bit + key2/validation)
The decoder collects 64 bits → key1, then 16 bits → the validation field (key2_low). After the mode23 XOR
decrypt (fields from `extract_fields_mode23`):
- **serial:** `(buf[2]<<16) | (buf[3]<<8) | buf[4]` (24-bit)
- **button:** `buf[8] & 0xF` — Lock=0, Unlock=1, Trunk=2
- **counter:** `(buf[5]<<8) | buf[6]` (16-bit)
- **crc:** `buf[7]`
`data` = `(key1_high << 32) | key1_low` (64 bits).
## RF
- **Encoding:** Manchester (canonical Flipper table; standard 250/500 µs or half-rate 125/250 µs)
- **RF modulation:** AM (OOK)
- **Encryption:** TEA + nibble-checksum-gated mode23 XOR path; brute-force fallback reserved for offline decrypt
- **Frequencies:** 433.92 MHz
## Decoder Steps
1. **WaitEdge (State0)** — detect the preamble rate (250 µs → Pattern 1, 125 µs → Pattern 2).
2. **CountPattern250/125 (State1/3)** — count preamble pulses past the threshold, then a long pulse enters the Manchester decode state.
3. **DecodeManchester250/125 (State2/4)** — Manchester-decode to 80 bits (key1 + validation), detect end-of-packet, then `finalize_frame` runs the cheap mode23 XOR gate and emits only on a successful field-bearing decrypt.
## Encoder
Supported (mode23 path). Increments the counter, builds key1/validation via `encode_mode23` (inverse XOR
stage + nibble checksum), then emits an 80-pair preamble, a sync, 64 key1 bits + 16 validation bits
MSB-first, and an end burst.
## Validation
Decodes REAL IMPORTS captures (IMPORTS/GROUPE PSA, serial `0x99EB25`). Unit tests also cover the TEA
round trip, the mode23 encode→decode round trip, the nibble checksum vs. the reference, and the emitted
Manchester frame length.
+69
View File
@@ -0,0 +1,69 @@
---
layout: default
---
# Toyota Protocol
**Rust module:** `src/protocols/toyota.rs`
**Reference:** `Flipper-ARF lib/subghz/protocols/toyota.c`
## Overview
Toyota / Lexus KeeLoq keyfobs, a dual-variant decoder. Two variants are detected from the first HIGH
pulse width (threshold 310 µs):
- **Variant A** (Corolla / 433.92 MHz) — PWM pairs: LS (long HIGH + short LOW) = bit 0, SL (short HIGH +
long LOW) = bit 1. Preamble = repeated short-short pairs; first non-SS pair is the first data bit.
Frame = 68 bits.
- **Variant B** (Tundra / 315 MHz) — NRZ: each individual pulse encodes one bit by a midpoint classifier
(≤287 µs → 0, >287 µs → 1). Preamble = short-HIGH/long-LOW pairs terminated by a sync gap (LOW between
1500 and 2600 µs). Frame = 67 bits.
KeeLoq hopping: the hop field is left encrypted (the reference does not decrypt or run a CRC). Emission
is gated tightly (exact frame bit count + structural preamble/sync + non-zero serial) so it does not
false-match the other KeeLoq-PWM protocols. The shared 433 MHz KeeLoq-PWM air encoding means a Variant-A
frame that also satisfies Kia V3/V4's 68-bit/CRC4 structure is claimed by Kia V3/V4 first (earlier in the
registry); Toyota uniquely claims 60-bit frames and Variant-B NRZ at 315 MHz.
## Timing
| Parameter | Value | Notes |
|------------------|--------|-----------------------------|
| A short | 400 µs | ±175 µs (Variant A) |
| A long | 800 µs | ±175 µs |
| B short | 200 µs | ±120 µs (Variant B preamble)|
| B long | 390 µs | ±120 µs |
| B NRZ midpoint | 287 µs | ≤ → 0, > → 1 |
| B sync gap | 15002600 µs | |
| Min bits | 60 | A frame = 68, B frame = 67 |
| Variant threshold| 310 µs | first HIGH: < B, ≥ A |
## Frame Layout
`data = (hop << 32) | (serial << 4) | button` (matches the reference `generic.data`):
- **hop:** 32-bit KeeLoq ciphertext (left encrypted)
- **serial:** 28-bit
- **button:** 4-bit
## RF
- **Encoding:** Variant A = PWM pairs; Variant B = NRZ
- **RF modulation:** AM/OOK
- **Encryption:** KeeLoq hop left encrypted (no key / no CRC); a fully-structured frame of the exact bit count with non-zero serial is the validity criterion
- **Frequencies:** 433.92 MHz (Variant A), 315 MHz (Variant B)
## Decoder Steps
1. **Reset** — detect the variant from the first SHORT HIGH (<310 µs → B, ≥310 µs → A).
2. **Variant A** — PreambleA (count SS pairs) → DataA (decode LS/SL pairs, cap at 68 bits, emit on the terminating gap so Kia wins shared frames by registry order).
3. **Variant B** — PreambleB (short-HIGH/long-LOW pairs) → on the sync gap → DataB (each pulse is one NRZ bit; emit at 67 bits or on an end gap).
## Encoder
Not supported (the reference `encoder` field is NULL). Decode-only.
## Validation
Decodes REAL IMPORTS captures (IMPORTS Toyota + Lexus Camry, NRZ Variant B). Unit tests also cover
synthetic Variant A and Variant B frames and rejection of an all-zero serial.