48ee413c9b
New protocols (from ProtoPirate): - Mazda V0: 433MHz, pair-based decoding, XOR deobfuscation - Mitsubishi V0: 868MHz, PWM, bit negation + XOR unscrambling - Porsche Touareg: 433/868MHz, sync preamble, 24-bit rotate cipher - Fiat V1 (Magneti Marelli BSI): 433MHz, auto-detected timing variants Protocol fixes aligned with ProtoPirate reference: - Kia V1: Fix Manchester level mapping (inverted convention), off-by-one bit count, CRC4 simplified to 7 bytes + offset 1 - Kia V2: Fix CRC byte layout (was double-counting nibbles), off-by-one bit count, preamble short-HIGH handling - Fiat V0: Fix endbyte transform (remove (<<1)|1), standard Manchester encoder (was differential), 7 btn bits (was 6), gap path fallback - PSA: Fix modified TEA (XTEA-like dynamic key selection), XOR decrypt byte mapping, critical key2_low construction bug, encoder polarity and preamble, add key1_high nibble validation, dual preamble patterns - Ford V0: Add calculate_checksum (sums all serial+count bytes) for encoder Kia V6 encoder ported: - Forward AES-128 (SubBytes, ShiftRows, MixColumns, encrypt) - encrypt_payload: build plaintext, AES encrypt, pack into 3 parts - Two-pass Manchester upload (640 + 38 preamble pairs) - fx_field extraction stored in DecodedSignal.extra for encode roundtrip Updated README, protocol docs, and version bump to 1.2.0.
296 lines
10 KiB
Rust
296 lines
10 KiB
Rust
//! Kia V1 protocol decoder/encoder
|
|
//!
|
|
//! Aligned with ProtoPirate reference: `REFERENCES/ProtoPirate/protocols/kia_v1.c`.
|
|
//! Decode/encode logic (Manchester, CRC4, preamble, field layout) matches reference.
|
|
//!
|
|
//! Protocol characteristics:
|
|
//! - Manchester encoding: 800/1600µs timing
|
|
//! - 57 bits total (32 serial + 8 button + 12 counter + 4 CRC)
|
|
//! - Long preamble of ~90 long pairs
|
|
//! - CRC4 checksum (XOR of nibbles + offset 1, 7 bytes including cnt_high)
|
|
|
|
use super::{ProtocolDecoder, ProtocolTiming, DecodedSignal};
|
|
use crate::radio::demodulator::LevelDuration;
|
|
use crate::duration_diff;
|
|
|
|
const TE_SHORT: u32 = 800;
|
|
const TE_LONG: u32 = 1600;
|
|
const TE_DELTA: u32 = 200;
|
|
const MIN_COUNT_BIT: usize = 57;
|
|
|
|
/// Manchester decoder states (matches protopirate kia_v1 Manchester state machine)
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
enum ManchesterState {
|
|
Mid0,
|
|
Mid1,
|
|
Start0,
|
|
Start1,
|
|
}
|
|
|
|
/// Decoder states (matches protopirate's KiaV1DecoderStep)
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
enum DecoderStep {
|
|
Reset,
|
|
CheckPreamble,
|
|
DecodeData,
|
|
}
|
|
|
|
/// Kia V1 protocol decoder
|
|
pub struct KiaV1Decoder {
|
|
step: DecoderStep,
|
|
te_last: u32,
|
|
header_count: u16,
|
|
decode_data: u64,
|
|
decode_count_bit: usize,
|
|
manchester_state: ManchesterState,
|
|
}
|
|
|
|
impl KiaV1Decoder {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
step: DecoderStep::Reset,
|
|
te_last: 0,
|
|
header_count: 0,
|
|
decode_data: 0,
|
|
decode_count_bit: 0,
|
|
manchester_state: ManchesterState::Mid1,
|
|
}
|
|
}
|
|
|
|
/// CRC4 for Kia V1 (matches kia_v1.c: XOR nibbles + offset)
|
|
fn crc4(bytes: &[u8], offset: u8) -> u8 {
|
|
let mut crc: u8 = 0;
|
|
for &byte in bytes {
|
|
crc ^= (byte & 0x0F) ^ (byte >> 4);
|
|
}
|
|
(crc.wrapping_add(offset)) & 0x0F
|
|
}
|
|
|
|
/// Manchester state machine (Flipper convention: level ? ShortLow : ShortHigh)
|
|
fn manchester_advance(&mut self, is_short: bool, level: bool) -> Option<bool> {
|
|
// C: event = level ? ManchesterEventShortLow : ManchesterEventShortHigh (inverted)
|
|
let event = match (is_short, level) {
|
|
(true, true) => 0, // level=true → ShortLow
|
|
(true, false) => 1, // level=false → ShortHigh
|
|
(false, true) => 2, // level=true → LongLow
|
|
(false, false) => 3, // level=false → LongHigh
|
|
};
|
|
|
|
let (new_state, output) = match (self.manchester_state, event) {
|
|
(ManchesterState::Mid0, 0) | (ManchesterState::Mid1, 0) =>
|
|
(ManchesterState::Start0, None),
|
|
(ManchesterState::Mid0, 1) | (ManchesterState::Mid1, 1) =>
|
|
(ManchesterState::Start1, None),
|
|
|
|
(ManchesterState::Start1, 0) => (ManchesterState::Mid1, Some(true)),
|
|
(ManchesterState::Start1, 2) => (ManchesterState::Start0, Some(true)),
|
|
|
|
(ManchesterState::Start0, 1) => (ManchesterState::Mid0, Some(false)),
|
|
(ManchesterState::Start0, 3) => (ManchesterState::Start1, Some(false)),
|
|
|
|
_ => (ManchesterState::Mid1, None),
|
|
};
|
|
|
|
self.manchester_state = new_state;
|
|
output
|
|
}
|
|
|
|
/// Parse decoded data
|
|
fn parse_data(&self) -> DecodedSignal {
|
|
let data = self.decode_data;
|
|
|
|
// Field layout matches kia_v1.c: serial(32) | button(8) | cnt_low(8) | cnt_high(4) | crc(4)
|
|
let serial = (data >> 24) as u32;
|
|
let button = ((data >> 16) & 0xFF) as u8;
|
|
let cnt_low = ((data >> 8) & 0xFF) as u16;
|
|
let cnt_high = ((data >> 4) & 0x0F) as u16;
|
|
let counter = (cnt_high << 8) | cnt_low;
|
|
let received_crc = (data & 0x0F) as u8;
|
|
|
|
// Calculate CRC
|
|
let mut char_data = [0u8; 7];
|
|
char_data[0] = ((serial >> 24) & 0xFF) as u8;
|
|
char_data[1] = ((serial >> 16) & 0xFF) as u8;
|
|
char_data[2] = ((serial >> 8) & 0xFF) as u8;
|
|
char_data[3] = (serial & 0xFF) as u8;
|
|
char_data[4] = button;
|
|
char_data[5] = (counter & 0xFF) as u8;
|
|
|
|
// CRC4: always 7 bytes with offset 1 (matches updated ProtoPirate kia_v1.c)
|
|
char_data[6] = cnt_high as u8;
|
|
let crc = Self::crc4(&char_data, 1);
|
|
|
|
DecodedSignal {
|
|
serial: Some(serial),
|
|
button: Some(button),
|
|
counter: Some(counter),
|
|
crc_valid: received_crc == crc,
|
|
data,
|
|
data_count_bit: MIN_COUNT_BIT,
|
|
encoder_capable: true,
|
|
extra: None,
|
|
protocol_display_name: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ProtocolDecoder for KiaV1Decoder {
|
|
fn name(&self) -> &'static str {
|
|
"Kia V1"
|
|
}
|
|
|
|
fn timing(&self) -> ProtocolTiming {
|
|
ProtocolTiming {
|
|
te_short: TE_SHORT,
|
|
te_long: TE_LONG,
|
|
te_delta: TE_DELTA,
|
|
min_count_bit: MIN_COUNT_BIT,
|
|
}
|
|
}
|
|
|
|
fn supported_frequencies(&self) -> &[u32] {
|
|
&[315_000_000, 433_920_000]
|
|
}
|
|
|
|
fn reset(&mut self) {
|
|
self.step = DecoderStep::Reset;
|
|
self.te_last = 0;
|
|
self.header_count = 0;
|
|
self.decode_data = 0;
|
|
self.decode_count_bit = 0;
|
|
self.manchester_state = ManchesterState::Mid1;
|
|
}
|
|
|
|
fn feed(&mut self, level: bool, duration: u32) -> Option<DecodedSignal> {
|
|
let is_short = duration_diff!(duration, TE_SHORT) < TE_DELTA;
|
|
let is_long = duration_diff!(duration, TE_LONG) < TE_DELTA;
|
|
|
|
match self.step {
|
|
DecoderStep::Reset => {
|
|
if level && is_long {
|
|
self.step = DecoderStep::CheckPreamble;
|
|
self.te_last = duration;
|
|
self.header_count = 0;
|
|
self.decode_data = 0;
|
|
self.decode_count_bit = 0;
|
|
self.manchester_state = ManchesterState::Mid1;
|
|
}
|
|
}
|
|
|
|
DecoderStep::CheckPreamble => {
|
|
if !level {
|
|
if is_long && duration_diff!(self.te_last, TE_LONG) < TE_DELTA {
|
|
self.header_count += 1;
|
|
self.te_last = duration;
|
|
} else {
|
|
self.step = DecoderStep::Reset;
|
|
}
|
|
}
|
|
|
|
if self.header_count > 70 {
|
|
if !level && is_short && duration_diff!(self.te_last, TE_LONG) < TE_DELTA {
|
|
// C: decode_count_bit=1, then add_bit(1) increments to 2
|
|
self.decode_count_bit = 2;
|
|
self.decode_data = 1;
|
|
self.header_count = 0;
|
|
self.step = DecoderStep::DecodeData;
|
|
}
|
|
}
|
|
}
|
|
|
|
DecoderStep::DecodeData => {
|
|
if is_short {
|
|
if let Some(bit) = self.manchester_advance(true, level) {
|
|
self.decode_data = (self.decode_data << 1) | (bit as u64);
|
|
self.decode_count_bit += 1;
|
|
}
|
|
} else if is_long {
|
|
if let Some(bit) = self.manchester_advance(false, level) {
|
|
self.decode_data = (self.decode_data << 1) | (bit as u64);
|
|
self.decode_count_bit += 1;
|
|
}
|
|
} else {
|
|
self.step = DecoderStep::Reset;
|
|
return None;
|
|
}
|
|
|
|
if self.decode_count_bit == MIN_COUNT_BIT {
|
|
let result = self.parse_data();
|
|
self.step = DecoderStep::Reset;
|
|
return Some(result);
|
|
}
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
fn supports_encoding(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
fn encode(&self, decoded: &DecodedSignal, button: u8) -> Option<Vec<LevelDuration>> {
|
|
let serial = decoded.serial?;
|
|
let counter = decoded.counter.unwrap_or(0);
|
|
|
|
// Calculate CRC
|
|
let cnt_high = ((counter >> 8) & 0x0F) as u8;
|
|
let mut char_data = [0u8; 7];
|
|
char_data[0] = ((serial >> 24) & 0xFF) as u8;
|
|
char_data[1] = ((serial >> 16) & 0xFF) as u8;
|
|
char_data[2] = ((serial >> 8) & 0xFF) as u8;
|
|
char_data[3] = (serial & 0xFF) as u8;
|
|
char_data[4] = button;
|
|
char_data[5] = (counter & 0xFF) as u8;
|
|
|
|
// CRC4: always 7 bytes with offset 1 (matches updated ProtoPirate kia_v1.c)
|
|
char_data[6] = cnt_high;
|
|
let crc = Self::crc4(&char_data, 1);
|
|
|
|
// Build data
|
|
let data: u64 = ((serial as u64) << 24) |
|
|
((button as u64) << 16) |
|
|
(((counter & 0xFF) as u64) << 8) |
|
|
((cnt_high as u64) << 4) |
|
|
(crc as u64);
|
|
|
|
let mut signal = Vec::with_capacity(600);
|
|
|
|
// Generate 3 bursts (matches protopirate kia_v1 encode)
|
|
for burst in 0..3 {
|
|
if burst > 0 {
|
|
signal.push(LevelDuration::new(false, 25000));
|
|
}
|
|
|
|
// Preamble: 90 long pairs
|
|
for _ in 0..90 {
|
|
signal.push(LevelDuration::new(false, TE_LONG));
|
|
signal.push(LevelDuration::new(true, TE_LONG));
|
|
}
|
|
|
|
// Short gap before data
|
|
signal.push(LevelDuration::new(false, TE_SHORT));
|
|
|
|
// Data: 57 bits Manchester encoded, MSB first
|
|
for bit_num in (1..MIN_COUNT_BIT).rev() {
|
|
let bit = ((data >> (bit_num - 1)) & 1) == 1;
|
|
if bit {
|
|
signal.push(LevelDuration::new(true, TE_SHORT));
|
|
signal.push(LevelDuration::new(false, TE_SHORT));
|
|
} else {
|
|
signal.push(LevelDuration::new(false, TE_SHORT));
|
|
signal.push(LevelDuration::new(true, TE_SHORT));
|
|
}
|
|
}
|
|
}
|
|
|
|
Some(signal)
|
|
}
|
|
}
|
|
|
|
impl Default for KiaV1Decoder {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|