Add the two missing ASK line codes so an unrecognised tag yields its raw bits
instead of reading as "nothing":
- ask.nrz_bits: NRZ/direct demod. Keys the bit clock off the shortest run (not
2x a half-bit as in Manchester), resamples, yields both polarities/phases.
- ask.biphase_bits: biphase/differential-Manchester, reusing dsp.biphase_raw_decode
(the FDX-B path).
- capture.demod_raw: runs both, finds the shortest clean repeating frame, returns
{period, bits, hex} per encoding + the recovered clock. Sits BELOW the credential
decoders — it names no format, just hands back the bits.
- identify_lf falls back to demod_raw when no credential decodes off the air, so a
strong-but-unknown LF signal surfaces its frame instead of None.
Hardware-hardened: on a live non-NRZ/biphase tag the first cut reported a spurious
16-bit "frame" — biphase-decoding a non-biphase signal emits a short prefix then a
constant run that trivially repeats at any period. _frame_period now rejects
near-constant streams and finds the smallest *true* period (so 0101 fills read as
period 2 and are rejected, not reported at min_p). Regression tests cover all three.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
detect_clock estimated the bit clock from the *minimum* edge interval, so a few
stray sub-bit edges (which every real ADC capture carries) collapsed the estimate.
On a strongly-coupled, rail-clipping EM4100 the true half-bit of 32 dropped to ~3,
snapping the data clock to 8 and turning the ASK demod into all phase-error markers
— no config, no credential decoded.
Base the symbol on the shortest interval with real support (>=20% of the modal
count) instead of the raw minimum. Verified: clean synthetic still reads 32; the
hardware capture that broke it now clocks correctly. Live on a T5577 emulating
EM4100 00FFFFFFFF: DETECT decodes config 00148040, identify returns the credential.
Regression test reproduces the exact failure (sparse stray edges): old estimator
3.67 (dead demod), new 31.99.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hardware-validated. Two real bugs in the T55xx config decode, both fixed by requiring a
bit-rotation of the recovered repeating word to match a known preset:
- Rotation ambiguity: a block read recovers the 32-bit config at an arbitrary bit offset.
A real capture came back 0x000A4020 = the EM4100 word 0x00148040 rotated by one; the old
"looks sane" scorer accepted the rotation verbatim and mislabeled it FSK1/RF-6. Now every
rotation is tried and only a preset match (EM4100/HID/Indala/FDX-B/Viking/default) is
accepted -> resolves to 0x00148040 = EM4100.
- False positives: garbage config captures (all-0, all-1, a rotation of the tag's own
emission) no longer pass -> decode returns None and the reliable emitted-stream decode
carries the result.
identify_lf label now names the chip + the actual decoded credential:
"T5577 (EM4100 00FFFFFFFF)" instead of the config's guess. On real hardware this is now
stable 4/4: config 00148040, emitted EM4100 00FFFFFFFF.
Tests: rotation recovery (0x000A4020 -> EM4100), no-false-positive on all-1s/random,
updated label expectation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
capture drives the device synchronously (as Proxmark3.sync()'s proxy exposes it). Passing a
bare async Proxmark3() — whose methods return un-awaited coroutines, and which isn't even
connected — blew up deep in the demod with "object of type 'coroutine' has no len()". Now
identify_lf detects a coroutine-function device up front and raises an actionable TypeError
pointing at Proxmark3.sync().
The mock-based tests returned plain values, so they never exercised the async method shape;
added a test using a real coroutine function that reproduces and guards it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On this device the NG link is intermittent — a read/download that returns clean samples
one minute comes back as desynced BigBuf memory the next, and lf.read()'s reported count
is sometimes garbage (so sizing the download off it over-reads into memory). Rather than
trust either, capture now:
- downloads a fixed safe span (_DOWNLOAD_BYTES) instead of the flaky read-count,
- validates the result is an envelope, not memory (rejects the 0xDEADBEEF stack canary,
an embedded PM3 response magic, or a mostly-zero unwritten buffer),
- retries the read+download a few times, giving up cleanly to b'' if the device only
returns memory (needs a power-cycle / lower debug level).
read_emitted/read_config go through this, so identify recovers from transient desyncs
instead of demodulating garbage. Tests: validator rejects memory patterns; capture retries
past a garbage download to the good one, and gives up when all attempts are garbage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
With the debug-frame fix the BigBuf download now returns clean sample frames (verified on
hardware: offsets 0..N, real envelope data, then ACK). But identify downloaded a fixed
15000 bytes while the acquisition captured 12288 samples (98304 bits / 8), so the tail was
BigBuf memory past the samples (heap / the 0xDEADBEEF stack canary) — which flagged as
"garbage" and poisoned the demod.
read_emitted now sizes the download to lf.read()'s reported count (firmware returns the
size in BITS; /8 = samples at 8 bits/sample); read_config downloads the readbl acquisition
span. No trailing memory -> the demod runs on clean samples.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
identify_lf built its label from emitted['id_hex'], which only EM4100 provides — a
native HID/AWID/FDX-B tag (no T5577 config) would KeyError. Add credential_label() to
format each decoder's own fields: "HID Prox H10301 FC.. Card..", "AWID-26 FC.. Card..",
"FDX-B 124-000000000555 (animal)", "EM4100 <id>". Tests for native FDX-B + HID labels.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
protocols.decode_awid: FSK2a (no Manchester) via the ported detectAWID path, then
removeParity — the 88 bits after the preamble are 22 groups of 3 data + 1 odd-parity
bit, so stripping parity gives 66 bits whose first byte is the format length
(26/34/36/37/50) and the rest the facility/card fields (cmdlfawid.c index map). The 22
parity checks stand in for AWID's missing CRC, so it can't false-positive. Added to the
identify decoder chain.
Tests: AWID-26 round-trip across fc/card, parity gate rejects noise/EM4100. Full suite green.
LF stack now covers EM4100, HID, AWID, FDX-B + T55xx config. Indala (PSK) queued — the
one path needing real PSK phase-tracking (DetectPSKClock/pskRawDemod), best tuned against
a hardware capture.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- dsp.biphase_raw_decode: faithful port of lfdemod.c BiphaseRawDecode (pairwise
differential-Manchester decode with phase-fault offset nudge + error markers).
- protocols.decode_fdxb: ASK + biphase, find the 00000000001 preamble, de-interleave
the 8-bit data groups (every 9th bit a control 1), reassemble the 10-bit country +
38-bit national code. Accepted only when the CRC-16 over the eight data bytes matches
the stored one, so it can't false-positive. Both biphase polarities tried.
- protocols.crc16 / crc16_fdxb: parametric bitwise CRC-16 (Rocksoft model) matching the
firmware crc16_fast; FDX-B is poly 0x1021, init 0, refin=false, refout=true.
Added to the identify decoder chain (FDX-B is common in implants).
Tests: FDX-B round-trip (country/national/animal), CRC gate rejects noise/EM4100, and
a CRC known-answer test pinning the impl to CRC-16/XMODEM (0x31C3) and KERMIT (0x2189).
Full suite green.
Indala (PSK) + AWID queued.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New pm3py/lf/fsk.py — a function-for-function Python port of the Proxmark FSK
stack (firmware/common/lfdemod.c): fsk_wave_demod (count samples between 0->1
transitions: short wave = fc/8, long = fc/10) + aggregate_bits (runs -> clock-
resolution bits) = fskdemod, plus preamble_search, HIDdemodFSK, detectAWID hooks.
Ported to track the C reference (and thus real-tag output), not reinvented.
protocols.decode_hid: FSK2a + Manchester -> Wiegand. Format is told by frame
structure, not parity alone — the 26-bit H10301 wire frame carries a 0x20 header
at bit 37 + a sentinel 1 at bit 26 (per firmware add_HID_preamble); the 37-bit
H10304 frame is header-less (37-bit Manchester count, no 0x20). Each candidate is
still parity-validated by re-encoding through the shipped HIDProxTag, so a false
alignment can't slip through. Added to the identify decoder chain after EM4100.
Synthetic HID tests build the real add_HID_preamble framing (header+sentinel for
26-bit, bare 37-bit) and render fc/8/fc/10 square waves, so a green test means the
demod inverts what a real HID card / PM3 sim emits. Full suite 1232 green.
AWID (96-bit) framing decode + PSK/Indala + biphase/FDX-B queued next.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The firmware returns raw ADC envelope samples for LF reads (T55xx readbl replies
PM3_SUCCESS with NULL data); demodulation was always the C client's job, so pm3py
had none and LF identify could never see a tag. This adds the missing DSP stack in
Python — new pm3py.lf package:
- dsp.py: modulation-agnostic primitives — robust threshold, edge-interval clock
recovery, ASK binarize, half-bit resample, Manchester + biphase decode.
- protocols.py: EM4100 (ASK/Manchester -> 40-bit ID, validated by EM4100Code's own
header/row/col/stop parity checks) and best-effort T55xx block-0 config read
(find the repeating 32-bit word, score by T5577Config sanity, name the emulation).
- capture.py: live-device orchestration — read the emitted stream + probe a T55xx via
block-0 read, combine into "is it a T5577, and what is it (emulating)?".
rawcli identify now demodulates LF instead of the dead readbl-only probe: reports
"T5577 (EM4100 / EM4102)" for an emulator, "EM4100 <id>" for a bare credential.
Fully self-testing without hardware: the LF transponder models already encode these
protocols, so a synthetic envelope built from an encoder round-trips through the
demod. 17 demod tests (EM4100 across IDs/rates/mid-frame/noise, T55xx config, mocked
identify) + updated rawcli LF tests. Full suite 1225 green.
FSK/HID + PSK/Indala + biphase/FDX-B decoders queued (same dsp primitives).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>