Compare commits
33 Commits
dev-592bf5
...
dev-e881d6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e881d69ab3 | ||
|
|
b041177398 | ||
|
|
f347d5a976 | ||
|
|
3a6da87288 | ||
|
|
5d94639d81 | ||
|
|
5dcfc48e10 | ||
|
|
20a95b2fec | ||
|
|
3605669cc5 | ||
|
|
fb1c28a0dd | ||
|
|
64a971e806 | ||
|
|
12db96a8ab | ||
|
|
4b50b8b70c | ||
|
|
0f24f8c105 | ||
|
|
238f39d0d8 | ||
|
|
4c3581735b | ||
|
|
689df5262d | ||
|
|
86c740d923 | ||
|
|
0aef017c15 | ||
|
|
cea3bc3b6a | ||
|
|
f3d08573a1 | ||
|
|
9e52a6eb6b | ||
|
|
faf669b457 | ||
|
|
e445b28d73 | ||
|
|
19e2eaa554 | ||
|
|
2571ad7f22 | ||
|
|
22a0870559 | ||
|
|
1c9d1f404a | ||
|
|
fabb1ccc2d | ||
|
|
6a432a93ad | ||
|
|
d2cca91ec8 | ||
|
|
6e483393e1 | ||
|
|
4dc688c25b | ||
|
|
585ce97358 |
3
.github/workflows/build-dev.yml
vendored
@@ -17,6 +17,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build firmware
|
- name: Build firmware
|
||||||
run: |
|
run: |
|
||||||
|
export DIST_SUFFIX=Flipper-ARF
|
||||||
chmod +x fbt
|
chmod +x fbt
|
||||||
./fbt COMPACT=1 DEBUG=0 updater_package
|
./fbt COMPACT=1 DEBUG=0 updater_package
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ jobs:
|
|||||||
id: firmware
|
id: firmware
|
||||||
run: |
|
run: |
|
||||||
DIR=$(ls -d dist/f7-* | head -n 1)
|
DIR=$(ls -d dist/f7-* | head -n 1)
|
||||||
FILE="$DIR/flipper-z-f7-update-local.tgz"
|
FILE="$DIR/flipper-z-f7-update-Flipper-ARF.tgz"
|
||||||
|
|
||||||
if [ ! -f "$FILE" ]; then
|
if [ ! -f "$FILE" ]; then
|
||||||
echo "Firmware file not found!"
|
echo "Firmware file not found!"
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ This project may incorporate, adapt, or build upon **other open-source projects*
|
|||||||
| PSA (Peugeot/Citroën/DS) | PSA GROUP | 433 MHz | AM/FM | Yes | Yes | Yes |
|
| PSA (Peugeot/Citroën/DS) | PSA GROUP | 433 MHz | AM/FM | Yes | Yes | Yes |
|
||||||
| Ford | Ford V0 | 315/433 MHz | AM | Yes | Yes | Yes |
|
| Ford | Ford V0 | 315/433 MHz | AM | Yes | Yes | Yes |
|
||||||
| Fiat | Fiat SpA | 433 MHz | AM | Yes | Yes | Yes |
|
| Fiat | Fiat SpA | 433 MHz | AM | Yes | Yes | Yes |
|
||||||
| Fiat | Fiat Marelli | 433 MHz | AM | No | Yes | No |
|
| Fiat | Fiat Marelli/Delphi | 433 MHz | AM | No | Yes | No |
|
||||||
| Subaru | Subaru | 433 MHz | AM | Yes | Yes | No |
|
| Subaru | Subaru | 433 MHz | AM | Yes | Yes | No |
|
||||||
| Mazda | Siemens (5WK49365D) | 315/433 MHz | FM | Yes | Yes | Yes |
|
| Mazda | Siemens (5WK49365D) | 315/433 MHz | FM | Yes | Yes | Yes |
|
||||||
| Kia/Hyundai | Kia V0 | 433 MHz | FM | Yes | Yes | Yes |
|
| Kia/Hyundai | Kia V0 | 433 MHz | FM | Yes | Yes | Yes |
|
||||||
@@ -134,10 +134,9 @@ Flipper-ARF aims to achieve:
|
|||||||
## To Do / Planned Features
|
## To Do / Planned Features
|
||||||
|
|
||||||
- [ ] Add Scher Khan & Starline protocols
|
- [ ] Add Scher Khan & Starline protocols
|
||||||
- [ ] Marelli BSI encodere and encryption
|
- [ ] Marelli BSI encoder and encryption
|
||||||
- [ ] Fix and reintegrate RollJam app (future updates)
|
- [ ] Improve RollJam app
|
||||||
- [ ] Expand and refine Subaru, Kia, PSA, and other manufacturer protocols
|
- [ ] Expand and refine Subaru, Kia, PSA, and other manufacturer protocols
|
||||||
- [ ] Improve collaboration workflow to avoid overlapping work
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
11
applications/main/KeylessGoSniffer/application.fam
Normal 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",
|
||||||
|
)
|
||||||
506
applications/main/KeylessGoSniffer/lf_analyze.py
Normal 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()
|
||||||
446
applications/main/KeylessGoSniffer/lf_sniffer.c
Normal 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;
|
||||||
|
}
|
||||||
80
applications/main/KeylessGoSniffer/lf_sniffer.h
Normal 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
|
||||||
@@ -10,6 +10,12 @@
|
|||||||
|
|
||||||
static bool otg_was_enabled = false;
|
static bool otg_was_enabled = false;
|
||||||
|
|
||||||
|
static bool use_flux_capacitor = false;
|
||||||
|
|
||||||
|
void rolljam_ext_set_flux_capacitor(bool enabled) {
|
||||||
|
use_flux_capacitor = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
static void rolljam_ext_power_on(void) {
|
static void rolljam_ext_power_on(void) {
|
||||||
otg_was_enabled = furi_hal_power_is_otg_enabled();
|
otg_was_enabled = furi_hal_power_is_otg_enabled();
|
||||||
if(!otg_was_enabled) {
|
if(!otg_was_enabled) {
|
||||||
@@ -423,7 +429,7 @@ static int32_t jam_thread_worker(void* context) {
|
|||||||
0xAA,0x55
|
0xAA,0x55
|
||||||
};
|
};
|
||||||
|
|
||||||
furi_hal_gpio_write(pin_amp, true);
|
if(use_flux_capacitor) furi_hal_gpio_write(pin_amp, true);
|
||||||
jam_start_tx(noise_pattern, 62);
|
jam_start_tx(noise_pattern, 62);
|
||||||
|
|
||||||
uint8_t st = cc_state();
|
uint8_t st = cc_state();
|
||||||
@@ -432,7 +438,7 @@ static int32_t jam_thread_worker(void* context) {
|
|||||||
jam_start_tx(noise_pattern, 62);
|
jam_start_tx(noise_pattern, 62);
|
||||||
st = cc_state();
|
st = cc_state();
|
||||||
if(st != MARC_TX) {
|
if(st != MARC_TX) {
|
||||||
furi_hal_gpio_write(pin_amp, false);
|
if(use_flux_capacitor) furi_hal_gpio_write(pin_amp, false);
|
||||||
FURI_LOG_E(TAG, "JAM: Cannot enter TX!");
|
FURI_LOG_E(TAG, "JAM: Cannot enter TX!");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -492,7 +498,7 @@ static int32_t jam_thread_worker(void* context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cc_idle();
|
cc_idle();
|
||||||
furi_hal_gpio_write(pin_amp, false);
|
if(use_flux_capacitor) furi_hal_gpio_write(pin_amp, false);
|
||||||
cc_write(CC_IOCFG2, 0x2E);
|
cc_write(CC_IOCFG2, 0x2E);
|
||||||
FURI_LOG_I(TAG, "JAM: STOPPED (loops=%lu uf=%lu refills=%lu)", loops, underflows, refills);
|
FURI_LOG_I(TAG, "JAM: STOPPED (loops=%lu uf=%lu refills=%lu)", loops, underflows, refills);
|
||||||
return 0;
|
return 0;
|
||||||
@@ -512,13 +518,17 @@ void rolljam_ext_gpio_init(void) {
|
|||||||
furi_hal_gpio_write(pin_mosi, false);
|
furi_hal_gpio_write(pin_mosi, false);
|
||||||
furi_hal_gpio_init(pin_miso, GpioModeInput, GpioPullUp, GpioSpeedVeryHigh);
|
furi_hal_gpio_init(pin_miso, GpioModeInput, GpioPullUp, GpioSpeedVeryHigh);
|
||||||
furi_hal_gpio_init(pin_gdo0, GpioModeInput, GpioPullDown, GpioSpeedVeryHigh);
|
furi_hal_gpio_init(pin_gdo0, GpioModeInput, GpioPullDown, GpioSpeedVeryHigh);
|
||||||
furi_hal_gpio_init_simple(pin_amp, GpioModeOutputPushPull);
|
if(use_flux_capacitor) {
|
||||||
furi_hal_gpio_write(pin_amp, false);
|
furi_hal_gpio_init_simple(pin_amp, GpioModeOutputPushPull);
|
||||||
|
furi_hal_gpio_write(pin_amp, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void rolljam_ext_gpio_deinit(void) {
|
void rolljam_ext_gpio_deinit(void) {
|
||||||
furi_hal_gpio_write(pin_amp, false);
|
if(use_flux_capacitor) {
|
||||||
furi_hal_gpio_init_simple(pin_amp, GpioModeAnalog);
|
furi_hal_gpio_write(pin_amp, false);
|
||||||
|
furi_hal_gpio_init_simple(pin_amp, GpioModeAnalog);
|
||||||
|
}
|
||||||
furi_hal_gpio_init(pin_cs, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
furi_hal_gpio_init(pin_cs, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||||
furi_hal_gpio_init(pin_sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
furi_hal_gpio_init(pin_sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||||
furi_hal_gpio_init(pin_mosi, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
furi_hal_gpio_init(pin_mosi, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
void rolljam_ext_gpio_init(void);
|
void rolljam_ext_gpio_init(void);
|
||||||
|
void rolljam_ext_set_flux_capacitor(bool enabled);
|
||||||
void rolljam_ext_gpio_deinit(void);
|
void rolljam_ext_gpio_deinit(void);
|
||||||
void rolljam_jammer_start(RollJamApp* app);
|
void rolljam_jammer_start(RollJamApp* app);
|
||||||
void rolljam_jammer_stop(RollJamApp* app);
|
void rolljam_jammer_stop(RollJamApp* app);
|
||||||
|
|||||||
@@ -57,6 +57,11 @@ const char* jam_offset_names[] = {
|
|||||||
"1000 kHz",
|
"1000 kHz",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const char* hw_names[] = {
|
||||||
|
"CC1101",
|
||||||
|
"Flux Cap",
|
||||||
|
};
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Scene handlers table (extern declarations in scene header)
|
// Scene handlers table (extern declarations in scene header)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -119,6 +124,7 @@ static RollJamApp* rolljam_app_alloc(void) {
|
|||||||
app->mod_index = ModIndex_AM650;
|
app->mod_index = ModIndex_AM650;
|
||||||
app->jam_offset_index = JamOffIndex_700k;
|
app->jam_offset_index = JamOffIndex_700k;
|
||||||
app->jam_offset_hz = jam_offset_values[JamOffIndex_700k];
|
app->jam_offset_hz = jam_offset_values[JamOffIndex_700k];
|
||||||
|
app->hw_index = HwIndex_CC1101;
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
app->gui = furi_record_open(RECORD_GUI);
|
app->gui = furi_record_open(RECORD_GUI);
|
||||||
|
|||||||
@@ -69,6 +69,17 @@ typedef enum {
|
|||||||
extern const uint32_t jam_offset_values[];
|
extern const uint32_t jam_offset_values[];
|
||||||
extern const char* jam_offset_names[];
|
extern const char* jam_offset_names[];
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Hardware type
|
||||||
|
// ============================================================
|
||||||
|
typedef enum {
|
||||||
|
HwIndex_CC1101 = 0,
|
||||||
|
HwIndex_FluxCapacitor,
|
||||||
|
HwIndex_COUNT,
|
||||||
|
} HwIndex;
|
||||||
|
|
||||||
|
extern const char* hw_names[];
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Scenes
|
// Scenes
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -133,6 +144,7 @@ typedef struct {
|
|||||||
FreqIndex freq_index;
|
FreqIndex freq_index;
|
||||||
ModIndex mod_index;
|
ModIndex mod_index;
|
||||||
JamOffIndex jam_offset_index;
|
JamOffIndex jam_offset_index;
|
||||||
|
HwIndex hw_index;
|
||||||
uint32_t frequency;
|
uint32_t frequency;
|
||||||
uint32_t jam_frequency;
|
uint32_t jam_frequency;
|
||||||
uint32_t jam_offset_hz;
|
uint32_t jam_offset_hz;
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ void rolljam_scene_attack_phase1_on_enter(void* context) {
|
|||||||
view_dispatcher_switch_to_view(
|
view_dispatcher_switch_to_view(
|
||||||
app->view_dispatcher, RollJamViewWidget);
|
app->view_dispatcher, RollJamViewWidget);
|
||||||
|
|
||||||
|
// Configure hardware type
|
||||||
|
rolljam_ext_set_flux_capacitor(app->hw_index == HwIndex_FluxCapacitor);
|
||||||
|
|
||||||
// Start jamming
|
// Start jamming
|
||||||
rolljam_jammer_start(app);
|
rolljam_jammer_start(app);
|
||||||
|
|
||||||
|
|||||||
@@ -30,10 +30,18 @@ static void menu_jam_offset_changed(VariableItem* item) {
|
|||||||
variable_item_set_current_value_text(item, jam_offset_names[index]);
|
variable_item_set_current_value_text(item, jam_offset_names[index]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void menu_hw_changed(VariableItem* item) {
|
||||||
|
RollJamApp* app = variable_item_get_context(item);
|
||||||
|
uint8_t index = variable_item_get_current_value_index(item);
|
||||||
|
|
||||||
|
app->hw_index = index;
|
||||||
|
variable_item_set_current_value_text(item, hw_names[index]);
|
||||||
|
}
|
||||||
|
|
||||||
static void menu_enter_callback(void* context, uint32_t index) {
|
static void menu_enter_callback(void* context, uint32_t index) {
|
||||||
RollJamApp* app = context;
|
RollJamApp* app = context;
|
||||||
|
|
||||||
if(index == 3) {
|
if(index == 4) {
|
||||||
view_dispatcher_send_custom_event(
|
view_dispatcher_send_custom_event(
|
||||||
app->view_dispatcher, RollJamEventStartAttack);
|
app->view_dispatcher, RollJamEventStartAttack);
|
||||||
}
|
}
|
||||||
@@ -73,6 +81,16 @@ void rolljam_scene_menu_on_enter(void* context) {
|
|||||||
variable_item_set_current_value_index(offset_item, app->jam_offset_index);
|
variable_item_set_current_value_index(offset_item, app->jam_offset_index);
|
||||||
variable_item_set_current_value_text(offset_item, jam_offset_names[app->jam_offset_index]);
|
variable_item_set_current_value_text(offset_item, jam_offset_names[app->jam_offset_index]);
|
||||||
|
|
||||||
|
// --- Hardware ---
|
||||||
|
VariableItem* hw_item = variable_item_list_add(
|
||||||
|
app->var_item_list,
|
||||||
|
"Hardware",
|
||||||
|
HwIndex_COUNT,
|
||||||
|
menu_hw_changed,
|
||||||
|
app);
|
||||||
|
variable_item_set_current_value_index(hw_item, app->hw_index);
|
||||||
|
variable_item_set_current_value_text(hw_item, hw_names[app->hw_index]);
|
||||||
|
|
||||||
// --- Start button ---
|
// --- Start button ---
|
||||||
variable_item_list_add(
|
variable_item_list_add(
|
||||||
app->var_item_list,
|
app->var_item_list,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ App(
|
|||||||
"nfc",
|
"nfc",
|
||||||
"subghz",
|
"subghz",
|
||||||
"rolljam",
|
"rolljam",
|
||||||
|
"lf_sniffer",
|
||||||
"subghz_bruteforcer",
|
"subghz_bruteforcer",
|
||||||
"archive",
|
"archive",
|
||||||
"subghz_remote",
|
"subghz_remote",
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ typedef enum {
|
|||||||
SubGhzViewIdFrequencyAnalyzer,
|
SubGhzViewIdFrequencyAnalyzer,
|
||||||
SubGhzViewIdReadRAW,
|
SubGhzViewIdReadRAW,
|
||||||
SubGhzViewIdPsaDecrypt,
|
SubGhzViewIdPsaDecrypt,
|
||||||
|
SubGhzViewIdKeeloqDecrypt,
|
||||||
|
|
||||||
} SubGhzViewId;
|
} SubGhzViewId;
|
||||||
|
|
||||||
|
|||||||
@@ -1,107 +1,108 @@
|
|||||||
Filetype: Flipper SubGhz Keystore File
|
Filetype: Flipper SubGhz Keystore File
|
||||||
Version: 0
|
Version: 0
|
||||||
Encryption: 0
|
Encryption: 1
|
||||||
0000000000000000:1:Allgate_Simple
|
IV: 41 52 46 5F 54 68 65 5F 46 69 72 6D 77 61 72 65
|
||||||
0000000023304758:6:KGB/Subaru
|
1F55E94BD99C5FCA4E8CD620CB2F014854B25B5A5AC7633F7B8C2CE3993328A7A275ED382F23319461EC7B2E2BC450AC
|
||||||
0000000033205748:7:Magic_2
|
71DF40F4D16CF30DC5223ED59B395704BC67271E3058CB09D7F5D9CEE1C04852
|
||||||
00000000C3644917:2:VAG_Custom_Seed
|
250208FB825F12A07A8C3F295C3B5FA69F52F2C9CA80452FDD1AEFC5A25F24DC
|
||||||
0102030410203040:1:IronLogic
|
9D6D26F67532E91FE89476A9E754A60DF8ECE7B92CD1772A7AD3190FCABF06414C0A3762E0012D102798EE204A5549EC
|
||||||
0123456789ABCDEF:2:Stilmatic
|
0DCC0382D81B9D3E291FDEDDC697E2841D88A326D2869BA29F248D5DA4C96110
|
||||||
0132456789ABCDEF:1:Pandora_Test_Debug_2
|
F84B5EB3BB882F76E0264A5A1791EDAC8A1CA35E7579EA4D247E473EB1F8F4C1
|
||||||
05AEDAABAA981903:1:Rosh
|
F8A4AFFF527E13643AC511900CF1764408691F60ABD1373E6AFE477E9967FD7F57E3CE41AE4700722DFA383BC64E9669
|
||||||
08AEDAABAA981903:1:Dea_Mio
|
265C0060DE53B95EF07EE3E00045A6D03C89FE1D89F90EA3A2AFBFDB4A4636D6
|
||||||
1067DC33E7D88A46:0:Leopard
|
DA1EBA6372C50D2072AC38CAACA3B023DEFDAE50987E764BEDA1E9FE53390CBA
|
||||||
12332594A9189478:1:Sheriff
|
1C0378D5294FC62DCD95A385B3AD2E6FACC13D9AAC37EF7BCE4341E33876BCE8
|
||||||
1234567812345678:2:NICE_Flor_S
|
68AA58FB1DCDC05E56E0685DC57661333F66D890C6377771327DFB5EBEDA6AE1
|
||||||
1234567890123456:1:Cenmax
|
E647CDE269D1DC5F404830C30B3CE38D8C0B6E928DB4E8863523799E51977B2B
|
||||||
188B0544A9B1DF14:2:Centurion
|
AB40C8B0815B04C84BA1B1ACFFC93F20FE7F60F64AAE6E6AD4562415E6EFC049
|
||||||
1961DAC2EB198847:2:Guard_RF-311A
|
DCD258016EB06D81DFC494261017E9DF36601076970EA09B008EEA43DA0E68EE
|
||||||
1B36977B281597AC:1:Pandora_PRO
|
321F568C032B4C8B0B392A868ECC0D87EC9969E328FA35BBE9656701C77C35A8
|
||||||
207DBBE59D386F44:2:Cardin_S449
|
E14A72B0B0689AA7E08A6081E56A00862A34808D77111DE804DDFA39FFCF6782
|
||||||
2156EB02D8E9B977:1:Pandora_DEA
|
FF2573046D35FAA029BF0871A2D3216029B66927CECF527C72F192E06E13C3DC
|
||||||
2255EA01DBEABA76:1:Pandora_GIBIDI
|
FF890BCB54AD911EE78345FCC4F2DA65B653D68C7F6774C74DC998A2295F4916
|
||||||
2354E900DAEBBB75:1:Pandora_MCODE
|
8D1D92B53C1DD31BE8E1759640471793EFA8E987A4EDEE64A6DF658F2F67C136F5EFC0E895493BC02DB11B783E04DF84
|
||||||
2453EE07DDECBC72:1:Pandora_Unknown_1
|
20FD4B91D4169332B3CCCBDAB4FA5C730F37FF16A283ADBA6EA9D80F5D9C9F26
|
||||||
2552EF06DCEDBD73:1:Pandora_SUZUKI
|
7B710488CFE33CE2EDDD37A3E864A517F7309FB097F8959497A5AD42B0E5C8AC2BA7AC6CD6B0BFCDEF5C88F4A995F20C
|
||||||
2587010764070864:1:Harpoon
|
00D242BF328330A11124C423779CC73379BB80B8071CEFB8F413ED3C9A11BB1F7D4B3B22CA0AC10A74A68887E0994259
|
||||||
2626991902991902:1:Gibidi
|
5BA325DD503C710D0BC153C50A2CC4F621AB6AE87E9CBBCD2996ED1B2FA1A854
|
||||||
2651EC05DFEEBE70:1:Pandora_Unknown_2
|
731724428F104BB5697E7705B3E0834B41202A9F2D49C90C4889DDDB21F5AC3E
|
||||||
27193A9B117C0835:11:Jarolift
|
A22BAAB3818146B7B099CD3D65634F7CEBAD36015E7A5A16206ADFBD51988E038F2F62D273CA65CC592AF7DEB805510A
|
||||||
2739451414471820:2:Audii
|
8DD12F73EF956047011A61C212986775D2A41F98E629DD78C6FA70C0E2634ECA
|
||||||
2750ED04DEEFBF71:2:Pandora_NISSAN
|
25D57BF539C51295524A53E5EF633547C54CB3D9A8072D9CAD897CEBD2AED3B3
|
||||||
30317B307D794471:1:KEY
|
C54BA9B2D4C6DFDC639E2964316D5311B2A039631880D5F4986E38D63976E28EABFF01B643EFC853DFB8E2E1622A7674
|
||||||
314C3865304E3961:1:Novoferm
|
7A4062817B4848748A66AB34F9A4DA942BB3FE82CE1E264A12297FB7C6CF68B4
|
||||||
32130221685B9D8C:2:VAG_HELLA_VPM2
|
65DC6DC85C44CF8D04F7B786C16D30F9BB12C7AE80B68612464021CAAEB196AD
|
||||||
3519B934A4227995:1:Pandora_SUBARU
|
DD1F98CF4AF384B2412A786614C16109ED8FF9E842DCFF8E859B1D27BF1E08AD4C31793D1A6E07F8BCC7E5E0BCD4DF3B
|
||||||
352BABACA5F4DFE0:1:Pecinin
|
C0FF96999B5AC49EDCDFF82A51968F2A985D240A2AC91178C02B34DDD5F2EE77DA0804D9E47470889DC8FC8BA01B2C11
|
||||||
381D7D9A2A17AE99:1:Pandora_M101
|
51BAEBB4763C7E2A57C638ED824D83C73FB5E3E128992BBAAB7690CED5B40310
|
||||||
3E461AB4F76DA19B:2:Merlin
|
947F12090F89793CE465816679654F5E6397E3A15D726DF86A6023E6E8ABB065
|
||||||
4030201004030201:1:IL-100(Smart)
|
B70CC56D0AF6F8E04E63031BBCB02EED4DB59A80B81FD4F67B8B61DFA57A0D51
|
||||||
4130850A82610A14:1:Pantera_CLK
|
C3DC0E9717759C36DFBDA6CCDD146B54C5F1A52A3C3802ADE9D2303BD179F5CE
|
||||||
414C455831393830:1:Kingates_Stylo4k
|
5728832CD50226018ADD6A4736866EE4E932616C1CE74D67E2CE00D1427CBD96
|
||||||
4292903083134583:2:Monarch
|
222A41966802B8607EF496D898D5BCA41BD9D891552F53FD809C81C487EBB8C25DB6CA656AFF45D5911BE9B10BADBDF5
|
||||||
434144494C4C4143:2:Cadillac_GM
|
E171BE8EEC7773BB6AC1EC6B8AD13696267770931E4D72ADEB6A519A085F4EAF
|
||||||
43485259534C4552:2:Chrysler
|
29FA68F7BB01E55AA738A68866F674CF34873E6109C328D4654FF3CCAE7D3C73
|
||||||
444145574F4F0000:2:Daewoo
|
E6791ABDD092843AE7A9B3A936B1AA77B812FF16CEA352F7972F132480AA4561
|
||||||
4772736565734769:1:Aprimatic
|
701A4DCFF6C7A56D9DAC77F071220F7C28B8206BB5F3213F5761C8ED6DABEDC6
|
||||||
484D6C6D73545253:1:NICE_MHOUSE
|
AA4678E9AF3FE65F9B8E90A5CC95034CF510F1BD2C26DC89200CDAF3E15A9990
|
||||||
484F4E4441200000:2:Honda
|
A13B1578F6A2241830208014AD3E864CB0AA442AFF02952F3C2FCCC4F33140B2
|
||||||
4859554E44414920:2:Hyundai_Asia
|
7C08CEC7BD2CC73EC9C436D32BB692E9020B9E043CF5F428705097F0AB141DEF
|
||||||
4C6D4D7A55644F76:3:BFT
|
8F39C7668AD33A2F66E5451E07CDF87D4B44FC962DEDE7D68365EA8E729A058E
|
||||||
4D41474E64796E65:1:Pantera
|
67F6C50C2D1BDEF471913E405A182D91040D4F4160484ED8762037F7F2EE8EBA
|
||||||
4D49545355424953:2:Mitsubishi
|
4DF3627B7F42226780EB3C247F18C1F37CD25AA22C120F69B732A1FAC9D02C8A
|
||||||
4E495353414E2000:2:Nissan
|
2A277456C8BA44CDC5A44D4353B9FA04D3F45743DB04B1E659F7DB4A8219672E
|
||||||
535446524B453030:13:KIAV5
|
1E749351CBE258D2B8710B48FF9D3F4034244C7CAEA25C93FA4A2E04E2F4CB59
|
||||||
53555A554B490000:2:Suzuki
|
AEEA5813C69800830540B6768FCD0EF35A43FA58F6A8315AE06545A6103112D1
|
||||||
53696C7669618C14:5:FAAC_SLH
|
5A773FDCBABB5C9ADAD19B356D0300D5A5E905E6FEDED53D209A9BE2853C5BD8
|
||||||
54365CB7676284F9:1:Alligator_S-275
|
F7C5AA9B29EB56D16610B560F65E99E78DCFE9463E6347E9477F387AC5756B05
|
||||||
544F594F54410000:2:Toyota
|
59AF0DCF53E422B553B250F9131E365D9265ACA04C1763CEA379459D8E2D2026E4C6523DBEC8CFA9D2C287C7AB6F8719
|
||||||
54524D534543494E:1:NICE_Smilo
|
CC11C8105B3643EAF8F935EE3E5408181CD7A41011EDF11F37D8BE45A70CB5A2
|
||||||
5504045708301203:1:SL_A6-A9/Tomahawk_9010
|
0B71155A1BFE1A61D1C6122EC9961CF97BF34C188AEEA1FCBDA8A56631180926
|
||||||
572768229476CAFF:2:Motorline
|
C4EA8825FB1030BDFCAD7A991F3CC7D65CA4300E1ABA4C53EB7287CF33031286E699217258B9CECCF99412915C50A579
|
||||||
638664A880AA23FC:11:KIAV6A
|
A38F7B2D60ECD413609AF9A02924D50591FB07C2EF95B8512569E48D613855F0
|
||||||
6408076407018725:1:Tomahawk_TZ-9030
|
6EBF32A7D2070E13A7EB01CB3CB3C62A2173DD56AC3627320B54E3D3211BCBC2
|
||||||
66B446B980AC752B:1:Cenmax_St-7
|
CCD92DEBB91582B72DC921F8675002399299C243C6AE9BADFE3EEB1BCC79512C3FEA1A83393F795AC6E0546B3FFAE364
|
||||||
67732C5056334627:1:Pantera_XS/Jaguar
|
491AD804382AA360C866082778A8ABA358FF9586A955BD5A9FD28FDF0B05744B
|
||||||
685B9D8C5A130221:2:VAG_HELLA_VPM
|
9D6D8647BC32B0A4EE08F2D361B13037BA596402E6BEB4ED9D3D582F5552C89B9D96CD6836DF15E55251ED39E5D0E8C4
|
||||||
68732C5056334728:1:APS-1100_APS-2550
|
BE92434BC3F646A192146500D150462E8FDFADB93E0DC8C1BE1B2F9E4B3A762B
|
||||||
6912FA557DC2418A:0:Reff
|
7FA5E6D202BE57AA4DAFE94090FA8F84D483C3A6DEFD7EDF69D0F9972F55212EEBA83C847A8374059280AEDB24BA11BC
|
||||||
6B8E6CA088A22BF4:12:KIAV6B
|
B6CDA34C4E8DFE3E509A22DC89B67E9706A980198D3FC522362B59647D79DBEA
|
||||||
6D69736572657265:2:BFT_Miserere
|
BEED72A8B1DFBABF59D58D8EA98385BF706BDF403E3F84702D0BF5D87E0FAEBC
|
||||||
6EB6AE4815C63ED2:9:Beninca_ARC
|
B049623264E92A557F1F6B04546D0D73889BF732ACE3109583CD52E226D8C2BC
|
||||||
6F5E4D3B2AABCDEF:1:Tomahawk_Z,X_3-5
|
91881B87503555F8A82E3E4314C1276F636BD5F2BB451FF1E131CB8CD0E089BF
|
||||||
7BCBEED4376EDCBF:2:Sommer
|
AEA6163792A3B2D587F9270752F9D744849621E8A4DF9B5B82DFF06297DA828D4A95DD8E71267E560BFEB6C2EDB06F2A
|
||||||
7EAF1E9A392B19B9:1:Pandora_PRO2
|
98CEA896C163DBE5C9839CAE6E09DF46EE9AEC8A8978C5B018DB80E1081E01A3
|
||||||
8455F43584941223:1:DoorHan
|
27759BD9760153C94F982ADD85DA3F7FDD51EF17B3791968417ACDEBEBE59F9C
|
||||||
8607427861394E30:2:EcoStar
|
716574AB5E60E65600B86A66F6C8A6536FB97792D5B9A5EDE477D5B623F118B1
|
||||||
8765432187654321:2:Sommer_FM_868
|
740FD62AFEF9D781976A5C01D8041B00A0D69DE56F1FAE030CC680D0D81793CB
|
||||||
89146E59537903B7:1:JCM_Tech
|
9BFF7A62569BA492B586CA27B2A87F96861F5564696AD3F779A58C09955A6342
|
||||||
8A326438B62287F5:0:Faraon
|
9BBF2793C0326BD03BA66A59BE65977C68225E26A55AAA6B56AC4F1D21B795DB
|
||||||
8BC9E46704700800:1:Vag
|
B8D4A1B5134B09C81B586A8ADDFA6292DE50CA84D0FAA9448D610C68129FD9CD
|
||||||
96638C36C99C2C9B:1:Came_Space
|
46827F0D09BEBFF241B4615EF4F9E5ADDB559EB87F4FC15A2AC58816969E186A
|
||||||
9804655AA5000000:8:Magic_4
|
B090E1924CEF93ADC559E876CC71D174A43FE7ECB0FA2A7D748BD51E9B4D9780
|
||||||
9BF7F89BF8FE78DA:1:SL_A2-A4
|
8E8BFD7D356C101EB4998068767D9C259BE6C86A3BCE682C7BC05A8E6B32E106
|
||||||
9C61FD3A47B8E25C:2:Elmes_Poland
|
8E57374694C5EB4A928B5BC25AD17ED2A0FF9563ADFEE22DA5C5CB15896C8C08
|
||||||
9DA08153CF312BA7:1:Normstahl
|
F4DF5C45823C2F3C590D6D962D0B46CB7442CFE1CE9930159E03C6D1B99A728E
|
||||||
A8F5DFFC8DAA5CDB:10:KIA
|
72B3D5150BD2BFF2411DC4C85673D29B22649FA2F7CD4309C2C3BEB834D44CF8
|
||||||
A9748532B7655297:2:GSN
|
51E14868A82570D2736DB6BDA6ADF110ABABC3982A1CD3F3107C9A4774DA2C49
|
||||||
AA38A7A32189B5A1:0:Mutanco_Mutancode
|
7AFC1EA6CFAD93CBD16D7C6783E425378405F4E45A1F4757C5703513B693B069
|
||||||
AAFBFBA8F7CFEDFC:1:Cenmax_St-5
|
944DD7315336FF24EC1D08211FF22DE40669C2D3F5D1F8C6907E6E0DD0F3024B9C536D32C4D4D1B05C0DA2AF139156BC
|
||||||
AC35BB2759000000:8:Magic_3
|
F78DB3774E964AF6EA61A0E6CB6FA311A63777DD6C82E83CF1508A4845AD995A
|
||||||
AD3C12A17028F11E:1:Partisan_RX
|
ADE6563483B98ABBA28FCC0AC6A6D046EBA57C28C9274938E47D07F78B3256B9
|
||||||
B3E5625A8CCD7139:2:Steelmate
|
717C322142EAD4B0160D208CAB0EC6062A79D4CE917C805757A8812E7E1BE2B4
|
||||||
B51526E8F126D1D7:0:Teco
|
C6647C1643449A8E0A99F42AF0467376318FECE96EB11297EA6EC979CD50335B
|
||||||
B51A7AAB27351596:0:ZX-730-750-1055
|
CAFDBBCA3A8DCE026E6C131435656FA5B2FE2AB7340D227D37C7318C498B3F0C
|
||||||
BBEE9EDDAAF97ECC:1:Jolly_Motors
|
F546BFA436AC20207F604E7634B6B30BBA4AA8047E7E72041FF023E9BFCA8D48219740033478EE52FC2CA9ED69F6B1AF
|
||||||
CEB6AE48B5C63ED2:4:Beninca
|
E54DE969B061414A725483E3455923112462408DD43D221E7651F245DD9AECAA
|
||||||
D5A5E7B2A7C1A0BA:2:Mongoose
|
5E8A5BB037F8F6FF326190B9A3E6D5D0ED268D2F6EA77DAAE3E6FC2FC60AE46E
|
||||||
E5D2C83529C113E6:2:VAG_HELLA_PWM
|
5E4457C2A8CD8A9EC4EB16F674775D2F27AD91E7D0ED4A58E42F0C8FB2195F52
|
||||||
EEF3D4B2626C5578:1:Alligator
|
E802E4FD33685950082F2CC510B5BD5D394FAADF7B88107F8C6BD1B30EFA5951
|
||||||
F1A3E552C8647E55:2:Comunello
|
A8C339432E43C41206417FE5C5341B6063D011EC682914247939000741A4325E
|
||||||
F250006EF29E030E:2:Vauweh
|
97A26AAA3AA268F2271563B10173228C9CE67DE0872E7D6088B40D2B43599AA0
|
||||||
F6E5D4B32ABADCFE:1:SL_B6,B9_dop
|
452F34AA2ACB42D23BF801CBC2B9956C3DFED71B34672BA0D09D20CBC5B6AE5E
|
||||||
FAAC05600001FAAC:2:FAAC_RC,XT
|
AD473E39707EFECEC634A84099CF8BB05B9307390EAABFDF54901936E265F9EC
|
||||||
FAAC05600002FAAC:2:Genius_Bravo
|
E946D1047A519101DB2B4FA48939049E869D43E54AD34A2E314E67BA86D4D577
|
||||||
FADA12FADA34F0DA:1:Rossi
|
7FCE70BF03798364ECD144909FB344FFC3C66A34AE5CBC230D970423A5412ED0
|
||||||
FC4DE22CD5370320:1:DTM_Neo
|
0BF57416D2600E332F14737333A7FB7D8327B96B98F217B620B3080F61F2DFEB
|
||||||
FEDCBA9876543210:2:Came_Tam
|
8D77033A47064B6D304C29E61FD3497CECE0CB0BE1305A0A5C418479FA142CA7
|
||||||
|
ACDA2BB920997BF6BAA1BFFD1300BCDCE0117C81DBFAA986A2DD9820287D853E
|
||||||
|
|||||||
@@ -30,4 +30,7 @@ ADD_SCENE(subghz, protocol_list, ProtocolList)
|
|||||||
ADD_SCENE(subghz, keeloq_keys, KeeloqKeys)
|
ADD_SCENE(subghz, keeloq_keys, KeeloqKeys)
|
||||||
ADD_SCENE(subghz, keeloq_key_edit, KeeloqKeyEdit)
|
ADD_SCENE(subghz, keeloq_key_edit, KeeloqKeyEdit)
|
||||||
ADD_SCENE(subghz, psa_decrypt, PsaDecrypt)
|
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)
|
ADD_SCENE(subghz, counter_bf, CounterBf)
|
||||||
|
|||||||
@@ -5,9 +5,10 @@
|
|||||||
#define TAG "SubGhzCounterBf"
|
#define TAG "SubGhzCounterBf"
|
||||||
|
|
||||||
// How many ticks to wait between transmissions (1 tick ~100ms)
|
// 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 {
|
typedef enum {
|
||||||
|
CounterBfStateWarning,
|
||||||
CounterBfStateIdle,
|
CounterBfStateIdle,
|
||||||
CounterBfStateRunning,
|
CounterBfStateRunning,
|
||||||
CounterBfStateStopped,
|
CounterBfStateStopped,
|
||||||
@@ -22,8 +23,16 @@ typedef struct {
|
|||||||
uint32_t tick_wait;
|
uint32_t tick_wait;
|
||||||
} CounterBfContext;
|
} CounterBfContext;
|
||||||
|
|
||||||
#define CounterBfEventStart (0xC0)
|
#define CounterBfEventStart (0xC0)
|
||||||
#define CounterBfEventStop (0xC1)
|
#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) {
|
static void counter_bf_widget_callback(GuiButtonType result, InputType type, void* context) {
|
||||||
SubGhz* subghz = 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) {
|
static void counter_bf_draw(SubGhz* subghz, CounterBfContext* ctx) {
|
||||||
widget_reset(subghz->widget);
|
widget_reset(subghz->widget);
|
||||||
FuriString* str = furi_string_alloc();
|
FuriString* str = furi_string_alloc();
|
||||||
furi_string_printf(
|
furi_string_printf(
|
||||||
str,
|
str,
|
||||||
"Counter BruteForce\n"
|
"Counter BruteForce\n"
|
||||||
"Cnt: 0x%08lX\n"
|
"Cnt: 0x%06lX\n"
|
||||||
"Sent: %lu pkts\n"
|
"Start: 0x%06lX\n"
|
||||||
"Start: 0x%08lX",
|
"Sent: %lu",
|
||||||
ctx->current_cnt,
|
ctx->current_cnt & 0xFFFFFF,
|
||||||
ctx->packets_sent,
|
ctx->start_cnt & 0xFFFFFF,
|
||||||
ctx->start_cnt);
|
ctx->packets_sent);
|
||||||
widget_add_string_multiline_element(
|
widget_add_string_multiline_element(
|
||||||
subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(str));
|
subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(str));
|
||||||
furi_string_free(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) {
|
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);
|
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||||
FlipperFormat* file_fff = flipper_format_buffered_file_alloc(storage);
|
FlipperFormat* file_fff = flipper_format_buffered_file_alloc(storage);
|
||||||
if(flipper_format_buffered_file_open_existing(
|
if(flipper_format_buffered_file_open_existing(
|
||||||
file_fff, furi_string_get_cstr(subghz->file_path))) {
|
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");
|
FURI_LOG_E(TAG, "Failed to update Cnt in .sub file");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -77,16 +102,15 @@ static void counter_bf_save(SubGhz* subghz, CounterBfContext* ctx) {
|
|||||||
static void counter_bf_send(SubGhz* subghz, CounterBfContext* ctx) {
|
static void counter_bf_send(SubGhz* subghz, CounterBfContext* ctx) {
|
||||||
subghz_txrx_stop(subghz->txrx);
|
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;
|
uint32_t repeat = 20;
|
||||||
flipper_format_rewind(fff);
|
flipper_format_rewind(fff);
|
||||||
flipper_format_update_uint32(fff, "Repeat", &repeat, 1);
|
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);
|
subghz_tx_start(subghz, fff);
|
||||||
|
|
||||||
ctx->packets_sent++;
|
ctx->packets_sent++;
|
||||||
@@ -98,42 +122,38 @@ void subghz_scene_counter_bf_on_enter(void* context) {
|
|||||||
|
|
||||||
CounterBfContext* ctx = malloc(sizeof(CounterBfContext));
|
CounterBfContext* ctx = malloc(sizeof(CounterBfContext));
|
||||||
memset(ctx, 0, sizeof(CounterBfContext));
|
memset(ctx, 0, sizeof(CounterBfContext));
|
||||||
ctx->state = CounterBfStateIdle;
|
ctx->state = CounterBfStateWarning;
|
||||||
ctx->step = 1;
|
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* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||||
FlipperFormat* file_fff = flipper_format_buffered_file_alloc(storage);
|
flipper_format_rewind(fff);
|
||||||
if(flipper_format_buffered_file_open_existing(
|
uint32_t cnt = 0;
|
||||||
file_fff, furi_string_get_cstr(subghz->file_path))) {
|
if(flipper_format_read_uint32(fff, "Cnt", &cnt, 1)) {
|
||||||
uint32_t cnt = 0;
|
ctx->current_cnt = cnt & 0xFFFFFF;
|
||||||
if(flipper_format_read_uint32(file_fff, "Cnt", &cnt, 1)) {
|
ctx->start_cnt = cnt & 0xFFFFFF;
|
||||||
ctx->current_cnt = cnt;
|
|
||||||
ctx->start_cnt = cnt;
|
|
||||||
} else {
|
|
||||||
FURI_LOG_W(TAG, "Cnt field not found in file");
|
|
||||||
}
|
|
||||||
} else {
|
} 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(
|
scene_manager_set_scene_state(
|
||||||
subghz->scene_manager, SubGhzSceneCounterBf, (uint32_t)(uintptr_t)ctx);
|
subghz->scene_manager, SubGhzSceneCounterBf, (uint32_t)(uintptr_t)ctx);
|
||||||
|
|
||||||
// Deshabilitar auto-increment del protocolo para controlar el Cnt manualmente
|
counter_bf_draw_warning(subghz);
|
||||||
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);
|
|
||||||
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdWidget);
|
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(!ctx) return false;
|
||||||
|
|
||||||
if(event.type == SceneManagerEventTypeCustom) {
|
if(event.type == SceneManagerEventTypeCustom) {
|
||||||
|
if(event.event == CounterBfEventWarningOk) {
|
||||||
|
ctx->state = CounterBfStateIdle;
|
||||||
|
counter_bf_draw(subghz, ctx);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if(event.event == CounterBfEventStart) {
|
if(event.event == CounterBfEventStart) {
|
||||||
|
if(ctx->state == CounterBfStateWarning) return true;
|
||||||
|
|
||||||
if(ctx->state != CounterBfStateRunning) {
|
if(ctx->state != CounterBfStateRunning) {
|
||||||
ctx->state = CounterBfStateRunning;
|
ctx->state = CounterBfStateRunning;
|
||||||
ctx->tick_wait = 0;
|
ctx->tick_wait = 0;
|
||||||
subghz->state_notifications = SubGhzNotificationStateTx;
|
subghz->state_notifications = SubGhzNotificationStateTx;
|
||||||
counter_bf_send(subghz, ctx);
|
counter_bf_send(subghz, ctx);
|
||||||
} else {
|
} 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;
|
ctx->state = CounterBfStateStopped;
|
||||||
subghz_txrx_stop(subghz->txrx);
|
subghz_txrx_stop(subghz->txrx);
|
||||||
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
||||||
@@ -167,19 +193,24 @@ bool subghz_scene_counter_bf_on_event(void* context, SceneManagerEvent event) {
|
|||||||
if(ctx->tick_wait > 0) {
|
if(ctx->tick_wait > 0) {
|
||||||
ctx->tick_wait--;
|
ctx->tick_wait--;
|
||||||
} else {
|
} else {
|
||||||
ctx->current_cnt += ctx->step;
|
ctx->current_cnt = (ctx->current_cnt + ctx->step) & 0xFFFFFF;
|
||||||
counter_bf_send(subghz, ctx);
|
counter_bf_send(subghz, ctx);
|
||||||
|
counter_bf_save(subghz, ctx);
|
||||||
counter_bf_draw(subghz, ctx);
|
counter_bf_draw(subghz, ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else if(event.type == SceneManagerEventTypeBack) {
|
} 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_txrx_stop(subghz->txrx);
|
||||||
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
||||||
|
|
||||||
// FIX 2 (también en Back): guardar siempre al salir
|
|
||||||
counter_bf_save(subghz, ctx);
|
counter_bf_save(subghz, ctx);
|
||||||
|
|
||||||
furi_hal_subghz_set_rolling_counter_mult(1);
|
furi_hal_subghz_set_rolling_counter_mult(1);
|
||||||
free(ctx);
|
free(ctx);
|
||||||
scene_manager_previous_scene(subghz->scene_manager);
|
scene_manager_previous_scene(subghz->scene_manager);
|
||||||
|
|||||||
253
applications/main/subghz/scenes/subghz_scene_keeloq_bf2.c
Normal 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);
|
||||||
|
}
|
||||||
284
applications/main/subghz/scenes/subghz_scene_keeloq_decrypt.c
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
141
applications/main/subghz/scenes/subghz_scene_kl_bf_cleanup.c
Normal 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);
|
||||||
|
}
|
||||||
@@ -45,25 +45,24 @@ bool subghz_scene_need_saving_on_event(void* context, SceneManagerEvent event) {
|
|||||||
subghz_rx_key_state_set(subghz, SubGhzRxKeyStateBack);
|
subghz_rx_key_state_set(subghz, SubGhzRxKeyStateBack);
|
||||||
scene_manager_previous_scene(subghz->scene_manager);
|
scene_manager_previous_scene(subghz->scene_manager);
|
||||||
return true;
|
return true;
|
||||||
} else if(event.event == SubGhzCustomEventSceneExit) {
|
} else if(event.event == SubGhzCustomEventSceneExit) {
|
||||||
SubGhzRxKeyState state = subghz_rx_key_state_get(subghz);
|
SubGhzRxKeyState state = subghz_rx_key_state_get(subghz);
|
||||||
subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE);
|
subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE);
|
||||||
|
|
||||||
if(state == SubGhzRxKeyStateExit) {
|
if(state == SubGhzRxKeyStateExit) {
|
||||||
if(scene_manager_has_previous_scene(subghz->scene_manager, SubGhzSceneReadRAW)) {
|
if(scene_manager_has_previous_scene(subghz->scene_manager, SubGhzSceneReadRAW)) {
|
||||||
if(!furi_string_empty(subghz->file_path_tmp)) {
|
if(!furi_string_empty(subghz->file_path_tmp)) {
|
||||||
subghz_delete_file(subghz);
|
subghz_delete_file(subghz);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
subghz_txrx_set_preset(
|
subghz_txrx_set_preset(
|
||||||
subghz->txrx, "AM650", subghz->last_settings->frequency, NULL, 0);
|
subghz->txrx, "AM650", subghz->last_settings->frequency, NULL, 0);
|
||||||
scene_manager_search_and_switch_to_previous_scene(
|
if(!scene_manager_search_and_switch_to_previous_scene(
|
||||||
subghz->scene_manager, SubGhzSceneStart);
|
subghz->scene_manager, SubGhzSceneStart)) {
|
||||||
|
scene_manager_previous_scene(subghz->scene_manager);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
scene_manager_previous_scene(subghz->scene_manager);
|
scene_manager_previous_scene(subghz->scene_manager);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
enum SubmenuIndex {
|
enum SubmenuIndex {
|
||||||
SubmenuIndexEmulate,
|
SubmenuIndexEmulate,
|
||||||
|
SubmenuIndexPsaDecrypt,
|
||||||
SubmenuIndexEdit,
|
SubmenuIndexEdit,
|
||||||
SubmenuIndexDelete,
|
SubmenuIndexDelete,
|
||||||
SubmenuIndexSignalSettings,
|
SubmenuIndexSignalSettings,
|
||||||
SubmenuIndexPsaDecrypt,
|
|
||||||
SubmenuIndexCounterBf
|
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) {
|
void subghz_scene_saved_menu_on_enter(void* context) {
|
||||||
SubGhz* subghz = context;
|
SubGhz* subghz = context;
|
||||||
|
|
||||||
// Check protocol type for conditional menu items
|
|
||||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||||
bool is_psa_encrypted = false;
|
bool is_psa_encrypted = false;
|
||||||
bool has_counter = false;
|
bool has_counter = false;
|
||||||
@@ -26,7 +25,6 @@ void subghz_scene_saved_menu_on_enter(void* context) {
|
|||||||
flipper_format_rewind(fff);
|
flipper_format_rewind(fff);
|
||||||
if(flipper_format_read_string(fff, "Protocol", proto)) {
|
if(flipper_format_read_string(fff, "Protocol", proto)) {
|
||||||
if(furi_string_equal_str(proto, "PSA GROUP")) {
|
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();
|
FuriString* type_str = furi_string_alloc();
|
||||||
flipper_format_rewind(fff);
|
flipper_format_rewind(fff);
|
||||||
if(!flipper_format_read_string(fff, "Type", type_str) ||
|
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);
|
furi_string_free(proto);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if protocol has a Cnt field (supports counter bruteforce)
|
|
||||||
if(fff) {
|
if(fff) {
|
||||||
uint32_t cnt_tmp = 0;
|
uint32_t cnt_tmp = 0;
|
||||||
flipper_format_rewind(fff);
|
flipper_format_rewind(fff);
|
||||||
@@ -48,12 +45,23 @@ void subghz_scene_saved_menu_on_enter(void* context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
submenu_add_item(
|
if(!is_psa_encrypted) {
|
||||||
subghz->submenu,
|
submenu_add_item(
|
||||||
"Emulate",
|
subghz->submenu,
|
||||||
SubmenuIndexEmulate,
|
"Emulate",
|
||||||
subghz_scene_saved_menu_submenu_callback,
|
SubmenuIndexEmulate,
|
||||||
subghz);
|
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(
|
submenu_add_item(
|
||||||
subghz->submenu,
|
subghz->submenu,
|
||||||
@@ -76,15 +84,8 @@ void subghz_scene_saved_menu_on_enter(void* context) {
|
|||||||
SubmenuIndexSignalSettings,
|
SubmenuIndexSignalSettings,
|
||||||
subghz_scene_saved_menu_submenu_callback,
|
subghz_scene_saved_menu_submenu_callback,
|
||||||
subghz);
|
subghz);
|
||||||
};
|
|
||||||
if(is_psa_encrypted) {
|
|
||||||
submenu_add_item(
|
|
||||||
subghz->submenu,
|
|
||||||
"PSA Decrypt",
|
|
||||||
SubmenuIndexPsaDecrypt,
|
|
||||||
subghz_scene_saved_menu_submenu_callback,
|
|
||||||
subghz);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(has_counter) {
|
if(has_counter) {
|
||||||
submenu_add_item(
|
submenu_add_item(
|
||||||
subghz->submenu,
|
subghz->submenu,
|
||||||
@@ -110,6 +111,11 @@ bool subghz_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
|
|||||||
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexEmulate);
|
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexEmulate);
|
||||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTransmitter);
|
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTransmitter);
|
||||||
return true;
|
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) {
|
} else if(event.event == SubmenuIndexDelete) {
|
||||||
scene_manager_set_scene_state(
|
scene_manager_set_scene_state(
|
||||||
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexDelete);
|
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);
|
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexSignalSettings);
|
||||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSignalSettings);
|
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSignalSettings);
|
||||||
return true;
|
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) {
|
} else if(event.event == SubmenuIndexCounterBf) {
|
||||||
scene_manager_set_scene_state(
|
scene_manager_set_scene_state(
|
||||||
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexCounterBf);
|
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexCounterBf);
|
||||||
|
|||||||
@@ -55,6 +55,12 @@ void subghz_scene_start_on_enter(void* context) {
|
|||||||
SubmenuIndexKeeloqKeys,
|
SubmenuIndexKeeloqKeys,
|
||||||
subghz_scene_start_submenu_callback,
|
subghz_scene_start_submenu_callback,
|
||||||
subghz);
|
subghz);
|
||||||
|
submenu_add_item(
|
||||||
|
subghz->submenu,
|
||||||
|
"KeeLoq BF (2 Signals)",
|
||||||
|
SubmenuIndexKeeloqBf2,
|
||||||
|
subghz_scene_start_submenu_callback,
|
||||||
|
subghz);
|
||||||
submenu_set_selected_item(
|
submenu_set_selected_item(
|
||||||
subghz->submenu, scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneStart));
|
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);
|
subghz->scene_manager, SubGhzSceneStart, SubmenuIndexKeeloqKeys);
|
||||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneKeeloqKeys);
|
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneKeeloqKeys);
|
||||||
return true;
|
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;
|
return false;
|
||||||
|
|||||||
@@ -10,4 +10,5 @@ enum SubmenuIndex {
|
|||||||
SubmenuIndexProtocolList,
|
SubmenuIndexProtocolList,
|
||||||
SubmenuIndexRadioSetting,
|
SubmenuIndexRadioSetting,
|
||||||
SubmenuIndexKeeloqKeys,
|
SubmenuIndexKeeloqKeys,
|
||||||
|
SubmenuIndexKeeloqBf2,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -95,6 +95,11 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) {
|
|||||||
|
|
||||||
subghz->keeloq_keys_manager = NULL;
|
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 = furi_string_alloc();
|
||||||
subghz->file_path_tmp = furi_string_alloc();
|
subghz->file_path_tmp = furi_string_alloc();
|
||||||
|
|
||||||
@@ -195,6 +200,12 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) {
|
|||||||
SubGhzViewIdPsaDecrypt,
|
SubGhzViewIdPsaDecrypt,
|
||||||
subghz_view_psa_decrypt_get_view(subghz->subghz_psa_decrypt));
|
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
|
//init threshold rssi
|
||||||
subghz->threshold_rssi = subghz_threshold_rssi_alloc();
|
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);
|
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdPsaDecrypt);
|
||||||
subghz_view_psa_decrypt_free(subghz->subghz_psa_decrypt);
|
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
|
// Read RAW
|
||||||
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdReadRAW);
|
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdReadRAW);
|
||||||
subghz_read_raw_free(subghz->subghz_read_raw);
|
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);
|
||||||
furi_string_free(subghz->file_path_tmp);
|
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) {
|
if(subghz->keeloq_keys_manager) {
|
||||||
subghz_keeloq_keys_free(subghz->keeloq_keys_manager);
|
subghz_keeloq_keys_free(subghz->keeloq_keys_manager);
|
||||||
subghz->keeloq_keys_manager = NULL;
|
subghz->keeloq_keys_manager = NULL;
|
||||||
@@ -386,6 +403,7 @@ int32_t subghz_app(void* p) {
|
|||||||
subghz->view_dispatcher, subghz->gui, ViewDispatcherTypeFullscreen);
|
subghz->view_dispatcher, subghz->gui, ViewDispatcherTypeFullscreen);
|
||||||
furi_string_set(subghz->file_path, SUBGHZ_APP_FOLDER);
|
furi_string_set(subghz->file_path, SUBGHZ_APP_FOLDER);
|
||||||
if(subghz_txrx_is_database_loaded(subghz->txrx)) {
|
if(subghz_txrx_is_database_loaded(subghz->txrx)) {
|
||||||
|
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneStart);
|
||||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiver);
|
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiver);
|
||||||
} else {
|
} else {
|
||||||
scene_manager_set_scene_state(
|
scene_manager_set_scene_state(
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "views/subghz_frequency_analyzer.h"
|
#include "views/subghz_frequency_analyzer.h"
|
||||||
#include "views/subghz_read_raw.h"
|
#include "views/subghz_read_raw.h"
|
||||||
#include "views/subghz_psa_decrypt.h"
|
#include "views/subghz_psa_decrypt.h"
|
||||||
|
#include "views/subghz_keeloq_decrypt.h"
|
||||||
|
|
||||||
#include <gui/gui.h>
|
#include <gui/gui.h>
|
||||||
#include <assets_icons.h>
|
#include <assets_icons.h>
|
||||||
@@ -74,6 +75,7 @@ struct SubGhz {
|
|||||||
SubGhzFrequencyAnalyzer* subghz_frequency_analyzer;
|
SubGhzFrequencyAnalyzer* subghz_frequency_analyzer;
|
||||||
SubGhzReadRAW* subghz_read_raw;
|
SubGhzReadRAW* subghz_read_raw;
|
||||||
SubGhzViewPsaDecrypt* subghz_psa_decrypt;
|
SubGhzViewPsaDecrypt* subghz_psa_decrypt;
|
||||||
|
SubGhzViewKeeloqDecrypt* subghz_keeloq_decrypt;
|
||||||
bool raw_send_only;
|
bool raw_send_only;
|
||||||
|
|
||||||
bool save_datetime_set;
|
bool save_datetime_set;
|
||||||
@@ -102,13 +104,25 @@ struct SubGhz {
|
|||||||
// KeeLoq key management
|
// KeeLoq key management
|
||||||
SubGhzKeeloqKeysManager* keeloq_keys_manager;
|
SubGhzKeeloqKeysManager* keeloq_keys_manager;
|
||||||
struct {
|
struct {
|
||||||
uint8_t key_bytes[8]; // ByteInput result
|
uint8_t key_bytes[8];
|
||||||
char name[65]; // TextInput result
|
char name[65];
|
||||||
uint16_t type; // selected learning type 1..8
|
uint16_t type;
|
||||||
bool is_new; // true = add, false = edit
|
bool is_new;
|
||||||
size_t edit_index; // valid when is_new == false
|
size_t edit_index;
|
||||||
uint8_t edit_step; // 0 = key, 1 = name, 2 = type
|
uint8_t edit_step;
|
||||||
} keeloq_edit;
|
} 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);
|
void subghz_blink_start(SubGhz* subghz);
|
||||||
|
|||||||
246
applications/main/subghz/views/subghz_keeloq_decrypt.c
Normal 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);
|
||||||
|
}
|
||||||
37
applications/main/subghz/views/subghz_keeloq_decrypt.h
Normal 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);
|
||||||
@@ -50,8 +50,10 @@ static void subghz_view_psa_decrypt_draw(Canvas* canvas, void* _model) {
|
|||||||
// Progress bar outline + fill
|
// Progress bar outline + fill
|
||||||
canvas_draw_rframe(canvas, 3, 15, 122, 12, 2);
|
canvas_draw_rframe(canvas, 3, 15, 122, 12, 2);
|
||||||
uint8_t fill = (uint8_t)((uint16_t)model->progress * 116 / 100);
|
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);
|
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);
|
canvas_set_font(canvas, FontSecondary);
|
||||||
@@ -90,19 +92,23 @@ static void subghz_view_psa_decrypt_draw(Canvas* canvas, void* _model) {
|
|||||||
// Cancel hint - bottom right
|
// Cancel hint - bottom right
|
||||||
canvas_draw_str_aligned(canvas, 126, 64, AlignRight, AlignBottom, "Hold BACK");
|
canvas_draw_str_aligned(canvas, 126, 64, AlignRight, AlignBottom, "Hold BACK");
|
||||||
} else {
|
} else {
|
||||||
// Result screen
|
canvas_set_font(canvas, FontPrimary);
|
||||||
canvas_set_font(canvas, FontSecondary);
|
canvas_draw_str_aligned(canvas, 64, 4, AlignCenter, AlignTop, "Decrypted!");
|
||||||
if(model->result_str) {
|
|
||||||
elements_multiline_text_aligned(
|
if(model->result_str) {
|
||||||
canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(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) {
|
static bool subghz_view_psa_decrypt_input(InputEvent* event, void* context) {
|
||||||
SubGhzViewPsaDecrypt* instance = (SubGhzViewPsaDecrypt*)context;
|
SubGhzViewPsaDecrypt* instance = (SubGhzViewPsaDecrypt*)context;
|
||||||
|
|
||||||
if(event->key == InputKeyBack) {
|
if(event->key == InputKeyBack || event->key == InputKeyOk) {
|
||||||
if(instance->callback) {
|
if(instance->callback) {
|
||||||
instance->callback(SubGhzCustomEventViewTransmitterBack, instance->context);
|
instance->callback(SubGhzCustomEventViewTransmitterBack, instance->context);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
Put your custom applications in this folder.
|
|
||||||
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 511 B After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 518 B After Width: | Height: | Size: 968 B |
|
Before Width: | Height: | Size: 483 B After Width: | Height: | Size: 1.2 KiB |
@@ -1,252 +0,0 @@
|
|||||||
#include "aes_common.h"
|
|
||||||
|
|
||||||
static const uint8_t aes_sbox[256] = {
|
|
||||||
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab,
|
|
||||||
0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4,
|
|
||||||
0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71,
|
|
||||||
0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2,
|
|
||||||
0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6,
|
|
||||||
0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb,
|
|
||||||
0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45,
|
|
||||||
0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5,
|
|
||||||
0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44,
|
|
||||||
0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a,
|
|
||||||
0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49,
|
|
||||||
0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d,
|
|
||||||
0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25,
|
|
||||||
0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e,
|
|
||||||
0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1,
|
|
||||||
0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
|
|
||||||
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb,
|
|
||||||
0x16};
|
|
||||||
|
|
||||||
static const uint8_t aes_sbox_inv[256] = {
|
|
||||||
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7,
|
|
||||||
0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde,
|
|
||||||
0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42,
|
|
||||||
0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49,
|
|
||||||
0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c,
|
|
||||||
0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15,
|
|
||||||
0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7,
|
|
||||||
0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02,
|
|
||||||
0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc,
|
|
||||||
0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad,
|
|
||||||
0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d,
|
|
||||||
0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b,
|
|
||||||
0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8,
|
|
||||||
0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51,
|
|
||||||
0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0,
|
|
||||||
0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
|
|
||||||
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c,
|
|
||||||
0x7d};
|
|
||||||
|
|
||||||
static const uint8_t aes_rcon[10] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36};
|
|
||||||
|
|
||||||
static uint8_t gf_mul2(uint8_t x) {
|
|
||||||
return ((x >> 7) * 0x1b) ^ (x << 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void aes_subbytes(uint8_t* state) {
|
|
||||||
for(uint8_t row = 0; row < 4; row++) {
|
|
||||||
for(uint8_t col = 0; col < 4; col++) {
|
|
||||||
state[row + col * 4] = aes_sbox[state[row + col * 4]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void aes_subbytes_inv(uint8_t* state) {
|
|
||||||
for(uint8_t row = 0; row < 4; row++) {
|
|
||||||
for(uint8_t col = 0; col < 4; col++) {
|
|
||||||
state[row + col * 4] = aes_sbox_inv[state[row + col * 4]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void aes_shiftrows(uint8_t* state) {
|
|
||||||
uint8_t temp;
|
|
||||||
|
|
||||||
temp = state[1];
|
|
||||||
state[1] = state[5];
|
|
||||||
state[5] = state[9];
|
|
||||||
state[9] = state[13];
|
|
||||||
state[13] = temp;
|
|
||||||
|
|
||||||
temp = state[2];
|
|
||||||
state[2] = state[10];
|
|
||||||
state[10] = temp;
|
|
||||||
temp = state[6];
|
|
||||||
state[6] = state[14];
|
|
||||||
state[14] = temp;
|
|
||||||
|
|
||||||
temp = state[15];
|
|
||||||
state[15] = state[11];
|
|
||||||
state[11] = state[7];
|
|
||||||
state[7] = state[3];
|
|
||||||
state[3] = temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void aes_shiftrows_inv(uint8_t* state) {
|
|
||||||
uint8_t temp;
|
|
||||||
|
|
||||||
temp = state[13];
|
|
||||||
state[13] = state[9];
|
|
||||||
state[9] = state[5];
|
|
||||||
state[5] = state[1];
|
|
||||||
state[1] = temp;
|
|
||||||
|
|
||||||
temp = state[2];
|
|
||||||
state[2] = state[10];
|
|
||||||
state[10] = temp;
|
|
||||||
temp = state[6];
|
|
||||||
state[6] = state[14];
|
|
||||||
state[14] = temp;
|
|
||||||
|
|
||||||
temp = state[3];
|
|
||||||
state[3] = state[7];
|
|
||||||
state[7] = state[11];
|
|
||||||
state[11] = state[15];
|
|
||||||
state[15] = temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void aes_mixcolumns(uint8_t* state) {
|
|
||||||
uint8_t a, b, c, d;
|
|
||||||
for(uint8_t i = 0; i < 4; i++) {
|
|
||||||
a = state[i * 4];
|
|
||||||
b = state[i * 4 + 1];
|
|
||||||
c = state[i * 4 + 2];
|
|
||||||
d = state[i * 4 + 3];
|
|
||||||
|
|
||||||
uint8_t a2 = gf_mul2(a);
|
|
||||||
uint8_t b2 = gf_mul2(b);
|
|
||||||
uint8_t c2 = gf_mul2(c);
|
|
||||||
uint8_t d2 = gf_mul2(d);
|
|
||||||
|
|
||||||
state[i * 4] = a2 ^ b2 ^ b ^ c ^ d;
|
|
||||||
state[i * 4 + 1] = a ^ b2 ^ c2 ^ c ^ d;
|
|
||||||
state[i * 4 + 2] = a ^ b ^ c2 ^ d2 ^ d;
|
|
||||||
state[i * 4 + 3] = a2 ^ a ^ b ^ c ^ d2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void aes_mixcolumns_inv(uint8_t* state) {
|
|
||||||
uint8_t a, b, c, d;
|
|
||||||
for(uint8_t i = 0; i < 4; i++) {
|
|
||||||
a = state[i * 4];
|
|
||||||
b = state[i * 4 + 1];
|
|
||||||
c = state[i * 4 + 2];
|
|
||||||
d = state[i * 4 + 3];
|
|
||||||
|
|
||||||
uint8_t a2 = gf_mul2(a);
|
|
||||||
uint8_t a4 = gf_mul2(a2);
|
|
||||||
uint8_t a8 = gf_mul2(a4);
|
|
||||||
uint8_t b2 = gf_mul2(b);
|
|
||||||
uint8_t b4 = gf_mul2(b2);
|
|
||||||
uint8_t b8 = gf_mul2(b4);
|
|
||||||
uint8_t c2 = gf_mul2(c);
|
|
||||||
uint8_t c4 = gf_mul2(c2);
|
|
||||||
uint8_t c8 = gf_mul2(c4);
|
|
||||||
uint8_t d2 = gf_mul2(d);
|
|
||||||
uint8_t d4 = gf_mul2(d2);
|
|
||||||
uint8_t d8 = gf_mul2(d4);
|
|
||||||
|
|
||||||
state[i * 4] = (a8 ^ a4 ^ a2) ^ (b8 ^ b2 ^ b) ^ (c8 ^ c4 ^ c) ^ (d8 ^ d);
|
|
||||||
state[i * 4 + 1] = (a8 ^ a) ^ (b8 ^ b4 ^ b2) ^ (c8 ^ c2 ^ c) ^ (d8 ^ d4 ^ d);
|
|
||||||
state[i * 4 + 2] = (a8 ^ a4 ^ a) ^ (b8 ^ b) ^ (c8 ^ c4 ^ c2) ^ (d8 ^ d2 ^ d);
|
|
||||||
state[i * 4 + 3] = (a8 ^ a2 ^ a) ^ (b8 ^ b4 ^ b) ^ (c8 ^ c) ^ (d8 ^ d4 ^ d2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void aes_addroundkey(uint8_t* state, const uint8_t* round_key) {
|
|
||||||
for(uint8_t col = 0; col < 4; col++) {
|
|
||||||
state[col * 4] ^= round_key[col * 4];
|
|
||||||
state[col * 4 + 1] ^= round_key[col * 4 + 1];
|
|
||||||
state[col * 4 + 2] ^= round_key[col * 4 + 2];
|
|
||||||
state[col * 4 + 3] ^= round_key[col * 4 + 3];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void aes_key_expansion(const uint8_t* key, uint8_t* round_keys) {
|
|
||||||
for(uint8_t i = 0; i < 16; i++) {
|
|
||||||
round_keys[i] = key[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
for(uint8_t i = 4; i < 44; i++) {
|
|
||||||
uint8_t prev_word_idx = (i - 1) * 4;
|
|
||||||
uint8_t b0 = round_keys[prev_word_idx];
|
|
||||||
uint8_t b1 = round_keys[prev_word_idx + 1];
|
|
||||||
uint8_t b2 = round_keys[prev_word_idx + 2];
|
|
||||||
uint8_t b3 = round_keys[prev_word_idx + 3];
|
|
||||||
|
|
||||||
if((i % 4) == 0) {
|
|
||||||
uint8_t new_b0 = aes_sbox[b1] ^ aes_rcon[(i / 4) - 1];
|
|
||||||
uint8_t new_b1 = aes_sbox[b2];
|
|
||||||
uint8_t new_b2 = aes_sbox[b3];
|
|
||||||
uint8_t new_b3 = aes_sbox[b0];
|
|
||||||
b0 = new_b0;
|
|
||||||
b1 = new_b1;
|
|
||||||
b2 = new_b2;
|
|
||||||
b3 = new_b3;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t back_word_idx = (i - 4) * 4;
|
|
||||||
b0 ^= round_keys[back_word_idx];
|
|
||||||
b1 ^= round_keys[back_word_idx + 1];
|
|
||||||
b2 ^= round_keys[back_word_idx + 2];
|
|
||||||
b3 ^= round_keys[back_word_idx + 3];
|
|
||||||
|
|
||||||
uint8_t curr_word_idx = i * 4;
|
|
||||||
round_keys[curr_word_idx] = b0;
|
|
||||||
round_keys[curr_word_idx + 1] = b1;
|
|
||||||
round_keys[curr_word_idx + 2] = b2;
|
|
||||||
round_keys[curr_word_idx + 3] = b3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void aes128_encrypt(const uint8_t* expanded_key, uint8_t* data) {
|
|
||||||
uint8_t state[16];
|
|
||||||
memcpy(state, data, 16);
|
|
||||||
|
|
||||||
aes_addroundkey(state, &expanded_key[0]);
|
|
||||||
|
|
||||||
for(uint8_t round = 1; round < 10; round++) {
|
|
||||||
aes_subbytes(state);
|
|
||||||
aes_shiftrows(state);
|
|
||||||
aes_mixcolumns(state);
|
|
||||||
aes_addroundkey(state, &expanded_key[round * 16]);
|
|
||||||
}
|
|
||||||
|
|
||||||
aes_subbytes(state);
|
|
||||||
aes_shiftrows(state);
|
|
||||||
aes_addroundkey(state, &expanded_key[160]);
|
|
||||||
|
|
||||||
memcpy(data, state, 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
void aes128_decrypt(const uint8_t* expanded_key, uint8_t* data) {
|
|
||||||
uint8_t state[16];
|
|
||||||
memcpy(state, data, 16);
|
|
||||||
|
|
||||||
aes_addroundkey(state, &expanded_key[160]);
|
|
||||||
|
|
||||||
for(uint8_t round = 9; round > 0; round--) {
|
|
||||||
aes_shiftrows_inv(state);
|
|
||||||
aes_subbytes_inv(state);
|
|
||||||
aes_addroundkey(state, &expanded_key[round * 16]);
|
|
||||||
aes_mixcolumns_inv(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
aes_shiftrows_inv(state);
|
|
||||||
aes_subbytes_inv(state);
|
|
||||||
aes_addroundkey(state, &expanded_key[0]);
|
|
||||||
|
|
||||||
memcpy(data, state, 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
void reverse_bits_in_bytes(uint8_t* data, uint8_t len) {
|
|
||||||
for(uint8_t i = 0; i < len; i++) {
|
|
||||||
uint8_t byte = data[i];
|
|
||||||
uint8_t step1 = ((byte & 0x55) << 1) | ((byte >> 1) & 0x55);
|
|
||||||
uint8_t step2 = ((step1 & 0x33) << 2) | ((step1 >> 2) & 0x33);
|
|
||||||
data[i] = ((step2 & 0x0F) << 4) | (step2 >> 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "base.h"
|
|
||||||
|
|
||||||
#include <furi.h>
|
|
||||||
|
|
||||||
void reverse_bits_in_bytes(uint8_t* data, uint8_t len);
|
|
||||||
void aes128_decrypt(const uint8_t* expanded_key, uint8_t* data);
|
|
||||||
void aes128_encrypt(const uint8_t* expanded_key, uint8_t* data);
|
|
||||||
void aes_key_expansion(const uint8_t* key, uint8_t* round_keys);
|
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
#include "core/log.h"
|
#include "core/log.h"
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include "aes_common.h"
|
#include <furi_hal_crypto.h>
|
||||||
|
|
||||||
#include "../blocks/custom_btn_i.h"
|
#include "../blocks/custom_btn_i.h"
|
||||||
|
|
||||||
@@ -152,6 +152,15 @@ static void get_subghz_protocol_beninca_arc_aes_key(SubGhzKeystore* keystore, ui
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void reverse_bits_in_bytes(uint8_t* data, uint8_t len) {
|
||||||
|
for(uint8_t i = 0; i < len; i++) {
|
||||||
|
uint8_t byte = data[i];
|
||||||
|
uint8_t step1 = ((byte & 0x55) << 1) | ((byte >> 1) & 0x55);
|
||||||
|
uint8_t step2 = ((step1 & 0x33) << 2) | ((step1 >> 2) & 0x33);
|
||||||
|
data[i] = ((step2 & 0x0F) << 4) | (step2 >> 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static uint64_t
|
static uint64_t
|
||||||
subghz_protocol_beninca_arc_decrypt(SubGhzBlockGeneric* generic, SubGhzKeystore* keystore) {
|
subghz_protocol_beninca_arc_decrypt(SubGhzBlockGeneric* generic, SubGhzKeystore* keystore) {
|
||||||
// Beninca ARC Decoder
|
// Beninca ARC Decoder
|
||||||
@@ -170,10 +179,9 @@ static uint64_t
|
|||||||
uint8_t aes_key[16];
|
uint8_t aes_key[16];
|
||||||
get_subghz_protocol_beninca_arc_aes_key(keystore, aes_key);
|
get_subghz_protocol_beninca_arc_aes_key(keystore, aes_key);
|
||||||
|
|
||||||
uint8_t expanded_key[176];
|
uint8_t decrypted[16];
|
||||||
aes_key_expansion(aes_key, expanded_key);
|
furi_hal_crypto_aes128_ecb_decrypt(aes_key, encrypted_data, decrypted);
|
||||||
|
memcpy(encrypted_data, decrypted, 16);
|
||||||
aes128_decrypt(expanded_key, encrypted_data);
|
|
||||||
|
|
||||||
// Serial number of remote
|
// Serial number of remote
|
||||||
generic->serial = ((uint32_t)encrypted_data[0] << 24) | ((uint32_t)encrypted_data[1] << 16) |
|
generic->serial = ((uint32_t)encrypted_data[0] << 24) | ((uint32_t)encrypted_data[1] << 16) |
|
||||||
@@ -235,10 +243,9 @@ static void subghz_protocol_beninca_arc_encrypt(
|
|||||||
uint8_t aes_key[16];
|
uint8_t aes_key[16];
|
||||||
get_subghz_protocol_beninca_arc_aes_key(keystore, aes_key);
|
get_subghz_protocol_beninca_arc_aes_key(keystore, aes_key);
|
||||||
|
|
||||||
uint8_t expanded_key[176];
|
uint8_t encrypted[16];
|
||||||
aes_key_expansion(aes_key, expanded_key);
|
furi_hal_crypto_aes128_ecb_encrypt(aes_key, plaintext, encrypted);
|
||||||
|
memcpy(plaintext, encrypted, 16);
|
||||||
aes128_encrypt(expanded_key, plaintext);
|
|
||||||
|
|
||||||
reverse_bits_in_bytes(plaintext, 16);
|
reverse_bits_in_bytes(plaintext, 16);
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +1,45 @@
|
|||||||
#include "fiat_marelli.h"
|
#include "fiat_marelli.h"
|
||||||
#include <inttypes.h>
|
|
||||||
|
#include "../blocks/const.h"
|
||||||
|
#include "../blocks/decoder.h"
|
||||||
|
#include "../blocks/encoder.h"
|
||||||
|
#include "../blocks/generic.h"
|
||||||
|
#include "../blocks/math.h"
|
||||||
|
#include "../blocks/custom_btn_i.h"
|
||||||
#include <lib/toolbox/manchester_decoder.h>
|
#include <lib/toolbox/manchester_decoder.h>
|
||||||
|
#include <lib/toolbox/manchester_encoder.h>
|
||||||
|
#include <furi_hal_subghz.h>
|
||||||
|
|
||||||
#define TAG "FiatMarelli"
|
#define TAG "FiatMarelli"
|
||||||
|
|
||||||
// Suspected Magneti Marelli BSI keyfob protocol
|
// Magneti Marelli BSI keyfob protocol (PCF7946)
|
||||||
// Found on: Fiat Panda (and possibly other Fiat/Lancia/Alfa ~2003-2012)
|
// Found on: Fiat Panda, Grande Punto (and possibly other Fiat/Lancia/Alfa ~2003-2012)
|
||||||
//
|
//
|
||||||
// RF: 433.92 MHz, Manchester encoding
|
// RF: 433.92 MHz, Manchester encoding
|
||||||
// te_short ~260us, te_long ~520us
|
// Two timing variants with identical frame structure:
|
||||||
// Preamble: ~191 short-short pairs (alternating 260us HIGH/LOW)
|
// Type A (e.g. Panda): te_short ~260us, te_long ~520us
|
||||||
// Gap: ~3126us LOW
|
// Type B (e.g. Grande Punto): te_short ~100us, te_long ~200us
|
||||||
// Sync: ~2065us HIGH
|
// TE is auto-detected from preamble pulse averaging.
|
||||||
// Data: 88 Manchester bits (often decoded as 104 with 16-bit 0xFFFF preamble residue)
|
|
||||||
// Retransmissions: 7-10 per press
|
|
||||||
//
|
//
|
||||||
// Frame layout (after stripping 16-bit 0xFFFF preamble):
|
// Frame layout (103-104 bits = 13 bytes):
|
||||||
// Bytes 0-3: Fixed ID / Serial (32 bits)
|
// Bytes 0-1: 0xFFFF/0xFFFC preamble residue
|
||||||
// Byte 4: Button (upper nibble) | Type (lower nibble)
|
// Bytes 2-5: Serial (32 bits)
|
||||||
// Buttons: 0x7=Lock, 0xB=Unlock, 0xD=Trunk
|
// Byte 6: [Button:4 | Epoch:4]
|
||||||
// Bytes 5-10: Rolling/encrypted code (48 bits)
|
// Byte 7: [Counter:5 | Scramble:2 | Fixed:1]
|
||||||
#define FIAT_MARELLI_PREAMBLE_MIN 200 // Min preamble pulses (100 pairs)
|
// Bytes 8-12: Encrypted payload (40 bits)
|
||||||
#define FIAT_MARELLI_GAP_MIN 2500 // Gap detection threshold (us)
|
|
||||||
#define FIAT_MARELLI_SYNC_MIN 1500 // Sync pulse minimum (us)
|
#define FIAT_MARELLI_PREAMBLE_PULSE_MIN 50
|
||||||
#define FIAT_MARELLI_SYNC_MAX 2600 // Sync pulse maximum (us)
|
#define FIAT_MARELLI_PREAMBLE_PULSE_MAX 350
|
||||||
#define FIAT_MARELLI_MAX_DATA_BITS 104 // Max data bits to collect (13 bytes)
|
#define FIAT_MARELLI_PREAMBLE_MIN 80
|
||||||
|
#define FIAT_MARELLI_MAX_DATA_BITS 104
|
||||||
|
#define FIAT_MARELLI_MIN_DATA_BITS 80
|
||||||
|
#define FIAT_MARELLI_GAP_TE_MULT 4
|
||||||
|
#define FIAT_MARELLI_SYNC_TE_MIN_MULT 4
|
||||||
|
#define FIAT_MARELLI_SYNC_TE_MAX_MULT 12
|
||||||
|
#define FIAT_MARELLI_RETX_GAP_MIN 5000
|
||||||
|
#define FIAT_MARELLI_RETX_SYNC_MIN 400
|
||||||
|
#define FIAT_MARELLI_RETX_SYNC_MAX 2800
|
||||||
|
#define FIAT_MARELLI_TE_TYPE_AB_BOUNDARY 180
|
||||||
|
|
||||||
static const SubGhzBlockConst subghz_protocol_fiat_marelli_const = {
|
static const SubGhzBlockConst subghz_protocol_fiat_marelli_const = {
|
||||||
.te_short = 260,
|
.te_short = 260,
|
||||||
@@ -40,16 +55,23 @@ struct SubGhzProtocolDecoderFiatMarelli {
|
|||||||
ManchesterState manchester_state;
|
ManchesterState manchester_state;
|
||||||
uint8_t decoder_state;
|
uint8_t decoder_state;
|
||||||
uint16_t preamble_count;
|
uint16_t preamble_count;
|
||||||
uint8_t raw_data[13]; // Up to 104 bits (13 bytes)
|
uint8_t raw_data[13];
|
||||||
uint8_t bit_count;
|
uint8_t bit_count;
|
||||||
uint32_t extra_data; // Bits beyond first 64, right-aligned
|
uint32_t extra_data;
|
||||||
uint32_t te_last;
|
uint32_t te_last;
|
||||||
|
uint32_t te_sum;
|
||||||
|
uint16_t te_count;
|
||||||
|
uint32_t te_detected;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SubGhzProtocolEncoderFiatMarelli {
|
struct SubGhzProtocolEncoderFiatMarelli {
|
||||||
SubGhzProtocolEncoderBase base;
|
SubGhzProtocolEncoderBase base;
|
||||||
SubGhzProtocolBlockEncoder encoder;
|
SubGhzProtocolBlockEncoder encoder;
|
||||||
SubGhzBlockGeneric generic;
|
SubGhzBlockGeneric generic;
|
||||||
|
uint8_t raw_data[13];
|
||||||
|
uint32_t extra_data;
|
||||||
|
uint8_t bit_count;
|
||||||
|
uint32_t te_detected;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
@@ -57,12 +79,9 @@ typedef enum {
|
|||||||
FiatMarelliDecoderStepPreamble = 1,
|
FiatMarelliDecoderStepPreamble = 1,
|
||||||
FiatMarelliDecoderStepSync = 2,
|
FiatMarelliDecoderStepSync = 2,
|
||||||
FiatMarelliDecoderStepData = 3,
|
FiatMarelliDecoderStepData = 3,
|
||||||
|
FiatMarelliDecoderStepRetxSync = 4,
|
||||||
} FiatMarelliDecoderStep;
|
} FiatMarelliDecoderStep;
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// PROTOCOL INTERFACE DEFINITIONS
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
const SubGhzProtocolDecoder subghz_protocol_fiat_marelli_decoder = {
|
const SubGhzProtocolDecoder subghz_protocol_fiat_marelli_decoder = {
|
||||||
.alloc = subghz_protocol_decoder_fiat_marelli_alloc,
|
.alloc = subghz_protocol_decoder_fiat_marelli_alloc,
|
||||||
.free = subghz_protocol_decoder_fiat_marelli_free,
|
.free = subghz_protocol_decoder_fiat_marelli_free,
|
||||||
@@ -86,21 +105,29 @@ const SubGhzProtocol subghz_protocol_fiat_marelli = {
|
|||||||
.name = FIAT_MARELLI_PROTOCOL_NAME,
|
.name = FIAT_MARELLI_PROTOCOL_NAME,
|
||||||
.type = SubGhzProtocolTypeDynamic,
|
.type = SubGhzProtocolTypeDynamic,
|
||||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
|
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
|
||||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
|
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||||
.decoder = &subghz_protocol_fiat_marelli_decoder,
|
.decoder = &subghz_protocol_fiat_marelli_decoder,
|
||||||
.encoder = &subghz_protocol_fiat_marelli_encoder,
|
.encoder = &subghz_protocol_fiat_marelli_encoder,
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// ENCODER STUBS (decode-only protocol)
|
// Encoder
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
|
#define FIAT_MARELLI_ENCODER_UPLOAD_MAX 1500
|
||||||
|
#define FIAT_MARELLI_ENCODER_REPEAT 3
|
||||||
|
#define FIAT_MARELLI_PREAMBLE_PAIRS 100
|
||||||
|
|
||||||
void* subghz_protocol_encoder_fiat_marelli_alloc(SubGhzEnvironment* environment) {
|
void* subghz_protocol_encoder_fiat_marelli_alloc(SubGhzEnvironment* environment) {
|
||||||
UNUSED(environment);
|
UNUSED(environment);
|
||||||
SubGhzProtocolEncoderFiatMarelli* instance = calloc(1, sizeof(SubGhzProtocolEncoderFiatMarelli));
|
SubGhzProtocolEncoderFiatMarelli* instance = calloc(1, sizeof(SubGhzProtocolEncoderFiatMarelli));
|
||||||
furi_check(instance);
|
furi_check(instance);
|
||||||
instance->base.protocol = &subghz_protocol_fiat_marelli;
|
instance->base.protocol = &subghz_protocol_fiat_marelli;
|
||||||
instance->generic.protocol_name = instance->base.protocol->name;
|
instance->generic.protocol_name = instance->base.protocol->name;
|
||||||
|
instance->encoder.repeat = FIAT_MARELLI_ENCODER_REPEAT;
|
||||||
|
instance->encoder.size_upload = FIAT_MARELLI_ENCODER_UPLOAD_MAX;
|
||||||
|
instance->encoder.upload = malloc(FIAT_MARELLI_ENCODER_UPLOAD_MAX * sizeof(LevelDuration));
|
||||||
|
furi_check(instance->encoder.upload);
|
||||||
instance->encoder.is_running = false;
|
instance->encoder.is_running = false;
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
@@ -108,42 +135,95 @@ void* subghz_protocol_encoder_fiat_marelli_alloc(SubGhzEnvironment* environment)
|
|||||||
void subghz_protocol_encoder_fiat_marelli_free(void* context) {
|
void subghz_protocol_encoder_fiat_marelli_free(void* context) {
|
||||||
furi_check(context);
|
furi_check(context);
|
||||||
SubGhzProtocolEncoderFiatMarelli* instance = context;
|
SubGhzProtocolEncoderFiatMarelli* instance = context;
|
||||||
|
free(instance->encoder.upload);
|
||||||
free(instance);
|
free(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
SubGhzProtocolStatus
|
// Manchester encoding from decoder FSM:
|
||||||
subghz_protocol_encoder_fiat_marelli_deserialize(void* context, FlipperFormat* flipper_format) {
|
// From Mid1: bit 1 = LOW_TE + HIGH_TE, bit 0 = LOW_2TE
|
||||||
UNUSED(context);
|
// From Mid0: bit 0 = HIGH_TE + LOW_TE, bit 1 = HIGH_2TE
|
||||||
UNUSED(flipper_format);
|
static bool fiat_marelli_encoder_get_upload(SubGhzProtocolEncoderFiatMarelli* instance) {
|
||||||
return SubGhzProtocolStatusError;
|
uint32_t te = instance->te_detected;
|
||||||
|
if(te == 0) te = subghz_protocol_fiat_marelli_const.te_short;
|
||||||
|
|
||||||
|
uint32_t te_short = te;
|
||||||
|
uint32_t te_long = te * 2;
|
||||||
|
uint32_t gap_duration = te * 12;
|
||||||
|
uint32_t sync_duration = te * 8;
|
||||||
|
|
||||||
|
size_t index = 0;
|
||||||
|
size_t max_upload = FIAT_MARELLI_ENCODER_UPLOAD_MAX;
|
||||||
|
uint8_t data_bits = instance->bit_count;
|
||||||
|
if(data_bits == 0) data_bits = instance->generic.data_count_bit;
|
||||||
|
if(data_bits < FIAT_MARELLI_MIN_DATA_BITS || data_bits > FIAT_MARELLI_MAX_DATA_BITS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(uint8_t i = 0; i < FIAT_MARELLI_PREAMBLE_PAIRS && (index + 1) < max_upload; i++) {
|
||||||
|
instance->encoder.upload[index++] = level_duration_make(true, te_short);
|
||||||
|
if(i < FIAT_MARELLI_PREAMBLE_PAIRS - 1) {
|
||||||
|
instance->encoder.upload[index++] = level_duration_make(false, te_short);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(index < max_upload) {
|
||||||
|
instance->encoder.upload[index++] = level_duration_make(false, te_short + gap_duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(index < max_upload) {
|
||||||
|
instance->encoder.upload[index++] = level_duration_make(true, sync_duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool in_mid1 = true;
|
||||||
|
|
||||||
|
for(uint8_t bit_i = 0; bit_i < data_bits && (index + 1) < max_upload; bit_i++) {
|
||||||
|
uint8_t byte_idx = bit_i / 8;
|
||||||
|
uint8_t bit_pos = 7 - (bit_i % 8);
|
||||||
|
bool data_bit = (instance->raw_data[byte_idx] >> bit_pos) & 1;
|
||||||
|
|
||||||
|
if(in_mid1) {
|
||||||
|
if(data_bit) {
|
||||||
|
instance->encoder.upload[index++] = level_duration_make(false, te_short);
|
||||||
|
instance->encoder.upload[index++] = level_duration_make(true, te_short);
|
||||||
|
} else {
|
||||||
|
instance->encoder.upload[index++] = level_duration_make(false, te_long);
|
||||||
|
in_mid1 = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(data_bit) {
|
||||||
|
instance->encoder.upload[index++] = level_duration_make(true, te_long);
|
||||||
|
in_mid1 = true;
|
||||||
|
} else {
|
||||||
|
instance->encoder.upload[index++] = level_duration_make(true, te_short);
|
||||||
|
instance->encoder.upload[index++] = level_duration_make(false, te_short);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(in_mid1) {
|
||||||
|
if(index < max_upload) {
|
||||||
|
instance->encoder.upload[index++] =
|
||||||
|
level_duration_make(false, te_short + gap_duration * 3);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(index > 0) {
|
||||||
|
instance->encoder.upload[index - 1] =
|
||||||
|
level_duration_make(false, te_short + gap_duration * 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
instance->encoder.size_upload = index;
|
||||||
|
return index > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void subghz_protocol_encoder_fiat_marelli_stop(void* context) {
|
static void fiat_marelli_encoder_rebuild_raw_data(SubGhzProtocolEncoderFiatMarelli* instance) {
|
||||||
furi_check(context);
|
|
||||||
SubGhzProtocolEncoderFiatMarelli* instance = context;
|
|
||||||
instance->encoder.is_running = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
LevelDuration subghz_protocol_encoder_fiat_marelli_yield(void* context) {
|
|
||||||
UNUSED(context);
|
|
||||||
return level_duration_reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// DECODER IMPLEMENTATION
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
// Helper: rebuild raw_data[] from generic.data + extra_data
|
|
||||||
static void fiat_marelli_rebuild_raw_data(SubGhzProtocolDecoderFiatMarelli* instance) {
|
|
||||||
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||||
|
|
||||||
// First 64 bits from generic.data
|
|
||||||
uint64_t key = instance->generic.data;
|
uint64_t key = instance->generic.data;
|
||||||
for(int i = 0; i < 8; i++) {
|
for(int i = 0; i < 8; i++) {
|
||||||
instance->raw_data[i] = (uint8_t)(key >> (56 - i * 8));
|
instance->raw_data[i] = (uint8_t)(key >> (56 - i * 8));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remaining bits from extra_data (right-aligned)
|
|
||||||
uint8_t extra_bits =
|
uint8_t extra_bits =
|
||||||
instance->generic.data_count_bit > 64 ? (instance->generic.data_count_bit - 64) : 0;
|
instance->generic.data_count_bit > 64 ? (instance->generic.data_count_bit - 64) : 0;
|
||||||
for(uint8_t i = 0; i < extra_bits && i < 32; i++) {
|
for(uint8_t i = 0; i < extra_bits && i < 32; i++) {
|
||||||
@@ -157,6 +237,117 @@ static void fiat_marelli_rebuild_raw_data(SubGhzProtocolDecoderFiatMarelli* inst
|
|||||||
instance->bit_count = instance->generic.data_count_bit;
|
instance->bit_count = instance->generic.data_count_bit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SubGhzProtocolStatus
|
||||||
|
subghz_protocol_encoder_fiat_marelli_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||||
|
furi_check(context);
|
||||||
|
SubGhzProtocolEncoderFiatMarelli* instance = context;
|
||||||
|
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||||
|
|
||||||
|
do {
|
||||||
|
ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
|
||||||
|
if(ret != SubGhzProtocolStatusOk) break;
|
||||||
|
|
||||||
|
uint32_t extra = 0;
|
||||||
|
if(flipper_format_read_uint32(flipper_format, "Extra", &extra, 1)) {
|
||||||
|
instance->extra_data = extra;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t te = 0;
|
||||||
|
if(flipper_format_read_uint32(flipper_format, "TE", &te, 1)) {
|
||||||
|
instance->te_detected = te;
|
||||||
|
}
|
||||||
|
|
||||||
|
fiat_marelli_encoder_rebuild_raw_data(instance);
|
||||||
|
|
||||||
|
if(!fiat_marelli_encoder_get_upload(instance)) {
|
||||||
|
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance->encoder.repeat = FIAT_MARELLI_ENCODER_REPEAT;
|
||||||
|
instance->encoder.front = 0;
|
||||||
|
instance->encoder.is_running = true;
|
||||||
|
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_encoder_fiat_marelli_stop(void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
SubGhzProtocolEncoderFiatMarelli* instance = context;
|
||||||
|
instance->encoder.is_running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LevelDuration subghz_protocol_encoder_fiat_marelli_yield(void* context) {
|
||||||
|
furi_check(context);
|
||||||
|
SubGhzProtocolEncoderFiatMarelli* instance = context;
|
||||||
|
|
||||||
|
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
|
||||||
|
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) {
|
||||||
|
if(!subghz_block_generic_global.endless_tx) {
|
||||||
|
instance->encoder.repeat--;
|
||||||
|
}
|
||||||
|
instance->encoder.front = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Decoder
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
static void fiat_marelli_rebuild_raw_data(SubGhzProtocolDecoderFiatMarelli* instance) {
|
||||||
|
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||||
|
|
||||||
|
uint64_t key = instance->generic.data;
|
||||||
|
for(int i = 0; i < 8; i++) {
|
||||||
|
instance->raw_data[i] = (uint8_t)(key >> (56 - i * 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t extra_bits =
|
||||||
|
instance->generic.data_count_bit > 64 ? (instance->generic.data_count_bit - 64) : 0;
|
||||||
|
for(uint8_t i = 0; i < extra_bits && i < 32; i++) {
|
||||||
|
uint8_t byte_idx = 8 + (i / 8);
|
||||||
|
uint8_t bit_pos = 7 - (i % 8);
|
||||||
|
if(instance->extra_data & (1UL << (extra_bits - 1 - i))) {
|
||||||
|
instance->raw_data[byte_idx] |= (1 << bit_pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
instance->bit_count = instance->generic.data_count_bit;
|
||||||
|
|
||||||
|
if(instance->bit_count >= 56) {
|
||||||
|
instance->generic.serial =
|
||||||
|
((uint32_t)instance->raw_data[2] << 24) |
|
||||||
|
((uint32_t)instance->raw_data[3] << 16) |
|
||||||
|
((uint32_t)instance->raw_data[4] << 8) |
|
||||||
|
((uint32_t)instance->raw_data[5]);
|
||||||
|
instance->generic.btn = (instance->raw_data[6] >> 4) & 0xF;
|
||||||
|
instance->generic.cnt = (instance->raw_data[7] >> 3) & 0x1F;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fiat_marelli_prepare_data(SubGhzProtocolDecoderFiatMarelli* instance) {
|
||||||
|
instance->bit_count = 0;
|
||||||
|
instance->extra_data = 0;
|
||||||
|
instance->generic.data = 0;
|
||||||
|
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||||
|
manchester_advance(
|
||||||
|
instance->manchester_state,
|
||||||
|
ManchesterEventReset,
|
||||||
|
&instance->manchester_state,
|
||||||
|
NULL);
|
||||||
|
instance->decoder_state = FiatMarelliDecoderStepData;
|
||||||
|
}
|
||||||
|
|
||||||
void* subghz_protocol_decoder_fiat_marelli_alloc(SubGhzEnvironment* environment) {
|
void* subghz_protocol_decoder_fiat_marelli_alloc(SubGhzEnvironment* environment) {
|
||||||
UNUSED(environment);
|
UNUSED(environment);
|
||||||
SubGhzProtocolDecoderFiatMarelli* instance =
|
SubGhzProtocolDecoderFiatMarelli* instance =
|
||||||
@@ -181,6 +372,9 @@ void subghz_protocol_decoder_fiat_marelli_reset(void* context) {
|
|||||||
instance->bit_count = 0;
|
instance->bit_count = 0;
|
||||||
instance->extra_data = 0;
|
instance->extra_data = 0;
|
||||||
instance->te_last = 0;
|
instance->te_last = 0;
|
||||||
|
instance->te_sum = 0;
|
||||||
|
instance->te_count = 0;
|
||||||
|
instance->te_detected = 0;
|
||||||
instance->generic.data = 0;
|
instance->generic.data = 0;
|
||||||
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||||
instance->manchester_state = ManchesterStateMid1;
|
instance->manchester_state = ManchesterStateMid1;
|
||||||
@@ -189,35 +383,51 @@ void subghz_protocol_decoder_fiat_marelli_reset(void* context) {
|
|||||||
void subghz_protocol_decoder_fiat_marelli_feed(void* context, bool level, uint32_t duration) {
|
void subghz_protocol_decoder_fiat_marelli_feed(void* context, bool level, uint32_t duration) {
|
||||||
furi_check(context);
|
furi_check(context);
|
||||||
SubGhzProtocolDecoderFiatMarelli* instance = context;
|
SubGhzProtocolDecoderFiatMarelli* instance = context;
|
||||||
uint32_t te_short = (uint32_t)subghz_protocol_fiat_marelli_const.te_short;
|
|
||||||
uint32_t te_long = (uint32_t)subghz_protocol_fiat_marelli_const.te_long;
|
uint32_t te_short = instance->te_detected ? instance->te_detected
|
||||||
uint32_t te_delta = (uint32_t)subghz_protocol_fiat_marelli_const.te_delta;
|
: (uint32_t)subghz_protocol_fiat_marelli_const.te_short;
|
||||||
|
uint32_t te_long = te_short * 2;
|
||||||
|
uint32_t te_delta = te_short / 2;
|
||||||
|
if(te_delta < 30) te_delta = 30;
|
||||||
uint32_t diff;
|
uint32_t diff;
|
||||||
|
|
||||||
switch(instance->decoder_state) {
|
switch(instance->decoder_state) {
|
||||||
case FiatMarelliDecoderStepReset:
|
case FiatMarelliDecoderStepReset:
|
||||||
// Wait for first short HIGH pulse to start preamble
|
if(level) {
|
||||||
if(!level) return;
|
if(duration >= FIAT_MARELLI_PREAMBLE_PULSE_MIN &&
|
||||||
diff = (duration > te_short) ? (duration - te_short) : (te_short - duration);
|
duration <= FIAT_MARELLI_PREAMBLE_PULSE_MAX) {
|
||||||
if(diff < te_delta) {
|
instance->decoder_state = FiatMarelliDecoderStepPreamble;
|
||||||
instance->decoder_state = FiatMarelliDecoderStepPreamble;
|
instance->preamble_count = 1;
|
||||||
instance->preamble_count = 1;
|
instance->te_sum = duration;
|
||||||
instance->te_last = duration;
|
instance->te_count = 1;
|
||||||
|
instance->te_last = duration;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(duration > FIAT_MARELLI_RETX_GAP_MIN) {
|
||||||
|
instance->decoder_state = FiatMarelliDecoderStepRetxSync;
|
||||||
|
instance->te_last = duration;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FiatMarelliDecoderStepPreamble:
|
case FiatMarelliDecoderStepPreamble:
|
||||||
diff = (duration > te_short) ? (duration - te_short) : (te_short - duration);
|
if(duration >= FIAT_MARELLI_PREAMBLE_PULSE_MIN &&
|
||||||
|
duration <= FIAT_MARELLI_PREAMBLE_PULSE_MAX) {
|
||||||
if(diff < te_delta) {
|
|
||||||
// Short pulse (HIGH or LOW) preamble continues
|
|
||||||
instance->preamble_count++;
|
instance->preamble_count++;
|
||||||
|
instance->te_sum += duration;
|
||||||
|
instance->te_count++;
|
||||||
instance->te_last = duration;
|
instance->te_last = duration;
|
||||||
} else if(!level && duration > FIAT_MARELLI_GAP_MIN) {
|
} else if(!level) {
|
||||||
// Long LOW potential gap after preamble
|
if(instance->preamble_count >= FIAT_MARELLI_PREAMBLE_MIN && instance->te_count > 0) {
|
||||||
if(instance->preamble_count >= FIAT_MARELLI_PREAMBLE_MIN) {
|
instance->te_detected = instance->te_sum / instance->te_count;
|
||||||
instance->decoder_state = FiatMarelliDecoderStepSync;
|
uint32_t gap_threshold = instance->te_detected * FIAT_MARELLI_GAP_TE_MULT;
|
||||||
instance->te_last = duration;
|
|
||||||
|
if(duration > gap_threshold) {
|
||||||
|
instance->decoder_state = FiatMarelliDecoderStepSync;
|
||||||
|
instance->te_last = duration;
|
||||||
|
} else {
|
||||||
|
instance->decoder_state = FiatMarelliDecoderStepReset;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
instance->decoder_state = FiatMarelliDecoderStepReset;
|
instance->decoder_state = FiatMarelliDecoderStepReset;
|
||||||
}
|
}
|
||||||
@@ -226,20 +436,28 @@ void subghz_protocol_decoder_fiat_marelli_feed(void* context, bool level, uint32
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FiatMarelliDecoderStepSync:
|
case FiatMarelliDecoderStepSync: {
|
||||||
// Expect sync HIGH pulse ~2065us after the gap
|
uint32_t sync_min = instance->te_detected * FIAT_MARELLI_SYNC_TE_MIN_MULT;
|
||||||
if(level && duration >= FIAT_MARELLI_SYNC_MIN && duration <= FIAT_MARELLI_SYNC_MAX) {
|
uint32_t sync_max = instance->te_detected * FIAT_MARELLI_SYNC_TE_MAX_MULT;
|
||||||
// Sync detected prepare for Manchester data
|
|
||||||
instance->bit_count = 0;
|
if(level && duration >= sync_min && duration <= sync_max) {
|
||||||
instance->extra_data = 0;
|
fiat_marelli_prepare_data(instance);
|
||||||
instance->generic.data = 0;
|
instance->te_last = duration;
|
||||||
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
} else {
|
||||||
manchester_advance(
|
instance->decoder_state = FiatMarelliDecoderStepReset;
|
||||||
instance->manchester_state,
|
}
|
||||||
ManchesterEventReset,
|
break;
|
||||||
&instance->manchester_state,
|
}
|
||||||
NULL);
|
|
||||||
instance->decoder_state = FiatMarelliDecoderStepData;
|
case FiatMarelliDecoderStepRetxSync:
|
||||||
|
if(level && duration >= FIAT_MARELLI_RETX_SYNC_MIN &&
|
||||||
|
duration <= FIAT_MARELLI_RETX_SYNC_MAX) {
|
||||||
|
if(!instance->te_detected) {
|
||||||
|
instance->te_detected = duration / 8;
|
||||||
|
if(instance->te_detected < 70) instance->te_detected = 100;
|
||||||
|
if(instance->te_detected > 350) instance->te_detected = 260;
|
||||||
|
}
|
||||||
|
fiat_marelli_prepare_data(instance);
|
||||||
instance->te_last = duration;
|
instance->te_last = duration;
|
||||||
} else {
|
} else {
|
||||||
instance->decoder_state = FiatMarelliDecoderStepReset;
|
instance->decoder_state = FiatMarelliDecoderStepReset;
|
||||||
@@ -250,7 +468,6 @@ void subghz_protocol_decoder_fiat_marelli_feed(void* context, bool level, uint32
|
|||||||
ManchesterEvent event = ManchesterEventReset;
|
ManchesterEvent event = ManchesterEventReset;
|
||||||
bool frame_complete = false;
|
bool frame_complete = false;
|
||||||
|
|
||||||
// Classify duration as short or long Manchester edge
|
|
||||||
diff = (duration > te_short) ? (duration - te_short) : (te_short - duration);
|
diff = (duration > te_short) ? (duration - te_short) : (te_short - duration);
|
||||||
if(diff < te_delta) {
|
if(diff < te_delta) {
|
||||||
event = level ? ManchesterEventShortLow : ManchesterEventShortHigh;
|
event = level ? ManchesterEventShortLow : ManchesterEventShortHigh;
|
||||||
@@ -291,7 +508,7 @@ void subghz_protocol_decoder_fiat_marelli_feed(void* context, bool level, uint32
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if(instance->bit_count >= subghz_protocol_fiat_marelli_const.min_count_bit_for_found) {
|
if(instance->bit_count >= FIAT_MARELLI_MIN_DATA_BITS) {
|
||||||
frame_complete = true;
|
frame_complete = true;
|
||||||
} else {
|
} else {
|
||||||
instance->decoder_state = FiatMarelliDecoderStepReset;
|
instance->decoder_state = FiatMarelliDecoderStepReset;
|
||||||
@@ -301,36 +518,13 @@ void subghz_protocol_decoder_fiat_marelli_feed(void* context, bool level, uint32
|
|||||||
if(frame_complete) {
|
if(frame_complete) {
|
||||||
instance->generic.data_count_bit = instance->bit_count;
|
instance->generic.data_count_bit = instance->bit_count;
|
||||||
|
|
||||||
// Frame layout: bytes 0-1 are 0xFFFF preamble residue
|
|
||||||
// Bytes 2-5: Fixed ID (serial)
|
|
||||||
// Byte 6: Button (upper nibble) | subtype (lower nibble)
|
|
||||||
// Bytes 7-12: Rolling/encrypted code (48 bits)
|
|
||||||
instance->generic.serial =
|
instance->generic.serial =
|
||||||
((uint32_t)instance->raw_data[2] << 24) |
|
((uint32_t)instance->raw_data[2] << 24) |
|
||||||
((uint32_t)instance->raw_data[3] << 16) |
|
((uint32_t)instance->raw_data[3] << 16) |
|
||||||
((uint32_t)instance->raw_data[4] << 8) |
|
((uint32_t)instance->raw_data[4] << 8) |
|
||||||
((uint32_t)instance->raw_data[5]);
|
((uint32_t)instance->raw_data[5]);
|
||||||
instance->generic.btn = (instance->raw_data[6] >> 4) & 0xF;
|
instance->generic.btn = (instance->raw_data[6] >> 4) & 0xF;
|
||||||
instance->generic.cnt =
|
instance->generic.cnt = (instance->raw_data[7] >> 3) & 0x1F;
|
||||||
((uint32_t)instance->raw_data[7] << 16) |
|
|
||||||
((uint32_t)instance->raw_data[8] << 8) |
|
|
||||||
((uint32_t)instance->raw_data[9]);
|
|
||||||
|
|
||||||
FURI_LOG_I(
|
|
||||||
TAG,
|
|
||||||
"Decoded %d bits: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X",
|
|
||||||
instance->bit_count,
|
|
||||||
instance->raw_data[0],
|
|
||||||
instance->raw_data[1],
|
|
||||||
instance->raw_data[2],
|
|
||||||
instance->raw_data[3],
|
|
||||||
instance->raw_data[4],
|
|
||||||
instance->raw_data[5],
|
|
||||||
instance->raw_data[6],
|
|
||||||
instance->raw_data[7],
|
|
||||||
instance->raw_data[8],
|
|
||||||
instance->raw_data[9],
|
|
||||||
instance->raw_data[10]);
|
|
||||||
|
|
||||||
if(instance->base.callback) {
|
if(instance->base.callback) {
|
||||||
instance->base.callback(&instance->base, instance->base.context);
|
instance->base.callback(&instance->base, instance->base.context);
|
||||||
@@ -342,6 +536,7 @@ void subghz_protocol_decoder_fiat_marelli_feed(void* context, bool level, uint32
|
|||||||
instance->te_last = duration;
|
instance->te_last = duration;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,14 +562,15 @@ SubGhzProtocolStatus subghz_protocol_decoder_fiat_marelli_serialize(
|
|||||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||||
|
|
||||||
if(ret == SubGhzProtocolStatusOk) {
|
if(ret == SubGhzProtocolStatusOk) {
|
||||||
// Save extra data (bits 64+ right-aligned in uint32_t)
|
|
||||||
flipper_format_write_uint32(flipper_format, "Extra", &instance->extra_data, 1);
|
flipper_format_write_uint32(flipper_format, "Extra", &instance->extra_data, 1);
|
||||||
|
|
||||||
// Save total bit count explicitly (generic serialize also saves it, but Extra needs context)
|
|
||||||
uint32_t extra_bits = instance->generic.data_count_bit > 64
|
uint32_t extra_bits = instance->generic.data_count_bit > 64
|
||||||
? (instance->generic.data_count_bit - 64)
|
? (instance->generic.data_count_bit - 64)
|
||||||
: 0;
|
: 0;
|
||||||
flipper_format_write_uint32(flipper_format, "Extra_bits", &extra_bits, 1);
|
flipper_format_write_uint32(flipper_format, "Extra_bits", &extra_bits, 1);
|
||||||
|
|
||||||
|
uint32_t te = instance->te_detected;
|
||||||
|
flipper_format_write_uint32(flipper_format, "TE", &te, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
@@ -395,6 +591,11 @@ SubGhzProtocolStatus subghz_protocol_decoder_fiat_marelli_deserialize(
|
|||||||
instance->extra_data = extra;
|
instance->extra_data = extra;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t te = 0;
|
||||||
|
if(flipper_format_read_uint32(flipper_format, "TE", &te, 1)) {
|
||||||
|
instance->te_detected = te;
|
||||||
|
}
|
||||||
|
|
||||||
fiat_marelli_rebuild_raw_data(instance);
|
fiat_marelli_rebuild_raw_data(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -418,29 +619,35 @@ void subghz_protocol_decoder_fiat_marelli_get_string(void* context, FuriString*
|
|||||||
furi_check(context);
|
furi_check(context);
|
||||||
SubGhzProtocolDecoderFiatMarelli* instance = context;
|
SubGhzProtocolDecoderFiatMarelli* instance = context;
|
||||||
|
|
||||||
uint8_t total_bytes = (instance->bit_count + 7) / 8;
|
uint8_t epoch = instance->raw_data[6] & 0xF;
|
||||||
if(total_bytes > 13) total_bytes = 13;
|
uint8_t counter = (instance->raw_data[7] >> 3) & 0x1F;
|
||||||
|
const char* variant = (instance->te_detected &&
|
||||||
|
instance->te_detected < FIAT_MARELLI_TE_TYPE_AB_BOUNDARY)
|
||||||
|
? "B"
|
||||||
|
: "A";
|
||||||
|
uint8_t scramble = (instance->raw_data[7] >> 1) & 0x3;
|
||||||
|
uint8_t fixed = instance->raw_data[7] & 0x1;
|
||||||
|
|
||||||
furi_string_cat_printf(
|
furi_string_cat_printf(
|
||||||
output,
|
output,
|
||||||
"%s %dbit\r\n"
|
"%s %dbit\r\n"
|
||||||
"Sn:%08lX Btn:%s(0x%X)\r\n"
|
"Enc:%02X%02X%02X%02X%02X Scr:%02X\r\n"
|
||||||
"Roll:%02X%02X%02X%02X%02X%02X\r\n"
|
"Raw:%02X%02X Fixed:%X\r\n"
|
||||||
"Data:",
|
"Sn:%08X Cnt:%02X\r\n"
|
||||||
|
"Btn:%02X:[%s] Ep:%02X\r\n"
|
||||||
|
"Tp:%s\r\n",
|
||||||
instance->generic.protocol_name,
|
instance->generic.protocol_name,
|
||||||
instance->bit_count,
|
(int)instance->bit_count,
|
||||||
instance->generic.serial,
|
instance->raw_data[8], instance->raw_data[9],
|
||||||
|
instance->raw_data[10], instance->raw_data[11],
|
||||||
|
instance->raw_data[12],
|
||||||
|
(unsigned)scramble,
|
||||||
|
instance->raw_data[6], instance->raw_data[7],
|
||||||
|
(unsigned)fixed,
|
||||||
|
(unsigned int)instance->generic.serial,
|
||||||
|
(unsigned)counter,
|
||||||
|
(unsigned)instance->generic.btn,
|
||||||
fiat_marelli_button_name(instance->generic.btn),
|
fiat_marelli_button_name(instance->generic.btn),
|
||||||
instance->generic.btn,
|
(unsigned)epoch,
|
||||||
instance->raw_data[7],
|
variant);
|
||||||
instance->raw_data[8],
|
|
||||||
instance->raw_data[9],
|
|
||||||
(total_bytes > 10) ? instance->raw_data[10] : 0,
|
|
||||||
(total_bytes > 11) ? instance->raw_data[11] : 0,
|
|
||||||
(total_bytes > 12) ? instance->raw_data[12] : 0);
|
|
||||||
|
|
||||||
for(uint8_t i = 0; i < total_bytes; i++) {
|
|
||||||
furi_string_cat_printf(output, "%02X", instance->raw_data[i]);
|
|
||||||
}
|
|
||||||
furi_string_cat_printf(output, "\r\n");
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <furi.h>
|
#include "base.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>
|
#include <flipper_format/flipper_format.h>
|
||||||
|
|
||||||
#define FIAT_MARELLI_PROTOCOL_NAME "Fiat Marelli"
|
#define FIAT_MARELLI_PROTOCOL_NAME "Fiat Marelli"
|
||||||
@@ -31,7 +23,6 @@ SubGhzProtocolStatus
|
|||||||
subghz_protocol_decoder_fiat_marelli_deserialize(void* context, FlipperFormat* flipper_format);
|
subghz_protocol_decoder_fiat_marelli_deserialize(void* context, FlipperFormat* flipper_format);
|
||||||
void subghz_protocol_decoder_fiat_marelli_get_string(void* context, FuriString* output);
|
void subghz_protocol_decoder_fiat_marelli_get_string(void* context, FuriString* output);
|
||||||
|
|
||||||
// Encoder stubs
|
|
||||||
void* subghz_protocol_encoder_fiat_marelli_alloc(SubGhzEnvironment* environment);
|
void* subghz_protocol_encoder_fiat_marelli_alloc(SubGhzEnvironment* environment);
|
||||||
void subghz_protocol_encoder_fiat_marelli_free(void* context);
|
void subghz_protocol_encoder_fiat_marelli_free(void* context);
|
||||||
SubGhzProtocolStatus
|
SubGhzProtocolStatus
|
||||||
|
|||||||
517
lib/subghz/protocols/fiat_spa.c
Normal 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;
|
||||||
|
}
|
||||||
32
lib/subghz/protocols/fiat_spa.h
Normal 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);
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "../blocks/custom_btn_i.h"
|
#include "../blocks/custom_btn_i.h"
|
||||||
#include <lib/toolbox/manchester_decoder.h>
|
#include <lib/toolbox/manchester_decoder.h>
|
||||||
#include <flipper_format/flipper_format.h>
|
#include <flipper_format/flipper_format.h>
|
||||||
|
#include <furi_hal_crypto.h>
|
||||||
|
|
||||||
#define TAG "SubGhzProtocolKiaV6"
|
#define TAG "SubGhzProtocolKiaV6"
|
||||||
|
|
||||||
@@ -43,29 +44,6 @@ static const uint8_t aes_sbox[256] = {
|
|||||||
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
|
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
|
||||||
};
|
};
|
||||||
|
|
||||||
static const uint8_t aes_sbox_inv[256] = {
|
|
||||||
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
|
|
||||||
0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
|
|
||||||
0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
|
|
||||||
0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
|
|
||||||
0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
|
|
||||||
0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
|
|
||||||
0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
|
|
||||||
0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
|
|
||||||
0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
|
|
||||||
0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
|
|
||||||
0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
|
|
||||||
0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
|
|
||||||
0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
|
|
||||||
0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
|
|
||||||
0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
|
|
||||||
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d
|
|
||||||
};
|
|
||||||
|
|
||||||
static const uint8_t aes_rcon[10] = {
|
|
||||||
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SubGhzProtocolDecoderKiaV6 {
|
struct SubGhzProtocolDecoderKiaV6 {
|
||||||
SubGhzProtocolDecoderBase base;
|
SubGhzProtocolDecoderBase base;
|
||||||
SubGhzBlockDecoder decoder;
|
SubGhzBlockDecoder decoder;
|
||||||
@@ -159,181 +137,6 @@ static uint8_t kia_v6_custom_to_btn(uint8_t custom) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t gf_mul2(uint8_t x) {
|
|
||||||
return ((x >> 7) * 0x1b) ^ (x << 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void aes_subbytes_inv(uint8_t* state) {
|
|
||||||
for (int row = 0; row < 4; row++) {
|
|
||||||
for (int col = 0; col < 4; col++) {
|
|
||||||
state[row + col * 4] = aes_sbox_inv[state[row + col * 4]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void aes_shiftrows_inv(uint8_t* state) {
|
|
||||||
uint8_t temp;
|
|
||||||
temp = state[13];
|
|
||||||
state[13] = state[9];
|
|
||||||
state[9] = state[5];
|
|
||||||
state[5] = state[1];
|
|
||||||
state[1] = temp;
|
|
||||||
|
|
||||||
temp = state[2];
|
|
||||||
state[2] = state[10];
|
|
||||||
state[10] = temp;
|
|
||||||
temp = state[6];
|
|
||||||
state[6] = state[14];
|
|
||||||
state[14] = temp;
|
|
||||||
|
|
||||||
temp = state[3];
|
|
||||||
state[3] = state[7];
|
|
||||||
state[7] = state[11];
|
|
||||||
state[11] = state[15];
|
|
||||||
state[15] = temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void aes_mixcolumns_inv(uint8_t* state) {
|
|
||||||
uint8_t a, b, c, d;
|
|
||||||
for(int i = 0; i < 4; i++) {
|
|
||||||
a = state[i*4];
|
|
||||||
b = state[i*4+1];
|
|
||||||
c = state[i*4+2];
|
|
||||||
d = state[i*4+3];
|
|
||||||
|
|
||||||
uint8_t a2 = gf_mul2(a);
|
|
||||||
uint8_t a4 = gf_mul2(a2);
|
|
||||||
uint8_t a8 = gf_mul2(a4);
|
|
||||||
uint8_t b2 = gf_mul2(b);
|
|
||||||
uint8_t b4 = gf_mul2(b2);
|
|
||||||
uint8_t b8 = gf_mul2(b4);
|
|
||||||
uint8_t c2 = gf_mul2(c);
|
|
||||||
uint8_t c4 = gf_mul2(c2);
|
|
||||||
uint8_t c8 = gf_mul2(c4);
|
|
||||||
uint8_t d2 = gf_mul2(d);
|
|
||||||
uint8_t d4 = gf_mul2(d2);
|
|
||||||
uint8_t d8 = gf_mul2(d4);
|
|
||||||
|
|
||||||
state[i*4] = (a8^a4^a2) ^ (b8^b2^b) ^ (c8^c4^c) ^ (d8^d);
|
|
||||||
state[i*4+1] = (a8^a) ^ (b8^b4^b2) ^ (c8^c2^c) ^ (d8^d4^d);
|
|
||||||
state[i*4+2] = (a8^a4^a) ^ (b8^b) ^ (c8^c4^c2) ^ (d8^d2^d);
|
|
||||||
state[i*4+3] = (a8^a2^a) ^ (b8^b4^b) ^ (c8^c) ^ (d8^d4^d2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void aes_addroundkey(uint8_t* state, const uint8_t* round_key) {
|
|
||||||
for (int col = 0; col < 4; col++) {
|
|
||||||
state[col * 4] ^= round_key[col * 4];
|
|
||||||
state[col * 4 + 1] ^= round_key[col * 4 + 1];
|
|
||||||
state[col * 4 + 2] ^= round_key[col * 4 + 2];
|
|
||||||
state[col * 4 + 3] ^= round_key[col * 4 + 3];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void aes_subbytes(uint8_t* state) {
|
|
||||||
for (int row = 0; row < 4; row++) {
|
|
||||||
for (int col = 0; col < 4; col++) {
|
|
||||||
state[row + col * 4] = aes_sbox[state[row + col * 4]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void aes_shiftrows(uint8_t* state) {
|
|
||||||
uint8_t temp;
|
|
||||||
temp = state[1];
|
|
||||||
state[1] = state[5];
|
|
||||||
state[5] = state[9];
|
|
||||||
state[9] = state[13];
|
|
||||||
state[13] = temp;
|
|
||||||
temp = state[2];
|
|
||||||
state[2] = state[10];
|
|
||||||
state[10] = temp;
|
|
||||||
temp = state[6];
|
|
||||||
state[6] = state[14];
|
|
||||||
state[14] = temp;
|
|
||||||
temp = state[3];
|
|
||||||
state[3] = state[15];
|
|
||||||
state[15] = state[11];
|
|
||||||
state[11] = state[7];
|
|
||||||
state[7] = temp;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void aes_mixcolumns(uint8_t* state) {
|
|
||||||
uint8_t a, b, c, d;
|
|
||||||
for (int i = 0; i < 4; i++) {
|
|
||||||
a = state[i * 4];
|
|
||||||
b = state[i * 4 + 1];
|
|
||||||
c = state[i * 4 + 2];
|
|
||||||
d = state[i * 4 + 3];
|
|
||||||
state[i * 4] = gf_mul2(a) ^ gf_mul2(b) ^ b ^ c ^ d;
|
|
||||||
state[i * 4 + 1] = a ^ gf_mul2(b) ^ gf_mul2(c) ^ c ^ d;
|
|
||||||
state[i * 4 + 2] = a ^ b ^ gf_mul2(c) ^ gf_mul2(d) ^ d;
|
|
||||||
state[i * 4 + 3] = gf_mul2(a) ^ a ^ b ^ c ^ gf_mul2(d);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void aes_key_expansion(const uint8_t* key, uint8_t* round_keys) {
|
|
||||||
for (int i = 0; i < 16; i++) {
|
|
||||||
round_keys[i] = key[i];
|
|
||||||
}
|
|
||||||
for (int i = 4; i < 44; i++) {
|
|
||||||
int prev_word_idx = (i - 1) * 4;
|
|
||||||
uint8_t b0 = round_keys[prev_word_idx];
|
|
||||||
uint8_t b1 = round_keys[prev_word_idx + 1];
|
|
||||||
uint8_t b2 = round_keys[prev_word_idx + 2];
|
|
||||||
uint8_t b3 = round_keys[prev_word_idx + 3];
|
|
||||||
if ((i % 4) == 0) {
|
|
||||||
uint8_t new_b0 = aes_sbox[b1] ^ aes_rcon[(i / 4) - 1];
|
|
||||||
uint8_t new_b1 = aes_sbox[b2];
|
|
||||||
uint8_t new_b2 = aes_sbox[b3];
|
|
||||||
uint8_t new_b3 = aes_sbox[b0];
|
|
||||||
b0 = new_b0; b1 = new_b1; b2 = new_b2; b3 = new_b3;
|
|
||||||
}
|
|
||||||
int back_word_idx = (i - 4) * 4;
|
|
||||||
b0 ^= round_keys[back_word_idx];
|
|
||||||
b1 ^= round_keys[back_word_idx + 1];
|
|
||||||
b2 ^= round_keys[back_word_idx + 2];
|
|
||||||
b3 ^= round_keys[back_word_idx + 3];
|
|
||||||
int curr_word_idx = i * 4;
|
|
||||||
round_keys[curr_word_idx] = b0;
|
|
||||||
round_keys[curr_word_idx + 1] = b1;
|
|
||||||
round_keys[curr_word_idx + 2] = b2;
|
|
||||||
round_keys[curr_word_idx + 3] = b3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void aes128_decrypt(const uint8_t* expanded_key, uint8_t* data) {
|
|
||||||
uint8_t state[16];
|
|
||||||
memcpy(state, data, 16);
|
|
||||||
aes_addroundkey(state, &expanded_key[160]);
|
|
||||||
for (int round = 9; round > 0; round--) {
|
|
||||||
aes_shiftrows_inv(state);
|
|
||||||
aes_subbytes_inv(state);
|
|
||||||
aes_addroundkey(state, &expanded_key[round*16]);
|
|
||||||
aes_mixcolumns_inv(state);
|
|
||||||
}
|
|
||||||
aes_shiftrows_inv(state);
|
|
||||||
aes_subbytes_inv(state);
|
|
||||||
aes_addroundkey(state, &expanded_key[0]);
|
|
||||||
memcpy(data, state, 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void aes128_encrypt(const uint8_t* expanded_key, uint8_t* data) {
|
|
||||||
uint8_t state[16];
|
|
||||||
memcpy(state, data, 16);
|
|
||||||
aes_addroundkey(state, &expanded_key[0]);
|
|
||||||
for (int round = 1; round < 10; round++) {
|
|
||||||
aes_subbytes(state);
|
|
||||||
aes_shiftrows(state);
|
|
||||||
aes_mixcolumns(state);
|
|
||||||
aes_addroundkey(state, &expanded_key[round * 16]);
|
|
||||||
}
|
|
||||||
aes_subbytes(state);
|
|
||||||
aes_shiftrows(state);
|
|
||||||
aes_addroundkey(state, &expanded_key[160]);
|
|
||||||
memcpy(data, state, 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void get_kia_v6_aes_key(uint8_t* aes_key) {
|
static void get_kia_v6_aes_key(uint8_t* aes_key) {
|
||||||
uint64_t keystore_a = 0x37CE21F8C9F862A8ULL ^ 0x5448455049524154ULL;
|
uint64_t keystore_a = 0x37CE21F8C9F862A8ULL ^ 0x5448455049524154ULL;
|
||||||
uint32_t keystore_a_hi = (keystore_a >> 32) & 0xFFFFFFFF;
|
uint32_t keystore_a_hi = (keystore_a >> 32) & 0xFFFFFFFF;
|
||||||
@@ -381,9 +184,9 @@ static bool kia_v6_decrypt(SubGhzProtocolDecoderKiaV6* instance) {
|
|||||||
|
|
||||||
uint8_t aes_key[16];
|
uint8_t aes_key[16];
|
||||||
get_kia_v6_aes_key(aes_key);
|
get_kia_v6_aes_key(aes_key);
|
||||||
uint8_t expanded_key[176];
|
uint8_t decrypted_buf[16];
|
||||||
aes_key_expansion(aes_key, expanded_key);
|
furi_hal_crypto_aes128_ecb_decrypt(aes_key, encrypted_data, decrypted_buf);
|
||||||
aes128_decrypt(expanded_key, encrypted_data);
|
memcpy(encrypted_data, decrypted_buf, 16);
|
||||||
|
|
||||||
uint8_t *decrypted = encrypted_data;
|
uint8_t *decrypted = encrypted_data;
|
||||||
uint8_t calculated_crc = kia_v6_crc8(decrypted, 15, 0xFF, 0x07);
|
uint8_t calculated_crc = kia_v6_crc8(decrypted, 15, 0xFF, 0x07);
|
||||||
@@ -444,9 +247,9 @@ static void kia_v6_encrypt_payload(
|
|||||||
|
|
||||||
uint8_t aes_key[16];
|
uint8_t aes_key[16];
|
||||||
get_kia_v6_aes_key(aes_key);
|
get_kia_v6_aes_key(aes_key);
|
||||||
uint8_t expanded_key[176];
|
uint8_t encrypted[16];
|
||||||
aes_key_expansion(aes_key, expanded_key);
|
furi_hal_crypto_aes128_ecb_encrypt(aes_key, plain, encrypted);
|
||||||
aes128_encrypt(expanded_key, plain);
|
memcpy(plain, encrypted, 16);
|
||||||
|
|
||||||
uint8_t fx_hi = 0x20 | (fx_field >> 4);
|
uint8_t fx_hi = 0x20 | (fx_field >> 4);
|
||||||
uint8_t fx_lo = fx_field & 0x0F;
|
uint8_t fx_lo = fx_field & 0x0F;
|
||||||
|
|||||||
@@ -1,48 +1,76 @@
|
|||||||
#include "protocol_items.h" // IWYU pragma: keep
|
#include "protocol_items.h" // IWYU pragma: keep
|
||||||
|
|
||||||
const SubGhzProtocol* const subghz_protocol_registry_items[] = {
|
const SubGhzProtocol* const subghz_protocol_registry_items[] = {
|
||||||
&subghz_protocol_gate_tx, &subghz_protocol_keeloq,
|
&subghz_protocol_gate_tx,
|
||||||
&subghz_protocol_nice_flo, &subghz_protocol_came,
|
&subghz_protocol_keeloq,
|
||||||
&subghz_protocol_faac_slh, &subghz_protocol_nice_flor_s,
|
&subghz_protocol_nice_flo,
|
||||||
&subghz_protocol_came_twee, &subghz_protocol_came_atomo,
|
&subghz_protocol_came,
|
||||||
//&subghz_protocol_nero_sketch, //&subghz_protocol_ido,
|
&subghz_protocol_faac_slh,
|
||||||
&subghz_protocol_hormann, //&subghz_protocol_nero_radio,
|
&subghz_protocol_nice_flor_s,
|
||||||
&subghz_protocol_somfy_telis, &subghz_protocol_somfy_keytis,
|
&subghz_protocol_came_twee,
|
||||||
&subghz_protocol_princeton, &subghz_protocol_raw,
|
&subghz_protocol_came_atomo,
|
||||||
&subghz_protocol_linear, &subghz_protocol_secplus_v2,
|
//&subghz_protocol_nero_sketch,
|
||||||
&subghz_protocol_secplus_v1, &subghz_protocol_megacode,
|
//&subghz_protocol_ido,
|
||||||
//&subghz_protocol_holtek,
|
&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_chamb_code,
|
||||||
//&subghz_protocol_power_smart,
|
//&subghz_protocol_power_smart,
|
||||||
&subghz_protocol_marantec,
|
&subghz_protocol_marantec,
|
||||||
//&subghz_protocol_bett,
|
//&subghz_protocol_bett,
|
||||||
&subghz_protocol_doitrand,
|
&subghz_protocol_doitrand,
|
||||||
&subghz_protocol_phoenix_v2, //&subghz_protocol_honeywell_wdb,
|
&subghz_protocol_phoenix_v2,
|
||||||
|
//&subghz_protocol_honeywell_wdb,
|
||||||
//&subghz_protocol_magellan,
|
//&subghz_protocol_magellan,
|
||||||
//&subghz_protocol_intertechno_v3,
|
//&subghz_protocol_intertechno_v3,
|
||||||
//&subghz_protocol_clemsa, //&subghz_protocol_ansonic,
|
//&subghz_protocol_clemsa,
|
||||||
&subghz_protocol_smc5326, //&subghz_protocol_holtek_th12x,
|
//&subghz_protocol_ansonic,
|
||||||
&subghz_protocol_linear_delta3, //&subghz_protocol_dooya,
|
&subghz_protocol_smc5326,
|
||||||
&subghz_protocol_alutech_at_4n, &subghz_protocol_kinggates_stylo_4k,
|
&subghz_protocol_holtek_th12x,
|
||||||
&subghz_protocol_bin_raw, &subghz_protocol_mastercode,
|
&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_honeywell,
|
||||||
//&subghz_protocol_legrand,
|
//&subghz_protocol_legrand,
|
||||||
&subghz_protocol_dickert_mahs, //&subghz_protocol_gangqi,
|
&subghz_protocol_dickert_mahs,
|
||||||
&subghz_protocol_marantec24, //&subghz_protocol_hollarm,
|
//&subghz_protocol_gangqi,
|
||||||
&subghz_protocol_hay21, &subghz_protocol_revers_rb2,
|
&subghz_protocol_marantec24,
|
||||||
|
//&subghz_protocol_hollarm,
|
||||||
|
&subghz_protocol_hay21,
|
||||||
|
&subghz_protocol_revers_rb2,
|
||||||
//&subghz_protocol_feron,
|
//&subghz_protocol_feron,
|
||||||
&subghz_protocol_roger,
|
&subghz_protocol_roger,
|
||||||
//&subghz_protocol_elplast,
|
//&subghz_protocol_elplast,
|
||||||
//&subghz_protocol_treadmill37,
|
//&subghz_protocol_treadmill37,
|
||||||
&subghz_protocol_beninca_arc, //&subghz_protocol_jarolift,
|
&subghz_protocol_beninca_arc,
|
||||||
&subghz_protocol_vag, &subghz_protocol_porsche_cayenne, &subghz_protocol_ford_v0,
|
//&subghz_protocol_jarolift,
|
||||||
|
&subghz_protocol_vag,
|
||||||
|
&subghz_protocol_porsche_cayenne,
|
||||||
|
&subghz_protocol_ford_v0,
|
||||||
&subghz_protocol_psa,
|
&subghz_protocol_psa,
|
||||||
&subghz_protocol_fiat_v0, &subghz_protocol_fiat_marelli,
|
&subghz_protocol_fiat_spa,
|
||||||
&subghz_protocol_subaru, &subghz_protocol_mazda_siemens,
|
&subghz_protocol_fiat_marelli,
|
||||||
&subghz_protocol_kia_v0, &subghz_protocol_kia_v1,
|
&subghz_protocol_subaru,
|
||||||
&subghz_protocol_kia_v2, &subghz_protocol_kia_v3_v4,
|
&subghz_protocol_mazda_siemens,
|
||||||
&subghz_protocol_kia_v5, &subghz_protocol_kia_v6,
|
&subghz_protocol_kia_v0,
|
||||||
&subghz_protocol_suzuki, &subghz_protocol_mitsubishi_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 = {
|
const SubGhzProtocolRegistry subghz_protocol_registry = {
|
||||||
|
|||||||
@@ -23,16 +23,16 @@
|
|||||||
#include "secplus_v2.h"
|
#include "secplus_v2.h"
|
||||||
#include "secplus_v1.h"
|
#include "secplus_v1.h"
|
||||||
#include "megacode.h"
|
#include "megacode.h"
|
||||||
//#include "holtek.h"
|
#include "holtek.h"
|
||||||
#include "chamberlain_code.h"
|
#include "chamberlain_code.h"
|
||||||
#include "power_smart.h"
|
#include "power_smart.h"
|
||||||
#include "marantec.h"
|
#include "marantec.h"
|
||||||
#include "bett.h"
|
#include "bett.h"
|
||||||
#include "doitrand.h"
|
#include "doitrand.h"
|
||||||
#include "phoenix_v2.h"
|
#include "phoenix_v2.h"
|
||||||
//#include "honeywell_wdb.h"
|
#include "honeywell_wdb.h"
|
||||||
//#include "magellan.h"
|
#include "magellan.h"
|
||||||
//#include "intertechno_v3.h"
|
#include "intertechno_v3.h"
|
||||||
#include "clemsa.h"
|
#include "clemsa.h"
|
||||||
#include "ansonic.h"
|
#include "ansonic.h"
|
||||||
#include "smc5326.h"
|
#include "smc5326.h"
|
||||||
@@ -42,8 +42,8 @@
|
|||||||
#include "kinggates_stylo_4k.h"
|
#include "kinggates_stylo_4k.h"
|
||||||
#include "bin_raw.h"
|
#include "bin_raw.h"
|
||||||
#include "mastercode.h"
|
#include "mastercode.h"
|
||||||
//#include "honeywell.h"
|
#include "honeywell.h"
|
||||||
//#include "legrand.h"
|
#include "legrand.h"
|
||||||
#include "dickert_mahs.h"
|
#include "dickert_mahs.h"
|
||||||
#include "gangqi.h"
|
#include "gangqi.h"
|
||||||
#include "marantec24.h"
|
#include "marantec24.h"
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
#include "porsche_cayenne.h"
|
#include "porsche_cayenne.h"
|
||||||
#include "ford_v0.h"
|
#include "ford_v0.h"
|
||||||
#include "psa.h"
|
#include "psa.h"
|
||||||
#include "fiat_v0.h"
|
#include "fiat_spa.h"
|
||||||
#include "fiat_marelli.h"
|
#include "fiat_marelli.h"
|
||||||
#include "subaru.h"
|
#include "subaru.h"
|
||||||
#include "kia_generic.h"
|
#include "kia_generic.h"
|
||||||
@@ -73,4 +73,3 @@
|
|||||||
#include "suzuki.h"
|
#include "suzuki.h"
|
||||||
#include "mitsubishi_v0.h"
|
#include "mitsubishi_v0.h"
|
||||||
#include "mazda_siemens.h"
|
#include "mazda_siemens.h"
|
||||||
#include "keys.h"
|
|
||||||
|
|||||||
@@ -290,6 +290,32 @@ __attribute__((optimize("O3"), always_inline)) static inline void
|
|||||||
*v1 = b;
|
*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) {
|
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) |
|
*w0 = ((uint32_t)buffer[3] << 16) | ((uint32_t)buffer[2] << 24) |
|
||||||
((uint32_t)buffer[4] << 8) | (uint32_t)buffer[5];
|
((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) {
|
__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;
|
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++) {
|
for(uint32_t counter = PSA_BF1_START; counter < PSA_BF1_END; counter++) {
|
||||||
if(progress_cb && ((counter - PSA_BF1_START) & 0xFFFF) == 0) {
|
if(progress_cb && ((counter - PSA_BF1_START) & 0xFFFF) == 0) {
|
||||||
uint8_t pct = (uint8_t)(((uint64_t)(counter - PSA_BF1_START) * 50) / bf1_total);
|
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 wk2 = PSA_BF1_CONST_U4;
|
||||||
uint32_t wk3 = counter;
|
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 wk0 = (counter << 8) | 0x0E;
|
||||||
uint32_t wk1 = PSA_BF1_CONST_U5;
|
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 working_key[4] = {wk0, wk1, wk2, wk3};
|
||||||
|
|
||||||
uint32_t dec_v0 = w0;
|
uint32_t dec_v0 = w0;
|
||||||
uint32_t dec_v1 = w1;
|
uint32_t dec_v1 = w1;
|
||||||
psa_tea_decrypt(&dec_v0, &dec_v1, working_key);
|
psa_tea_decrypt(&dec_v0, &dec_v1, working_key);
|
||||||
|
|
||||||
if((counter & 0xFFFFFF) == (dec_v0 >> 8)) {
|
if((counter & 0xFFFFFF) == (dec_v0 >> 8)) {
|
||||||
uint8_t crc = psa_calculate_tea_crc(dec_v0, dec_v1);
|
uint8_t crc = psa_calculate_tea_crc(dec_v0, dec_v1);
|
||||||
if(crc == (dec_v1 & 0xFF)) {
|
if(crc == (dec_v1 & 0xFF)) {
|
||||||
psa_unpack_tea_result_to_buffer(buffer, dec_v0, dec_v1);
|
psa_unpack_tea_result_to_buffer(buffer, dec_v0, dec_v1);
|
||||||
psa_extract_fields_mode36(buffer, instance);
|
psa_extract_fields_mode36(buffer, instance);
|
||||||
instance->decrypted_seed = counter; // bf1 found key
|
instance->decrypted_seed = counter;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1976,7 +2004,7 @@ bool subghz_protocol_psa_decrypt_file(FlipperFormat* flipper_format, FuriString*
|
|||||||
|
|
||||||
if(result_str != NULL) {
|
if(result_str != NULL) {
|
||||||
furi_string_printf(result_str,
|
furi_string_printf(result_str,
|
||||||
"Decrypted!\nType: %02X\nKey: %08lX",
|
"Type: %02X\nSeed: %08lX",
|
||||||
instance.decrypted_type,
|
instance.decrypted_type,
|
||||||
instance.decrypted_seed);
|
instance.decrypted_seed);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ static Version version = {
|
|||||||
.magic = VERSION_MAGIC,
|
.magic = VERSION_MAGIC,
|
||||||
.major = VERSION_MAJOR,
|
.major = VERSION_MAJOR,
|
||||||
.minor = VERSION_MINOR,
|
.minor = VERSION_MINOR,
|
||||||
.git_hash = "ARF CFW",
|
.git_hash = GIT_COMMIT,
|
||||||
.git_branch = GIT_BRANCH,
|
.git_branch = GIT_BRANCH,
|
||||||
.build_date = BUILD_DATE,
|
.build_date = BUILD_DATE,
|
||||||
.version = VERSION
|
.version = VERSION
|
||||||
|
|||||||
@@ -1248,6 +1248,8 @@ Function,+,furi_hal_crypto_enclave_load_key,_Bool,"uint8_t, const uint8_t*"
|
|||||||
Function,+,furi_hal_crypto_enclave_store_key,_Bool,"FuriHalCryptoKey*, uint8_t*"
|
Function,+,furi_hal_crypto_enclave_store_key,_Bool,"FuriHalCryptoKey*, uint8_t*"
|
||||||
Function,+,furi_hal_crypto_enclave_unload_key,_Bool,uint8_t
|
Function,+,furi_hal_crypto_enclave_unload_key,_Bool,uint8_t
|
||||||
Function,+,furi_hal_crypto_enclave_verify,_Bool,"uint8_t*, uint8_t*"
|
Function,+,furi_hal_crypto_enclave_verify,_Bool,"uint8_t*, uint8_t*"
|
||||||
|
Function,+,furi_hal_crypto_aes128_ecb_decrypt,_Bool,"const uint8_t*, const uint8_t*, uint8_t*"
|
||||||
|
Function,+,furi_hal_crypto_aes128_ecb_encrypt,_Bool,"const uint8_t*, const uint8_t*, uint8_t*"
|
||||||
Function,+,furi_hal_crypto_encrypt,_Bool,"const uint8_t*, uint8_t*, size_t"
|
Function,+,furi_hal_crypto_encrypt,_Bool,"const uint8_t*, uint8_t*, size_t"
|
||||||
Function,+,furi_hal_crypto_gcm,_Bool,"const uint8_t*, const uint8_t*, const uint8_t*, size_t, const uint8_t*, uint8_t*, size_t, uint8_t*, _Bool"
|
Function,+,furi_hal_crypto_gcm,_Bool,"const uint8_t*, const uint8_t*, const uint8_t*, size_t, const uint8_t*, uint8_t*, size_t, uint8_t*, _Bool"
|
||||||
Function,+,furi_hal_crypto_gcm_decrypt_and_verify,FuriHalCryptoGCMState,"const uint8_t*, const uint8_t*, const uint8_t*, size_t, const uint8_t*, uint8_t*, size_t, const uint8_t*"
|
Function,+,furi_hal_crypto_gcm_decrypt_and_verify,FuriHalCryptoGCMState,"const uint8_t*, const uint8_t*, const uint8_t*, size_t, const uint8_t*, uint8_t*, size_t, const uint8_t*"
|
||||||
|
|||||||
|
@@ -1454,6 +1454,8 @@ Function,+,furi_hal_crypto_enclave_load_key,_Bool,"uint8_t, const uint8_t*"
|
|||||||
Function,+,furi_hal_crypto_enclave_store_key,_Bool,"FuriHalCryptoKey*, uint8_t*"
|
Function,+,furi_hal_crypto_enclave_store_key,_Bool,"FuriHalCryptoKey*, uint8_t*"
|
||||||
Function,+,furi_hal_crypto_enclave_unload_key,_Bool,uint8_t
|
Function,+,furi_hal_crypto_enclave_unload_key,_Bool,uint8_t
|
||||||
Function,+,furi_hal_crypto_enclave_verify,_Bool,"uint8_t*, uint8_t*"
|
Function,+,furi_hal_crypto_enclave_verify,_Bool,"uint8_t*, uint8_t*"
|
||||||
|
Function,+,furi_hal_crypto_aes128_ecb_decrypt,_Bool,"const uint8_t*, const uint8_t*, uint8_t*"
|
||||||
|
Function,+,furi_hal_crypto_aes128_ecb_encrypt,_Bool,"const uint8_t*, const uint8_t*, uint8_t*"
|
||||||
Function,+,furi_hal_crypto_encrypt,_Bool,"const uint8_t*, uint8_t*, size_t"
|
Function,+,furi_hal_crypto_encrypt,_Bool,"const uint8_t*, uint8_t*, size_t"
|
||||||
Function,+,furi_hal_crypto_gcm,_Bool,"const uint8_t*, const uint8_t*, const uint8_t*, size_t, const uint8_t*, uint8_t*, size_t, uint8_t*, _Bool"
|
Function,+,furi_hal_crypto_gcm,_Bool,"const uint8_t*, const uint8_t*, const uint8_t*, size_t, const uint8_t*, uint8_t*, size_t, uint8_t*, _Bool"
|
||||||
Function,+,furi_hal_crypto_gcm_decrypt_and_verify,FuriHalCryptoGCMState,"const uint8_t*, const uint8_t*, const uint8_t*, size_t, const uint8_t*, uint8_t*, size_t, const uint8_t*"
|
Function,+,furi_hal_crypto_gcm_decrypt_and_verify,FuriHalCryptoGCMState,"const uint8_t*, const uint8_t*, const uint8_t*, size_t, const uint8_t*, uint8_t*, size_t, const uint8_t*"
|
||||||
|
|||||||
|
@@ -21,9 +21,11 @@
|
|||||||
#define CRYPTO_MODE_DECRYPT (AES_CR_MODE_1)
|
#define CRYPTO_MODE_DECRYPT (AES_CR_MODE_1)
|
||||||
#define CRYPTO_MODE_DECRYPT_INIT (AES_CR_MODE_0 | AES_CR_MODE_1)
|
#define CRYPTO_MODE_DECRYPT_INIT (AES_CR_MODE_0 | AES_CR_MODE_1)
|
||||||
|
|
||||||
#define CRYPTO_DATATYPE_32B 0U
|
#define CRYPTO_DATATYPE_32B 0U
|
||||||
#define CRYPTO_KEYSIZE_256B (AES_CR_KEYSIZE)
|
#define CRYPTO_DATATYPE_8B (AES_CR_DATATYPE_1)
|
||||||
#define CRYPTO_AES_CBC (AES_CR_CHMOD_0)
|
#define CRYPTO_KEYSIZE_256B (AES_CR_KEYSIZE)
|
||||||
|
#define CRYPTO_AES_ECB 0U
|
||||||
|
#define CRYPTO_AES_CBC (AES_CR_CHMOD_0)
|
||||||
|
|
||||||
#define CRYPTO_AES_CTR (AES_CR_CHMOD_1)
|
#define CRYPTO_AES_CTR (AES_CR_CHMOD_1)
|
||||||
#define CRYPTO_CTR_IV_LEN (12U)
|
#define CRYPTO_CTR_IV_LEN (12U)
|
||||||
@@ -748,3 +750,72 @@ FuriHalCryptoGCMState furi_hal_crypto_gcm_decrypt_and_verify(
|
|||||||
|
|
||||||
return FuriHalCryptoGCMStateOk;
|
return FuriHalCryptoGCMStateOk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void crypto_key_init_ecb128(const uint8_t* key) {
|
||||||
|
CLEAR_BIT(AES1->CR, AES_CR_EN);
|
||||||
|
MODIFY_REG(
|
||||||
|
AES1->CR,
|
||||||
|
AES_CR_DATATYPE | AES_CR_KEYSIZE | AES_CR_CHMOD,
|
||||||
|
CRYPTO_DATATYPE_8B | CRYPTO_AES_ECB);
|
||||||
|
|
||||||
|
AES1->KEYR3 = ((uint32_t*)key)[0];
|
||||||
|
AES1->KEYR2 = ((uint32_t*)key)[1];
|
||||||
|
AES1->KEYR1 = ((uint32_t*)key)[2];
|
||||||
|
AES1->KEYR0 = ((uint32_t*)key)[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool furi_hal_crypto_aes128_ecb_encrypt(
|
||||||
|
const uint8_t* key,
|
||||||
|
const uint8_t* input,
|
||||||
|
uint8_t* output) {
|
||||||
|
furi_check(furi_hal_crypto_mutex);
|
||||||
|
furi_check(furi_mutex_acquire(furi_hal_crypto_mutex, FuriWaitForever) == FuriStatusOk);
|
||||||
|
|
||||||
|
furi_hal_bus_enable(FuriHalBusAES1);
|
||||||
|
crypto_key_init_ecb128(key);
|
||||||
|
|
||||||
|
MODIFY_REG(AES1->CR, AES_CR_MODE, CRYPTO_MODE_ENCRYPT);
|
||||||
|
SET_BIT(AES1->CR, AES_CR_EN);
|
||||||
|
|
||||||
|
bool state = crypto_process_block((uint32_t*)input, (uint32_t*)output, 4);
|
||||||
|
|
||||||
|
CLEAR_BIT(AES1->CR, AES_CR_EN);
|
||||||
|
furi_hal_bus_disable(FuriHalBusAES1);
|
||||||
|
furi_check(furi_mutex_release(furi_hal_crypto_mutex) == FuriStatusOk);
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool furi_hal_crypto_aes128_ecb_decrypt(
|
||||||
|
const uint8_t* key,
|
||||||
|
const uint8_t* input,
|
||||||
|
uint8_t* output) {
|
||||||
|
furi_check(furi_hal_crypto_mutex);
|
||||||
|
furi_check(furi_mutex_acquire(furi_hal_crypto_mutex, FuriWaitForever) == FuriStatusOk);
|
||||||
|
|
||||||
|
furi_hal_bus_enable(FuriHalBusAES1);
|
||||||
|
crypto_key_init_ecb128(key);
|
||||||
|
|
||||||
|
MODIFY_REG(AES1->CR, AES_CR_MODE, CRYPTO_MODE_DECRYPT_INIT);
|
||||||
|
SET_BIT(AES1->CR, AES_CR_EN);
|
||||||
|
|
||||||
|
if(!furi_hal_crypto_wait_flag(AES_SR_CCF)) {
|
||||||
|
CLEAR_BIT(AES1->CR, AES_CR_EN);
|
||||||
|
furi_hal_bus_disable(FuriHalBusAES1);
|
||||||
|
furi_check(furi_mutex_release(furi_hal_crypto_mutex) == FuriStatusOk);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SET_BIT(AES1->CR, AES_CR_CCFC);
|
||||||
|
|
||||||
|
MODIFY_REG(AES1->CR, AES_CR_MODE, CRYPTO_MODE_DECRYPT);
|
||||||
|
SET_BIT(AES1->CR, AES_CR_EN);
|
||||||
|
|
||||||
|
bool state = crypto_process_block((uint32_t*)input, (uint32_t*)output, 4);
|
||||||
|
|
||||||
|
CLEAR_BIT(AES1->CR, AES_CR_EN);
|
||||||
|
furi_hal_bus_disable(FuriHalBusAES1);
|
||||||
|
furi_check(furi_mutex_release(furi_hal_crypto_mutex) == FuriStatusOk);
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|||||||
@@ -290,6 +290,32 @@ FuriHalCryptoGCMState furi_hal_crypto_gcm_decrypt_and_verify(
|
|||||||
size_t length,
|
size_t length,
|
||||||
const uint8_t* tag);
|
const uint8_t* tag);
|
||||||
|
|
||||||
|
/** Encrypt a single 16-byte block using AES-128-ECB
|
||||||
|
*
|
||||||
|
* @param[in] key pointer to 16 bytes key data
|
||||||
|
* @param[in] input pointer to 16 bytes input data
|
||||||
|
* @param[out] output pointer to 16 bytes output data
|
||||||
|
*
|
||||||
|
* @return true on success
|
||||||
|
*/
|
||||||
|
bool furi_hal_crypto_aes128_ecb_encrypt(
|
||||||
|
const uint8_t* key,
|
||||||
|
const uint8_t* input,
|
||||||
|
uint8_t* output);
|
||||||
|
|
||||||
|
/** Decrypt a single 16-byte block using AES-128-ECB
|
||||||
|
*
|
||||||
|
* @param[in] key pointer to 16 bytes key data
|
||||||
|
* @param[in] input pointer to 16 bytes input data
|
||||||
|
* @param[out] output pointer to 16 bytes output data
|
||||||
|
*
|
||||||
|
* @return true on success
|
||||||
|
*/
|
||||||
|
bool furi_hal_crypto_aes128_ecb_decrypt(
|
||||||
|
const uint8_t* key,
|
||||||
|
const uint8_t* input,
|
||||||
|
uint8_t* output);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||