Skip to content

Card Wire Bit-Length

A card value travels between PQ and an adapter as a hex string. Hex alone is lossy about width — leading zeros vanish, so 0A2B3C could be a 22-, 24-, or 26-bit value. When a panel encodes cards on the wire with a fixed bit width (Wiegand and similar), the adapter needs the bit-length, not just the hex. This page is the contract for carrying it.

One human number, many internal widths

To a person a card is simply "a 26-bit card" — one number, the card's identity width. That number is human-facing and lives on the card itself.

The wire width of a specific encoding is a different, internal number. A single card can be presented on several channels, each with its own width:

  • the canonical/identity channel — the card's own width (e.g. 26)
  • a transformed channel — e.g. the parity-stripped payload of a 26-bit Wiegand frame is 24 bits

26 (the full frame, including parity) and 24 (the payload) are both legitimate widths of the same card on different channels. That is exactly why width is carried per channel, never as one number per card. These per-channel widths are an adapter-only detail; they are never shown to a person.

The three flows

1. Sync — PQ → adapter (consume)

CardCredentialData carries exactly one card code. The framework reduces the server's projection to the single representation the panel's readers consume before your Transform runs, so iterating representations is impossible by construction:

Field Meaning
CodeType the selected code's format/encoding key (empty string = the card's identity/CSN channel); null when the card has no code
Code the selected code's hex wire value; null when the card has no code
CodeBits that channel's wire bit-length; null when the width is unknown
// writing a card onto a fixed-width panel: take the width from CodeBits, do not infer it from hex
if (card.Code is { } hex)
{
    panel.WriteCard(hex, card.CodeBits);   // CodeBits == null → width genuinely unknown; fall back as the panel allows
}

Recommended: an adapter that writes cards with a fixed wire format reads the width from CodeBits, rather than guessing it from the hex length. A null CodeBits means the width is genuinely unknown — treat it exactly as you do today.

The framework selects the code from the reader card_format preference order (a stable ordinal order when no reader declares a format). A person with several cards still produces one record per card; only the multiple representations of one physical card collapse to a single code.

2. Enroll — adapter → PQ (propagate)

When the reader captures a card, CardEnrollmentData carries the value plus an optional width:

var data = new CardEnrollmentData
{
    EnrollmentId = command.EnrollmentId,
    PersonId = command.PersonId,
    CardCode = ExtractCardCode(deviceEvent),
    Technology = "vendor.card",
    BitLength = ExtractBitCount(deviceEvent),   // set it WHEN the reader/SDK reports a bit count
};

Required when known: if the protocol or SDK hands you the wire bit count alongside the card data (many reader transactions include an explicit bit-count field), set BitLength. Leave it null only when the source truly does not provide it — never invent one.

3. Unknown card — adapter → PQ (propagate)

When an unrecognized card is presented, the denial event carries the value and an optional width:

await reader.AccessDeniedUnknown(
    timestamp,
    card_code: ExtractCardCode(deviceEvent),
    card_bits: ExtractBitCount(deviceEvent));   // pass it WHEN known

Required when known: pass card_bits whenever the protocol gives you the bit count. It lets PQ later identify the card's format. Omit it only when the width is genuinely unavailable.

The rule of thumb

If the protocol hands you a bit count next to the card data, carry it through — on enroll (CardEnrollmentData.BitLength) and on unknown-card denial (AccessDeniedUnknown(card_bits:)). If it hands you a card to write, take the width from CardCredentialData.CodeBits, not from the hex. Never fabricate a width you were not given; null/omitted means "unknown", which is a valid answer.

The propagation direction (enroll, unknown card) is enforced — see the compliance rule RULE-053. The consume direction is a strong recommendation, not a gate, because whether an adapter needs the width depends on its panel's encoding.