flipper-zero-tutorials/subghz/apps/rolling-flaws/rolling_flaws_keeloq.c
2023-09-02 22:08:39 -05:00

226 lines
7.6 KiB
C

#include "rolling_flaws_keeloq.h"
#include "rolling_flaws_utils.h"
#include "rolling_flaws_settings.h"
typedef struct {
uint32_t fix;
uint32_t hop;
uint32_t sn;
uint32_t btn;
uint32_t cnt;
uint32_t enc;
FuriString* mf;
} KeeLoqData;
KeeLoqData* keeloq_data_alloc() {
KeeLoqData* data = malloc(sizeof(KeeLoqData));
data->mf = furi_string_alloc();
return data;
}
void keeloq_data_free(KeeLoqData* data) {
furi_string_free(data->mf);
free(data);
}
static uint32_t get_forward_distance(uint32_t current_count, uint32_t new_count) {
uint32_t distance = 0;
if(new_count >= current_count) {
distance = new_count - current_count;
} else {
distance = (0xFFFF - current_count) + new_count;
}
return distance;
}
static bool is_open(RollingFlawsModel* model, KeeLoqData* data) {
bool any_mf = rolling_flaws_setting_protocol_mf_name_get(model)[0] == '*';
if(!any_mf &&
furi_string_cmp(data->mf, rolling_flaws_setting_protocol_mf_name_get(model)) != 0) {
FURI_LOG_I(
TAG,
"Wrong MF. Expected >%s< but got >%s<",
rolling_flaws_setting_protocol_mf_name_get(model),
furi_string_get_cstr(data->mf));
furi_string_set(model->status, "BAD MF");
return false;
}
if(data->fix != rolling_flaws_setting_fix_get(model)) {
FURI_LOG_I(
TAG,
"Wrong fix. Expected >%08lX< but got >%08lX<",
rolling_flaws_setting_fix_get(model),
data->fix);
furi_string_set(model->status, "BAD FIX");
return false;
}
if((rolling_flaws_setting_fix_get(model) & 0xFFFFFFF) == 0) {
FURI_LOG_I(TAG, "Fix is test. Not checking data.");
furi_string_set(model->status, "TEST");
model->future_count = 0xFFFFFFFF;
model->count = data->cnt;
return true;
}
if(data->enc != FAILED_TO_PARSE) {
FURI_LOG_I(TAG, "Encrypted payload is %08lX", data->enc);
if(!rolling_flaws_setting_sn_zero_get(model)) {
FURI_LOG_I(TAG, "SN wildcard by 00 disabled.");
if((data->fix & 0xFF) != 0) {
FURI_LOG_I(TAG, "SN does not end in 00, validating enc %08lX.", data->enc);
if((data->enc & 0xFF) == 0) {
FURI_LOG_I(TAG, "Encrypted payload SN is zero.");
furi_string_set(model->status, "SN 00");
return false;
}
}
}
uint8_t match_bits = rolling_flaws_setting_sn_bits_get(model);
if(match_bits != 0) {
uint32_t mask = 0xFFFFFFFF;
mask = mask >> (32 - match_bits);
uint32_t fix_sn = data->fix & mask;
uint32_t enc_sn = data->enc & mask;
if(fix_sn != enc_sn) {
FURI_LOG_I(TAG, "SN does not match. Fix: %08lX Enc: %08lX", fix_sn, enc_sn);
furi_string_set(model->status, "BAD SN");
return false;
} else {
FURI_LOG_I(TAG, "SN matches. Fix: %08lX Enc: %08lX", fix_sn, enc_sn);
}
}
}
uint32_t distance = get_forward_distance(model->count, data->cnt);
FURI_LOG_I(TAG, "Distance: %08lX", distance);
if(distance == 0 && rolling_flaws_setting_replay_get(model)) {
FURI_LOG_I(TAG, "Replay attack detected");
furi_string_set(model->status, "REPLAY");
model->future_count = 0xFFFFFFFF;
model->count = data->cnt;
return true;
}
if(rolling_flaws_setting_count_zero_get(model) && data->cnt == 0) {
FURI_LOG_I(TAG, "Count zero allowed.");
furi_string_set(model->status, "COUNT0");
model->future_count = 0xFFFFFFFF;
// We don't reset count in this case.
return true;
}
if(distance == 0) {
distance = 0x10000;
}
if(distance <= rolling_flaws_setting_window_next_get(model)) {
FURI_LOG_I(TAG, "Within next window");
furi_string_set(model->status, "NEXT");
model->future_count = 0xFFFFFFFF;
model->count = data->cnt;
return true;
}
if(distance <= rolling_flaws_setting_window_future_get(model)) {
FURI_LOG_I(TAG, "Within future window");
if(model->future_count > 0xFFFF) {
FURI_LOG_I(TAG, "Set future value to %08lX.", data->cnt);
furi_string_set(model->status, "FUTURE");
model->future_count = data->cnt;
return false;
}
uint32_t future_gap = get_forward_distance(model->future_count, data->cnt);
if(future_gap > 0 && future_gap <= rolling_flaws_setting_window_future_gap_get(model)) {
FURI_LOG_I(TAG, "Future gap accepted. Gap is %08lX", future_gap);
furi_string_set(model->status, "GAP");
model->future_count = 0xFFFFFFFF;
model->count = data->cnt;
return true;
}
if(future_gap == 0) {
FURI_LOG_I(TAG, "Future gap is zero. Set future value to %08lX.", data->cnt);
furi_string_set(model->status, "FUTURE");
model->future_count = data->cnt;
return false;
}
FURI_LOG_I(
TAG,
"Future gap too large. %08lX > %08lX",
future_gap,
rolling_flaws_setting_window_future_gap_get(model));
furi_string_set(model->status, "BAD GAP");
model->future_count = data->cnt;
return false;
}
FURI_LOG_I(TAG, "Signal must be from the past (non-future).");
furi_string_set(model->status, "PAST");
return false;
}
uint32_t last_decode = 0;
void decode_keeloq(RollingFlawsModel* model, FuriString* buffer, bool sync) {
FURI_LOG_T(TAG, "Decoding KeeLoq 64bit");
uint32_t now = furi_get_tick();
if(now - last_decode < furi_ms_to_ticks(500)) {
FURI_LOG_D(TAG, "Ignoring decode. Too soon.");
last_decode = now;
return;
}
last_decode = now;
KeeLoqData* data = keeloq_data_alloc();
__furi_string_extract_string_until(buffer, 0, "MF:", '\r', data->mf);
__furi_string_extract_string(buffer, 0, "Key:", '\r', model->key);
data->fix = __furi_string_extract_int(buffer, "Fix:0x", ' ', FAILED_TO_PARSE);
data->hop = __furi_string_extract_int(buffer, "Hop:0x", ' ', FAILED_TO_PARSE);
data->sn = __furi_string_extract_int(buffer, "Sn:0x", ' ', FAILED_TO_PARSE);
if(data->sn == FAILED_TO_PARSE) {
FURI_LOG_I(TAG, "Sn:0x not found. Using Fix data.");
data->sn = data->fix & 0x0FFFFFFF;
}
data->btn = __furi_string_extract_int(buffer, "Btn:", '\r', FAILED_TO_PARSE);
data->cnt = __furi_string_extract_int(buffer, "Cnt:", '\r', FAILED_TO_PARSE);
// NOTE: "Enc:" needs to be added to "keeloq.c" subghz_protocol_decoder_keeloq_get_string() method.
data->enc = __furi_string_extract_int(buffer, "Enc:", '\r', FAILED_TO_PARSE);
FURI_LOG_I(
TAG,
"fix: %08lX hop: %08lX sn: %08lX btn: %08lX cnt: %08lX enc:%08lX key:%s mf:%s",
data->fix,
data->hop,
data->sn,
data->btn,
data->cnt,
data->enc,
furi_string_get_cstr(model->key),
furi_string_get_cstr(data->mf));
if(!sync) {
model->opened = is_open(model, data);
if(model->opened) {
model->count = data->cnt;
}
__gui_redraw();
} else {
model->custom_fix = data->fix;
model->count = data->cnt;
model->future_count = 0xFFFFFFFF;
model->opened = false;
rolling_flaws_setting_protocol_custom_mf_set(model, data->mf);
furi_string_set(model->status, "SYNCED");
}
keeloq_data_free(data);
}