270 lines
9.0 KiB
Rust
270 lines
9.0 KiB
Rust
//! 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)
|
|
}
|
|
}
|