Merge feat/decoded-key-exact-db-match: exact signature-DB match for decoded KEY .sub files

This commit is contained in:
leetcrypt
2026-07-22 09:12:38 -07:00
2 changed files with 189 additions and 3 deletions
+58 -3
View File
@@ -146,10 +146,41 @@ class PatternDecoder:
Identify a decoded (KEY) .sub file from its Protocol name.
KEY files have no RAW pulses but carry a decoded `Protocol:` name,
frequency and key. We route the protocol name to a device category and
emit a match surfacing the real Flipper protocol + routed category, so
decoded uploads are no longer silently unidentifiable.
frequency and key. Two cases:
1. The named protocol is in our signature database. This is the
strongest possible identification — the capture device already
demodulated and *named* the protocol, and we recognise it — so we
surface the REAL catalog signature (true manufacturer + category)
with high, frequency-aware confidence. This is an exact "database
match", not a timing guess, and it keeps the surfaced category
consistent with the catalog (e.g. Princeton -> Garage Door Opener)
instead of the router's coarser guess.
2. The name is unknown to us. Fall back to category routing so the
upload is still identified by family rather than silently dropped.
"""
known = self._lookup_known_protocol(metadata.protocol, metadata.frequency)
if known is not None:
freq_ok = bool(metadata.frequency) and known.matches_frequency(metadata.frequency)
# Exact, already-decoded match against a known protocol. Confidence
# is high but never a literal 1.0 (the key payload itself is not
# cryptographically verified); a consistent frequency lifts it.
confidence = 0.95 if freq_ok else 0.90
details = {
"predicted_category": known.category,
"source": "decoded_key_exact",
"bit_length": metadata.bit_length,
"frequency_match": freq_ok,
"manufacturer": known.manufacturer,
}
return [DeviceMatch(
protocol=known,
confidence=confidence,
match_method="decoded_key_exact",
details=details,
)]
pred = self.category_router.route_by_protocol(
metadata.protocol, metadata.frequency
)
@@ -172,6 +203,30 @@ class PatternDecoder:
details=details,
)]
def _lookup_known_protocol(
self, name: Optional[str], frequency: Optional[int]
) -> Optional[ProtocolSignature]:
"""Exact (case-insensitive) protocol-name lookup in the signature DB.
When several catalog entries share a name, prefer one whose frequency
band matches the capture; otherwise return the first. Returns None if
the name is unknown.
"""
if not name:
return None
target = name.strip().lower()
candidates = [
p for p in self.protocol_db.get_all()
if p.name.strip().lower() == target
]
if not candidates:
return None
if frequency:
for cand in candidates:
if cand.matches_frequency(frequency):
return cand
return candidates[0]
def _identify_pulse_widths(self, pulses: List[int]) -> Tuple[int, int, int, int]:
"""
Identify SHORT/LONG pulse and gap durations using robust timing analyzer
@@ -0,0 +1,131 @@
#!/usr/bin/env python3
"""
Offline unit tests for decoded (KEY) .sub identification against the signature DB.
A Flipper KEY file carries no RAW pulses but *names* a decoded protocol (e.g.
``Protocol: Princeton``). When that name is in our signature database this is the
strongest possible identification -- the capture device already demodulated and
named the protocol and we recognise it -- so it must:
- resolve to the REAL catalog signature (true manufacturer + category), not the
category router's coarser guess (Princeton is a Garage Door Opener in the
catalog, which the router previously mislabelled "Remote Control"), and
- score with high, frequency-aware confidence (exact database match), not the
~0.45 fuzzy-guess confidence it used to get.
Unknown protocol names must still fall back to category routing so an upload is
identified by family rather than silently dropped ("accept any capture ->
identify the device").
Offline / author-time: bare ``pytest`` runs this with no docker, no server, no
network. KEY .sub content is embedded and written to ``tmp_path``.
"""
import pytest
from src.parser.sub_parser import SubFileParser
from src.matcher.engine import SignatureMatcher
from src.matcher.pattern_decoder import PatternDecoder
from src.matcher.protocol_database import get_protocol_database
def _key_sub(protocol: str, frequency: int, bit: int = 24, key: str = "00 00 00 00 00 95 D5 D4") -> str:
return "\n".join([
"Filetype: Flipper SubGhz Key File",
"Version: 1",
f"Frequency: {frequency}",
"Preset: FuriHalSubGhzPresetOok270Async",
f"Protocol: {protocol}",
f"Bit: {bit}",
f"Key: {key}",
"TE: 400",
"",
])
def _write(tmp_path, content, name="k.sub"):
p = tmp_path / name
p.write_text(content)
return str(p)
def _identify(tmp_path, content):
meta = SubFileParser().parse(_write(tmp_path, content))
return meta, SignatureMatcher().match(meta, max_results=5)
# A known catalog protocol and a known one that carries a manufacturer, both
# taken from the DB itself so the tests stay correct if the catalog changes.
_DB = get_protocol_database()
_KNOWN = next(p for p in _DB.get_all())
_KNOWN_WITH_MFR = next((p for p in _DB.get_all() if p.manufacturer), None)
_UNKNOWN_NAME = "ZZ_NOT_A_REAL_PROTOCOL_9999"
class TestKnownProtocolExactMatch:
def test_named_protocol_resolves_to_catalog_signature(self, tmp_path):
meta, matches = _identify(tmp_path, _key_sub(_KNOWN.name, _KNOWN.frequency))
assert meta.file_format == "KEY"
assert meta.is_decoded is True
assert len(matches) >= 1
top = matches[0]
assert top.device_name == _KNOWN.name
assert top.match_method == "decoded_key_exact"
# Category comes from the catalog, not the router's guess.
assert top.match_details.get("device_category") == _KNOWN.category
def test_exact_match_scores_high_when_frequency_consistent(self, tmp_path):
_, matches = _identify(tmp_path, _key_sub(_KNOWN.name, _KNOWN.frequency))
assert matches[0].confidence == pytest.approx(0.95)
assert matches[0].match_details.get("frequency_match") is True
def test_exact_match_still_high_but_lower_on_frequency_mismatch(self, tmp_path):
# Same known name, but a frequency far outside its band.
off_band = _KNOWN.frequency + 50_000_000
_, matches = _identify(tmp_path, _key_sub(_KNOWN.name, off_band))
top = matches[0]
assert top.match_method == "decoded_key_exact"
assert top.confidence == pytest.approx(0.90)
assert top.match_details.get("frequency_match") is False
@pytest.mark.skipif(_KNOWN_WITH_MFR is None, reason="no catalog protocol carries a manufacturer")
def test_real_manufacturer_is_surfaced(self, tmp_path):
_, matches = _identify(
tmp_path, _key_sub(_KNOWN_WITH_MFR.name, _KNOWN_WITH_MFR.frequency, bit=66)
)
assert matches[0].manufacturer == _KNOWN_WITH_MFR.manufacturer
class TestUnknownProtocolFallback:
def test_unknown_name_falls_back_to_router(self, tmp_path):
_, matches = _identify(tmp_path, _key_sub(_UNKNOWN_NAME, 433920000))
assert len(matches) >= 1, "unknown decoded protocol must still be identified by family"
top = matches[0]
assert top.match_method == "decoded_key"
# A routed guess must not masquerade as an exact database match.
assert top.confidence < 0.90
def test_unknown_name_still_names_the_protocol(self, tmp_path):
_, matches = _identify(tmp_path, _key_sub(_UNKNOWN_NAME, 433920000))
assert matches[0].device_name == _UNKNOWN_NAME
class TestLookupHelper:
def test_known_name_case_insensitive(self):
dec = PatternDecoder()
sig = dec._lookup_known_protocol(_KNOWN.name.upper(), _KNOWN.frequency)
assert sig is not None
assert sig.name == _KNOWN.name
def test_unknown_name_returns_none(self):
assert PatternDecoder()._lookup_known_protocol(_UNKNOWN_NAME, 433920000) is None
def test_empty_name_returns_none(self):
assert PatternDecoder()._lookup_known_protocol(None, 433920000) is None
def test_prefers_frequency_matching_candidate(self):
"""If duplicate-named entries exist, the frequency-consistent one wins."""
dec = PatternDecoder()
sig = dec._lookup_known_protocol(_KNOWN.name, _KNOWN.frequency)
assert sig is not None
assert sig.matches_frequency(_KNOWN.frequency)