diff --git a/.gitignore b/.gitignore index 794f537..a016848 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target /EXPORTS +desktop.ini diff --git a/README.md b/README.md index eeb1102..35cadf5 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ A terminal-based RF signal analysis tool for capturing, decoding, and retransmit - **Multi-protocol decoding** — 14 protocol decoders: Kia V0–V6, Ford V0, Fiat V0, Subaru, Suzuki, VAG (VW/Audi/Seat/Skoda), PSA, Scher-Khan, Star Line; adaptive demodulation for real-world conditions - **RF modulation metadata** — each protocol tagged as AM, FM, or both (from ProtoPirate); shown in signal detail and exported in .fob - **Rich signal detail** — encoding (PWM/Manchester), RF (AM/FM), encryption, serial, counter, key data, CRC, frequency, and raw level/duration pairs -- **Signal retransmission** — transmit Lock, Unlock, Trunk, and Panic commands from decoded captures +- **Signal retransmission** — transmit Lock, Unlock, Trunk, and Panic commands from decoded captures (VAG supports full encode from capture via stored vag_type/key_idx) - **Export formats** — `.fob` (versioned JSON with vehicle metadata, signal info, optional raw pairs) and `.sub` (Flipper Zero compatible) - **Import support** — load `.fob` files with automatic v1/v2 format detection - **Research mode** — config option to show unknown (unidentified) signals in addition to successfully decoded ones @@ -209,7 +209,7 @@ Protocol behavior and RF modulation (AM/FM) follow the ProtoPirate reference. KA | Fiat V0 | Manchester | FM | Fixed Code | 433.92 MHz | | Subaru | PWM | AM | Rolling Code | 433.92 MHz | | Suzuki | PWM | AM | Rolling Code | 433.92 MHz | -| VAG (VW/Audi/Seat/Skoda) | Manchester | AM | AUT64/XTEA | 433.92 / 434.42 MHz | +| VAG (VW/Audi/Seat/Skoda) | Manchester | AM | AUT64/XTEA | 433.92 / 434.42 MHz (decode + encode) | | Scher-Khan | PWM | FM | Magic Code | 433.92 MHz | | Star Line | PWM | AM | KeeLoq | 433.92 MHz | | PSA (Peugeot/Citroën) | Manchester | FM | XTEA/XOR | 433.92 MHz | @@ -231,13 +231,17 @@ The **AM/OOK** demodulator turns IQ samples into level/duration pairs for protoc - **Debounce** — 40µs minimum pulse width to reject noise spikes - **Gap detection** — 20 ms gap treated as end of signal +## IMPORTS folder + +The `IMPORTS/` directory holds sample `.sub` captures by manufacturer for testing. If you see `desktop.ini` files in subfolders (e.g. from Windows), you can delete them; they are ignored by git (see `.gitignore`). + ## Project Structure ``` src/ ├── main.rs # Entry point, event loop, key handling ├── app.rs # Application state, radio events, signal actions -├── capture.rs # Capture data, encoding/RF modulation, encryption helpers +├── capture.rs # Capture data (data_extra for protocol encode state), encoding/RF modulation ├── storage.rs # Config (INI), export dir, resolve_config_dir, Storage ├── keystore/ │ ├── mod.rs # Keystore trait and access @@ -247,13 +251,13 @@ src/ │ └── flipper.rs # Flipper Zero .sub export ├── protocols/ │ ├── mod.rs # Protocol registry, decoder trait, duration_diff macro -│ ├── common.rs # Shared CRC, bit helpers, button codes +│ ├── common.rs # DecodedSignal (extra for VAG encode), CRC, bit helpers, button codes │ ├── keeloq_common.rs # KeeLoq cipher + learning key algorithms │ ├── aut64.rs # AUT64 block cipher (VAG) │ ├── keys.rs # Key loading (embedded + optional file), KIA/VAG │ ├── kia_v0..kia_v6.rs │ ├── ford_v0.rs, fiat_v0.rs, subaru.rs, suzuki.rs -│ ├── vag.rs # VAG decoder/encoder (4 sub-types) +│ ├── vag.rs # VAG decoder/encoder (4 sub-types; encode from capture via extra) │ ├── scher_khan.rs, star_line.rs, psa.rs │ └── ... ├── radio/ diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..951e662 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,24 @@ +# KAT Protocol Documentation + +This folder describes how each keyfob protocol supported by KAT works. Each document corresponds to a decoder/encoder in `src/protocols/`. + +**Capture/encode model:** Decoded signals expose optional `extra` (e.g. VAG: vag_type + key_idx). When present, the app stores it in `Capture.data_extra` so retransmit can encode from the capture without decoder instance state. See [vag.md](vag.md) for the VAG encode-from-capture flow. + +| Protocol | Rust module | Doc | +|----------|-------------|-----| +| Kia V0 | `kia_v0.rs` | [kia_v0.md](kia_v0.md) | +| Kia V1 | `kia_v1.rs` | [kia_v1.md](kia_v1.md) | +| Kia V2 | `kia_v2.rs` | [kia_v2.md](kia_v2.md) | +| 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) | +| Ford V0 | `ford_v0.rs` | [ford_v0.md](ford_v0.md) | +| Subaru | `subaru.rs` | [subaru.md](subaru.md) | +| VAG | `vag.rs` | [vag.md](vag.md) | +| Fiat V0 | `fiat_v0.rs` | [fiat_v0.md](fiat_v0.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) | + +Implementations are aligned with the ProtoPirate reference in `REFERENCES/ProtoPirate/protocols/`. diff --git a/docs/fiat_v0.md b/docs/fiat_v0.md new file mode 100644 index 0000000..56111d6 --- /dev/null +++ b/docs/fiat_v0.md @@ -0,0 +1,40 @@ +# Fiat V0 Protocol + +**Rust module:** `src/protocols/fiat_v0.rs` +**Reference:** `REFERENCES/ProtoPirate/protocols/fiat_v0.c` (older reference) + +## Overview + +Fiat V0 uses differential Manchester. Preamble: count short pulses (HIGH or LOW, 200 µs ±100); when count ≥ 150 (0x96), accept 800 µs LOW gap and enter Data. Data: 64 bits (serial = data_low, cnt = data_high) then 7 more bits; complete when bit_count > 0x46 with btn = (data_low << 1) | 1; 71 bits total. Encoder: 150 preamble pairs, last LOW = 800 µs gap; 64 data bits then 6 button bits (btn >> 1); end marker te_short×8 LOW. + +## Timing + +| Parameter | Value | Notes | +|------------|--------|--------------| +| Short | 200 µs | ±100 µs | +| Long | 400 µs | ±100 µs | +| Preamble | ≥150 short pulses | | +| Gap | 800 µs | ±100 µs | +| Min bits | 71 | | + +## Encoding + +Differential Manchester; 6 button bits sent as btn >> 1; end marker 200×8 µs LOW. + +## Frame Layout (71 bits) + +- 64 bits: serial (data_low), counter (data_high). +- 7 bits more; button = (data_low << 1) | 1 (decoded). + +## Decoder Steps + +1. **Reset** — Count short (200 µs) HIGH or LOW; when count ≥ 150 and LOW gap ~800 µs → Data. +2. **Data** — Manchester decode; at > 0x46 bits set btn = (data_low << 1) | 1, return 71-bit decode. + +## Encoder + +Supported; 3 bursts, 25 ms inter-burst gap; 150 preamble pairs; 800 µs gap; 64 + 6 button bits; end marker. + +## Frequencies + +433.92 MHz. diff --git a/docs/ford_v0.md b/docs/ford_v0.md new file mode 100644 index 0000000..4d981cb --- /dev/null +++ b/docs/ford_v0.md @@ -0,0 +1,44 @@ +# Ford V0 Protocol + +**Rust module:** `src/protocols/ford_v0.rs` +**Reference:** `REFERENCES/ProtoPirate/protocols/ford_v0.c` + +## Overview + +Ford V0 uses Manchester encoding at 250/500 µs. 80 bits total: 64-bit key1 + 16-bit key2. CRC is computed via GF(2) matrix multiplication (CRC matrix in code). BS (byte) and “BS magic” are used for encoding and validation. 6 bursts; 4 preamble pairs per burst; 3500 µs gap before data. Flipper-style Manchester: level true → ShortLow/LongLow, level false → ShortHigh/LongHigh. + +## Timing + +| Parameter | Value | Notes | +|------------|--------|--------------------| +| Short | 250 µs | ±100 µs (te_delta) | +| Long | 500 µs | ±100 µs | +| Gap | 3500 µs| ±250 µs | +| Min bits | 64 | (key1); 80 total | + +## Encoding + +Manchester: short/low, short/high, long/low, long/high map to events 0–3; state machine emits data bits. First bit after gap is implicit 1; then 79 more bits from Manchester (64 for key1, 16 for key2). key1/key2 sent inverted (~key1, ~key2). + +## Frame Layout (80 bits) + +- **key1 (64 bits):** header byte, serial, button, counter, XOR/parity and mixed nibbles (see decode_ford_v0 in code). +- **key2 (16 bits):** BS (high byte), CRC (low byte) XOR 0x80. + +CRC is matrix-based over key1 bytes and BS byte; received CRC is key2 low byte XOR 0x80. + +## Decoder Steps + +1. **Reset** — Short HIGH (250 µs) or long HIGH (500 µs) → Preamble (allows re-sync mid-preamble). +2. **Preamble** — LOW long (500 µs) → PreambleCheck. +3. **PreambleCheck** — HIGH long → header_count++, back to Preamble; HIGH short → Gap. +4. **Gap** — LOW ~3500 µs (±250) → Data, set first bit to 1, bit_count=1. +5. **Data** — Manchester events (short/long × level); add_bit (two 64-bit registers, C-style); at 64 bits form key1 = ~combined, clear; at 80 bits key2 = ~low 16, decode_ford_v0, verify CRC, return. + +## Encoder + +Supported. Builds 6 bursts: short-long, 4 preamble pairs, short-long, gap, then Manchester 80 bits (key1 then key2, inverted). Inter-burst gap long×100. + +## Frequencies + +315 MHz, 433.92 MHz. diff --git a/docs/kia_v0.md b/docs/kia_v0.md new file mode 100644 index 0000000..7e89031 --- /dev/null +++ b/docs/kia_v0.md @@ -0,0 +1,45 @@ +# Kia V0 Protocol + +**Rust module:** `src/protocols/kia_v0.rs` +**Reference:** `REFERENCES/ProtoPirate/protocols/kia_v0.c` + +## Overview + +Kia V0 is a PWM keyfob protocol: short pulse = 0, long pulse = 1. No Manchester encoding. 61 bits per frame (1 sync bit + 60 data bits). CRC8 over bits 8–55; polynomial 0x7F, init 0x00. + +## Timing + +| Parameter | Value | Notes | +|------------|--------|--------------------| +| Short (0) | 250 µs | ±100 µs (te_delta) | +| Long (1) | 500 µs | ±100 µs | +| Min bits | 61 | | + +## Frame Layout (61 bits) + +- **Sync:** 1 bit (the long-long pattern also counts as first data bit = 1). +- **Data:** 60 bits MSB first: + - Bits 56–59: 4-bit prefix (often preserved from capture). + - Bits 40–55: 16-bit counter. + - Bits 12–39: 28-bit serial. + - Bits 8–11: 4-bit button. + - Bits 0–7: 8-bit CRC (over bits 8–55, 6 bytes). + +## Decoder Steps + +1. **Reset** — Wait for short HIGH (250 µs ±100). +2. **CheckPreamble** — Count preamble: alternating short pulses; on LOW, if short–short pair then `header_count++`; if long–long and `header_count > 15` → go to SaveDuration, add first bit 1. +3. **SaveDuration** — On HIGH: if duration ≥ 500 + 200 µs (end marker), check `decode_count_bit == 61` and return decode; else store duration and go to CheckDuration. On LOW → Reset. +4. **CheckDuration** — On LOW: short–short → add bit 0, back to SaveDuration; long–long → add bit 1, back to SaveDuration; else Reset. + +## Encoder + +- 2 bursts; inter-burst gap 25 000 µs. +- 32 alternating short (250 µs) preamble pairs. +- Sync: long HIGH, long LOW (500 µs each). +- Data: 59 bits sent (mask 1ULL << (58 - bit_num)), i.e. bits 58 down to 0 (per reference). +- End marker: long × 2 (1000 µs HIGH). + +## Frequencies + +433.92 MHz. diff --git a/docs/kia_v1.md b/docs/kia_v1.md new file mode 100644 index 0000000..245617f --- /dev/null +++ b/docs/kia_v1.md @@ -0,0 +1,41 @@ +# Kia V1 Protocol + +**Rust module:** `src/protocols/kia_v1.rs` +**Reference:** `REFERENCES/ProtoPirate/protocols/kia_v1.c` + +## Overview + +Kia V1 uses Manchester encoding at 800/1600 µs. 57 bits total: 32 serial + 8 button + 12 counter + 4 CRC. Long preamble (~90 long pairs). CRC4 with offset rules (cnt_high 0 vs ≥ 6). + +## Timing + +| Parameter | Value | Notes | +|-----------|---------|---------| +| Short | 800 µs | ±200 µs | +| Long | 1600 µs | ±200 µs | +| Min bits | 57 | | + +## Encoding + +Manchester: symbol duration short or long; bit value from transition direction. + +## Frame Layout (57 bits) + +- 32 bits: serial +- 8 bits: button +- 12 bits: counter +- 4 bits: CRC4 (checksum with offset rules) + +## Decoder Steps + +1. **Reset** — Wait for long pulse (preamble). +2. **CheckPreamble** — Count long pairs until enough; then transition to data. +3. **DecodeData** — Manchester decode; at 57 bits validate CRC4 and return. + +## Encoder + +Supported; builds preamble and Manchester-encoded 57-bit frame. + +## Frequencies + +433.92 MHz. diff --git a/docs/kia_v2.md b/docs/kia_v2.md new file mode 100644 index 0000000..6079229 --- /dev/null +++ b/docs/kia_v2.md @@ -0,0 +1,38 @@ +# Kia V2 Protocol + +**Rust module:** `src/protocols/kia_v2.rs` +**Reference:** `REFERENCES/ProtoPirate/protocols/kia_v2.c` + +## Overview + +Kia V2 uses Manchester encoding at 500/1000 µs. 53 bits: 32 serial + 4 button + 12 counter + 4 CRC, plus start bit. Long preamble of 252 long pairs. CRC4 (XOR nibbles + offset 1). + +## Timing + +| Parameter | Value | Notes | +|-----------|---------|---------| +| Short | 500 µs | ±150 µs | +| Long | 1000 µs | ±150 µs | +| Min bits | 53 | | + +## Encoding + +Manchester encoding; bit value from short/long and transition. + +## Frame Layout (53 bits) + +- Start bit + 32 serial + 4 button + 12 counter (byte-swapped) + 4 CRC. + +## Decoder Steps + +1. **Reset** — Wait for preamble (long pulses). +2. **CheckPreamble** — Count 252 long pairs. +3. **CollectRawBits** — Manchester decode; at 53 bits validate CRC4 and return. + +## Encoder + +Supported; preamble and Manchester 53-bit frame. + +## Frequencies + +433.92 MHz. diff --git a/docs/kia_v3_v4.md b/docs/kia_v3_v4.md new file mode 100644 index 0000000..98a61cf --- /dev/null +++ b/docs/kia_v3_v4.md @@ -0,0 +1,42 @@ +# Kia V3/V4 Protocol + +**Rust module:** `src/protocols/kia_v3_v4.rs` +**Reference:** `REFERENCES/ProtoPirate/protocols/kia_v3_v4.c` + +## Overview + +Kia V3 and V4 use PWM (short = 0, long = 1) with KeeLoq encryption. 68 bits: 8 bytes encrypted + 4 bits CRC. Short preamble of 16 pairs; sync 1200 µs (V4: long HIGH, V3: long LOW). KeeLoq uses the KIA manufacturer key from the keystore (type 10). + +## Timing + +| Parameter | Value | Notes | +|------------|---------|---------| +| Short (0) | 400 µs | ±150 µs | +| Long (1) | 800 µs | ±150 µs | +| Sync | 1200 µs | | +| Min bits | 68 | | + +## Frame Layout (68 bits) + +- Preamble: 16 short/long pairs. +- Sync: 1200 µs (polarity distinguishes V3 vs V4). +- 64 raw bits (8 bytes) then 4 CRC bits. +- 64 bits are KeeLoq-encrypted; decrypt with KIA MF key to get serial/button/counter. + +## Decoder Steps + +1. **Reset** — Wait for preamble. +2. **CheckPreamble** — Count 16 pairs; detect sync polarity (V3 vs V4). +3. **CollectRawBits** — Collect 68 bits (64 + 4 CRC); KeeLoq-decrypt 64 bits, validate CRC4, extract fields. + +## Encoder + +Supported; 3 bursts, 10 s inter-burst gap; preamble, sync, encrypted payload + CRC. + +## Frequencies + +433.92 MHz. + +## Keystore + +Requires KIA manufacturer key (keystore type 10, `kia_mf_key`). Loaded from embedded blob or keystore.ini. diff --git a/docs/kia_v5.md b/docs/kia_v5.md new file mode 100644 index 0000000..e3c0868 --- /dev/null +++ b/docs/kia_v5.md @@ -0,0 +1,43 @@ +# Kia V5 Protocol + +**Rust module:** `src/protocols/kia_v5.rs` +**Reference:** `REFERENCES/ProtoPirate/protocols/kia_v5.c` + +## Overview + +Kia V5 uses Manchester encoding at 400/800 µs with **opposite polarity** to V1/V2: level true → ShortHigh, level false → ShortLow. 64 data bits + 3-bit CRC (67 bits on air). Preamble: 40+ short/long pairs; then LONG HIGH (sync), SHORT LOW (alignment), then Manchester data. Counter is encrypted with a custom mixer cipher using the KIA V5 key (YEK); serial/button from YEK. + +## Timing + +| Parameter | Value | Notes | +|-----------|--------|---------| +| Short | 400 µs | ±150 µs | +| Long | 800 µs | ±150 µs | +| Min bits | 64 | (+ 3 CRC) | + +## Encoding + +Manchester with V5 polarity (level ? ShortHigh : ShortLow). + +## Frame Layout + +- Preamble (40+ pairs) → sync (long HIGH) → alignment (short LOW) → 64 Manchester bits → 3 CRC bits. +- 64-bit key (YEK) = bit-reverse of stored value; serial/button/counter extracted from YEK; counter half is mixer-decrypted with keystore V5 key. + +## Decoder Steps + +1. **Reset** — Wait for preamble. +2. **CheckPreamble** — Count pairs; detect sync and alignment. +3. **Data** — Manchester decode 67 bits; extract YEK, mixer-decode counter, validate CRC. + +## Encoder + +Decode-only in KAT (reference has encoder under ENABLE_EMULATE_FEATURE). + +## Frequencies + +433.92 MHz. + +## Keystore + +Requires KIA V5 mixer key (keystore type 13, `kia_v5_key`). Used for mixer decryption of counter. diff --git a/docs/kia_v6.md b/docs/kia_v6.md new file mode 100644 index 0000000..23c2109 --- /dev/null +++ b/docs/kia_v6.md @@ -0,0 +1,47 @@ +# Kia V6 Protocol + +**Rust module:** `src/protocols/kia_v6.rs` +**Reference:** `REFERENCES/ProtoPirate/protocols/kia_v6.c` + +## Overview + +Kia V6 uses Manchester encoding at 200/400 µs (level convention inverted vs Flipper). 144 bits in three parts: part1 (64) + part2 (64) + part3 (16); each part stored inverted. Long preamble of 601 pairs; sync bits 1,1,0,1 then data. AES-128 decryption with key derived from KIA V6 A and B keystores (types 11 and 12) and XOR masks. + +## Timing + +| Parameter | Value | Notes | +|-----------|--------|---------| +| Short | 200 µs | ±100 µs | +| Long | 400 µs | ±100 µs | +| Preamble | 601 pairs | | +| Min bits | 144 | | + +## Encoding + +Manchester (event mapping 0/2/4/6 for level convention). Three 64/64/16-bit segments, each inverted when stored. + +## Frame Layout (144 bits) + +- Part1: 64 bits (inverted) +- Part2: 64 bits (inverted) +- Part3: 16 bits (inverted) + +AES-128 decrypt with key = f(keystore_a, keystore_b, XOR_MASK_LOW, XOR_MASK_HIGH). Serial/button/counter and CRC extracted after decryption. + +## Decoder Steps + +1. **Reset** — Wait for preamble. +2. **CheckPreamble** — Count 601 pairs; detect sync pattern 1,1,0,1. +3. **Data** — Manchester decode 144 bits; invert segments; AES-128 decrypt; validate CRC8; return fields. + +## Encoder + +Decode-only in KAT (no encoder in reference). + +## Frequencies + +433.92 MHz. + +## Keystore + +Requires KIA V6 A and B keys (keystore types 11 and 12: `kia_v6_a_key`, `kia_v6_b_key`). XOR masks applied to derive AES key. diff --git a/docs/psa.md b/docs/psa.md new file mode 100644 index 0000000..01252df --- /dev/null +++ b/docs/psa.md @@ -0,0 +1,44 @@ +# PSA (Peugeot/Citroën) Protocol + +**Rust module:** `src/protocols/psa.rs` +**Reference:** `REFERENCES/ProtoPirate/protocols/psa.c` + +## Overview + +PSA uses Manchester at 250/500 µs symbol (125/250 µs sub-symbol for preamble). 128 bits total: key1 (64) + validation (16) + key2/rest (48); decode uses key1 + 16-bit validation. TEA decrypt/encrypt with fixed key schedules; mode 0x23 adds an XOR layer. Two modes: seed 0x23 (TEA + XOR), seed 0xF3/0x36 (TEA, BF2 key schedule). + +## Timing + +| Parameter | Value | Notes | +|------------|--------|---------| +| Symbol short | 250 µs | ±100 µs | +| Symbol long | 500 µs | ±100 µs | +| Preamble | 125/250 µs sub-symbols | | +| Min bits | 128 | | + +## Encoding + +Manchester; preamble uses 125/250 µs; then 250/500 µs symbols. TEA encrypt; mode 0x23 adds XOR. + +## Frame Layout (128 bits) + +- key1: 64 bits +- validation: 16 bits +- key2/rest: 48 bits + +TEA decrypt key1 (and validation); mode 0x23: XOR with BF1 key schedule; mode 0x36: TEA with BF2 key schedule. Serial/button/counter extracted from decrypted key1. + +## Decoder Steps + +1. **WaitEdge** — Wait for edge/preamble. +2. **CountPattern** — Count preamble pattern (125/250 µs). +3. **DecodeManchester** — Manchester decode 128 bits; TEA decrypt; apply mode (0x23 XOR or 0x36); extract fields. +4. **End** — End marker (e.g. 1000 µs); return decode. + +## Encoder + +Supported; preamble, Manchester 128 bits, TEA (+ XOR for 0x23). + +## Frequencies + +433.92 MHz. diff --git a/docs/scher_khan.md b/docs/scher_khan.md new file mode 100644 index 0000000..7aecc5b --- /dev/null +++ b/docs/scher_khan.md @@ -0,0 +1,40 @@ +# Scher-Khan Protocol + +**Rust module:** `src/protocols/scher_khan.rs` +**Reference:** `REFERENCES/ProtoPirate/protocols/scher_khan.c` + +## Overview + +Scher-Khan is decode-only. PWM: 750 µs = 0, 1100 µs = 1; preamble uses 2× short then alternating. Variable bit count (35, 51, 57, 63, 64, 81, 82); only 51-bit format is parsed for serial/button/counter. Reference: phreakerclub.com/72. + +## Timing + +| Parameter | Value | Notes | +|-----------|---------|---------| +| Short (0) | 750 µs | ±160 µs | +| Long (1) | 1100 µs| ±160 µs | +| Min bits | 35 | (variable) | + +## Encoding + +PWM; preamble: two short then alternating short/long. + +## Frame Layout (variable) + +- **51-bit format:** serial (28) | button (4) | counter (16) — “MAGIC CODE” / Dynamic; parsed for serial/btn/cnt. +- Other lengths (35, 57, 63, 64, 81, 82) decoded as raw bit count but not field-parsed. + +## Decoder Steps + +1. **Reset** — Wait for preamble (2 short + alternating). +2. **CheckPreamble** — Confirm preamble pattern. +3. **SaveDuration** — Store pulse duration. +4. **CheckDuration** — Short–short = 0, long–long = 1 (or equivalent); add bit; on end marker, if bit_count in {35,51,57,63,64,81,82} return decode (51-bit parsed for fields). + +## Encoder + +Not implemented (no encoder in reference). + +## Frequencies + +433.92 MHz. diff --git a/docs/star_line.md b/docs/star_line.md new file mode 100644 index 0000000..3f84e7d --- /dev/null +++ b/docs/star_line.md @@ -0,0 +1,45 @@ +# Star Line Protocol + +**Rust module:** `src/protocols/star_line.rs` +**Reference:** `REFERENCES/ProtoPirate/protocols/star_line.c` + +## Overview + +Star Line uses PWM: 250 µs = 0, 500 µs = 1. 64 bits: key_fix (32) + key_hop (32), sent MSB-first (reversed on air). Header: 6 pairs of 1000 µs HIGH + 1000 µs LOW. KeeLoq: fix = serial(24) + button(8); hop encrypted with manufacturer key or normal-learning derived key. + +## Timing + +| Parameter | Value | Notes | +|-----------|---------|---------| +| Short (0) | 250 µs | ±120 µs | +| Long (1) | 500 µs | ±120 µs | +| Header | 1000 µs × 2 (6 pairs) | | +| Min bits | 64 | | + +## Encoding + +PWM; 64 bits MSB-first (reversed on air). KeeLoq encrypt for hop; fix half plain. + +## Frame Layout (64 bits) + +- **key_fix (32 bits):** serial (24) + button (8). +- **key_hop (32 bits):** KeeLoq-encrypted rolling code (MF key or normal-learning key). + +## Decoder Steps + +1. **Reset** — Wait for header (6 × 1000 µs HIGH, 1000 µs LOW). +2. **CheckPreamble** — Confirm 6 header pairs. +3. **SaveDuration** — Store duration. +4. **CheckDuration** — Short–short = 0, long–long = 1; at 64 bits KeeLoq-decrypt hop (or normal-learning), extract serial/button/counter, return. + +## Encoder + +Supported; header, 64-bit fix+hop (KeeLoq encrypt hop with MF or derived key). + +## Frequencies + +433.92 MHz. + +## Keystore + +Star Line manufacturer key (keystore type 20, `star_line_mf_key`). Used for KeeLoq hop decryption and normal-learning derivation. diff --git a/docs/subaru.md b/docs/subaru.md new file mode 100644 index 0000000..b0e88db --- /dev/null +++ b/docs/subaru.md @@ -0,0 +1,43 @@ +# Subaru Protocol + +**Rust module:** `src/protocols/subaru.rs` +**Reference:** `REFERENCES/ProtoPirate/protocols/subaru.c` + +## Overview + +Subaru uses PWM: 800 µs HIGH = 1, 1600 µs HIGH = 0; LOW is 800 µs after each bit. 64 bits total (8 bytes MSB first: button(4) + serial(24) + counter-related). Preamble: 79 full 1600 µs pairs + 80th HIGH only; then gap 2800 µs, sync 2800 µs HIGH + 1600 µs LOW. Complex counter decoding from bytes 4–7. + +## Timing + +| Parameter | Value | Notes | +|-----------|---------|---------| +| Short (1) | 800 µs | ±200 µs | +| Long (0) | 1600 µs | ±200 µs | +| Gap | 2800 µs | | +| Sync | 2800 µs HIGH + 1600 µs LOW | | +| Min bits | 64 | | + +## Encoding + +PWM: duration of HIGH = bit value (800 = 1, 1600 = 0); LOW 800 µs after each bit. + +## Frame Layout (64 bits = 8 bytes) + +- 4 bits button, 24 bits serial, counter-related in bytes 4–7 (decode_counter in code). + +## Decoder Steps + +1. **Reset** — Wait for long HIGH (preamble). +2. **CheckPreamble** — Count 79 long pairs + 80th HIGH; then expect gap 2800 µs. +3. **FoundGap** — Gap seen → look for sync (2800 HIGH, 1600 LOW). +4. **FoundSync** — Sync seen → SaveDuration. +5. **SaveDuration** — On HIGH: store duration, → CheckDuration. On end marker → return decode if 64 bits. +6. **CheckDuration** — On LOW: pair (te_last, duration) → short–short = 1, long–long = 0; add bit, → SaveDuration. + +## Encoder + +Supported; builds preamble, gap, sync, then 64 PWM bits. + +## Frequencies + +433.92 MHz (and/or 315 MHz as in reference). diff --git a/docs/suzuki.md b/docs/suzuki.md new file mode 100644 index 0000000..d8c4725 --- /dev/null +++ b/docs/suzuki.md @@ -0,0 +1,42 @@ +# Suzuki Protocol + +**Rust module:** `src/protocols/suzuki.rs` +**Reference:** `REFERENCES/ProtoPirate/protocols/suzuki.c` + +## Overview + +Suzuki uses PWM: 250 µs HIGH = 0, 500 µs HIGH = 1; LOW 250 µs after each bit. 64 bits total. Preamble: 350 short HIGH / short LOW pairs; 2000 µs gap at end. Field layout: serial = (data_high&0xFFF)<<16 | data_low>>16; btn = (data_low>>12)&0xF; cnt = (data_high<<4)>>16. + +## Timing + +| Parameter | Value | Notes | +|------------|---------|---------| +| Short (0) | 250 µs | ±99 µs | +| Long (1) | 500 µs | ±99 µs | +| Preamble | 350 pairs | | +| Gap | 2000 µs | ±399 µs | +| Min bits | 64 | | + +## Encoding + +PWM: 250 µs HIGH = 0, 500 µs HIGH = 1; LOW 250 µs after each bit. + +## Frame Layout (64 bits) + +- serial: (data_high & 0xFFF) << 16 | data_low >> 16 +- button: (data_low >> 12) & 0xF +- counter: (data_high << 4) >> 16 + +## Decoder Steps + +1. **Reset** — Wait for short pulse (preamble). +2. **CountPreamble** — Count 350 short pairs; on 2000 µs gap → DecodeData. +3. **DecodeData** — Short = 0, long = 1; at 64 bits parse fields and return. + +## Encoder + +Supported; 350 preamble pairs, gap, 64 PWM bits. + +## Frequencies + +433.92 MHz. diff --git a/docs/vag.md b/docs/vag.md new file mode 100644 index 0000000..095255b --- /dev/null +++ b/docs/vag.md @@ -0,0 +1,43 @@ +# VAG (VW/Audi/Seat/Skoda) Protocol + +**Rust module:** `src/protocols/vag.rs` +**Reference:** `REFERENCES/ProtoPirate/protocols/vag.c` + +## Overview + +VAG supports multiple types (1, 2, 3, 4) with different timing and encryption. Manchester, 80 bits (key1 64 + key2 16). Type 1/2: 300/600 µs, prefix 0xAF3F / 0xAF1C. Type 3/4: 500 µs, 45 preamble pairs, sync 1000+500 then 3×750 µs; key1/key2 not inverted. Type 1/2 use AUT64 or TEA decryption; type 3/4 use AUT64. Button names: Unlock, Lock, Boot. Keys loaded from keystore (VAG raw 64 bytes = 4×16-byte AUT64 keys; lookup by index 1, 2, 3). **Decode and encode** are both supported; encode uses capture data plus stored vag_type/key_idx. + +## Timing + +- **Type 1/2:** 300 µs short, 600 µs long, ±79/80; Preamble1→Data1 gap 600 µs ±79; end-of-data gap < 4000 µs. +- **Type 3/4:** 500 µs short, 1000 µs long, ±80; Preamble2 count 500±80; Sync2A 500/1000 µs ±79; Sync2B/Sync2C 750 µs ±79; Data2 short 500±120, long 1000±120. + +## Encoding + +Manchester; type-dependent short/long and sync sequences. Encoder builds the waveform from **decoded capture data** (serial, button, counter, key1) plus **protocol extra** (vag_type, key_idx) so retransmit works after decoder reset. + +## Frame Layout (80 bits) + +- **key1 (64 bits)** + **key2 (16 bits)**. Dispatch byte in key2; key index for AUT64 (type 1/2). Type 3/4: AUT64 decrypt. + +## Decoder Steps + +1. **Reset** — 300 µs or 500 µs (type-dependent) → Preamble1 or Preamble2. +2. **Preamble1** — Count; gap 600 µs → Data1. **Data1** — Manchester; 80 bits; parse type 1/2 (AUT64 or TEA). +3. **Preamble2** — Count 500 µs pulses; Sync2A (500/1000) → Sync2B (750) → Sync2C (750) → Data2. **Data2** — Manchester 80 bits; AUT64 decrypt (type 3/4). + +Parse (vag_parse_data): dispatch 0x2A/0x1C/0x46 and 0x2B/0x1D/0x47; try AUT64 with key index 0,1,2; or TEA for type 2. + +## Encoder (encode from capture) + +- **Supports encoding:** protocol reports `true`; TX Lock/Unlock/Trunk available when the capture has encoder data. +- **Stored state:** On successful decrypt, the decoder sets `DecodedSignal.extra = Some(vag_type | (key_idx << 8))`. The app copies this to `Capture.data_extra`. Without `data_extra` (e.g. old import or undecrypted capture), encode returns `None`. +- **Encode path:** `encode_signal(decoded)` uses `decoded.serial`, `decoded.button`, `decoded.counter`, `decoded.data` (key1 / type_byte), and `decoded.extra` (vag_type, key_idx) to build type 1, 2, or 3/4 waveform. No decoder instance state is used so retransmit works after reset. + +## Frequencies + +433.92 MHz, 434.42 MHz. + +## Keystore + +VAG AUT64 keys: 4×16-byte packed keys from embedded blob (“VAG ” + 64 bytes) or file. Lookup by `get_vag_key((key_index+1) as u8)` (index 1, 2, 3). diff --git a/src/app.rs b/src/app.rs index 0049c59..8e677e8 100644 --- a/src/app.rs +++ b/src/app.rs @@ -5,7 +5,7 @@ use std::sync::mpsc::{self, Receiver, Sender}; use crate::capture::{ButtonCommand, Capture}; use crate::protocols::ProtocolRegistry; -use crate::radio::HackRfController; +use crate::radio::{HackRfController, LevelDuration}; use crate::storage::Storage; /// Input mode for the application @@ -47,6 +47,7 @@ pub enum ExportFormat { /// Items available in the signal action menu #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SignalAction { + Replay, Lock, Unlock, Trunk, @@ -57,7 +58,8 @@ pub enum SignalAction { } impl SignalAction { - pub const ALL: [SignalAction; 7] = [ + pub const ALL: [SignalAction; 8] = [ + SignalAction::Replay, SignalAction::Lock, SignalAction::Unlock, SignalAction::Trunk, @@ -69,6 +71,7 @@ impl SignalAction { pub fn label(&self) -> &'static str { match self { + SignalAction::Replay => "Replay", SignalAction::Lock => "TX Lock", SignalAction::Unlock => "TX Unlock", SignalAction::Trunk => "TX Trunk", @@ -632,6 +635,7 @@ impl App { data: capture.data, data_count_bit: capture.data_count_bit, encoder_capable: true, + extra: capture.data_extra, }; // Generate the signal with the new button @@ -655,6 +659,37 @@ impl App { Ok(()) } + /// Replay a capture by re-transmitting its raw level/duration pairs (no re-encoding). + pub fn replay_capture(&mut self, id: u32) -> Result<()> { + let capture = match self.captures.iter().find(|c| c.id == id) { + Some(c) => c, + None => { + self.last_error = Some(format!("Capture {} not found", id)); + return Ok(()); + } + }; + + if capture.raw_pairs.is_empty() { + self.last_error = Some("No raw signal to replay (capture has no level/duration data)".to_string()); + return Ok(()); + } + + let signal: Vec = capture + .raw_pairs + .iter() + .map(|p| LevelDuration::new(p.level, p.duration_us)) + .collect(); + + if let Some(ref mut hackrf) = self.hackrf { + hackrf.transmit(&signal, capture.frequency)?; + self.status_message = Some(format!("Replayed capture {} ({} pairs)", id, signal.len())); + } else { + self.last_error = Some("HackRF not connected".to_string()); + } + + Ok(()) + } + /// Delete the currently selected capture (if any). No-op if none selected or list empty. pub fn delete_selected_capture(&mut self) -> Result<()> { let id = match self.selected_capture { @@ -735,6 +770,7 @@ impl App { capture.crc_valid = decoded.crc_valid; capture.data = decoded.data; capture.data_count_bit = decoded.data_count_bit; + capture.data_extra = decoded.extra; capture.status = if decoded.encoder_capable { crate::capture::CaptureStatus::EncoderCapable } else { @@ -785,6 +821,9 @@ impl App { }; match action { + SignalAction::Replay => { + self.replay_capture(capture_id)?; + } SignalAction::Lock => { let id_str = capture_id.to_string(); self.transmit_command(Some(&&*id_str.as_str()), ButtonCommand::Lock)?; @@ -942,6 +981,7 @@ impl App { capture.crc_valid = decoded.crc_valid; capture.data = decoded.data; capture.data_count_bit = decoded.data_count_bit; + capture.data_extra = decoded.extra; capture.status = if decoded.encoder_capable { crate::capture::CaptureStatus::EncoderCapable } else { @@ -978,6 +1018,7 @@ impl App { capture.crc_valid = decoded.crc_valid; capture.data = decoded.data; capture.data_count_bit = decoded.data_count_bit; + capture.data_extra = decoded.extra; capture.status = if decoded.encoder_capable { crate::capture::CaptureStatus::EncoderCapable } else { @@ -1149,6 +1190,7 @@ impl App { crc_valid: true, data: 0x5A2B3C4D00001234, data_count_bit: 64, + data_extra: None, raw_pairs: vec![], status: crate::capture::CaptureStatus::EncoderCapable, received_rf: None, diff --git a/src/capture.rs b/src/capture.rs index bec76e4..abcfe32 100644 --- a/src/capture.rs +++ b/src/capture.rs @@ -54,6 +54,9 @@ pub struct Capture { pub data: u64, /// Number of valid bits in data pub data_count_bit: usize, + /// Protocol-specific extra for encoding (e.g. VAG vag_type + key_idx) + #[serde(default)] + pub data_extra: Option, /// Raw level+duration pairs pub raw_pairs: Vec, /// Current status @@ -131,6 +134,7 @@ impl Capture { crc_valid: false, data: 0, data_count_bit: 0, + data_extra: None, raw_pairs: pairs, status: CaptureStatus::Unknown, received_rf, diff --git a/src/export/fob.rs b/src/export/fob.rs index 146d128..9cf7bf4 100644 --- a/src/export/fob.rs +++ b/src/export/fob.rs @@ -298,6 +298,7 @@ fn import_fob_v2(fob: &FobFile, next_id: u32) -> Result { crc_valid: sig.crc_valid, data, data_count_bit: sig.data_bits, + data_extra: None, raw_pairs, status, received_rf: None, @@ -363,6 +364,7 @@ fn import_fob_v1(fob: &FobFileV1, next_id: u32) -> Result { crc_valid: cap.crc_valid, data, data_count_bit: cap.data_bits, + data_extra: None, raw_pairs, status, received_rf: None, diff --git a/src/protocols/common.rs b/src/protocols/common.rs index 8795404..6bb5765 100644 --- a/src/protocols/common.rs +++ b/src/protocols/common.rs @@ -30,6 +30,8 @@ pub struct DecodedSignal { pub data_count_bit: usize, /// Whether encoding is supported pub encoder_capable: bool, + /// Protocol-specific extra data for encoding (e.g. VAG: vag_type + key_idx) + pub extra: Option, } impl DecodedSignal { @@ -43,6 +45,7 @@ impl DecodedSignal { data, data_count_bit: bit_count, encoder_capable: false, + extra: None, } } } diff --git a/src/protocols/fiat_v0.rs b/src/protocols/fiat_v0.rs index 4fb8e67..d7ac8d5 100644 --- a/src/protocols/fiat_v0.rs +++ b/src/protocols/fiat_v0.rs @@ -116,6 +116,7 @@ impl FiatV0Decoder { data, data_count_bit: 71, encoder_capable: true, + extra: None, } } } diff --git a/src/protocols/ford_v0.rs b/src/protocols/ford_v0.rs index 2be5c8f..2850a1e 100644 --- a/src/protocols/ford_v0.rs +++ b/src/protocols/ford_v0.rs @@ -608,6 +608,7 @@ impl ProtocolDecoder for FordV0Decoder { data: self.key1, data_count_bit: MIN_COUNT_BIT, encoder_capable: true, + extra: None, }; self.data_low = 0; diff --git a/src/protocols/kia_v0.rs b/src/protocols/kia_v0.rs index d67776f..01d2726 100644 --- a/src/protocols/kia_v0.rs +++ b/src/protocols/kia_v0.rs @@ -84,6 +84,7 @@ impl KiaV0Decoder { data, data_count_bit: MIN_COUNT_BIT, encoder_capable: true, + extra: None, } } } diff --git a/src/protocols/kia_v1.rs b/src/protocols/kia_v1.rs index c444d19..754a0e8 100644 --- a/src/protocols/kia_v1.rs +++ b/src/protocols/kia_v1.rs @@ -133,6 +133,7 @@ impl KiaV1Decoder { data, data_count_bit: MIN_COUNT_BIT, encoder_capable: true, + extra: None, } } } diff --git a/src/protocols/kia_v2.rs b/src/protocols/kia_v2.rs index 75a3d1c..a036263 100644 --- a/src/protocols/kia_v2.rs +++ b/src/protocols/kia_v2.rs @@ -128,6 +128,7 @@ impl KiaV2Decoder { data, data_count_bit: MIN_COUNT_BIT, encoder_capable: true, + extra: None, } } } diff --git a/src/protocols/kia_v3_v4.rs b/src/protocols/kia_v3_v4.rs index e30e523..6c3f694 100644 --- a/src/protocols/kia_v3_v4.rs +++ b/src/protocols/kia_v3_v4.rs @@ -154,6 +154,7 @@ impl KiaV3V4Decoder { data: key_data, data_count_bit: MIN_COUNT_BIT, encoder_capable: true, + extra: None, }) } } diff --git a/src/protocols/kia_v5.rs b/src/protocols/kia_v5.rs index 61534b2..cc481bb 100644 --- a/src/protocols/kia_v5.rs +++ b/src/protocols/kia_v5.rs @@ -198,6 +198,7 @@ impl KiaV5Decoder { data: key, data_count_bit: MIN_COUNT_BIT, encoder_capable: false, // V5 is decode-only + extra: None, }) } } diff --git a/src/protocols/kia_v6.rs b/src/protocols/kia_v6.rs index 5ac05c1..3411fff 100644 --- a/src/protocols/kia_v6.rs +++ b/src/protocols/kia_v6.rs @@ -538,6 +538,7 @@ impl ProtocolDecoder for KiaV6Decoder { data: key_data, data_count_bit: MIN_COUNT_BIT, encoder_capable: false, + extra: None, }); } diff --git a/src/protocols/psa.rs b/src/protocols/psa.rs index 38d148a..57cd77b 100644 --- a/src/protocols/psa.rs +++ b/src/protocols/psa.rs @@ -262,6 +262,7 @@ impl PsaDecoder { data, data_count_bit: MIN_COUNT_BIT, encoder_capable: true, + extra: None, } } else { DecodedSignal { @@ -272,6 +273,7 @@ impl PsaDecoder { data, data_count_bit: MIN_COUNT_BIT, encoder_capable: false, + extra: None, } } } diff --git a/src/protocols/scher_khan.rs b/src/protocols/scher_khan.rs index b7de7c1..849733a 100644 --- a/src/protocols/scher_khan.rs +++ b/src/protocols/scher_khan.rs @@ -70,6 +70,7 @@ impl ScherKhanDecoder { data, data_count_bit: bit_count, encoder_capable: false, + extra: None, } } } diff --git a/src/protocols/star_line.rs b/src/protocols/star_line.rs index ca55a61..458547d 100644 --- a/src/protocols/star_line.rs +++ b/src/protocols/star_line.rs @@ -101,6 +101,7 @@ impl StarLineDecoder { data, data_count_bit: MIN_COUNT_BIT, encoder_capable: true, + extra: None, } } } diff --git a/src/protocols/subaru.rs b/src/protocols/subaru.rs index 2fe28bb..55d0a57 100644 --- a/src/protocols/subaru.rs +++ b/src/protocols/subaru.rs @@ -150,6 +150,7 @@ impl SubaruDecoder { data: key, data_count_bit: 64, encoder_capable: true, + extra: None, }) } } diff --git a/src/protocols/suzuki.rs b/src/protocols/suzuki.rs index d33a526..f213c2c 100644 --- a/src/protocols/suzuki.rs +++ b/src/protocols/suzuki.rs @@ -71,6 +71,7 @@ impl SuzukiDecoder { data, data_count_bit: MIN_COUNT_BIT, encoder_capable: true, + extra: None, } } } diff --git a/src/protocols/vag.rs b/src/protocols/vag.rs index 7a98d63..b5fce68 100644 --- a/src/protocols/vag.rs +++ b/src/protocols/vag.rs @@ -451,41 +451,54 @@ impl VagDecoder { } } - /// Build encoder output for the given signal + /// Build encoder output from decoded signal (uses decoded + extra; extra = vag_type | (key_idx<<8)) fn encode_signal(&self, decoded: &DecodedSignal) -> Option> { - if !self.decrypted { - return None; - } + let extra = match decoded.extra { + Some(e) => e, + None => return None, + }; + let vag_type_num = (extra & 0xFF) as u8; + let vag_type = match vag_type_num { + 1 => VagType::Type1, + 2 => VagType::Type2, + 3 => VagType::Type3, + 4 => VagType::Type4, + _ => return None, + }; + let key_idx = ((extra >> 8) & 0xFF) as u8; - match self.vag_type { - VagType::Type1 => self.encode_type1(decoded), - VagType::Type2 => self.encode_type2(decoded), - VagType::Type3 | VagType::Type4 => self.encode_type3_4(decoded), + match vag_type { + VagType::Type1 => Self::encode_type1(decoded, key_idx), + VagType::Type2 => Self::encode_type2(decoded), + VagType::Type3 | VagType::Type4 => Self::encode_type3_4(decoded, vag_type, key_idx), _ => None, } } /// Encode Type 1 (300µs, AUT64) - fn encode_type1(&self, _decoded: &DecodedSignal) -> Option> { + fn encode_type1(decoded: &DecodedSignal, key_idx: u8) -> Option> { let mut upload = Vec::with_capacity(700); - let btn_byte = self.btn; + let serial = decoded.serial.unwrap_or(0); + let btn = decoded.button.unwrap_or(0); + let cnt = decoded.counter.unwrap_or(0) as u32; + let type_byte = (decoded.data >> 56) as u8; + let btn_byte = btn; let dispatch = Self::get_dispatch_byte(btn_byte, 1); - let type_byte = (self.key1_high >> 24) as u8; // Build plaintext block let mut block = [0u8; 8]; - block[0] = (self.serial >> 24) as u8; - block[1] = (self.serial >> 16) as u8; - block[2] = (self.serial >> 8) as u8; - block[3] = self.serial as u8; - block[4] = self.cnt as u8; - block[5] = (self.cnt >> 8) as u8; - block[6] = (self.cnt >> 16) as u8; + block[0] = (serial >> 24) as u8; + block[1] = (serial >> 16) as u8; + block[2] = (serial >> 8) as u8; + block[3] = serial as u8; + block[4] = cnt as u8; + block[5] = (cnt >> 8) as u8; + block[6] = (cnt >> 16) as u8; block[7] = btn_byte; // Encrypt with AUT64 - let key_idx = if self.key_idx != 0xFF { self.key_idx as usize } else { 0 }; + let key_idx = if key_idx != 0xFF { key_idx as usize } else { 0 }; let store = keys::get_keystore(); if let Some(key) = store.get_vag_key((key_idx + 1) as u8) { aut64::aut64_encrypt(key, &mut block); @@ -533,22 +546,25 @@ impl VagDecoder { } /// Encode Type 2 (300µs, TEA) - fn encode_type2(&self, _decoded: &DecodedSignal) -> Option> { + fn encode_type2(decoded: &DecodedSignal) -> Option> { let mut upload = Vec::with_capacity(700); - let btn_byte = Self::btn_to_byte(self.btn, 2); + let serial = decoded.serial.unwrap_or(0); + let btn = decoded.button.unwrap_or(0); + let cnt = decoded.counter.unwrap_or(0) as u32; + let type_byte = (decoded.data >> 56) as u8; + let btn_byte = Self::btn_to_byte(btn, 2); let dispatch = Self::get_dispatch_byte(btn_byte, 2); - let type_byte = (self.key1_high >> 24) as u8; // Build plaintext block let mut block = [0u8; 8]; - block[0] = (self.serial >> 24) as u8; - block[1] = (self.serial >> 16) as u8; - block[2] = (self.serial >> 8) as u8; - block[3] = self.serial as u8; - block[4] = self.cnt as u8; - block[5] = (self.cnt >> 8) as u8; - block[6] = (self.cnt >> 16) as u8; + block[0] = (serial >> 24) as u8; + block[1] = (serial >> 16) as u8; + block[2] = (serial >> 8) as u8; + block[3] = serial as u8; + block[4] = cnt as u8; + block[5] = (cnt >> 8) as u8; + block[6] = (cnt >> 16) as u8; block[7] = btn_byte; // Encrypt with TEA @@ -606,27 +622,30 @@ impl VagDecoder { } /// Encode Type 3/4 (500µs, AUT64) - fn encode_type3_4(&self, _decoded: &DecodedSignal) -> Option> { + fn encode_type3_4(decoded: &DecodedSignal, vag_type: VagType, key_idx: u8) -> Option> { let mut upload = Vec::with_capacity(600); - let vag_type_num = self.vag_type as u8; + let vag_type_num = vag_type as u8; - let btn_byte = Self::btn_to_byte(self.btn, vag_type_num); + let serial = decoded.serial.unwrap_or(0); + let btn = decoded.button.unwrap_or(0); + let cnt = decoded.counter.unwrap_or(0) as u32; + let type_byte = (decoded.data >> 56) as u8; + let btn_byte = Self::btn_to_byte(btn, vag_type_num); let dispatch = Self::get_dispatch_byte(btn_byte, vag_type_num); - let type_byte = (self.key1_high >> 24) as u8; let mut block = [0u8; 8]; - block[0] = (self.serial >> 24) as u8; - block[1] = (self.serial >> 16) as u8; - block[2] = (self.serial >> 8) as u8; - block[3] = self.serial as u8; - block[4] = self.cnt as u8; - block[5] = (self.cnt >> 8) as u8; - block[6] = (self.cnt >> 16) as u8; + block[0] = (serial >> 24) as u8; + block[1] = (serial >> 16) as u8; + block[2] = (serial >> 8) as u8; + block[3] = serial as u8; + block[4] = cnt as u8; + block[5] = (cnt >> 8) as u8; + block[6] = (cnt >> 16) as u8; block[7] = btn_byte; - let key_idx = if self.key_idx != 0xFF { - self.key_idx as usize - } else if self.vag_type == VagType::Type4 { 2 } else { 1 }; + let key_idx = if key_idx != 0xFF { + key_idx as usize + } else if vag_type == VagType::Type4 { 2 } else { 1 }; let store = keys::get_keystore(); if let Some(key) = store.get_vag_key((key_idx + 1) as u8) { @@ -758,9 +777,14 @@ impl VagDecoder { } } - /// Build DecodedSignal from internal state + /// Build DecodedSignal from internal state (sets extra when decrypted for encode-from-capture) fn build_decoded_signal(&self) -> DecodedSignal { let key1 = ((self.key1_high as u64) << 32) | (self.key1_low as u64); + let extra = if self.decrypted { + Some((self.vag_type as u8 as u64) | ((self.key_idx as u64) << 8)) + } else { + None + }; DecodedSignal { serial: if self.decrypted { Some(self.serial) } else { None }, @@ -770,6 +794,7 @@ impl VagDecoder { data: key1, data_count_bit: self.data_count_bit, encoder_capable: self.decrypted, + extra, } } } @@ -1160,7 +1185,7 @@ impl ProtocolDecoder for VagDecoder { } fn supports_encoding(&self) -> bool { - self.decrypted + true } fn encode(&self, decoded: &DecodedSignal, _button: u8) -> Option> {