Skip to content

Add Transparent Reader Support (TRS)#324

Open
sidcha wants to merge 7 commits into
masterfrom
trs_dev
Open

Add Transparent Reader Support (TRS)#324
sidcha wants to merge 7 commits into
masterfrom
trs_dev

Conversation

@sidcha

@sidcha sidcha commented Jul 12, 2026

Copy link
Copy Markdown
Member

TRS tunnels raw smart-card APDUs from the CP, through a PD acting as a transparent pipe, to a card in the reader (OSDP CMD_XWR/REPLY_XRD, capability SMART_CARD_SUPPORT). The control panel drives the card directly, so a compromised reader cannot forge a credential.

The CP runs the session as a library-driven state machine: it negotiates transparent mode, streams the app's APDUs, and tears the session down on osdp_cp_trs_stop(). Apps submit OSDP_CMD_XWR commands and receive card responses as OSDP_EVENT_TRS events.

The PD answers each APDU either synchronously, in the command callback, or deferred: it ACKs "working" and delivers the response on a later poll once a slow card responds. Deferral keeps a shared multi-drop bus non-blocking, so each PD's session makes progress independently.

Gated behind OPT_BUILD_OSDP_TRS (default off); wired into both build systems and covered by a CP<->PD APDU round-trip unit test.

Co-developed-by: Aaron Tulino (Aaronjamt) git@aaronjamt.com

@sidcha
sidcha force-pushed the trs_dev branch 3 times, most recently from dcdb1a3 to e6e5c66 Compare July 14, 2026 17:30
sidcha added 7 commits July 17, 2026 16:32
TRS tunnels raw smart-card APDUs from the CP, through a PD acting as
a transparent pipe, to a card in the reader (OSDP CMD_XWR/REPLY_XRD,
capability SMART_CARD_SUPPORT). The control panel drives the card
directly, so a compromised reader cannot forge a credential.

The CP runs the session as a library-driven state machine: it
negotiates transparent mode, streams the app's APDUs, and tears the
session down on osdp_cp_trs_stop(). Apps submit OSDP_CMD_XWR commands
and receive card responses as OSDP_EVENT_TRS events.

The PD answers each APDU either synchronously, in the command
callback, or deferred: it ACKs "working" and delivers the response on
a later poll once a slow card responds. Deferral keeps a shared
multi-drop bus non-blocking, so each PD's session makes progress
independently.

A Card Present reply is accepted with or without the trailing status
byte -- the spec marks it optional (v2.2 section 7.26.8) and readers
like the HID RPK40 omit it; the status then reports as unspecified.

Gated behind OPT_BUILD_OSDP_TRS (default off); wired into both build
systems and covered by a CP<->PD APDU round-trip unit test.

Fixes: #22
Co-developed-by: Aaron Tulino (Aaronjamt) <git@aaronjamt.com>
Signed-off-by: Siddharth Chandrasekaran <sidcha.dev@gmail.com>
Table 36 marks the mode-config byte optional: in its absence 0x00 is
used. The PD decoder demanded both mode and config, NAK-ing a 3-byte
Mode-Set that a foreign ACU may legitimately send.

Related-to: #22
Signed-off-by: Siddharth Chandrasekaran <sidcha.dev@gmail.com>
The RPK40 NAKs a CARD_SCAN it does not implement with MSG_CHK, which
the CP's corrupted-frame heuristic answered with up to eight blind
retransmissions before tearing the card session down. Replaying card
traffic can have card-side effects (PIN retry counters, transaction
state), so CMD_XWR is exempt from the resend heuristic entirely; and
since a reader may pick any NAK code it fancies for an unimplemented
card command, every NAK to an app-owned card command is a soft
failure -- one failed command, session intact -- unless the code says
the link itself is broken (SEQ_NUM, SC_UNSUP, SC_COND).

The test runs on a plaintext link, as in the field trace: the mock
channel rewrites the reader's reply into a NAK(MSG_CHK) at the same
sequence number, which is also why the mock gained a no-SCBK device
setup -- rewriting a secured reply would desync the MAC chain, which
a real quirky reader never does.

Related-to: #22
Signed-off-by: Siddharth Chandrasekaran <sidcha.dev@gmail.com>
A reader left in transparent mode -- failed teardown, CP restart --
keeps answering polls with XRD replies. The CP treated those as
unknown responses and cycled the PD offline for the full retry
penalty, taking a healthy link down over a stale reader mode.

Swallow the reply with a warning instead: it is well-formed, it just
has no session to route to. The payload is dropped, no event reaches
the app, and the link stays up.

Related-to: #22
Signed-off-by: Siddharth Chandrasekaran <sidcha.dev@gmail.com>
The 64-byte APDU carrier could not hold what the headline use case
moves: PIV certificate reads chain GET RESPONSE chunks up to the
short-APDU maximum of 258 bytes (255 data + SW1SW2).

Raise the default to 258 behind an #ifndef so constrained builds can
dial it back down -- osdp_cmd/osdp_event embed a buffer of this size,
so the override is also the memory knob. The usable size is further
bound by the negotiated packet size; a C-APDU that cannot fit it is
now rejected at submit time instead of failing mid-band. Full-size
chunks additionally need -DOSDP_PACKET_BUF_SIZE=512 (default 256).

Chunking inside the library would be wrong here: TRS is a transparent
tunnel and ISO 7816-4 has its own chaining.

Related-to: #22
Signed-off-by: Siddharth Chandrasekaran <sidcha.dev@gmail.com>
The card (emu-card.c, libosdp-free) speaks just enough ISO 7816-4 to
model the headline workflow: SELECT of the PIV AID, GET DATA of a
1.2KB certificate-sized object, and 61xx/GET RESPONSE chaining.

The scenarios drive it through a real CP<->PD session over the mock
channel, modeled on the reader behaviour reported in issue #22: a
full chained certificate read, a session that must ride out a busy
spell, a permanently-busy reader costing one soft-failed command, and
a card leaving the field mid-band with no card-not-present courtesy
from the reader.

Related-to: #22
Signed-off-by: Siddharth Chandrasekaran <sidcha.dev@gmail.com>
The channel-hook frame parsers assumed the leading mark byte from
build options, but its presence also varies by direction and RX mode;
detect it from the frame instead. Also add test-pd-zc.c to the lean
build's test sources -- the zero-copy unit-test binary never linked
there.

Related-to: #22
Signed-off-by: Siddharth Chandrasekaran <sidcha.dev@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant