v1.0.0
This commit is contained in:
@@ -1,2 +1,3 @@
|
||||
/target
|
||||
/EXPORTS
|
||||
desktop.ini
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -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/`.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
+44
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
@@ -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.
|
||||
+43
@@ -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).
|
||||
+44
-2
@@ -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<LevelDuration> = 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,
|
||||
|
||||
@@ -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<u64>,
|
||||
/// Raw level+duration pairs
|
||||
pub raw_pairs: Vec<StoredLevelDuration>,
|
||||
/// 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,
|
||||
|
||||
@@ -298,6 +298,7 @@ fn import_fob_v2(fob: &FobFile, next_id: u32) -> Result<Capture> {
|
||||
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<Capture> {
|
||||
crc_valid: cap.crc_valid,
|
||||
data,
|
||||
data_count_bit: cap.data_bits,
|
||||
data_extra: None,
|
||||
raw_pairs,
|
||||
status,
|
||||
received_rf: None,
|
||||
|
||||
@@ -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<u64>,
|
||||
}
|
||||
|
||||
impl DecodedSignal {
|
||||
@@ -43,6 +45,7 @@ impl DecodedSignal {
|
||||
data,
|
||||
data_count_bit: bit_count,
|
||||
encoder_capable: false,
|
||||
extra: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +116,7 @@ impl FiatV0Decoder {
|
||||
data,
|
||||
data_count_bit: 71,
|
||||
encoder_capable: true,
|
||||
extra: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -84,6 +84,7 @@ impl KiaV0Decoder {
|
||||
data,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: true,
|
||||
extra: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +133,7 @@ impl KiaV1Decoder {
|
||||
data,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: true,
|
||||
extra: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,6 +128,7 @@ impl KiaV2Decoder {
|
||||
data,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: true,
|
||||
extra: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,6 +154,7 @@ impl KiaV3V4Decoder {
|
||||
data: key_data,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: true,
|
||||
extra: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,6 +198,7 @@ impl KiaV5Decoder {
|
||||
data: key,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: false, // V5 is decode-only
|
||||
extra: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -538,6 +538,7 @@ impl ProtocolDecoder for KiaV6Decoder {
|
||||
data: key_data,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: false,
|
||||
extra: None,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ impl ScherKhanDecoder {
|
||||
data,
|
||||
data_count_bit: bit_count,
|
||||
encoder_capable: false,
|
||||
extra: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +101,7 @@ impl StarLineDecoder {
|
||||
data,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: true,
|
||||
extra: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,6 +150,7 @@ impl SubaruDecoder {
|
||||
data: key,
|
||||
data_count_bit: 64,
|
||||
encoder_capable: true,
|
||||
extra: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ impl SuzukiDecoder {
|
||||
data,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: true,
|
||||
extra: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+70
-45
@@ -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<Vec<LevelDuration>> {
|
||||
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<Vec<LevelDuration>> {
|
||||
fn encode_type1(decoded: &DecodedSignal, key_idx: u8) -> Option<Vec<LevelDuration>> {
|
||||
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<Vec<LevelDuration>> {
|
||||
fn encode_type2(decoded: &DecodedSignal) -> Option<Vec<LevelDuration>> {
|
||||
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<Vec<LevelDuration>> {
|
||||
fn encode_type3_4(decoded: &DecodedSignal, vag_type: VagType, key_idx: u8) -> Option<Vec<LevelDuration>> {
|
||||
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<Vec<LevelDuration>> {
|
||||
|
||||
Reference in New Issue
Block a user