Compare commits

...

18 Commits

Author SHA1 Message Date
d4rks1d33
e881d69ab3 Added passive Keyless sniffer for future analysis of keyless entry systems
All checks were successful
Build Dev Firmware / build (push) Successful in 6m46s
2026-03-17 21:22:30 -03:00
grugnoymeme
b041177398 fixing problem of exit confirm not working when entering subghz app with right arrow ALREADY WIP
All checks were successful
Build Dev Firmware / build (push) Successful in 6m35s
2026-03-17 22:18:26 +01:00
grugnoymeme
f347d5a976 better display of datas after decrypt for PSA
All checks were successful
Build Dev Firmware / build (push) Successful in 6m54s
2026-03-17 21:08:18 +01:00
grugnoymeme
3a6da87288 hide Emulate choice in subghz saved menu for psa encrypted sub files bc useless, and moved PSA decrypt first
All checks were successful
Build Dev Firmware / build (push) Successful in 6m41s
2026-03-17 20:17:21 +01:00
grugnoymeme
5d94639d81 Merge remote-tracking branch 'refs/remotes/origin/main' 2026-03-17 20:12:54 +01:00
grugnoymeme
5dcfc48e10 restored holtekS protocols bc super commons, fixed fiat spa, scheduled tea iterations for faster bf1 in psa, fixed progress bar's issue on 1 percent in psa decrypt, fmt protocol items 2026-03-17 20:07:07 +01:00
Andrea Santaniello
20a95b2fec Apprently using the bt thread to save the keys causes a crash due to the low memory
All checks were successful
Build Dev Firmware / build (push) Successful in 6m40s
2026-03-17 17:24:03 +01:00
Andrea Santaniello
3605669cc5 Fixing crash on finding first good candidate
All checks were successful
Build Dev Firmware / build (push) Successful in 6m38s
2026-03-17 15:28:16 +01:00
Andrea Santaniello
fb1c28a0dd Update subghz_scene_kl_bf_cleanup.c
All checks were successful
Build Dev Firmware / build (push) Successful in 6m42s
2026-03-17 14:23:56 +01:00
Andrea Santaniello
64a971e806 Keeloq Bruteforcer updates
Some checks failed
Build Dev Firmware / build (push) Failing after 2m37s
2026-03-17 14:01:27 +01:00
d4rks1d33
12db96a8ab Fix counter brute force, open window to 500ms per transmission making more stable
All checks were successful
Build Dev Firmware / build (push) Successful in 6m34s
2026-03-16 21:41:33 -03:00
d4rks1d33
4b50b8b70c Fix warning UI
All checks were successful
Build Dev Firmware / build (push) Successful in 6m35s
2026-03-16 20:09:04 -03:00
d4rks1d33
0f24f8c105 Added warning on counter bruteforce 2026-03-16 19:45:54 -03:00
Andrea Santaniello
238f39d0d8 Fixes 2026-03-16 23:39:22 +01:00
Andrea Santaniello
4c3581735b Better handling of the keeloq bf
All checks were successful
Build Dev Firmware / build (push) Successful in 6m23s
2026-03-16 22:31:16 +01:00
Andrea Santaniello
689df5262d Compiler bitch fix
All checks were successful
Build Dev Firmware / build (push) Successful in 6m28s
2026-03-16 17:57:09 +01:00
Andrea Santaniello
86c740d923 Preliminary stuff for phone accellerate Keeloq bruteforce
Some checks failed
Build Dev Firmware / build (push) Failing after 2m35s
2026-03-16 16:55:25 +01:00
Andrea Santaniello
0aef017c15 New assets by GONZOsint (https://github.com/GONZOsint)
All checks were successful
Build Dev Firmware / build (push) Successful in 6m46s
2026-03-16 13:57:23 +01:00
34 changed files with 2831 additions and 834 deletions

View File

@@ -0,0 +1,11 @@
App(
appid="lf_sniffer",
name="KeylessGO Sniffer",
apptype=FlipperAppType.MENUEXTERNAL,
entry_point="lf_sniffer_app",
requires=["gui", "notification", "storage"],
stack_size=4 * 1024,
fap_category="GPIO",
fap_version=(1, 0),
fap_description="LF 125kHz challenge sniffer for KeylessGO analysis",
)

View File

@@ -0,0 +1,506 @@
#!/usr/bin/env python3
"""
LF Analyzer -- Decodes captures from the LF Sniffer FAP
=========================================================
Analyzes edge timings captured by the Flipper Zero FAP and determines:
- Modulation type (ASK/FSK/PSK)
- Encoding (Manchester, Biphase, NRZ, PWM)
- Bit period and carrier frequency
- Decoded bit stream
- Likely protocol (Hitag2, Hitag S, EM4100, etc.)
- KeylessGO challenge candidate if applicable
Usage:
python3 lf_analyze.py capture_0000.csv
python3 lf_analyze.py capture_0000.csv --verbose
python3 lf_analyze.py capture_0000.csv --protocol hitag2
"""
import argparse
import csv
import sys
import math
from collections import Counter
from pathlib import Path
# -- CSV loader ----------------------------------------------------------------
def load_csv(path: str) -> tuple[list, dict]:
edges = []
meta = {}
with open(path, 'r') as f:
for line in f:
line = line.strip()
if line.startswith('#'):
# Parse key:value pairs from metadata comment lines
if 'edges:' in line:
for token in line.split():
if ':' in token:
k, v = token.split(':', 1)
try:
meta[k.lstrip('#')] = int(v)
except ValueError:
pass
continue
if line.startswith('index'):
continue # skip column header
parts = line.split(',')
if len(parts) >= 3:
try:
edges.append({
'idx': int(parts[0]),
'dur_us': int(parts[1]),
'level': int(parts[2]),
'note': parts[3].strip() if len(parts) > 3 else ''
})
except ValueError:
pass
print(f"[*] Loaded {len(edges)} edges")
if meta:
print(f" Metadata: {meta}")
return edges, meta
# -- Carrier frequency detection ----------------------------------------------
def analyze_carrier(edges: list, verbose: bool = False) -> dict:
"""
Detects the LF carrier by measuring the shortest pulses in the capture.
These correspond to half-periods of the unmodulated carrier.
"""
durations = [e['dur_us'] for e in edges if 1 < e['dur_us'] < 50]
if not durations:
return {'carrier_hz': 0, 'half_period_us': 0}
counter = Counter(durations)
most_common = counter.most_common(20)
if verbose:
print("\n[*] Most frequent edge durations (us):")
for dur, cnt in most_common[:10]:
print(f" {dur:4d} us x{cnt:5d}")
# Carrier half-period = most frequent pulse in the 1-20 us range
short = [(d, c) for d, c in most_common if 1 < d < 20]
if not short:
short = [(d, c) for d, c in most_common if d < 50]
if not short:
return {'carrier_hz': 0, 'half_period_us': 0}
half_period = short[0][0]
carrier_hz = int(1_000_000 / (2 * half_period)) if half_period > 0 else 0
print(f"\n[*] Detected carrier:")
print(f" Half-period : {half_period} us")
print(f" Frequency : {carrier_hz:,} Hz (~{carrier_hz/1000:.1f} kHz)")
if 100_000 < carrier_hz < 150_000:
print(f" [+] Matches 125 kHz LF band (KeylessGO / RFID)")
elif 110_000 < carrier_hz < 140_000:
print(f" [~] Close to 125 kHz")
return {
'carrier_hz': carrier_hz,
'half_period_us': half_period,
'period_us': half_period * 2,
}
# -- Packet segmentation ------------------------------------------------------
def find_packets(edges: list, gap_threshold_us: int = 5000) -> list:
"""Splits the edge list into packets separated by gaps."""
packets = []
start = 0
for i, e in enumerate(edges):
if e['dur_us'] > gap_threshold_us:
if i - start > 8:
packets.append(edges[start:i])
start = i + 1
if len(edges) - start > 8:
packets.append(edges[start:])
print(f"\n[*] Detected packets: {len(packets)}")
for i, pkt in enumerate(packets):
total = sum(e['dur_us'] for e in pkt)
print(f" PKT {i}: {len(pkt)} edges, {total} us ({total/1000:.1f} ms)")
return packets
# -- Bit period detection -----------------------------------------------------
def detect_bit_period(packet: list, carrier: dict, verbose: bool = False) -> dict:
"""
Estimates the bit period from modulated pulse widths.
KeylessGO uses Manchester at ~4 kbps over 125 kHz, so bit_period ~250 us.
EM4100 / Hitag use 125 kHz / 32 = ~256 us per bit.
"""
min_dur = carrier.get('half_period_us', 4)
durations = [e['dur_us'] for e in packet if e['dur_us'] > min_dur * 2]
if not durations:
return {'bit_period_us': 0, 'baud_rate': 0}
# Histogram with 10 us resolution
hist = Counter(round(d / 10) * 10 for d in durations)
peaks = sorted(hist.items(), key=lambda x: x[1], reverse=True)
if verbose:
print("\n Modulated pulse durations (top 15):")
for dur, cnt in peaks[:15]:
print(f" {dur:5d} us x{cnt:4d}")
if not peaks:
return {'bit_period_us': 0, 'baud_rate': 0}
# Bit period = smallest frequently-occurring modulated pulse
candidates = [d for d, c in peaks if c >= 3]
if not candidates:
candidates = [peaks[0][0]]
half_period = min(candidates)
# Check if this is a half-period (consecutive same-width pulses that
# alternate level = Manchester half-periods). If so, double it.
# Two equal half-periods make one Manchester bit period.
same_width_pairs = sum(
1 for i in range(len(packet)-1)
if abs(packet[i]['dur_us'] - half_period) < half_period*0.3
and abs(packet[i+1]['dur_us'] - half_period) < half_period*0.3
)
total_half_pulses = sum(
1 for e in packet
if abs(e['dur_us'] - half_period) < half_period*0.3
)
# If >80% of pulses are this width, they are half-periods
if total_half_pulses > len(packet) * 0.7:
bit_period = half_period * 2
is_half = True
else:
bit_period = half_period
is_half = False
baud_rate = int(1_000_000 / bit_period) if bit_period > 0 else 0
print(f"\n[*] Estimated bit period: {bit_period} us ({baud_rate} baud)")
if is_half:
print(f" (half-period detected: {half_period} us x2)")
if 3800 < baud_rate < 4200:
print(" -> Likely: Manchester 4 kbps (Hitag2 / Hitag S / EM4100 / Keyless)")
elif 7500 < baud_rate < 8500:
print(" -> Likely: Manchester 8 kbps")
elif 1900 < baud_rate < 2100:
print(" -> Likely: 2 kbps biphase (EM4100)")
return {
'bit_period_us': bit_period,
'baud_rate': baud_rate,
'half_bit_us': bit_period // 2,
}
# -- Manchester decoder -------------------------------------------------------
def decode_manchester(packet: list, bit_period_us: int, verbose: bool = False) -> list:
"""
Decodes Manchester-encoded bit stream from edge timings.
Both half-period and full-period pulses are handled.
Returns a list of integers (0 or 1).
"""
if bit_period_us == 0:
return []
half = bit_period_us // 2
tolerance = half // 2 # +/- 50% of the half-period
bits = []
i = 0
while i < len(packet):
dur = packet[i]['dur_us']
if abs(dur - half) < tolerance:
# Half-period pulse -- needs the next one to complete a bit
if i + 1 < len(packet):
dur2 = packet[i + 1]['dur_us']
if abs(dur2 - half) < tolerance:
level = packet[i]['level']
bits.append(1 if level else 0)
i += 2
continue
i += 1
elif abs(dur - bit_period_us) < tolerance:
# Full-period pulse -- encodes one bit by itself
level = packet[i]['level']
bits.append(1 if level else 0)
i += 1
else:
if verbose:
print(f" [!] Unexpected duration at edge {i}: {dur} us")
i += 1
return bits
# -- Bit / byte helpers -------------------------------------------------------
def bits_to_bytes(bits: list) -> bytes:
result = bytearray()
for i in range(0, len(bits) - len(bits) % 8, 8):
byte = 0
for j in range(8):
byte = (byte << 1) | bits[i + j]
result.append(byte)
return bytes(result)
def print_bits(bits: list, label: str = "Bits"):
print(f"\n[*] {label} ({len(bits)} bits):")
for i in range(0, len(bits), 64):
chunk = bits[i:i + 64]
groups = [chunk[j:j + 8] for j in range(0, len(chunk), 8)]
line = ' '.join(''.join(str(b) for b in g) for g in groups)
print(f" {i//8:4d}: {line}")
if len(bits) >= 8:
data = bits_to_bytes(bits)
hex_str = ' '.join(f'{b:02X}' for b in data)
print(f"\n HEX: {hex_str}")
# -- Protocol identification --------------------------------------------------
def identify_protocol(bits: list, baud_rate: int, carrier_hz: int) -> str:
n = len(bits)
print(f"\n[*] Protocol identification:")
print(f" Bits: {n} | Baud: {baud_rate} | Carrier: {carrier_hz:,} Hz")
# EM4100: 64-bit frame, 9-bit preamble of all-ones, Manchester
if 50 < n < 80 and baud_rate > 3000:
for start in range(n - 9):
if all(bits[start + j] == 1 for j in range(9)):
print(f" -> EM4100 candidate (preamble at bit {start})")
data_bits = bits[start + 9:]
if len(data_bits) >= 55:
_decode_em4100(data_bits[:55])
break
# Hitag2: ~96-bit frame, Manchester 4 kbps
if 80 < n < 120 and 3500 < baud_rate < 4500:
print(f" -> Hitag2 candidate ({n} bits)")
_analyze_hitag2(bits)
# Hitag S: variable length, Manchester 4 kbps
if n > 120 and 3500 < baud_rate < 4500:
print(f" -> Hitag S candidate ({n} bits)")
# KeylessGO challenge: typically 32-64 data bits
if 20 < n < 80 and 3500 < baud_rate < 4500:
print(f" -> KeylessGO challenge candidate")
_analyze_keylessgo_challenge(bits)
return "unknown"
def _decode_em4100(bits: list):
if len(bits) < 55:
return
# EM4100 data layout: [version 8b][data 32b][col_parity 4b][stop 1b]
# Each row: 4 data bits + 1 parity bit
print("\n EM4100 decode:")
customer = (bits[0] << 7) | (bits[1] << 6) | (bits[2] << 5) | \
(bits[3] << 4) | (bits[5] << 3) | (bits[6] << 2) | \
(bits[7] << 1) | bits[8]
print(f" Customer code: 0x{customer:02X} ({customer})")
def _analyze_hitag2(bits: list):
if len(bits) < 32:
return
data = bits_to_bytes(bits[:32])
print(f"\n Hitag2 first 32 bits: {data.hex().upper()}")
print(f" As uint32 BE : 0x{int.from_bytes(data, 'big'):08X}")
def _analyze_keylessgo_challenge(bits: list):
"""
Looks for a KeylessGO challenge structure.
The car transmits: [sync/preamble] [32-bit challenge] [checksum]
"""
print(f"\n Keyless challenge analysis:")
# Alternating preamble (010101...) is typically used as sync
for start in range(min(len(bits) - 8, 20)):
window = bits[start:start + 8]
alternating = all(window[j] != window[j + 1] for j in range(7))
if alternating:
print(f" Alternating sync at bit {start}: {''.join(str(b) for b in window)}")
payload_start = start + 8
if len(bits) - payload_start >= 32:
challenge_bits = bits[payload_start:payload_start + 32]
challenge_bytes = bits_to_bytes(challenge_bits)
challenge_val = int.from_bytes(challenge_bytes, 'big')
print(f" Challenge candidate (32b): 0x{challenge_val:08X}")
print(f" As bytes : {challenge_bytes.hex().upper()}")
break
# Also print every 32-bit aligned window as a candidate
print(f"\n All 32-bit blocks in capture:")
for offset in range(0, min(len(bits) - 32, 48), 4):
chunk = bits[offset:offset + 32]
val = 0
for b in chunk:
val = (val << 1) | b
preview = ''.join(str(b) for b in chunk[:16])
print(f" offset {offset:3d}: 0x{val:08X} [{preview}...]")
# -- Full analysis entry point ------------------------------------------------
def analyze_capture(path: str, verbose: bool = False, protocol_hint: str = None):
print(f"\n{'='*60}")
print(f"LF Capture Analyzer -- {Path(path).name}")
print('='*60)
edges, meta = load_csv(path)
if not edges:
print("ERROR: No data found in file.")
return
# Step 1: detect carrier
carrier = analyze_carrier(edges, verbose)
# Step 2: segment packets
packets = find_packets(edges)
if not packets:
print("\n[!] No packets detected. Check:")
print(" - Car is emitting 125 kHz LF field")
print(" - Coil is connected correctly to PB2")
print(" - LM393 is powered from 3.3V")
return
# Step 3: analyze each packet
for i, pkt in enumerate(packets):
print(f"\n{''*50}")
print(f"PACKET {i} -- {len(pkt)} edges")
print(''*50)
bit_info = detect_bit_period(pkt, carrier, verbose)
bit_period = bit_info.get('bit_period_us', 0)
baud_rate = bit_info.get('baud_rate', 0)
if bit_period == 0:
print(" [!] Could not determine bit period")
continue
# Step 4: Manchester decode
bits = decode_manchester(pkt, bit_period, verbose)
if len(bits) < 8:
print(f" [!] Only {len(bits)} bits decoded -- likely noise or raw carrier")
if verbose:
raw = [e['dur_us'] for e in pkt[:40]]
print(f" RAW first durations: {raw}")
continue
print_bits(bits, f"Manchester decoded ({len(bits)} bits)")
# Step 5: identify protocol
identify_protocol(bits, baud_rate, carrier.get('carrier_hz', 0))
# Step 6: apply explicit decoder if requested
if protocol_hint == 'hitag2':
decode_hitag2_explicit(bits)
print(f"\n{'='*60}")
print("SUMMARY")
print('='*60)
print(f" Carrier : {carrier.get('carrier_hz', 0):,} Hz")
print(f" Packets : {len(packets)}")
print(f" Total edges: {len(edges)}")
print()
print("NEXT STEPS:")
print(" 1. If you see 'Keyless challenge candidate' -> use the 0xXXXXXXXX value")
print(" with your AUT64 implementation to compute the expected response.")
print(" 2. If Hitag2 is detected -> the protocol is HMAC-SHA1 / AUT64 depending")
print(" on the generation.")
print(" 3. If carrier is 125 kHz but no packets appear -> car is not emitting")
print(" a challenge (get closer, < 30 cm).")
def decode_hitag2_explicit(bits: list):
"""Explicit Hitag2 frame decode when --protocol hitag2 is specified."""
print("\n[*] Hitag2 frame decode:")
if len(bits) < 5:
return
print(f" Start of frame : {bits[0]}")
if len(bits) > 5:
cmd = 0
for b in bits[1:5]:
cmd = (cmd << 1) | b
cmds = {
0b0001: 'REQUEST',
0b0011: 'SELECT',
0b0101: 'READ',
0b1001: 'WRITE'
}
print(f" Command : 0b{cmd:04b} = {cmds.get(cmd, 'UNKNOWN')}")
if len(bits) >= 37:
uid_bits = bits[5:37]
uid_val = 0
for b in uid_bits:
uid_val = (uid_val << 1) | b
print(f" UID candidate : 0x{uid_val:08X}")
# -- CLI ----------------------------------------------------------------------
def main():
parser = argparse.ArgumentParser(
description='Analyze LF captures from the Flipper Zero LF Sniffer FAP',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python3 lf_analyze.py capture_0000.csv
python3 lf_analyze.py capture_0000.csv --verbose
python3 lf_analyze.py capture_0000.csv --protocol hitag2
"""
)
parser.add_argument('capture', help='CSV file from LF Sniffer FAP')
parser.add_argument('--verbose', '-v', action='store_true')
parser.add_argument('--protocol',
choices=['hitag2', 'em4100', 'Keyless', 'auto'],
default='auto',
help='Force a specific protocol decoder (default: auto)')
args = parser.parse_args()
if not Path(args.capture).exists():
print(f"ERROR: {args.capture} not found")
sys.exit(1)
analyze_capture(
args.capture,
verbose=args.verbose,
protocol_hint=args.protocol if args.protocol != 'auto' else None
)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,446 @@
#include "lf_sniffer.h"
#include <furi.h>
#include <furi_hal_gpio.h>
#include <furi_hal_resources.h>
#include <furi_hal_cortex.h>
#include <gui/gui.h>
#include <notification/notification_messages.h>
#include <storage/storage.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define TAG "LFSniffer"
#define DWT_CYCCNT (*(volatile uint32_t*)0xE0001004)
#define DWT_CTRL (*(volatile uint32_t*)0xE0001000)
#define DEMCR (*(volatile uint32_t*)0xE000EDFC)
static inline void dwt_init(void) {
DEMCR |= (1U << 24);
DWT_CYCCNT = 0;
DWT_CTRL |= 1U;
}
static inline uint32_t dwt_us(void) {
return DWT_CYCCNT / 64;
}
static volatile LFEdge* g_edges = NULL;
static volatile uint32_t g_edge_count = 0;
static volatile uint32_t g_last_time = 0;
static volatile bool g_active = false;
static volatile bool g_overflow = false;
static void lf_gpio_isr(void* ctx) {
UNUSED(ctx);
if(!g_active) return;
uint32_t now = dwt_us();
uint32_t delta = now - g_last_time;
g_last_time = now;
if(g_edge_count >= LF_MAX_EDGES) {
g_overflow = true;
g_active = false;
return;
}
bool current_level = furi_hal_gpio_read(LF_INPUT_PIN);
g_edges[g_edge_count].duration_us = delta;
g_edges[g_edge_count].level = !current_level;
g_edge_count++;
}
static void draw_callback(Canvas* canvas, void* ctx) {
LFSnifferApp* app = ctx;
furi_mutex_acquire(app->mutex, FuriWaitForever);
canvas_clear(canvas);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 2, 12, "KeylessGO Sniffer");
canvas_set_font(canvas, FontSecondary);
switch(app->state) {
case LFStateIdle:
canvas_draw_str(canvas, 2, 26, "Pin: PB2 (header pin 6)");
canvas_draw_str(canvas, 2, 36, "OK = Start capture");
canvas_draw_str(canvas, 2, 46, "Approach car without key");
canvas_draw_str(canvas, 2, 58, "Back = Exit");
break;
case LFStateCapturing: {
char buf[32];
canvas_draw_str(canvas, 2, 26, "CAPTURING...");
snprintf(buf, sizeof(buf), "Edges: %lu", (unsigned long)app->edge_count);
canvas_draw_str(canvas, 2, 36, buf);
snprintf(buf, sizeof(buf), "Packets: %lu", (unsigned long)app->packet_count);
canvas_draw_str(canvas, 2, 46, buf);
canvas_draw_str(canvas, 2, 58, "OK/Back = Stop");
break;
}
case LFStateDone: {
char buf[48];
snprintf(buf, sizeof(buf), "Edges:%lu Pkts:%lu",
(unsigned long)app->edge_count,
(unsigned long)app->packet_count);
canvas_draw_str(canvas, 2, 26, buf);
snprintf(buf, sizeof(buf), "Min:%luus Max:%luus",
(unsigned long)app->min_pulse_us,
(unsigned long)app->max_pulse_us);
canvas_draw_str(canvas, 2, 36, buf);
canvas_draw_str(canvas, 2, 46, "OK = Save to SD");
canvas_draw_str(canvas, 2, 58, "Back = Discard");
break;
}
case LFStateSaving:
canvas_draw_str(canvas, 2, 26, "Saving...");
canvas_draw_str(canvas, 2, 36, app->filename);
break;
case LFStateSaved:
canvas_draw_str(canvas, 2, 26, "Saved OK:");
canvas_draw_str(canvas, 2, 36, app->filename);
canvas_draw_str(canvas, 2, 46, app->status_msg);
canvas_draw_str(canvas, 2, 58, "Back = New capture");
break;
case LFStateError:
canvas_draw_str(canvas, 2, 26, "ERROR:");
canvas_draw_str(canvas, 2, 36, app->status_msg);
canvas_draw_str(canvas, 2, 58, "Back = Retry");
break;
}
furi_mutex_release(app->mutex);
}
static void input_callback(InputEvent* event, void* ctx) {
LFSnifferApp* app = ctx;
furi_message_queue_put(app->queue, event, FuriWaitForever);
}
static void lf_analyze_packets(LFSnifferApp* app) {
app->packet_count = 0;
app->min_pulse_us = 0xFFFFFFFF;
app->max_pulse_us = 0;
app->carrier_pulses = 0;
app->data_pulses = 0;
if(app->edge_count < 4) return;
uint32_t pkt_start = 0;
bool in_packet = false;
for(uint32_t i = 1; i < app->edge_count; i++) {
uint32_t dur = app->edges[i].duration_us;
if(dur < app->min_pulse_us) app->min_pulse_us = dur;
if(dur > app->max_pulse_us) app->max_pulse_us = dur;
if(dur < 12) {
app->carrier_pulses++;
} else if(dur < 15) {
app->data_pulses++;
}
if(dur > LF_GAP_THRESHOLD_US) {
if(in_packet && i > pkt_start + 8) {
if(app->packet_count < LF_MAX_PACKETS) {
app->packets[app->packet_count].start_idx = pkt_start;
app->packets[app->packet_count].edge_count = i - pkt_start;
app->packets[app->packet_count].duration_us = 0;
for(uint32_t j = pkt_start; j < i; j++)
app->packets[app->packet_count].duration_us +=
app->edges[j].duration_us;
app->packet_count++;
}
}
in_packet = false;
pkt_start = i;
} else {
if(!in_packet) {
in_packet = true;
pkt_start = i;
}
}
}
if(in_packet && app->edge_count > pkt_start + 8) {
if(app->packet_count < LF_MAX_PACKETS) {
app->packets[app->packet_count].start_idx = pkt_start;
app->packets[app->packet_count].edge_count = app->edge_count - pkt_start;
app->packets[app->packet_count].duration_us = 0;
for(uint32_t j = pkt_start; j < app->edge_count; j++)
app->packets[app->packet_count].duration_us +=
app->edges[j].duration_us;
app->packet_count++;
}
}
}
static bool lf_save_csv(LFSnifferApp* app) {
Storage* storage = furi_record_open(RECORD_STORAGE);
storage_common_mkdir(storage, "/ext/keyless_sniffer");
snprintf(app->filename, sizeof(app->filename),
"/ext/keyless_sniffer/capture_%04lu.csv",
(unsigned long)app->file_index);
File* file = storage_file_alloc(storage);
bool ok = false;
if(storage_file_open(file, app->filename, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
const char* header = "index,duration_us,level,note\n";
storage_file_write(file, header, strlen(header));
char meta[128];
snprintf(meta, sizeof(meta),
"# Keyless Sniffer capture -- edges:%lu packets:%lu\n",
(unsigned long)app->edge_count,
(unsigned long)app->packet_count);
storage_file_write(file, meta, strlen(meta));
snprintf(meta, sizeof(meta),
"# min_us:%lu max_us:%lu carrier:%lu data:%lu\n",
(unsigned long)app->min_pulse_us,
(unsigned long)app->max_pulse_us,
(unsigned long)app->carrier_pulses,
(unsigned long)app->data_pulses);
storage_file_write(file, meta, strlen(meta));
uint32_t pkt_idx = 0;
char line[64];
for(uint32_t i = 0; i < app->edge_count; i++) {
const char* note = "";
if(pkt_idx < app->packet_count &&
app->packets[pkt_idx].start_idx == i) {
note = "PKT_START";
pkt_idx++;
}
snprintf(line, sizeof(line),
"%lu,%lu,%d,%s\n",
(unsigned long)i,
(unsigned long)app->edges[i].duration_us,
app->edges[i].level ? 1 : 0,
note);
storage_file_write(file, line, strlen(line));
}
storage_file_write(file, "# PACKETS\n", 10);
for(uint32_t p = 0; p < app->packet_count; p++) {
snprintf(meta, sizeof(meta),
"# PKT %lu: start=%lu edges=%lu dur=%luus\n",
(unsigned long)p,
(unsigned long)app->packets[p].start_idx,
(unsigned long)app->packets[p].edge_count,
(unsigned long)app->packets[p].duration_us);
storage_file_write(file, meta, strlen(meta));
}
storage_file_close(file);
ok = true;
snprintf(app->status_msg, sizeof(app->status_msg),
"%lu edges, %lu packets",
(unsigned long)app->edge_count,
(unsigned long)app->packet_count);
} else {
snprintf(app->status_msg, sizeof(app->status_msg), "Failed to open file");
}
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
return ok;
}
static void lf_start_capture(LFSnifferApp* app) {
furi_mutex_acquire(app->mutex, FuriWaitForever);
app->edge_count = 0;
app->packet_count = 0;
app->total_time_us = 0;
app->min_pulse_us = 0xFFFFFFFF;
app->max_pulse_us = 0;
app->carrier_pulses = 0;
app->data_pulses = 0;
g_overflow = false;
g_edges = app->edges;
g_edge_count = 0;
g_last_time = dwt_us();
g_active = true;
furi_hal_gpio_init(LF_INPUT_PIN, GpioModeInterruptRiseFall,
GpioPullUp, GpioSpeedVeryHigh);
furi_hal_gpio_add_int_callback(LF_INPUT_PIN, lf_gpio_isr, NULL);
app->state = LFStateCapturing;
furi_mutex_release(app->mutex);
notification_message(app->notif, &sequence_blink_green_100);
FURI_LOG_I(TAG, "Capture started");
}
static void lf_stop_capture(LFSnifferApp* app) {
g_active = false;
furi_hal_gpio_remove_int_callback(LF_INPUT_PIN);
furi_hal_gpio_init(LF_INPUT_PIN, GpioModeInput, GpioPullUp, GpioSpeedLow);
furi_mutex_acquire(app->mutex, FuriWaitForever);
app->edge_count = g_edge_count;
lf_analyze_packets(app);
app->state = LFStateDone;
furi_mutex_release(app->mutex);
notification_message(app->notif, &sequence_blink_blue_100);
FURI_LOG_I(TAG, "Capture stopped: %lu edges, %lu packets",
(unsigned long)app->edge_count,
(unsigned long)app->packet_count);
}
int32_t lf_sniffer_app(void* p) {
UNUSED(p);
dwt_init();
LFSnifferApp* app = malloc(sizeof(LFSnifferApp));
furi_check(app != NULL);
memset(app, 0, sizeof(LFSnifferApp));
app->edges = malloc(LF_MAX_EDGES * sizeof(LFEdge));
furi_check(app->edges != NULL);
app->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
app->queue = furi_message_queue_alloc(16, sizeof(InputEvent));
app->notif = furi_record_open(RECORD_NOTIFICATION);
app->view_port = view_port_alloc();
app->state = LFStateIdle;
app->file_index = 0;
view_port_draw_callback_set(app->view_port, draw_callback, app);
view_port_input_callback_set(app->view_port, input_callback, app);
app->gui = furi_record_open(RECORD_GUI);
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
FURI_LOG_I(TAG, "Keyless Sniffer started");
InputEvent event;
bool running = true;
while(running) {
if(app->state == LFStateCapturing) {
furi_mutex_acquire(app->mutex, FuriWaitForever);
app->edge_count = g_edge_count;
if(g_overflow) {
furi_mutex_release(app->mutex);
lf_stop_capture(app);
furi_mutex_acquire(app->mutex, FuriWaitForever);
snprintf(app->status_msg, sizeof(app->status_msg),
"Buffer full (%d edges)", LF_MAX_EDGES);
}
furi_mutex_release(app->mutex);
view_port_update(app->view_port);
}
FuriStatus status = furi_message_queue_get(app->queue, &event, 100);
if(status != FuriStatusOk) continue;
if(event.type != InputTypeShort && event.type != InputTypeLong) continue;
switch(app->state) {
case LFStateIdle:
if(event.key == InputKeyOk) {
lf_start_capture(app);
view_port_update(app->view_port);
} else if(event.key == InputKeyBack) {
running = false;
}
break;
case LFStateCapturing:
if(event.key == InputKeyOk || event.key == InputKeyBack) {
lf_stop_capture(app);
view_port_update(app->view_port);
}
break;
case LFStateDone:
if(event.key == InputKeyOk) {
furi_mutex_acquire(app->mutex, FuriWaitForever);
app->state = LFStateSaving;
furi_mutex_release(app->mutex);
view_port_update(app->view_port);
bool saved = lf_save_csv(app);
furi_mutex_acquire(app->mutex, FuriWaitForever);
app->state = saved ? LFStateSaved : LFStateError;
app->file_index++;
furi_mutex_release(app->mutex);
view_port_update(app->view_port);
if(saved) {
notification_message(app->notif, &sequence_success);
FURI_LOG_I(TAG, "Saved: %s", app->filename);
}
} else if(event.key == InputKeyBack) {
furi_mutex_acquire(app->mutex, FuriWaitForever);
app->state = LFStateIdle;
app->edge_count = 0;
app->packet_count = 0;
furi_mutex_release(app->mutex);
view_port_update(app->view_port);
}
break;
case LFStateSaved:
case LFStateError:
if(event.key == InputKeyBack) {
furi_mutex_acquire(app->mutex, FuriWaitForever);
app->state = LFStateIdle;
app->edge_count = 0;
app->packet_count = 0;
furi_mutex_release(app->mutex);
view_port_update(app->view_port);
}
break;
default:
break;
}
}
if(app->state == LFStateCapturing) {
g_active = false;
furi_hal_gpio_remove_int_callback(LF_INPUT_PIN);
furi_hal_gpio_init(LF_INPUT_PIN, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
}
view_port_enabled_set(app->view_port, false);
gui_remove_view_port(app->gui, app->view_port);
view_port_free(app->view_port);
furi_record_close(RECORD_GUI);
furi_record_close(RECORD_NOTIFICATION);
furi_mutex_free(app->mutex);
furi_message_queue_free(app->queue);
free(app->edges);
free(app);
return 0;
}

View File

@@ -0,0 +1,80 @@
#pragma once
#include <furi.h>
#include <gui/gui.h>
#include <gui/view_port.h>
#include <notification/notification.h>
#include <input/input.h>
#include <storage/storage.h>
#include <furi_hal_gpio.h>
#include <furi_hal_resources.h>
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define LF_MAX_EDGES 4096
#define LF_GAP_THRESHOLD_US 5000
#define LF_CARRIER_HZ 125000
#define LF_BIT_PERIOD_US 250
#define LF_TIMEOUT_MS 10000
#define LF_MAX_PACKETS 16
#define LF_INPUT_PIN (&gpio_ext_pb2)
typedef struct {
uint32_t duration_us;
bool level;
} LFEdge;
typedef struct {
uint32_t start_idx;
uint32_t edge_count;
uint32_t duration_us;
} LFPacket;
typedef enum {
LFStateIdle,
LFStateCapturing,
LFStateDone,
LFStateSaving,
LFStateSaved,
LFStateError,
} LFState;
typedef struct {
Gui* gui;
ViewPort* view_port;
FuriMessageQueue* queue;
FuriMutex* mutex;
NotificationApp* notif;
LFState state;
char status_msg[64];
LFEdge* edges;
uint32_t edge_count;
uint32_t total_time_us;
LFPacket packets[LF_MAX_PACKETS];
uint32_t packet_count;
uint32_t last_edge_time_us;
bool capturing;
uint32_t carrier_pulses;
uint32_t data_pulses;
uint32_t min_pulse_us;
uint32_t max_pulse_us;
char filename[128];
uint32_t file_index;
} LFSnifferApp;
int32_t lf_sniffer_app(void* p);
#ifdef __cplusplus
}
#endif

View File

@@ -9,6 +9,7 @@ App(
"nfc",
"subghz",
"rolljam",
"lf_sniffer",
"subghz_bruteforcer",
"archive",
"subghz_remote",

View File

@@ -93,6 +93,7 @@ typedef enum {
SubGhzViewIdFrequencyAnalyzer,
SubGhzViewIdReadRAW,
SubGhzViewIdPsaDecrypt,
SubGhzViewIdKeeloqDecrypt,
} SubGhzViewId;

View File

@@ -30,4 +30,7 @@ ADD_SCENE(subghz, protocol_list, ProtocolList)
ADD_SCENE(subghz, keeloq_keys, KeeloqKeys)
ADD_SCENE(subghz, keeloq_key_edit, KeeloqKeyEdit)
ADD_SCENE(subghz, psa_decrypt, PsaDecrypt)
ADD_SCENE(subghz, keeloq_decrypt, KeeloqDecrypt)
ADD_SCENE(subghz, keeloq_bf2, KeeloqBf2)
ADD_SCENE(subghz, kl_bf_cleanup, KlBfCleanup)
ADD_SCENE(subghz, counter_bf, CounterBf)

View File

@@ -5,9 +5,10 @@
#define TAG "SubGhzCounterBf"
// How many ticks to wait between transmissions (1 tick ~100ms)
#define COUNTER_BF_TX_INTERVAL_TICKS 3
#define COUNTER_BF_TX_INTERVAL_TICKS 5
typedef enum {
CounterBfStateWarning,
CounterBfStateIdle,
CounterBfStateRunning,
CounterBfStateStopped,
@@ -22,8 +23,16 @@ typedef struct {
uint32_t tick_wait;
} CounterBfContext;
#define CounterBfEventStart (0xC0)
#define CounterBfEventStop (0xC1)
#define CounterBfEventStart (0xC0)
#define CounterBfEventStop (0xC1)
#define CounterBfEventWarningOk (0xC2)
static void counter_bf_warning_callback(GuiButtonType result, InputType type, void* context) {
SubGhz* subghz = context;
if(result == GuiButtonTypeCenter && type == InputTypeShort) {
view_dispatcher_send_custom_event(subghz->view_dispatcher, CounterBfEventWarningOk);
}
}
static void counter_bf_widget_callback(GuiButtonType result, InputType type, void* context) {
SubGhz* subghz = context;
@@ -32,18 +41,36 @@ static void counter_bf_widget_callback(GuiButtonType result, InputType type, voi
}
}
static void counter_bf_draw_warning(SubGhz* subghz) {
widget_reset(subghz->widget);
widget_add_string_multiline_element(
subghz->widget,
64,
20,
AlignCenter,
AlignCenter,
FontSecondary,
"WARNING:\nThis may desync\nyour fob!");
widget_add_button_element(
subghz->widget,
GuiButtonTypeCenter,
"OK",
counter_bf_warning_callback,
subghz);
}
static void counter_bf_draw(SubGhz* subghz, CounterBfContext* ctx) {
widget_reset(subghz->widget);
FuriString* str = furi_string_alloc();
furi_string_printf(
str,
"Counter BruteForce\n"
"Cnt: 0x%08lX\n"
"Sent: %lu pkts\n"
"Start: 0x%08lX",
ctx->current_cnt,
ctx->packets_sent,
ctx->start_cnt);
"Cnt: 0x%06lX\n"
"Start: 0x%06lX\n"
"Sent: %lu",
ctx->current_cnt & 0xFFFFFF,
ctx->start_cnt & 0xFFFFFF,
ctx->packets_sent);
widget_add_string_multiline_element(
subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(str));
furi_string_free(str);
@@ -57,14 +84,12 @@ static void counter_bf_draw(SubGhz* subghz, CounterBfContext* ctx) {
}
static void counter_bf_save(SubGhz* subghz, CounterBfContext* ctx) {
// Escribir el Cnt final directamente en el archivo .sub en disco.
// No usar subghz_save_protocol_to_file() porque ese serializa el estado
// actual del encoder (que puede tener el Cnt ya incrementado internamente).
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* file_fff = flipper_format_buffered_file_alloc(storage);
if(flipper_format_buffered_file_open_existing(
file_fff, furi_string_get_cstr(subghz->file_path))) {
if(!flipper_format_update_uint32(file_fff, "Cnt", &ctx->current_cnt, 1)) {
uint32_t cnt = ctx->current_cnt & 0xFFFFFF;
if(!flipper_format_update_uint32(file_fff, "Cnt", &cnt, 1)) {
FURI_LOG_E(TAG, "Failed to update Cnt in .sub file");
}
} else {
@@ -77,16 +102,15 @@ static void counter_bf_save(SubGhz* subghz, CounterBfContext* ctx) {
static void counter_bf_send(SubGhz* subghz, CounterBfContext* ctx) {
subghz_txrx_stop(subghz->txrx);
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
uint32_t delta = (ctx->current_cnt - ctx->start_cnt) & 0xFFFFFF;
furi_hal_subghz_set_rolling_counter_mult((int32_t)delta);
subghz_block_generic_global_counter_override_set(ctx->current_cnt & 0xFFFFFF);
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
uint32_t repeat = 20;
flipper_format_rewind(fff);
flipper_format_update_uint32(fff, "Repeat", &repeat, 1);
// Actualizar Cnt DESPUES de Repeat (update es secuencial en el buffer)
flipper_format_rewind(fff);
flipper_format_update_uint32(fff, "Cnt", &ctx->current_cnt, 1);
subghz_tx_start(subghz, fff);
ctx->packets_sent++;
@@ -98,42 +122,38 @@ void subghz_scene_counter_bf_on_enter(void* context) {
CounterBfContext* ctx = malloc(sizeof(CounterBfContext));
memset(ctx, 0, sizeof(CounterBfContext));
ctx->state = CounterBfStateIdle;
ctx->state = CounterBfStateWarning;
ctx->step = 1;
furi_hal_subghz_set_rolling_counter_mult(0);
subghz_key_load(subghz, furi_string_get_cstr(subghz->file_path), false);
// FIX: Leer el Cnt DIRECTAMENTE del archivo en disco con un FlipperFormat
// propio, completamente separado del fff en memoria (que puede tener el Cnt
// modificado por TXs previas y no refleja el estado real del .sub).
{
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* file_fff = flipper_format_buffered_file_alloc(storage);
if(flipper_format_buffered_file_open_existing(
file_fff, furi_string_get_cstr(subghz->file_path))) {
uint32_t cnt = 0;
if(flipper_format_read_uint32(file_fff, "Cnt", &cnt, 1)) {
ctx->current_cnt = cnt;
ctx->start_cnt = cnt;
} else {
FURI_LOG_W(TAG, "Cnt field not found in file");
}
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
flipper_format_rewind(fff);
uint32_t cnt = 0;
if(flipper_format_read_uint32(fff, "Cnt", &cnt, 1)) {
ctx->current_cnt = cnt & 0xFFFFFF;
ctx->start_cnt = cnt & 0xFFFFFF;
} else {
FURI_LOG_E(TAG, "Failed to open .sub file for Cnt read");
FURI_LOG_W(TAG, "Cnt not in fff after key_load, reading from disk");
Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* file_fff = flipper_format_buffered_file_alloc(storage);
if(flipper_format_buffered_file_open_existing(
file_fff, furi_string_get_cstr(subghz->file_path))) {
if(flipper_format_read_uint32(file_fff, "Cnt", &cnt, 1)) {
ctx->current_cnt = cnt & 0xFFFFFF;
ctx->start_cnt = cnt & 0xFFFFFF;
}
}
flipper_format_free(file_fff);
furi_record_close(RECORD_STORAGE);
}
flipper_format_free(file_fff);
furi_record_close(RECORD_STORAGE);
}
scene_manager_set_scene_state(
subghz->scene_manager, SubGhzSceneCounterBf, (uint32_t)(uintptr_t)ctx);
// Deshabilitar auto-increment del protocolo para controlar el Cnt manualmente
furi_hal_subghz_set_rolling_counter_mult(0);
// Recargar el protocolo DESPUES de haber leído el Cnt del disco,
// para preparar el fff para TX sin que pise nuestro valor leído.
subghz_key_load(subghz, furi_string_get_cstr(subghz->file_path), false);
counter_bf_draw(subghz, ctx);
counter_bf_draw_warning(subghz);
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdWidget);
}
@@ -144,15 +164,21 @@ bool subghz_scene_counter_bf_on_event(void* context, SceneManagerEvent event) {
if(!ctx) return false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == CounterBfEventWarningOk) {
ctx->state = CounterBfStateIdle;
counter_bf_draw(subghz, ctx);
return true;
}
if(event.event == CounterBfEventStart) {
if(ctx->state == CounterBfStateWarning) return true;
if(ctx->state != CounterBfStateRunning) {
ctx->state = CounterBfStateRunning;
ctx->tick_wait = 0;
subghz->state_notifications = SubGhzNotificationStateTx;
counter_bf_send(subghz, ctx);
} else {
// FIX 2: Al detener, guardar el contador actual en el .sub
// para que al volver a emular manualmente continúe desde acá.
ctx->state = CounterBfStateStopped;
subghz_txrx_stop(subghz->txrx);
subghz->state_notifications = SubGhzNotificationStateIDLE;
@@ -167,19 +193,24 @@ bool subghz_scene_counter_bf_on_event(void* context, SceneManagerEvent event) {
if(ctx->tick_wait > 0) {
ctx->tick_wait--;
} else {
ctx->current_cnt += ctx->step;
ctx->current_cnt = (ctx->current_cnt + ctx->step) & 0xFFFFFF;
counter_bf_send(subghz, ctx);
counter_bf_save(subghz, ctx);
counter_bf_draw(subghz, ctx);
}
}
return true;
} else if(event.type == SceneManagerEventTypeBack) {
if(ctx->state == CounterBfStateWarning) {
furi_hal_subghz_set_rolling_counter_mult(1);
free(ctx);
scene_manager_previous_scene(subghz->scene_manager);
return true;
}
subghz_txrx_stop(subghz->txrx);
subghz->state_notifications = SubGhzNotificationStateIDLE;
// FIX 2 (también en Back): guardar siempre al salir
counter_bf_save(subghz, ctx);
furi_hal_subghz_set_rolling_counter_mult(1);
free(ctx);
scene_manager_previous_scene(subghz->scene_manager);

View File

@@ -0,0 +1,253 @@
#include "../subghz_i.h"
#include <lib/subghz/protocols/keeloq.h>
#include <dialogs/dialogs.h>
enum {
KlBf2IndexLoadSig1,
KlBf2IndexLoadSig2,
KlBf2IndexType,
KlBf2IndexStartBf,
};
static const char* kl_bf2_type_labels[] = {
"Type: Auto (6>7>8)",
"Type: 6 (Serial 1)",
"Type: 7 (Serial 2)",
"Type: 8 (Serial 3)",
};
static const uint8_t kl_bf2_type_values[] = {0, 6, 7, 8};
static bool kl_bf2_extract_key(SubGhz* subghz, uint32_t* out_fix, uint32_t* out_hop) {
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
flipper_format_rewind(fff);
uint8_t key_data[8] = {0};
if(!flipper_format_read_hex(fff, "Key", key_data, 8)) return false;
*out_fix = ((uint32_t)key_data[0] << 24) | ((uint32_t)key_data[1] << 16) |
((uint32_t)key_data[2] << 8) | key_data[3];
*out_hop = ((uint32_t)key_data[4] << 24) | ((uint32_t)key_data[5] << 16) |
((uint32_t)key_data[6] << 8) | key_data[7];
return true;
}
static bool kl_bf2_is_keeloq(SubGhz* subghz) {
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
flipper_format_rewind(fff);
FuriString* proto = furi_string_alloc();
bool ok = flipper_format_read_string(fff, "Protocol", proto) &&
furi_string_equal_str(proto, "KeeLoq");
furi_string_free(proto);
return ok;
}
static void kl_bf2_submenu_callback(void* context, uint32_t index) {
SubGhz* subghz = context;
view_dispatcher_send_custom_event(subghz->view_dispatcher, index);
}
static bool kl_bf2_load_signal(SubGhz* subghz, FuriString* out_path) {
DialogsFileBrowserOptions browser_options;
dialog_file_browser_set_basic_options(
&browser_options, SUBGHZ_APP_FILENAME_EXTENSION, &I_sub1_10px);
browser_options.base_path = SUBGHZ_APP_FOLDER;
FuriString* selected = furi_string_alloc();
furi_string_set(selected, SUBGHZ_APP_FOLDER);
bool res = dialog_file_browser_show(subghz->dialogs, selected, selected, &browser_options);
if(res) {
res = subghz_key_load(subghz, furi_string_get_cstr(selected), true);
if(res) {
furi_string_set(out_path, selected);
}
}
furi_string_free(selected);
return res;
}
static void kl_bf2_rebuild_menu(SubGhz* subghz) {
submenu_reset(subghz->submenu);
char label1[64];
char label2[64];
if(subghz->keeloq_bf2.sig1_loaded) {
FuriString* name = furi_string_alloc();
path_extract_filename(subghz->keeloq_bf2.sig1_path, name, true);
snprintf(label1, sizeof(label1), "Sig 1: %s", furi_string_get_cstr(name));
furi_string_free(name);
} else {
snprintf(label1, sizeof(label1), "Load Signal 1");
}
if(subghz->keeloq_bf2.sig2_loaded) {
FuriString* name = furi_string_alloc();
path_extract_filename(subghz->keeloq_bf2.sig2_path, name, true);
snprintf(label2, sizeof(label2), "Sig 2: %s", furi_string_get_cstr(name));
furi_string_free(name);
} else {
snprintf(label2, sizeof(label2), "Load Signal 2");
}
submenu_add_item(
subghz->submenu, label1, KlBf2IndexLoadSig1,
kl_bf2_submenu_callback, subghz);
submenu_add_item(
subghz->submenu, label2, KlBf2IndexLoadSig2,
kl_bf2_submenu_callback, subghz);
int type_idx = 0;
for(int i = 0; i < 4; i++) {
if(kl_bf2_type_values[i] == subghz->keeloq_bf2.learn_type) {
type_idx = i;
break;
}
}
submenu_add_item(
subghz->submenu, kl_bf2_type_labels[type_idx], KlBf2IndexType,
kl_bf2_submenu_callback, subghz);
if(subghz->keeloq_bf2.sig1_loaded && subghz->keeloq_bf2.sig2_loaded) {
submenu_add_item(
subghz->submenu, "Start BF", KlBf2IndexStartBf,
kl_bf2_submenu_callback, subghz);
}
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdMenu);
}
void subghz_scene_keeloq_bf2_on_enter(void* context) {
SubGhz* subghz = context;
subghz->keeloq_bf2.sig1_loaded = false;
subghz->keeloq_bf2.sig2_loaded = false;
subghz->keeloq_bf2.learn_type = 0;
kl_bf2_rebuild_menu(subghz);
}
bool subghz_scene_keeloq_bf2_on_event(void* context, SceneManagerEvent event) {
SubGhz* subghz = context;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == KlBf2IndexLoadSig1) {
FuriString* path = furi_string_alloc();
if(kl_bf2_load_signal(subghz, path)) {
if(!kl_bf2_is_keeloq(subghz)) {
dialog_message_show_storage_error(
subghz->dialogs, "Not a KeeLoq\nprotocol file");
furi_string_free(path);
kl_bf2_rebuild_menu(subghz);
return true;
}
uint32_t fix, hop;
if(!kl_bf2_extract_key(subghz, &fix, &hop)) {
dialog_message_show_storage_error(
subghz->dialogs, "Cannot read Key\nfrom file");
furi_string_free(path);
kl_bf2_rebuild_menu(subghz);
return true;
}
subghz->keeloq_bf2.fix = fix;
subghz->keeloq_bf2.hop1 = hop;
subghz->keeloq_bf2.serial = fix & 0x0FFFFFFF;
subghz->keeloq_bf2.sig1_loaded = true;
furi_string_set(subghz->keeloq_bf2.sig1_path, path);
subghz->keeloq_bf2.sig2_loaded = false;
}
furi_string_free(path);
kl_bf2_rebuild_menu(subghz);
return true;
} else if(event.event == KlBf2IndexLoadSig2) {
if(!subghz->keeloq_bf2.sig1_loaded) {
dialog_message_show_storage_error(
subghz->dialogs, "Load Signal 1 first");
kl_bf2_rebuild_menu(subghz);
return true;
}
FuriString* path = furi_string_alloc();
if(kl_bf2_load_signal(subghz, path)) {
if(!kl_bf2_is_keeloq(subghz)) {
dialog_message_show_storage_error(
subghz->dialogs, "Not a KeeLoq\nprotocol file");
furi_string_free(path);
kl_bf2_rebuild_menu(subghz);
return true;
}
uint32_t fix2, hop2;
if(!kl_bf2_extract_key(subghz, &fix2, &hop2)) {
dialog_message_show_storage_error(
subghz->dialogs, "Cannot read Key\nfrom file");
furi_string_free(path);
kl_bf2_rebuild_menu(subghz);
return true;
}
uint32_t serial2 = fix2 & 0x0FFFFFFF;
if(serial2 != subghz->keeloq_bf2.serial) {
dialog_message_show_storage_error(
subghz->dialogs, "Serial mismatch!\nMust be same remote");
furi_string_free(path);
kl_bf2_rebuild_menu(subghz);
return true;
}
if(hop2 == subghz->keeloq_bf2.hop1) {
dialog_message_show_storage_error(
subghz->dialogs, "Same hop code!\nUse a different\ncapture");
furi_string_free(path);
kl_bf2_rebuild_menu(subghz);
return true;
}
subghz->keeloq_bf2.hop2 = hop2;
subghz->keeloq_bf2.sig2_loaded = true;
furi_string_set(subghz->keeloq_bf2.sig2_path, path);
}
furi_string_free(path);
kl_bf2_rebuild_menu(subghz);
return true;
} else if(event.event == KlBf2IndexType) {
uint8_t cur = subghz->keeloq_bf2.learn_type;
if(cur == 0) cur = 6;
else if(cur == 6) cur = 7;
else if(cur == 7) cur = 8;
else cur = 0;
subghz->keeloq_bf2.learn_type = cur;
kl_bf2_rebuild_menu(subghz);
return true;
} else if(event.event == KlBf2IndexStartBf) {
if(!subghz->keeloq_bf2.sig1_loaded || !subghz->keeloq_bf2.sig2_loaded) {
return true;
}
if(!subghz_key_load(
subghz,
furi_string_get_cstr(subghz->keeloq_bf2.sig1_path),
true)) {
dialog_message_show_storage_error(
subghz->dialogs, "Cannot reload\nSignal 1");
kl_bf2_rebuild_menu(subghz);
return true;
}
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneKeeloqDecrypt);
return true;
}
}
return false;
}
void subghz_scene_keeloq_bf2_on_exit(void* context) {
SubGhz* subghz = context;
submenu_reset(subghz->submenu);
}

View File

@@ -0,0 +1,284 @@
#include "../subghz_i.h"
#include "../helpers/subghz_txrx_i.h"
#include <lib/subghz/protocols/keeloq.h>
#include <lib/subghz/protocols/keeloq_common.h>
#include <lib/subghz/environment.h>
#include <lib/subghz/subghz_keystore.h>
#include <furi.h>
#include <bt/bt_service/bt.h>
#define KL_DECRYPT_EVENT_DONE (0xD2)
#define KL_DECRYPT_EVENT_CANDIDATE (0xD3)
#define KL_TOTAL_KEYS 0x100000000ULL
#define KL_MSG_BF_REQUEST 0x10
#define KL_MSG_BF_PROGRESS 0x11
#define KL_MSG_BF_RESULT 0x12
#define KL_MSG_BF_CANCEL 0x13
typedef struct {
SubGhz* subghz;
volatile bool cancel;
uint32_t start_tick;
bool success;
FuriString* result;
uint32_t fix;
uint32_t hop;
uint32_t serial;
uint8_t btn;
uint16_t disc;
uint32_t hop2;
uint32_t candidate_count;
uint64_t recovered_mfkey;
uint16_t recovered_type;
uint32_t recovered_cnt;
bool ble_offload;
} KlDecryptCtx;
static void kl_ble_data_received(uint8_t* data, uint16_t size, void* context) {
KlDecryptCtx* ctx = context;
if(size < 1 || ctx->cancel) return;
if(data[0] == KL_MSG_BF_PROGRESS && size >= 10) {
uint32_t keys_tested, keys_per_sec;
memcpy(&keys_tested, data + 2, 4);
memcpy(&keys_per_sec, data + 6, 4);
uint32_t elapsed_sec = (furi_get_tick() - ctx->start_tick) / 1000;
uint32_t remaining = (keys_tested > 0) ? (0xFFFFFFFFU - keys_tested) : 0xFFFFFFFFU;
uint32_t eta_sec = (keys_per_sec > 0) ? (remaining / keys_per_sec) : 0;
uint8_t pct = (uint8_t)((uint64_t)keys_tested * 100 / 0xFFFFFFFFULL);
subghz_view_keeloq_decrypt_update_stats(
ctx->subghz->subghz_keeloq_decrypt, pct, keys_tested, keys_per_sec, elapsed_sec, eta_sec);
} else if(data[0] == KL_MSG_BF_RESULT && size >= 26) {
uint8_t found = data[1];
if(found == 1) {
uint64_t mfkey = 0;
uint32_t cnt = 0;
memcpy(&mfkey, data + 2, 8);
memcpy(&cnt, data + 18, 4);
uint16_t learn_type = (size >= 27) ? data[26] : 6;
ctx->candidate_count++;
ctx->recovered_mfkey = mfkey;
ctx->recovered_type = learn_type;
ctx->recovered_cnt = cnt;
subghz_view_keeloq_decrypt_update_candidates(
ctx->subghz->subghz_keeloq_decrypt, ctx->candidate_count);
view_dispatcher_send_custom_event(
ctx->subghz->view_dispatcher, KL_DECRYPT_EVENT_CANDIDATE);
} else if(found == 2) {
ctx->success = (ctx->candidate_count > 0);
view_dispatcher_send_custom_event(
ctx->subghz->view_dispatcher, KL_DECRYPT_EVENT_DONE);
}
}
}
static void kl_ble_cleanup(KlDecryptCtx* ctx) {
if(!ctx->ble_offload) return;
Bt* bt = furi_record_open(RECORD_BT);
bt_set_custom_data_callback(bt, NULL, NULL);
furi_record_close(RECORD_BT);
ctx->ble_offload = false;
}
static bool kl_ble_start_offload(KlDecryptCtx* ctx) {
Bt* bt = furi_record_open(RECORD_BT);
if(!bt_is_connected(bt)) {
furi_record_close(RECORD_BT);
return false;
}
bt_set_custom_data_callback(bt, kl_ble_data_received, ctx);
uint8_t req[18];
req[0] = KL_MSG_BF_REQUEST;
req[1] = ctx->subghz->keeloq_bf2.learn_type;
memcpy(req + 2, &ctx->fix, 4);
memcpy(req + 6, &ctx->hop, 4);
memcpy(req + 10, &ctx->hop2, 4);
memcpy(req + 14, &ctx->serial, 4);
bt_custom_data_tx(bt, req, sizeof(req));
furi_record_close(RECORD_BT);
ctx->ble_offload = true;
subghz_view_keeloq_decrypt_set_status(
ctx->subghz->subghz_keeloq_decrypt, "[BT] Offloading...");
return true;
}
static void kl_decrypt_view_callback(SubGhzCustomEvent event, void* context) {
SubGhz* subghz = context;
view_dispatcher_send_custom_event(subghz->view_dispatcher, event);
}
void subghz_scene_keeloq_decrypt_on_enter(void* context) {
SubGhz* subghz = context;
KlDecryptCtx* ctx = malloc(sizeof(KlDecryptCtx));
memset(ctx, 0, sizeof(KlDecryptCtx));
ctx->subghz = subghz;
ctx->result = furi_string_alloc_set("No result");
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
flipper_format_rewind(fff);
uint8_t key_data[8] = {0};
if(flipper_format_read_hex(fff, "Key", key_data, 8)) {
ctx->fix = ((uint32_t)key_data[0] << 24) | ((uint32_t)key_data[1] << 16) |
((uint32_t)key_data[2] << 8) | key_data[3];
ctx->hop = ((uint32_t)key_data[4] << 24) | ((uint32_t)key_data[5] << 16) |
((uint32_t)key_data[6] << 8) | key_data[7];
}
ctx->serial = ctx->fix & 0x0FFFFFFF;
ctx->btn = ctx->fix >> 28;
ctx->disc = ctx->serial & 0x3FF;
ctx->hop2 = subghz->keeloq_bf2.sig2_loaded ? subghz->keeloq_bf2.hop2 : 0;
scene_manager_set_scene_state(
subghz->scene_manager, SubGhzSceneKeeloqDecrypt, (uint32_t)(uintptr_t)ctx);
subghz_view_keeloq_decrypt_reset(subghz->subghz_keeloq_decrypt);
subghz_view_keeloq_decrypt_set_callback(
subghz->subghz_keeloq_decrypt, kl_decrypt_view_callback, subghz);
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdKeeloqDecrypt);
ctx->start_tick = furi_get_tick();
if(!kl_ble_start_offload(ctx)) {
char msg[128];
snprintf(msg, sizeof(msg),
"No BLE connection!\n"
"Connect companion app\n"
"and try again.\n\n"
"Fix:0x%08lX\nHop:0x%08lX",
ctx->fix, ctx->hop);
subghz_view_keeloq_decrypt_set_result(
subghz->subghz_keeloq_decrypt, false, msg);
}
}
bool subghz_scene_keeloq_decrypt_on_event(void* context, SceneManagerEvent event) {
SubGhz* subghz = context;
KlDecryptCtx* ctx = (KlDecryptCtx*)(uintptr_t)scene_manager_get_scene_state(
subghz->scene_manager, SubGhzSceneKeeloqDecrypt);
if(!ctx) return false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == KL_DECRYPT_EVENT_CANDIDATE) {
if(!subghz->keeloq_keys_manager) {
subghz->keeloq_keys_manager = subghz_keeloq_keys_alloc();
}
char key_name[24];
snprintf(key_name, sizeof(key_name), "BF_%07lX", ctx->serial);
subghz_keeloq_keys_add(
subghz->keeloq_keys_manager,
ctx->recovered_mfkey,
ctx->recovered_type,
key_name);
subghz_keeloq_keys_save(subghz->keeloq_keys_manager);
SubGhzKeystore* env_ks = subghz_environment_get_keystore(
subghz->txrx->environment);
SubGhzKeyArray_t* env_arr = subghz_keystore_get_data(env_ks);
SubGhzKey* entry = SubGhzKeyArray_push_raw(*env_arr);
entry->name = furi_string_alloc_set(key_name);
entry->key = ctx->recovered_mfkey;
entry->type = ctx->recovered_type;
return true;
} else if(event.event == KL_DECRYPT_EVENT_DONE) {
kl_ble_cleanup(ctx);
subghz->keeloq_bf2.sig1_loaded = false;
subghz->keeloq_bf2.sig2_loaded = false;
if(ctx->success) {
furi_string_printf(
ctx->result,
"Found %lu candidate(s)\n"
"Last: %08lX%08lX\n"
"Type:%u Cnt:%04lX\n"
"Saved to user keys",
ctx->candidate_count,
(uint32_t)(ctx->recovered_mfkey >> 32),
(uint32_t)(ctx->recovered_mfkey & 0xFFFFFFFF),
ctx->recovered_type,
ctx->recovered_cnt);
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
flipper_format_rewind(fff);
char mf_str[20];
snprintf(mf_str, sizeof(mf_str), "BF_%07lX", ctx->serial);
flipper_format_insert_or_update_string_cstr(fff, "Manufacture", mf_str);
uint32_t cnt_val = ctx->recovered_cnt;
flipper_format_rewind(fff);
flipper_format_insert_or_update_uint32(fff, "Cnt", &cnt_val, 1);
if(ctx->hop2 != 0) {
flipper_format_rewind(fff);
flipper_format_insert_or_update_uint32(fff, "Hop2", &ctx->hop2, 1);
}
if(subghz_path_is_file(subghz->file_path)) {
subghz_save_protocol_to_file(
subghz,
subghz_txrx_get_fff_data(subghz->txrx),
furi_string_get_cstr(subghz->file_path));
}
subghz_view_keeloq_decrypt_set_result(
subghz->subghz_keeloq_decrypt, true, furi_string_get_cstr(ctx->result));
} else if(!ctx->cancel) {
subghz_view_keeloq_decrypt_set_result(
subghz->subghz_keeloq_decrypt, false,
"Key NOT found.\nNo matching key in\n2^32 search space.");
} else {
subghz_view_keeloq_decrypt_set_result(
subghz->subghz_keeloq_decrypt, false, "Cancelled.");
}
return true;
} else if(event.event == SubGhzCustomEventViewTransmitterBack) {
if(ctx->ble_offload) {
Bt* bt = furi_record_open(RECORD_BT);
uint8_t cancel_msg = KL_MSG_BF_CANCEL;
bt_custom_data_tx(bt, &cancel_msg, 1);
furi_record_close(RECORD_BT);
}
ctx->cancel = true;
scene_manager_previous_scene(subghz->scene_manager);
return true;
}
}
return false;
}
void subghz_scene_keeloq_decrypt_on_exit(void* context) {
SubGhz* subghz = context;
KlDecryptCtx* ctx = (KlDecryptCtx*)(uintptr_t)scene_manager_get_scene_state(
subghz->scene_manager, SubGhzSceneKeeloqDecrypt);
if(ctx) {
kl_ble_cleanup(ctx);
ctx->cancel = true;
furi_string_free(ctx->result);
free(ctx);
scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneKeeloqDecrypt, 0);
}
}

View File

@@ -0,0 +1,141 @@
#include "../subghz_i.h"
#include <lib/subghz/protocols/keeloq_common.h>
typedef struct {
uint32_t serial;
uint32_t fix;
uint32_t hop;
uint32_t hop2;
uint8_t btn;
uint16_t disc;
size_t bf_indices[32];
size_t bf_count;
size_t valid_indices[32];
size_t valid_count;
} KlCleanupCtx;
static bool kl_cleanup_validate_hop(uint64_t key, uint32_t hop, uint8_t btn, uint16_t disc) {
uint32_t dec = subghz_protocol_keeloq_common_decrypt(hop, key);
if((dec >> 28) != btn) return false;
uint16_t dec_disc = (dec >> 16) & 0x3FF;
if(dec_disc == disc) return true;
if((dec_disc & 0xFF) == (disc & 0xFF)) return true;
return false;
}
static bool kl_cleanup_validate_key(uint64_t key, uint32_t hop1, uint32_t hop2, uint8_t btn, uint16_t disc) {
if(!kl_cleanup_validate_hop(key, hop1, btn, disc)) return false;
if(hop2 == 0) return true;
if(!kl_cleanup_validate_hop(key, hop2, btn, disc)) return false;
uint32_t dec1 = subghz_protocol_keeloq_common_decrypt(hop1, key);
uint32_t dec2 = subghz_protocol_keeloq_common_decrypt(hop2, key);
uint16_t cnt1 = dec1 & 0xFFFF;
uint16_t cnt2 = dec2 & 0xFFFF;
int diff = (int)cnt2 - (int)cnt1;
return (diff >= 1 && diff <= 256);
}
void subghz_scene_kl_bf_cleanup_on_enter(void* context) {
SubGhz* subghz = context;
KlCleanupCtx* ctx = malloc(sizeof(KlCleanupCtx));
memset(ctx, 0, sizeof(KlCleanupCtx));
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
flipper_format_rewind(fff);
uint8_t key_data[8] = {0};
if(flipper_format_read_hex(fff, "Key", key_data, 8)) {
ctx->fix = ((uint32_t)key_data[0] << 24) | ((uint32_t)key_data[1] << 16) |
((uint32_t)key_data[2] << 8) | key_data[3];
ctx->hop = ((uint32_t)key_data[4] << 24) | ((uint32_t)key_data[5] << 16) |
((uint32_t)key_data[6] << 8) | key_data[7];
ctx->serial = ctx->fix & 0x0FFFFFFF;
ctx->btn = ctx->fix >> 28;
ctx->disc = ctx->serial & 0x3FF;
}
ctx->hop2 = 0;
flipper_format_rewind(fff);
flipper_format_read_uint32(fff, "Hop2", &ctx->hop2, 1);
scene_manager_set_scene_state(
subghz->scene_manager, SubGhzSceneKlBfCleanup, (uint32_t)(uintptr_t)ctx);
if(!subghz->keeloq_keys_manager) {
subghz->keeloq_keys_manager = subghz_keeloq_keys_alloc();
}
char bf_name[24];
snprintf(bf_name, sizeof(bf_name), "BF_%07lX", ctx->serial);
size_t user_count = subghz_keeloq_keys_user_count(subghz->keeloq_keys_manager);
ctx->bf_count = 0;
ctx->valid_count = 0;
for(size_t i = 0; i < user_count && ctx->bf_count < 32; i++) {
SubGhzKey* k = subghz_keeloq_keys_get(subghz->keeloq_keys_manager, i);
if(!k || !k->name) continue;
const char* name = furi_string_get_cstr(k->name);
if(strcmp(name, bf_name) == 0) {
ctx->bf_indices[ctx->bf_count] = i;
if(kl_cleanup_validate_key(k->key, ctx->hop, ctx->hop2, ctx->btn, ctx->disc)) {
ctx->valid_indices[ctx->valid_count++] = i;
}
ctx->bf_count++;
}
}
FuriString* msg = furi_string_alloc();
if(ctx->bf_count == 0) {
furi_string_set_str(msg, "No BF candidate keys\nfound for this serial.");
} else if(ctx->bf_count == 1) {
furi_string_set_str(msg, "Only 1 BF key exists.\nNothing to clean up.");
} else if(ctx->valid_count == 1) {
size_t deleted = 0;
for(int i = (int)ctx->bf_count - 1; i >= 0; i--) {
if(ctx->bf_indices[i] != ctx->valid_indices[0]) {
subghz_keeloq_keys_delete(subghz->keeloq_keys_manager, ctx->bf_indices[i]);
deleted++;
}
}
subghz_keeloq_keys_save(subghz->keeloq_keys_manager);
furi_string_printf(msg,
"Cleaned %u keys.\nKept valid key:\n%s",
deleted, bf_name);
} else if(ctx->valid_count == 0) {
furi_string_printf(msg,
"%u BF keys found\nbut none validates\nhop. Kept all.",
ctx->bf_count);
} else {
furi_string_printf(msg,
"%u BF keys, %u valid.\nCannot auto-select.\nKept all.",
ctx->bf_count, ctx->valid_count);
}
widget_add_text_scroll_element(subghz->widget, 0, 0, 128, 64, furi_string_get_cstr(msg));
furi_string_free(msg);
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdWidget);
}
bool subghz_scene_kl_bf_cleanup_on_event(void* context, SceneManagerEvent event) {
UNUSED(context);
UNUSED(event);
return false;
}
void subghz_scene_kl_bf_cleanup_on_exit(void* context) {
SubGhz* subghz = context;
KlCleanupCtx* ctx = (KlCleanupCtx*)(uintptr_t)scene_manager_get_scene_state(
subghz->scene_manager, SubGhzSceneKlBfCleanup);
if(ctx) {
free(ctx);
scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneKlBfCleanup, 0);
}
widget_reset(subghz->widget);
}

View File

@@ -45,25 +45,24 @@ bool subghz_scene_need_saving_on_event(void* context, SceneManagerEvent event) {
subghz_rx_key_state_set(subghz, SubGhzRxKeyStateBack);
scene_manager_previous_scene(subghz->scene_manager);
return true;
} else if(event.event == SubGhzCustomEventSceneExit) {
} else if(event.event == SubGhzCustomEventSceneExit) {
SubGhzRxKeyState state = subghz_rx_key_state_get(subghz);
subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE);
if(state == SubGhzRxKeyStateExit) {
if(scene_manager_has_previous_scene(subghz->scene_manager, SubGhzSceneReadRAW)) {
if(!furi_string_empty(subghz->file_path_tmp)) {
subghz_delete_file(subghz);
}
}
subghz_txrx_set_preset(
subghz->txrx, "AM650", subghz->last_settings->frequency, NULL, 0);
scene_manager_search_and_switch_to_previous_scene(
subghz->scene_manager, SubGhzSceneStart);
if(!scene_manager_search_and_switch_to_previous_scene(
subghz->scene_manager, SubGhzSceneStart)) {
scene_manager_previous_scene(subghz->scene_manager);
}
} else {
scene_manager_previous_scene(subghz->scene_manager);
}
return true;
}
}

View File

@@ -2,10 +2,10 @@
enum SubmenuIndex {
SubmenuIndexEmulate,
SubmenuIndexPsaDecrypt,
SubmenuIndexEdit,
SubmenuIndexDelete,
SubmenuIndexSignalSettings,
SubmenuIndexPsaDecrypt,
SubmenuIndexCounterBf
};
@@ -17,7 +17,6 @@ void subghz_scene_saved_menu_submenu_callback(void* context, uint32_t index) {
void subghz_scene_saved_menu_on_enter(void* context) {
SubGhz* subghz = context;
// Check protocol type for conditional menu items
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
bool is_psa_encrypted = false;
bool has_counter = false;
@@ -26,7 +25,6 @@ void subghz_scene_saved_menu_on_enter(void* context) {
flipper_format_rewind(fff);
if(flipper_format_read_string(fff, "Protocol", proto)) {
if(furi_string_equal_str(proto, "PSA GROUP")) {
// Check if Type field is missing or zero (not yet decrypted)
FuriString* type_str = furi_string_alloc();
flipper_format_rewind(fff);
if(!flipper_format_read_string(fff, "Type", type_str) ||
@@ -39,7 +37,6 @@ void subghz_scene_saved_menu_on_enter(void* context) {
furi_string_free(proto);
}
// Check if protocol has a Cnt field (supports counter bruteforce)
if(fff) {
uint32_t cnt_tmp = 0;
flipper_format_rewind(fff);
@@ -48,12 +45,23 @@ void subghz_scene_saved_menu_on_enter(void* context) {
}
}
submenu_add_item(
subghz->submenu,
"Emulate",
SubmenuIndexEmulate,
subghz_scene_saved_menu_submenu_callback,
subghz);
if(!is_psa_encrypted) {
submenu_add_item(
subghz->submenu,
"Emulate",
SubmenuIndexEmulate,
subghz_scene_saved_menu_submenu_callback,
subghz);
}
if(is_psa_encrypted) {
submenu_add_item(
subghz->submenu,
"PSA Decrypt",
SubmenuIndexPsaDecrypt,
subghz_scene_saved_menu_submenu_callback,
subghz);
}
submenu_add_item(
subghz->submenu,
@@ -76,15 +84,8 @@ void subghz_scene_saved_menu_on_enter(void* context) {
SubmenuIndexSignalSettings,
subghz_scene_saved_menu_submenu_callback,
subghz);
};
if(is_psa_encrypted) {
submenu_add_item(
subghz->submenu,
"PSA Decrypt",
SubmenuIndexPsaDecrypt,
subghz_scene_saved_menu_submenu_callback,
subghz);
}
if(has_counter) {
submenu_add_item(
subghz->submenu,
@@ -110,6 +111,11 @@ bool subghz_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexEmulate);
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTransmitter);
return true;
} else if(event.event == SubmenuIndexPsaDecrypt) {
scene_manager_set_scene_state(
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexPsaDecrypt);
scene_manager_next_scene(subghz->scene_manager, SubGhzScenePsaDecrypt);
return true;
} else if(event.event == SubmenuIndexDelete) {
scene_manager_set_scene_state(
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexDelete);
@@ -125,11 +131,6 @@ bool subghz_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexSignalSettings);
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSignalSettings);
return true;
} else if(event.event == SubmenuIndexPsaDecrypt) {
scene_manager_set_scene_state(
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexPsaDecrypt);
scene_manager_next_scene(subghz->scene_manager, SubGhzScenePsaDecrypt);
return true;
} else if(event.event == SubmenuIndexCounterBf) {
scene_manager_set_scene_state(
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexCounterBf);

View File

@@ -55,6 +55,12 @@ void subghz_scene_start_on_enter(void* context) {
SubmenuIndexKeeloqKeys,
subghz_scene_start_submenu_callback,
subghz);
submenu_add_item(
subghz->submenu,
"KeeLoq BF (2 Signals)",
SubmenuIndexKeeloqBf2,
subghz_scene_start_submenu_callback,
subghz);
submenu_set_selected_item(
subghz->submenu, scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneStart));
@@ -112,6 +118,11 @@ bool subghz_scene_start_on_event(void* context, SceneManagerEvent event) {
subghz->scene_manager, SubGhzSceneStart, SubmenuIndexKeeloqKeys);
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneKeeloqKeys);
return true;
} else if(event.event == SubmenuIndexKeeloqBf2) {
scene_manager_set_scene_state(
subghz->scene_manager, SubGhzSceneStart, SubmenuIndexKeeloqBf2);
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneKeeloqBf2);
return true;
}
}
return false;

View File

@@ -10,4 +10,5 @@ enum SubmenuIndex {
SubmenuIndexProtocolList,
SubmenuIndexRadioSetting,
SubmenuIndexKeeloqKeys,
SubmenuIndexKeeloqBf2,
};

View File

@@ -95,6 +95,11 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) {
subghz->keeloq_keys_manager = NULL;
subghz->keeloq_bf2.sig1_loaded = false;
subghz->keeloq_bf2.sig2_loaded = false;
subghz->keeloq_bf2.sig1_path = furi_string_alloc();
subghz->keeloq_bf2.sig2_path = furi_string_alloc();
subghz->file_path = furi_string_alloc();
subghz->file_path_tmp = furi_string_alloc();
@@ -195,6 +200,12 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) {
SubGhzViewIdPsaDecrypt,
subghz_view_psa_decrypt_get_view(subghz->subghz_psa_decrypt));
subghz->subghz_keeloq_decrypt = subghz_view_keeloq_decrypt_alloc();
view_dispatcher_add_view(
subghz->view_dispatcher,
SubGhzViewIdKeeloqDecrypt,
subghz_view_keeloq_decrypt_get_view(subghz->subghz_keeloq_decrypt));
//init threshold rssi
subghz->threshold_rssi = subghz_threshold_rssi_alloc();
@@ -306,6 +317,10 @@ void subghz_free(SubGhz* subghz, bool alloc_for_tx_only) {
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdPsaDecrypt);
subghz_view_psa_decrypt_free(subghz->subghz_psa_decrypt);
// KeeLoq Decrypt
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdKeeloqDecrypt);
subghz_view_keeloq_decrypt_free(subghz->subghz_keeloq_decrypt);
// Read RAW
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdReadRAW);
subghz_read_raw_free(subghz->subghz_read_raw);
@@ -353,7 +368,9 @@ void subghz_free(SubGhz* subghz, bool alloc_for_tx_only) {
furi_string_free(subghz->file_path);
furi_string_free(subghz->file_path_tmp);
// KeeLoq key manager (may still be live if app exited from within the edit scene)
furi_string_free(subghz->keeloq_bf2.sig1_path);
furi_string_free(subghz->keeloq_bf2.sig2_path);
if(subghz->keeloq_keys_manager) {
subghz_keeloq_keys_free(subghz->keeloq_keys_manager);
subghz->keeloq_keys_manager = NULL;
@@ -386,6 +403,7 @@ int32_t subghz_app(void* p) {
subghz->view_dispatcher, subghz->gui, ViewDispatcherTypeFullscreen);
furi_string_set(subghz->file_path, SUBGHZ_APP_FOLDER);
if(subghz_txrx_is_database_loaded(subghz->txrx)) {
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneStart);
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiver);
} else {
scene_manager_set_scene_state(

View File

@@ -9,6 +9,7 @@
#include "views/subghz_frequency_analyzer.h"
#include "views/subghz_read_raw.h"
#include "views/subghz_psa_decrypt.h"
#include "views/subghz_keeloq_decrypt.h"
#include <gui/gui.h>
#include <assets_icons.h>
@@ -74,6 +75,7 @@ struct SubGhz {
SubGhzFrequencyAnalyzer* subghz_frequency_analyzer;
SubGhzReadRAW* subghz_read_raw;
SubGhzViewPsaDecrypt* subghz_psa_decrypt;
SubGhzViewKeeloqDecrypt* subghz_keeloq_decrypt;
bool raw_send_only;
bool save_datetime_set;
@@ -102,13 +104,25 @@ struct SubGhz {
// KeeLoq key management
SubGhzKeeloqKeysManager* keeloq_keys_manager;
struct {
uint8_t key_bytes[8]; // ByteInput result
char name[65]; // TextInput result
uint16_t type; // selected learning type 1..8
bool is_new; // true = add, false = edit
size_t edit_index; // valid when is_new == false
uint8_t edit_step; // 0 = key, 1 = name, 2 = type
uint8_t key_bytes[8];
char name[65];
uint16_t type;
bool is_new;
size_t edit_index;
uint8_t edit_step;
} keeloq_edit;
struct {
uint32_t fix;
uint32_t hop1;
uint32_t hop2;
uint32_t serial;
bool sig1_loaded;
bool sig2_loaded;
FuriString* sig1_path;
FuriString* sig2_path;
uint8_t learn_type;
} keeloq_bf2;
};
void subghz_blink_start(SubGhz* subghz);

View File

@@ -0,0 +1,246 @@
#include "subghz_keeloq_decrypt.h"
#include <gui/elements.h>
#include <furi.h>
#define KL_TOTAL_KEYS 0x100000000ULL
struct SubGhzViewKeeloqDecrypt {
View* view;
SubGhzViewKeeloqDecryptCallback callback;
void* context;
};
typedef struct {
uint8_t progress;
uint32_t keys_tested;
uint32_t keys_per_sec;
uint32_t elapsed_sec;
uint32_t eta_sec;
bool done;
bool success;
uint32_t candidates;
FuriString* result_str;
char status_line[40];
} SubGhzKeeloqDecryptModel;
static void subghz_view_keeloq_decrypt_format_count(char* buf, size_t len, uint32_t count) {
if(count >= 1000000) {
snprintf(buf, len, "%lu.%luM", count / 1000000, (count % 1000000) / 100000);
} else if(count >= 1000) {
snprintf(buf, len, "%luK", count / 1000);
} else {
snprintf(buf, len, "%lu", count);
}
}
static void subghz_view_keeloq_decrypt_draw(Canvas* canvas, void* _model) {
SubGhzKeeloqDecryptModel* model = (SubGhzKeeloqDecryptModel*)_model;
canvas_clear(canvas);
if(!model->done) {
canvas_set_font(canvas, FontPrimary);
if(model->status_line[0]) {
canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, model->status_line);
} else {
canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, "KeeLoq BF");
}
canvas_draw_rframe(canvas, 3, 15, 122, 12, 2);
uint8_t fill = (uint8_t)((uint16_t)model->progress * 116 / 100);
if(fill > 0) {
canvas_draw_rbox(canvas, 5, 17, fill, 8, 1);
}
canvas_set_font(canvas, FontSecondary);
char keys_str[32];
char tested_buf[12];
subghz_view_keeloq_decrypt_format_count(tested_buf, sizeof(tested_buf), model->keys_tested);
snprintf(keys_str, sizeof(keys_str), "%d%% - %s / 4G keys", model->progress, tested_buf);
canvas_draw_str(canvas, 2, 38, keys_str);
char speed_str[40];
char speed_buf[12];
subghz_view_keeloq_decrypt_format_count(speed_buf, sizeof(speed_buf), model->keys_per_sec);
uint32_t eta_m = model->eta_sec / 60;
uint32_t eta_s = model->eta_sec % 60;
if(eta_m > 0) {
snprintf(speed_str, sizeof(speed_str), "%s keys/sec ETA %lum %lus", speed_buf, eta_m, eta_s);
} else {
snprintf(speed_str, sizeof(speed_str), "%s keys/sec ETA %lus", speed_buf, eta_s);
}
canvas_draw_str(canvas, 2, 48, speed_str);
if(model->candidates > 0) {
char cand_str[32];
snprintf(cand_str, sizeof(cand_str), "Candidates: %lu", model->candidates);
canvas_draw_str(canvas, 2, 58, cand_str);
} else {
char elapsed_str[24];
uint32_t el_m = model->elapsed_sec / 60;
uint32_t el_s = model->elapsed_sec % 60;
if(el_m > 0) {
snprintf(elapsed_str, sizeof(elapsed_str), "Elapsed: %lum %lus", el_m, el_s);
} else {
snprintf(elapsed_str, sizeof(elapsed_str), "Elapsed: %lus", el_s);
}
canvas_draw_str(canvas, 2, 58, elapsed_str);
}
canvas_draw_str_aligned(canvas, 126, 64, AlignRight, AlignBottom, "Hold BACK");
} else {
canvas_set_font(canvas, FontSecondary);
if(model->result_str) {
elements_multiline_text_aligned(
canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(model->result_str));
}
}
}
static bool subghz_view_keeloq_decrypt_input(InputEvent* event, void* context) {
SubGhzViewKeeloqDecrypt* instance = (SubGhzViewKeeloqDecrypt*)context;
if(event->key == InputKeyBack) {
if(instance->callback) {
instance->callback(SubGhzCustomEventViewTransmitterBack, instance->context);
}
return true;
}
return false;
}
SubGhzViewKeeloqDecrypt* subghz_view_keeloq_decrypt_alloc(void) {
SubGhzViewKeeloqDecrypt* instance = malloc(sizeof(SubGhzViewKeeloqDecrypt));
instance->view = view_alloc();
view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(SubGhzKeeloqDecryptModel));
view_set_context(instance->view, instance);
view_set_draw_callback(instance->view, subghz_view_keeloq_decrypt_draw);
view_set_input_callback(instance->view, subghz_view_keeloq_decrypt_input);
with_view_model(
instance->view,
SubGhzKeeloqDecryptModel * model,
{
model->result_str = furi_string_alloc();
model->progress = 0;
model->keys_tested = 0;
model->keys_per_sec = 0;
model->elapsed_sec = 0;
model->eta_sec = 0;
model->done = false;
model->success = false;
model->candidates = 0;
},
false);
return instance;
}
void subghz_view_keeloq_decrypt_free(SubGhzViewKeeloqDecrypt* instance) {
furi_check(instance);
with_view_model(
instance->view,
SubGhzKeeloqDecryptModel * model,
{ furi_string_free(model->result_str); },
false);
view_free(instance->view);
free(instance);
}
View* subghz_view_keeloq_decrypt_get_view(SubGhzViewKeeloqDecrypt* instance) {
furi_check(instance);
return instance->view;
}
void subghz_view_keeloq_decrypt_set_callback(
SubGhzViewKeeloqDecrypt* instance,
SubGhzViewKeeloqDecryptCallback callback,
void* context) {
furi_check(instance);
instance->callback = callback;
instance->context = context;
}
void subghz_view_keeloq_decrypt_update_stats(
SubGhzViewKeeloqDecrypt* instance,
uint8_t progress,
uint32_t keys_tested,
uint32_t keys_per_sec,
uint32_t elapsed_sec,
uint32_t eta_sec) {
furi_check(instance);
with_view_model(
instance->view,
SubGhzKeeloqDecryptModel * model,
{
model->progress = progress;
model->keys_tested = keys_tested;
model->keys_per_sec = keys_per_sec;
model->elapsed_sec = elapsed_sec;
model->eta_sec = eta_sec;
},
true);
}
void subghz_view_keeloq_decrypt_set_result(
SubGhzViewKeeloqDecrypt* instance,
bool success,
const char* result) {
furi_check(instance);
with_view_model(
instance->view,
SubGhzKeeloqDecryptModel * model,
{
model->done = true;
model->success = success;
furi_string_set_str(model->result_str, result);
},
true);
}
void subghz_view_keeloq_decrypt_reset(SubGhzViewKeeloqDecrypt* instance) {
furi_check(instance);
with_view_model(
instance->view,
SubGhzKeeloqDecryptModel * model,
{
model->progress = 0;
model->keys_tested = 0;
model->keys_per_sec = 0;
model->elapsed_sec = 0;
model->eta_sec = 0;
model->done = false;
model->success = false;
model->candidates = 0;
furi_string_reset(model->result_str);
model->status_line[0] = '\0';
},
false);
}
void subghz_view_keeloq_decrypt_set_status(SubGhzViewKeeloqDecrypt* instance, const char* status) {
furi_check(instance);
with_view_model(
instance->view,
SubGhzKeeloqDecryptModel * model,
{
if(status) {
strlcpy(model->status_line, status, sizeof(model->status_line));
} else {
model->status_line[0] = '\0';
}
},
true);
}
void subghz_view_keeloq_decrypt_update_candidates(
SubGhzViewKeeloqDecrypt* instance, uint32_t count) {
furi_check(instance);
with_view_model(
instance->view,
SubGhzKeeloqDecryptModel * model,
{ model->candidates = count; },
true);
}

View File

@@ -0,0 +1,37 @@
#pragma once
#include <gui/view.h>
#include "../helpers/subghz_custom_event.h"
typedef struct SubGhzViewKeeloqDecrypt SubGhzViewKeeloqDecrypt;
typedef void (*SubGhzViewKeeloqDecryptCallback)(SubGhzCustomEvent event, void* context);
SubGhzViewKeeloqDecrypt* subghz_view_keeloq_decrypt_alloc(void);
void subghz_view_keeloq_decrypt_free(SubGhzViewKeeloqDecrypt* instance);
View* subghz_view_keeloq_decrypt_get_view(SubGhzViewKeeloqDecrypt* instance);
void subghz_view_keeloq_decrypt_set_callback(
SubGhzViewKeeloqDecrypt* instance,
SubGhzViewKeeloqDecryptCallback callback,
void* context);
void subghz_view_keeloq_decrypt_update_stats(
SubGhzViewKeeloqDecrypt* instance,
uint8_t progress,
uint32_t keys_tested,
uint32_t keys_per_sec,
uint32_t elapsed_sec,
uint32_t eta_sec);
void subghz_view_keeloq_decrypt_set_result(
SubGhzViewKeeloqDecrypt* instance,
bool success,
const char* result);
void subghz_view_keeloq_decrypt_reset(SubGhzViewKeeloqDecrypt* instance);
void subghz_view_keeloq_decrypt_set_status(SubGhzViewKeeloqDecrypt* instance, const char* status);
void subghz_view_keeloq_decrypt_update_candidates(
SubGhzViewKeeloqDecrypt* instance, uint32_t count);

View File

@@ -50,8 +50,10 @@ static void subghz_view_psa_decrypt_draw(Canvas* canvas, void* _model) {
// Progress bar outline + fill
canvas_draw_rframe(canvas, 3, 15, 122, 12, 2);
uint8_t fill = (uint8_t)((uint16_t)model->progress * 116 / 100);
if(fill > 0) {
if(fill > 2) {
canvas_draw_rbox(canvas, 5, 17, fill, 8, 1);
} else if(fill > 0) {
canvas_draw_box(canvas, 5, 17, fill, 8);
}
canvas_set_font(canvas, FontSecondary);
@@ -90,19 +92,23 @@ static void subghz_view_psa_decrypt_draw(Canvas* canvas, void* _model) {
// Cancel hint - bottom right
canvas_draw_str_aligned(canvas, 126, 64, AlignRight, AlignBottom, "Hold BACK");
} else {
// Result screen
canvas_set_font(canvas, FontSecondary);
if(model->result_str) {
elements_multiline_text_aligned(
canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(model->result_str));
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas, 64, 4, AlignCenter, AlignTop, "Decrypted!");
if(model->result_str) {
canvas_set_font(canvas, FontSecondary);
elements_multiline_text_aligned(canvas, 64, 20, AlignCenter, AlignTop,
furi_string_get_cstr(model->result_str));
}
elements_button_center(canvas, "Ok");
}
}
static bool subghz_view_psa_decrypt_input(InputEvent* event, void* context) {
SubGhzViewPsaDecrypt* instance = (SubGhzViewPsaDecrypt*)context;
if(event->key == InputKeyBack) {
if(event->key == InputKeyBack || event->key == InputKeyOk) {
if(instance->callback) {
instance->callback(SubGhzCustomEventViewTransmitterBack, instance->context);
}

View File

@@ -1 +0,0 @@
Put your custom applications in this folder.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 511 B

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 518 B

After

Width:  |  Height:  |  Size: 968 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 483 B

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,517 @@
#include "fiat_spa.h"
#include "../blocks/const.h"
#include "../blocks/decoder.h"
#include "../blocks/encoder.h"
#include "../blocks/generic.h"
#include "../blocks/math.h"
#include <lib/toolbox/manchester_decoder.h>
#define TAG "SubGhzProtocolFiatSpa"
static const SubGhzBlockConst subghz_protocol_fiat_spa_const = {
.te_short = 200,
.te_long = 400,
.te_delta = 100,
.min_count_bit_for_found = 64,
};
#define FIAT_SPA_PREAMBLE_PAIRS 150
#define FIAT_SPA_GAP_US 800
#define FIAT_SPA_TOTAL_BURSTS 3
#define FIAT_SPA_INTER_BURST_GAP 25000
#define FIAT_SPA_UPLOAD_MAX 1328
struct SubGhzProtocolDecoderFiatSpa {
SubGhzProtocolDecoderBase base;
SubGhzBlockGeneric generic;
SubGhzBlockDecoder decoder;
ManchesterState manchester_state;
uint16_t preamble_count;
uint32_t data_low;
uint32_t data_high;
uint8_t bit_count;
uint32_t hop;
uint32_t fix;
uint8_t endbyte;
};
struct SubGhzProtocolEncoderFiatSpa {
SubGhzProtocolEncoderBase base;
void* decoder_callback;
void* decoder_context;
SubGhzBlockGeneric generic;
SubGhzProtocolBlockEncoder encoder;
uint32_t hop;
uint32_t fix;
uint8_t endbyte;
};
typedef struct {
SubGhzProtocolDecoderBase base;
SubGhzBlockGeneric generic;
} SubGhzProtocolCommonFiatSpa;
typedef enum {
FiatSpaDecoderStepReset = 0,
FiatSpaDecoderStepPreamble,
FiatSpaDecoderStepData,
} FiatSpaDecoderStep;
const SubGhzProtocolDecoder subghz_protocol_fiat_spa_decoder = {
.alloc = subghz_protocol_decoder_fiat_spa_alloc,
.free = subghz_protocol_decoder_fiat_spa_free,
.feed = subghz_protocol_decoder_fiat_spa_feed,
.reset = subghz_protocol_decoder_fiat_spa_reset,
.get_hash_data = subghz_protocol_decoder_fiat_spa_get_hash_data,
.serialize = subghz_protocol_decoder_fiat_spa_serialize,
.deserialize = subghz_protocol_decoder_fiat_spa_deserialize,
.get_string = subghz_protocol_decoder_fiat_spa_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_fiat_spa_encoder = {
.alloc = subghz_protocol_encoder_fiat_spa_alloc,
.free = subghz_protocol_encoder_fiat_spa_free,
.deserialize = subghz_protocol_encoder_fiat_spa_deserialize,
.stop = subghz_protocol_encoder_fiat_spa_stop,
.yield = subghz_protocol_encoder_fiat_spa_yield,
};
const SubGhzProtocol subghz_protocol_fiat_spa = {
.name = SUBGHZ_PROTOCOL_FIAT_SPA_NAME,
.type = SubGhzProtocolTypeStatic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_fiat_spa_decoder,
.encoder = &subghz_protocol_fiat_spa_encoder,
};
void* subghz_protocol_decoder_fiat_spa_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderFiatSpa* instance = malloc(sizeof(SubGhzProtocolDecoderFiatSpa));
instance->base.protocol = &subghz_protocol_fiat_spa;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_fiat_spa_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderFiatSpa* instance = context;
free(instance);
}
void subghz_protocol_decoder_fiat_spa_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderFiatSpa* instance = context;
instance->decoder.parser_step = FiatSpaDecoderStepReset;
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->preamble_count = 0;
instance->data_low = 0;
instance->data_high = 0;
instance->bit_count = 0;
instance->hop = 0;
instance->fix = 0;
instance->endbyte = 0;
instance->manchester_state = ManchesterStateMid1;
}
void subghz_protocol_decoder_fiat_spa_feed(void* context, bool level, uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderFiatSpa* instance = context;
uint32_t te_short = (uint32_t)subghz_protocol_fiat_spa_const.te_short;
uint32_t te_long = (uint32_t)subghz_protocol_fiat_spa_const.te_long;
uint32_t te_delta = (uint32_t)subghz_protocol_fiat_spa_const.te_delta;
uint32_t gap_threshold = FIAT_SPA_GAP_US;
uint32_t diff;
switch(instance->decoder.parser_step) {
case FiatSpaDecoderStepReset:
if(!level) return;
if(duration < te_short) {
diff = te_short - duration;
} else {
diff = duration - te_short;
}
if(diff < te_delta) {
instance->data_low = 0;
instance->data_high = 0;
instance->decoder.parser_step = FiatSpaDecoderStepPreamble;
instance->preamble_count = 0;
instance->bit_count = 0;
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
manchester_advance(
instance->manchester_state,
ManchesterEventReset,
&instance->manchester_state,
NULL);
}
break;
case FiatSpaDecoderStepPreamble:
if(level) {
if(duration < te_short) {
diff = te_short - duration;
} else {
diff = duration - te_short;
}
if(diff < te_delta) {
instance->preamble_count++;
} else {
instance->decoder.parser_step = FiatSpaDecoderStepReset;
}
return;
}
if(duration < te_short) {
diff = te_short - duration;
} else {
diff = duration - te_short;
}
if(diff < te_delta) {
instance->preamble_count++;
} else {
if(instance->preamble_count >= FIAT_SPA_PREAMBLE_PAIRS) {
if(duration < gap_threshold) {
diff = gap_threshold - duration;
} else {
diff = duration - gap_threshold;
}
if(diff < te_delta) {
instance->decoder.parser_step = FiatSpaDecoderStepData;
instance->preamble_count = 0;
instance->data_low = 0;
instance->data_high = 0;
instance->bit_count = 0;
manchester_advance(instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
return;
}
}
instance->decoder.parser_step = FiatSpaDecoderStepReset;
}
if(instance->preamble_count >= FIAT_SPA_PREAMBLE_PAIRS &&
instance->decoder.parser_step == FiatSpaDecoderStepPreamble) {
if(duration < gap_threshold) {
diff = gap_threshold - duration;
} else {
diff = duration - gap_threshold;
}
if(diff < te_delta) {
instance->decoder.parser_step = FiatSpaDecoderStepData;
instance->preamble_count = 0;
instance->data_low = 0;
instance->data_high = 0;
instance->bit_count = 0;
manchester_advance(instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
return;
}
}
break;
case FiatSpaDecoderStepData: {
ManchesterEvent event = ManchesterEventReset;
if(duration < te_short) {
diff = te_short - duration;
if(diff < te_delta) {
event = level ? ManchesterEventShortLow : ManchesterEventShortHigh;
}
} else {
diff = duration - te_short;
if(diff < te_delta) {
event = level ? ManchesterEventShortLow : ManchesterEventShortHigh;
} else {
if(duration < te_long) {
diff = te_long - duration;
} else {
diff = duration - te_long;
}
if(diff < te_delta) {
event = level ? ManchesterEventLongLow : ManchesterEventLongHigh;
}
}
}
if(event != ManchesterEventReset) {
bool data_bit_bool;
if(manchester_advance(
instance->manchester_state,
event,
&instance->manchester_state,
&data_bit_bool)) {
uint32_t new_bit = data_bit_bool ? 1 : 0;
uint32_t carry = (instance->data_low >> 31) & 1;
instance->data_low = (instance->data_low << 1) | new_bit;
instance->data_high = (instance->data_high << 1) | carry;
instance->bit_count++;
if(instance->bit_count == 64) {
instance->fix = instance->data_low;
instance->hop = instance->data_high;
instance->data_low = 0;
instance->data_high = 0;
}
if(instance->bit_count == 0x47) {
instance->endbyte = (uint8_t)(instance->data_low & 0x3F);
instance->generic.data = ((uint64_t)instance->hop << 32) | instance->fix;
instance->generic.data_count_bit = 71;
instance->generic.serial = instance->fix;
instance->generic.btn = instance->endbyte;
instance->generic.cnt = instance->hop;
instance->decoder.decode_data = instance->generic.data;
instance->decoder.decode_count_bit = instance->generic.data_count_bit;
if(instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
instance->data_low = 0;
instance->data_high = 0;
instance->bit_count = 0;
instance->decoder.parser_step = FiatSpaDecoderStepReset;
}
}
} else {
if(instance->bit_count == 0x47) {
instance->endbyte = (uint8_t)(instance->data_low & 0x3F);
instance->generic.data = ((uint64_t)instance->hop << 32) | instance->fix;
instance->generic.data_count_bit = 71;
instance->generic.serial = instance->fix;
instance->generic.btn = instance->endbyte;
instance->generic.cnt = instance->hop;
instance->decoder.decode_data = instance->generic.data;
instance->decoder.decode_count_bit = instance->generic.data_count_bit;
if(instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
instance->data_low = 0;
instance->data_high = 0;
instance->bit_count = 0;
instance->decoder.parser_step = FiatSpaDecoderStepReset;
} else if(instance->bit_count < 64) {
instance->decoder.parser_step = FiatSpaDecoderStepReset;
}
}
break;
}
}
}
uint8_t subghz_protocol_decoder_fiat_spa_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderFiatSpa* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_fiat_spa_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderFiatSpa* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
if(subghz_block_generic_serialize(&instance->generic, flipper_format, preset) !=
SubGhzProtocolStatusOk) {
break;
}
if(!flipper_format_write_uint32(
flipper_format, "EndByte", (uint32_t*)&instance->endbyte, 1)) {
break;
}
ret = SubGhzProtocolStatusOk;
} while(false);
return ret;
}
SubGhzProtocolStatus subghz_protocol_decoder_fiat_spa_deserialize(
void* context,
FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderFiatSpa* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
if(ret != SubGhzProtocolStatusOk) break;
uint32_t endbyte_temp = 0;
if(!flipper_format_read_uint32(flipper_format, "EndByte", &endbyte_temp, 1)) {
instance->endbyte = 0;
} else {
instance->endbyte = (uint8_t)endbyte_temp;
}
instance->hop = (uint32_t)(instance->generic.data >> 32);
instance->fix = (uint32_t)(instance->generic.data & 0xFFFFFFFF);
instance->generic.cnt = instance->hop;
instance->generic.serial = instance->fix;
instance->generic.btn = instance->endbyte;
ret = SubGhzProtocolStatusOk;
} while(false);
return ret;
}
void subghz_protocol_decoder_fiat_spa_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolCommonFiatSpa* instance = context;
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%08lX%08lX\r\n"
"Fix:%08lX\r\n"
"Hop:%08lX\r\n"
"EndByte:%02X",
instance->generic.protocol_name,
instance->generic.data_count_bit,
(uint32_t)(instance->generic.data >> 32),
(uint32_t)(instance->generic.data & 0xFFFFFFFF),
instance->generic.serial,
instance->generic.cnt,
instance->generic.btn);
}
void* subghz_protocol_encoder_fiat_spa_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderFiatSpa* instance = malloc(sizeof(SubGhzProtocolEncoderFiatSpa));
instance->base.protocol = &subghz_protocol_fiat_spa;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 3;
instance->encoder.size_upload = FIAT_SPA_UPLOAD_MAX;
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.is_running = false;
instance->encoder.front = 0;
instance->hop = 0;
instance->fix = 0;
instance->endbyte = 0;
return instance;
}
void subghz_protocol_encoder_fiat_spa_free(void* context) {
furi_assert(context);
SubGhzProtocolEncoderFiatSpa* instance = context;
if(instance->encoder.upload) {
free(instance->encoder.upload);
}
free(instance);
}
void subghz_protocol_encoder_fiat_spa_stop(void* context) {
furi_assert(context);
SubGhzProtocolEncoderFiatSpa* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_fiat_spa_yield(void* context) {
furi_assert(context);
SubGhzProtocolEncoderFiatSpa* instance = context;
if(!instance->encoder.is_running || instance->encoder.repeat == 0) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
static void subghz_protocol_encoder_fiat_spa_get_upload(SubGhzProtocolEncoderFiatSpa* instance) {
furi_assert(instance);
size_t index = 0;
uint32_t te_short = subghz_protocol_fiat_spa_const.te_short;
uint32_t te_long = subghz_protocol_fiat_spa_const.te_long;
uint64_t data = ((uint64_t)instance->hop << 32) | instance->fix;
uint8_t endbyte_to_send = instance->endbyte >> 1;
for(uint8_t burst = 0; burst < FIAT_SPA_TOTAL_BURSTS; burst++) {
if(burst > 0) {
instance->encoder.upload[index++] =
level_duration_make(false, FIAT_SPA_INTER_BURST_GAP);
}
for(int i = 0; i < FIAT_SPA_PREAMBLE_PAIRS; i++) {
instance->encoder.upload[index++] = level_duration_make(true, te_short);
instance->encoder.upload[index++] = level_duration_make(false, te_short);
}
instance->encoder.upload[index - 1] = level_duration_make(false, FIAT_SPA_GAP_US);
bool first_bit = (data >> 63) & 1;
if(first_bit) {
instance->encoder.upload[index++] = level_duration_make(true, te_long);
} else {
instance->encoder.upload[index++] = level_duration_make(true, te_short);
instance->encoder.upload[index++] = level_duration_make(false, te_long);
}
bool prev_bit = first_bit;
for(int bit = 62; bit >= 0; bit--) {
bool curr_bit = (data >> bit) & 1;
if(!prev_bit && !curr_bit) {
instance->encoder.upload[index++] = level_duration_make(true, te_short);
instance->encoder.upload[index++] = level_duration_make(false, te_short);
} else if(!prev_bit && curr_bit) {
instance->encoder.upload[index++] = level_duration_make(true, te_long);
} else if(prev_bit && !curr_bit) {
instance->encoder.upload[index++] = level_duration_make(false, te_long);
} else {
instance->encoder.upload[index++] = level_duration_make(false, te_short);
instance->encoder.upload[index++] = level_duration_make(true, te_short);
}
prev_bit = curr_bit;
}
for(int bit = 5; bit >= 0; bit--) {
bool curr_bit = (endbyte_to_send >> bit) & 1;
if(!prev_bit && !curr_bit) {
instance->encoder.upload[index++] = level_duration_make(true, te_short);
instance->encoder.upload[index++] = level_duration_make(false, te_short);
} else if(!prev_bit && curr_bit) {
instance->encoder.upload[index++] = level_duration_make(true, te_long);
} else if(prev_bit && !curr_bit) {
instance->encoder.upload[index++] = level_duration_make(false, te_long);
} else {
instance->encoder.upload[index++] = level_duration_make(false, te_short);
instance->encoder.upload[index++] = level_duration_make(true, te_short);
}
prev_bit = curr_bit;
}
if(prev_bit) {
instance->encoder.upload[index++] = level_duration_make(false, te_short);
}
instance->encoder.upload[index++] = level_duration_make(false, te_short * 8);
}
instance->encoder.size_upload = index;
instance->encoder.front = 0;
}
SubGhzProtocolStatus subghz_protocol_encoder_fiat_spa_deserialize(
void* context,
FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolEncoderFiatSpa* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
if(ret != SubGhzProtocolStatusOk) break;
instance->hop = (uint32_t)(instance->generic.data >> 32);
instance->fix = (uint32_t)(instance->generic.data & 0xFFFFFFFF);
uint32_t endbyte_temp = 0;
if(!flipper_format_read_uint32(flipper_format, "EndByte", &endbyte_temp, 1)) {
instance->endbyte = 0;
} else {
instance->endbyte = (uint8_t)endbyte_temp;
}
instance->generic.cnt = instance->hop;
instance->generic.serial = instance->fix;
instance->generic.btn = instance->endbyte;
subghz_protocol_encoder_fiat_spa_get_upload(instance);
instance->encoder.is_running = true;
ret = SubGhzProtocolStatusOk;
} while(false);
return ret;
}

View File

@@ -0,0 +1,32 @@
#pragma once
#include "base.h"
#define SUBGHZ_PROTOCOL_FIAT_SPA_NAME "FIAT SPA"
typedef struct SubGhzProtocolDecoderFiatSpa SubGhzProtocolDecoderFiatSpa;
typedef struct SubGhzProtocolEncoderFiatSpa SubGhzProtocolEncoderFiatSpa;
extern const SubGhzProtocol subghz_protocol_fiat_spa;
void* subghz_protocol_decoder_fiat_spa_alloc(SubGhzEnvironment* environment);
void subghz_protocol_decoder_fiat_spa_free(void* context);
void subghz_protocol_decoder_fiat_spa_reset(void* context);
void subghz_protocol_decoder_fiat_spa_feed(void* context, bool level, uint32_t duration);
uint8_t subghz_protocol_decoder_fiat_spa_get_hash_data(void* context);
SubGhzProtocolStatus subghz_protocol_decoder_fiat_spa_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
SubGhzProtocolStatus subghz_protocol_decoder_fiat_spa_deserialize(
void* context,
FlipperFormat* flipper_format);
void subghz_protocol_decoder_fiat_spa_get_string(void* context, FuriString* output);
void* subghz_protocol_encoder_fiat_spa_alloc(SubGhzEnvironment* environment);
void subghz_protocol_encoder_fiat_spa_free(void* context);
SubGhzProtocolStatus subghz_protocol_encoder_fiat_spa_deserialize(
void* context,
FlipperFormat* flipper_format);
void subghz_protocol_encoder_fiat_spa_stop(void* context);
LevelDuration subghz_protocol_encoder_fiat_spa_yield(void* context);

View File

@@ -1,655 +0,0 @@
#include "fiat_v0.h"
#include <inttypes.h>
#include <lib/toolbox/manchester_decoder.h>
#define TAG "FiatProtocolV0"
#define FIAT_V0_PREAMBLE_PAIRS 150
#define FIAT_V0_GAP_US 800
#define FIAT_V0_TOTAL_BURSTS 3
#define FIAT_V0_INTER_BURST_GAP 25000
static const SubGhzBlockConst subghz_protocol_fiat_v0_const = {
.te_short = 200,
.te_long = 400,
.te_delta = 100,
.min_count_bit_for_found = 71,
};
struct SubGhzProtocolDecoderFiatV0 {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
ManchesterState manchester_state;
uint8_t decoder_state;
uint16_t preamble_count;
uint32_t data_low;
uint32_t data_high;
uint8_t bit_count;
uint32_t hop;
uint32_t fix;
uint8_t endbyte;
uint8_t final_count;
uint32_t te_last;
};
struct SubGhzProtocolEncoderFiatV0 {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
uint32_t hop;
uint32_t fix;
uint8_t endbyte;
size_t upload_capacity;
};
typedef enum {
FiatV0DecoderStepReset = 0,
FiatV0DecoderStepPreamble = 1,
FiatV0DecoderStepData = 2,
} FiatV0DecoderStep;
// ============================================================================
// PROTOCOL INTERFACE DEFINITIONS
// ============================================================================
const SubGhzProtocolDecoder subghz_protocol_fiat_v0_decoder = {
.alloc = subghz_protocol_decoder_fiat_v0_alloc,
.free = subghz_protocol_decoder_fiat_v0_free,
.feed = subghz_protocol_decoder_fiat_v0_feed,
.reset = subghz_protocol_decoder_fiat_v0_reset,
.get_hash_data = subghz_protocol_decoder_fiat_v0_get_hash_data,
.serialize = subghz_protocol_decoder_fiat_v0_serialize,
.deserialize = subghz_protocol_decoder_fiat_v0_deserialize,
.get_string = subghz_protocol_decoder_fiat_v0_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_fiat_v0_encoder = {
.alloc = subghz_protocol_encoder_fiat_v0_alloc,
.free = subghz_protocol_encoder_fiat_v0_free,
.deserialize = subghz_protocol_encoder_fiat_v0_deserialize,
.stop = subghz_protocol_encoder_fiat_v0_stop,
.yield = subghz_protocol_encoder_fiat_v0_yield,
};
const SubGhzProtocol subghz_protocol_fiat_v0 = {
.name = FIAT_PROTOCOL_V0_NAME,
.type = SubGhzProtocolTypeStatic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_fiat_v0_decoder,
.encoder = &subghz_protocol_fiat_v0_encoder,
};
// ============================================================================
// ENCODER IMPLEMENTATION
// ============================================================================
static size_t fiat_v0_encoder_calc_required_upload(void) {
// Per burst:
// preamble: FIAT_V0_PREAMBLE_PAIRS pairs => 2 elements each
// data: 64 bits Manchester => 2 elements per bit
// endbyte: 7 bits Manchester => 2 elements per bit
// trailer: 1 element (extended low)
const size_t per_burst = (FIAT_V0_PREAMBLE_PAIRS * 2) + (64 * 2) + (7 * 2) + 1;
// Inter-burst gap: 1 element between each pair of bursts
return (FIAT_V0_TOTAL_BURSTS * per_burst) +
(FIAT_V0_TOTAL_BURSTS > 0 ? (FIAT_V0_TOTAL_BURSTS - 1) : 0);
}
void* subghz_protocol_encoder_fiat_v0_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderFiatV0* instance = calloc(1, sizeof(SubGhzProtocolEncoderFiatV0));
furi_check(instance);
instance->base.protocol = &subghz_protocol_fiat_v0;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 10;
instance->encoder.size_upload = 0;
instance->upload_capacity = fiat_v0_encoder_calc_required_upload();
instance->encoder.upload = calloc(instance->upload_capacity, sizeof(LevelDuration));
furi_check(instance->encoder.upload);
instance->encoder.is_running = false;
return instance;
}
void subghz_protocol_encoder_fiat_v0_free(void* context) {
furi_check(context);
SubGhzProtocolEncoderFiatV0* instance = context;
if(instance->encoder.upload) {
free(instance->encoder.upload);
}
free(instance);
}
static void subghz_protocol_encoder_fiat_v0_get_upload(SubGhzProtocolEncoderFiatV0* instance) {
furi_check(instance);
const size_t required = fiat_v0_encoder_calc_required_upload();
// Capacity is pre-allocated at alloc time — assert it is sufficient
furi_check(required <= instance->upload_capacity);
size_t index = 0;
uint32_t te_short = subghz_protocol_fiat_v0_const.te_short;
FURI_LOG_I(
TAG,
"Building upload: hop=0x%08lX, fix=0x%08lX, endbyte=0x%02X",
instance->hop,
instance->fix,
instance->endbyte & 0x7F);
for(uint8_t burst = 0; burst < FIAT_V0_TOTAL_BURSTS; burst++) {
if(burst > 0) {
instance->encoder.upload[index++] =
level_duration_make(false, FIAT_V0_INTER_BURST_GAP);
}
// Preamble: alternating short pulses
for(int i = 0; i < FIAT_V0_PREAMBLE_PAIRS; i++) {
instance->encoder.upload[index++] = level_duration_make(true, te_short);
instance->encoder.upload[index++] = level_duration_make(false, te_short);
}
// Extend last LOW to create the sync gap
instance->encoder.upload[index - 1] = level_duration_make(false, FIAT_V0_GAP_US);
// Combine hop and fix into 64-bit data word
uint64_t data = ((uint64_t)instance->hop << 32) | instance->fix;
// Manchester encode 64 bits of data (MSB first)
for(int bit = 63; bit >= 0; bit--) {
bool curr_bit = (data >> bit) & 1;
if(curr_bit) {
instance->encoder.upload[index++] = level_duration_make(true, te_short);
instance->encoder.upload[index++] = level_duration_make(false, te_short);
} else {
instance->encoder.upload[index++] = level_duration_make(false, te_short);
instance->encoder.upload[index++] = level_duration_make(true, te_short);
}
}
// Manchester encode 7 bits of endbyte (bits 6:0, MSB first)
uint8_t endbyte = (uint8_t)(instance->endbyte & 0x7F);
for(int bit = 6; bit >= 0; bit--) {
bool curr_bit = (endbyte >> bit) & 1;
if(curr_bit) {
instance->encoder.upload[index++] = level_duration_make(true, te_short);
instance->encoder.upload[index++] = level_duration_make(false, te_short);
} else {
instance->encoder.upload[index++] = level_duration_make(false, te_short);
instance->encoder.upload[index++] = level_duration_make(true, te_short);
}
}
// Burst trailer: extended LOW
instance->encoder.upload[index++] = level_duration_make(false, te_short * 4);
}
furi_check(index <= instance->upload_capacity);
instance->encoder.size_upload = index;
instance->encoder.front = 0;
FURI_LOG_I(TAG, "Upload built: %zu elements", instance->encoder.size_upload);
}
SubGhzProtocolStatus
subghz_protocol_encoder_fiat_v0_deserialize(void* context, FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolEncoderFiatV0* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
instance->encoder.is_running = false;
instance->encoder.front = 0;
instance->encoder.repeat = 10;
flipper_format_rewind(flipper_format);
FuriString* temp_str = furi_string_alloc();
furi_check(temp_str);
do {
if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) {
FURI_LOG_E(TAG, "Missing Protocol");
break;
}
if(!furi_string_equal(temp_str, instance->base.protocol->name)) {
FURI_LOG_E(TAG, "Wrong protocol: %s", furi_string_get_cstr(temp_str));
break;
}
uint32_t bit_count_temp = 0;
if(flipper_format_read_uint32(flipper_format, "Bit", &bit_count_temp, 1)) {
// Protocol transmits 71 bits: 64-bit key + 7-bit endbyte
if(bit_count_temp == 64 || bit_count_temp == 71) {
instance->generic.data_count_bit = bit_count_temp;
} else {
FURI_LOG_E(
TAG,
"Unexpected Bit value %lu, defaulting to 71",
(unsigned long)bit_count_temp);
instance->generic.data_count_bit = 71;
}
} else {
FURI_LOG_E(TAG, "Missing Bit");
break;
}
if(!flipper_format_read_string(flipper_format, "Key", temp_str)) {
FURI_LOG_E(TAG, "Missing Key");
break;
}
const char* key_str = furi_string_get_cstr(temp_str);
uint64_t key = 0;
size_t str_len = strlen(key_str);
size_t hex_pos = 0;
for(size_t i = 0; i < str_len && hex_pos < 16; i++) {
char c = key_str[i];
if(c == ' ') continue;
uint8_t nibble;
if(c >= '0' && c <= '9') {
nibble = (uint8_t)(c - '0');
} else if(c >= 'A' && c <= 'F') {
nibble = (uint8_t)(c - 'A' + 10);
} else if(c >= 'a' && c <= 'f') {
nibble = (uint8_t)(c - 'a' + 10);
} else {
break;
}
key = (key << 4) | nibble;
hex_pos++;
}
if(hex_pos != 16) {
FURI_LOG_E(TAG, "Key parse error: expected 16 hex nibbles, got %u", (unsigned)hex_pos);
break;
}
instance->generic.data = key;
instance->hop = (uint32_t)(key >> 32);
instance->fix = (uint32_t)(key & 0xFFFFFFFF);
uint32_t btn_temp = 0;
if(flipper_format_read_uint32(flipper_format, "Btn", &btn_temp, 1)) {
instance->endbyte = (uint8_t)(btn_temp & 0x7F);
} else {
instance->endbyte = 0;
}
instance->generic.btn = instance->endbyte;
instance->generic.cnt = instance->hop;
instance->generic.serial = instance->fix;
uint32_t repeat_temp = 0;
if(flipper_format_read_uint32(flipper_format, "Repeat", &repeat_temp, 1)) {
instance->encoder.repeat = repeat_temp;
} else {
instance->encoder.repeat = 10;
}
subghz_protocol_encoder_fiat_v0_get_upload(instance);
instance->encoder.is_running = true;
FURI_LOG_I(
TAG,
"Encoder ready: hop=0x%08lX, fix=0x%08lX, endbyte=0x%02X",
instance->hop,
instance->fix,
instance->endbyte);
ret = SubGhzProtocolStatusOk;
} while(false);
furi_string_free(temp_str);
return ret;
}
void subghz_protocol_encoder_fiat_v0_stop(void* context) {
furi_check(context);
SubGhzProtocolEncoderFiatV0* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_fiat_v0_yield(void* context) {
furi_check(context);
SubGhzProtocolEncoderFiatV0* instance = context;
if(!instance->encoder.is_running || instance->encoder.repeat == 0) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
// ============================================================================
// DECODER IMPLEMENTATION
// ============================================================================
void* subghz_protocol_decoder_fiat_v0_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderFiatV0* instance = calloc(1, sizeof(SubGhzProtocolDecoderFiatV0));
furi_check(instance);
instance->base.protocol = &subghz_protocol_fiat_v0;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_fiat_v0_free(void* context) {
furi_check(context);
SubGhzProtocolDecoderFiatV0* instance = context;
free(instance);
}
void subghz_protocol_decoder_fiat_v0_reset(void* context) {
furi_check(context);
SubGhzProtocolDecoderFiatV0* instance = context;
instance->decoder.parser_step = FiatV0DecoderStepReset;
instance->decoder_state = 0;
instance->preamble_count = 0;
instance->data_low = 0;
instance->data_high = 0;
instance->bit_count = 0;
instance->hop = 0;
instance->fix = 0;
instance->endbyte = 0;
instance->final_count = 0;
instance->te_last = 0;
instance->manchester_state = ManchesterStateMid1;
}
// Helper: transition decoder into data-collection state
static void
fiat_v0_decoder_enter_data_state(SubGhzProtocolDecoderFiatV0* instance, uint32_t duration) {
instance->decoder_state = FiatV0DecoderStepData;
instance->preamble_count = 0;
instance->data_low = 0;
instance->data_high = 0;
instance->bit_count = 0;
instance->te_last = duration;
manchester_advance(
instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
}
void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t duration) {
furi_check(context);
SubGhzProtocolDecoderFiatV0* instance = context;
uint32_t te_short = (uint32_t)subghz_protocol_fiat_v0_const.te_short;
uint32_t te_long = (uint32_t)subghz_protocol_fiat_v0_const.te_long;
uint32_t te_delta = (uint32_t)subghz_protocol_fiat_v0_const.te_delta;
uint32_t gap_threshold = FIAT_V0_GAP_US;
uint32_t diff;
switch(instance->decoder_state) {
case FiatV0DecoderStepReset:
if(!level) {
return;
}
if(duration < te_short) {
diff = te_short - duration;
} else {
diff = duration - te_short;
}
if(diff < te_delta) {
instance->data_low = 0;
instance->data_high = 0;
instance->decoder_state = FiatV0DecoderStepPreamble;
instance->te_last = duration;
instance->preamble_count = 0;
instance->bit_count = 0;
manchester_advance(
instance->manchester_state,
ManchesterEventReset,
&instance->manchester_state,
NULL);
}
break;
case FiatV0DecoderStepPreamble:
if(level) {
if(duration < te_short) {
diff = te_short - duration;
} else {
diff = duration - te_short;
}
if(diff < te_delta) {
instance->preamble_count++;
instance->te_last = duration;
} else {
instance->decoder_state = FiatV0DecoderStepReset;
}
return;
}
if(duration < te_short) {
diff = te_short - duration;
} else {
diff = duration - te_short;
}
if(diff < te_delta) {
instance->preamble_count++;
instance->te_last = duration;
} else {
if(instance->preamble_count >= 0x96) {
if(duration < gap_threshold) {
diff = gap_threshold - duration;
} else {
diff = duration - gap_threshold;
}
if(diff < te_delta) {
fiat_v0_decoder_enter_data_state(instance, duration);
return;
}
}
instance->decoder_state = FiatV0DecoderStepReset;
}
if(instance->preamble_count >= 0x96 &&
instance->decoder_state == FiatV0DecoderStepPreamble) {
if(duration < gap_threshold) {
diff = gap_threshold - duration;
} else {
diff = duration - gap_threshold;
}
if(diff < te_delta) {
fiat_v0_decoder_enter_data_state(instance, duration);
return;
}
}
break;
case FiatV0DecoderStepData:
ManchesterEvent event = ManchesterEventReset;
if(duration < te_short) {
diff = te_short - duration;
if(diff < te_delta) {
event = level ? ManchesterEventShortLow : ManchesterEventShortHigh;
}
} else {
diff = duration - te_short;
if(diff < te_delta) {
event = level ? ManchesterEventShortLow : ManchesterEventShortHigh;
} else {
if(duration < te_long) {
diff = te_long - duration;
} else {
diff = duration - te_long;
}
if(diff < te_delta) {
event = level ? ManchesterEventLongLow : ManchesterEventLongHigh;
}
}
}
if(event != ManchesterEventReset) {
bool data_bit_bool;
if(manchester_advance(
instance->manchester_state,
event,
&instance->manchester_state,
&data_bit_bool)) {
uint32_t new_bit = data_bit_bool ? 1 : 0;
uint32_t carry = (instance->data_low >> 31) & 1;
instance->data_low = (instance->data_low << 1) | new_bit;
instance->data_high = (instance->data_high << 1) | carry;
instance->bit_count++;
if(instance->bit_count == 0x40) {
instance->fix = instance->data_low;
instance->hop = instance->data_high;
instance->data_low = 0;
instance->data_high = 0;
}
if(instance->bit_count == 0x47) {
instance->final_count = instance->bit_count;
instance->endbyte = (uint8_t)instance->data_low;
FURI_LOG_I(TAG, "Decoded: hop=0x%08lX fix=0x%08lX endbyte=0x%02X",
instance->hop, instance->fix, instance->endbyte & 0x7F);
instance->generic.data = ((uint64_t)instance->hop << 32) | instance->fix;
instance->generic.data_count_bit = 71;
instance->generic.serial = instance->fix;
instance->generic.btn = instance->endbyte;
instance->generic.cnt = instance->hop;
if(instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
instance->data_low = 0;
instance->data_high = 0;
instance->bit_count = 0;
instance->decoder_state = FiatV0DecoderStepReset;
}
}
} else {
if(instance->bit_count == 0x47) {
uint8_t data_low_byte = (uint8_t)instance->data_low;
instance->endbyte = data_low_byte;
FURI_LOG_I(TAG, "Decoded (gap): hop=0x%08lX fix=0x%08lX endbyte=0x%02X",
instance->hop, instance->fix, instance->endbyte & 0x7F);
instance->generic.data = ((uint64_t)instance->hop << 32) | instance->fix;
instance->generic.data_count_bit = 71;
instance->generic.serial = instance->fix;
instance->generic.btn = instance->endbyte;
instance->generic.cnt = instance->hop;
if(instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
instance->data_low = 0;
instance->data_high = 0;
instance->bit_count = 0;
instance->decoder_state = FiatV0DecoderStepReset;
} else if(instance->bit_count < 0x40) {
instance->decoder_state = FiatV0DecoderStepReset;
}
}
instance->te_last = duration;
break;
}
}
uint8_t subghz_protocol_decoder_fiat_v0_get_hash_data(void* context) {
furi_check(context);
SubGhzProtocolDecoderFiatV0* instance = context;
SubGhzBlockDecoder decoder = {
.decode_data = instance->generic.data,
.decode_count_bit = instance->generic.data_count_bit};
return subghz_protocol_blocks_get_hash_data(&decoder, (decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_fiat_v0_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_check(context);
SubGhzProtocolDecoderFiatV0* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
// Use the standard generic serialize helper (handles Filetype, Version, Frequency, Preset, Protocol, Bit, Key)
ret = subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
if(ret == SubGhzProtocolStatusOk) {
// Save CRC - calculate from key bytes (use uint32_t as required by flipper_format_write_uint32)
uint64_t key64 = instance->generic.data;
uint32_t crc = 0;
for(int i = 0; i < 8; i++) {
crc ^= (uint32_t)((key64 >> (i * 8)) & 0xFF);
}
flipper_format_write_uint32(flipper_format, "CRC", &crc, 1);
// Save decoded fields
flipper_format_write_uint32(flipper_format, "Serial", &instance->generic.serial, 1);
uint32_t temp = instance->generic.btn;
flipper_format_write_uint32(flipper_format, "Btn", &temp, 1);
flipper_format_write_uint32(flipper_format, "Cnt", &instance->generic.cnt, 1);
}
return ret;
}
SubGhzProtocolStatus subghz_protocol_decoder_fiat_v0_deserialize(void* context, FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolDecoderFiatV0* instance = context;
// Use the standard generic deserialize helper
SubGhzProtocolStatus ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
if(ret == SubGhzProtocolStatusOk) {
// Extract hop and fix from the loaded key
instance->hop = (uint32_t)(instance->generic.data >> 32);
instance->fix = (uint32_t)(instance->generic.data & 0xFFFFFFFF);
// The btn value is already loaded by generic_deserialize into instance->generic.btn
instance->endbyte = instance->generic.btn;
}
return ret;
}
void subghz_protocol_decoder_fiat_v0_get_string(void* context, FuriString* output) {
furi_check(context);
SubGhzProtocolDecoderFiatV0* instance = context;
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%08lX%08lX\r\n"
"Hop:%08lX\r\n"
"Sn:%08lX\r\n"
"EndByte:%02X\r\n",
instance->generic.protocol_name,
instance->generic.data_count_bit,
instance->hop,
instance->fix,
instance->hop,
instance->fix,
instance->endbyte & 0x7F);
}

View File

@@ -1,41 +0,0 @@
#pragma once
#include <furi.h>
#include <lib/subghz/protocols/base.h>
#include <lib/subghz/types.h>
#include <lib/subghz/blocks/const.h>
#include <lib/subghz/blocks/decoder.h>
#include <lib/subghz/blocks/encoder.h>
#include <lib/subghz/blocks/generic.h>
#include <lib/subghz/blocks/math.h>
#include <lib/toolbox/manchester_decoder.h>
#include <flipper_format/flipper_format.h>
#define FIAT_PROTOCOL_V0_NAME "Fiat SpA"
typedef struct SubGhzProtocolDecoderFiatV0 SubGhzProtocolDecoderFiatV0;
typedef struct SubGhzProtocolEncoderFiatV0 SubGhzProtocolEncoderFiatV0;
extern const SubGhzProtocol subghz_protocol_fiat_v0;
// Decoder functions
void* subghz_protocol_decoder_fiat_v0_alloc(SubGhzEnvironment* environment);
void subghz_protocol_decoder_fiat_v0_free(void* context);
void subghz_protocol_decoder_fiat_v0_reset(void* context);
void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t duration);
uint8_t subghz_protocol_decoder_fiat_v0_get_hash_data(void* context);
SubGhzProtocolStatus subghz_protocol_decoder_fiat_v0_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
SubGhzProtocolStatus
subghz_protocol_decoder_fiat_v0_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_fiat_v0_get_string(void* context, FuriString* output);
// Encoder functions
void* subghz_protocol_encoder_fiat_v0_alloc(SubGhzEnvironment* environment);
void subghz_protocol_encoder_fiat_v0_free(void* context);
SubGhzProtocolStatus
subghz_protocol_encoder_fiat_v0_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_encoder_fiat_v0_stop(void* context);
LevelDuration subghz_protocol_encoder_fiat_v0_yield(void* context);

View File

@@ -1,48 +1,76 @@
#include "protocol_items.h" // IWYU pragma: keep
const SubGhzProtocol* const subghz_protocol_registry_items[] = {
&subghz_protocol_gate_tx, &subghz_protocol_keeloq,
&subghz_protocol_nice_flo, &subghz_protocol_came,
&subghz_protocol_faac_slh, &subghz_protocol_nice_flor_s,
&subghz_protocol_came_twee, &subghz_protocol_came_atomo,
//&subghz_protocol_nero_sketch, //&subghz_protocol_ido,
&subghz_protocol_hormann, //&subghz_protocol_nero_radio,
&subghz_protocol_somfy_telis, &subghz_protocol_somfy_keytis,
&subghz_protocol_princeton, &subghz_protocol_raw,
&subghz_protocol_linear, &subghz_protocol_secplus_v2,
&subghz_protocol_secplus_v1, &subghz_protocol_megacode,
//&subghz_protocol_holtek,
&subghz_protocol_gate_tx,
&subghz_protocol_keeloq,
&subghz_protocol_nice_flo,
&subghz_protocol_came,
&subghz_protocol_faac_slh,
&subghz_protocol_nice_flor_s,
&subghz_protocol_came_twee,
&subghz_protocol_came_atomo,
//&subghz_protocol_nero_sketch,
//&subghz_protocol_ido,
&subghz_protocol_hormann,
//&subghz_protocol_nero_radio,
&subghz_protocol_somfy_telis,
&subghz_protocol_somfy_keytis,
&subghz_protocol_princeton,
&subghz_protocol_raw,
&subghz_protocol_linear,
&subghz_protocol_secplus_v2,
&subghz_protocol_secplus_v1,
&subghz_protocol_megacode,
&subghz_protocol_holtek,
&subghz_protocol_chamb_code,
//&subghz_protocol_power_smart,
&subghz_protocol_marantec,
//&subghz_protocol_bett,
&subghz_protocol_doitrand,
&subghz_protocol_phoenix_v2, //&subghz_protocol_honeywell_wdb,
&subghz_protocol_phoenix_v2,
//&subghz_protocol_honeywell_wdb,
//&subghz_protocol_magellan,
//&subghz_protocol_intertechno_v3,
//&subghz_protocol_clemsa, //&subghz_protocol_ansonic,
&subghz_protocol_smc5326, //&subghz_protocol_holtek_th12x,
&subghz_protocol_linear_delta3, //&subghz_protocol_dooya,
&subghz_protocol_alutech_at_4n, &subghz_protocol_kinggates_stylo_4k,
&subghz_protocol_bin_raw, &subghz_protocol_mastercode,
//&subghz_protocol_clemsa,
//&subghz_protocol_ansonic,
&subghz_protocol_smc5326,
&subghz_protocol_holtek_th12x,
&subghz_protocol_linear_delta3,
//&subghz_protocol_dooya,
&subghz_protocol_alutech_at_4n,
&subghz_protocol_kinggates_stylo_4k,
&subghz_protocol_bin_raw,
&subghz_protocol_mastercode,
//&subghz_protocol_honeywell,
//&subghz_protocol_legrand,
&subghz_protocol_dickert_mahs, //&subghz_protocol_gangqi,
&subghz_protocol_marantec24, //&subghz_protocol_hollarm,
&subghz_protocol_hay21, &subghz_protocol_revers_rb2,
&subghz_protocol_dickert_mahs,
//&subghz_protocol_gangqi,
&subghz_protocol_marantec24,
//&subghz_protocol_hollarm,
&subghz_protocol_hay21,
&subghz_protocol_revers_rb2,
//&subghz_protocol_feron,
&subghz_protocol_roger,
//&subghz_protocol_elplast,
//&subghz_protocol_treadmill37,
&subghz_protocol_beninca_arc, //&subghz_protocol_jarolift,
&subghz_protocol_vag, &subghz_protocol_porsche_cayenne, &subghz_protocol_ford_v0,
&subghz_protocol_beninca_arc,
//&subghz_protocol_jarolift,
&subghz_protocol_vag,
&subghz_protocol_porsche_cayenne,
&subghz_protocol_ford_v0,
&subghz_protocol_psa,
&subghz_protocol_fiat_v0, &subghz_protocol_fiat_marelli,
&subghz_protocol_subaru, &subghz_protocol_mazda_siemens,
&subghz_protocol_kia_v0, &subghz_protocol_kia_v1,
&subghz_protocol_kia_v2, &subghz_protocol_kia_v3_v4,
&subghz_protocol_kia_v5, &subghz_protocol_kia_v6,
&subghz_protocol_suzuki, &subghz_protocol_mitsubishi_v0,
&subghz_protocol_fiat_spa,
&subghz_protocol_fiat_marelli,
&subghz_protocol_subaru,
&subghz_protocol_mazda_siemens,
&subghz_protocol_kia_v0,
&subghz_protocol_kia_v1,
&subghz_protocol_kia_v2,
&subghz_protocol_kia_v3_v4,
&subghz_protocol_kia_v5,
&subghz_protocol_kia_v6,
&subghz_protocol_suzuki,
&subghz_protocol_mitsubishi_v0,
};
const SubGhzProtocolRegistry subghz_protocol_registry = {

View File

@@ -23,16 +23,16 @@
#include "secplus_v2.h"
#include "secplus_v1.h"
#include "megacode.h"
//#include "holtek.h"
#include "holtek.h"
#include "chamberlain_code.h"
#include "power_smart.h"
#include "marantec.h"
#include "bett.h"
#include "doitrand.h"
#include "phoenix_v2.h"
//#include "honeywell_wdb.h"
//#include "magellan.h"
//#include "intertechno_v3.h"
#include "honeywell_wdb.h"
#include "magellan.h"
#include "intertechno_v3.h"
#include "clemsa.h"
#include "ansonic.h"
#include "smc5326.h"
@@ -42,8 +42,8 @@
#include "kinggates_stylo_4k.h"
#include "bin_raw.h"
#include "mastercode.h"
//#include "honeywell.h"
//#include "legrand.h"
#include "honeywell.h"
#include "legrand.h"
#include "dickert_mahs.h"
#include "gangqi.h"
#include "marantec24.h"
@@ -60,7 +60,7 @@
#include "porsche_cayenne.h"
#include "ford_v0.h"
#include "psa.h"
#include "fiat_v0.h"
#include "fiat_spa.h"
#include "fiat_marelli.h"
#include "subaru.h"
#include "kia_generic.h"
@@ -73,4 +73,3 @@
#include "suzuki.h"
#include "mitsubishi_v0.h"
#include "mazda_siemens.h"
#include "keys.h"

View File

@@ -290,6 +290,32 @@ __attribute__((optimize("O3"), always_inline)) static inline void
*v1 = b;
}
typedef struct {
uint32_t s0[TEA_ROUNDS];
uint32_t s1[TEA_ROUNDS];
} PsaTeaSchedule;
__attribute__((optimize("O3"), always_inline)) static inline void
psa_tea_build_schedule(const uint32_t* key, PsaTeaSchedule* out) {
for(int i = 0; i < TEA_ROUNDS; i++) {
uint32_t sum0 = (uint32_t)((uint64_t)i * TEA_DELTA);
uint32_t sum1 = (uint32_t)((uint64_t)(i + 1) * TEA_DELTA);
out->s0[i] = key[sum0 & 3] + sum0;
out->s1[i] = key[(sum1 >> 11) & 3] + sum1;
}
}
__attribute__((optimize("O3"), always_inline)) static inline void
psa_tea_encrypt_with_schedule(uint32_t* restrict v0, uint32_t* restrict v1, const PsaTeaSchedule* sched) {
uint32_t a = *v0, b = *v1;
for(int i = 0; i < TEA_ROUNDS; i++) {
a += (sched->s0[i] ^ (((b >> 5) ^ (b << 4)) + b));
b += (sched->s1[i] ^ (((a >> 5) ^ (a << 4)) + a));
}
*v0 = a;
*v1 = b;
}
static void psa_prepare_tea_data(uint8_t* buffer, uint32_t* w0, uint32_t* w1) {
*w0 = ((uint32_t)buffer[3] << 16) | ((uint32_t)buffer[2] << 24) |
((uint32_t)buffer[4] << 8) | (uint32_t)buffer[5];
@@ -380,6 +406,8 @@ static void psa_extract_fields_mode36(uint8_t* buffer, SubGhzProtocolDecoderPSA*
__attribute__((optimize("O3"))) static bool psa_brute_force_decrypt_bf1(SubGhzProtocolDecoderPSA* instance, uint8_t* buffer, uint32_t w0, uint32_t w1, PsaDecryptProgressCallback progress_cb, void* progress_ctx) {
uint32_t bf1_total = PSA_BF1_END - PSA_BF1_START;
PsaTeaSchedule bf1_sched;
psa_tea_build_schedule(PSA_BF1_KEY_SCHEDULE, &bf1_sched);
for(uint32_t counter = PSA_BF1_START; counter < PSA_BF1_END; counter++) {
if(progress_cb && ((counter - PSA_BF1_START) & 0xFFFF) == 0) {
uint8_t pct = (uint8_t)(((uint64_t)(counter - PSA_BF1_START) * 50) / bf1_total);
@@ -387,24 +415,24 @@ __attribute__((optimize("O3"))) static bool psa_brute_force_decrypt_bf1(SubGhzPr
}
uint32_t wk2 = PSA_BF1_CONST_U4;
uint32_t wk3 = counter;
psa_tea_encrypt(&wk2, &wk3, PSA_BF1_KEY_SCHEDULE);
psa_tea_encrypt_with_schedule(&wk2, &wk3, &bf1_sched);
uint32_t wk0 = (counter << 8) | 0x0E;
uint32_t wk1 = PSA_BF1_CONST_U5;
psa_tea_encrypt(&wk0, &wk1, PSA_BF1_KEY_SCHEDULE);
psa_tea_encrypt_with_schedule(&wk0, &wk1, &bf1_sched);
uint32_t working_key[4] = {wk0, wk1, wk2, wk3};
uint32_t dec_v0 = w0;
uint32_t dec_v1 = w1;
psa_tea_decrypt(&dec_v0, &dec_v1, working_key);
if((counter & 0xFFFFFF) == (dec_v0 >> 8)) {
uint8_t crc = psa_calculate_tea_crc(dec_v0, dec_v1);
if(crc == (dec_v1 & 0xFF)) {
psa_unpack_tea_result_to_buffer(buffer, dec_v0, dec_v1);
psa_extract_fields_mode36(buffer, instance);
instance->decrypted_seed = counter; // bf1 found key
instance->decrypted_seed = counter;
return true;
}
}
@@ -1976,7 +2004,7 @@ bool subghz_protocol_psa_decrypt_file(FlipperFormat* flipper_format, FuriString*
if(result_str != NULL) {
furi_string_printf(result_str,
"Decrypted!\nType: %02X\nKey: %08lX",
"Type: %02X\nSeed: %08lX",
instance.decrypted_type,
instance.decrypted_seed);
}