nfc
stableEncode and decode NFC Data Exchange Format (NDEF) messages and build ISO 7816-4 APDU commands for smart card communication.
use plugin nfc::{encode_text_record, encode_uri_record, encode_ndef_message, …} Functions (14)
- encode_text_record Encode an NDEF Text Record (TNF=0x01)
- encode_uri_record Encode an NDEF URI Record with prefix abbreviation
- encode_ndef_message Wrap NDEF records in TLV-framed message bytes
- decode_ndef_message Parse raw NDEF bytes into a records table
- uri_prefix_code Get numeric code for a URI prefix string
- uri_prefix_from_code Get URI prefix string from numeric code
- apdu_select Build ISO 7816-4 SELECT APDU for an AID
- apdu_read Build ISO 7816-4 READ BINARY APDU
- apdu_write Build ISO 7816-4 UPDATE BINARY APDU
- parse_uid Format raw UID bytes as colon-separated hex
- tag_info Build a tag info summary from UID/ATQA/SAK
- detect_technology Detect NFC technology from ATQA and SAK
- decode_text_payload Decode an NDEF Text Record payload
- decode_uri_payload Decode an NDEF URI Record payload
Overview
nfc is a dependency-free toolkit for the byte-level encoding work behind NFC
tags and contactless smart cards. It has no live reader handle and holds no
state — every function is a pure transform between Zolo values and raw bytes, so
you pair it with whatever transport actually talks to hardware (a reader plugin,
a Web NFC bridge, an APDU channel). Two layers live here: NDEF message building
and parsing (text and URI records, TLV framing) and ISO 7816-4 APDU command
construction (SELECT, READ BINARY, UPDATE BINARY) plus tag-identification
helpers (UID formatting, ATQA/SAK technology detection).
The mental model is a pipeline of bytes. Build individual records with
encode_text_record / encode_uri_record, wrap them with
encode_ndef_message, and write them to a tag; on the way back, split a message
with decode_ndef_message and expand each record's payload with
decode_text_payload / decode_uri_payload. For smart-card I/O the apdu_*
helpers emit ready-to-transmit command frames, while parse_uid, tag_info,
and detect_technology turn the raw bytes a reader reports into readable
summaries.
Common patterns
Build a complete NDEF message and turn it into a writable APDU command:
use plugin nfc::{encode_uri_record, encode_ndef_message, apdu_write}
let record = encode_uri_record("https://zolo-lang.dev")
let message = encode_ndef_message(#{1: record})
let cmd = apdu_write(0, message)
print("write command ready ({message} bytes wrapped)")
Decode a message read back from a tag, branching on each record's type:
use plugin nfc::{decode_ndef_message, decode_text_payload, decode_uri_payload}
let records = decode_ndef_message(raw_bytes)
for record in records {
if record["type"] == "T" {
let t = decode_text_payload(record["payload"])
print("text [{t["language"]}]: {t["text"]}")
} else if record["type"] == "U" {
let u = decode_uri_payload(record["payload"])
print("uri: {u["uri"]}")
}
}
Identify a freshly detected tag from the bytes a reader reports:
use plugin nfc::{parse_uid, detect_technology, tag_info}
let info = tag_info(uid_bytes, atqa_bytes, 0x08)
print("UID {info["uid"]} is a {info["tag_type"]}")
print("speaks {detect_technology(atqa_bytes, 0x08)}")
Encode an NDEF Text Record (TNF=0x01)
Encodes a UTF-8 text string into an NDEF Text Record byte sequence (TNF=0x01, type="T"). The status byte encodes the language code length; the record is a complete short record with MB and ME flags set.
use plugin nfc::{encode_text_record}
let record = encode_text_record("Hello NFC", "en")
print("record bytes: {record}")
The language code drives the status byte, so a non-English tag just changes the second argument before wrapping into a message:
use plugin nfc::{encode_text_record, encode_ndef_message}
let pt = encode_text_record("Olá mundo", "pt")
let message = encode_ndef_message(#{1: pt})
print("Portuguese text record wrapped")
Encode an NDEF URI Record with prefix abbreviation
Encodes a URI into an NDEF URI Record, automatically selecting the longest matching prefix abbreviation from the NFC Forum table (e.g., https:// becomes code 0x04). Use this before passing to encode_ndef_message.
use plugin nfc::{encode_uri_record, encode_ndef_message}
let rec = encode_uri_record("https://zolo-lang.dev")
let msg = encode_ndef_message(#{1: rec})
print("NDEF message ready to write")
Wrap NDEF records in TLV-framed message bytes
Wraps one or more raw NDEF record byte arrays in a TLV-framed NDEF Message (type 0x03, terminated by 0xFE). Adjusts the MB/ME flags on each record automatically.
use plugin nfc::{encode_text_record, encode_ndef_message}
let r1 = encode_text_record("Title", "en")
let msg = encode_ndef_message(#{1: r1})
Passing more than one record produces a multi-record message; the MB/ME flags are corrected so the first record opens and the last record closes the message:
use plugin nfc::{encode_text_record, encode_uri_record, encode_ndef_message}
let title = encode_text_record("Zolo", "en")
let link = encode_uri_record("https://zolo-lang.dev")
let msg = encode_ndef_message(#{1: title, 2: link})
print("two-record NDEF message built")
Parse raw NDEF bytes into a records table
Parses a raw NDEF message byte sequence (without TLV wrapper) into a table of records. Each record entry has keys tnf, type, and payload.
use plugin nfc::{decode_ndef_message}
let records = decode_ndef_message(raw_bytes)
let first = records[1]
print("record type: {first["type"]}")
Iterate every record to inspect the type-name fields the parser extracts:
use plugin nfc::{decode_ndef_message}
let records = decode_ndef_message(raw_bytes)
for record in records {
print("tnf={record["tnf"]} type={record["type"]}")
}
Get numeric code for a URI prefix string
Returns the numeric NFC Forum URI prefix code for a given prefix string (e.g., "https://" → 4). Returns -1 if the prefix is not in the table.
use plugin nfc::{uri_prefix_code}
let code = uri_prefix_code("https://")
print("code: {code}")
Get URI prefix string from numeric code
Returns the URI prefix string for a given NFC Forum numeric code. Returns an empty string for unknown codes.
use plugin nfc::{uri_prefix_from_code}
let prefix = uri_prefix_from_code(4)
print("prefix: {prefix}")
Build ISO 7816-4 SELECT APDU for an AID
Builds an ISO 7816-4 SELECT command APDU for the given AID (Application Identifier) specified as a hex string. Use when initiating communication with a smart card applet.
use plugin nfc::{apdu_select}
let cmd = apdu_select("D2760000850101")
print("SELECT APDU ready")
Build ISO 7816-4 READ BINARY APDU
Builds an ISO 7816-4 READ BINARY APDU to read length bytes starting at offset from the currently selected file or applet.
use plugin nfc::{apdu_read}
let cmd = apdu_read(0, 32)
Walk a file in fixed-size pages by advancing the offset between reads:
use plugin nfc::{apdu_read}
let page = 0
while page < 4 {
let cmd = apdu_read(page * 16, 16)
print("read page {page}")
page = page + 1
}
Build ISO 7816-4 UPDATE BINARY APDU
Builds an ISO 7816-4 UPDATE BINARY APDU to write data bytes at the given offset. The data must be at most 255 bytes.
use plugin nfc::{apdu_write, encode_ndef_message, encode_text_record}
let msg = encode_ndef_message(#{1: encode_text_record("hello", "en")})
let cmd = apdu_write(0, msg)
Format raw UID bytes as colon-separated hex
Converts raw NFC tag UID bytes into a human-readable colon-separated uppercase hex string (e.g., "04:A3:2B:1C").
use plugin nfc::{parse_uid}
let uid_str = parse_uid(uid_bytes)
print("UID: {uid_str}")
Build a tag info summary from UID/ATQA/SAK
Builds a summary table for an NFC-A tag from its UID, ATQA, and SAK bytes. Returns {uid, atqa, sak, tag_type, uid_length} where tag_type identifies common chip families (MIFARE Ultralight, Classic, DESFire, etc.).
use plugin nfc::{tag_info}
let info = tag_info(uid_bytes, atqa_bytes, 0x08)
print("tag: {info["tag_type"]}")
print("uid: {info["uid"]}")
Detect NFC technology from ATQA and SAK
Determines the NFC communication technology (e.g., "NFC-A (ISO 14443-4)") from the ATQA presence and SAK bit flags. Useful for routing to the correct protocol handler.
use plugin nfc::{detect_technology}
let tech = detect_technology(atqa_bytes, 0x20)
print("technology: {tech}")
Decode an NDEF Text Record payload
Decodes an NDEF Text Record payload bytes into {language, text, encoding}. Useful after extracting the payload field from decode_ndef_message.
use plugin nfc::{decode_ndef_message, decode_text_payload}
let records = decode_ndef_message(raw_bytes)
let decoded = decode_text_payload(records[1]["payload"])
print("{decoded["language"]}: {decoded["text"]}")
Decode an NDEF URI Record payload
Decodes an NDEF URI Record payload bytes into {prefix, uri}, expanding the prefix abbreviation code into the full prefix string and reconstructing the complete URI.
use plugin nfc::{decode_uri_payload}
let result = decode_uri_payload(payload_bytes)
print("URI: {result["uri"]}")
Round-trips with encode_uri_record: the abbreviation code is expanded back into
the original prefix, so the reconstructed URI matches what you encoded:
use plugin nfc::{encode_uri_record, decode_ndef_message, decode_uri_payload}
let record = encode_uri_record("https://www.example.com")
let parsed = decode_ndef_message(record)
let result = decode_uri_payload(parsed[1]["payload"])
print("prefix: {result["prefix"]}")
print("uri: {result["uri"]}")