ndef

package
v0.577.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 6, 2026 License: AGPL-3.0 Imports: 7 Imported by: 0

Documentation

Overview

Package ndef decodes NFC Data Exchange Format messages — the payload format every NDEF-formatted NFC tag stores. Pure offline parser; no transport, no hardware.

Wrap-vs-native judgement: NDEF is a fully open NFC Forum specification (NDEF 1.0). The walker is a recursive descent over record headers + payloads with a well-known type catalog (URI prefix table, Text language code, Smart Poster nesting). Wrapping a FAP for this would add an SD-card install step + a firmware-fork dependency for a pure parser. We implement natively so operators can paste an NFC dump (or just the NDEF bytes pulled out of one) and decode every record without the tag present.

What this package covers:

  • NDEF message walker (multi-record messages, chunked records reassembled, short and long record headers, ID-length present / absent)
  • Header bit decode (MB / ME / CF / SR / IL flags + TNF)
  • Well-known type decoders: URI (with the 36-entry NFC Forum prefix table), Text (UTF-8 / UTF-16 with language code), Smart Poster (recursive nested message)
  • Connection Handover records: Handover Select / Request (Hs / Hr — version + recursive nested message), Alternative Carrier (ac — power state + carrier/auxiliary references), Collision Resolution (cr), Error (err). The nested message is recursed so the referenced Wi-Fi WSC / Bluetooth OOB carrier records are decoded in place — the full tap-to-pair / tap-to-connect tree
  • MIME-type pass-through (TNF=2) with MIME-type field + raw payload; the application/vnd.wfa.wsc Wi-Fi credential payload ("tap-to-connect" tag) is decoded via internal/wsc, and the application/vnd.bluetooth.{ep,le}.oob pairing records ("tap-to-pair" tag) via internal/btoob
  • External-type pass-through (TNF=4) with vendor:name field
  • raw payload
  • Empty / Absolute URI / Unknown / Unchanged record kinds

What this package does NOT cover (deliberately out of scope):

  • Tag-format wrappers (NTAG header / Type 2 TLV / Type 4 CC-file walker) — operators bring the bare NDEF bytes; wrapper parsing is a separate concern
  • Signature record (TNF=1 Sig) crypto verification — public keys live out-of-band
  • Re-encode — happy to add if a caller materialises

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Encode added in v0.380.0

func Encode(records []EncodeRecord) ([]byte, error)

Encode builds the raw bytes of an NDEF message from a list of records — the inverse of DecodeBytes. The first record gets MB (Message Begin), the last gets ME (Message End); each uses a short-record length when its payload is < 256 bytes. Supports the highest-runner record types — URI, Text, and Smart Poster (well-known); MIME media-type records; and External records (TNF 0x04, e.g. an Android Application Record "android.com:pkg") — all round-trip-verified against Decode.

Wrap-vs-native judgement

Native, and the inverse of the existing parser. The NDEF record layout + the URI Identifier Code table are public (NFC Forum NDEF + RTD specs); encoding is pure byte assembly + a prefix-abbreviation lookup — no crypto, no hardware. It produces the bytes an operator writes to an NFC tag (e.g. a spoofed URI or text record); generation only, no tag write/TX, so it is Low risk like the parser. Correctness is verifiable two ways: round-trip against Decode and hand-computed record bytes.

Deliberately deferred

Chunked records and the Empty / Unknown / Absolute-URI / Unchanged TNFs — the supported set covers the overwhelming majority of tag-writing use; the rest can be added when there's a verified need. ID fields are omitted (IL=0).

Types

type EncodeRecord added in v0.380.0

type EncodeRecord struct {
	Kind   string `json:"kind"`
	URI    string `json:"uri,omitempty"`
	Text   string `json:"text,omitempty"`
	Lang   string `json:"lang,omitempty"`
	Action string `json:"action,omitempty"`
	// Type is the record type for "mime" (a MIME media type, e.g.
	// "text/vcard") and "external" (a "domain:type" name, e.g.
	// "android.com:pkg" for an Android Application Record).
	Type string `json:"type,omitempty"`
	// Payload is the raw payload as hex for "mime"/"external". When empty,
	// Text (UTF-8) is used as the payload — the common case (vCard text, an
	// AAR package name).
	Payload string `json:"payload,omitempty"`
}

