#!/usr/bin/env python3 """Try the two things a Silicon Labs payload usually has: PN9 whitening and a checksum.""" import json, itertools, binascii FR = json.load(open("/home/tmco/sdr/deep/locked_frames.json")) def bits_to_bytes(bits): n = len(bits) // 8 * 8 return bytes(int(bits[i:i + 8], 2) for i in range(0, n, 8)), bits[n:] # ---------------- PN9 (x^9 + x^5 + 1) used by Si4x3x whiteners ---------------- def pn9_stream(nbytes, seed=0x1FF): state = seed out = bytearray() for _ in range(nbytes): b = 0 for i in range(8): bit = ((state >> 8) ^ (state >> 4)) & 1 state = ((state << 1) | bit) & 0x1FF b = (b << 1) | bit out.append(b) return bytes(out) def dewhiten(data, seed=0x1FF, skip=0): s = pn9_stream(len(data) + skip, seed)[skip:] return bytes(a ^ b for a, b in zip(data, s)) # ---------------- checksum candidates ---------------- def crc8(data, poly, init, refin, refout, xorout): crc = init for byte in data: if refin: byte = int(f"{byte:08b}"[::-1], 2) crc ^= byte for _ in range(8): crc = ((crc << 1) ^ poly) & 0xFF if crc & 0x80 else (crc << 1) & 0xFF if refout: crc = int(f"{crc:08b}"[::-1], 2) return crc ^ xorout def crc16(data, poly, init, refin): crc = init for byte in data: if refin: byte = int(f"{byte:08b}"[::-1], 2) crc ^= byte << 8 for _ in range(8): crc = ((crc << 1) ^ poly) & 0xFFFF if crc & 0x8000 else (crc << 1) & 0xFFFF return crc def main(): shorts, longs = [], [] for f in FR: b, _ = bits_to_bytes(f["payload"]) (shorts if len(b) == 9 else longs).append((b, f)) print(f"byte-aligned short frames: {len(shorts)} long frames: {len(longs)}") print("\n=== short frame bytes ===") for b, f in shorts[:4]: print(" ", b.hex(" ")) # ---- checksum search on the short frame: last byte vs the previous six body = shorts[0][0][:-1] target = shorts[0][0][-1] print(f"\n=== is byte 9 (0x{target:02X}) a checksum over {body.hex(' ')}? ===") hits = [] for poly in (0x07, 0x31, 0x9B, 0x39, 0xD5, 0x1D, 0x2F, 0x4D): for init in (0x00, 0xFF): for refin, refout in ((False, False), (True, True)): for xorout in (0x00, 0xFF): if crc8(body, poly, init, refin, refout, xorout) == target: hits.append(f"CRC-8 poly=0x{poly:02X} init=0x{init:02X} " f"refin={refin} refout={refout} xorout=0x{xorout:02X}") for name, val in (("sum8", sum(body) & 0xFF), ("xor8", __import__("functools").reduce(lambda a, b: a ^ b, body)), ("sum8_inv", (~sum(body)) & 0xFF)): if val == target: hits.append(name) print(" matches:", hits if hits else "none of the common CRC-8/sum configurations") # ---- PN9 de-whitening on the long frames print("\n=== long frames: de-whitened with PN9 (seed 0x1FF) ===") for b, f in longs[:5]: d0 = dewhiten(b) d1 = dewhiten(b, skip=8) def printable(x): return sum(32 <= c < 127 for c in x) / max(len(x), 1) print(f" raw {b.hex(' ')[:72]}") print(f" pn9 {d0.hex(' ')[:72]} printable {printable(d0)*100:.0f}%") print(f" pn9+1B {d1.hex(' ')[:72]} printable {printable(d1)*100:.0f}%") print() # ---- does the 5th byte correlate with frame length? print("=== byte 6 (index 5) vs payload length ===") for b, f in longs: print(f" len {len(b):3d} bytes byte5=0x{b[5]:02X} tail={b[-4:].hex(' ')}") for b, f in shorts[:3]: print(f" len {len(b):3d} bytes byte5=0x{b[5]:02X} tail={b[-4:].hex(' ')}") if __name__ == "__main__": main()