Version 1.0.0
This commit is contained in:
@@ -0,0 +1,290 @@
|
||||
//! AUT64 block cipher implementation
|
||||
//!
|
||||
//! Ported from protopirate's aut64.c
|
||||
//!
|
||||
//! AUT64 algorithm: 12 rounds, 8-byte block/key size
|
||||
//! Based on: Reference AUT64 implementation
|
||||
//! See: https://www.usenix.org/system/files/conference/usenixsecurity16/sec16_paper_garcia.pdf
|
||||
|
||||
pub const AUT64_NUM_ROUNDS: usize = 12;
|
||||
pub const AUT64_BLOCK_SIZE: usize = 8;
|
||||
pub const AUT64_KEY_SIZE: usize = 8;
|
||||
pub const AUT64_PBOX_SIZE: usize = 8;
|
||||
pub const AUT64_SBOX_SIZE: usize = 16;
|
||||
#[allow(dead_code)]
|
||||
pub const AUT64_KEY_STRUCT_PACKED_SIZE: usize = 16;
|
||||
|
||||
/// AUT64 key structure
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Aut64Key {
|
||||
pub index: u8,
|
||||
pub key: [u8; AUT64_KEY_SIZE],
|
||||
pub pbox: [u8; AUT64_PBOX_SIZE],
|
||||
pub sbox: [u8; AUT64_SBOX_SIZE],
|
||||
}
|
||||
|
||||
impl Default for Aut64Key {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
index: 0,
|
||||
key: [0u8; AUT64_KEY_SIZE],
|
||||
pbox: [0u8; AUT64_PBOX_SIZE],
|
||||
sbox: [0u8; AUT64_SBOX_SIZE],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Round-dependent upper-nibble lookup table
|
||||
static TABLE_LN: [[u8; 8]; AUT64_NUM_ROUNDS] = [
|
||||
[0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3], // Round 0
|
||||
[0x5, 0x4, 0x7, 0x6, 0x1, 0x0, 0x3, 0x2], // Round 1
|
||||
[0x6, 0x7, 0x4, 0x5, 0x2, 0x3, 0x0, 0x1], // Round 2
|
||||
[0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0], // Round 3
|
||||
[0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7], // Round 4
|
||||
[0x1, 0x0, 0x3, 0x2, 0x5, 0x4, 0x7, 0x6], // Round 5
|
||||
[0x2, 0x3, 0x0, 0x1, 0x6, 0x7, 0x4, 0x5], // Round 6
|
||||
[0x3, 0x2, 0x1, 0x0, 0x7, 0x6, 0x5, 0x4], // Round 7
|
||||
[0x5, 0x4, 0x7, 0x6, 0x1, 0x0, 0x3, 0x2], // Round 8
|
||||
[0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3], // Round 9
|
||||
[0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0], // Round 10
|
||||
[0x6, 0x7, 0x4, 0x5, 0x2, 0x3, 0x0, 0x1], // Round 11
|
||||
];
|
||||
|
||||
/// Round-dependent lower-nibble lookup table
|
||||
static TABLE_UN: [[u8; 8]; AUT64_NUM_ROUNDS] = [
|
||||
[0x1, 0x0, 0x3, 0x2, 0x5, 0x4, 0x7, 0x6], // Round 0
|
||||
[0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7], // Round 1
|
||||
[0x3, 0x2, 0x1, 0x0, 0x7, 0x6, 0x5, 0x4], // Round 2
|
||||
[0x2, 0x3, 0x0, 0x1, 0x6, 0x7, 0x4, 0x5], // Round 3
|
||||
[0x5, 0x4, 0x7, 0x6, 0x1, 0x0, 0x3, 0x2], // Round 4
|
||||
[0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3], // Round 5
|
||||
[0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0], // Round 6
|
||||
[0x6, 0x7, 0x4, 0x5, 0x2, 0x3, 0x0, 0x1], // Round 7
|
||||
[0x3, 0x2, 0x1, 0x0, 0x7, 0x6, 0x5, 0x4], // Round 8
|
||||
[0x2, 0x3, 0x0, 0x1, 0x6, 0x7, 0x4, 0x5], // Round 9
|
||||
[0x1, 0x0, 0x3, 0x2, 0x5, 0x4, 0x7, 0x6], // Round 10
|
||||
[0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7], // Round 11
|
||||
];
|
||||
|
||||
/// GF(2^4) multiplication table (nibble offset table)
|
||||
#[rustfmt::skip]
|
||||
static TABLE_OFFSET: [u8; 256] = [
|
||||
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // 0
|
||||
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, // 1
|
||||
0x0, 0x2, 0x4, 0x6, 0x8, 0xA, 0xC, 0xE, 0x3, 0x1, 0x7, 0x5, 0xB, 0x9, 0xF, 0xD, // 2
|
||||
0x0, 0x3, 0x6, 0x5, 0xC, 0xF, 0xA, 0x9, 0xB, 0x8, 0xD, 0xE, 0x7, 0x4, 0x1, 0x2, // 3
|
||||
0x0, 0x4, 0x8, 0xC, 0x3, 0x7, 0xB, 0xF, 0x6, 0x2, 0xE, 0xA, 0x5, 0x1, 0xD, 0x9, // 4
|
||||
0x0, 0x5, 0xA, 0xF, 0x7, 0x2, 0xD, 0x8, 0xE, 0xB, 0x4, 0x1, 0x9, 0xC, 0x3, 0x6, // 5
|
||||
0x0, 0x6, 0xC, 0xA, 0xB, 0xD, 0x7, 0x1, 0x5, 0x3, 0x9, 0xF, 0xE, 0x8, 0x2, 0x4, // 6
|
||||
0x0, 0x7, 0xE, 0x9, 0xF, 0x8, 0x1, 0x6, 0xD, 0xA, 0x3, 0x4, 0x2, 0x5, 0xC, 0xB, // 7
|
||||
0x0, 0x8, 0x3, 0xB, 0x6, 0xE, 0x5, 0xD, 0xC, 0x4, 0xF, 0x7, 0xA, 0x2, 0x9, 0x1, // 8
|
||||
0x0, 0x9, 0x1, 0x8, 0x2, 0xB, 0x3, 0xA, 0x4, 0xD, 0x5, 0xC, 0x6, 0xF, 0x7, 0xE, // 9
|
||||
0x0, 0xA, 0x7, 0xD, 0xE, 0x4, 0x9, 0x3, 0xF, 0x5, 0x8, 0x2, 0x1, 0xB, 0x6, 0xC, // A
|
||||
0x0, 0xB, 0x5, 0xE, 0xA, 0x1, 0xF, 0x4, 0x7, 0xC, 0x2, 0x9, 0xD, 0x6, 0x8, 0x3, // B
|
||||
0x0, 0xC, 0xB, 0x7, 0x5, 0x9, 0xE, 0x2, 0xA, 0x6, 0x1, 0xD, 0xF, 0x3, 0x4, 0x8, // C
|
||||
0x0, 0xD, 0x9, 0x4, 0x1, 0xC, 0x8, 0x5, 0x2, 0xF, 0xB, 0x6, 0x3, 0xE, 0xA, 0x7, // D
|
||||
0x0, 0xE, 0xF, 0x1, 0xD, 0x3, 0x2, 0xC, 0x9, 0x7, 0x6, 0x8, 0x4, 0xA, 0xB, 0x5, // E
|
||||
0x0, 0xF, 0xD, 0x2, 0x9, 0x6, 0x4, 0xB, 0x1, 0xE, 0xC, 0x3, 0x8, 0x7, 0x5, 0xA, // F
|
||||
];
|
||||
|
||||
/// S-box substitution table
|
||||
static TABLE_SUB: [u8; 16] = [
|
||||
0x0, 0x1, 0x9, 0xE, 0xD, 0xB, 0x7, 0x6,
|
||||
0xF, 0x2, 0xC, 0x5, 0xA, 0x4, 0x3, 0x8,
|
||||
];
|
||||
|
||||
/// Key nibble operation: apply key-dependent GF offset
|
||||
fn key_nibble(key: &Aut64Key, nibble: u8, table: &[u8; 8], iteration: usize) -> u8 {
|
||||
let key_value = key.key[table[iteration] as usize];
|
||||
let offset = ((key_value as usize) << 4) | (nibble as usize);
|
||||
TABLE_OFFSET[offset]
|
||||
}
|
||||
|
||||
/// Compute round key from state
|
||||
fn round_key(key: &Aut64Key, state: &[u8], round_n: usize) -> u8 {
|
||||
let mut result_hi: u8 = 0;
|
||||
let mut result_lo: u8 = 0;
|
||||
|
||||
for i in 0..(AUT64_BLOCK_SIZE - 1) {
|
||||
result_hi ^= key_nibble(key, state[i] >> 4, &TABLE_UN[round_n], i);
|
||||
result_lo ^= key_nibble(key, state[i] & 0x0F, &TABLE_LN[round_n], i);
|
||||
}
|
||||
|
||||
(result_hi << 4) | result_lo
|
||||
}
|
||||
|
||||
/// Final byte nibble for key schedule
|
||||
fn final_byte_nibble(key: &Aut64Key, table: &[u8; 8]) -> u8 {
|
||||
let key_value = key.key[table[AUT64_BLOCK_SIZE - 1] as usize];
|
||||
TABLE_SUB[key_value as usize] << 4
|
||||
}
|
||||
|
||||
/// Encrypt final byte nibble (inverse S-box lookup through offset table)
|
||||
fn encrypt_final_byte_nibble(key: &Aut64Key, nibble: u8, table: &[u8; 8]) -> u8 {
|
||||
let offset = final_byte_nibble(key, table) as usize;
|
||||
|
||||
for i in 0u8..16 {
|
||||
if TABLE_OFFSET[offset + i as usize] == nibble {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
0 // Should not reach here for valid inputs
|
||||
}
|
||||
|
||||
/// Encrypt compress: compute encrypted output byte for a round
|
||||
fn encrypt_compress(key: &Aut64Key, state: &[u8], round_n: usize) -> u8 {
|
||||
let round_k = round_key(key, state, round_n);
|
||||
let mut result_hi = round_k >> 4;
|
||||
let mut result_lo = round_k & 0x0F;
|
||||
|
||||
result_hi ^= encrypt_final_byte_nibble(key, state[AUT64_BLOCK_SIZE - 1] >> 4, &TABLE_UN[round_n]);
|
||||
result_lo ^= encrypt_final_byte_nibble(key, state[AUT64_BLOCK_SIZE - 1] & 0x0F, &TABLE_LN[round_n]);
|
||||
|
||||
(result_hi << 4) | result_lo
|
||||
}
|
||||
|
||||
/// Decrypt final byte nibble (forward S-box through offset table)
|
||||
fn decrypt_final_byte_nibble(key: &Aut64Key, nibble: u8, table: &[u8; 8], result: u8) -> u8 {
|
||||
let offset = final_byte_nibble(key, table) as usize;
|
||||
TABLE_OFFSET[(result ^ nibble) as usize + offset]
|
||||
}
|
||||
|
||||
/// Decrypt compress: compute decrypted output byte for a round
|
||||
fn decrypt_compress(key: &Aut64Key, state: &[u8], round_n: usize) -> u8 {
|
||||
let round_k = round_key(key, state, round_n);
|
||||
let result_hi = round_k >> 4;
|
||||
let result_lo = round_k & 0x0F;
|
||||
|
||||
let hi = decrypt_final_byte_nibble(
|
||||
key,
|
||||
state[AUT64_BLOCK_SIZE - 1] >> 4,
|
||||
&TABLE_UN[round_n],
|
||||
result_hi,
|
||||
);
|
||||
let lo = decrypt_final_byte_nibble(
|
||||
key,
|
||||
state[AUT64_BLOCK_SIZE - 1] & 0x0F,
|
||||
&TABLE_LN[round_n],
|
||||
result_lo,
|
||||
);
|
||||
|
||||
(hi << 4) | lo
|
||||
}
|
||||
|
||||
/// S-box substitution on a full byte (applies S-box to each nibble independently)
|
||||
fn substitute(key: &Aut64Key, byte: u8) -> u8 {
|
||||
(key.sbox[(byte >> 4) as usize] << 4) | key.sbox[(byte & 0x0F) as usize]
|
||||
}
|
||||
|
||||
/// Byte-level permutation using P-box
|
||||
fn permute_bytes(key: &Aut64Key, state: &mut [u8]) {
|
||||
let mut result = [0u8; AUT64_PBOX_SIZE];
|
||||
for i in 0..AUT64_PBOX_SIZE {
|
||||
result[key.pbox[i] as usize] = state[i];
|
||||
}
|
||||
state[..AUT64_PBOX_SIZE].copy_from_slice(&result);
|
||||
}
|
||||
|
||||
/// Bit-level permutation using P-box
|
||||
fn permute_bits(key: &Aut64Key, byte: u8) -> u8 {
|
||||
let mut result: u8 = 0;
|
||||
for i in 0..8 {
|
||||
if byte & (1 << i) != 0 {
|
||||
result |= 1 << key.pbox[i];
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Compute inverse permutation box
|
||||
fn reverse_box(box_in: &[u8], len: usize) -> Vec<u8> {
|
||||
let mut reversed = vec![0u8; len];
|
||||
for i in 0..len {
|
||||
for j in 0..len {
|
||||
if box_in[j] == i as u8 {
|
||||
reversed[i] = j as u8;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
reversed
|
||||
}
|
||||
|
||||
/// AUT64 encrypt: 12 rounds of the cipher
|
||||
pub fn aut64_encrypt(key: &Aut64Key, message: &mut [u8]) {
|
||||
// Create reverse key for encryption
|
||||
let mut reverse_key = key.clone();
|
||||
let rev_pbox = reverse_box(&key.pbox, AUT64_PBOX_SIZE);
|
||||
let rev_sbox = reverse_box(&key.sbox, AUT64_SBOX_SIZE);
|
||||
reverse_key.pbox.copy_from_slice(&rev_pbox);
|
||||
reverse_key.sbox.copy_from_slice(&rev_sbox);
|
||||
|
||||
for i in 0..AUT64_NUM_ROUNDS {
|
||||
permute_bytes(&reverse_key, message);
|
||||
message[7] = encrypt_compress(&reverse_key, message, i);
|
||||
message[7] = substitute(&reverse_key, message[7]);
|
||||
message[7] = permute_bits(&reverse_key, message[7]);
|
||||
message[7] = substitute(&reverse_key, message[7]);
|
||||
}
|
||||
}
|
||||
|
||||
/// AUT64 decrypt: 12 rounds of the cipher (reverse order)
|
||||
pub fn aut64_decrypt(key: &Aut64Key, message: &mut [u8]) {
|
||||
for i in (0..AUT64_NUM_ROUNDS).rev() {
|
||||
message[7] = substitute(key, message[7]);
|
||||
message[7] = permute_bits(key, message[7]);
|
||||
message[7] = substitute(key, message[7]);
|
||||
message[7] = decrypt_compress(key, message, i);
|
||||
permute_bytes(key, message);
|
||||
}
|
||||
}
|
||||
|
||||
/// Pack an AUT64 key structure into a 16-byte array
|
||||
#[allow(dead_code)]
|
||||
pub fn aut64_pack(src: &Aut64Key) -> [u8; AUT64_KEY_STRUCT_PACKED_SIZE] {
|
||||
let mut dest = [0u8; AUT64_KEY_STRUCT_PACKED_SIZE];
|
||||
dest[0] = src.index;
|
||||
|
||||
for i in 0..(src.key.len() / 2) {
|
||||
dest[i + 1] = (src.key[i * 2] << 4) | src.key[i * 2 + 1];
|
||||
}
|
||||
|
||||
let mut pbox: u32 = 0;
|
||||
for i in 0..src.pbox.len() {
|
||||
pbox = (pbox << 3) | src.pbox[i] as u32;
|
||||
}
|
||||
dest[5] = (pbox >> 16) as u8;
|
||||
dest[6] = ((pbox >> 8) & 0xFF) as u8;
|
||||
dest[7] = (pbox & 0xFF) as u8;
|
||||
|
||||
for i in 0..(src.sbox.len() / 2) {
|
||||
dest[i + 8] = (src.sbox[i * 2] << 4) | src.sbox[i * 2 + 1];
|
||||
}
|
||||
|
||||
dest
|
||||
}
|
||||
|
||||
/// Unpack a 16-byte array into an AUT64 key structure
|
||||
#[allow(dead_code)]
|
||||
pub fn aut64_unpack(src: &[u8]) -> Aut64Key {
|
||||
let mut dest = Aut64Key::default();
|
||||
dest.index = src[0];
|
||||
|
||||
for i in 0..(dest.key.len() / 2) {
|
||||
dest.key[i * 2] = src[i + 1] >> 4;
|
||||
dest.key[i * 2 + 1] = src[i + 1] & 0xF;
|
||||
}
|
||||
|
||||
let pbox: u32 = ((src[5] as u32) << 16) | ((src[6] as u32) << 8) | src[7] as u32;
|
||||
for i in (0..dest.pbox.len()).rev() {
|
||||
dest.pbox[i] = ((pbox >> ((dest.pbox.len() - 1 - i) * 3)) & 0x7) as u8;
|
||||
}
|
||||
|
||||
for i in 0..(dest.sbox.len() / 2) {
|
||||
dest.sbox[i * 2] = src[i + 8] >> 4;
|
||||
dest.sbox[i * 2 + 1] = src[i + 8] & 0xF;
|
||||
}
|
||||
|
||||
dest
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
//! Common utilities for protocol implementations.
|
||||
|
||||
/// Decoded signal information
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DecodedSignal {
|
||||
/// Serial number / device ID
|
||||
pub serial: Option<u32>,
|
||||
/// Button code
|
||||
pub button: Option<u8>,
|
||||
/// Rolling counter
|
||||
pub counter: Option<u16>,
|
||||
/// CRC is valid
|
||||
pub crc_valid: bool,
|
||||
/// Raw data (up to 64 bits)
|
||||
pub data: u64,
|
||||
/// Number of bits in data
|
||||
pub data_count_bit: usize,
|
||||
/// Whether encoding is supported
|
||||
pub encoder_capable: bool,
|
||||
}
|
||||
|
||||
impl DecodedSignal {
|
||||
#[allow(dead_code)]
|
||||
pub fn new(data: u64, bit_count: usize) -> Self {
|
||||
Self {
|
||||
serial: None,
|
||||
button: None,
|
||||
counter: None,
|
||||
crc_valid: false,
|
||||
data,
|
||||
data_count_bit: bit_count,
|
||||
encoder_capable: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// CRC8 calculation with custom polynomial
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `data` - Data bytes to calculate CRC over
|
||||
/// * `poly` - CRC polynomial
|
||||
/// * `init` - Initial CRC value
|
||||
pub fn crc8(data: &[u8], poly: u8, init: u8) -> u8 {
|
||||
let mut crc = init;
|
||||
for &byte in data {
|
||||
crc ^= byte;
|
||||
for _ in 0..8 {
|
||||
if (crc & 0x80) != 0 {
|
||||
crc = (crc << 1) ^ poly;
|
||||
} else {
|
||||
crc <<= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
crc
|
||||
}
|
||||
|
||||
/// CRC8 for Kia protocol (polynomial 0x7F, init 0x00)
|
||||
pub fn crc8_kia(data: &[u8]) -> u8 {
|
||||
crc8(data, 0x7F, 0x00)
|
||||
}
|
||||
|
||||
/// Add a bit to the decoder's data accumulator
|
||||
#[inline]
|
||||
pub fn add_bit(data: &mut u64, count: &mut usize, bit: bool) {
|
||||
*data = (*data << 1) | (bit as u64);
|
||||
*count += 1;
|
||||
}
|
||||
|
||||
/// Button names for common keyfob buttons
|
||||
#[allow(dead_code)]
|
||||
pub fn get_button_name(btn: u8) -> &'static str {
|
||||
match btn {
|
||||
0x01 => "Lock",
|
||||
0x02 => "Unlock",
|
||||
0x03 => "Lock+Unlock",
|
||||
0x04 => "Trunk",
|
||||
0x08 => "Panic",
|
||||
_ => "Unknown",
|
||||
}
|
||||
}
|
||||
|
||||
/// Button code constants
|
||||
#[allow(dead_code)]
|
||||
pub mod buttons {
|
||||
pub const LOCK: u8 = 0x01;
|
||||
pub const UNLOCK: u8 = 0x02;
|
||||
pub const TRUNK: u8 = 0x04;
|
||||
pub const PANIC: u8 = 0x08;
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
//! Fiat V0 protocol decoder/encoder
|
||||
//!
|
||||
//! Ported from protopirate's fiat_v0.c
|
||||
//!
|
||||
//! Protocol characteristics:
|
||||
//! - Differential Manchester encoding: 200/400µs timing
|
||||
//! - 64-bit data (cnt:32 | serial:32) + 6-bit button
|
||||
//! - 150 preamble pairs, 800µs gap, 3 bursts
|
||||
|
||||
use super::{DecodedSignal, ProtocolDecoder, ProtocolTiming};
|
||||
use crate::duration_diff;
|
||||
use crate::radio::demodulator::LevelDuration;
|
||||
|
||||
const TE_SHORT: u32 = 200;
|
||||
const TE_LONG: u32 = 400;
|
||||
const TE_DELTA: u32 = 100;
|
||||
#[allow(dead_code)]
|
||||
const MIN_COUNT_BIT: usize = 64;
|
||||
const PREAMBLE_PAIRS: u16 = 150;
|
||||
const GAP_US: u32 = 800;
|
||||
const TOTAL_BURSTS: u8 = 3;
|
||||
const INTER_BURST_GAP: u32 = 25000;
|
||||
|
||||
/// Manchester decoder states
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum ManchesterState {
|
||||
Mid0,
|
||||
Mid1,
|
||||
Start0,
|
||||
Start1,
|
||||
}
|
||||
|
||||
/// Decoder states
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum DecoderStep {
|
||||
Reset,
|
||||
Preamble,
|
||||
Data,
|
||||
}
|
||||
|
||||
/// Fiat V0 protocol decoder
|
||||
pub struct FiatV0Decoder {
|
||||
step: DecoderStep,
|
||||
preamble_count: u16,
|
||||
manchester_state: ManchesterState,
|
||||
data_low: u32,
|
||||
data_high: u32,
|
||||
bit_count: u8,
|
||||
cnt: u32,
|
||||
serial: u32,
|
||||
btn: u8,
|
||||
te_last: u32,
|
||||
}
|
||||
|
||||
impl FiatV0Decoder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
step: DecoderStep::Reset,
|
||||
preamble_count: 0,
|
||||
manchester_state: ManchesterState::Mid1,
|
||||
data_low: 0,
|
||||
data_high: 0,
|
||||
bit_count: 0,
|
||||
cnt: 0,
|
||||
serial: 0,
|
||||
btn: 0,
|
||||
te_last: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Manchester advance - returns decoded bit or None
|
||||
fn manchester_advance(&mut self, is_short: bool, is_high: bool) -> Option<bool> {
|
||||
let event = match (is_short, is_high) {
|
||||
(true, true) => 0,
|
||||
(true, false) => 1,
|
||||
(false, true) => 2,
|
||||
(false, false) => 3,
|
||||
};
|
||||
|
||||
let (new_state, output) = match (self.manchester_state, event) {
|
||||
(ManchesterState::Mid0, 0) | (ManchesterState::Mid1, 0) => {
|
||||
(ManchesterState::Start1, None)
|
||||
}
|
||||
(ManchesterState::Mid0, 1) | (ManchesterState::Mid1, 1) => {
|
||||
(ManchesterState::Start0, None)
|
||||
}
|
||||
(ManchesterState::Start1, 1) => (ManchesterState::Mid1, Some(true)),
|
||||
(ManchesterState::Start1, 3) => (ManchesterState::Start0, Some(true)),
|
||||
(ManchesterState::Start0, 0) => (ManchesterState::Mid0, Some(false)),
|
||||
(ManchesterState::Start0, 2) => (ManchesterState::Start1, Some(false)),
|
||||
_ => (ManchesterState::Mid1, None),
|
||||
};
|
||||
|
||||
self.manchester_state = new_state;
|
||||
output
|
||||
}
|
||||
|
||||
fn manchester_reset(&mut self) {
|
||||
self.manchester_state = ManchesterState::Mid1;
|
||||
}
|
||||
|
||||
fn add_manchester_bit(&mut self, bit: bool) {
|
||||
let new_bit = if bit { 1u32 } else { 0u32 };
|
||||
let carry = (self.data_low >> 31) & 1;
|
||||
self.data_low = (self.data_low << 1) | new_bit;
|
||||
self.data_high = (self.data_high << 1) | carry;
|
||||
self.bit_count += 1;
|
||||
|
||||
if self.bit_count == 0x40 {
|
||||
self.serial = self.data_low;
|
||||
self.cnt = self.data_high;
|
||||
self.data_low = 0;
|
||||
self.data_high = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_data(&self) -> DecodedSignal {
|
||||
let data = ((self.cnt as u64) << 32) | (self.serial as u64);
|
||||
|
||||
DecodedSignal {
|
||||
serial: Some(self.serial),
|
||||
button: Some(self.btn),
|
||||
counter: Some(self.cnt as u16),
|
||||
crc_valid: true, // No CRC in Fiat V0
|
||||
data,
|
||||
data_count_bit: 71,
|
||||
encoder_capable: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolDecoder for FiatV0Decoder {
|
||||
fn name(&self) -> &'static str {
|
||||
"Fiat V0"
|
||||
}
|
||||
|
||||
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] {
|
||||
&[433_920_000]
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.step = DecoderStep::Reset;
|
||||
self.preamble_count = 0;
|
||||
self.data_low = 0;
|
||||
self.data_high = 0;
|
||||
self.bit_count = 0;
|
||||
self.cnt = 0;
|
||||
self.serial = 0;
|
||||
self.btn = 0;
|
||||
self.te_last = 0;
|
||||
self.manchester_reset();
|
||||
}
|
||||
|
||||
fn feed(&mut self, level: bool, duration: u32) -> Option<DecodedSignal> {
|
||||
match self.step {
|
||||
DecoderStep::Reset => {
|
||||
if !level {
|
||||
return None;
|
||||
}
|
||||
if duration_diff!(duration, TE_SHORT) < TE_DELTA {
|
||||
self.data_low = 0;
|
||||
self.data_high = 0;
|
||||
self.step = DecoderStep::Preamble;
|
||||
self.te_last = duration;
|
||||
self.preamble_count = 0;
|
||||
self.bit_count = 0;
|
||||
self.manchester_reset();
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::Preamble => {
|
||||
// Count short pulses in preamble, look for gap
|
||||
if duration_diff!(duration, TE_SHORT) < TE_DELTA {
|
||||
self.preamble_count += 1;
|
||||
self.te_last = duration;
|
||||
} else if self.preamble_count >= PREAMBLE_PAIRS {
|
||||
// Check for gap
|
||||
if duration_diff!(duration, GAP_US) < TE_DELTA {
|
||||
self.step = DecoderStep::Data;
|
||||
self.preamble_count = 0;
|
||||
self.data_low = 0;
|
||||
self.data_high = 0;
|
||||
self.bit_count = 0;
|
||||
self.te_last = duration;
|
||||
return None;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::Data => {
|
||||
let is_short = duration_diff!(duration, TE_SHORT) < TE_DELTA;
|
||||
let is_long = duration_diff!(duration, TE_LONG) < TE_DELTA;
|
||||
|
||||
if is_short || is_long {
|
||||
if let Some(bit) = self.manchester_advance(is_short, level) {
|
||||
self.add_manchester_bit(bit);
|
||||
|
||||
if self.bit_count > 0x46 {
|
||||
self.btn = ((self.data_low << 1) | 1) as u8;
|
||||
let result = self.parse_data();
|
||||
self.data_low = 0;
|
||||
self.data_high = 0;
|
||||
self.bit_count = 0;
|
||||
self.step = DecoderStep::Reset;
|
||||
return Some(result);
|
||||
}
|
||||
}
|
||||
} else if duration > TE_LONG * 3 {
|
||||
// End of signal
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
|
||||
self.te_last = duration;
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn supports_encoding(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn encode(&self, decoded: &DecodedSignal, button: u8) -> Option<Vec<LevelDuration>> {
|
||||
let serial = decoded.serial?;
|
||||
let cnt = decoded.counter.unwrap_or(0) as u32;
|
||||
|
||||
let data = ((cnt as u64) << 32) | (serial as u64);
|
||||
// Reverse the decoder's btn fix: decoder does (x << 1) | 1
|
||||
let btn_to_send = button >> 1;
|
||||
|
||||
let mut signal = Vec::with_capacity(1024);
|
||||
|
||||
for burst in 0..TOTAL_BURSTS {
|
||||
if burst > 0 {
|
||||
signal.push(LevelDuration::new(false, INTER_BURST_GAP));
|
||||
}
|
||||
|
||||
// Preamble
|
||||
for i in 0..PREAMBLE_PAIRS {
|
||||
signal.push(LevelDuration::new(true, TE_SHORT));
|
||||
if i < PREAMBLE_PAIRS - 1 {
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
} else {
|
||||
signal.push(LevelDuration::new(false, GAP_US));
|
||||
}
|
||||
}
|
||||
|
||||
// First bit (bit 63)
|
||||
let first_bit = (data >> 63) & 1 == 1;
|
||||
if first_bit {
|
||||
signal.push(LevelDuration::new(true, TE_LONG));
|
||||
} else {
|
||||
signal.push(LevelDuration::new(true, TE_SHORT));
|
||||
signal.push(LevelDuration::new(false, TE_LONG));
|
||||
}
|
||||
|
||||
let mut prev_bit = first_bit;
|
||||
|
||||
// Remaining 63 data bits using differential Manchester
|
||||
for bit in (0..63).rev() {
|
||||
let curr_bit = (data >> bit) & 1 == 1;
|
||||
match (prev_bit, curr_bit) {
|
||||
(false, false) => {
|
||||
signal.push(LevelDuration::new(true, TE_SHORT));
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
}
|
||||
(false, true) => {
|
||||
signal.push(LevelDuration::new(true, TE_LONG));
|
||||
}
|
||||
(true, false) => {
|
||||
signal.push(LevelDuration::new(false, TE_LONG));
|
||||
}
|
||||
(true, true) => {
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
signal.push(LevelDuration::new(true, TE_SHORT));
|
||||
}
|
||||
}
|
||||
prev_bit = curr_bit;
|
||||
}
|
||||
|
||||
// 6 button bits
|
||||
for bit in (0..6).rev() {
|
||||
let curr_bit = (btn_to_send >> bit) & 1 == 1;
|
||||
match (prev_bit, curr_bit) {
|
||||
(false, false) => {
|
||||
signal.push(LevelDuration::new(true, TE_SHORT));
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
}
|
||||
(false, true) => {
|
||||
signal.push(LevelDuration::new(true, TE_LONG));
|
||||
}
|
||||
(true, false) => {
|
||||
signal.push(LevelDuration::new(false, TE_LONG));
|
||||
}
|
||||
(true, true) => {
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
signal.push(LevelDuration::new(true, TE_SHORT));
|
||||
}
|
||||
}
|
||||
prev_bit = curr_bit;
|
||||
}
|
||||
|
||||
// End marker
|
||||
if prev_bit {
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
}
|
||||
signal.push(LevelDuration::new(false, TE_SHORT * 8));
|
||||
}
|
||||
|
||||
Some(signal)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
//! Ford V0 protocol decoder
|
||||
//!
|
||||
//! Ported from protopirate's ford_v0.c
|
||||
//!
|
||||
//! Protocol characteristics:
|
||||
//! - Manchester encoding: 250/500µs timing
|
||||
//! - 64 bits total
|
||||
//! - Matrix-based CRC
|
||||
|
||||
use super::{ProtocolDecoder, ProtocolTiming, DecodedSignal};
|
||||
use crate::radio::demodulator::LevelDuration;
|
||||
use crate::duration_diff;
|
||||
|
||||
const TE_SHORT: u32 = 250;
|
||||
const TE_LONG: u32 = 500;
|
||||
const TE_DELTA: u32 = 100;
|
||||
const MIN_COUNT_BIT: usize = 64;
|
||||
|
||||
/// Manchester decoder states
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum ManchesterState {
|
||||
Mid0,
|
||||
Mid1,
|
||||
Start0,
|
||||
Start1,
|
||||
}
|
||||
|
||||
/// Decoder states
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum DecoderStep {
|
||||
Reset,
|
||||
CheckPreamble,
|
||||
SaveDuration,
|
||||
CheckDuration,
|
||||
}
|
||||
|
||||
/// Ford V0 protocol decoder
|
||||
pub struct FordV0Decoder {
|
||||
step: DecoderStep,
|
||||
te_last: u32,
|
||||
header_count: u16,
|
||||
decode_data: u64,
|
||||
decode_count_bit: usize,
|
||||
manchester_state: ManchesterState,
|
||||
}
|
||||
|
||||
impl FordV0Decoder {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
/// Manchester decode: advance state machine
|
||||
fn manchester_advance(&mut self, is_short: bool, is_high: bool) -> Option<bool> {
|
||||
let event = match (is_short, is_high) {
|
||||
(true, true) => 0, // Short High
|
||||
(true, false) => 1, // Short Low
|
||||
(false, true) => 2, // Long High
|
||||
(false, false) => 3, // Long Low
|
||||
};
|
||||
|
||||
let (new_state, output) = match (self.manchester_state, event) {
|
||||
// From Mid0 or Mid1
|
||||
(ManchesterState::Mid0, 0) | (ManchesterState::Mid1, 0) =>
|
||||
(ManchesterState::Start1, None),
|
||||
(ManchesterState::Mid0, 1) | (ManchesterState::Mid1, 1) =>
|
||||
(ManchesterState::Start0, None),
|
||||
|
||||
// From Start1
|
||||
(ManchesterState::Start1, 1) => (ManchesterState::Mid1, Some(true)),
|
||||
(ManchesterState::Start1, 3) => (ManchesterState::Start0, Some(true)),
|
||||
|
||||
// From Start0
|
||||
(ManchesterState::Start0, 0) => (ManchesterState::Mid0, Some(false)),
|
||||
(ManchesterState::Start0, 2) => (ManchesterState::Start1, Some(false)),
|
||||
|
||||
// Reset on invalid transitions
|
||||
_ => (ManchesterState::Mid1, None),
|
||||
};
|
||||
|
||||
self.manchester_state = new_state;
|
||||
output
|
||||
}
|
||||
|
||||
/// CRC matrix for Ford
|
||||
const CRC_MATRIX: [[u8; 4]; 8] = [
|
||||
[0x0C, 0xBB, 0x51, 0x25],
|
||||
[0x18, 0xB1, 0x62, 0xCB],
|
||||
[0x30, 0xA7, 0x44, 0x57],
|
||||
[0x60, 0x89, 0x88, 0xAE],
|
||||
[0xC0, 0xD6, 0xD4, 0x97],
|
||||
[0x25, 0x49, 0x6D, 0xE1],
|
||||
[0x4A, 0x92, 0xDA, 0x03],
|
||||
[0x94, 0xE1, 0x71, 0x06],
|
||||
];
|
||||
|
||||
/// Calculate Ford CRC
|
||||
fn calculate_crc(data: u64) -> u8 {
|
||||
let mut crc = 0u8;
|
||||
|
||||
for byte_idx in 0..7 {
|
||||
let byte = ((data >> (56 - byte_idx * 8)) & 0xFF) as u8;
|
||||
for bit in 0..8 {
|
||||
if (byte >> (7 - bit)) & 1 == 1 {
|
||||
crc ^= Self::CRC_MATRIX[bit][byte_idx % 4];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
crc
|
||||
}
|
||||
|
||||
/// Parse decoded data
|
||||
fn parse_data(data: u64) -> DecodedSignal {
|
||||
// Ford V0 format:
|
||||
// Bits 60-63: Prefix (0x5)
|
||||
// Bits 32-59: Serial (28 bits)
|
||||
// Bits 28-31: Button (4 bits)
|
||||
// Bits 16-27: Counter (12 bits)
|
||||
// Bits 8-15: Encrypted data
|
||||
// Bits 0-7: CRC
|
||||
|
||||
let serial = ((data >> 32) & 0x0FFFFFFF) as u32;
|
||||
let button = ((data >> 28) & 0x0F) as u8;
|
||||
let counter = ((data >> 16) & 0x0FFF) as u16;
|
||||
let received_crc = (data & 0xFF) as u8;
|
||||
let calculated_crc = Self::calculate_crc(data);
|
||||
|
||||
DecodedSignal {
|
||||
serial: Some(serial),
|
||||
button: Some(button),
|
||||
counter: Some(counter),
|
||||
crc_valid: received_crc == calculated_crc,
|
||||
data,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolDecoder for FordV0Decoder {
|
||||
fn name(&self) -> &'static str {
|
||||
"Ford V0"
|
||||
}
|
||||
|
||||
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] // 315 MHz (US) and 433.92 MHz (EU)
|
||||
}
|
||||
|
||||
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_short {
|
||||
self.step = DecoderStep::CheckPreamble;
|
||||
self.header_count = 1;
|
||||
self.manchester_state = ManchesterState::Mid1;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::CheckPreamble => {
|
||||
if is_short {
|
||||
self.header_count += 1;
|
||||
if self.header_count > 20 && !level {
|
||||
// Enough preamble, start looking for data
|
||||
self.step = DecoderStep::SaveDuration;
|
||||
self.decode_data = 0;
|
||||
self.decode_count_bit = 0;
|
||||
self.manchester_state = ManchesterState::Mid1;
|
||||
}
|
||||
} else if is_long {
|
||||
if self.header_count > 10 {
|
||||
self.step = DecoderStep::SaveDuration;
|
||||
self.decode_data = 0;
|
||||
self.decode_count_bit = 0;
|
||||
self.manchester_state = ManchesterState::Mid1;
|
||||
|
||||
// Process this long pulse
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::SaveDuration => {
|
||||
self.te_last = duration;
|
||||
self.step = DecoderStep::CheckDuration;
|
||||
}
|
||||
|
||||
DecoderStep::CheckDuration => {
|
||||
let last_short = duration_diff!(self.te_last, TE_SHORT) < TE_DELTA;
|
||||
let last_long = duration_diff!(self.te_last, TE_LONG) < TE_DELTA;
|
||||
|
||||
// Check for end of transmission
|
||||
if duration > TE_LONG * 3 {
|
||||
if self.decode_count_bit >= MIN_COUNT_BIT {
|
||||
let result = Self::parse_data(self.decode_data);
|
||||
self.step = DecoderStep::Reset;
|
||||
return Some(result);
|
||||
}
|
||||
self.step = DecoderStep::Reset;
|
||||
return None;
|
||||
}
|
||||
|
||||
// Manchester decode
|
||||
if last_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 last_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;
|
||||
}
|
||||
}
|
||||
|
||||
if is_short || is_long {
|
||||
if let Some(bit) = self.manchester_advance(is_short, level) {
|
||||
self.decode_data = (self.decode_data << 1) | (bit as u64);
|
||||
self.decode_count_bit += 1;
|
||||
}
|
||||
self.step = DecoderStep::SaveDuration;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
|
||||
// Check if we have enough bits
|
||||
if self.decode_count_bit >= MIN_COUNT_BIT {
|
||||
let result = Self::parse_data(self.decode_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);
|
||||
|
||||
// Build data packet
|
||||
let mut data: u64 = 0;
|
||||
data |= 0x5 << 60; // Prefix
|
||||
data |= ((serial as u64) & 0x0FFFFFFF) << 32;
|
||||
data |= ((button as u64) & 0x0F) << 28;
|
||||
data |= ((counter as u64) & 0x0FFF) << 16;
|
||||
data |= ((decoded.data >> 8) & 0xFF) << 8; // Keep encrypted byte
|
||||
data |= Self::calculate_crc(data) as u64;
|
||||
|
||||
let mut signal = Vec::with_capacity(256);
|
||||
|
||||
// Preamble
|
||||
for _ in 0..30 {
|
||||
signal.push(LevelDuration::new(true, TE_SHORT));
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
}
|
||||
|
||||
// Sync
|
||||
signal.push(LevelDuration::new(true, TE_LONG));
|
||||
signal.push(LevelDuration::new(false, TE_LONG));
|
||||
|
||||
// Data: Manchester encoded, 64 bits MSB first
|
||||
for bit_num in (0..64).rev() {
|
||||
let bit = (data >> bit_num) & 1 == 1;
|
||||
if bit {
|
||||
// Manchester 1: low-high
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
signal.push(LevelDuration::new(true, TE_SHORT));
|
||||
} else {
|
||||
// Manchester 0: high-low
|
||||
signal.push(LevelDuration::new(true, TE_SHORT));
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
}
|
||||
}
|
||||
|
||||
// End
|
||||
signal.push(LevelDuration::new(false, TE_LONG * 4));
|
||||
|
||||
Some(signal)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
//! KeeLoq common encryption/decryption routines
|
||||
//!
|
||||
//! Shared by Kia V3/V4, Star Line, and other KeeLoq-based protocols.
|
||||
//! Based on the NLF (Non-Linear Feedback) function with constant 0x3A5C742E.
|
||||
|
||||
/// The KeeLoq NLF constant
|
||||
const KEELOQ_NLF: u32 = 0x3A5C742E;
|
||||
|
||||
/// KeeLoq decrypt: 528 rounds of the KeeLoq cipher (decrypt direction)
|
||||
pub fn keeloq_decrypt(data: u32, key: u64) -> u32 {
|
||||
let mut block = data;
|
||||
let mut tkey = key;
|
||||
|
||||
for _ in 0..528 {
|
||||
let lutkey = ((block >> 0) & 1)
|
||||
| ((block >> 7) & 2)
|
||||
| ((block >> 17) & 4)
|
||||
| ((block >> 22) & 8)
|
||||
| ((block >> 26) & 16);
|
||||
let lsb = ((block >> 31)
|
||||
^ ((block >> 15) & 1)
|
||||
^ ((KEELOQ_NLF >> lutkey) & 1)
|
||||
^ (((tkey >> 15) & 1) as u32)) as u32;
|
||||
block = ((block & 0x7FFFFFFF) << 1) | lsb;
|
||||
tkey = ((tkey & 0x7FFFFFFFFFFFFFFF) << 1) | (tkey >> 63);
|
||||
}
|
||||
block
|
||||
}
|
||||
|
||||
/// KeeLoq encrypt: 528 rounds of the KeeLoq cipher (encrypt direction)
|
||||
pub fn keeloq_encrypt(data: u32, key: u64) -> u32 {
|
||||
let mut block = data;
|
||||
let mut tkey = key;
|
||||
|
||||
for _ in 0..528 {
|
||||
let lutkey = ((block >> 1) & 1)
|
||||
| ((block >> 8) & 2)
|
||||
| ((block >> 18) & 4)
|
||||
| ((block >> 23) & 8)
|
||||
| ((block >> 27) & 16);
|
||||
let msb = ((block >> 0)
|
||||
^ ((block >> 16) & 1)
|
||||
^ ((KEELOQ_NLF >> lutkey) & 1)
|
||||
^ (((tkey >> 0) & 1) as u32)) as u32;
|
||||
block = ((block >> 1) & 0x7FFFFFFF) | (msb << 31);
|
||||
tkey = ((tkey >> 1) & 0x7FFFFFFFFFFFFFFF) | ((tkey & 1) << 63);
|
||||
}
|
||||
block
|
||||
}
|
||||
|
||||
/// Normal learning key derivation
|
||||
/// Derives a 64-bit key from a 32-bit fix code and a 64-bit manufacturer key
|
||||
pub fn keeloq_normal_learning(fix: u32, manufacturer_key: u64) -> u64 {
|
||||
let serial_low = fix & 0xFFFF;
|
||||
let serial_high = (fix >> 16) & 0xFFFF;
|
||||
|
||||
let key_low = keeloq_decrypt(serial_low as u32 | 0x20000000, manufacturer_key);
|
||||
let key_high = keeloq_decrypt(serial_high as u32 | 0x60000000, manufacturer_key);
|
||||
|
||||
((key_high as u64) << 32) | (key_low as u64)
|
||||
}
|
||||
|
||||
/// Reverse the bits in a 64-bit key (for protocols that store data MSB-first)
|
||||
pub fn reverse_key(key: u64, bit_count: usize) -> u64 {
|
||||
let mut result: u64 = 0;
|
||||
for i in 0..bit_count {
|
||||
if (key >> i) & 1 == 1 {
|
||||
result |= 1 << (bit_count - 1 - i);
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Reverse bits in a byte
|
||||
#[allow(dead_code)]
|
||||
pub fn reverse8(byte: u8) -> u8 {
|
||||
let mut b = byte;
|
||||
b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
|
||||
b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
|
||||
b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
|
||||
b
|
||||
}
|
||||
|
||||
/// Secure learning key derivation
|
||||
/// Derives a 64-bit key from a serial, seed, and manufacturer key
|
||||
#[allow(dead_code)]
|
||||
pub fn keeloq_secure_learning(data: u32, seed: u32, key: u64) -> u64 {
|
||||
let serial = data & 0x0FFFFFFF;
|
||||
let k1 = keeloq_decrypt(serial, key);
|
||||
let k2 = keeloq_decrypt(seed, key);
|
||||
((k1 as u64) << 32) | (k2 as u64)
|
||||
}
|
||||
|
||||
/// FAAC SLH (Spa) learning key derivation
|
||||
/// Derives a 64-bit key from a seed and manufacturer key
|
||||
#[allow(dead_code)]
|
||||
pub fn keeloq_faac_learning(seed: u32, key: u64) -> u64 {
|
||||
let hs = (seed >> 16) as u16;
|
||||
let ending: u16 = 0x544D;
|
||||
let lsb = ((hs as u32) << 16) | (ending as u32);
|
||||
((keeloq_encrypt(seed, key) as u64) << 32) | (keeloq_encrypt(lsb, key) as u64)
|
||||
}
|
||||
|
||||
/// Magic XOR Type 1 learning key derivation
|
||||
#[allow(dead_code)]
|
||||
pub fn keeloq_magic_xor_type1_learning(data: u32, xor: u64) -> u64 {
|
||||
let serial = data & 0x0FFFFFFF;
|
||||
(((serial as u64) << 32) | (serial as u64)) ^ xor
|
||||
}
|
||||
|
||||
/// Magic Serial Type 1 learning key derivation
|
||||
#[allow(dead_code)]
|
||||
pub fn keeloq_magic_serial_type1_learning(data: u32, man: u64) -> u64 {
|
||||
(man & 0xFFFFFFFF)
|
||||
| ((data as u64) << 40)
|
||||
| (((((data & 0xFF).wrapping_add((data >> 8) & 0xFF)) & 0xFF) as u64) << 32)
|
||||
}
|
||||
|
||||
/// Magic Serial Type 2 learning key derivation
|
||||
#[allow(dead_code)]
|
||||
pub fn keeloq_magic_serial_type2_learning(data: u32, man: u64) -> u64 {
|
||||
let p = data.to_le_bytes();
|
||||
let mut m = man.to_le_bytes();
|
||||
m[7] = p[0];
|
||||
m[6] = p[1];
|
||||
m[5] = p[2];
|
||||
m[4] = p[3];
|
||||
u64::from_le_bytes(m)
|
||||
}
|
||||
|
||||
/// Magic Serial Type 3 learning key derivation
|
||||
#[allow(dead_code)]
|
||||
pub fn keeloq_magic_serial_type3_learning(data: u32, man: u64) -> u64 {
|
||||
(man & 0xFFFFFFFFFF000000) | ((data & 0xFFFFFF) as u64)
|
||||
}
|
||||
|
||||
/// KeeLoq learning type constants
|
||||
#[allow(dead_code)]
|
||||
pub mod learning_types {
|
||||
pub const UNKNOWN: u32 = 0;
|
||||
pub const SIMPLE: u32 = 1;
|
||||
pub const NORMAL: u32 = 2;
|
||||
// pub const SECURE: u32 = 3;
|
||||
pub const MAGIC_XOR_TYPE_1: u32 = 4;
|
||||
// pub const FAAC: u32 = 5;
|
||||
pub const MAGIC_SERIAL_TYPE_1: u32 = 6;
|
||||
pub const MAGIC_SERIAL_TYPE_2: u32 = 7;
|
||||
pub const MAGIC_SERIAL_TYPE_3: u32 = 8;
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
//! Key management module for protocol encryption/decryption
|
||||
//!
|
||||
//! Ported from protopirate's keys.c
|
||||
//!
|
||||
//! Manages manufacturer keys used by various protocols:
|
||||
//! - KIA V3/V4: kia_mf_key (manufacturer key for KeeLoq)
|
||||
//! - KIA V5: kia_v5_key (custom mixer cipher key)
|
||||
//! - KIA V6: kia_v6_a_key, kia_v6_b_key (AES-128 XOR mask keys)
|
||||
//! - VAG: AUT64 keys loaded from keystore files
|
||||
|
||||
use super::aut64::{self, Aut64Key, AUT64_KEY_STRUCT_PACKED_SIZE};
|
||||
use std::path::Path;
|
||||
use std::sync::{OnceLock, RwLock};
|
||||
use tracing::{info, warn, error};
|
||||
|
||||
/// Key type identifiers (matches protopirate's keystore types)
|
||||
const KIA_KEY1: u32 = 10; // kia_mf_key
|
||||
const KIA_KEY2: u32 = 11; // kia_v6_a_key
|
||||
const KIA_KEY3: u32 = 12; // kia_v6_b_key
|
||||
const KIA_KEY4: u32 = 13; // kia_v5_key
|
||||
|
||||
/// Maximum number of VAG AUT64 keys
|
||||
const VAG_KEYS_COUNT: usize = 3;
|
||||
|
||||
/// Global key store - thread-safe access to loaded keys
|
||||
pub struct KeyStore {
|
||||
/// KIA manufacturer key (for KeeLoq-based V3/V4)
|
||||
pub kia_mf_key: u64,
|
||||
/// KIA V6 AES key A
|
||||
pub kia_v6_a_key: u64,
|
||||
/// KIA V6 AES key B
|
||||
pub kia_v6_b_key: u64,
|
||||
/// KIA V5 mixer key
|
||||
pub kia_v5_key: u64,
|
||||
/// VAG AUT64 keys
|
||||
pub vag_keys: Vec<Aut64Key>,
|
||||
/// Whether VAG keys have been loaded
|
||||
pub vag_keys_loaded: bool,
|
||||
}
|
||||
|
||||
impl Default for KeyStore {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
kia_mf_key: 0,
|
||||
kia_v6_a_key: 0,
|
||||
kia_v6_b_key: 0,
|
||||
kia_v5_key: 0,
|
||||
vag_keys: Vec::new(),
|
||||
vag_keys_loaded: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyStore {
|
||||
/// Create a new empty key store
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Load KIA keys from a key entries list
|
||||
/// Each entry is (type_id, key_value)
|
||||
pub fn load_kia_keys(&mut self, entries: &[(u32, u64)]) {
|
||||
for &(key_type, key_value) in entries {
|
||||
match key_type {
|
||||
KIA_KEY1 => self.kia_mf_key = key_value,
|
||||
KIA_KEY2 => self.kia_v6_a_key = key_value,
|
||||
KIA_KEY3 => self.kia_v6_b_key = key_value,
|
||||
KIA_KEY4 => self.kia_v5_key = key_value,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Load VAG AUT64 keys from raw binary data
|
||||
/// The data should contain packed AUT64 key structures (16 bytes each)
|
||||
pub fn load_vag_keys_from_data(&mut self, data: &[u8]) {
|
||||
if self.vag_keys_loaded {
|
||||
return;
|
||||
}
|
||||
|
||||
self.vag_keys.clear();
|
||||
|
||||
for i in 0..VAG_KEYS_COUNT {
|
||||
let offset = i * AUT64_KEY_STRUCT_PACKED_SIZE;
|
||||
if offset + AUT64_KEY_STRUCT_PACKED_SIZE > data.len() {
|
||||
error!("VAG key data too short for key {}", i);
|
||||
break;
|
||||
}
|
||||
|
||||
let key = aut64::aut64_unpack(&data[offset..offset + AUT64_KEY_STRUCT_PACKED_SIZE]);
|
||||
self.vag_keys.push(key);
|
||||
}
|
||||
|
||||
self.vag_keys_loaded = true;
|
||||
info!("Loaded {} VAG keys", self.vag_keys.len());
|
||||
}
|
||||
|
||||
/// Load VAG AUT64 keys from a file path
|
||||
pub fn load_vag_keys_from_file(&mut self, path: &str) {
|
||||
if self.vag_keys_loaded {
|
||||
return;
|
||||
}
|
||||
|
||||
let file_path = Path::new(path);
|
||||
if !file_path.exists() {
|
||||
warn!("VAG key file not found: {}", path);
|
||||
return;
|
||||
}
|
||||
|
||||
match std::fs::read(file_path) {
|
||||
Ok(data) => {
|
||||
self.load_vag_keys_from_data(&data);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to read VAG key file {}: {}", path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a VAG AUT64 key by its internal index field
|
||||
pub fn get_vag_key(&self, index: u8) -> Option<&Aut64Key> {
|
||||
self.vag_keys.iter().find(|k| k.index == index)
|
||||
}
|
||||
|
||||
/// Get a VAG AUT64 key by array position (0-based)
|
||||
pub fn get_vag_key_by_position(&self, position: usize) -> Option<&Aut64Key> {
|
||||
self.vag_keys.get(position)
|
||||
}
|
||||
|
||||
/// Get the KIA manufacturer key
|
||||
pub fn get_kia_mf_key(&self) -> u64 {
|
||||
self.kia_mf_key
|
||||
}
|
||||
|
||||
/// Get the KIA V6 AES key A
|
||||
pub fn get_kia_v6_keystore_a(&self) -> u64 {
|
||||
self.kia_v6_a_key
|
||||
}
|
||||
|
||||
/// Get the KIA V6 AES key B
|
||||
pub fn get_kia_v6_keystore_b(&self) -> u64 {
|
||||
self.kia_v6_b_key
|
||||
}
|
||||
|
||||
/// Get the KIA V5 mixer key
|
||||
pub fn get_kia_v5_key(&self) -> u64 {
|
||||
self.kia_v5_key
|
||||
}
|
||||
}
|
||||
|
||||
/// Global singleton keystore
|
||||
fn global_keystore() -> &'static RwLock<KeyStore> {
|
||||
static GLOBAL_KEYSTORE: OnceLock<RwLock<KeyStore>> = OnceLock::new();
|
||||
GLOBAL_KEYSTORE.get_or_init(|| RwLock::new(KeyStore::new()))
|
||||
}
|
||||
|
||||
/// Get a read reference to the global keystore
|
||||
pub fn get_keystore() -> std::sync::RwLockReadGuard<'static, KeyStore> {
|
||||
global_keystore().read().unwrap()
|
||||
}
|
||||
|
||||
/// Get a write reference to the global keystore
|
||||
pub fn get_keystore_mut() -> std::sync::RwLockWriteGuard<'static, KeyStore> {
|
||||
global_keystore().write().unwrap()
|
||||
}
|
||||
|
||||
/// Initialize the global keystore with KIA keys
|
||||
pub fn load_keys(kia_entries: &[(u32, u64)]) {
|
||||
let mut store = get_keystore_mut();
|
||||
store.load_kia_keys(kia_entries);
|
||||
}
|
||||
|
||||
/// Initialize VAG keys from file
|
||||
pub fn load_vag_keys(path: &str) {
|
||||
let mut store = get_keystore_mut();
|
||||
store.load_vag_keys_from_file(path);
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
//! Kia V0 protocol decoder
|
||||
//!
|
||||
//! Ported from protopirate's kia_v0.c
|
||||
//!
|
||||
//! Protocol characteristics:
|
||||
//! - PWM encoding: short pulse (250µs) = 0, long pulse (500µs) = 1
|
||||
//! - 61 bits total
|
||||
//! - Preamble: alternating short pulses
|
||||
//! - Sync: long-long pattern
|
||||
//! - Data: 59 bits (4-bit prefix + 16-bit counter + 28-bit serial + 4-bit button + 8-bit CRC)
|
||||
|
||||
use super::{ProtocolDecoder, ProtocolTiming, DecodedSignal};
|
||||
use super::common::{crc8_kia, add_bit};
|
||||
use crate::radio::demodulator::LevelDuration;
|
||||
use crate::duration_diff;
|
||||
|
||||
const TE_SHORT: u32 = 250;
|
||||
const TE_LONG: u32 = 500;
|
||||
const TE_DELTA: u32 = 100;
|
||||
const MIN_COUNT_BIT: usize = 61;
|
||||
|
||||
/// Decoder states
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum DecoderStep {
|
||||
Reset,
|
||||
CheckPreamble,
|
||||
SaveDuration,
|
||||
CheckDuration,
|
||||
}
|
||||
|
||||
/// Kia V0 protocol decoder
|
||||
pub struct KiaV0Decoder {
|
||||
step: DecoderStep,
|
||||
te_last: u32,
|
||||
header_count: u16,
|
||||
decode_data: u64,
|
||||
decode_count_bit: usize,
|
||||
}
|
||||
|
||||
impl KiaV0Decoder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
step: DecoderStep::Reset,
|
||||
te_last: 0,
|
||||
header_count: 0,
|
||||
decode_data: 0,
|
||||
decode_count_bit: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate CRC for Kia data packet
|
||||
fn calculate_crc(data: u64) -> u8 {
|
||||
let crc_data = [
|
||||
((data >> 48) & 0xFF) as u8,
|
||||
((data >> 40) & 0xFF) as u8,
|
||||
((data >> 32) & 0xFF) as u8,
|
||||
((data >> 24) & 0xFF) as u8,
|
||||
((data >> 16) & 0xFF) as u8,
|
||||
((data >> 8) & 0xFF) as u8,
|
||||
];
|
||||
crc8_kia(&crc_data)
|
||||
}
|
||||
|
||||
/// Verify CRC of received data
|
||||
fn verify_crc(data: u64) -> bool {
|
||||
let received_crc = (data & 0xFF) as u8;
|
||||
let calculated_crc = Self::calculate_crc(data);
|
||||
received_crc == calculated_crc
|
||||
}
|
||||
|
||||
/// Extract fields from decoded data
|
||||
fn parse_data(data: u64) -> DecodedSignal {
|
||||
let serial = ((data >> 12) & 0x0FFFFFFF) as u32;
|
||||
let button = ((data >> 8) & 0x0F) as u8;
|
||||
let counter = ((data >> 40) & 0xFFFF) as u16;
|
||||
let crc_valid = Self::verify_crc(data);
|
||||
|
||||
DecodedSignal {
|
||||
serial: Some(serial),
|
||||
button: Some(button),
|
||||
counter: Some(counter),
|
||||
crc_valid,
|
||||
data,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolDecoder for KiaV0Decoder {
|
||||
fn name(&self) -> &'static str {
|
||||
"Kia V0"
|
||||
}
|
||||
|
||||
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] {
|
||||
&[433_920_000] // 433.92 MHz
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
fn feed(&mut self, level: bool, duration: u32) -> Option<DecodedSignal> {
|
||||
match self.step {
|
||||
DecoderStep::Reset => {
|
||||
if level && duration_diff!(duration, TE_SHORT) < TE_DELTA {
|
||||
self.step = DecoderStep::CheckPreamble;
|
||||
self.te_last = duration;
|
||||
self.header_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::CheckPreamble => {
|
||||
if level {
|
||||
if duration_diff!(duration, TE_SHORT) < TE_DELTA ||
|
||||
duration_diff!(duration, TE_LONG) < TE_DELTA {
|
||||
self.te_last = duration;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
} else if duration_diff!(duration, TE_SHORT) < TE_DELTA &&
|
||||
duration_diff!(self.te_last, TE_SHORT) < TE_DELTA {
|
||||
// Short-short pair in preamble
|
||||
self.header_count += 1;
|
||||
} else if duration_diff!(duration, TE_LONG) < TE_DELTA &&
|
||||
duration_diff!(self.te_last, TE_LONG) < TE_DELTA {
|
||||
// Long-long sync pattern
|
||||
if self.header_count > 15 {
|
||||
self.step = DecoderStep::SaveDuration;
|
||||
self.decode_data = 0;
|
||||
self.decode_count_bit = 1;
|
||||
// Add first bit (the sync is also a '1' bit)
|
||||
add_bit(&mut self.decode_data, &mut self.decode_count_bit, true);
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::SaveDuration => {
|
||||
if level {
|
||||
if duration >= TE_LONG + TE_DELTA * 2 {
|
||||
// End of transmission
|
||||
self.step = DecoderStep::Reset;
|
||||
|
||||
if self.decode_count_bit == MIN_COUNT_BIT {
|
||||
return Some(Self::parse_data(self.decode_data));
|
||||
}
|
||||
} else {
|
||||
self.te_last = duration;
|
||||
self.step = DecoderStep::CheckDuration;
|
||||
}
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::CheckDuration => {
|
||||
if !level {
|
||||
if duration_diff!(self.te_last, TE_SHORT) < TE_DELTA &&
|
||||
duration_diff!(duration, TE_SHORT) < TE_DELTA {
|
||||
// Short-short = bit 0
|
||||
add_bit(&mut self.decode_data, &mut self.decode_count_bit, false);
|
||||
self.step = DecoderStep::SaveDuration;
|
||||
} else if duration_diff!(self.te_last, TE_LONG) < TE_DELTA &&
|
||||
duration_diff!(duration, TE_LONG) < TE_DELTA {
|
||||
// Long-long = bit 1
|
||||
add_bit(&mut self.decode_data, &mut self.decode_count_bit, true);
|
||||
self.step = DecoderStep::SaveDuration;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// Build data packet
|
||||
let mut data: u64 = 0;
|
||||
|
||||
// Bits 56-59: Preserve from original (usually 0xF)
|
||||
data |= decoded.data & 0x0F00000000000000;
|
||||
|
||||
// Bits 40-55: Counter (16 bits)
|
||||
data |= ((counter as u64) & 0xFFFF) << 40;
|
||||
|
||||
// Bits 12-39: Serial (28 bits)
|
||||
data |= ((serial as u64) & 0x0FFFFFFF) << 12;
|
||||
|
||||
// Bits 8-11: Button (4 bits)
|
||||
data |= ((button as u64) & 0x0F) << 8;
|
||||
|
||||
// Bits 0-7: CRC
|
||||
let crc = Self::calculate_crc(data);
|
||||
data |= crc as u64;
|
||||
|
||||
let mut signal = Vec::with_capacity(256);
|
||||
|
||||
// Generate 2 bursts
|
||||
for burst in 0..2 {
|
||||
if burst > 0 {
|
||||
// Inter-burst gap
|
||||
signal.push(LevelDuration::new(false, 25000));
|
||||
}
|
||||
|
||||
// Preamble: 32 alternating short pulses
|
||||
for i in 0..32 {
|
||||
let is_high = (i % 2) == 0;
|
||||
signal.push(LevelDuration::new(is_high, TE_SHORT));
|
||||
}
|
||||
|
||||
// Sync: long-long
|
||||
signal.push(LevelDuration::new(true, TE_LONG));
|
||||
signal.push(LevelDuration::new(false, TE_LONG));
|
||||
|
||||
// Data: 59 bits (MSB first)
|
||||
for bit_num in 0..59 {
|
||||
let bit_mask = 1u64 << (58 - bit_num);
|
||||
let bit = (data & bit_mask) != 0;
|
||||
let duration = if bit { TE_LONG } else { TE_SHORT };
|
||||
|
||||
signal.push(LevelDuration::new(true, duration));
|
||||
signal.push(LevelDuration::new(false, duration));
|
||||
}
|
||||
|
||||
// End marker
|
||||
signal.push(LevelDuration::new(true, TE_LONG * 2));
|
||||
}
|
||||
|
||||
Some(signal)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
//! Kia V1 protocol decoder
|
||||
//!
|
||||
//! Ported from protopirate's kia_v1.c
|
||||
//!
|
||||
//! Protocol characteristics:
|
||||
//! - Manchester encoding: 800/1600µs timing
|
||||
//! - 57 bits total
|
||||
//! - Long preamble of ~90 pulses
|
||||
//! - CRC4 checksum
|
||||
|
||||
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 states
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum ManchesterState {
|
||||
Mid0,
|
||||
Mid1,
|
||||
Start0,
|
||||
Start1,
|
||||
}
|
||||
|
||||
/// Decoder states
|
||||
#[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 calculation for Kia V1
|
||||
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
|
||||
fn manchester_advance(&mut self, is_short: bool, is_high: bool) -> Option<bool> {
|
||||
let event = match (is_short, is_high) {
|
||||
(true, false) => 0, // Short Low
|
||||
(true, true) => 1, // Short High
|
||||
(false, false) => 2, // Long Low
|
||||
(false, true) => 3, // Long High
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
// Extract fields per kia_v1.c
|
||||
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;
|
||||
|
||||
let crc = if cnt_high == 0 {
|
||||
let offset = if counter >= 0x098 { button } else { 1 };
|
||||
Self::crc4(&char_data[..6], offset)
|
||||
} else if cnt_high >= 0x6 {
|
||||
char_data[6] = cnt_high as u8;
|
||||
Self::crc4(&char_data, 1)
|
||||
} else {
|
||||
Self::crc4(&char_data[..6], 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
self.decode_count_bit = 1;
|
||||
self.decode_data = 1; // Add first bit
|
||||
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;
|
||||
|
||||
let crc = if cnt_high == 0 {
|
||||
let offset = if counter >= 0x098 { button } else { 1 };
|
||||
Self::crc4(&char_data[..6], offset)
|
||||
} else if cnt_high >= 0x6 {
|
||||
char_data[6] = cnt_high;
|
||||
Self::crc4(&char_data, 1)
|
||||
} else {
|
||||
Self::crc4(&char_data[..6], 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
|
||||
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
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
|
||||
// Data: 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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
//! Kia V2 protocol decoder
|
||||
//!
|
||||
//! Ported from protopirate's kia_v2.c
|
||||
//!
|
||||
//! Protocol characteristics:
|
||||
//! - Manchester encoding: 500/1000µs timing
|
||||
//! - 53 bits total
|
||||
//! - Long preamble of 252+ pairs
|
||||
//! - CRC4 checksum
|
||||
|
||||
use super::{ProtocolDecoder, ProtocolTiming, DecodedSignal};
|
||||
use crate::radio::demodulator::LevelDuration;
|
||||
use crate::duration_diff;
|
||||
|
||||
const TE_SHORT: u32 = 500;
|
||||
const TE_LONG: u32 = 1000;
|
||||
const TE_DELTA: u32 = 150;
|
||||
const MIN_COUNT_BIT: usize = 53;
|
||||
|
||||
/// Manchester states
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum ManchesterState {
|
||||
Mid0,
|
||||
Mid1,
|
||||
Start0,
|
||||
Start1,
|
||||
}
|
||||
|
||||
/// Decoder states
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum DecoderStep {
|
||||
Reset,
|
||||
CheckPreamble,
|
||||
CollectRawBits,
|
||||
}
|
||||
|
||||
/// Kia V2 protocol decoder
|
||||
pub struct KiaV2Decoder {
|
||||
step: DecoderStep,
|
||||
te_last: u32,
|
||||
header_count: u16,
|
||||
decode_data: u64,
|
||||
decode_count_bit: usize,
|
||||
manchester_state: ManchesterState,
|
||||
}
|
||||
|
||||
impl KiaV2Decoder {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate CRC for Kia V2
|
||||
fn calculate_crc(data: u64) -> u8 {
|
||||
let serial = ((data >> 20) & 0xFFFFFFFF) as u32;
|
||||
let u_var4 = (data & 0xFFFFFFFF) as u32;
|
||||
|
||||
let mut bytes = [0u8; 6];
|
||||
bytes[0] = (u_var4 >> 20) as u8;
|
||||
bytes[1] = ((u_var4 >> 28) | ((serial & 0x0F) << 4)) as u8;
|
||||
bytes[2] = (serial >> 4) as u8;
|
||||
bytes[3] = (serial >> 12) as u8;
|
||||
bytes[4] = (u_var4 >> 4) as u8;
|
||||
bytes[5] = (u_var4 >> 12) as u8;
|
||||
|
||||
let mut crc: u8 = 0;
|
||||
for &byte in &bytes {
|
||||
crc ^= (byte & 0x0F) ^ (byte >> 4);
|
||||
}
|
||||
|
||||
(crc.wrapping_add(1)) & 0x0F
|
||||
}
|
||||
|
||||
/// Manchester state machine
|
||||
fn manchester_advance(&mut self, is_short: bool, is_high: bool) -> Option<bool> {
|
||||
let event = match (is_short, is_high) {
|
||||
(true, false) => 0, // Short Low
|
||||
(true, true) => 1, // Short High
|
||||
(false, false) => 2, // Long Low
|
||||
(false, true) => 3, // Long High
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
let serial = ((data >> 20) & 0xFFFFFFFF) as u32;
|
||||
let button = ((data >> 16) & 0x0F) as u8;
|
||||
|
||||
// Counter has byte-swapped format
|
||||
let raw_count = ((data >> 4) & 0xFFF) as u16;
|
||||
let counter = ((raw_count >> 4) | (raw_count << 8)) & 0xFFF;
|
||||
|
||||
let received_crc = (data & 0x0F) as u8;
|
||||
let calculated_crc = Self::calculate_crc(data);
|
||||
|
||||
DecodedSignal {
|
||||
serial: Some(serial),
|
||||
button: Some(button),
|
||||
counter: Some(counter),
|
||||
crc_valid: received_crc == calculated_crc,
|
||||
data,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolDecoder for KiaV2Decoder {
|
||||
fn name(&self) -> &'static str {
|
||||
"Kia V2"
|
||||
}
|
||||
|
||||
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.manchester_state = ManchesterState::Mid1;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::CheckPreamble => {
|
||||
if level {
|
||||
if is_long {
|
||||
self.te_last = duration;
|
||||
self.header_count += 1;
|
||||
} else if is_short && self.header_count >= 100 {
|
||||
self.header_count = 0;
|
||||
self.decode_data = 0;
|
||||
self.decode_count_bit = 1;
|
||||
self.step = DecoderStep::CollectRawBits;
|
||||
self.decode_data = 1; // First bit
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
} else {
|
||||
if is_long {
|
||||
self.header_count += 1;
|
||||
self.te_last = duration;
|
||||
} else if !is_short {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::CollectRawBits => {
|
||||
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);
|
||||
|
||||
// Reconstruct data in V2 format
|
||||
let u_var6 = ((counter & 0xFF) as u32) << 8 |
|
||||
((button & 0x0F) as u32) << 16 |
|
||||
(((counter >> 4) & 0xF0) as u32);
|
||||
|
||||
let mut new_data: u64 = 1u64 << 52; // Start bit
|
||||
new_data |= ((serial as u64) << 20) & 0xFFFFFFFFF00000;
|
||||
new_data |= u_var6 as u64;
|
||||
|
||||
// Calculate and apply CRC
|
||||
let crc = Self::calculate_crc(new_data);
|
||||
new_data = (new_data & !0x0F) | (crc as u64);
|
||||
|
||||
let mut signal = Vec::with_capacity(700);
|
||||
|
||||
// Generate 2 bursts
|
||||
for _burst in 0..2 {
|
||||
// Preamble: 252 long pairs
|
||||
for _ in 0..252 {
|
||||
signal.push(LevelDuration::new(false, TE_LONG));
|
||||
signal.push(LevelDuration::new(true, TE_LONG));
|
||||
}
|
||||
|
||||
// Short gap
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
|
||||
// Data: Manchester encoded, MSB first
|
||||
for bit_num in (1..MIN_COUNT_BIT).rev() {
|
||||
let bit = ((new_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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
//! Kia V3/V4 protocol decoder
|
||||
//!
|
||||
//! Ported from protopirate's kia_v3_v4.c
|
||||
//!
|
||||
//! Protocol characteristics:
|
||||
//! - PWM encoding: 400/800µs timing
|
||||
//! - 68 bits total
|
||||
//! - Short preamble of 16 pairs
|
||||
//! - KeeLoq encryption (requires manufacturer key)
|
||||
//! - V3 and V4 differ only in sync polarity
|
||||
|
||||
use super::{ProtocolDecoder, ProtocolTiming, DecodedSignal};
|
||||
use crate::radio::demodulator::LevelDuration;
|
||||
use crate::duration_diff;
|
||||
|
||||
const TE_SHORT: u32 = 400;
|
||||
const TE_LONG: u32 = 800;
|
||||
const TE_DELTA: u32 = 150;
|
||||
const MIN_COUNT_BIT: usize = 68;
|
||||
const SYNC_DURATION: u32 = 1200;
|
||||
const INTER_BURST_GAP_US: u32 = 10000;
|
||||
const PREAMBLE_PAIRS: usize = 16;
|
||||
const TOTAL_BURSTS: usize = 3;
|
||||
|
||||
/// Decoder states
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum DecoderStep {
|
||||
Reset,
|
||||
CheckPreamble,
|
||||
CollectRawBits,
|
||||
}
|
||||
|
||||
/// Kia V3/V4 protocol decoder
|
||||
pub struct KiaV3V4Decoder {
|
||||
step: DecoderStep,
|
||||
te_last: u32,
|
||||
header_count: u16,
|
||||
raw_bits: [u8; 32],
|
||||
raw_bit_count: u16,
|
||||
is_v3_sync: bool,
|
||||
}
|
||||
|
||||
impl KiaV3V4Decoder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
step: DecoderStep::Reset,
|
||||
te_last: 0,
|
||||
header_count: 0,
|
||||
raw_bits: [0; 32],
|
||||
raw_bit_count: 0,
|
||||
is_v3_sync: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Reverse bits in a byte
|
||||
fn reverse8(byte: u8) -> u8 {
|
||||
let mut byte = byte;
|
||||
byte = (byte & 0xF0) >> 4 | (byte & 0x0F) << 4;
|
||||
byte = (byte & 0xCC) >> 2 | (byte & 0x33) << 2;
|
||||
byte = (byte & 0xAA) >> 1 | (byte & 0x55) << 1;
|
||||
byte
|
||||
}
|
||||
|
||||
/// Add a raw bit to the buffer
|
||||
fn add_raw_bit(&mut self, bit: bool) {
|
||||
if self.raw_bit_count < 256 {
|
||||
let byte_idx = (self.raw_bit_count / 8) as usize;
|
||||
let bit_idx = 7 - (self.raw_bit_count % 8);
|
||||
if bit {
|
||||
self.raw_bits[byte_idx] |= 1 << bit_idx;
|
||||
} else {
|
||||
self.raw_bits[byte_idx] &= !(1 << bit_idx);
|
||||
}
|
||||
self.raw_bit_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// CRC4 calculation
|
||||
fn calculate_crc(bytes: &[u8]) -> u8 {
|
||||
let mut crc: u8 = 0;
|
||||
for &byte in bytes.iter().take(8) {
|
||||
crc ^= (byte & 0x0F) ^ (byte >> 4);
|
||||
}
|
||||
crc & 0x0F
|
||||
}
|
||||
|
||||
/// KeeLoq decrypt
|
||||
fn keeloq_decrypt(data: u32, key: u64) -> u32 {
|
||||
let mut block = data;
|
||||
let mut tkey = key;
|
||||
|
||||
for _ in 0..528 {
|
||||
let lutkey = ((block >> 0) & 1) |
|
||||
((block >> 7) & 2) |
|
||||
((block >> 17) & 4) |
|
||||
((block >> 22) & 8) |
|
||||
((block >> 26) & 16);
|
||||
let lsb = ((block >> 31) ^
|
||||
((block >> 15) & 1) ^
|
||||
((0x3A5C742E_u32 >> lutkey) & 1) ^
|
||||
(((tkey >> 15) & 1) as u32)) as u32;
|
||||
block = ((block & 0x7FFFFFFF) << 1) | lsb;
|
||||
tkey = ((tkey & 0x7FFFFFFFFFFFFFFF) << 1) | (tkey >> 63);
|
||||
}
|
||||
block
|
||||
}
|
||||
|
||||
/// KeeLoq encrypt
|
||||
fn keeloq_encrypt(data: u32, key: u64) -> u32 {
|
||||
let mut block = data;
|
||||
let mut tkey = key;
|
||||
|
||||
for _ in 0..528 {
|
||||
let lutkey = ((block >> 1) & 1) |
|
||||
((block >> 8) & 2) |
|
||||
((block >> 18) & 4) |
|
||||
((block >> 23) & 8) |
|
||||
((block >> 27) & 16);
|
||||
let msb = ((block >> 0) ^
|
||||
((block >> 16) & 1) ^
|
||||
((0x3A5C742E_u32 >> lutkey) & 1) ^
|
||||
(((tkey >> 0) & 1) as u32)) as u32;
|
||||
block = ((block >> 1) & 0x7FFFFFFF) | (msb << 31);
|
||||
tkey = ((tkey >> 1) & 0x7FFFFFFFFFFFFFFF) | ((tkey & 1) << 63);
|
||||
}
|
||||
block
|
||||
}
|
||||
|
||||
/// Get manufacturer key (placeholder - in real use, this would be loaded from config)
|
||||
fn get_mf_key() -> u64 {
|
||||
// This is a placeholder - actual key should be loaded from secure storage
|
||||
0x0000000000000000
|
||||
}
|
||||
|
||||
/// Process the collected buffer and validate
|
||||
fn process_buffer(&self) -> Option<DecodedSignal> {
|
||||
if self.raw_bit_count < 68 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut b = self.raw_bits;
|
||||
|
||||
// V3 sync means data is inverted
|
||||
if self.is_v3_sync {
|
||||
let num_bytes = ((self.raw_bit_count + 7) / 8) as usize;
|
||||
for i in 0..num_bytes {
|
||||
b[i] = !b[i];
|
||||
}
|
||||
}
|
||||
|
||||
let _crc = (b[8] >> 4) & 0x0F;
|
||||
|
||||
let encrypted = ((Self::reverse8(b[3]) as u32) << 24) |
|
||||
((Self::reverse8(b[2]) as u32) << 16) |
|
||||
((Self::reverse8(b[1]) as u32) << 8) |
|
||||
(Self::reverse8(b[0]) as u32);
|
||||
|
||||
let serial = ((Self::reverse8(b[7] & 0xF0) as u32) << 24) |
|
||||
((Self::reverse8(b[6]) as u32) << 16) |
|
||||
((Self::reverse8(b[5]) as u32) << 8) |
|
||||
(Self::reverse8(b[4]) as u32);
|
||||
|
||||
let button = (Self::reverse8(b[7]) & 0xF0) >> 4;
|
||||
let our_serial_lsb = (serial & 0xFF) as u8;
|
||||
|
||||
let mf_key = Self::get_mf_key();
|
||||
let decrypted = Self::keeloq_decrypt(encrypted, mf_key);
|
||||
let dec_btn = ((decrypted >> 28) & 0x0F) as u8;
|
||||
let dec_serial_lsb = ((decrypted >> 16) & 0xFF) as u8;
|
||||
|
||||
// Validate decryption (may fail if key is wrong)
|
||||
let crc_valid = if mf_key != 0 {
|
||||
dec_btn == button && dec_serial_lsb == our_serial_lsb
|
||||
} else {
|
||||
// Can't validate without key
|
||||
true
|
||||
};
|
||||
|
||||
let counter = (decrypted & 0xFFFF) as u16;
|
||||
|
||||
// Build key data
|
||||
let key_data = ((b[0] as u64) << 56) |
|
||||
((b[1] as u64) << 48) |
|
||||
((b[2] as u64) << 40) |
|
||||
((b[3] as u64) << 32) |
|
||||
((b[4] as u64) << 24) |
|
||||
((b[5] as u64) << 16) |
|
||||
((b[6] as u64) << 8) |
|
||||
(b[7] as u64);
|
||||
|
||||
Some(DecodedSignal {
|
||||
serial: Some(serial),
|
||||
button: Some(button),
|
||||
counter: Some(counter),
|
||||
crc_valid,
|
||||
data: key_data,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolDecoder for KiaV3V4Decoder {
|
||||
fn name(&self) -> &'static str {
|
||||
"Kia V3/V4"
|
||||
}
|
||||
|
||||
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.raw_bits = [0; 32];
|
||||
self.raw_bit_count = 0;
|
||||
self.is_v3_sync = false;
|
||||
}
|
||||
|
||||
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;
|
||||
let is_sync = duration > 1000 && duration < 1500;
|
||||
let is_very_long = duration > 1500;
|
||||
|
||||
match self.step {
|
||||
DecoderStep::Reset => {
|
||||
if level && is_short {
|
||||
self.step = DecoderStep::CheckPreamble;
|
||||
self.te_last = duration;
|
||||
self.header_count = 1;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::CheckPreamble => {
|
||||
if level {
|
||||
if is_short {
|
||||
self.te_last = duration;
|
||||
} else if is_sync && self.header_count >= 8 {
|
||||
// V4 sync: long HIGH
|
||||
self.step = DecoderStep::CollectRawBits;
|
||||
self.raw_bit_count = 0;
|
||||
self.is_v3_sync = false;
|
||||
self.raw_bits = [0; 32];
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
} else {
|
||||
if is_sync && self.header_count >= 8 {
|
||||
// V3 sync: long LOW
|
||||
self.step = DecoderStep::CollectRawBits;
|
||||
self.raw_bit_count = 0;
|
||||
self.is_v3_sync = true;
|
||||
self.raw_bits = [0; 32];
|
||||
} else if is_short && duration_diff!(self.te_last, TE_SHORT) < TE_DELTA {
|
||||
self.header_count += 1;
|
||||
} else if is_very_long {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::CollectRawBits => {
|
||||
if level {
|
||||
if is_sync || is_very_long {
|
||||
// End of data
|
||||
let result = self.process_buffer();
|
||||
self.step = DecoderStep::Reset;
|
||||
return result;
|
||||
} else if is_short {
|
||||
self.add_raw_bit(false);
|
||||
} else if is_long {
|
||||
self.add_raw_bit(true);
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
} else {
|
||||
if is_sync || is_very_long {
|
||||
let result = self.process_buffer();
|
||||
self.step = DecoderStep::Reset;
|
||||
return result;
|
||||
}
|
||||
// LOW durations don't carry data in PWM
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// Build plaintext for encryption
|
||||
let plaintext = (counter as u32) |
|
||||
((serial & 0xFF) << 16) |
|
||||
(0x1 << 24) |
|
||||
(((button & 0x0F) as u32) << 28);
|
||||
|
||||
let mf_key = Self::get_mf_key();
|
||||
let encrypted = Self::keeloq_encrypt(plaintext, mf_key);
|
||||
|
||||
// Build raw bytes
|
||||
let mut raw_bytes = [0u8; 9];
|
||||
raw_bytes[0] = Self::reverse8((encrypted >> 0) as u8);
|
||||
raw_bytes[1] = Self::reverse8((encrypted >> 8) as u8);
|
||||
raw_bytes[2] = Self::reverse8((encrypted >> 16) as u8);
|
||||
raw_bytes[3] = Self::reverse8((encrypted >> 24) as u8);
|
||||
|
||||
let serial_btn = (serial & 0x0FFFFFFF) | (((button & 0x0F) as u32) << 28);
|
||||
raw_bytes[4] = Self::reverse8((serial_btn >> 0) as u8);
|
||||
raw_bytes[5] = Self::reverse8((serial_btn >> 8) as u8);
|
||||
raw_bytes[6] = Self::reverse8((serial_btn >> 16) as u8);
|
||||
raw_bytes[7] = Self::reverse8((serial_btn >> 24) as u8);
|
||||
|
||||
let crc = Self::calculate_crc(&raw_bytes);
|
||||
raw_bytes[8] = crc << 4;
|
||||
|
||||
// Use V4 encoding by default
|
||||
let version = 0;
|
||||
|
||||
if version == 1 {
|
||||
// V3: invert data
|
||||
for byte in raw_bytes.iter_mut() {
|
||||
*byte = !*byte;
|
||||
}
|
||||
}
|
||||
|
||||
let mut signal = Vec::with_capacity(600);
|
||||
|
||||
for burst in 0..TOTAL_BURSTS {
|
||||
if burst > 0 {
|
||||
signal.push(LevelDuration::new(false, INTER_BURST_GAP_US));
|
||||
}
|
||||
|
||||
// Preamble
|
||||
for _ in 0..PREAMBLE_PAIRS {
|
||||
signal.push(LevelDuration::new(true, TE_SHORT));
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
}
|
||||
|
||||
// Sync pulse
|
||||
if version == 0 {
|
||||
// V4: long HIGH, short LOW
|
||||
signal.push(LevelDuration::new(true, SYNC_DURATION));
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
} else {
|
||||
// V3: short HIGH, long LOW
|
||||
signal.push(LevelDuration::new(true, TE_SHORT));
|
||||
signal.push(LevelDuration::new(false, SYNC_DURATION));
|
||||
}
|
||||
|
||||
// Data bits
|
||||
for byte_idx in 0..9 {
|
||||
let bits_in_byte = if byte_idx == 8 { 4 } else { 8 };
|
||||
for bit_idx in (8 - bits_in_byte..8).rev() {
|
||||
let bit = (raw_bytes[byte_idx] >> bit_idx) & 1 != 0;
|
||||
if bit {
|
||||
signal.push(LevelDuration::new(true, TE_LONG));
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
} else {
|
||||
signal.push(LevelDuration::new(true, TE_SHORT));
|
||||
signal.push(LevelDuration::new(false, TE_LONG));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(signal)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
//! Kia V5 protocol decoder
|
||||
//!
|
||||
//! Ported from protopirate's kia_v5.c
|
||||
//!
|
||||
//! Protocol characteristics:
|
||||
//! - Manchester encoding: 400/800µs timing
|
||||
//! - 64 bits total (+3 bit CRC)
|
||||
//! - Preamble of ~40+ short pairs
|
||||
//! - Custom "mixer" encryption
|
||||
//! - Decode-only (no encoder)
|
||||
|
||||
use super::{ProtocolDecoder, ProtocolTiming, DecodedSignal};
|
||||
use crate::radio::demodulator::LevelDuration;
|
||||
use crate::duration_diff;
|
||||
|
||||
const TE_SHORT: u32 = 400;
|
||||
const TE_LONG: u32 = 800;
|
||||
const TE_DELTA: u32 = 150;
|
||||
const MIN_COUNT_BIT: usize = 64;
|
||||
|
||||
/// Manchester states
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum ManchesterState {
|
||||
Mid0,
|
||||
Mid1,
|
||||
Start0,
|
||||
Start1,
|
||||
}
|
||||
|
||||
/// Decoder states
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum DecoderStep {
|
||||
Reset,
|
||||
CheckPreamble,
|
||||
Data,
|
||||
}
|
||||
|
||||
/// Kia V5 protocol decoder
|
||||
pub struct KiaV5Decoder {
|
||||
step: DecoderStep,
|
||||
te_last: u32,
|
||||
header_count: u16,
|
||||
bit_count: u8,
|
||||
decoded_data: u64,
|
||||
saved_key: u64,
|
||||
manchester_state: ManchesterState,
|
||||
}
|
||||
|
||||
impl KiaV5Decoder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
step: DecoderStep::Reset,
|
||||
te_last: 0,
|
||||
header_count: 0,
|
||||
bit_count: 0,
|
||||
decoded_data: 0,
|
||||
saved_key: 0,
|
||||
manchester_state: ManchesterState::Mid1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get encryption key (placeholder)
|
||||
fn get_v5_key() -> u64 {
|
||||
0x0000000000000000
|
||||
}
|
||||
|
||||
/// Custom mixer decryption
|
||||
fn mixer_decode(encrypted: u32) -> u16 {
|
||||
let mut s0 = (encrypted & 0xFF) as u8;
|
||||
let mut s1 = ((encrypted >> 8) & 0xFF) as u8;
|
||||
let mut s2 = ((encrypted >> 16) & 0xFF) as u8;
|
||||
let mut s3 = ((encrypted >> 24) & 0xFF) as u8;
|
||||
|
||||
let key = Self::get_v5_key();
|
||||
let mut keystore_bytes = [0u8; 8];
|
||||
for i in 0..8 {
|
||||
keystore_bytes[i] = ((key >> ((7 - i) * 8)) & 0xFF) as u8;
|
||||
}
|
||||
|
||||
let mut round_index: usize = 1;
|
||||
for _ in 0..18 {
|
||||
let mut r = keystore_bytes[round_index];
|
||||
let mut steps = 8;
|
||||
while steps > 0 {
|
||||
let base = if (s3 & 0x40) == 0 {
|
||||
if (s3 & 0x02) == 0 { 0x74 } else { 0x2E }
|
||||
} else {
|
||||
if (s3 & 0x02) == 0 { 0x3A } else { 0x5C }
|
||||
};
|
||||
|
||||
let mut base = base;
|
||||
if s2 & 0x08 != 0 {
|
||||
base = ((base >> 4) & 0x0F) | ((base & 0x0F) << 4);
|
||||
}
|
||||
if s1 & 0x01 != 0 {
|
||||
base = (base & 0x3F) << 2;
|
||||
}
|
||||
if s0 & 0x01 != 0 {
|
||||
base = base << 1;
|
||||
}
|
||||
|
||||
let temp = (s3 ^ s1) & 0xFF;
|
||||
s3 = (s3 & 0x7F) << 1;
|
||||
if s2 & 0x80 != 0 {
|
||||
s3 |= 0x01;
|
||||
}
|
||||
s2 = (s2 & 0x7F) << 1;
|
||||
if s1 & 0x80 != 0 {
|
||||
s2 |= 0x01;
|
||||
}
|
||||
s1 = (s1 & 0x7F) << 1;
|
||||
if s0 & 0x80 != 0 {
|
||||
s1 |= 0x01;
|
||||
}
|
||||
s0 = (s0 & 0x7F) << 1;
|
||||
|
||||
let chk = (base ^ (r ^ temp)) & 0xFF;
|
||||
if chk & 0x80 != 0 {
|
||||
s0 |= 0x01;
|
||||
}
|
||||
r = (r & 0x7F) << 1;
|
||||
steps -= 1;
|
||||
}
|
||||
round_index = (round_index.wrapping_sub(1)) & 0x7;
|
||||
}
|
||||
|
||||
((s0 as u16) + ((s1 as u16) << 8)) & 0xFFFF
|
||||
}
|
||||
|
||||
/// Reverse bits in 64-bit value
|
||||
fn compute_yek(key: u64) -> u64 {
|
||||
let mut yek: u64 = 0;
|
||||
for i in 0..8 {
|
||||
let byte = ((key >> (i * 8)) & 0xFF) as u8;
|
||||
let mut reversed: u8 = 0;
|
||||
for b in 0..8 {
|
||||
if byte & (1 << b) != 0 {
|
||||
reversed |= 1 << (7 - b);
|
||||
}
|
||||
}
|
||||
yek |= (reversed as u64) << ((7 - i) * 8);
|
||||
}
|
||||
yek
|
||||
}
|
||||
|
||||
/// Manchester state machine
|
||||
fn manchester_advance(&mut self, is_short: bool, is_high: bool) -> Option<bool> {
|
||||
let event = match (is_short, is_high) {
|
||||
(true, true) => 0, // Short High
|
||||
(true, false) => 1, // Short Low
|
||||
(false, true) => 2, // Long High
|
||||
(false, false) => 3, // Long Low
|
||||
};
|
||||
|
||||
let (new_state, output) = match (self.manchester_state, event) {
|
||||
(ManchesterState::Mid0, 1) | (ManchesterState::Mid1, 1) =>
|
||||
(ManchesterState::Start0, None),
|
||||
(ManchesterState::Mid0, 0) | (ManchesterState::Mid1, 0) =>
|
||||
(ManchesterState::Start1, None),
|
||||
|
||||
(ManchesterState::Start1, 1) => (ManchesterState::Mid1, Some(true)),
|
||||
(ManchesterState::Start1, 3) => (ManchesterState::Start0, Some(true)),
|
||||
|
||||
(ManchesterState::Start0, 0) => (ManchesterState::Mid0, Some(false)),
|
||||
(ManchesterState::Start0, 2) => (ManchesterState::Start1, Some(false)),
|
||||
|
||||
_ => (ManchesterState::Mid1, None),
|
||||
};
|
||||
|
||||
self.manchester_state = new_state;
|
||||
output
|
||||
}
|
||||
|
||||
/// Parse decoded data
|
||||
fn parse_data(&self) -> Option<DecodedSignal> {
|
||||
if self.bit_count < MIN_COUNT_BIT as u8 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let key = self.saved_key;
|
||||
let yek = Self::compute_yek(key);
|
||||
|
||||
let serial = ((yek >> 32) & 0x0FFFFFFF) as u32;
|
||||
let button = ((yek >> 60) & 0x0F) as u8;
|
||||
let encrypted = (yek & 0xFFFFFFFF) as u32;
|
||||
let counter = Self::mixer_decode(encrypted);
|
||||
|
||||
let _crc = (self.decoded_data & 0x07) as u8;
|
||||
|
||||
Some(DecodedSignal {
|
||||
serial: Some(serial),
|
||||
button: Some(button),
|
||||
counter: Some(counter),
|
||||
crc_valid: true, // V5 doesn't have a standard CRC validation
|
||||
data: key,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: false, // V5 is decode-only
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolDecoder for KiaV5Decoder {
|
||||
fn name(&self) -> &'static str {
|
||||
"Kia V5"
|
||||
}
|
||||
|
||||
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] {
|
||||
&[433_920_000]
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.step = DecoderStep::Reset;
|
||||
self.te_last = 0;
|
||||
self.header_count = 0;
|
||||
self.bit_count = 0;
|
||||
self.decoded_data = 0;
|
||||
self.saved_key = 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_short {
|
||||
self.step = DecoderStep::CheckPreamble;
|
||||
self.te_last = duration;
|
||||
self.header_count = 1;
|
||||
self.bit_count = 0;
|
||||
self.decoded_data = 0;
|
||||
self.manchester_state = ManchesterState::Mid1;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::CheckPreamble => {
|
||||
if level {
|
||||
if is_long {
|
||||
if self.header_count > 40 {
|
||||
self.step = DecoderStep::Data;
|
||||
self.bit_count = 0;
|
||||
self.decoded_data = 0;
|
||||
self.saved_key = 0;
|
||||
self.header_count = 0;
|
||||
} else {
|
||||
self.te_last = duration;
|
||||
}
|
||||
} else if is_short {
|
||||
self.te_last = duration;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
} else {
|
||||
if (is_short && duration_diff!(self.te_last, TE_SHORT) < TE_DELTA) ||
|
||||
(is_long && duration_diff!(self.te_last, TE_SHORT) < TE_DELTA) ||
|
||||
(duration_diff!(self.te_last, TE_LONG) < TE_DELTA) {
|
||||
self.header_count += 1;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
self.te_last = duration;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::Data => {
|
||||
if !is_short && !is_long {
|
||||
// End of data - try to parse
|
||||
if self.bit_count >= MIN_COUNT_BIT as u8 {
|
||||
let result = self.parse_data();
|
||||
self.step = DecoderStep::Reset;
|
||||
return result;
|
||||
}
|
||||
self.step = DecoderStep::Reset;
|
||||
return None;
|
||||
}
|
||||
|
||||
if self.bit_count <= 66 {
|
||||
if let Some(bit) = self.manchester_advance(is_short, level) {
|
||||
self.decoded_data = (self.decoded_data << 1) | (bit as u64);
|
||||
self.bit_count += 1;
|
||||
|
||||
if self.bit_count == 64 {
|
||||
self.saved_key = self.decoded_data;
|
||||
self.decoded_data = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.te_last = duration;
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn supports_encoding(&self) -> bool {
|
||||
false // V5 is decode-only in protopirate
|
||||
}
|
||||
|
||||
fn encode(&self, _decoded: &DecodedSignal, _button: u8) -> Option<Vec<LevelDuration>> {
|
||||
None // V5 doesn't support encoding
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,553 @@
|
||||
//! Kia V6 protocol decoder
|
||||
//!
|
||||
//! Ported from protopirate's kia_v6.c
|
||||
//!
|
||||
//! Protocol characteristics:
|
||||
//! - Manchester encoding: 200/400µs timing
|
||||
//! - 144 bits total (split into 3 parts)
|
||||
//! - Long preamble of 600+ pairs
|
||||
//! - AES-128 encryption
|
||||
//! - Decode-only (no encoder)
|
||||
|
||||
use super::{ProtocolDecoder, ProtocolTiming, DecodedSignal};
|
||||
use crate::radio::demodulator::LevelDuration;
|
||||
use crate::duration_diff;
|
||||
|
||||
const TE_SHORT: u32 = 200;
|
||||
const TE_LONG: u32 = 400;
|
||||
const TE_DELTA: u32 = 100;
|
||||
const MIN_COUNT_BIT: usize = 144;
|
||||
const PREAMBLE_COUNT: u16 = 601;
|
||||
|
||||
const XOR_MASK_LOW: u32 = 0x84AF25FB;
|
||||
const XOR_MASK_HIGH: u32 = 0x638766AB;
|
||||
|
||||
/// AES S-box
|
||||
const AES_SBOX: [u8; 256] = [
|
||||
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
|
||||
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
|
||||
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
|
||||
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
|
||||
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
|
||||
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
|
||||
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
|
||||
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
|
||||
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
|
||||
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
|
||||
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
|
||||
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
|
||||
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
|
||||
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
|
||||
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
|
||||
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16,
|
||||
];
|
||||
|
||||
/// AES inverse S-box
|
||||
const AES_SBOX_INV: [u8; 256] = [
|
||||
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
|
||||
0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
|
||||
0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
|
||||
0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
|
||||
0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
|
||||
0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
|
||||
0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
|
||||
0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
|
||||
0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
|
||||
0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
|
||||
0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
|
||||
0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
|
||||
0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
|
||||
0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
|
||||
0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
|
||||
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d,
|
||||
];
|
||||
|
||||
const AES_RCON: [u8; 10] = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36];
|
||||
|
||||
/// Manchester states
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum ManchesterState {
|
||||
Mid0,
|
||||
Mid1,
|
||||
Start0,
|
||||
Start1,
|
||||
}
|
||||
|
||||
/// Decoder states
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum DecoderStep {
|
||||
Reset,
|
||||
WaitFirstHigh,
|
||||
WaitLongHigh,
|
||||
Data,
|
||||
}
|
||||
|
||||
/// Kia V6 protocol decoder
|
||||
pub struct KiaV6Decoder {
|
||||
step: DecoderStep,
|
||||
te_last: u32,
|
||||
header_count: u16,
|
||||
manchester_state: ManchesterState,
|
||||
|
||||
data_part1_low: u32,
|
||||
data_part1_high: u32,
|
||||
stored_part1_low: u32,
|
||||
stored_part1_high: u32,
|
||||
stored_part2_low: u32,
|
||||
stored_part2_high: u32,
|
||||
data_part3: u16,
|
||||
|
||||
bit_count: u8,
|
||||
}
|
||||
|
||||
impl KiaV6Decoder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
step: DecoderStep::Reset,
|
||||
te_last: 0,
|
||||
header_count: 0,
|
||||
manchester_state: ManchesterState::Mid1,
|
||||
data_part1_low: 0,
|
||||
data_part1_high: 0,
|
||||
stored_part1_low: 0,
|
||||
stored_part1_high: 0,
|
||||
stored_part2_low: 0,
|
||||
stored_part2_high: 0,
|
||||
data_part3: 0,
|
||||
bit_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get keystore A (placeholder)
|
||||
fn get_keystore_a() -> u64 {
|
||||
0x0000000000000000
|
||||
}
|
||||
|
||||
/// Get keystore B (placeholder)
|
||||
fn get_keystore_b() -> u64 {
|
||||
0x0000000000000000
|
||||
}
|
||||
|
||||
/// CRC8 calculation
|
||||
fn crc8(data: &[u8], init: u8, polynomial: u8) -> u8 {
|
||||
let mut crc = init;
|
||||
for &byte in data {
|
||||
crc ^= byte;
|
||||
for _ in 0..8 {
|
||||
let b = crc << 1;
|
||||
if (crc & 0x80) != 0 {
|
||||
crc = b ^ polynomial;
|
||||
} else {
|
||||
crc = b;
|
||||
}
|
||||
}
|
||||
}
|
||||
crc
|
||||
}
|
||||
|
||||
/// GF(2^8) multiply by 2
|
||||
fn gf_mul2(x: u8) -> u8 {
|
||||
((x >> 7).wrapping_mul(0x1b)) ^ (x << 1)
|
||||
}
|
||||
|
||||
/// AES inverse SubBytes
|
||||
fn aes_subbytes_inv(state: &mut [u8; 16]) {
|
||||
for i in 0..16 {
|
||||
state[i] = AES_SBOX_INV[state[i] as usize];
|
||||
}
|
||||
}
|
||||
|
||||
/// AES inverse ShiftRows
|
||||
fn aes_shiftrows_inv(state: &mut [u8; 16]) {
|
||||
let temp = state[13];
|
||||
state[13] = state[9];
|
||||
state[9] = state[5];
|
||||
state[5] = state[1];
|
||||
state[1] = temp;
|
||||
|
||||
let temp = state[2];
|
||||
state[2] = state[10];
|
||||
state[10] = temp;
|
||||
let temp = state[6];
|
||||
state[6] = state[14];
|
||||
state[14] = temp;
|
||||
|
||||
let temp = state[3];
|
||||
state[3] = state[7];
|
||||
state[7] = state[11];
|
||||
state[11] = state[15];
|
||||
state[15] = temp;
|
||||
}
|
||||
|
||||
/// AES inverse MixColumns
|
||||
fn aes_mixcolumns_inv(state: &mut [u8; 16]) {
|
||||
for i in 0..4 {
|
||||
let a = state[i * 4];
|
||||
let b = state[i * 4 + 1];
|
||||
let c = state[i * 4 + 2];
|
||||
let d = state[i * 4 + 3];
|
||||
|
||||
let a2 = Self::gf_mul2(a);
|
||||
let a4 = Self::gf_mul2(a2);
|
||||
let a8 = Self::gf_mul2(a4);
|
||||
let b2 = Self::gf_mul2(b);
|
||||
let b4 = Self::gf_mul2(b2);
|
||||
let b8 = Self::gf_mul2(b4);
|
||||
let c2 = Self::gf_mul2(c);
|
||||
let c4 = Self::gf_mul2(c2);
|
||||
let c8 = Self::gf_mul2(c4);
|
||||
let d2 = Self::gf_mul2(d);
|
||||
let d4 = Self::gf_mul2(d2);
|
||||
let d8 = Self::gf_mul2(d4);
|
||||
|
||||
state[i * 4] = (a8 ^ a4 ^ a2) ^ (b8 ^ b2 ^ b) ^ (c8 ^ c4 ^ c) ^ (d8 ^ d);
|
||||
state[i * 4 + 1] = (a8 ^ a) ^ (b8 ^ b4 ^ b2) ^ (c8 ^ c2 ^ c) ^ (d8 ^ d4 ^ d);
|
||||
state[i * 4 + 2] = (a8 ^ a4 ^ a) ^ (b8 ^ b) ^ (c8 ^ c4 ^ c2) ^ (d8 ^ d2 ^ d);
|
||||
state[i * 4 + 3] = (a8 ^ a2 ^ a) ^ (b8 ^ b4 ^ b) ^ (c8 ^ c) ^ (d8 ^ d4 ^ d2);
|
||||
}
|
||||
}
|
||||
|
||||
/// AES AddRoundKey
|
||||
fn aes_addroundkey(state: &mut [u8; 16], round_key: &[u8]) {
|
||||
for i in 0..16 {
|
||||
state[i] ^= round_key[i];
|
||||
}
|
||||
}
|
||||
|
||||
/// AES key expansion
|
||||
fn aes_key_expansion(key: &[u8; 16]) -> [u8; 176] {
|
||||
let mut round_keys = [0u8; 176];
|
||||
round_keys[..16].copy_from_slice(key);
|
||||
|
||||
for i in 4..44 {
|
||||
let prev_word_idx = (i - 1) * 4;
|
||||
let mut b0 = round_keys[prev_word_idx];
|
||||
let mut b1 = round_keys[prev_word_idx + 1];
|
||||
let mut b2 = round_keys[prev_word_idx + 2];
|
||||
let mut b3 = round_keys[prev_word_idx + 3];
|
||||
|
||||
if (i % 4) == 0 {
|
||||
let new_b0 = AES_SBOX[b1 as usize] ^ AES_RCON[(i / 4) - 1];
|
||||
let new_b1 = AES_SBOX[b2 as usize];
|
||||
let new_b2 = AES_SBOX[b3 as usize];
|
||||
let new_b3 = AES_SBOX[b0 as usize];
|
||||
b0 = new_b0;
|
||||
b1 = new_b1;
|
||||
b2 = new_b2;
|
||||
b3 = new_b3;
|
||||
}
|
||||
|
||||
let back_word_idx = (i - 4) * 4;
|
||||
b0 ^= round_keys[back_word_idx];
|
||||
b1 ^= round_keys[back_word_idx + 1];
|
||||
b2 ^= round_keys[back_word_idx + 2];
|
||||
b3 ^= round_keys[back_word_idx + 3];
|
||||
|
||||
let curr_word_idx = i * 4;
|
||||
round_keys[curr_word_idx] = b0;
|
||||
round_keys[curr_word_idx + 1] = b1;
|
||||
round_keys[curr_word_idx + 2] = b2;
|
||||
round_keys[curr_word_idx + 3] = b3;
|
||||
}
|
||||
|
||||
round_keys
|
||||
}
|
||||
|
||||
/// AES-128 decrypt
|
||||
fn aes128_decrypt(expanded_key: &[u8; 176], data: &mut [u8; 16]) {
|
||||
let mut state = *data;
|
||||
|
||||
Self::aes_addroundkey(&mut state, &expanded_key[160..176]);
|
||||
|
||||
for round in (1..10).rev() {
|
||||
Self::aes_shiftrows_inv(&mut state);
|
||||
Self::aes_subbytes_inv(&mut state);
|
||||
Self::aes_addroundkey(&mut state, &expanded_key[round * 16..(round + 1) * 16]);
|
||||
Self::aes_mixcolumns_inv(&mut state);
|
||||
}
|
||||
|
||||
Self::aes_shiftrows_inv(&mut state);
|
||||
Self::aes_subbytes_inv(&mut state);
|
||||
Self::aes_addroundkey(&mut state, &expanded_key[0..16]);
|
||||
|
||||
*data = state;
|
||||
}
|
||||
|
||||
/// Get AES key from keystores
|
||||
fn get_aes_key() -> [u8; 16] {
|
||||
let keystore_a = Self::get_keystore_a();
|
||||
let keystore_a_hi = ((keystore_a >> 32) & 0xFFFFFFFF) as u32;
|
||||
let keystore_a_lo = (keystore_a & 0xFFFFFFFF) as u32;
|
||||
|
||||
let u_var15_a = keystore_a_lo ^ XOR_MASK_LOW;
|
||||
let u_var5_a = XOR_MASK_HIGH ^ keystore_a_hi;
|
||||
|
||||
let val64_a = ((u_var5_a as u64) << 32) | (u_var15_a as u64);
|
||||
|
||||
let keystore_b = Self::get_keystore_b();
|
||||
let keystore_b_hi = ((keystore_b >> 32) & 0xFFFFFFFF) as u32;
|
||||
let keystore_b_lo = (keystore_b & 0xFFFFFFFF) as u32;
|
||||
|
||||
let u_var15_b = keystore_b_lo ^ XOR_MASK_LOW;
|
||||
let u_var5_b = XOR_MASK_HIGH ^ keystore_b_hi;
|
||||
|
||||
let val64_b = ((u_var5_b as u64) << 32) | (u_var15_b as u64);
|
||||
|
||||
let mut aes_key = [0u8; 16];
|
||||
for i in 0..8 {
|
||||
aes_key[i] = ((val64_a >> (56 - i * 8)) & 0xFF) as u8;
|
||||
}
|
||||
for i in 0..8 {
|
||||
aes_key[i + 8] = ((val64_b >> (56 - i * 8)) & 0xFF) as u8;
|
||||
}
|
||||
|
||||
aes_key
|
||||
}
|
||||
|
||||
/// Decrypt the stored data
|
||||
fn decrypt(&self) -> Option<(u32, u8, u32, bool)> {
|
||||
let mut encrypted_data = [0u8; 16];
|
||||
|
||||
encrypted_data[0] = ((self.stored_part1_high >> 8) & 0xFF) as u8;
|
||||
encrypted_data[1] = (self.stored_part1_high & 0xFF) as u8;
|
||||
encrypted_data[2] = ((self.stored_part1_low >> 24) & 0xFF) as u8;
|
||||
encrypted_data[3] = ((self.stored_part1_low >> 16) & 0xFF) as u8;
|
||||
encrypted_data[4] = ((self.stored_part1_low >> 8) & 0xFF) as u8;
|
||||
encrypted_data[5] = (self.stored_part1_low & 0xFF) as u8;
|
||||
encrypted_data[6] = ((self.stored_part2_high >> 24) & 0xFF) as u8;
|
||||
encrypted_data[7] = ((self.stored_part2_high >> 16) & 0xFF) as u8;
|
||||
encrypted_data[8] = ((self.stored_part2_high >> 8) & 0xFF) as u8;
|
||||
encrypted_data[9] = (self.stored_part2_high & 0xFF) as u8;
|
||||
encrypted_data[10] = ((self.stored_part2_low >> 24) & 0xFF) as u8;
|
||||
encrypted_data[11] = ((self.stored_part2_low >> 16) & 0xFF) as u8;
|
||||
encrypted_data[12] = ((self.stored_part2_low >> 8) & 0xFF) as u8;
|
||||
encrypted_data[13] = (self.stored_part2_low & 0xFF) as u8;
|
||||
encrypted_data[14] = ((self.data_part3 >> 8) & 0xFF) as u8;
|
||||
encrypted_data[15] = (self.data_part3 & 0xFF) as u8;
|
||||
|
||||
let aes_key = Self::get_aes_key();
|
||||
let expanded_key = Self::aes_key_expansion(&aes_key);
|
||||
|
||||
Self::aes128_decrypt(&expanded_key, &mut encrypted_data);
|
||||
|
||||
let decrypted = &encrypted_data;
|
||||
let calculated_crc = Self::crc8(&decrypted[..15], 0xFF, 0x07);
|
||||
let stored_crc = decrypted[15];
|
||||
let crc_valid = (calculated_crc ^ stored_crc) < 2;
|
||||
|
||||
// Serial: bytes 4-6 as 24-bit big-endian
|
||||
let serial = ((decrypted[4] as u32) << 16) | ((decrypted[5] as u32) << 8) | (decrypted[6] as u32);
|
||||
let button = decrypted[7];
|
||||
let counter = ((decrypted[8] as u32) << 24) |
|
||||
((decrypted[9] as u32) << 16) |
|
||||
((decrypted[10] as u32) << 8) |
|
||||
(decrypted[11] as u32);
|
||||
|
||||
Some((serial, button, counter, crc_valid))
|
||||
}
|
||||
|
||||
/// Manchester state machine
|
||||
fn manchester_advance(&mut self, is_short: bool, is_high: bool) -> Option<bool> {
|
||||
let event = match (is_short, is_high) {
|
||||
(true, true) => 0, // Short High
|
||||
(true, false) => 2, // Short Low
|
||||
(false, true) => 6, // Long High
|
||||
(false, false) => 4, // Long Low
|
||||
};
|
||||
|
||||
let (new_state, output) = match (self.manchester_state, event) {
|
||||
(ManchesterState::Mid0, 2) | (ManchesterState::Mid1, 2) =>
|
||||
(ManchesterState::Start0, None),
|
||||
(ManchesterState::Mid0, 0) | (ManchesterState::Mid1, 0) =>
|
||||
(ManchesterState::Start1, None),
|
||||
|
||||
(ManchesterState::Start1, 2) => (ManchesterState::Mid1, Some(true)),
|
||||
(ManchesterState::Start1, 4) => (ManchesterState::Start0, Some(true)),
|
||||
|
||||
(ManchesterState::Start0, 0) => (ManchesterState::Mid0, Some(false)),
|
||||
(ManchesterState::Start0, 6) => (ManchesterState::Start1, Some(false)),
|
||||
|
||||
_ => (ManchesterState::Mid1, None),
|
||||
};
|
||||
|
||||
self.manchester_state = new_state;
|
||||
output
|
||||
}
|
||||
|
||||
/// Add initial sync bits
|
||||
fn add_sync_bits(&mut self) {
|
||||
// Add 1, 1, 0, 1 as initial bits
|
||||
for bit in [true, true, false, true] {
|
||||
let carry = self.data_part1_low >> 31;
|
||||
self.data_part1_low = (self.data_part1_low << 1) | (bit as u32);
|
||||
self.data_part1_high = (self.data_part1_high << 1) | carry;
|
||||
self.bit_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolDecoder for KiaV6Decoder {
|
||||
fn name(&self) -> &'static str {
|
||||
"Kia V6"
|
||||
}
|
||||
|
||||
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] {
|
||||
&[433_920_000]
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.step = DecoderStep::Reset;
|
||||
self.te_last = 0;
|
||||
self.header_count = 0;
|
||||
self.manchester_state = ManchesterState::Mid1;
|
||||
self.data_part1_low = 0;
|
||||
self.data_part1_high = 0;
|
||||
self.stored_part1_low = 0;
|
||||
self.stored_part1_high = 0;
|
||||
self.stored_part2_low = 0;
|
||||
self.stored_part2_high = 0;
|
||||
self.data_part3 = 0;
|
||||
self.bit_count = 0;
|
||||
}
|
||||
|
||||
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_short {
|
||||
self.step = DecoderStep::WaitFirstHigh;
|
||||
self.te_last = duration;
|
||||
self.header_count = 0;
|
||||
self.manchester_state = ManchesterState::Mid1;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::WaitFirstHigh => {
|
||||
if level {
|
||||
return None;
|
||||
}
|
||||
|
||||
let diff_short = duration_diff!(duration, TE_SHORT);
|
||||
let diff_long = duration_diff!(duration, TE_LONG);
|
||||
|
||||
if diff_long < TE_DELTA && diff_long < diff_short {
|
||||
if self.header_count >= PREAMBLE_COUNT {
|
||||
self.header_count = 0;
|
||||
self.te_last = duration;
|
||||
self.step = DecoderStep::WaitLongHigh;
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
if diff_short >= TE_DELTA && diff_long >= TE_DELTA {
|
||||
self.step = DecoderStep::Reset;
|
||||
return None;
|
||||
}
|
||||
|
||||
if duration_diff!(self.te_last, TE_SHORT) < TE_DELTA {
|
||||
self.te_last = duration;
|
||||
self.header_count += 1;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::WaitLongHigh => {
|
||||
if !level {
|
||||
self.step = DecoderStep::Reset;
|
||||
return None;
|
||||
}
|
||||
|
||||
let diff_long = duration_diff!(duration, TE_LONG);
|
||||
let diff_short = duration_diff!(duration, TE_SHORT);
|
||||
|
||||
if diff_long >= TE_DELTA && diff_short >= TE_DELTA {
|
||||
self.step = DecoderStep::Reset;
|
||||
return None;
|
||||
}
|
||||
|
||||
if duration_diff!(self.te_last, TE_LONG) >= TE_DELTA {
|
||||
self.step = DecoderStep::Reset;
|
||||
return None;
|
||||
}
|
||||
|
||||
self.data_part1_low = 0;
|
||||
self.data_part1_high = 0;
|
||||
self.bit_count = 0;
|
||||
self.add_sync_bits();
|
||||
self.step = DecoderStep::Data;
|
||||
}
|
||||
|
||||
DecoderStep::Data => {
|
||||
if !is_short && !is_long {
|
||||
self.step = DecoderStep::Reset;
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(bit) = self.manchester_advance(is_short, level) {
|
||||
let carry = self.data_part1_low >> 31;
|
||||
self.data_part1_low = (self.data_part1_low << 1) | (bit as u32);
|
||||
self.data_part1_high = (self.data_part1_high << 1) | carry;
|
||||
self.bit_count += 1;
|
||||
|
||||
if self.bit_count == 64 {
|
||||
self.stored_part1_low = !self.data_part1_low;
|
||||
self.stored_part1_high = !self.data_part1_high;
|
||||
self.data_part1_low = 0;
|
||||
self.data_part1_high = 0;
|
||||
} else if self.bit_count == 128 {
|
||||
self.stored_part2_low = !self.data_part1_low;
|
||||
self.stored_part2_high = !self.data_part1_high;
|
||||
self.data_part1_low = 0;
|
||||
self.data_part1_high = 0;
|
||||
}
|
||||
}
|
||||
|
||||
self.te_last = duration;
|
||||
|
||||
if self.bit_count as usize == MIN_COUNT_BIT {
|
||||
self.data_part3 = !(self.data_part1_low as u16);
|
||||
|
||||
if let Some((serial, button, counter, crc_valid)) = self.decrypt() {
|
||||
let key_data = ((self.stored_part1_high as u64) << 32) |
|
||||
(self.stored_part1_low as u64);
|
||||
|
||||
self.step = DecoderStep::Reset;
|
||||
return Some(DecodedSignal {
|
||||
serial: Some(serial),
|
||||
button: Some(button),
|
||||
counter: Some((counter & 0xFFFF) as u16), // V6 has 32-bit counter but we only store 16
|
||||
crc_valid,
|
||||
data: key_data,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: false,
|
||||
});
|
||||
}
|
||||
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn supports_encoding(&self) -> bool {
|
||||
false // V6 is decode-only
|
||||
}
|
||||
|
||||
fn encode(&self, _decoded: &DecodedSignal, _button: u8) -> Option<Vec<LevelDuration>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
//! Protocol decoders and encoders for various keyfob systems.
|
||||
//!
|
||||
//! This module implements decoders for various keyfob protocols, ported from
|
||||
//! protopirate. Each protocol processes level+duration pairs from the demodulator.
|
||||
|
||||
mod common;
|
||||
pub mod keeloq_common;
|
||||
#[allow(dead_code)]
|
||||
pub mod aut64;
|
||||
#[allow(dead_code)]
|
||||
pub mod keys;
|
||||
mod kia_v0;
|
||||
mod kia_v1;
|
||||
mod kia_v2;
|
||||
mod kia_v3_v4;
|
||||
mod kia_v5;
|
||||
mod kia_v6;
|
||||
mod subaru;
|
||||
mod ford_v0;
|
||||
mod vag;
|
||||
mod fiat_v0;
|
||||
mod suzuki;
|
||||
mod scher_khan;
|
||||
mod star_line;
|
||||
mod psa;
|
||||
|
||||
pub use common::DecodedSignal;
|
||||
|
||||
use crate::capture::Capture;
|
||||
use crate::radio::demodulator::LevelDuration;
|
||||
|
||||
/// Protocol timing constants
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[allow(dead_code)]
|
||||
pub struct ProtocolTiming {
|
||||
/// Short pulse duration in µs
|
||||
pub te_short: u32,
|
||||
/// Long pulse duration in µs
|
||||
pub te_long: u32,
|
||||
/// Tolerance for timing matching in µs
|
||||
pub te_delta: u32,
|
||||
/// Minimum bit count for valid decode
|
||||
pub min_count_bit: usize,
|
||||
}
|
||||
|
||||
/// Trait for protocol decoders
|
||||
///
|
||||
/// Each protocol implements a state machine that processes level+duration pairs.
|
||||
pub trait ProtocolDecoder: Send + Sync {
|
||||
/// Get the protocol name
|
||||
fn name(&self) -> &'static str;
|
||||
|
||||
/// Get timing constants
|
||||
#[allow(dead_code)]
|
||||
fn timing(&self) -> ProtocolTiming;
|
||||
|
||||
/// Get supported frequencies in Hz
|
||||
fn supported_frequencies(&self) -> &[u32];
|
||||
|
||||
/// Reset the decoder state machine
|
||||
fn reset(&mut self);
|
||||
|
||||
/// Feed a level+duration pair to the decoder
|
||||
/// Returns Some(DecodedSignal) when a complete valid signal is decoded
|
||||
fn feed(&mut self, level: bool, duration_us: u32) -> Option<DecodedSignal>;
|
||||
|
||||
/// Check if this protocol supports encoding
|
||||
fn supports_encoding(&self) -> bool;
|
||||
|
||||
/// Encode a signal with the given button command
|
||||
fn encode(&self, decoded: &DecodedSignal, button: u8) -> Option<Vec<LevelDuration>>;
|
||||
}
|
||||
|
||||
/// Registry of all supported protocols
|
||||
pub struct ProtocolRegistry {
|
||||
decoders: Vec<Box<dyn ProtocolDecoder>>,
|
||||
}
|
||||
|
||||
impl ProtocolRegistry {
|
||||
/// Create a new protocol registry with all built-in protocols
|
||||
pub fn new() -> Self {
|
||||
let decoders: Vec<Box<dyn ProtocolDecoder>> = vec![
|
||||
// Kia protocols
|
||||
Box::new(kia_v0::KiaV0Decoder::new()),
|
||||
Box::new(kia_v1::KiaV1Decoder::new()),
|
||||
Box::new(kia_v2::KiaV2Decoder::new()),
|
||||
Box::new(kia_v3_v4::KiaV3V4Decoder::new()),
|
||||
Box::new(kia_v5::KiaV5Decoder::new()),
|
||||
Box::new(kia_v6::KiaV6Decoder::new()),
|
||||
// Other protocols
|
||||
Box::new(subaru::SubaruDecoder::new()),
|
||||
Box::new(ford_v0::FordV0Decoder::new()),
|
||||
Box::new(vag::VagDecoder::new()),
|
||||
Box::new(fiat_v0::FiatV0Decoder::new()),
|
||||
Box::new(suzuki::SuzukiDecoder::new()),
|
||||
Box::new(scher_khan::ScherKhanDecoder::new()),
|
||||
Box::new(star_line::StarLineDecoder::new()),
|
||||
Box::new(psa::PsaDecoder::new()),
|
||||
];
|
||||
|
||||
Self { decoders }
|
||||
}
|
||||
|
||||
/// Process level+duration pairs from demodulator
|
||||
/// Returns decoded signal info if any protocol matches
|
||||
pub fn process_signal(&mut self, pairs: &[LevelDuration], frequency: u32) -> Option<(String, DecodedSignal)> {
|
||||
// Reset all decoders
|
||||
for decoder in &mut self.decoders {
|
||||
decoder.reset();
|
||||
}
|
||||
|
||||
// Feed pairs to all decoders that support this frequency
|
||||
for pair in pairs {
|
||||
for decoder in &mut self.decoders {
|
||||
// Check frequency support
|
||||
let freq_supported = decoder
|
||||
.supported_frequencies()
|
||||
.iter()
|
||||
.any(|&f| {
|
||||
let diff = if f > frequency { f - frequency } else { frequency - f };
|
||||
diff < (f / 50) // 2% tolerance
|
||||
});
|
||||
|
||||
if !freq_supported {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(decoded) = decoder.feed(pair.level, pair.duration_us) {
|
||||
return Some((decoder.name().to_string(), decoded));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Try to decode a capture (for compatibility with old interface)
|
||||
#[allow(dead_code)]
|
||||
pub fn try_decode(&mut self, capture: &Capture) -> Option<(String, DecodedSignal)> {
|
||||
// Convert raw pairs to LevelDuration and process
|
||||
if capture.raw_pairs.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let pairs: Vec<LevelDuration> = capture.raw_pairs
|
||||
.iter()
|
||||
.map(|p| LevelDuration::new(p.level, p.duration_us))
|
||||
.collect();
|
||||
|
||||
self.process_signal(&pairs, capture.frequency)
|
||||
}
|
||||
|
||||
/// Get a decoder by name
|
||||
pub fn get(&self, name: &str) -> Option<&dyn ProtocolDecoder> {
|
||||
self.decoders
|
||||
.iter()
|
||||
.find(|d| d.name().eq_ignore_ascii_case(name))
|
||||
.map(|d| d.as_ref())
|
||||
}
|
||||
|
||||
/// List all protocol names
|
||||
#[allow(dead_code)]
|
||||
pub fn list_protocols(&self) -> Vec<&'static str> {
|
||||
self.decoders.iter().map(|d| d.name()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ProtocolRegistry {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper macro for duration comparison (matches protopirate's DURATION_DIFF)
|
||||
#[macro_export]
|
||||
macro_rules! duration_diff {
|
||||
($actual:expr, $expected:expr) => {
|
||||
if $actual > $expected {
|
||||
$actual - $expected
|
||||
} else {
|
||||
$expected - $actual
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,489 @@
|
||||
//! PSA (Peugeot/Citroen) protocol decoder/encoder
|
||||
//!
|
||||
//! Ported from protopirate's psa.c
|
||||
//!
|
||||
//! Protocol characteristics:
|
||||
//! - Manchester encoding: 125/250µs bit timing, 250/500µs symbol timing
|
||||
//! - 128 bits total (key1: 64 bits, key2: 16 bits, validation: 48 bits)
|
||||
//! - TEA and XOR encryption schemes
|
||||
//! - Two modes: 0x23 and 0x36
|
||||
|
||||
use super::{DecodedSignal, ProtocolDecoder, ProtocolTiming};
|
||||
use crate::duration_diff;
|
||||
use crate::radio::demodulator::LevelDuration;
|
||||
|
||||
const TE_SHORT: u32 = 250;
|
||||
const TE_LONG: u32 = 500;
|
||||
const TE_DELTA: u32 = 100;
|
||||
const MIN_COUNT_BIT: usize = 128;
|
||||
|
||||
// Internal timing for Manchester sub-symbol detection
|
||||
const TE_SHORT_125: u32 = 125;
|
||||
const TE_LONG_250: u32 = 250;
|
||||
const TE_TOLERANCE_49: u32 = 49;
|
||||
const TE_TOLERANCE_50: u32 = 50;
|
||||
const TE_TOLERANCE_99: u32 = 99;
|
||||
const TE_END_1000: u32 = 1000;
|
||||
|
||||
// TEA constants
|
||||
const TEA_DELTA: u32 = 0x9E3779B9;
|
||||
const TEA_ROUNDS: u32 = 32;
|
||||
|
||||
// Brute-force constants for mode 0x23
|
||||
const BF1_KEY_SCHEDULE: [u32; 4] = [0x4A434915, 0xD6743C2B, 0x1F29D308, 0xE6B79A64];
|
||||
|
||||
// Brute-force constants for mode 0x36
|
||||
const BF2_KEY_SCHEDULE: [u32; 4] = [0x4039C240, 0xEDA92CAB, 0x4306C02A, 0x02192A04];
|
||||
|
||||
/// Manchester decoder states
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum ManchesterState {
|
||||
Mid0,
|
||||
Mid1,
|
||||
Start0,
|
||||
Start1,
|
||||
}
|
||||
|
||||
/// Decoder states
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum DecoderState {
|
||||
/// Waiting for first edge
|
||||
WaitEdge,
|
||||
/// Counting preamble pattern
|
||||
CountPattern,
|
||||
/// Decoding Manchester data
|
||||
DecodeManchester,
|
||||
/// Found end of data
|
||||
End,
|
||||
}
|
||||
|
||||
/// PSA protocol decoder
|
||||
pub struct PsaDecoder {
|
||||
state: DecoderState,
|
||||
prev_duration: u32,
|
||||
manchester_state: ManchesterState,
|
||||
pattern_counter: u16,
|
||||
data_low: u32,
|
||||
data_high: u32,
|
||||
bit_count: u8,
|
||||
// Decoded fields
|
||||
key1_low: u32,
|
||||
key1_high: u32,
|
||||
validation_field: u16,
|
||||
key2_low: u32,
|
||||
key2_high: u32,
|
||||
seed: u32,
|
||||
}
|
||||
|
||||
impl PsaDecoder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
state: DecoderState::WaitEdge,
|
||||
prev_duration: 0,
|
||||
manchester_state: ManchesterState::Mid1,
|
||||
pattern_counter: 0,
|
||||
data_low: 0,
|
||||
data_high: 0,
|
||||
bit_count: 0,
|
||||
key1_low: 0,
|
||||
key1_high: 0,
|
||||
validation_field: 0,
|
||||
key2_low: 0,
|
||||
key2_high: 0,
|
||||
seed: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Manchester advance
|
||||
fn manchester_advance(&mut self, is_short: bool, is_high: bool) -> Option<bool> {
|
||||
let event = match (is_short, is_high) {
|
||||
(true, true) => 0,
|
||||
(true, false) => 1,
|
||||
(false, true) => 2,
|
||||
(false, false) => 3,
|
||||
};
|
||||
|
||||
let (new_state, output) = match (self.manchester_state, event) {
|
||||
(ManchesterState::Mid0, 0) | (ManchesterState::Mid1, 0) => {
|
||||
(ManchesterState::Start1, None)
|
||||
}
|
||||
(ManchesterState::Mid0, 1) | (ManchesterState::Mid1, 1) => {
|
||||
(ManchesterState::Start0, None)
|
||||
}
|
||||
(ManchesterState::Start1, 1) => (ManchesterState::Mid1, Some(true)),
|
||||
(ManchesterState::Start1, 3) => (ManchesterState::Start0, Some(true)),
|
||||
(ManchesterState::Start0, 0) => (ManchesterState::Mid0, Some(false)),
|
||||
(ManchesterState::Start0, 2) => (ManchesterState::Start1, Some(false)),
|
||||
_ => (ManchesterState::Mid1, None),
|
||||
};
|
||||
|
||||
self.manchester_state = new_state;
|
||||
output
|
||||
}
|
||||
|
||||
fn add_bit(&mut self, bit: bool) {
|
||||
let new_bit = if bit { 1u32 } else { 0u32 };
|
||||
let carry = (self.data_low >> 31) & 1;
|
||||
self.data_low = (self.data_low << 1) | new_bit;
|
||||
self.data_high = (self.data_high << 1) | carry;
|
||||
self.bit_count += 1;
|
||||
|
||||
// Extract key1 at 64 bits
|
||||
if self.bit_count == 64 {
|
||||
self.key1_low = self.data_low;
|
||||
self.key1_high = self.data_high;
|
||||
self.data_low = 0;
|
||||
self.data_high = 0;
|
||||
}
|
||||
// Extract validation at 80 bits (16 more)
|
||||
else if self.bit_count == 80 {
|
||||
self.validation_field = self.data_low as u16;
|
||||
self.data_low = 0;
|
||||
self.data_high = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// TEA decrypt
|
||||
fn tea_decrypt(v0: &mut u32, v1: &mut u32, key: &[u32; 4]) {
|
||||
let mut sum = TEA_DELTA.wrapping_mul(TEA_ROUNDS);
|
||||
for _ in 0..TEA_ROUNDS {
|
||||
*v1 = v1.wrapping_sub(
|
||||
(v0.wrapping_shl(4).wrapping_add(key[2]))
|
||||
^ (v0.wrapping_add(sum))
|
||||
^ (v0.wrapping_shr(5).wrapping_add(key[3])),
|
||||
);
|
||||
*v0 = v0.wrapping_sub(
|
||||
(v1.wrapping_shl(4).wrapping_add(key[0]))
|
||||
^ (v1.wrapping_add(sum))
|
||||
^ (v1.wrapping_shr(5).wrapping_add(key[1])),
|
||||
);
|
||||
sum = sum.wrapping_sub(TEA_DELTA);
|
||||
}
|
||||
}
|
||||
|
||||
/// TEA encrypt
|
||||
fn tea_encrypt(v0: &mut u32, v1: &mut u32, key: &[u32; 4]) {
|
||||
let mut sum: u32 = 0;
|
||||
for _ in 0..TEA_ROUNDS {
|
||||
sum = sum.wrapping_add(TEA_DELTA);
|
||||
*v0 = v0.wrapping_add(
|
||||
(v1.wrapping_shl(4).wrapping_add(key[0]))
|
||||
^ (v1.wrapping_add(sum))
|
||||
^ (v1.wrapping_shr(5).wrapping_add(key[1])),
|
||||
);
|
||||
*v1 = v1.wrapping_add(
|
||||
(v0.wrapping_shl(4).wrapping_add(key[2]))
|
||||
^ (v0.wrapping_add(sum))
|
||||
^ (v0.wrapping_shr(5).wrapping_add(key[3])),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// XOR decrypt (mode 0x23)
|
||||
fn xor_decrypt(buffer: &mut [u8]) {
|
||||
let e6 = buffer[8];
|
||||
let e7 = buffer[9];
|
||||
let e5 = buffer[7];
|
||||
let e0 = buffer[2];
|
||||
let e1 = buffer[3];
|
||||
let e2 = buffer[4];
|
||||
let e3 = buffer[5];
|
||||
let e4 = buffer[6];
|
||||
|
||||
buffer[2] = e0 ^ e5;
|
||||
buffer[3] = e1 ^ (e0 ^ e5 ^ e6 ^ e7);
|
||||
buffer[4] = e2 ^ e0;
|
||||
buffer[5] = e3 ^ (e0 ^ e5 ^ e6 ^ e7);
|
||||
buffer[6] = e4 ^ e2;
|
||||
buffer[7] = e5 ^ e6 ^ e7;
|
||||
}
|
||||
|
||||
fn try_decrypt(&self) -> Option<(u32, u8, u32, u16, u8)> {
|
||||
// Try mode 0x23 first
|
||||
let seed_byte = (self.key1_high >> 24) as u8;
|
||||
|
||||
if seed_byte >= 0x23 && seed_byte < 0x24 {
|
||||
// Mode 0x23 - TEA + XOR
|
||||
let mut v0 = self.key1_high;
|
||||
let mut v1 = self.key1_low;
|
||||
Self::tea_decrypt(&mut v0, &mut v1, &BF1_KEY_SCHEDULE);
|
||||
|
||||
let mut buffer = [0u8; 10];
|
||||
buffer[0] = (v0 >> 24) as u8;
|
||||
buffer[1] = (v0 >> 16) as u8;
|
||||
buffer[2] = (v0 >> 8) as u8;
|
||||
buffer[3] = (v0 >> 0) as u8;
|
||||
buffer[4] = (v1 >> 24) as u8;
|
||||
buffer[5] = (v1 >> 16) as u8;
|
||||
buffer[6] = (v1 >> 8) as u8;
|
||||
buffer[7] = (v1 >> 0) as u8;
|
||||
buffer[8] = (self.validation_field >> 8) as u8;
|
||||
buffer[9] = (self.validation_field & 0xFF) as u8;
|
||||
|
||||
Self::xor_decrypt(&mut buffer);
|
||||
|
||||
let serial = ((buffer[2] as u32) << 16)
|
||||
| ((buffer[3] as u32) << 8)
|
||||
| (buffer[4] as u32);
|
||||
let counter = ((buffer[5] as u32) << 8) | (buffer[6] as u32);
|
||||
let crc = buffer[7] as u16;
|
||||
let btn = buffer[8] & 0x0F;
|
||||
|
||||
return Some((serial, btn, counter, crc, 0x23));
|
||||
}
|
||||
|
||||
if seed_byte >= 0xF3 && seed_byte < 0xF4 {
|
||||
// Mode 0x36 - TEA + different key schedule
|
||||
let mut v0 = self.key1_high;
|
||||
let mut v1 = self.key1_low;
|
||||
Self::tea_decrypt(&mut v0, &mut v1, &BF2_KEY_SCHEDULE);
|
||||
|
||||
let serial = ((v0 >> 8) & 0xFFFF00) | ((v0 & 0xFF) as u32);
|
||||
let counter = v1 >> 16;
|
||||
let btn = ((v1 >> 8) & 0xF) as u8;
|
||||
let crc = (v1 & 0xFF) as u16;
|
||||
|
||||
return Some((serial, btn, counter, crc, 0x36));
|
||||
}
|
||||
|
||||
// Cannot decrypt - return raw data
|
||||
None
|
||||
}
|
||||
|
||||
fn parse_data(&self) -> DecodedSignal {
|
||||
// Combine into 128-bit data (store lower 64)
|
||||
let data = ((self.key1_high as u64) << 32) | (self.key1_low as u64);
|
||||
|
||||
if let Some((serial, btn, counter, _crc, _mode)) = self.try_decrypt() {
|
||||
DecodedSignal {
|
||||
serial: Some(serial),
|
||||
button: Some(btn),
|
||||
counter: Some(counter as u16),
|
||||
crc_valid: true,
|
||||
data,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: true,
|
||||
}
|
||||
} else {
|
||||
DecodedSignal {
|
||||
serial: None,
|
||||
button: None,
|
||||
counter: None,
|
||||
crc_valid: false,
|
||||
data,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolDecoder for PsaDecoder {
|
||||
fn name(&self) -> &'static str {
|
||||
"PSA"
|
||||
}
|
||||
|
||||
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] {
|
||||
&[433_920_000]
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.state = DecoderState::WaitEdge;
|
||||
self.prev_duration = 0;
|
||||
self.manchester_state = ManchesterState::Mid1;
|
||||
self.pattern_counter = 0;
|
||||
self.data_low = 0;
|
||||
self.data_high = 0;
|
||||
self.bit_count = 0;
|
||||
self.key1_low = 0;
|
||||
self.key1_high = 0;
|
||||
self.validation_field = 0;
|
||||
self.key2_low = 0;
|
||||
self.key2_high = 0;
|
||||
self.seed = 0;
|
||||
}
|
||||
|
||||
fn feed(&mut self, level: bool, duration: u32) -> Option<DecodedSignal> {
|
||||
match self.state {
|
||||
DecoderState::WaitEdge => {
|
||||
if level && duration_diff!(duration, TE_SHORT_125) < TE_TOLERANCE_49 {
|
||||
self.state = DecoderState::CountPattern;
|
||||
self.prev_duration = duration;
|
||||
self.pattern_counter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderState::CountPattern => {
|
||||
let diff_125 = duration_diff!(duration, TE_SHORT_125);
|
||||
let diff_250 = duration_diff!(duration, TE_LONG_250);
|
||||
|
||||
if diff_125 < TE_TOLERANCE_50 {
|
||||
self.pattern_counter += 1;
|
||||
self.prev_duration = duration;
|
||||
} else if diff_250 < TE_TOLERANCE_99 && self.pattern_counter >= 0x46 {
|
||||
// Found end of preamble, start Manchester decoding
|
||||
self.state = DecoderState::DecodeManchester;
|
||||
self.data_low = 0;
|
||||
self.data_high = 0;
|
||||
self.bit_count = 0;
|
||||
self.manchester_state = ManchesterState::Mid1;
|
||||
self.prev_duration = duration;
|
||||
} else if self.pattern_counter < 2 {
|
||||
self.state = DecoderState::WaitEdge;
|
||||
} else {
|
||||
self.prev_duration = duration;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderState::DecodeManchester => {
|
||||
let is_short = duration_diff!(duration, TE_SHORT) < TE_DELTA;
|
||||
let is_long = duration_diff!(duration, TE_LONG) < TE_DELTA;
|
||||
let is_end = duration > TE_END_1000;
|
||||
|
||||
if is_end || self.bit_count >= 121 {
|
||||
// End of data
|
||||
self.state = DecoderState::End;
|
||||
|
||||
if self.bit_count >= 96 {
|
||||
// Got enough data
|
||||
let result = self.parse_data();
|
||||
self.state = DecoderState::WaitEdge;
|
||||
return Some(result);
|
||||
}
|
||||
self.state = DecoderState::WaitEdge;
|
||||
return None;
|
||||
}
|
||||
|
||||
if is_short || is_long {
|
||||
if let Some(bit) = self.manchester_advance(is_short, level) {
|
||||
self.add_bit(bit);
|
||||
}
|
||||
} else {
|
||||
self.state = DecoderState::WaitEdge;
|
||||
}
|
||||
|
||||
self.prev_duration = duration;
|
||||
}
|
||||
|
||||
DecoderState::End => {
|
||||
self.state = DecoderState::WaitEdge;
|
||||
}
|
||||
}
|
||||
|
||||
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).wrapping_add(1) as u32;
|
||||
|
||||
// Build plaintext buffer for mode 0x23
|
||||
let mut buffer = [0u8; 10];
|
||||
buffer[0] = 0x23;
|
||||
buffer[1] = 0x00;
|
||||
buffer[2] = (serial >> 16) as u8;
|
||||
buffer[3] = (serial >> 8) as u8;
|
||||
buffer[4] = serial as u8;
|
||||
buffer[5] = (counter >> 8) as u8;
|
||||
buffer[6] = counter as u8;
|
||||
buffer[7] = 0; // CRC placeholder
|
||||
buffer[8] = button & 0x0F;
|
||||
buffer[9] = 0;
|
||||
|
||||
// XOR encrypt
|
||||
{
|
||||
let e6 = buffer[8];
|
||||
let e7 = buffer[9];
|
||||
let p0 = buffer[2];
|
||||
let p1 = buffer[3];
|
||||
let p2 = buffer[4];
|
||||
let p3 = buffer[5];
|
||||
let p4 = buffer[6];
|
||||
let p5 = buffer[7];
|
||||
|
||||
let ne5 = p5 ^ e7 ^ e6;
|
||||
let ne0 = p2 ^ ne5;
|
||||
let ne2 = p4 ^ ne0;
|
||||
let ne4 = p3 ^ ne2;
|
||||
let ne3 = p0 ^ ne5;
|
||||
let ne1 = p1 ^ ne3;
|
||||
|
||||
buffer[2] = ne0;
|
||||
buffer[3] = ne1;
|
||||
buffer[4] = ne2;
|
||||
buffer[5] = ne3;
|
||||
buffer[6] = ne4;
|
||||
buffer[7] = ne5;
|
||||
}
|
||||
|
||||
// TEA encrypt
|
||||
let mut v0 = ((buffer[0] as u32) << 24)
|
||||
| ((buffer[1] as u32) << 16)
|
||||
| ((buffer[2] as u32) << 8)
|
||||
| (buffer[3] as u32);
|
||||
let mut v1 = ((buffer[4] as u32) << 24)
|
||||
| ((buffer[5] as u32) << 16)
|
||||
| ((buffer[6] as u32) << 8)
|
||||
| (buffer[7] as u32);
|
||||
|
||||
Self::tea_encrypt(&mut v0, &mut v1, &BF1_KEY_SCHEDULE);
|
||||
|
||||
let key1_high = v0;
|
||||
let key1_low = v1;
|
||||
let validation = ((buffer[8] as u16) << 8) | (buffer[9] as u16);
|
||||
|
||||
// Build signal
|
||||
let mut signal = Vec::with_capacity(512);
|
||||
|
||||
// Preamble: alternating 125µs pulses
|
||||
for _ in 0..70 {
|
||||
signal.push(LevelDuration::new(true, TE_SHORT_125));
|
||||
signal.push(LevelDuration::new(false, TE_SHORT_125));
|
||||
}
|
||||
|
||||
// Sync
|
||||
signal.push(LevelDuration::new(true, TE_LONG_250));
|
||||
signal.push(LevelDuration::new(false, TE_LONG_250));
|
||||
|
||||
// Key1: 64 bits Manchester encoded
|
||||
let key1 = ((key1_high as u64) << 32) | (key1_low as u64);
|
||||
for bit in (0..64).rev() {
|
||||
if (key1 >> bit) & 1 == 1 {
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
signal.push(LevelDuration::new(true, TE_SHORT));
|
||||
} else {
|
||||
signal.push(LevelDuration::new(true, TE_SHORT));
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
}
|
||||
}
|
||||
|
||||
// Validation: 16 bits Manchester encoded
|
||||
for bit in (0..16).rev() {
|
||||
if (validation >> bit) & 1 == 1 {
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
signal.push(LevelDuration::new(true, TE_SHORT));
|
||||
} else {
|
||||
signal.push(LevelDuration::new(true, TE_SHORT));
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
}
|
||||
}
|
||||
|
||||
// End marker
|
||||
signal.push(LevelDuration::new(false, TE_END_1000));
|
||||
|
||||
Some(signal)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
//! Scher-Khan protocol decoder
|
||||
//!
|
||||
//! Ported from protopirate's scher_khan.c
|
||||
//!
|
||||
//! Protocol characteristics:
|
||||
//! - PWM encoding: 750/1100µs timing
|
||||
//! - Variable bit count (35, 51, 57, 63, 64, 81, 82)
|
||||
//! - Decode-only (no encoder)
|
||||
//!
|
||||
//! References:
|
||||
//! - https://phreakerclub.com/72
|
||||
|
||||
use super::{DecodedSignal, ProtocolDecoder, ProtocolTiming};
|
||||
use crate::duration_diff;
|
||||
use crate::radio::demodulator::LevelDuration;
|
||||
|
||||
const TE_SHORT: u32 = 750;
|
||||
const TE_LONG: u32 = 1100;
|
||||
const TE_DELTA: u32 = 160;
|
||||
const MIN_COUNT_BIT: usize = 35;
|
||||
|
||||
/// Decoder states
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum DecoderStep {
|
||||
Reset,
|
||||
CheckPreamble,
|
||||
SaveDuration,
|
||||
CheckDuration,
|
||||
}
|
||||
|
||||
/// Scher-Khan protocol decoder
|
||||
pub struct ScherKhanDecoder {
|
||||
step: DecoderStep,
|
||||
te_last: u32,
|
||||
header_count: u16,
|
||||
decode_data: u64,
|
||||
decode_count_bit: usize,
|
||||
}
|
||||
|
||||
impl ScherKhanDecoder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
step: DecoderStep::Reset,
|
||||
te_last: 0,
|
||||
header_count: 0,
|
||||
decode_data: 0,
|
||||
decode_count_bit: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_data(data: u64, bit_count: usize) -> DecodedSignal {
|
||||
let (serial, btn, cnt) = match bit_count {
|
||||
51 => {
|
||||
// MAGIC CODE, Dynamic
|
||||
let serial =
|
||||
((data >> 24) & 0xFFFFFF0) as u32 | ((data >> 20) & 0x0F) as u32;
|
||||
let btn = ((data >> 24) & 0x0F) as u8;
|
||||
let cnt = (data & 0xFFFF) as u16;
|
||||
(Some(serial), Some(btn), Some(cnt))
|
||||
}
|
||||
_ => (None, None, None),
|
||||
};
|
||||
|
||||
DecodedSignal {
|
||||
serial,
|
||||
button: btn,
|
||||
counter: cnt,
|
||||
crc_valid: true,
|
||||
data,
|
||||
data_count_bit: bit_count,
|
||||
encoder_capable: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolDecoder for ScherKhanDecoder {
|
||||
fn name(&self) -> &'static str {
|
||||
"Scher-Khan"
|
||||
}
|
||||
|
||||
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] {
|
||||
&[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;
|
||||
}
|
||||
|
||||
fn feed(&mut self, level: bool, duration: u32) -> Option<DecodedSignal> {
|
||||
match self.step {
|
||||
DecoderStep::Reset => {
|
||||
if level && duration_diff!(duration, TE_SHORT * 2) < TE_DELTA {
|
||||
self.step = DecoderStep::CheckPreamble;
|
||||
self.te_last = duration;
|
||||
self.header_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::CheckPreamble => {
|
||||
if level {
|
||||
if duration_diff!(duration, TE_SHORT * 2) < TE_DELTA
|
||||
|| duration_diff!(duration, TE_SHORT) < TE_DELTA
|
||||
{
|
||||
self.te_last = duration;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
} else if duration_diff!(duration, TE_SHORT * 2) < TE_DELTA
|
||||
|| duration_diff!(duration, TE_SHORT) < TE_DELTA
|
||||
{
|
||||
if duration_diff!(self.te_last, TE_SHORT * 2) < TE_DELTA {
|
||||
self.header_count += 1;
|
||||
} else if duration_diff!(self.te_last, TE_SHORT) < TE_DELTA {
|
||||
// Found start bit
|
||||
if self.header_count >= 2 {
|
||||
self.step = DecoderStep::SaveDuration;
|
||||
self.decode_data = 0;
|
||||
self.decode_count_bit = 1;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::SaveDuration => {
|
||||
if level {
|
||||
if duration >= (TE_DELTA * 2 + TE_LONG) {
|
||||
// Found stop bit
|
||||
self.step = DecoderStep::Reset;
|
||||
if self.decode_count_bit >= MIN_COUNT_BIT {
|
||||
let result =
|
||||
Self::parse_data(self.decode_data, self.decode_count_bit);
|
||||
self.decode_data = 0;
|
||||
self.decode_count_bit = 0;
|
||||
return Some(result);
|
||||
}
|
||||
self.decode_data = 0;
|
||||
self.decode_count_bit = 0;
|
||||
} else {
|
||||
self.te_last = duration;
|
||||
self.step = DecoderStep::CheckDuration;
|
||||
}
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::CheckDuration => {
|
||||
if !level {
|
||||
if duration_diff!(self.te_last, TE_SHORT) < TE_DELTA
|
||||
&& duration_diff!(duration, TE_SHORT) < TE_DELTA
|
||||
{
|
||||
// Bit 0
|
||||
self.decode_data = (self.decode_data << 1) | 0;
|
||||
self.decode_count_bit += 1;
|
||||
self.step = DecoderStep::SaveDuration;
|
||||
} else if duration_diff!(self.te_last, TE_LONG) < TE_DELTA
|
||||
&& duration_diff!(duration, TE_LONG) < TE_DELTA
|
||||
{
|
||||
// Bit 1
|
||||
self.decode_data = (self.decode_data << 1) | 1;
|
||||
self.decode_count_bit += 1;
|
||||
self.step = DecoderStep::SaveDuration;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn supports_encoding(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn encode(&self, _decoded: &DecodedSignal, _button: u8) -> Option<Vec<LevelDuration>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
//! Star Line protocol decoder/encoder
|
||||
//!
|
||||
//! Ported from protopirate's star_line.c
|
||||
//!
|
||||
//! Protocol characteristics:
|
||||
//! - PWM encoding: 250/500µs timing
|
||||
//! - 64 bits total
|
||||
//! - Header: 6 pairs of 1000µs HIGH + 1000µs LOW
|
||||
//! - KeeLoq encryption (requires manufacturer key)
|
||||
|
||||
use super::keeloq_common::{keeloq_decrypt, keeloq_encrypt, keeloq_normal_learning, reverse_key};
|
||||
use super::{DecodedSignal, ProtocolDecoder, ProtocolTiming};
|
||||
use crate::duration_diff;
|
||||
use crate::radio::demodulator::LevelDuration;
|
||||
|
||||
const TE_SHORT: u32 = 250;
|
||||
const TE_LONG: u32 = 500;
|
||||
const TE_DELTA: u32 = 120;
|
||||
const MIN_COUNT_BIT: usize = 64;
|
||||
const HEADER_DURATION: u32 = 1000; // te_long * 2
|
||||
|
||||
/// Decoder states
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum DecoderStep {
|
||||
Reset,
|
||||
CheckPreamble,
|
||||
SaveDuration,
|
||||
CheckDuration,
|
||||
}
|
||||
|
||||
/// Star Line protocol decoder
|
||||
pub struct StarLineDecoder {
|
||||
step: DecoderStep,
|
||||
te_last: u32,
|
||||
header_count: u16,
|
||||
decode_data: u64,
|
||||
decode_count_bit: usize,
|
||||
}
|
||||
|
||||
impl StarLineDecoder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
step: DecoderStep::Reset,
|
||||
te_last: 0,
|
||||
header_count: 0,
|
||||
decode_data: 0,
|
||||
decode_count_bit: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get manufacturer key (placeholder)
|
||||
fn get_mf_key() -> u64 {
|
||||
0x0000000000000000
|
||||
}
|
||||
|
||||
fn parse_data(data: u64) -> DecodedSignal {
|
||||
// Data is stored MSB-first in the air, reverse to get fix|hop
|
||||
let reversed = reverse_key(data, MIN_COUNT_BIT);
|
||||
let key_fix = (reversed >> 32) as u32;
|
||||
let key_hop = (reversed & 0xFFFFFFFF) as u32;
|
||||
|
||||
let serial = key_fix & 0x00FFFFFF;
|
||||
let btn = (key_fix >> 24) as u8;
|
||||
|
||||
// Attempt KeeLoq decryption
|
||||
let mf_key = Self::get_mf_key();
|
||||
let counter = if mf_key != 0 {
|
||||
// Try simple learning first
|
||||
let decrypt = keeloq_decrypt(key_hop, mf_key);
|
||||
let dec_btn = (decrypt >> 24) as u8;
|
||||
let dec_serial_lsb = ((decrypt >> 16) & 0xFF) as u8;
|
||||
let serial_lsb = (serial & 0xFF) as u8;
|
||||
|
||||
if dec_btn == btn && dec_serial_lsb == serial_lsb {
|
||||
Some((decrypt & 0xFFFF) as u16)
|
||||
} else {
|
||||
// Try normal learning
|
||||
let man_key = keeloq_normal_learning(key_fix, mf_key);
|
||||
let decrypt = keeloq_decrypt(key_hop, man_key);
|
||||
let dec_btn = (decrypt >> 24) as u8;
|
||||
let dec_serial_lsb = ((decrypt >> 16) & 0xFF) as u8;
|
||||
|
||||
if dec_btn == btn && dec_serial_lsb == serial_lsb {
|
||||
Some((decrypt & 0xFFFF) as u16)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
DecodedSignal {
|
||||
serial: Some(serial),
|
||||
button: Some(btn),
|
||||
counter: counter.or(Some(0)),
|
||||
crc_valid: counter.is_some() || mf_key == 0,
|
||||
data,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolDecoder for StarLineDecoder {
|
||||
fn name(&self) -> &'static str {
|
||||
"Star Line"
|
||||
}
|
||||
|
||||
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] {
|
||||
&[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;
|
||||
}
|
||||
|
||||
fn feed(&mut self, level: bool, duration: u32) -> Option<DecodedSignal> {
|
||||
match self.step {
|
||||
DecoderStep::Reset => {
|
||||
if level {
|
||||
if duration_diff!(duration, HEADER_DURATION) < TE_DELTA * 2 {
|
||||
self.step = DecoderStep::CheckPreamble;
|
||||
self.header_count += 1;
|
||||
} else if self.header_count > 4 {
|
||||
self.decode_data = 0;
|
||||
self.decode_count_bit = 0;
|
||||
self.te_last = duration;
|
||||
self.step = DecoderStep::CheckDuration;
|
||||
}
|
||||
} else {
|
||||
self.header_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::CheckPreamble => {
|
||||
if !level && duration_diff!(duration, HEADER_DURATION) < TE_DELTA * 2 {
|
||||
// Found preamble pair
|
||||
self.step = DecoderStep::Reset;
|
||||
} else {
|
||||
self.header_count = 0;
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::SaveDuration => {
|
||||
if level {
|
||||
if duration >= (TE_LONG + TE_DELTA) {
|
||||
// End of data - check if we have enough bits
|
||||
self.step = DecoderStep::Reset;
|
||||
if self.decode_count_bit >= MIN_COUNT_BIT
|
||||
&& self.decode_count_bit <= MIN_COUNT_BIT + 2
|
||||
{
|
||||
let result = Self::parse_data(self.decode_data);
|
||||
self.decode_data = 0;
|
||||
self.decode_count_bit = 0;
|
||||
self.header_count = 0;
|
||||
return Some(result);
|
||||
}
|
||||
self.decode_data = 0;
|
||||
self.decode_count_bit = 0;
|
||||
self.header_count = 0;
|
||||
} else {
|
||||
self.te_last = duration;
|
||||
self.step = DecoderStep::CheckDuration;
|
||||
}
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::CheckDuration => {
|
||||
if !level {
|
||||
if duration_diff!(self.te_last, TE_SHORT) < TE_DELTA
|
||||
&& duration_diff!(duration, TE_SHORT) < TE_DELTA
|
||||
{
|
||||
// Bit 0: short HIGH + short LOW
|
||||
if self.decode_count_bit < MIN_COUNT_BIT {
|
||||
self.decode_data = (self.decode_data << 1) | 0;
|
||||
self.decode_count_bit += 1;
|
||||
} else {
|
||||
self.decode_count_bit += 1;
|
||||
}
|
||||
self.step = DecoderStep::SaveDuration;
|
||||
} else if duration_diff!(self.te_last, TE_LONG) < TE_DELTA
|
||||
&& duration_diff!(duration, TE_LONG) < TE_DELTA
|
||||
{
|
||||
// Bit 1: long HIGH + long LOW
|
||||
if self.decode_count_bit < MIN_COUNT_BIT {
|
||||
self.decode_data = (self.decode_data << 1) | 1;
|
||||
self.decode_count_bit += 1;
|
||||
} else {
|
||||
self.decode_count_bit += 1;
|
||||
}
|
||||
self.step = DecoderStep::SaveDuration;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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).wrapping_add(1);
|
||||
|
||||
let fix = ((button as u32) << 24) | (serial & 0x00FFFFFF);
|
||||
let plaintext = ((button as u32) << 24)
|
||||
| (((serial & 0xFF) as u32) << 16)
|
||||
| (counter as u32);
|
||||
|
||||
let mf_key = Self::get_mf_key();
|
||||
let hop = if mf_key != 0 {
|
||||
keeloq_encrypt(plaintext, mf_key)
|
||||
} else {
|
||||
// Without a key, replay the original hop
|
||||
let reversed = reverse_key(decoded.data, MIN_COUNT_BIT);
|
||||
(reversed & 0xFFFFFFFF) as u32
|
||||
};
|
||||
|
||||
let yek = ((fix as u64) << 32) | (hop as u64);
|
||||
let data = reverse_key(yek, MIN_COUNT_BIT);
|
||||
|
||||
let mut signal = Vec::with_capacity(256);
|
||||
|
||||
// Header: 6 pairs of LONG*2 HIGH + LONG*2 LOW
|
||||
for _ in 0..6 {
|
||||
signal.push(LevelDuration::new(true, HEADER_DURATION));
|
||||
signal.push(LevelDuration::new(false, HEADER_DURATION));
|
||||
}
|
||||
|
||||
// Data: 64 bits, MSB first
|
||||
for bit in (0..64).rev() {
|
||||
if (data >> bit) & 1 == 1 {
|
||||
// Bit 1: LONG HIGH + LONG LOW
|
||||
signal.push(LevelDuration::new(true, TE_LONG));
|
||||
signal.push(LevelDuration::new(false, TE_LONG));
|
||||
} else {
|
||||
// Bit 0: SHORT HIGH + SHORT LOW
|
||||
signal.push(LevelDuration::new(true, TE_SHORT));
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
}
|
||||
}
|
||||
|
||||
Some(signal)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
//! Subaru protocol decoder
|
||||
//!
|
||||
//! Ported from protopirate's subaru.c
|
||||
//!
|
||||
//! Protocol characteristics:
|
||||
//! - PWM encoding: short HIGH (800µs) = 1, long HIGH (1600µs) = 0
|
||||
//! - 64 bits total
|
||||
//! - Long preamble of 1600µs pulses
|
||||
//! - Gap and sync pattern
|
||||
//! - Complex counter encoding
|
||||
|
||||
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;
|
||||
#[allow(dead_code)]
|
||||
const MIN_COUNT_BIT: usize = 64;
|
||||
|
||||
const GAP_US: u32 = 2800;
|
||||
const SYNC_US: u32 = 2800;
|
||||
|
||||
/// Decoder states
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum DecoderStep {
|
||||
Reset,
|
||||
CheckPreamble,
|
||||
FoundGap,
|
||||
FoundSync,
|
||||
SaveDuration,
|
||||
CheckDuration,
|
||||
}
|
||||
|
||||
/// Subaru protocol decoder
|
||||
pub struct SubaruDecoder {
|
||||
step: DecoderStep,
|
||||
te_last: u32,
|
||||
header_count: u16,
|
||||
data: [u8; 8],
|
||||
bit_count: usize,
|
||||
}
|
||||
|
||||
impl SubaruDecoder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
step: DecoderStep::Reset,
|
||||
te_last: 0,
|
||||
header_count: 0,
|
||||
data: [0u8; 8],
|
||||
bit_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a bit to the data buffer
|
||||
fn add_bit(&mut self, bit: bool) {
|
||||
if self.bit_count < 64 {
|
||||
let byte_idx = self.bit_count / 8;
|
||||
let bit_idx = 7 - (self.bit_count % 8);
|
||||
if bit {
|
||||
self.data[byte_idx] |= 1 << bit_idx;
|
||||
} else {
|
||||
self.data[byte_idx] &= !(1 << bit_idx);
|
||||
}
|
||||
self.bit_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Decode the counter from the complex Subaru encoding
|
||||
fn decode_counter(kb: &[u8; 8]) -> u16 {
|
||||
let mut lo: u8 = 0;
|
||||
if (kb[4] & 0x40) == 0 { lo |= 0x01; }
|
||||
if (kb[4] & 0x80) == 0 { lo |= 0x02; }
|
||||
if (kb[5] & 0x01) == 0 { lo |= 0x04; }
|
||||
if (kb[5] & 0x02) == 0 { lo |= 0x08; }
|
||||
if (kb[6] & 0x01) == 0 { lo |= 0x10; }
|
||||
if (kb[6] & 0x02) == 0 { lo |= 0x20; }
|
||||
if (kb[5] & 0x40) == 0 { lo |= 0x40; }
|
||||
if (kb[5] & 0x80) == 0 { lo |= 0x80; }
|
||||
|
||||
let mut reg_sh1 = (kb[7] << 4) & 0xF0;
|
||||
if kb[5] & 0x04 != 0 { reg_sh1 |= 0x04; }
|
||||
if kb[5] & 0x08 != 0 { reg_sh1 |= 0x08; }
|
||||
if kb[6] & 0x80 != 0 { reg_sh1 |= 0x02; }
|
||||
if kb[6] & 0x40 != 0 { reg_sh1 |= 0x01; }
|
||||
|
||||
let reg_sh2 = ((kb[6] << 2) & 0xF0) | ((kb[7] >> 4) & 0x0F);
|
||||
|
||||
let mut ser0 = kb[3];
|
||||
let mut ser1 = kb[1];
|
||||
let mut ser2 = kb[2];
|
||||
|
||||
let total_rot = 4 + lo;
|
||||
for _ in 0..total_rot {
|
||||
let t_bit = (ser0 >> 7) & 1;
|
||||
ser0 = ((ser0 << 1) & 0xFE) | ((ser1 >> 7) & 1);
|
||||
ser1 = ((ser1 << 1) & 0xFE) | ((ser2 >> 7) & 1);
|
||||
ser2 = ((ser2 << 1) & 0xFE) | t_bit;
|
||||
}
|
||||
|
||||
let t1 = ser1 ^ reg_sh1;
|
||||
let t2 = ser2 ^ reg_sh2;
|
||||
|
||||
let mut hi: u8 = 0;
|
||||
if (t1 & 0x10) == 0 { hi |= 0x04; }
|
||||
if (t1 & 0x20) == 0 { hi |= 0x08; }
|
||||
if (t2 & 0x80) == 0 { hi |= 0x02; }
|
||||
if (t2 & 0x40) == 0 { hi |= 0x01; }
|
||||
if (t1 & 0x01) == 0 { hi |= 0x40; }
|
||||
if (t1 & 0x02) == 0 { hi |= 0x80; }
|
||||
if (t2 & 0x08) == 0 { hi |= 0x20; }
|
||||
if (t2 & 0x04) == 0 { hi |= 0x10; }
|
||||
|
||||
((hi as u16) << 8) | (lo as u16)
|
||||
}
|
||||
|
||||
/// Process the decoded data
|
||||
fn process_data(&self) -> Option<DecodedSignal> {
|
||||
if self.bit_count < 64 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let b = &self.data;
|
||||
|
||||
// Build 64-bit key
|
||||
let key = ((b[0] as u64) << 56) | ((b[1] as u64) << 48) |
|
||||
((b[2] as u64) << 40) | ((b[3] as u64) << 32) |
|
||||
((b[4] as u64) << 24) | ((b[5] as u64) << 16) |
|
||||
((b[6] as u64) << 8) | (b[7] as u64);
|
||||
|
||||
let serial = ((b[1] as u32) << 16) | ((b[2] as u32) << 8) | (b[3] as u32);
|
||||
let button = b[0] & 0x0F;
|
||||
let counter = Self::decode_counter(&self.data);
|
||||
|
||||
Some(DecodedSignal {
|
||||
serial: Some(serial),
|
||||
button: Some(button),
|
||||
counter: Some(counter),
|
||||
crc_valid: true, // Subaru doesn't use CRC
|
||||
data: key,
|
||||
data_count_bit: 64,
|
||||
encoder_capable: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolDecoder for SubaruDecoder {
|
||||
fn name(&self) -> &'static str {
|
||||
"Subaru"
|
||||
}
|
||||
|
||||
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] {
|
||||
&[433_920_000]
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.step = DecoderStep::Reset;
|
||||
self.te_last = 0;
|
||||
self.header_count = 0;
|
||||
self.data = [0u8; 8];
|
||||
self.bit_count = 0;
|
||||
}
|
||||
|
||||
fn feed(&mut self, level: bool, duration: u32) -> Option<DecodedSignal> {
|
||||
match self.step {
|
||||
DecoderStep::Reset => {
|
||||
if level && duration_diff!(duration, TE_LONG) < TE_DELTA {
|
||||
self.step = DecoderStep::CheckPreamble;
|
||||
self.te_last = duration;
|
||||
self.header_count = 1;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::CheckPreamble => {
|
||||
if !level {
|
||||
if duration_diff!(duration, TE_LONG) < TE_DELTA {
|
||||
self.header_count += 1;
|
||||
} else if duration > 2000 && duration < 3500 {
|
||||
// Gap detected
|
||||
if self.header_count > 20 {
|
||||
self.step = DecoderStep::FoundGap;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
} else {
|
||||
if duration_diff!(duration, TE_LONG) < TE_DELTA {
|
||||
self.te_last = duration;
|
||||
self.header_count += 1;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::FoundGap => {
|
||||
if level && duration > 2000 && duration < 3500 {
|
||||
self.step = DecoderStep::FoundSync;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::FoundSync => {
|
||||
if !level && duration_diff!(duration, TE_LONG) < TE_DELTA {
|
||||
self.step = DecoderStep::SaveDuration;
|
||||
self.bit_count = 0;
|
||||
self.data = [0u8; 8];
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::SaveDuration => {
|
||||
if level {
|
||||
if duration_diff!(duration, TE_SHORT) < TE_DELTA {
|
||||
// Short HIGH = bit 1
|
||||
self.add_bit(true);
|
||||
self.te_last = duration;
|
||||
self.step = DecoderStep::CheckDuration;
|
||||
} else if duration_diff!(duration, TE_LONG) < TE_DELTA {
|
||||
// Long HIGH = bit 0
|
||||
self.add_bit(false);
|
||||
self.te_last = duration;
|
||||
self.step = DecoderStep::CheckDuration;
|
||||
} else if duration > 3000 {
|
||||
// End of transmission
|
||||
if self.bit_count >= 64 {
|
||||
let result = self.process_data();
|
||||
self.step = DecoderStep::Reset;
|
||||
return result;
|
||||
}
|
||||
self.step = DecoderStep::Reset;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::CheckDuration => {
|
||||
if !level {
|
||||
if duration_diff!(duration, TE_SHORT) < TE_DELTA ||
|
||||
duration_diff!(duration, TE_LONG) < TE_DELTA {
|
||||
self.step = DecoderStep::SaveDuration;
|
||||
} else if duration > 3000 {
|
||||
// Gap - end of packet
|
||||
if self.bit_count >= 64 {
|
||||
let result = self.process_data();
|
||||
self.step = DecoderStep::Reset;
|
||||
return result;
|
||||
}
|
||||
self.step = DecoderStep::Reset;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn supports_encoding(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn encode(&self, decoded: &DecodedSignal, _button: u8) -> Option<Vec<LevelDuration>> {
|
||||
let key = decoded.data;
|
||||
let mut signal = Vec::with_capacity(512);
|
||||
|
||||
// Generate 3 bursts
|
||||
for burst in 0..3 {
|
||||
if burst > 0 {
|
||||
signal.push(LevelDuration::new(false, 25000));
|
||||
}
|
||||
|
||||
// Preamble: 80 long HIGH/LOW pairs
|
||||
for _ in 0..80 {
|
||||
signal.push(LevelDuration::new(true, TE_LONG));
|
||||
signal.push(LevelDuration::new(false, TE_LONG));
|
||||
}
|
||||
|
||||
// Gap
|
||||
signal.push(LevelDuration::new(false, GAP_US));
|
||||
|
||||
// Sync
|
||||
signal.push(LevelDuration::new(true, SYNC_US));
|
||||
signal.push(LevelDuration::new(false, TE_LONG));
|
||||
|
||||
// Data: 64 bits (MSB first)
|
||||
// Short HIGH = 1, Long HIGH = 0
|
||||
for bit in (0..64).rev() {
|
||||
if (key >> bit) & 1 == 1 {
|
||||
signal.push(LevelDuration::new(true, TE_SHORT));
|
||||
} else {
|
||||
signal.push(LevelDuration::new(true, TE_LONG));
|
||||
}
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
}
|
||||
|
||||
// End marker
|
||||
signal.push(LevelDuration::new(false, TE_LONG * 2));
|
||||
}
|
||||
|
||||
Some(signal)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
//! Suzuki protocol decoder/encoder
|
||||
//!
|
||||
//! Ported from protopirate's suzuki.c
|
||||
//!
|
||||
//! Protocol characteristics:
|
||||
//! - PWM encoding: 250/500µs timing
|
||||
//! - 64 bits total
|
||||
//! - 350 preamble pairs
|
||||
//! - 2000µs gap between transmissions
|
||||
|
||||
use super::{DecodedSignal, ProtocolDecoder, ProtocolTiming};
|
||||
use crate::duration_diff;
|
||||
use crate::radio::demodulator::LevelDuration;
|
||||
|
||||
const TE_SHORT: u32 = 250;
|
||||
const TE_LONG: u32 = 500;
|
||||
const TE_DELTA: u32 = 99;
|
||||
const MIN_COUNT_BIT: usize = 64;
|
||||
const PREAMBLE_COUNT: u16 = 350;
|
||||
const GAP_TIME: u32 = 2000;
|
||||
const GAP_DELTA: u32 = 399;
|
||||
|
||||
/// Decoder states
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum DecoderStep {
|
||||
Reset,
|
||||
CountPreamble,
|
||||
DecodeData,
|
||||
}
|
||||
|
||||
/// Suzuki protocol decoder
|
||||
pub struct SuzukiDecoder {
|
||||
step: DecoderStep,
|
||||
header_count: u16,
|
||||
decode_data: u64,
|
||||
decode_count_bit: usize,
|
||||
te_last: u32,
|
||||
}
|
||||
|
||||
impl SuzukiDecoder {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
step: DecoderStep::Reset,
|
||||
header_count: 0,
|
||||
decode_data: 0,
|
||||
decode_count_bit: 0,
|
||||
te_last: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn add_bit(&mut self, bit: u8) {
|
||||
self.decode_data = (self.decode_data << 1) | (bit as u64);
|
||||
self.decode_count_bit += 1;
|
||||
}
|
||||
|
||||
fn parse_data(data: u64) -> DecodedSignal {
|
||||
let data_high = (data >> 32) as u32;
|
||||
let data_low = data as u32;
|
||||
|
||||
let serial = ((data_high & 0xFFF) << 16) | (data_low >> 16);
|
||||
let btn = ((data_low >> 12) & 0xF) as u8;
|
||||
let cnt = ((data_high << 4) >> 16) as u16;
|
||||
|
||||
DecodedSignal {
|
||||
serial: Some(serial),
|
||||
button: Some(btn),
|
||||
counter: Some(cnt),
|
||||
crc_valid: true, // CRC checked via structure
|
||||
data,
|
||||
data_count_bit: MIN_COUNT_BIT,
|
||||
encoder_capable: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProtocolDecoder for SuzukiDecoder {
|
||||
fn name(&self) -> &'static str {
|
||||
"Suzuki"
|
||||
}
|
||||
|
||||
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] {
|
||||
&[433_920_000]
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.step = DecoderStep::Reset;
|
||||
self.header_count = 0;
|
||||
self.decode_data = 0;
|
||||
self.decode_count_bit = 0;
|
||||
self.te_last = 0;
|
||||
}
|
||||
|
||||
fn feed(&mut self, level: bool, duration: u32) -> Option<DecodedSignal> {
|
||||
match self.step {
|
||||
DecoderStep::Reset => {
|
||||
if !level {
|
||||
return None;
|
||||
}
|
||||
if duration_diff!(duration, TE_SHORT) > TE_DELTA {
|
||||
return None;
|
||||
}
|
||||
self.decode_data = 0;
|
||||
self.decode_count_bit = 0;
|
||||
self.step = DecoderStep::CountPreamble;
|
||||
self.header_count = 0;
|
||||
}
|
||||
|
||||
DecoderStep::CountPreamble => {
|
||||
if level {
|
||||
// HIGH pulse
|
||||
if self.header_count >= 300 {
|
||||
if duration_diff!(duration, TE_LONG) <= TE_DELTA {
|
||||
self.step = DecoderStep::DecodeData;
|
||||
self.add_bit(1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// LOW pulse
|
||||
if duration_diff!(duration, TE_SHORT) <= TE_DELTA {
|
||||
self.te_last = duration;
|
||||
self.header_count += 1;
|
||||
} else {
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DecoderStep::DecodeData => {
|
||||
if level {
|
||||
// HIGH pulse - determines bit value
|
||||
let diff_long = duration_diff!(duration, TE_LONG);
|
||||
let diff_short = duration_diff!(duration, TE_SHORT);
|
||||
|
||||
if diff_long <= TE_DELTA {
|
||||
self.add_bit(1);
|
||||
} else if diff_short <= TE_DELTA {
|
||||
self.add_bit(0);
|
||||
}
|
||||
} else {
|
||||
// LOW pulse - check for gap (end of transmission)
|
||||
let diff_gap = duration_diff!(duration, GAP_TIME);
|
||||
|
||||
if diff_gap <= GAP_DELTA {
|
||||
if self.decode_count_bit == MIN_COUNT_BIT {
|
||||
let result = Self::parse_data(self.decode_data);
|
||||
self.decode_data = 0;
|
||||
self.decode_count_bit = 0;
|
||||
self.step = DecoderStep::Reset;
|
||||
return Some(result);
|
||||
}
|
||||
|
||||
self.decode_data = 0;
|
||||
self.decode_count_bit = 0;
|
||||
self.step = DecoderStep::Reset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn supports_encoding(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn encode(&self, decoded: &DecodedSignal, _button: u8) -> Option<Vec<LevelDuration>> {
|
||||
let data = decoded.data;
|
||||
|
||||
let mut signal = Vec::with_capacity(1024);
|
||||
|
||||
// Preamble: SHORT HIGH / SHORT LOW pairs
|
||||
for _ in 0..PREAMBLE_COUNT {
|
||||
signal.push(LevelDuration::new(true, TE_SHORT));
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
}
|
||||
|
||||
// Data: 64 bits, MSB first
|
||||
// SHORT HIGH (~250µs) = 0, LONG HIGH (~500µs) = 1
|
||||
for bit in (0..64).rev() {
|
||||
if (data >> bit) & 1 == 1 {
|
||||
signal.push(LevelDuration::new(true, TE_LONG));
|
||||
} else {
|
||||
signal.push(LevelDuration::new(true, TE_SHORT));
|
||||
}
|
||||
signal.push(LevelDuration::new(false, TE_SHORT));
|
||||
}
|
||||
|
||||
// End gap
|
||||
signal.push(LevelDuration::new(false, GAP_TIME));
|
||||
|
||||
Some(signal)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user