EncodeRecord describes one NDEF record to build. Kind selects the well-known type: "uri" (URI record, type "U"), "text" (Text record, type "T"), or "smartposter" (Smart Poster, type "Sp" — a record whose payload is a nested NDEF message of a URI + optional title Text + optional Action). For text/smartposter, Lang defaults to "en". For smartposter: URI is the target, Text is the optional title, Action is "" / "do" (launch) / "save" / "edit".

type Message

type Message struct {
	// Records is the list of decoded records in order.
	Records []Record `json:"records"`
	// Count is len(Records).
	Count int `json:"count"`
	// Warnings collects non-fatal observations (chunk
	// re-assembly notes, unrecognised well-known types, etc.).
	Warnings []string `json:"warnings,omitempty"`
}

Message is the top-level parsed NDEF message.

func Decode

func Decode(hexBlob string) (Message, error)

Decode parses a hex-encoded NDEF message. Tolerates ':' / '-' / '_' / whitespace separators.

func DecodeBytes

func DecodeBytes(b []byte) (Message, error)

DecodeBytes is the byte-slice variant for callers that already have raw NDEF bytes (e.g. from a tag-format walker).

type Record

type Record struct {
	// Header is the raw header byte for callers that want bit-
	// level access.
	Header int `json:"header"`
	// MB / ME / CF / SR / IL are the documented header flags.
	MessageBegin bool `json:"message_begin"`
	MessageEnd   bool `json:"message_end"`
	ChunkFlag    bool `json:"chunk_flag"`
	ShortRecord  bool `json:"short_record"`
	IDLength     bool `json:"id_length_present"`
	// TNF is the 3-bit Type Name Format field as an enumerated
	// int (0-7) — see TNF constants.
	TNF     int    `json:"tnf"`
	TNFName string `json:"tnf_name"`
	// Type is the bytes from the Type field. Rendered as UTF-8
	// when valid (well-known types use ASCII like "U", "T",
	// "Sp").
	Type string `json:"type"`
	// TypeHex is the operator-facing hex rendering — useful when
	// the type bytes aren't ASCII.
	TypeHex string `json:"type_hex,omitempty"`
	// ID is the bytes from the ID field. Empty when IL=0.
	ID string `json:"id,omitempty"`
	// Payload is the raw payload bytes as hex.
	PayloadHex string `json:"payload_hex"`
	// Decoded is the per-type field decode. Populated for
	// well-known types (URI / Text / Smart Poster) and MIME /
	// External / Absolute URI. nil for Empty / Unknown /
	// Unchanged.
	Decoded map[string]any `json:"decoded,omitempty"`
}

Record is one decoded NDEF record.

type TNF

type TNF int

TNF is the 3-bit Type Name Format field in the record header.

const (
	// TNFEmpty — record carries no type / id / payload.
	TNFEmpty TNF = 0
	// TNFWellKnown — type is from the NFC Forum well-known set
	// (URI / Text / Smart Poster / Handover / etc.).
	TNFWellKnown TNF = 1
	// TNFMIME — type is a media-type per RFC 2046 ("text/plain",
	// "application/json", etc.).
	TNFMIME TNF = 2
	// TNFAbsoluteURI — type field is the full URI (rare).
	TNFAbsoluteURI TNF = 3
	// TNFExternal — type is a vendor:name external type.
	TNFExternal TNF = 4
	// TNFUnknown — payload semantics are unknown.
	TNFUnknown TNF = 5
	// TNFUnchanged — middle/last chunk of a chunked record.
	TNFUnchanged TNF = 6
	// TNFReserved — reserved by the spec.
	TNFReserved TNF = 7
)

func (TNF) String

func (t TNF) String() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL