Added a generic keeloq decode against manufacure keys
This commit is contained in:
@@ -212,6 +212,7 @@ impl Capture {
|
|||||||
"Subaru" => ModulationType::Pwm,
|
"Subaru" => ModulationType::Pwm,
|
||||||
"Suzuki" => ModulationType::Pwm,
|
"Suzuki" => ModulationType::Pwm,
|
||||||
"Star Line" => ModulationType::Pwm,
|
"Star Line" => ModulationType::Pwm,
|
||||||
|
p if p.starts_with("Keeloq (") => ModulationType::Pwm,
|
||||||
"Scher-Khan" => ModulationType::Pwm,
|
"Scher-Khan" => ModulationType::Pwm,
|
||||||
// Unknown
|
// Unknown
|
||||||
_ => ModulationType::Unknown,
|
_ => ModulationType::Unknown,
|
||||||
@@ -237,6 +238,7 @@ impl Capture {
|
|||||||
"Subaru" => RfModulation::AM,
|
"Subaru" => RfModulation::AM,
|
||||||
"Suzuki" => RfModulation::AM,
|
"Suzuki" => RfModulation::AM,
|
||||||
"Star Line" => RfModulation::AM,
|
"Star Line" => RfModulation::AM,
|
||||||
|
p if p.starts_with("Keeloq (") => RfModulation::AM,
|
||||||
// Both AM and FM (Kia V3/V4)
|
// Both AM and FM (Kia V3/V4)
|
||||||
p if p.starts_with("Kia V3") || p.starts_with("Kia V4") => RfModulation::Both,
|
p if p.starts_with("Kia V3") || p.starts_with("Kia V4") => RfModulation::Both,
|
||||||
_ => RfModulation::Unknown,
|
_ => RfModulation::Unknown,
|
||||||
@@ -248,6 +250,7 @@ impl Capture {
|
|||||||
match self.protocol_name() {
|
match self.protocol_name() {
|
||||||
p if p.starts_with("Kia V3") || p.starts_with("Kia V4") => "KeeLoq",
|
p if p.starts_with("Kia V3") || p.starts_with("Kia V4") => "KeeLoq",
|
||||||
"Star Line" => "KeeLoq",
|
"Star Line" => "KeeLoq",
|
||||||
|
p if p.starts_with("Keeloq (") => "KeeLoq",
|
||||||
"PSA" => "XTEA/XOR",
|
"PSA" => "XTEA/XOR",
|
||||||
"VAG" => "AUT64/XTEA",
|
"VAG" => "AUT64/XTEA",
|
||||||
"Scher-Khan" => "Magic Code",
|
"Scher-Khan" => "Magic Code",
|
||||||
|
|||||||
+41
-12
@@ -5,6 +5,22 @@ mod embedded;
|
|||||||
|
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
|
||||||
|
/// Type IDs that are KeeLoq manufacturer keys (64-bit keys used for keeloq_decrypt).
|
||||||
|
const KEELOQ_MF_TYPES: &[u32] = &[0, 1, 2, 10, 20];
|
||||||
|
|
||||||
|
/// Names for each key entry in the blob (same order as entries before VAG). Used for fallback decode display.
|
||||||
|
const KEY_ENTRY_NAMES: &[&str] = &[
|
||||||
|
"KIA", "KIAV6A", "KIAV6B", "KIAV5", "Alligator", "Mongoose",
|
||||||
|
"SL_A6-A9/Tomahawk_9010", "Pantera", "SL_A2-A4", "Cenmax_St-5", "SL_B6,B9_dop",
|
||||||
|
"Harpoon", "Tomahawk_TZ-9030", "Tomahawk_Z,X_3-5", "Cenmax_St-7", "Sheriff",
|
||||||
|
"Pantera_CLK", "Cenmax", "Alligator_S-275", "Guard_RF-311A", "Partisan_RX",
|
||||||
|
"APS-1100_APS-2550", "Pantera_XS/Jaguar", "Teco", "Leopard", "Faraon", "Reff",
|
||||||
|
"ZX-730-750-1055", "Star Line",
|
||||||
|
"Pandora_M101", "Pandora_PRO", "Pandora_PRO2", "Pandora_SUBARU", "Pandora_SUZUKI",
|
||||||
|
"Pandora_DEA", "Pandora_GIBIDI", "Pandora_MCODE", "Pandora_Unknown_1", "Pandora_Unknown_2",
|
||||||
|
"Pandora_Test_Debug_2",
|
||||||
|
];
|
||||||
|
|
||||||
const MAGIC: &[u8; 4] = b"KATK";
|
const MAGIC: &[u8; 4] = b"KATK";
|
||||||
const VAG_TAG: &[u8; 4] = b"VAG ";
|
const VAG_TAG: &[u8; 4] = b"VAG ";
|
||||||
const VAG_SIZE: usize = 64;
|
const VAG_SIZE: usize = 64;
|
||||||
@@ -47,6 +63,30 @@ pub fn parse_blob(blob: &[u8]) -> Option<ParsedKeystore> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return all KeeLoq manufacturer keys from the embedded blob with display names.
|
||||||
|
/// Used when an unknown signal is tried as KeeLoq with every key; on success we show "Keeloq (name)".
|
||||||
|
/// Only includes entry types that are KeeLoq MF keys (0, 1, 2, 10, 20).
|
||||||
|
pub fn keeloq_mf_keys_with_names() -> Vec<(String, u64)> {
|
||||||
|
let blob = embedded_blob();
|
||||||
|
let Some(parsed) = parse_blob(blob) else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
parsed
|
||||||
|
.entries
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, (ty, _))| KEELOQ_MF_TYPES.contains(ty))
|
||||||
|
.filter_map(|(i, (_, key))| {
|
||||||
|
let name = KEY_ENTRY_NAMES.get(i).copied().unwrap_or("?").to_string();
|
||||||
|
if *key == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some((name, *key))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the embedded keystore blob for loading.
|
/// Return the embedded keystore blob for loading.
|
||||||
pub fn embedded_blob() -> &'static [u8] {
|
pub fn embedded_blob() -> &'static [u8] {
|
||||||
embedded::KEYSTORE_BLOB
|
embedded::KEYSTORE_BLOB
|
||||||
@@ -66,20 +106,9 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn dump_keystore_keys_for_pandora_compare() {
|
fn dump_keystore_keys_for_pandora_compare() {
|
||||||
let entries = embedded_entries_for_compare();
|
let entries = embedded_entries_for_compare();
|
||||||
const NAMES: &[&str] = &[
|
|
||||||
"KIA", "KIAV6A", "KIAV6B", "KIAV5", "Alligator", "Mongoose",
|
|
||||||
"SL_A6-A9/Tomahawk_9010", "Pantera", "SL_A2-A4", "Cenmax_St-5", "SL_B6,B9_dop",
|
|
||||||
"Harpoon", "Tomahawk_TZ-9030", "Tomahawk_Z,X_3-5", "Cenmax_St-7", "Sheriff",
|
|
||||||
"Pantera_CLK", "Cenmax", "Alligator_S-275", "Guard_RF-311A", "Partisan_RX",
|
|
||||||
"APS-1100_APS-2550", "Pantera_XS/Jaguar", "Teco", "Leopard", "Faraon", "Reff",
|
|
||||||
"ZX-730-750-1055", "Star Line",
|
|
||||||
"Pandora_M101", "Pandora_PRO", "Pandora_PRO2", "Pandora_SUBARU", "Pandora_SUZUKI",
|
|
||||||
"Pandora_DEA", "Pandora_GIBIDI", "Pandora_MCODE", "Pandora_Unknown_1", "Pandora_Unknown_2",
|
|
||||||
"Pandora_Test_Debug_2",
|
|
||||||
];
|
|
||||||
eprintln!("KAT embedded keystore keys (type, hex, name):");
|
eprintln!("KAT embedded keystore keys (type, hex, name):");
|
||||||
for (i, (ty, key)) in entries.iter().enumerate() {
|
for (i, (ty, key)) in entries.iter().enumerate() {
|
||||||
let name = NAMES.get(i).copied().unwrap_or("?");
|
let name = KEY_ENTRY_NAMES.get(i).copied().unwrap_or("?");
|
||||||
eprintln!(" type {} {} {}", ty, format!("{:016X}", key), name);
|
eprintln!(" type {} {} {}", ty, format!("{:016X}", key), name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,180 @@
|
|||||||
|
//! Generic KeeLoq fallback decoder.
|
||||||
|
//!
|
||||||
|
//! When no known protocol decodes a signal, we try to decode it as KeeLoq using every
|
||||||
|
//! manufacturer key in the keystore. We support two common air formats (bit layout + timing):
|
||||||
|
//! - **Kia V3/V4 format**: 68 bits, 400/800µs PWM, 315/433 MHz
|
||||||
|
//! - **Star Line format**: 64 bits, 250/500µs PWM, 433 MHz
|
||||||
|
//!
|
||||||
|
//! All decryption is done via [keeloq_common]; this module only orchestrates bit collection
|
||||||
|
//! (delegated to the format-specific collectors) and tries each key.
|
||||||
|
|
||||||
|
use super::common::DecodedSignal;
|
||||||
|
use super::keeloq_common::{keeloq_decrypt, keeloq_normal_learning, reverse_key, reverse8};
|
||||||
|
use super::kia_v3_v4;
|
||||||
|
use super::star_line;
|
||||||
|
use crate::keystore;
|
||||||
|
use crate::radio::demodulator::LevelDuration;
|
||||||
|
|
||||||
|
const KIA_V3_V4_BITS: usize = 68;
|
||||||
|
const STAR_LINE_BITS: usize = 64;
|
||||||
|
|
||||||
|
/// Try to decode an unknown signal as KeeLoq using every keystore manufacturer key.
|
||||||
|
/// Tries Kia V3/V4 format first (315/433 MHz), then Star Line format (433 MHz).
|
||||||
|
/// Returns `("Keeloq (keystore name)", decoded)` on first successful decrypt.
|
||||||
|
pub fn try_decode(
|
||||||
|
pairs: &[LevelDuration],
|
||||||
|
frequency: u32,
|
||||||
|
) -> Option<(String, DecodedSignal)> {
|
||||||
|
let keys = keystore::keeloq_mf_keys_with_names();
|
||||||
|
if keys.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kia V3/V4 format: 400/800µs, 315/433 MHz
|
||||||
|
let kia_freq_ok = [315_000_000u32, 433_920_000].iter().any(|&f| {
|
||||||
|
let diff = if f > frequency { f - frequency } else { frequency - f };
|
||||||
|
diff < (f / 50)
|
||||||
|
});
|
||||||
|
if kia_freq_ok {
|
||||||
|
for invert in [false, true] {
|
||||||
|
if let Some((buf, is_v3)) = kia_v3_v4::collect_kia_v3_v4_bits(pairs, invert) {
|
||||||
|
if let Some((name, decoded)) = try_kia_v3_v4_format(&buf, is_v3, &keys) {
|
||||||
|
return Some((format!("Keeloq ({})", name), decoded));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Star Line format: 250/500µs, 433 MHz
|
||||||
|
if frequency.abs_diff(433_920_000) < (433_920_000 / 50) {
|
||||||
|
for invert in [false, true] {
|
||||||
|
if let Some(data) = star_line::collect_star_line_bits(pairs, invert) {
|
||||||
|
if let Some((name, decoded)) = try_star_line_format(data, &keys) {
|
||||||
|
return Some((format!("Keeloq ({})", name), decoded));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try Kia V3/V4 68-bit format with each key. Uses [keeloq_common::keeloq_decrypt] for validation.
|
||||||
|
fn try_kia_v3_v4_format(
|
||||||
|
raw_bits: &[u8; 9],
|
||||||
|
is_v3_sync: bool,
|
||||||
|
keys: &[(String, u64)],
|
||||||
|
) -> Option<(String, DecodedSignal)> {
|
||||||
|
let mut b = *raw_bits;
|
||||||
|
if is_v3_sync {
|
||||||
|
for i in 0..9 {
|
||||||
|
b[i] = !b[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let encrypted = ((reverse8(b[3]) as u32) << 24)
|
||||||
|
| ((reverse8(b[2]) as u32) << 16)
|
||||||
|
| ((reverse8(b[1]) as u32) << 8)
|
||||||
|
| (reverse8(b[0]) as u32);
|
||||||
|
let serial = ((reverse8(b[7] & 0xF0) as u32) << 24)
|
||||||
|
| ((reverse8(b[6]) as u32) << 16)
|
||||||
|
| ((reverse8(b[5]) as u32) << 8)
|
||||||
|
| (reverse8(b[4]) as u32);
|
||||||
|
let button = (reverse8(b[7]) & 0xF0) >> 4;
|
||||||
|
let our_serial_lsb = (serial & 0xFF) as u8;
|
||||||
|
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);
|
||||||
|
|
||||||
|
for (name, mf_key) in keys {
|
||||||
|
if *mf_key == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let decrypted = keeloq_decrypt(encrypted, *mf_key);
|
||||||
|
let dec_btn = ((decrypted >> 28) & 0x0F) as u8;
|
||||||
|
let dec_serial_lsb = ((decrypted >> 16) & 0xFF) as u8;
|
||||||
|
if dec_btn == button && dec_serial_lsb == our_serial_lsb {
|
||||||
|
let counter = (decrypted & 0xFFFF) as u16;
|
||||||
|
return Some((
|
||||||
|
name.clone(),
|
||||||
|
DecodedSignal {
|
||||||
|
serial: Some(serial),
|
||||||
|
button: Some(button),
|
||||||
|
counter: Some(counter),
|
||||||
|
crc_valid: true,
|
||||||
|
data: key_data,
|
||||||
|
data_count_bit: KIA_V3_V4_BITS,
|
||||||
|
encoder_capable: true,
|
||||||
|
extra: None,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try Star Line 64-bit format with each key. Uses [keeloq_common::keeloq_decrypt] and
|
||||||
|
/// [keeloq_common::keeloq_normal_learning] for validation.
|
||||||
|
fn try_star_line_format(
|
||||||
|
data: u64,
|
||||||
|
keys: &[(String, u64)],
|
||||||
|
) -> Option<(String, DecodedSignal)> {
|
||||||
|
let reversed = reverse_key(data, STAR_LINE_BITS);
|
||||||
|
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;
|
||||||
|
let serial_lsb = (serial & 0xFF) as u8;
|
||||||
|
|
||||||
|
for (name, mf_key) in keys {
|
||||||
|
if *mf_key == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Simple learning
|
||||||
|
let decrypt = keeloq_decrypt(key_hop, *mf_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 {
|
||||||
|
let counter = (decrypt & 0xFFFF) as u16;
|
||||||
|
return Some((
|
||||||
|
name.clone(),
|
||||||
|
DecodedSignal {
|
||||||
|
serial: Some(serial),
|
||||||
|
button: Some(btn),
|
||||||
|
counter: Some(counter),
|
||||||
|
crc_valid: true,
|
||||||
|
data,
|
||||||
|
data_count_bit: STAR_LINE_BITS,
|
||||||
|
encoder_capable: true,
|
||||||
|
extra: None,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
// 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 {
|
||||||
|
let counter = (decrypt & 0xFFFF) as u16;
|
||||||
|
return Some((
|
||||||
|
name.clone(),
|
||||||
|
DecodedSignal {
|
||||||
|
serial: Some(serial),
|
||||||
|
button: Some(btn),
|
||||||
|
counter: Some(counter),
|
||||||
|
crc_valid: true,
|
||||||
|
data,
|
||||||
|
data_count_bit: STAR_LINE_BITS,
|
||||||
|
encoder_capable: true,
|
||||||
|
extra: None,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
@@ -159,6 +159,102 @@ impl KiaV3V4Decoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Collect 68-bit Kia V3/V4 payload from level+duration pairs (for keeloq_generic fallback).
|
||||||
|
/// Returns (raw_bits[0..9], is_v3_sync) when a valid 68-bit burst is found.
|
||||||
|
pub fn collect_kia_v3_v4_bits(
|
||||||
|
pairs: &[LevelDuration],
|
||||||
|
invert_level: bool,
|
||||||
|
) -> Option<([u8; 9], bool)> {
|
||||||
|
let mut step = DecoderStep::Reset;
|
||||||
|
let mut te_last = 0u32;
|
||||||
|
let mut header_count = 0u16;
|
||||||
|
let mut raw_bits = [0u8; 32];
|
||||||
|
let mut raw_bit_count = 0u16;
|
||||||
|
let mut is_v3_sync = false;
|
||||||
|
|
||||||
|
for pair in pairs {
|
||||||
|
let level = if invert_level { !pair.level } else { pair.level };
|
||||||
|
let duration = pair.duration_us;
|
||||||
|
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 step {
|
||||||
|
DecoderStep::Reset => {
|
||||||
|
if level && is_short {
|
||||||
|
step = DecoderStep::CheckPreamble;
|
||||||
|
te_last = duration;
|
||||||
|
header_count = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DecoderStep::CheckPreamble => {
|
||||||
|
if level {
|
||||||
|
if is_short {
|
||||||
|
te_last = duration;
|
||||||
|
} else if is_sync && header_count >= 8 {
|
||||||
|
step = DecoderStep::CollectRawBits;
|
||||||
|
raw_bit_count = 0;
|
||||||
|
is_v3_sync = false;
|
||||||
|
raw_bits = [0; 32];
|
||||||
|
} else {
|
||||||
|
step = DecoderStep::Reset;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if is_sync && header_count >= 8 {
|
||||||
|
step = DecoderStep::CollectRawBits;
|
||||||
|
raw_bit_count = 0;
|
||||||
|
is_v3_sync = true;
|
||||||
|
raw_bits = [0; 32];
|
||||||
|
} else if is_short && duration_diff!(te_last, TE_SHORT) < TE_DELTA {
|
||||||
|
header_count += 1;
|
||||||
|
} else if is_very_long {
|
||||||
|
step = DecoderStep::Reset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DecoderStep::CollectRawBits => {
|
||||||
|
if level {
|
||||||
|
if is_sync || is_very_long {
|
||||||
|
if raw_bit_count >= 68 {
|
||||||
|
let mut out = [0u8; 9];
|
||||||
|
out.copy_from_slice(&raw_bits[0..9]);
|
||||||
|
return Some((out, is_v3_sync));
|
||||||
|
}
|
||||||
|
step = DecoderStep::Reset;
|
||||||
|
} else if is_short {
|
||||||
|
if raw_bit_count < 256 {
|
||||||
|
let byte_idx = (raw_bit_count / 8) as usize;
|
||||||
|
let bit_idx = 7 - (raw_bit_count % 8);
|
||||||
|
raw_bits[byte_idx] &= !(1 << bit_idx);
|
||||||
|
raw_bit_count += 1;
|
||||||
|
}
|
||||||
|
} else if is_long {
|
||||||
|
if raw_bit_count < 256 {
|
||||||
|
let byte_idx = (raw_bit_count / 8) as usize;
|
||||||
|
let bit_idx = 7 - (raw_bit_count % 8);
|
||||||
|
raw_bits[byte_idx] |= 1 << bit_idx;
|
||||||
|
raw_bit_count += 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
step = DecoderStep::Reset;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if is_sync || is_very_long {
|
||||||
|
if raw_bit_count >= 68 {
|
||||||
|
let mut out = [0u8; 9];
|
||||||
|
out.copy_from_slice(&raw_bits[0..9]);
|
||||||
|
return Some((out, is_v3_sync));
|
||||||
|
}
|
||||||
|
step = DecoderStep::Reset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
impl ProtocolDecoder for KiaV3V4Decoder {
|
impl ProtocolDecoder for KiaV3V4Decoder {
|
||||||
fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
"Kia V3/V4"
|
"Kia V3/V4"
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
pub mod keeloq_common;
|
pub mod keeloq_common;
|
||||||
|
mod keeloq_generic;
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub mod aut64;
|
pub mod aut64;
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -117,7 +118,11 @@ impl ProtocolRegistry {
|
|||||||
return Some(result);
|
return Some(result);
|
||||||
}
|
}
|
||||||
// Try inverted polarity (capture LOW = RF HIGH)
|
// Try inverted polarity (capture LOW = RF HIGH)
|
||||||
self.process_signal_inner(pairs, frequency, true)
|
if let Some(result) = self.process_signal_inner(pairs, frequency, true) {
|
||||||
|
return Some(result);
|
||||||
|
}
|
||||||
|
// No known protocol: try KeeLoq generic (uses keeloq_common with every keystore key)
|
||||||
|
keeloq_generic::try_decode(pairs, frequency)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ProtoPirate-style streaming decode: feed the whole stream, on each decode record and reset decoders, continue.
|
/// ProtoPirate-style streaming decode: feed the whole stream, on each decode record and reset decoders, continue.
|
||||||
|
|||||||
@@ -106,6 +106,97 @@ impl StarLineDecoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Collect 64-bit Star Line payload from level+duration pairs (for keeloq_generic fallback).
|
||||||
|
pub fn collect_star_line_bits(
|
||||||
|
pairs: &[LevelDuration],
|
||||||
|
invert_level: bool,
|
||||||
|
) -> Option<u64> {
|
||||||
|
let mut step = DecoderStep::Reset;
|
||||||
|
let mut header_count = 0u16;
|
||||||
|
let mut decode_data = 0u64;
|
||||||
|
let mut decode_count_bit = 0usize;
|
||||||
|
let mut te_last = 0u32;
|
||||||
|
|
||||||
|
for pair in pairs {
|
||||||
|
let level = if invert_level { !pair.level } else { pair.level };
|
||||||
|
let duration = pair.duration_us;
|
||||||
|
|
||||||
|
match step {
|
||||||
|
DecoderStep::Reset => {
|
||||||
|
if level {
|
||||||
|
if duration_diff!(duration, HEADER_DURATION) < TE_DELTA * 2 {
|
||||||
|
step = DecoderStep::CheckPreamble;
|
||||||
|
header_count += 1;
|
||||||
|
} else if header_count > 4 {
|
||||||
|
decode_data = 0;
|
||||||
|
decode_count_bit = 0;
|
||||||
|
te_last = duration;
|
||||||
|
step = DecoderStep::CheckDuration;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
header_count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DecoderStep::CheckPreamble => {
|
||||||
|
if !level && duration_diff!(duration, HEADER_DURATION) < TE_DELTA * 2 {
|
||||||
|
step = DecoderStep::Reset;
|
||||||
|
} else {
|
||||||
|
header_count = 0;
|
||||||
|
step = DecoderStep::Reset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DecoderStep::SaveDuration => {
|
||||||
|
if level {
|
||||||
|
if duration >= (TE_LONG + TE_DELTA) {
|
||||||
|
step = DecoderStep::Reset;
|
||||||
|
if decode_count_bit >= MIN_COUNT_BIT && decode_count_bit <= MIN_COUNT_BIT + 2 {
|
||||||
|
return Some(decode_data);
|
||||||
|
}
|
||||||
|
decode_data = 0;
|
||||||
|
decode_count_bit = 0;
|
||||||
|
header_count = 0;
|
||||||
|
} else {
|
||||||
|
te_last = duration;
|
||||||
|
step = DecoderStep::CheckDuration;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
step = DecoderStep::Reset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DecoderStep::CheckDuration => {
|
||||||
|
if !level {
|
||||||
|
if duration_diff!(te_last, TE_SHORT) < TE_DELTA
|
||||||
|
&& duration_diff!(duration, TE_SHORT) < TE_DELTA
|
||||||
|
{
|
||||||
|
if decode_count_bit < MIN_COUNT_BIT {
|
||||||
|
decode_data = (decode_data << 1) | 0;
|
||||||
|
decode_count_bit += 1;
|
||||||
|
} else {
|
||||||
|
decode_count_bit += 1;
|
||||||
|
}
|
||||||
|
step = DecoderStep::SaveDuration;
|
||||||
|
} else if duration_diff!(te_last, TE_LONG) < TE_DELTA
|
||||||
|
&& duration_diff!(duration, TE_LONG) < TE_DELTA
|
||||||
|
{
|
||||||
|
if decode_count_bit < MIN_COUNT_BIT {
|
||||||
|
decode_data = (decode_data << 1) | 1;
|
||||||
|
decode_count_bit += 1;
|
||||||
|
} else {
|
||||||
|
decode_count_bit += 1;
|
||||||
|
}
|
||||||
|
step = DecoderStep::SaveDuration;
|
||||||
|
} else {
|
||||||
|
step = DecoderStep::Reset;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
step = DecoderStep::Reset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
impl ProtocolDecoder for StarLineDecoder {
|
impl ProtocolDecoder for StarLineDecoder {
|
||||||
fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
"Star Line"
|
"Star Line"
|
||||||
|
|||||||
Reference in New Issue
Block a user