protocol updates

This commit is contained in:
leviathan
2026-02-07 18:53:53 -05:00
parent 4339895b41
commit 284bf45198
9 changed files with 793 additions and 227 deletions
Generated
+1 -1
View File
@@ -402,7 +402,7 @@ dependencies = [
[[package]]
name = "kat"
version = "0.2.0"
version = "1.0.0"
dependencies = [
"anyhow",
"atty",
+6
View File
@@ -227,6 +227,12 @@ impl App {
/// Create a new application instance
pub fn new() -> Result<Self> {
let storage = Storage::new()?;
// ── Load protocol encryption keys from keystore ──────────────────
let keystore_dir = storage.keystore_dir();
crate::protocols::keys::create_default_keystore(&keystore_dir);
crate::protocols::keys::load_keystore_from_dir(&keystore_dir);
let protocols = ProtocolRegistry::new();
let (radio_event_tx, radio_event_rx) = mpsc::channel();
+538 -195
View File
@@ -1,11 +1,13 @@
//! Ford V0 protocol decoder
//! Ford V0 protocol decoder/encoder
//!
//! Ported from protopirate's ford_v0.c
//!
//! Protocol characteristics:
//! - Manchester encoding: 250/500µs timing
//! - 64 bits total
//! - Matrix-based CRC
//! - 80 bits total (64-bit key1 + 16-bit key2)
//! - Matrix-based CRC in GF(2)
//! - BS (byte swap) magic calculation
//! - 6 bursts, 4 preamble pairs, 3500µs gap
use super::{ProtocolDecoder, ProtocolTiming, DecodedSignal};
use crate::radio::demodulator::LevelDuration;
@@ -15,131 +17,474 @@ const TE_SHORT: u32 = 250;
const TE_LONG: u32 = 500;
const TE_DELTA: u32 = 100;
const MIN_COUNT_BIT: usize = 64;
const TOTAL_BURSTS: u8 = 6;
const PREAMBLE_PAIRS: usize = 4;
const GAP_US: u32 = 3500;
/// Manchester decoder states
// CRC matrix for Ford V0 — GF(2) matrix multiplication
// Copied directly from protopirate's ford_v0.c
const CRC_MATRIX: [u8; 64] = [
0xDA, 0xB5, 0x55, 0x6A, 0xAA, 0xAA, 0xAA, 0xD5,
0xB6, 0x6C, 0xCC, 0xD9, 0x99, 0x99, 0x99, 0xB3,
0x71, 0xE3, 0xC3, 0xC7, 0x87, 0x87, 0x87, 0x8F,
0x0F, 0xE0, 0x3F, 0xC0, 0x7F, 0x80, 0x7F, 0x80,
0x00, 0x1F, 0xFF, 0xC0, 0x00, 0x7F, 0xFF, 0x80,
0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0x80,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F,
0x23, 0x12, 0x94, 0x84, 0x35, 0xF4, 0x55, 0x84,
];
/// Manchester state machine states (matches Flipper's manchester_decoder.h)
#[derive(Debug, Clone, Copy, PartialEq)]
enum ManchesterState {
Mid0,
Mid1,
Start0,
Start1,
Mid0 = 0,
Mid1 = 1,
Start0 = 2,
Start1 = 3,
}
/// Decoder states
/// Decoder step states (matches protopirate's FordV0DecoderStep)
#[derive(Debug, Clone, Copy, PartialEq)]
enum DecoderStep {
Reset,
CheckPreamble,
SaveDuration,
CheckDuration,
Preamble,
PreambleCheck,
Gap,
Data,
}
/// 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,
decode_data: u64,
bit_count: u8,
header_count: u16,
te_last: u32,
// Decoded results (stored for encoding)
key1: u64,
key2: u16,
serial: u32,
button: u8,
counter: u32,
bs_magic: u8,
}
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,
decode_data: 0,
bit_count: 0,
header_count: 0,
te_last: 0,
key1: 0,
key2: 0,
serial: 0,
button: 0,
counter: 0,
bs_magic: 0,
}
}
/// 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
};
/// Add a bit to the accumulator.
/// After 64 bits, key1 is extracted and the accumulator resets for key2.
fn add_bit(&mut self, bit: bool) {
self.decode_data = (self.decode_data << 1) | (bit as u64);
self.bit_count += 1;
}
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),
/// Check if we've reached a data milestone (64 or 80 bits).
/// At 64 bits: extract key1 (inverted) and reset accumulator.
/// At 80 bits: extract key2 (inverted), decode fields, return true.
fn process_data(&mut self) -> bool {
if self.bit_count == 64 {
// First 64 bits → key1 (bit-inverted)
self.key1 = !self.decode_data;
self.decode_data = 0;
return false;
}
// From Start1
(ManchesterState::Start1, 1) => (ManchesterState::Mid1, Some(true)),
(ManchesterState::Start1, 3) => (ManchesterState::Start0, Some(true)),
if self.bit_count == 80 {
// Next 16 bits → key2 (bit-inverted)
let key2_raw = (self.decode_data & 0xFFFF) as u16;
self.key2 = !key2_raw;
// From Start0
(ManchesterState::Start0, 0) => (ManchesterState::Mid0, Some(false)),
(ManchesterState::Start0, 2) => (ManchesterState::Start1, Some(false)),
// Decode serial, button, counter, bs_magic from key1+key2
let (serial, button, count, bs_magic) =
Self::decode_ford_v0(self.key1, self.key2);
self.serial = serial;
self.button = button;
self.counter = count;
self.bs_magic = bs_magic;
return true;
}
// Reset on invalid transitions
_ => (ManchesterState::Mid1, None),
false
}
/// Manchester state machine (matches Flipper's manchester_advance exactly).
///
/// Event values:
/// 0 = ShortLow (short HIGH pulse → transition to LOW)
/// 1 = ShortHigh (short LOW pulse → transition to HIGH)
/// 2 = LongLow (long HIGH pulse → transition to LOW)
/// 3 = LongHigh (long LOW pulse → transition to HIGH)
///
/// Returns Some(bit) when a data bit is produced.
fn manchester_advance(&mut self, event: u8) -> Option<bool> {
let (new_state, emit) = match (self.manchester_state, event) {
// State Mid0: currently in middle of a 0-bit (signal is LOW)
(ManchesterState::Mid0, 0) => (ManchesterState::Mid0, false), // ShortLow: error, stay
(ManchesterState::Mid0, 1) => (ManchesterState::Start1, true), // ShortHigh: emit
(ManchesterState::Mid0, 2) => (ManchesterState::Mid0, false), // LongLow: error
(ManchesterState::Mid0, 3) => (ManchesterState::Mid1, true), // LongHigh: emit
// State Mid1: currently in middle of a 1-bit (signal is HIGH)
(ManchesterState::Mid1, 0) => (ManchesterState::Start0, true), // ShortLow: emit
(ManchesterState::Mid1, 1) => (ManchesterState::Mid1, false), // ShortHigh: error, stay
(ManchesterState::Mid1, 2) => (ManchesterState::Mid0, true), // LongLow: emit
(ManchesterState::Mid1, 3) => (ManchesterState::Mid1, false), // LongHigh: error
// State Start0: at start of a 0-bit (signal is HIGH, waiting for H→L)
(ManchesterState::Start0, 0) => (ManchesterState::Mid0, false), // ShortLow: complete 0
(ManchesterState::Start0, 1) => (ManchesterState::Mid0, false), // error → reset
(ManchesterState::Start0, 2) => (ManchesterState::Mid0, false), // error
(ManchesterState::Start0, 3) => (ManchesterState::Mid1, false), // error
// State Start1: at start of a 1-bit (signal is LOW, waiting for L→H)
(ManchesterState::Start1, 0) => (ManchesterState::Mid0, false), // error
(ManchesterState::Start1, 1) => (ManchesterState::Mid1, false), // ShortHigh: complete 1
(ManchesterState::Start1, 2) => (ManchesterState::Mid0, false), // error
(ManchesterState::Start1, 3) => (ManchesterState::Mid1, false), // error
_ => (ManchesterState::Mid1, false),
};
self.manchester_state = new_state;
output
if emit {
// Bit value: 1 for High events (1,3), 0 for Low events (0,2)
Some((event & 1) == 1)
} else {
None
}
}
/// 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],
];
// =========================================================================
// CRC functions
// =========================================================================
/// Calculate Ford CRC
fn calculate_crc(data: u64) -> u8 {
/// Population count for a byte
fn popcount8(mut x: u8) -> u8 {
let mut count = 0u8;
while x != 0 {
count += x & 1;
x >>= 1;
}
count
}
/// Calculate CRC using GF(2) matrix multiplication.
/// buf must have at least 9 bytes; CRC is computed over buf[1..=8].
fn calculate_crc(buf: &[u8]) -> 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];
}
for row in 0..8 {
let mut xor_sum = 0u8;
for col in 0..8 {
xor_sum ^= CRC_MATRIX[row * 8 + col] & buf[col + 1];
}
let parity = Self::popcount8(xor_sum) & 1;
if parity != 0 {
crc |= 1 << row;
}
}
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
/// Calculate CRC for transmission (key1 bytes + BS byte, XOR 0x80)
fn calculate_crc_for_tx(key1: u64, bs: u8) -> u8 {
let mut buf = [0u8; 16];
for i in 0..8 {
buf[i] = (key1 >> (56 - i * 8)) as u8;
}
buf[8] = bs;
Self::calculate_crc(&buf) ^ 0x80
}
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);
/// Verify CRC of received key1 + key2
fn verify_crc(key1: u64, key2: u16) -> bool {
let mut buf = [0u8; 16];
for i in 0..8 {
buf[i] = (key1 >> (56 - i * 8)) as u8;
}
buf[8] = (key2 >> 8) as u8; // BS byte
let calculated_crc = Self::calculate_crc(&buf);
let received_crc = (key2 as u8) ^ 0x80;
calculated_crc == received_crc
}
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,
// =========================================================================
// BS calculation
// =========================================================================
/// Calculate BS = (count_low_byte + bs_magic + (button << 4)) with overflow handling
fn calculate_bs(count: u32, button: u8, bs_magic: u8) -> u8 {
let result: u16 = (count as u16 & 0xFF)
.wrapping_add(bs_magic as u16)
.wrapping_add((button as u16) << 4);
(result as u8).wrapping_sub(if result & 0xFF00 != 0 { 0x80 } else { 0 })
}
// =========================================================================
// Decode function — extract serial/button/counter/bs_magic from key1+key2
// Matches protopirate's decode_ford_v0() exactly
// =========================================================================
fn decode_ford_v0(key1: u64, key2: u16) -> (u32, u8, u32, u8) {
let mut buf = [0u8; 13];
// Extract key1 bytes (big-endian)
for i in 0..8 {
buf[i] = (key1 >> (56 - i * 8)) as u8;
}
// Extract key2 bytes
buf[8] = (key2 >> 8) as u8;
buf[9] = key2 as u8;
// BS parity calculation
let bs = buf[8];
let mut tmp = bs;
let mut parity = 0u8;
let parity_any: u8 = if tmp != 0 { 1 } else { 0 };
while tmp != 0 {
parity ^= tmp & 1;
tmp >>= 1;
}
buf[11] = if parity_any != 0 { parity } else { 0 };
// XOR decryption based on parity bit
let (xor_byte, limit) = if buf[11] != 0 {
(buf[7], 7usize)
} else {
(buf[6], 6usize)
};
for idx in 1..limit {
buf[idx] ^= xor_byte;
}
if buf[11] == 0 {
buf[7] ^= xor_byte;
}
// Bit-interleave swap of buf[6] and buf[7]
let orig_b7 = buf[7];
buf[7] = (orig_b7 & 0xAA) | (buf[6] & 0x55);
let mixed = (buf[6] & 0xAA) | (orig_b7 & 0x55);
buf[12] = mixed;
buf[6] = mixed;
// Extract serial (stored little-endian in buf[1..5], convert to big-endian)
let serial_le = (buf[1] as u32)
| ((buf[2] as u32) << 8)
| ((buf[3] as u32) << 16)
| ((buf[4] as u32) << 24);
let serial = ((serial_le & 0xFF) << 24)
| (((serial_le >> 8) & 0xFF) << 16)
| (((serial_le >> 16) & 0xFF) << 8)
| ((serial_le >> 24) & 0xFF);
// Extract button (high nibble of buf[5])
let button = (buf[5] >> 4) & 0x0F;
// Extract counter (20-bit)
let count = ((buf[5] as u32 & 0x0F) << 16) | ((buf[6] as u32) << 8) | (buf[7] as u32);
// Calculate BS magic number for this fob
let bs_magic = bs
.wrapping_add(if bs & 0x80 != 0 { 0x80 } else { 0 })
.wrapping_sub(button << 4)
.wrapping_sub(count as u8);
(serial, button, count, bs_magic)
}
// =========================================================================
// Encode function — rebuild key1 from serial/button/counter/bs
// Matches protopirate's encode_ford_v0() exactly
// =========================================================================
fn encode_ford_v0(
header_byte: u8,
serial: u32,
button: u8,
count: u32,
bs: u8,
) -> u64 {
let mut buf = [0u8; 8];
buf[0] = header_byte;
// Serial in big-endian
buf[1] = (serial >> 24) as u8;
buf[2] = (serial >> 16) as u8;
buf[3] = (serial >> 8) as u8;
buf[4] = serial as u8;
// Button + counter high nibble
buf[5] = ((button & 0x0F) << 4) | ((count >> 16) as u8 & 0x0F);
let count_mid = (count >> 8) as u8;
let count_low = count as u8;
// Bit-interleave: split even/odd bits between the two counter bytes
let post_xor_6 = (count_mid & 0xAA) | (count_low & 0x55);
let post_xor_7 = (count_low & 0xAA) | (count_mid & 0x55);
// Calculate BS parity
let mut parity = 0u8;
let mut tmp = bs;
while tmp != 0 {
parity ^= tmp & 1;
tmp >>= 1;
}
let parity_bit = if bs != 0 { parity != 0 } else { false };
// XOR encryption based on parity (inverse of decode)
if parity_bit {
let xor_byte = post_xor_7;
buf[1] ^= xor_byte;
buf[2] ^= xor_byte;
buf[3] ^= xor_byte;
buf[4] ^= xor_byte;
buf[5] ^= xor_byte;
buf[6] = post_xor_6 ^ xor_byte;
buf[7] = post_xor_7;
} else {
let xor_byte = post_xor_6;
buf[1] ^= xor_byte;
buf[2] ^= xor_byte;
buf[3] ^= xor_byte;
buf[4] ^= xor_byte;
buf[5] ^= xor_byte;
buf[6] = post_xor_6;
buf[7] = post_xor_7 ^ xor_byte;
}
// Pack into u64
let mut key1 = 0u64;
for b in &buf {
key1 = (key1 << 8) | (*b as u64);
}
key1
}
// =========================================================================
// Encoder signal builder — differential Manchester with ADD_LEVEL merging
// Matches protopirate's subghz_protocol_encoder_ford_v0_get_upload()
// =========================================================================
fn build_upload(key1: u64, key2: u16) -> Vec<LevelDuration> {
let mut signal = Vec::with_capacity(1024);
// Transmitted data is bit-inverted
let tx_key1 = !key1;
let tx_key2 = !key2;
for burst in 0..TOTAL_BURSTS {
// Preamble start: short HIGH + long LOW
Self::add_level(&mut signal, true, TE_SHORT);
Self::add_level(&mut signal, false, TE_LONG);
// Preamble pairs: long HIGH + long LOW
for _ in 0..PREAMBLE_PAIRS {
Self::add_level(&mut signal, true, TE_LONG);
Self::add_level(&mut signal, false, TE_LONG);
}
// End preamble: short HIGH + gap LOW
Self::add_level(&mut signal, true, TE_SHORT);
Self::add_level(&mut signal, false, GAP_US);
// First data bit (bit 62 of tx_key1; bit 63 is implicit from gap)
let first_bit = ((tx_key1 >> 62) & 1) == 1;
if first_bit {
Self::add_level(&mut signal, true, TE_LONG);
} else {
Self::add_level(&mut signal, true, TE_SHORT);
Self::add_level(&mut signal, false, TE_LONG);
}
let mut prev_bit = first_bit;
// Encode remaining key1 bits (61 down to 0) — differential Manchester
for bit_pos in (0..62).rev() {
let curr_bit = ((tx_key1 >> bit_pos) & 1) == 1;
Self::encode_diff_bit(&mut signal, prev_bit, curr_bit);
prev_bit = curr_bit;
}
// Encode key2 bits (15 down to 0)
for bit_pos in (0..16).rev() {
let curr_bit = ((tx_key2 >> bit_pos) & 1) == 1;
Self::encode_diff_bit(&mut signal, prev_bit, curr_bit);
prev_bit = curr_bit;
}
// Inter-burst gap (except for last burst)
if burst < TOTAL_BURSTS - 1 {
Self::add_level(&mut signal, false, TE_LONG * 100);
}
}
signal
}
/// Encode one differential Manchester bit transition
fn encode_diff_bit(signal: &mut Vec<LevelDuration>, prev: bool, curr: bool) {
match (prev, curr) {
(false, false) => {
// 0→0: mid-bit transition only (HIGH short, LOW short)
Self::add_level(signal, true, TE_SHORT);
Self::add_level(signal, false, TE_SHORT);
}
(false, true) => {
// 0→1: transition at start (extends to LONG HIGH)
Self::add_level(signal, true, TE_LONG);
}
(true, false) => {
// 1→0: transition at start (extends to LONG LOW)
Self::add_level(signal, false, TE_LONG);
}
(true, true) => {
// 1→1: mid-bit transition only (LOW short, HIGH short)
Self::add_level(signal, false, TE_SHORT);
Self::add_level(signal, true, TE_SHORT);
}
}
}
/// ADD_LEVEL equivalent: merge adjacent same-level pulses for efficiency.
/// This matches protopirate's ADD_LEVEL macro behavior.
fn add_level(signal: &mut Vec<LevelDuration>, level: bool, duration: u32) {
if let Some(last) = signal.last_mut() {
if last.level == level {
*last = LevelDuration::new(level, last.duration_us + duration);
return;
}
}
signal.push(LevelDuration::new(level, duration));
}
/// Get button name for Ford V0
fn button_name(btn: u8) -> &'static str {
match btn {
0x01 => "Lock",
0x02 => "Unlock",
0x04 => "Boot",
_ => "Unknown",
}
}
}
@@ -164,105 +509,116 @@ impl ProtocolDecoder for FordV0Decoder {
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;
self.decode_data = 0;
self.bit_count = 0;
self.header_count = 0;
self.te_last = 0;
self.key1 = 0;
self.key2 = 0;
self.serial = 0;
self.button = 0;
self.counter = 0;
self.bs_magic = 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 {
// ─── Step 1: Reset — wait for short HIGH pulse ───
DecoderStep::Reset => {
if level && is_short {
self.step = DecoderStep::CheckPreamble;
self.header_count = 1;
if level && duration_diff!(duration, TE_SHORT) < TE_DELTA {
self.decode_data = 0;
self.bit_count = 0;
self.header_count = 0;
self.step = DecoderStep::Preamble;
self.te_last = duration;
// Reset Manchester state machine
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;
}
// ─── Step 2: Preamble — wait for long LOW after short HIGH ───
DecoderStep::Preamble => {
if !level {
if duration_diff!(duration, TE_LONG) < TE_DELTA {
self.te_last = duration;
self.step = DecoderStep::PreambleCheck;
} else {
self.step = DecoderStep::Reset;
}
} else {
}
}
// ─── Step 3: PreambleCheck — count preamble pairs or transition to gap ───
DecoderStep::PreambleCheck => {
if level {
if duration_diff!(duration, TE_LONG) < TE_DELTA {
// Long HIGH: another preamble pair
self.header_count += 1;
self.te_last = duration;
self.step = DecoderStep::Preamble;
} else if duration_diff!(duration, TE_SHORT) < TE_DELTA {
// Short HIGH: end of preamble, transition to gap
self.step = DecoderStep::Gap;
} else {
self.step = DecoderStep::Reset;
}
}
}
// ─── Step 4: Gap — wait for ~3500µs LOW gap ───
DecoderStep::Gap => {
if !level && duration_diff!(duration, GAP_US) < 250 {
// Gap detected, start data collection
// First bit is implicitly 1 (matches protopirate)
self.decode_data = 1;
self.bit_count = 1;
self.step = DecoderStep::Data;
} else if !level && duration > GAP_US + 250 {
self.step = DecoderStep::Reset;
}
}
DecoderStep::SaveDuration => {
self.te_last = duration;
self.step = DecoderStep::CheckDuration;
}
// ─── Step 5: Data — Manchester decode 80 bits ───
DecoderStep::Data => {
// Map level+duration to Manchester event
// Flipper convention: level=true (HIGH) → Low event, level=false (LOW) → High event
let event = if duration_diff!(duration, TE_SHORT) < TE_DELTA {
if level { 0 } else { 1 } // ShortLow / ShortHigh
} else if duration_diff!(duration, TE_LONG) < TE_DELTA {
if level { 2 } else { 3 } // LongLow / LongHigh
} else {
self.step = DecoderStep::Reset;
return None;
};
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;
// Advance Manchester state machine
if let Some(data_bit) = self.manchester_advance(event) {
self.add_bit(data_bit);
// 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);
if self.process_data() {
// 80 bits decoded — check CRC and return result
let crc_ok = Self::verify_crc(self.key1, self.key2);
let _btn_name = Self::button_name(self.button);
let result = DecodedSignal {
serial: Some(self.serial),
button: Some(self.button),
counter: Some(self.counter as u16),
crc_valid: crc_ok,
data: self.key1,
data_count_bit: MIN_COUNT_BIT,
encoder_capable: true,
};
self.decode_data = 0;
self.bit_count = 0;
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);
}
self.te_last = duration;
}
}
@@ -275,46 +631,33 @@ impl ProtocolDecoder for FordV0Decoder {
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;
// Use the stored counter from last decode (or from decoded signal)
// Increment counter by 1 for the new transmission
let base_counter = if self.counter != 0 {
self.counter
} else {
decoded.counter.unwrap_or(0) as u32
};
let new_counter = base_counter.wrapping_add(1) & 0xFFFFF; // 20-bit counter
let mut signal = Vec::with_capacity(256);
// Use stored bs_magic (or default to 0x6F for backward compatibility)
let bs_magic = if self.bs_magic != 0 { self.bs_magic } else { 0x6F };
// Preamble
for _ in 0..30 {
signal.push(LevelDuration::new(true, TE_SHORT));
signal.push(LevelDuration::new(false, TE_SHORT));
}
// Calculate BS for the new counter value
let bs = Self::calculate_bs(new_counter, button, bs_magic);
// Sync
signal.push(LevelDuration::new(true, TE_LONG));
signal.push(LevelDuration::new(false, TE_LONG));
// Extract header byte from the original key1 (first byte)
let header_byte = (decoded.data >> 56) as u8;
// 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));
}
}
// Encode new key1 from fields
let new_key1 = Self::encode_ford_v0(header_byte, serial, button, new_counter, bs);
// End
signal.push(LevelDuration::new(false, TE_LONG * 4));
// Calculate CRC for key2
let crc = Self::calculate_crc_for_tx(new_key1, bs);
let new_key2 = ((bs as u16) << 8) | (crc as u16);
Some(signal)
// Build the signal upload
Some(Self::build_upload(new_key1, new_key2))
}
}
+183
View File
@@ -6,9 +6,14 @@
//! - 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)
//! - Star Line: star_line_mf_key (manufacturer key for KeeLoq)
//! - VAG: AUT64 keys loaded from keystore files
//!
//! Keys are loaded from `~/.config/KAT/keystore/keystore.ini` at startup.
//! See [`load_keystore_from_dir`] for the file format.
use super::aut64::{self, Aut64Key, AUT64_KEY_STRUCT_PACKED_SIZE};
use configparser::ini::Ini;
use std::path::Path;
use std::sync::{OnceLock, RwLock};
use tracing::{info, warn, error};
@@ -18,6 +23,7 @@ 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
const STAR_LINE_KEY: u32 = 20; // star_line_mf_key
/// Maximum number of VAG AUT64 keys
const VAG_KEYS_COUNT: usize = 3;
@@ -32,6 +38,8 @@ pub struct KeyStore {
pub kia_v6_b_key: u64,
/// KIA V5 mixer key
pub kia_v5_key: u64,
/// Star Line manufacturer key (for KeeLoq)
pub star_line_mf_key: u64,
/// VAG AUT64 keys
pub vag_keys: Vec<Aut64Key>,
/// Whether VAG keys have been loaded
@@ -45,6 +53,7 @@ impl Default for KeyStore {
kia_v6_a_key: 0,
kia_v6_b_key: 0,
kia_v5_key: 0,
star_line_mf_key: 0,
vag_keys: Vec::new(),
vag_keys_loaded: false,
}
@@ -66,6 +75,7 @@ impl KeyStore {
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,
STAR_LINE_KEY => self.star_line_mf_key = key_value,
_ => {}
}
}
@@ -146,6 +156,11 @@ impl KeyStore {
pub fn get_kia_v5_key(&self) -> u64 {
self.kia_v5_key
}
/// Get the Star Line manufacturer key
pub fn get_star_line_mf_key(&self) -> u64 {
self.star_line_mf_key
}
}
/// Global singleton keystore
@@ -175,3 +190,171 @@ pub fn load_vag_keys(path: &str) {
let mut store = get_keystore_mut();
store.load_vag_keys_from_file(path);
}
// =============================================================================
// Keystore file loading from ~/.config/KAT/keystore/
// =============================================================================
/// Parse a hex string (with or without "0x" prefix) into a u64.
fn parse_hex_u64(s: &str) -> Option<u64> {
let s = s.trim();
let s = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")).unwrap_or(s);
u64::from_str_radix(s, 16).ok()
}
/// Default keystore.ini template content.
/// Written to disk on first run so users know which keys to configure.
const DEFAULT_KEYSTORE_INI: &str = r#"; KAT Keystore — Protocol Encryption Keys
;
; Place your protocol keys here. Key values should be in hexadecimal
; with a 0x prefix (e.g. 0x0123456789ABCDEF).
;
; Keys left at 0x0000000000000000 or omitted are treated as "not loaded"
; and the corresponding protocol will decode without decryption
; (serial/button still visible, but counter may not validate).
;
; This file corresponds to protopirate's keystore/encrypted and keystore/vag
; assets. Since KAT runs on a PC (not a Flipper), keys must be provided
; in plaintext here rather than in the Flipper's encrypted keystore format.
[kia]
; KIA V3/V4: KeeLoq manufacturer key (used for hop-code decryption)
mf_key = 0x0000000000000000
; KIA V5: Custom mixer cipher key
v5_key = 0x0000000000000000
; KIA V6: AES-128 key components (XOR-masked before use)
v6_a_key = 0x0000000000000000
v6_b_key = 0x0000000000000000
[star_line]
; Star Line: KeeLoq manufacturer key (used for hop-code decryption)
mf_key = 0x0000000000000000
[vag]
; VAG: Path to AUT64 binary key file (raw packed keys, 16 bytes each).
; Can be absolute or relative to the keystore directory.
; Leave empty or commented out if you don't have VAG keys.
; keys_file = vag.bin
"#;
/// Write the default keystore.ini template if it doesn't exist yet.
pub fn create_default_keystore(keystore_dir: &Path) {
let ini_path = keystore_dir.join("keystore.ini");
if ini_path.exists() {
return;
}
match std::fs::write(&ini_path, DEFAULT_KEYSTORE_INI) {
Ok(_) => info!("Created default keystore template at {:?}", ini_path),
Err(e) => warn!("Could not create default keystore.ini: {}", e),
}
}
/// Load all keys from the keystore directory.
///
/// Reads `keystore.ini` from the given directory and populates the global
/// [`KeyStore`] with:
///
/// - KIA V3/V4 manufacturer key (`[kia] mf_key`)
/// - KIA V5 mixer key (`[kia] v5_key`)
/// - KIA V6 AES keys (`[kia] v6_a_key`, `[kia] v6_b_key`)
/// - Star Line manufacturer key (`[star_line] mf_key`)
/// - VAG AUT64 keys from binary file (`[vag] keys_file`)
///
/// Keys that are missing, zeroed, or unparseable are silently skipped.
pub fn load_keystore_from_dir(keystore_dir: &Path) {
let ini_path = keystore_dir.join("keystore.ini");
if !ini_path.exists() {
info!("No keystore.ini found at {:?} — keys not loaded", ini_path);
return;
}
let mut ini = Ini::new();
if let Err(e) = ini.load(ini_path.to_string_lossy().as_ref()) {
error!("Failed to parse keystore.ini: {}", e);
return;
}
let mut store = get_keystore_mut();
let mut loaded_count = 0u32;
// ── KIA keys ──────────────────────────────────────────────────────────
if let Some(val) = ini.get("kia", "mf_key").and_then(|s| parse_hex_u64(&s)) {
if val != 0 {
store.kia_mf_key = val;
loaded_count += 1;
info!("Loaded KIA V3/V4 manufacturer key");
}
}
if let Some(val) = ini.get("kia", "v5_key").and_then(|s| parse_hex_u64(&s)) {
if val != 0 {
store.kia_v5_key = val;
loaded_count += 1;
info!("Loaded KIA V5 mixer key");
}
}
if let Some(val) = ini.get("kia", "v6_a_key").and_then(|s| parse_hex_u64(&s)) {
if val != 0 {
store.kia_v6_a_key = val;
loaded_count += 1;
info!("Loaded KIA V6 AES key A");
}
}
if let Some(val) = ini.get("kia", "v6_b_key").and_then(|s| parse_hex_u64(&s)) {
if val != 0 {
store.kia_v6_b_key = val;
loaded_count += 1;
info!("Loaded KIA V6 AES key B");
}
}
// ── Star Line keys ────────────────────────────────────────────────────
if let Some(val) = ini.get("star_line", "mf_key").and_then(|s| parse_hex_u64(&s)) {
if val != 0 {
store.star_line_mf_key = val;
loaded_count += 1;
info!("Loaded Star Line manufacturer key");
}
}
// ── VAG AUT64 keys (binary file) ──────────────────────────────────────
if let Some(vag_file) = ini.get("vag", "keys_file") {
let vag_file = vag_file.trim().to_string();
if !vag_file.is_empty() {
let vag_path = if Path::new(&vag_file).is_absolute() {
std::path::PathBuf::from(&vag_file)
} else {
keystore_dir.join(&vag_file)
};
if vag_path.exists() {
match std::fs::read(&vag_path) {
Ok(data) => {
store.load_vag_keys_from_data(&data);
if store.vag_keys_loaded {
loaded_count += store.vag_keys.len() as u32;
info!("Loaded {} VAG AUT64 keys from {:?}", store.vag_keys.len(), vag_path);
}
}
Err(e) => {
error!("Failed to read VAG key file {:?}: {}", vag_path, e);
}
}
} else {
warn!("VAG key file not found: {:?}", vag_path);
}
}
}
if loaded_count > 0 {
info!("Keystore loaded: {} key(s) from {:?}", loaded_count, keystore_dir);
} else {
info!("Keystore loaded but no non-zero keys found. Edit keystore.ini to add your keys.");
}
}
+2 -2
View File
@@ -10,6 +10,7 @@
//! - V3 and V4 differ only in sync polarity
use super::{ProtocolDecoder, ProtocolTiming, DecodedSignal};
use super::keys;
use crate::radio::demodulator::LevelDuration;
use crate::duration_diff;
@@ -128,8 +129,7 @@ impl KiaV3V4Decoder {
/// 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
keys::get_keystore().get_kia_mf_key()
}
/// Process the collected buffer and validate
+21 -12
View File
@@ -10,6 +10,7 @@
//! - Decode-only (no encoder)
use super::{ProtocolDecoder, ProtocolTiming, DecodedSignal};
use super::keys;
use crate::radio::demodulator::LevelDuration;
use crate::duration_diff;
@@ -59,9 +60,9 @@ impl KiaV5Decoder {
}
}
/// Get encryption key (placeholder)
/// Get encryption key from global keystore
fn get_v5_key() -> u64 {
0x0000000000000000
keys::get_keystore().get_kia_v5_key()
}
/// Custom mixer decryption
@@ -144,25 +145,33 @@ impl KiaV5Decoder {
}
/// Manchester state machine
/// NOTE: V5 uses OPPOSITE Manchester polarity from V1/V2.
/// In protopirate, V1/V2 use: level ? ShortLow : ShortHigh
/// But V5 uses: level ? ShortHigh : ShortLow
/// To achieve this inversion, we swap the is_high mapping but use V1's
/// standard state table, so that only ONE inversion occurs.
fn manchester_advance(&mut self, is_short: bool, is_high: bool) -> Option<bool> {
// Invert polarity: V5 uses opposite Manchester convention from V1/V2
let is_high = !is_high;
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
(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, 1) | (ManchesterState::Mid1, 1) =>
(ManchesterState::Start0, None),
(ManchesterState::Mid0, 0) | (ManchesterState::Mid1, 0) =>
(ManchesterState::Start0, None),
(ManchesterState::Mid0, 1) | (ManchesterState::Mid1, 1) =>
(ManchesterState::Start1, None),
(ManchesterState::Start1, 1) => (ManchesterState::Mid1, Some(true)),
(ManchesterState::Start1, 3) => (ManchesterState::Start0, Some(true)),
(ManchesterState::Start1, 0) => (ManchesterState::Mid1, Some(true)),
(ManchesterState::Start1, 2) => (ManchesterState::Start0, Some(true)),
(ManchesterState::Start0, 0) => (ManchesterState::Mid0, Some(false)),
(ManchesterState::Start0, 2) => (ManchesterState::Start1, Some(false)),
(ManchesterState::Start0, 1) => (ManchesterState::Mid0, Some(false)),
(ManchesterState::Start0, 3) => (ManchesterState::Start1, Some(false)),
_ => (ManchesterState::Mid1, None),
};
+14 -8
View File
@@ -10,6 +10,7 @@
//! - Decode-only (no encoder)
use super::{ProtocolDecoder, ProtocolTiming, DecodedSignal};
use super::keys;
use crate::radio::demodulator::LevelDuration;
use crate::duration_diff;
@@ -118,14 +119,14 @@ impl KiaV6Decoder {
}
}
/// Get keystore A (placeholder)
/// Get keystore A from global keystore
fn get_keystore_a() -> u64 {
0x0000000000000000
keys::get_keystore().get_kia_v6_keystore_a()
}
/// Get keystore B (placeholder)
/// Get keystore B from global keystore
fn get_keystore_b() -> u64 {
0x0000000000000000
keys::get_keystore().get_kia_v6_keystore_b()
}
/// CRC8 calculation
@@ -347,12 +348,17 @@ impl KiaV6Decoder {
}
/// Manchester state machine
/// NOTE: Due to opposite level conventions between Flipper and KAT,
/// KAT level=true corresponds to Flipper level=false (and vice versa).
/// For short pulses: protopirate uses (level & 0x7F) << 1, which gives 0/2.
/// For long pulses: protopirate uses level ? 6 : 4.
/// With the inverted convention, KAT's is_high=true maps to Flipper level=false.
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
(true, true) => 0, // Short High (KAT) → Flipper level=false → 0
(true, false) => 2, // Short Low (KAT) → Flipper level=true → 2
(false, true) => 4, // Long High (KAT) → Flipper level=false → 4
(false, false) => 6, // Long Low (KAT) → Flipper level=true → 6
};
let (new_state, output) = match (self.manchester_state, event) {
+3 -2
View File
@@ -9,6 +9,7 @@
//! - KeeLoq encryption (requires manufacturer key)
use super::keeloq_common::{keeloq_decrypt, keeloq_encrypt, keeloq_normal_learning, reverse_key};
use super::keys;
use super::{DecodedSignal, ProtocolDecoder, ProtocolTiming};
use crate::duration_diff;
use crate::radio::demodulator::LevelDuration;
@@ -48,9 +49,9 @@ impl StarLineDecoder {
}
}
/// Get manufacturer key (placeholder)
/// Get manufacturer key from global keystore
fn get_mf_key() -> u64 {
0x0000000000000000
keys::get_keystore().get_star_line_mf_key()
}
fn parse_data(data: u64) -> DecodedSignal {
+20 -2
View File
@@ -1,4 +1,4 @@
//! Storage management for configuration and exports.
//! Storage management for configuration, exports, and keystores.
//!
//! All application data lives under `~/.config/KAT/`:
//!
@@ -6,6 +6,9 @@
//! ~/.config/KAT/
//! config.ini — User configuration
//! exports/ — Exported .fob / .sub files
//! keystore/ — Protocol encryption keys
//! keystore.ini — Key definitions (hex values)
//! vag.bin — VAG AUT64 binary key file (optional)
//! ```
//!
//! Captures are **in-memory only** and are discarded when KAT exits.
@@ -297,9 +300,19 @@ impl Storage {
);
}
// ── 5. Log resolved paths ───────────────────────────────────────
// ── 5. Ensure keystore directory exists ────────────────────────
let keystore_dir = config_dir.join("keystore");
if !keystore_dir.exists() {
fs::create_dir_all(&keystore_dir).with_context(|| {
format!("Failed to create keystore dir: {:?}", keystore_dir)
})?;
tracing::info!("Created keystore directory: {:?}", keystore_dir);
}
// ── 6. Log resolved paths ───────────────────────────────────────
tracing::info!("Config dir: {:?}", config_dir);
tracing::info!("Export dir: {:?}", config.export_directory);
tracing::info!("Keystore dir: {:?}", keystore_dir);
Ok(Self {
config_dir,
@@ -328,4 +341,9 @@ impl Storage {
pub fn export_dir(&self) -> &PathBuf {
&self.config.export_directory
}
/// Get the keystore directory path (`~/.config/KAT/keystore`)
pub fn keystore_dir(&self) -> PathBuf {
self.config_dir.join("keystore")
}
}