nfc

package
v1.1.3 Latest Latest
Warning

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

Go to latest
Published: Aug 15, 2026 License: MIT Imports: 13 Imported by: 0

README

NFC Package

A Go package providing a unified, high-level abstraction for NFC operations across multiple card types. Built on top of PC/SC via ebfe/scard.

Overview

The nfc package provides a modular architecture for working with NFC tags, abstracting away the complexity of different card types and protocols:

  • Unified API: Single interface for all supported card types
  • High-level abstractions: io.Reader/io.Writer interface for NDEF data
  • Low-level access: Direct protocol access when needed
  • Device management: Automatic device discovery and lifecycle management

Architecture

┌─────────────────────────────────────────┐
│          Application Layer              │
│      (WebSocket Server, CLI, etc)       │
└─────────────────────────────────────────┘
                   │
                   ↓
┌─────────────────────────────────────────┐
│          High-Level API                 │
│   Card (io.Reader/Writer interface)     │
│   Message (NDEF encoding/decoding)      │
└─────────────────────────────────────────┘
                   │
                   ↓
┌─────────────────────────────────────────┐
│          Tag Abstraction Layer          │
│  Tag interface (unified operations)     │
└─────────────────────────────────────────┘
                   │
       ┌───────────┴───────────┬──────────┐
       ↓                       ↓          ↓
┌──────────────┐  ┌─────────────────┐  ┌────────────┐
│ ClassicTag   │  │   DESFireTag    │  │ ISO14443   │
│ UltralightTag│  │   (others...)   │  │   Type4    │
└──────────────┘  └─────────────────┘  └────────────┘
                   │
                   ↓
┌─────────────────────────────────────────┐
│        Device Management Layer          │
│   Manager, Device (connection mgmt)     │
└─────────────────────────────────────────┘
                   │
                   ↓
┌─────────────────────────────────────────┐
│         PC/SC API (ebfe/scard)          │
│     System: pcsclite / WinSCard         │
└─────────────────────────────────────────┘

Core Components

Manager

Device discovery and connection management.

// Create a manager
manager := nfc.NewManager()

// List available NFC readers
devices, err := manager.ListDevices()

// Open a device
device, err := manager.OpenDevice(devices[0])
defer device.Close()

File: manager.go, manager_pcsc.go

Device

Represents a connected NFC reader device.

// Poll for tags
tags, err := device.GetTags()

// Get device information
connStr := device.Connection()

// Close device
device.Close()

Files: device.go, device_pcsc.go

Tag

Low-level hardware protocol interface. Provides direct access to tag operations.

type Tag interface {
    UID() string
    Type() string
    ReadData() ([]byte, error)
    WriteData(data []byte) error
    Connect() error
    Disconnect() error
    IsWritable() (bool, error)
}

Files:

  • tag.go - Interface definition
  • tag_classic.go - MIFARE Classic implementation
  • tag_desfire.go - MIFARE DESFire implementation
  • tag_ultralight.go - MIFARE Ultralight implementation
  • tag_iso14443.go - ISO14443-4 Type 4 implementation
Card

High-level abstraction implementing io.Reader, io.Writer, and io.Closer.

// Create card from tag
card := nfc.NewCard(tag)

// Read NDEF data
data, err := io.ReadAll(card)

// Write NDEF data
io.WriteString(card, "Hello, NFC!")
card.Close()

// Read as structured message
msg, err := card.ReadMessage()

File: card.go

Message

NDEF message encoding and decoding.

// Decode NDEF message
msg, err := nfc.DecodeNDEF(data)

// Access text records
text, err := msg.GetText()

// Create new text message
msg := nfc.NewNDEFMessage()
msg.AddTextRecord("Hello!", "en")
data, _ := msg.Encode()

Files: message.go, ndef.go

Supported Card Types

MIFARE Classic (1K/4K)

Sector and block-based memory structure with key authentication.

if classic, ok := tag.(*nfc.ClassicTag); ok {
    // Read specific sector/block
    data, err := classic.Read(sector, block, key, keyType)

    // Write to sector/block
    err = classic.Write(sector, block, data, key, keyType)
}

Features:

  • Sector/block read/write
  • Key A/B authentication
  • NDEF formatting and read/write
  • Auto key discovery for reading

File: tag_classic.go

MIFARE DESFire (EV1/EV2/EV3)

Application and file-based structure with advanced security.

if desfire, ok := tag.(*nfc.DESFireTag); ok {
    // List applications
    apps, err := desfire.ApplicationIds()

    // Select application
    err = desfire.SelectApplication(appId)

    // Read/write files
    data, err := desfire.ReadData(fileNo)
}

Features:

  • Application management
  • File-based data storage
  • NDEF read/write support
  • Secure authentication

File: tag_desfire.go

MIFARE Ultralight (including Ultralight C)

Page-based memory with simple read/write operations.

if ultralight, ok := tag.(*nfc.UltralightTag); ok {
    // Read page
    data, err := ultralight.ReadPage(page)

    // Write page
    err = ultralight.WritePage(page, data)
}

Features:

  • Page-based read/write
  • NDEF read/write support
  • Compact memory layout

File: tag_ultralight.go

ISO14443-4 Type 4A/B

Standard ISO14443 Type 4 tags with NDEF support.

// Works automatically through Card interface
card := nfc.NewCard(tag)
data, _ := io.ReadAll(card)

Features:

  • Standard NDEF operations
  • Direct APDU access via Transceive()

File: tag_iso14443.go

Usage Examples

Basic Read/Write
package main

import (
    "fmt"
    "io"
    "github.com/dotside-studios/davi-nfc-agent/nfc"
)

func main() {
    // Initialize manager and device
    manager := nfc.NewManager()
    devices, _ := manager.ListDevices()
    device, _ := manager.OpenDevice(devices[0])
    defer device.Close()

    // Get tags
    tags, _ := device.GetTags()
    if len(tags) == 0 {
        fmt.Println("No tags found")
        return
    }

    // Read from card
    card := nfc.NewCard(tags[0])
    data, _ := io.ReadAll(card)
    fmt.Printf("Read: %s\n", string(data))

    // Write to card
    card.Reset()
    io.WriteString(card, "Hello, NFC!")
    card.Close()
}
Working with NDEF Messages
// Read structured NDEF message
card := nfc.NewCard(tag)
msg, err := card.ReadMessage()

switch m := msg.(type) {
case *nfc.NDEFMessage:
    // Access text records
    text, _ := m.GetText()
    fmt.Printf("Text: %s\n", text)

    // Access URI records
    uri, _ := m.GetURI()
    fmt.Printf("URI: %s\n", uri)

case *nfc.TextMessage:
    // Raw bytes (non-NDEF)
    fmt.Printf("Raw: %x\n", m.Data)
}

// Write NDEF message
ndefMsg := nfc.NewNDEFMessage()
ndefMsg.AddTextRecord("Hello World", "en")
err = card.WriteMessage(ndefMsg)
Card-Specific Operations
// Get underlying tag for advanced operations
tag := card.GetUnderlyingTag()

// MIFARE Classic: sector access
if classic, ok := tag.(*nfc.ClassicTag); ok {
    key := []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
    data, _ := classic.Read(1, 0, key, nfc.KeyTypeA)
}

// DESFire: application management
if desfire, ok := tag.(*nfc.DESFireTag); ok {
    apps, _ := desfire.ApplicationIds()
    for _, app := range apps {
        fmt.Printf("App: %06X\n", app)
    }
}

// Ultralight: page operations
if ultralight, ok := tag.(*nfc.UltralightTag); ok {
    page4, _ := ultralight.ReadPage(4)
    fmt.Printf("Page 4: %x\n", page4)
}
Continuous Polling
manager := nfc.NewManager()
devices, _ := manager.ListDevices()
device, _ := manager.OpenDevice(devices[0])
defer device.Close()

for {
    tags, err := device.GetTags()
    if err != nil {
        time.Sleep(100 * time.Millisecond)
        continue
    }

    for _, tag := range tags {
        card := nfc.NewCard(tag)
        data, _ := io.ReadAll(card)
        fmt.Printf("UID: %s, Data: %s\n", card.UID, string(data))
    }

    time.Sleep(500 * time.Millisecond)
}

Testing

The package includes comprehensive mocks for testing without physical hardware:

// Mock manager
manager := nfc.NewMockManager()
device := nfc.NewMockDevice()
tag := nfc.NewMockTag("04A1B2C3D4E5F6", "MIFARE Classic 1K")

// Configure mock behavior
tag.SetReadData([]byte("test data"))

// Use in tests
card := nfc.NewCard(tag)
data, _ := io.ReadAll(card)

Files: tag_mock.go, device_mock.go, manager_mock.go

Key Design Decisions

Why separate Tag and Card?
  • Tag: Low-level hardware protocol interface

    • Direct access to card-specific features
    • Minimal abstraction over libfreefare
    • Type assertions for card-specific operations
  • Card: High-level data interface

    • Standard Go io.Reader/io.Writer semantics
    • NDEF-focused operations
    • Simplified API for common use cases
NDEF vs Raw Data

The package automatically handles NDEF parsing:

msg, err := card.ReadMessage()

// Returns NDEFMessage if NDEF-formatted
// Returns TextMessage (raw bytes) if not NDEF

This allows applications to handle both formatted and unformatted tags gracefully.

Connection Management

The package handles connection lifecycle automatically:

  • device.GetTags() performs polling and returns ready-to-use tags
  • Tag.Connect() / Disconnect() exist for interface completeness but are not required by the framework; tags are usable as soon as GetTags() returns them (custom tags can inherit these as no-ops from nfc.BaseTag)
  • Clean up with device.Close()

Thread Safety

The package is not thread-safe. If you need concurrent access:

  • Use one Manager per goroutine, OR
  • Protect Device and Tag operations with mutexes

Dependencies

  • github.com/ebfe/scard: Go bindings for PC/SC
  • PC/SC runtime (built into macOS/Windows, pcsclite on Linux)

Files Reference

File Purpose
manager.go Manager interface
manager_pcsc.go PC/SC manager implementation
device.go Device interface
device_pcsc.go PC/SC device implementation
tag.go Tag interface and base types
tag_base.go Shared base tag struct
tag_classic.go MIFARE Classic implementation
tag_desfire.go MIFARE DESFire implementation
tag_ultralight.go MIFARE Ultralight implementation
tag_ntag.go NTAG implementation
tag_iso14443.go ISO14443-4 Type 4 implementation
tagdetect.go ATR/UID-based tag detection
apdu.go APDU command construction
tlv.go TLV encode/decode utilities
card.go High-level Card abstraction
message.go Message interface and types
ndef.go NDEF encoding/decoding
constants.go Card type and key constants
keys.go Key management utilities
cache.go Tag caching for debouncing
capabilities.go Tag capability detection
errors.go Error types and handling
*_test.go Unit tests
*_mock.go Test mocks

Contributing

When adding support for new card types:

  1. Implement the Tag interface
  2. Add NDEF read/write if supported
  3. Include card-specific methods as needed
  4. Add unit tests with mocks
  5. Update this documentation

License

MIT License - See main repository LICENSE file

Documentation

Index

Constants

View Source
const (
	SW1Success     = 0x90
	SW2Success     = 0x00
	SW1MoreData    = 0x61 // More data available
	SW1WrongLength = 0x6C // Wrong Le field
)

APDU status words

View Source
const (
	CLAStandard   = 0x00 // Standard ISO7816-4
	CLAPCSC       = 0xFF // PC/SC pseudo-APDU (reader commands)
	CLADESFire    = 0x90 // DESFire native command wrapper
	CLAProprietry = 0x80 // Proprietary commands
)

Common APDU command classes

View Source
const (
	INSGetUID     = 0xCA // Get UID
	INSLoadKey    = 0x82 // Load authentication key
	INSAuth       = 0x86 // General authenticate
	INSReadBinary = 0xB0 // Read binary
	INSUpdateBin  = 0xD6 // Update binary
	INSDirectCmd  = 0x00 // Direct transmit (for wrapped commands)
	INSSelectFile = 0xA4 // Select file
)

PC/SC pseudo-APDU instructions

View Source
const (
	MIFAREKeyA = 0x60
	MIFAREKeyB = 0x61
)

MIFARE key types

View Source
const (
	DFCmdSelectApplication = 0x5A
	DFCmdGetApplicationIDs = 0x6A
	DFCmdGetFileIDs        = 0x6F
	DFCmdReadData          = 0xBD
	DFCmdWriteData         = 0x3D
	DFCmdAuthenticate      = 0x0A // Legacy DES auth
	DFCmdAuthenticateISO   = 0x1A // 3DES auth
	DFCmdAuthenticateAES   = 0xAA // AES auth
	DFCmdGetVersion        = 0x60
	DFCmdAdditionalFrame   = 0xAF
)

DESFire native command codes

View Source
const (
	MaxRetries          = 5
	BaseDelay           = 500 * time.Millisecond
	MaxReconnectTries   = 10
	ReconnectDelay      = time.Second * 2
	DeviceCheckInterval = time.Second * 2 // Interval to check for new devices
	DeviceEnumRetries   = 3               // Number of retries for device enumeration
)

Constants for NFC operations

View Source
const (
	ManagerTypeHardware   = "hardware"
	ManagerTypeSmartphone = "smartphone"
)

Manager type constants for identifying different manager implementations

View Source
const (
	CardTypeMifareClassic1K  = "MIFARE Classic 1K"
	CardTypeMifareClassic4K  = "MIFARE Classic 4K"
	CardTypeMifareUltralight = "MIFARE Ultralight"
	CardTypeNtag213          = "NTAG213"
	CardTypeNtag215          = "NTAG215"
	CardTypeNtag216          = "NTAG216"
	CardTypeDesfire          = "DESFire"
	CardTypeType4            = "Type4"
)

Card type constants for card type identification and filtering

View Source
const (
	// KeyTypeA is used for MIFARE Classic Key A authentication
	KeyTypeA = 0x60
	// KeyTypeB is used for MIFARE Classic Key B authentication
	KeyTypeB = 0x61
)

MIFARE Classic key type constants for authentication

View Source
const (
	DefaultPollingInterval      = 100 * time.Millisecond
	DeviceIdleCheckInterval     = 200 * time.Millisecond
	WriteCheckInterval          = 50 * time.Millisecond
	CardCheckTickerInterval     = 250 * time.Millisecond
	DeviceResetWaitTime         = 3 * time.Second
	DeviceErrorCooldownPeriod   = 10 * time.Second
	MaxRetriesCooldownPeriod    = 30 * time.Second
	PostErrorPauseTime          = 1 * time.Second
	UnhandledErrorRetryInterval = 1 * time.Second
)

Polling intervals

View Source
const (
	// DefaultMaxWriteAttempts is the number of write+verify attempts made on a
	// single write operation before giving up, when WriteOptions.MaxWriteAttempts
	// is not set.
	DefaultMaxWriteAttempts = 3
	// WriteRetryBackoff is the base delay between write retries. The delay grows
	// linearly with the attempt number (backoff, 2*backoff, ...).
	WriteRetryBackoff = 50 * time.Millisecond
)

Write reliability defaults.

View Source
const (
	TLVNull        = 0x00 // Null TLV
	TLVLockCtrl    = 0x01 // Lock Control TLV
	TLVMemCtrl     = 0x02 // Memory Control TLV
	TLVNDEF        = 0x03 // NDEF Message TLV
	TLVProprietary = 0xFD // Proprietary TLV
	TLVTerminator  = 0xFE // Terminator TLV
)

TLV types for NDEF

Variables

View Source
var (
	// ErrTimeout indicates a timeout occurred during device communication
	ErrTimeout = errors.New("device operation timed out")

	// ErrDeviceClosed indicates the device connection was closed
	ErrDeviceClosed = errors.New("device closed")

	// ErrIO indicates an input/output error with the device
	ErrIO = errors.New("device I/O error")

	// ErrDeviceConfig indicates a device configuration error
	ErrDeviceConfig = errors.New("device configuration error")

	// ErrCooldownRequired indicates the device needs a cooldown period
	ErrCooldownRequired = errors.New("device cooldown required")

	// ErrACR122Specific indicates an ACR122-specific error requiring cooldown
	ErrACR122Specific = errors.New("ACR122 device error")
)

Sentinel errors for device operations

View Source
var (
	// KeyDefault is the factory default key (all 0xFF)
	KeyDefault = []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
	// KeyNFCForum is the NFC Forum public key for NDEF
	KeyNFCForum = []byte{0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7}
	// KeyMAD is the MAD (MIFARE Application Directory) key
	KeyMAD = []byte{0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5}
)

Common MIFARE Classic keys

View Source
var DefaultKeyA = [6]byte{0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5}

DefaultKeyA is the MIFARE Application default key A.

View Source
var DefaultKeyB = [6]byte{0xd3, 0xf7, 0xd3, 0xf7, 0xd3, 0xf7}

DefaultKeyB is the NFC Forum default key B.

View Source
var DefaultKeys = [][6]byte{
	{0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
	{0xd3, 0xf7, 0xd3, 0xf7, 0xd3, 0xf7},
	{0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5},
	{0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5},
	{0x4d, 0x3a, 0x99, 0xc3, 0x51, 0xdd},
	{0x1a, 0x98, 0x2c, 0x7e, 0x45, 0x9a},
	{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff},
	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
}

DefaultKeys is a list of common MIFARE keys to try for authentication.

View Source
var FactoryKey = [6]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}

FactoryKey is the MIFARE Classic factory default key.

View Source
var PublicKey = DefaultKeyB

PublicKey is a common public key for NDEF applications, often the same as DefaultKeyB.

Functions

func AssertCapabilitiesConsistent added in v1.0.3

func AssertCapabilitiesConsistent(tag Tag) error

AssertCapabilitiesConsistent verifies that a tag's declared TagCapabilities agree with the tag's query methods, returning a descriptive error on the first mismatch (nil if consistent). It is intended for implementers' unit tests to catch capability drift between Capabilities() and behavior.

It performs ONLY non-mutating (read-only) checks and never calls WriteData, Transceive, or MakeReadOnly, since those may perform real or irreversible I/O on hardware. Specifically it checks:

  • Capabilities().CanLock matches CanMakeReadOnly()
  • When Capabilities().IsReadOnly is true, IsWritable() reports false

Supported write/transceive/lock behavior cannot be auto-verified without device I/O; cover those directly in your own tests.

func BuildAPDU

func BuildAPDU(cla, ins, p1, p2 byte, data []byte, le *byte) []byte

BuildAPDU constructs an APDU command

func BytesToHex

func BytesToHex(data []byte) string

BytesToHex converts bytes to uppercase hex string

func BytesToUint16

func BytesToUint16(b []byte) uint16

BytesToUint16 converts big-endian bytes to uint16

func CanTagLock

func CanTagLock(tag Tag) bool

CanTagLock checks if a tag can be made read-only.

func CanTagRead

func CanTagRead(tag Tag) bool

CanTagRead checks if a tag supports read operations.

func CanTagTransceive

func CanTagTransceive(tag Tag) bool

CanTagTransceive checks if a tag supports raw transceive operations.

func CanTagWrite

func CanTagWrite(tag Tag) bool

CanTagWrite checks if a tag supports write operations.

func DESFireAdditionalFrameAPDU

func DESFireAdditionalFrameAPDU(data []byte) []byte

DESFireAdditionalFrameAPDU returns APDU for sending additional frame data

func DESFireAuthAPDU

func DESFireAuthAPDU(keyNo byte, authType byte) []byte

DESFireAuthAPDU returns APDU for authentication

func DESFireGetAppIDsAPDU

func DESFireGetAppIDsAPDU() []byte

DESFireGetAppIDsAPDU returns APDU for listing application IDs

func DESFireGetFileIDsAPDU

func DESFireGetFileIDsAPDU() []byte

DESFireGetFileIDsAPDU returns APDU for listing file IDs

func DESFireReadDataAPDU

func DESFireReadDataAPDU(fileNo byte, offset uint32, length uint32) []byte

DESFireReadDataAPDU returns APDU for reading file data

func DESFireSelectAppAPDU

func DESFireSelectAppAPDU(aid []byte) []byte

DESFireSelectAppAPDU returns APDU for selecting a DESFire application

func DESFireWrapAPDU

func DESFireWrapAPDU(cmd byte, data []byte) []byte

DESFireWrapAPDU wraps a DESFire native command in ISO7816 APDU

func DESFireWriteDataAPDU

func DESFireWriteDataAPDU(fileNo byte, offset uint32, writeData []byte) []byte

DESFireWriteDataAPDU returns APDU for writing file data

func DirectTransmitAPDU

func DirectTransmitAPDU(cmd []byte) []byte

DirectTransmitAPDU wraps a command for direct transmission to the card Used for native commands (e.g., Ultralight READ, WRITE)

func EncodeNdefMessageWithTextRecord

func EncodeNdefMessageWithTextRecord(text string, langCodeStr string) []byte

EncodeNdefMessageWithTextRecord creates an NDEF message containing a single Text Record.

func GetAllCardTypes

func GetAllCardTypes() []string

GetAllCardTypes returns all supported card type constants

func GetLengthFieldSize

func GetLengthFieldSize(length int) int

GetLengthFieldSize returns the size of the TLV length field.

func GetUIDAPDU

func GetUIDAPDU() []byte

GetUIDAPDU returns the APDU for getting the card UID

func GetVersionAPDU

func GetVersionAPDU() []byte

GetVersionAPDU returns the APDU for getting NTAG/Ultralight version This is wrapped in a direct transmit command

func HexToBytes

func HexToBytes(hex string) ([]byte, error)

HexToBytes converts a hex string to bytes

func IsACR122Error

func IsACR122Error(err error) bool

func IsAuthError

func IsAuthError(err error) bool

IsAuthError checks if an error indicates authentication failure.

func IsCapacityExceededError added in v1.0.3

func IsCapacityExceededError(err error) bool

IsCapacityExceededError checks if an error indicates the data exceeded the tag's usable NDEF capacity.

func IsCardRemovedError

func IsCardRemovedError(err error) bool

IsCardRemovedError checks if an error indicates the card was removed during operation. This requires device reconnection to detect new cards. All card removal errors are created via NewCardRemovedError() at the device layer.

func IsDeviceClosedError

func IsDeviceClosedError(err error) bool

func IsDeviceConfigError

func IsDeviceConfigError(err error) bool

func IsIOError

func IsIOError(err error) bool

func IsNoCardError

func IsNoCardError(err error) bool

IsNoCardError checks if an error indicates no card is present in the reader. This is a normal condition and should not be logged as a device error.

func IsNotSupportedError

func IsNotSupportedError(err error) bool

IsNotSupportedError checks if an error indicates an unsupported operation.

func IsReadOnlyError added in v1.0.3

func IsReadOnlyError(err error) bool

IsReadOnlyError checks if an error indicates the tag is read-only.

func IsRemoteDevice added in v1.1.3

func IsRemoteDevice(m Manager, devicePath string) bool

IsRemoteDevice reports whether a device path names a phone rather than a reader. A pinned path that does can never be opened as a reader, so it is worth recognizing before it becomes a connection retried forever.

func IsTagRemovedError

func IsTagRemovedError(err error) bool

IsTagRemovedError checks if an error indicates the tag was removed.

func IsTimeoutError

func IsTimeoutError(err error) bool

func IsUnsupportedTagError

func IsUnsupportedTagError(err error) bool

IsUnsupportedTagError checks if an error indicates the tag type is not supported.

func IsWriteError added in v1.0.3

func IsWriteError(err error) bool

IsWriteError checks if an error indicates a write failure.

func ListReaders added in v1.1.3

func ListReaders(m Manager) ([]string, error)

ListReaders returns the devices that can serve as this agent's reader, falling back to every device a manager knows for one that draws no distinction.

func LoadKeyAPDU

func LoadKeyAPDU(keySlot byte, key []byte) []byte

LoadKeyAPDU returns the APDU for loading a key into reader memory keySlot: 0x00-0x1F for volatile, 0x20+ for non-volatile

func MIFAREAuthAPDU

func MIFAREAuthAPDU(block byte, keyType byte, keySlot byte) []byte

MIFAREAuthAPDU returns the APDU for MIFARE authentication block: block number to authenticate keyType: MIFAREKeyA (0x60) or MIFAREKeyB (0x61) keySlot: slot where key was loaded

func MakeTextRecordPayload

func MakeTextRecordPayload(text string, langCodeStr string) []byte

MakeTextRecordPayload creates an NDEF Text Record payload with the specified text and language code.

func MakeURIRecordPayload

func MakeURIRecordPayload(uri string) []byte

MakeURIRecordPayload creates the payload for an NDEF URI record. It selects the longest matching NFC Forum abbreviation prefix to minimize the bytes written to the tag (URI capacity is scarce on small tags).

func NewCardRemovedError

func NewCardRemovedError(cause error) error

NewCardRemovedError creates a card removed error.

func NewUnsupportedTagError

func NewUnsupportedTagError(atr string) error

NewUnsupportedTagError creates an unsupported tag error.

func ParseNdefMessageForTextRecord

func ParseNdefMessageForTextRecord(ndefMessage []byte) (string, error)

ParseNdefMessageForTextRecord parses an NDEF message and returns the text from the first Text Record. This is a convenience function that uses the record-based parsing internally.

func ParseNdefMessageForURIRecord

func ParseNdefMessageForURIRecord(ndefMessage []byte) (string, error)

ParseNdefMessageForURIRecord parses an NDEF message and returns the URI from the first URI Record. This is a convenience function that uses the record-based parsing internally.

func ParseTLVBlock

func ParseTLVBlock(data []byte) map[byte][]byte

ParseTLVBlock parses all TLVs in a block and returns a map of type -> value Useful for parsing Capability Container TLVs

func ReadBinaryAPDU

func ReadBinaryAPDU(offset byte, length byte) []byte

ReadBinaryAPDU returns the APDU for reading binary data For MIFARE: block/page number in P2, length in Le

func ReadBinaryExtAPDU

func ReadBinaryExtAPDU(offset uint16, length byte) []byte

ReadBinaryExtAPDU returns an extended APDU for reading with 2-byte offset

func SelectFileAPDU

func SelectFileAPDU(fid []byte) []byte

SelectFileAPDU returns the APDU for selecting a file by ID

func SelectFileByAIDAPDU

func SelectFileByAIDAPDU(aid []byte) []byte

SelectFileByAIDAPDU returns the APDU for selecting application by AID

func TLVDecode

func TLVDecode(data []byte) (value []byte, tlvType byte)

TLVDecode decodes a TLV structure and returns the value and type It skips Null TLVs and stops at the first non-null TLV or Terminator

func TLVEncode

func TLVEncode(data []byte, tlvType byte) []byte

TLVEncode encodes data into TLV format For NDEF, use type = 0x03 (TLVNDEF) Returns: [Type][Length][Value][Terminator (0xFE)]

func TLVFindNDEF

func TLVFindNDEF(data []byte) ([]byte, bool)

TLVFindNDEF finds the NDEF Message TLV in a TLV block Returns the NDEF message data and true if found, nil and false otherwise

func TLVGetLength

func TLVGetLength(data []byte) int

TLVGetLength extracts the length from a TLV record data should start at the type byte

func TLVRecordLength

func TLVRecordLength(data []byte) (fls, fvs int)

TLVRecordLength returns the field length start offset and field value start offset relative to the start of the TLV record (including type byte) fls: offset where length field starts (1 for type byte) fvs: offset where value starts Returns (0, 0) if the TLV is malformed

func Uint16ToBytes

func Uint16ToBytes(v uint16) []byte

Uint16ToBytes converts uint16 to big-endian bytes

func UltralightReadAPDU

func UltralightReadAPDU(page byte) []byte

UltralightReadAPDU returns the native Ultralight READ command (wrapped) Reads 4 pages (16 bytes) starting from the specified page

func UltralightWriteAPDU

func UltralightWriteAPDU(page byte, data []byte) []byte

UltralightWriteAPDU returns the native Ultralight WRITE command (wrapped) Writes 4 bytes to the specified page

func UpdateBinaryAPDU

func UpdateBinaryAPDU(offset byte, data []byte) []byte

UpdateBinaryAPDU returns the APDU for writing binary data For MIFARE: block/page number in P2

func UpdateBinaryExtAPDU

func UpdateBinaryExtAPDU(offset uint16, data []byte) []byte

UpdateBinaryExtAPDU returns an extended APDU for writing with 2-byte offset

func WireError added in v1.1.0

func WireError(err error) protocol.ErrorPayload

WireError projects an error onto the wire taxonomy. An NFCError carries its code, operation, and tag through; anything else lands on UNKNOWN_ERROR, which is not retryable — an error we cannot classify is not one we should encourage a device to repeat.

Types

type APDUResponse

type APDUResponse struct {
	Data []byte
	SW1  byte
	SW2  byte
}

APDUResponse represents a parsed APDU response

func ParseAPDUResponse

func ParseAPDUResponse(raw []byte) (APDUResponse, error)

ParseAPDUResponse parses a raw response into APDUResponse

func (APDUResponse) Error

func (r APDUResponse) Error() error

Error returns an error if the response is not successful

func (APDUResponse) HasMoreData

func (r APDUResponse) HasMoreData() bool

HasMoreData returns true if more data is available (SW1=61)

func (APDUResponse) IsSuccess

func (r APDUResponse) IsSuccess() bool

IsSuccess returns true if the response indicates success (SW1=90, SW2=00)

func (APDUResponse) StatusWord

func (r APDUResponse) StatusWord() uint16

StatusWord returns the 2-byte status word as uint16

type AdvancedWriter

type AdvancedWriter interface {
	WriteDataWithOptions(data []byte, opts TagWriteOptions) error
}

AdvancedWriter is an optional interface that tags can implement to support write operations with options. If a tag implements this interface, the reader will use WriteDataWithOptions instead of WriteData when options are provided.

type BaseTag added in v1.0.3

type BaseTag struct{}

BaseTag provides default implementations of the optional Tag behaviors so custom tag types only need to implement the parts they actually support.

Embed BaseTag in your tag struct and override the methods your tag supports. The defaults are safe: connection management is a no-op, and write/transceive/lock operations report "not supported". You still must implement the universally-required identity and read methods yourself, since no sensible default exists for them:

  • UID() string
  • Type() string
  • NumericType() int
  • ReadData() ([]byte, error)

This mirrors the capability-based philosophy: advertise what you support via Capabilities() (implementing TagCapabilityProvider) and only override the methods backing those capabilities.

Example — a read-only tag needs four methods, not eleven:

type MyTag struct {
    nfc.BaseTag
    uid  string
    data []byte
}

func (t *MyTag) UID() string               { return t.uid }
func (t *MyTag) Type() string              { return "MyTag" }
func (t *MyTag) NumericType() int          { return 0 }
func (t *MyTag) ReadData() ([]byte, error) { return t.data, nil }
// Connect/Disconnect/WriteData/Transceive/IsWritable/CanMakeReadOnly/
// MakeReadOnly are inherited from BaseTag.

func (BaseTag) CanMakeReadOnly added in v1.0.3

func (BaseTag) CanMakeReadOnly() (bool, error)

func (BaseTag) Connect added in v1.0.3

func (BaseTag) Connect() error

func (BaseTag) Disconnect added in v1.0.3

func (BaseTag) Disconnect() error

func (BaseTag) IsWritable added in v1.0.3

func (BaseTag) IsWritable() (bool, error)

func (BaseTag) MakeReadOnly added in v1.0.3

func (BaseTag) MakeReadOnly() error

func (BaseTag) Transceive added in v1.0.3

func (BaseTag) Transceive(data []byte) ([]byte, error)

func (BaseTag) WriteData added in v1.0.3

func (BaseTag) WriteData(data []byte) error

type Card

type Card struct {
	// Metadata about the card
	UID          string    `json:"uid"`                    // Unique identifier of the card
	Type         string    `json:"type"`                   // Human-readable type (e.g., "MIFARE Classic 1K", "Type4")
	Technology   string    `json:"technology"`             // Technology family (e.g., "ISO14443A", "ISO14443B")
	ScannedAt    time.Time `json:"scanned_at"`             // When the card was detected
	LastAccessed time.Time `json:"last_accessed"`          // Last read/write operation time
	MessageData  Message   `json:"message_data,omitempty"` // Cached message data, if any
	// contains filtered or unexported fields
}

Card represents a detected NFC card with its metadata and provides io.Reader and io.Writer interfaces for reading and writing NDEF data.

Example usage:

card, err := reader.Scan()
if err != nil {
	log.Fatal(err)
}

// Read data
data, _ := io.ReadAll(card)
fmt.Printf("Card UID: %s, Type: %s, Data: %s\n", card.UID, card.Type, data)

// Write data
card.Reset()
io.WriteString(card, "Hello NFC!")
card.Close()

func NewCard

func NewCard(tag Tag) *Card

NewCard creates an Card from a Tag. This is an internal constructor used by the reader/manager.

func (*Card) Capabilities added in v1.0.3

func (c *Card) Capabilities() TagCapabilities

Capabilities reports what operations the card's tag supports (memory, writability, lock/password support, read-only state). When the underlying tag is available it is queried directly; otherwise capabilities are inferred from the tag type string.

func (*Card) Close

func (c *Card) Close() error

Close implements io.Closer. Writes any buffered data to the card.

func (*Card) Flush

func (c *Card) Flush() error

Flush writes the buffered data to the card immediately without closing. The buffer is cleared after a successful write.

func (*Card) GetUnderlyingTag

func (c *Card) GetUnderlyingTag() Tag

GetUnderlyingTag returns the underlying Tag for advanced operations. Use this only when you need tag-specific functionality not available through the standard io.Reader/Writer interface.

Example for MIFARE Classic specific operations:

if classicTag, ok := card.GetUnderlyingTag().(ClassicTag); ok {
	data, err := classicTag.Read(1, 0, key, keyType)
}

func (*Card) Read

func (c *Card) Read(p []byte) (n int, err error)

Read implements io.Reader. Reads NDEF message data from the card. The first call to Read() fetches the entire NDEF message from the card. Subsequent calls stream from the cached data.

Example:

data, err := io.ReadAll(card)
if err != nil {
	log.Fatal(err)
}

func (*Card) ReadMessage

func (c *Card) ReadMessage() (Message, error)

ReadMessage reads and decodes a message from the card. It attempts to parse as NDEF first, falling back to TextMessage (raw bytes) if that fails.

Example:

msg, err := card.ReadMessage()
switch m := msg.(type) {
case *nfc.NDEFMessage:
    text, _ := m.GetText()
case *nfc.TextMessage:
    raw := m.Data
}

func (*Card) Reset

func (c *Card) Reset()

Reset clears the read cache, allowing fresh data to be read from the card. Useful if you want to re-read after writing or if the card's data may have changed.

func (*Card) String

func (c *Card) String() string

String returns a string representation of the card metadata.

func (*Card) Write

func (c *Card) Write(p []byte) (n int, err error)

Write implements io.Writer. Buffers data to be written to the card. The actual write to the card happens on Close() or Flush().

Example:

n, err := card.Write([]byte("Hello World"))
// or
io.WriteString(card, "Hello World")

func (*Card) WriteMessage

func (c *Card) WriteMessage(msg Message) error

WriteMessage encodes and writes a message to the card.

Example:

msg := nfc.NewTextMessage("Hello!", "en")
err := card.WriteMessage(msg)

type CardTransport added in v1.0.3

type CardTransport interface {
	Transceive(cmd []byte) ([]byte, error)
	IsCardPresent() bool
}

CardTransport is the hardware boundary every PC/SC tag talks through: it sends an APDU and reports card presence. *pcscDevice satisfies it in production; an in-memory emulator satisfies it in tests (see package nfctest), letting the real tag I/O logic (page math, lock bytes, TLV) run against emulated silicon without hardware. Wrap one in a driver with NewEmulatedTag.

type ClassicTag

type ClassicTag interface {
	Tag

	// Read reads a 16-byte block from the specified sector using the provided key.
	// sector: sector number (0-15 for 1K, 0-39 for 4K)
	// block: block within sector (0-2 for data blocks, 3 is sector trailer)
	// key: 6-byte authentication key
	// keyType: KeyTypeA or KeyTypeB
	Read(sector, block uint8, key []byte, keyType int) ([]byte, error)

	// Write writes 16 bytes to the specified block using the provided key.
	// sector: sector number (0-15 for 1K, 0-39 for 4K)
	// block: block within sector (0-2 for data blocks, 3 is sector trailer)
	// data: exactly 16 bytes to write
	// key: 6-byte authentication key
	// keyType: KeyTypeA or KeyTypeB
	Write(sector, block uint8, data []byte, key []byte, keyType int) error
}

ClassicTag provides MIFARE Classic specific operations. This interface extends Tag with sector/block-level access using authentication keys.

MIFARE Classic tags have a sector-based memory structure:

  • Classic 1K: 16 sectors × 4 blocks (64 blocks total)
  • Classic 4K: 32 sectors × 4 blocks + 8 sectors × 16 blocks (256 blocks total)

Each sector has a trailer block containing keys and access conditions. Blocks are 16 bytes each.

Example:

if classic, ok := tag.(nfc.ClassicTag); ok {
    key := []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
    data, err := classic.Read(1, 0, key, nfc.KeyTypeA)
    if err != nil {
        log.Fatal(err)
    }
}

type Clock

type Clock interface {
	// Now returns the current time
	Now() time.Time

	// Sleep pauses execution for the given duration
	Sleep(d time.Duration)

	// NewTicker creates a new ticker that will send on its channel
	// at intervals specified by the duration
	NewTicker(d time.Duration) Ticker

	// NewTimer creates a new timer that will send on its channel
	// after the specified duration
	NewTimer(d time.Duration) Timer

	// After returns a channel that will receive a value after the duration
	After(d time.Duration) <-chan time.Time
}

Clock provides an abstraction over time operations to enable testing without real time delays.

func NewRealClock

func NewRealClock() Clock

NewRealClock creates a new RealClock

type DetectedTagType

type DetectedTagType int

DetectedTagType represents detected tag type from ATR/commands

const (
	DetectedUnknown DetectedTagType = iota
	DetectedClassic1K
	DetectedClassic4K
	DetectedMini
	DetectedUltralight
	DetectedUltralightC
	DetectedUltralightEV1
	DetectedNTAG213
	DetectedNTAG215
	DetectedNTAG216
	DetectedDESFire
	DetectedDESFireEV1
	DetectedDESFireEV2
	DetectedISO14443_4
	DetectedPlus2K
	DetectedPlus4K
)

Detected tag type constants for PC/SC detection

type Device

type Device interface {
	Close() error
	String() string
	Connection() string
	Transceive(txData []byte) ([]byte, error)
	GetTags() ([]Tag, error)
}

Device represents an NFC reader/writer hardware device.

A Device is obtained from a Manager and provides low-level access to NFC communication capabilities. Devices are returned ready-to-use from Manager.OpenDevice() - no additional initialization is required.

Example:

manager := nfc.NewManager()
device, err := manager.OpenDevice("")
defer device.Close()

type DeviceCapabilities

type DeviceCapabilities struct {
	// Communication capabilities
	CanTransceive bool `json:"canTransceive"`
	CanPoll       bool `json:"canPoll"`

	// Supported tag types
	SupportedTagTypes []string `json:"supportedTagTypes,omitempty"`

	// Hardware info
	DeviceType  string `json:"deviceType"`            // "libnfc", "smartphone", etc.
	MaxBaudRate int    `json:"maxBaudRate,omitempty"` // Max baud rate in bps

	// Event capabilities
	SupportsEvents bool `json:"supportsEvents"` // Tag arrival/removal events
}

DeviceCapabilities describes what operations a device supports.

func BuildDeviceCapabilities

func BuildDeviceCapabilities(device Device) DeviceCapabilities

BuildDeviceCapabilities constructs a DeviceCapabilities struct by checking which interfaces the device implements.

func GetDeviceCapabilities

func GetDeviceCapabilities(device Device) DeviceCapabilities

GetDeviceCapabilities returns capabilities for any Device. Capabilities are built by checking which interfaces the device implements.

type DeviceChangeNotifier

type DeviceChangeNotifier interface {
	// DeviceChanges returns a channel that signals when devices are added or removed.
	DeviceChanges() <-chan struct{}
}

DeviceChangeNotifier is optionally implemented by Managers that support notifying when devices are added or removed.

type DeviceEvent

type DeviceEvent struct {
	Type      DeviceEventType
	Timestamp time.Time
	Device    Device // nil if disconnected
	Message   string // Human-readable description
	Err       error  // Associated error, if any
}

DeviceEvent represents a device lifecycle event

type DeviceEventEmitter

type DeviceEventEmitter interface {
	SupportsEvents() bool
}

DeviceEventEmitter is a marker interface for devices that emit tag events (e.g., tag arrival/removal) rather than requiring polling.

type DeviceEventType

type DeviceEventType int

DeviceEventType categorizes device lifecycle events

const (
	// DeviceConnected indicates successful device connection
	DeviceConnected DeviceEventType = iota

	// DeviceDisconnected indicates device was disconnected
	DeviceDisconnected

	// DeviceReconnecting indicates an automatic reconnection attempt is starting
	DeviceReconnecting

	// DeviceReconnectFailed indicates a reconnection attempt failed
	DeviceReconnectFailed

	// CooldownStarted indicates device entered cooldown period
	CooldownStarted

	// CooldownEnded indicates cooldown period completed
	CooldownEnded

	// DeviceError indicates a recoverable device error occurred
	DeviceError
)

func (DeviceEventType) String

func (et DeviceEventType) String() string

String returns the event type as a string

type DeviceHealthChecker

type DeviceHealthChecker interface {
	IsHealthy() error
}

DeviceHealthChecker is an optional interface for devices that support health/connectivity checks. Use type assertion to check if a device implements this interface.

Example:

if checker, ok := device.(DeviceHealthChecker); ok {
    if err := checker.IsHealthy(); err != nil {
        // Device is not responding, handle reconnection
    }
}

type DeviceInfoProvider

type DeviceInfoProvider interface {
	DeviceType() string
	SupportedTagTypes() []string
}

DeviceInfoProvider provides device metadata for capability building. Implement this interface to provide device-specific information.

type DeviceManager

type DeviceManager struct {
	// contains filtered or unexported fields
}

DeviceManager handles device lifecycle, connection management, and reconnection logic. It maintains a connection to a single NFC device and handles recovery from errors.

func NewDeviceManager

func NewDeviceManager(manager Manager, devicePath string, clock Clock) *DeviceManager

NewDeviceManager creates a new DeviceManager for managing an NFC device connection. If clock is nil, a RealClock is used by default.

func (*DeviceManager) Close

func (dm *DeviceManager) Close()

Close closes the current device connection.

func (*DeviceManager) CooldownChannel

func (dm *DeviceManager) CooldownChannel() <-chan time.Time

CooldownChannel returns the cooldown timer channel for select statements.

func (*DeviceManager) Device

func (dm *DeviceManager) Device() Device

Device returns the current active device, or nil if not connected.

func (*DeviceManager) DevicePath

func (dm *DeviceManager) DevicePath() string

DevicePath returns the path of the device being managed.

func (*DeviceManager) EndCooldown

func (dm *DeviceManager) EndCooldown(stopChan <-chan struct{})

EndCooldown ends the current cooldown period and attempts to reconnect.

func (*DeviceManager) EnsureConnected

func (dm *DeviceManager) EnsureConnected(stopChan <-chan struct{}) error

EnsureConnected ensures the device is connected and responsive. If not connected, attempts to connect. If in cooldown, returns an error. This method manages internal retry state for the device manager.

func (*DeviceManager) Events

func (dm *DeviceManager) Events() <-chan DeviceEvent

Events returns a read-only channel for device lifecycle events.

func (*DeviceManager) ForceReconnect

func (dm *DeviceManager) ForceReconnect(stopChan <-chan struct{}) error

ForceReconnect attempts to force reconnect with device reset wait time.

func (*DeviceManager) HandleError

func (dm *DeviceManager) HandleError(err error, stopChan <-chan struct{}) (needsCooldown bool)

HandleError processes device errors and determines the appropriate recovery action. Returns whether a cooldown was initiated. Retry state is now managed internally.

func (*DeviceManager) HasDevice

func (dm *DeviceManager) HasDevice() bool

HasDevice returns true if a device is currently connected.

func (*DeviceManager) InCooldown

func (dm *DeviceManager) InCooldown() bool

InCooldown returns true if the device manager is in a cooldown period.

func (*DeviceManager) Manager added in v1.0.1

func (dm *DeviceManager) Manager() Manager

Manager returns the underlying NFC manager for device discovery.

func (*DeviceManager) Reconnect

func (dm *DeviceManager) Reconnect(stopChan <-chan struct{}) error

Reconnect attempts to reconnect to the device with exponential backoff.

func (*DeviceManager) SetDevicePath added in v1.0.1

func (dm *DeviceManager) SetDevicePath(path string)

SetDevicePath sets the device path to use for connections.

func (*DeviceManager) TryConnect

func (dm *DeviceManager) TryConnect() error

TryConnect attempts to connect to the device. If the device is already connected and responsive, it returns nil. Otherwise, it attempts to open and initialize the device.

type DeviceStatus

type DeviceStatus struct {
	Connected   bool
	Message     string
	CardPresent bool
}

DeviceStatus represents the status of the NFC device. This type might be used by the main application to display status.

type DeviceTransceiver added in v1.0.3

type DeviceTransceiver interface {
	SupportsTransceive() bool
}

DeviceTransceiver is an optional interface that lets a device declare whether it actually supports raw Transceive. Devices that do not implement it default to CanTransceive=true (the common case for polling hardware readers). Implement this to report false when your device's Transceive returns a NotSupported error, so GetDeviceCapabilities reflects reality.

type ErrorCode

type ErrorCode int

ErrorCode represents a specific type of NFC error for programmatic handling.

const (
	// Tag operation errors (100-199)
	ErrCodeNotSupported ErrorCode = iota + 100
	ErrCodeTagRemoved
	ErrCodeAuthFailed
	ErrCodeReadFailed
	ErrCodeWriteFailed
	ErrCodeTransceiveFailed
	ErrCodeTagNotConnected
	ErrCodeReadOnly
	ErrCodeCapacityExceeded
	ErrCodeInvalidData
)

func GetErrorCode

func GetErrorCode(err error) ErrorCode

GetErrorCode extracts the ErrorCode from an error if it's an NFCError. Returns 0 if the error is not an NFCError.

func InternalErrorCode added in v1.1.0

func InternalErrorCode(wire protocol.ErrorCode, fallback ErrorCode) ErrorCode

InternalErrorCode maps a wire code back to an internal one, for outcomes reported by a remote device. Codes with no internal equivalent — protocol faults, or a device sending something we do not know — fall back to the caller's own notion of what failed.

type FakeClock

type FakeClock struct {
	// contains filtered or unexported fields
}

FakeClock implements Clock for testing with controllable time

func NewFakeClock

func NewFakeClock(startTime time.Time) *FakeClock

NewFakeClock creates a new FakeClock starting at the given time

func (*FakeClock) Advance

func (fc *FakeClock) Advance(d time.Duration)

Advance moves the fake clock forward by the given duration and fires any tickers/timers that should fire

func (*FakeClock) After

func (fc *FakeClock) After(d time.Duration) <-chan time.Time

func (*FakeClock) NewTicker

func (fc *FakeClock) NewTicker(d time.Duration) Ticker

func (*FakeClock) NewTimer

func (fc *FakeClock) NewTimer(d time.Duration) Timer

func (*FakeClock) Now

func (fc *FakeClock) Now() time.Time

func (*FakeClock) Sleep

func (fc *FakeClock) Sleep(d time.Duration)

type LockResult added in v1.0.3

type LockResult struct {
	// UID of the tag that was locked.
	UID string `json:"uid"`
	// TagType is the human-readable tag type string.
	TagType string `json:"tagType"`
	// Locked is true when the tag was made permanently read-only.
	Locked bool `json:"locked"`
}

LockResult describes the outcome of a make-read-only (lock) operation.

type Manager

type Manager interface {
	OpenDevice(deviceStr string) (Device, error)
	ListDevices() ([]string, error)
}

Manager handles NFC device discovery.

Manager provides methods to list available NFC readers and open connections to devices.

Example:

manager := nfc.NewManager()
devices, _ := manager.ListDevices()
device, _ := manager.OpenDevice(devices[0])
tags, _ := device.GetTags()

func NewManager

func NewManager() Manager

NewManager creates a new Manager using the PC/SC implementation.

Example:

manager := nfc.NewManager()

type Message

type Message interface {
	// Encode converts the message to bytes for writing to card
	Encode() ([]byte, error)

	// Type returns the message type for debugging
	Type() string
}

Message represents data that can be written to/read from a card. Different implementations handle different encoding schemes.

type MockClassicTag

type MockClassicTag struct {
	*MockTag

	// BlockData stores data for each sector/block combination
	// Key format: "sector:block" (e.g., "1:0")
	BlockData map[string][]byte

	// ReadError, if set, will be returned by Read()
	ReadError error

	// WriteError, if set, will be returned by Write()
	WriteError error
	// contains filtered or unexported fields
}

MockClassicTag is a test implementation of ClassicTag for MIFARE Classic tags.

func NewMockClassicTag

func NewMockClassicTag(uid string) *MockClassicTag

NewMockClassicTag creates a new MockClassicTag with default values.

func (*MockClassicTag) GetBlockData

func (m *MockClassicTag) GetBlockData(sector, block uint8) ([]byte, bool)

GetBlockData retrieves the data for a specific sector/block combination.

func (*MockClassicTag) Read

func (m *MockClassicTag) Read(sector, block uint8, key []byte, keyType int) ([]byte, error)

Read simulates reading a block from the tag.

func (*MockClassicTag) SetBlockData

func (m *MockClassicTag) SetBlockData(sector, block uint8, data []byte)

SetBlockData sets the data for a specific sector/block combination.

func (*MockClassicTag) Write

func (m *MockClassicTag) Write(sector, block uint8, data []byte, key []byte, keyType int) error

Write simulates writing a block to the tag.

type MockDevice

type MockDevice struct {
	// DeviceName is the simulated device name returned by String()
	DeviceName string

	// DeviceConnection is the simulated connection string returned by Connection()
	DeviceConnection string

	// IsOpen tracks whether the device is currently open
	IsOpen bool

	// InitError, if set, will be returned by IsHealthy()
	InitError error

	// CloseError, if set, will be returned by Close()
	CloseError error

	// TransceiveFunc allows custom transceive behavior for testing
	// If nil, returns TransceiveResponse or TransceiveError
	TransceiveFunc func([]byte) ([]byte, error)

	// TransceiveResponse is the default response for Transceive calls
	TransceiveResponse []byte

	// TransceiveError, if set, will be returned by Transceive()
	TransceiveError error

	// GetTagsFunc allows custom GetTags behavior for testing
	// If nil, returns Tags or GetTagsError
	GetTagsFunc func() ([]Tag, error)

	// Tags is the list of tags returned by GetTags()
	Tags []Tag

	// GetTagsError, if set, will be returned by GetTags()
	GetTagsError error

	// CallLog tracks all method calls for verification in tests
	CallLog []string

	// MockDeviceType allows overriding the device type (default: "mock")
	MockDeviceType string

	// MockSupportedTagTypes allows overriding supported tag types
	MockSupportedTagTypes []string

	// MockSupportsEvents makes the device report as event-based (like smartphone)
	MockSupportsEvents bool
	// contains filtered or unexported fields
}

MockDevice is a test implementation of Device that simulates NFC hardware.

MockDevice allows testing NFC functionality without physical hardware by simulating device behavior, connection states, and data transmission. It also implements DeviceHealthChecker for health check simulation.

Example:

mock := &MockDevice{
    DeviceName: "Mock NFC Reader",
    DeviceConnection: "mock:usb:001",
}
tags, err := mock.GetTags()

func NewMockDevice

func NewMockDevice() *MockDevice

NewMockDevice creates a new MockDevice with default values.

func (*MockDevice) AddTag

func (m *MockDevice) AddTag(tag Tag)

AddTag adds a tag to the list returned by GetTags().

func (*MockDevice) ClearCallLog

func (m *MockDevice) ClearCallLog()

ClearCallLog clears the call log.

func (*MockDevice) ClearTags

func (m *MockDevice) ClearTags()

ClearTags removes all tags from the list returned by GetTags().

func (*MockDevice) Close

func (m *MockDevice) Close() error

Close simulates closing the device.

func (*MockDevice) Connection

func (m *MockDevice) Connection() string

Connection returns the simulated connection string.

func (*MockDevice) DeviceType

func (m *MockDevice) DeviceType() string

DeviceType returns the device type (implements DeviceInfoProvider).

func (*MockDevice) GetCallLog

func (m *MockDevice) GetCallLog() []string

GetCallLog returns a copy of the call log for verification.

func (*MockDevice) GetTags

func (m *MockDevice) GetTags() ([]Tag, error)

GetTags simulates detecting tags on the device.

func (*MockDevice) IsHealthy

func (m *MockDevice) IsHealthy() error

IsHealthy checks if the mock device is healthy (implements DeviceHealthChecker).

func (*MockDevice) SetTags

func (m *MockDevice) SetTags(tags []Tag)

SetTags sets the tags that will be returned by GetTags().

func (*MockDevice) String

func (m *MockDevice) String() string

String returns the simulated device name.

func (*MockDevice) SupportedTagTypes

func (m *MockDevice) SupportedTagTypes() []string

SupportedTagTypes returns the supported tag types (implements DeviceInfoProvider).

func (*MockDevice) SupportsEvents

func (m *MockDevice) SupportsEvents() bool

SupportsEvents returns whether this device emits events (implements DeviceEventEmitter).

func (*MockDevice) Transceive

func (m *MockDevice) Transceive(txData []byte) ([]byte, error)

Transceive simulates data transmission with the device.

type MockISO14443Tag

type MockISO14443Tag struct {
	*MockTag
}

MockISO14443Tag is a test implementation of ISO14443Tag for Type 4 tags.

func NewMockISO14443Tag

func NewMockISO14443Tag(uid string) *MockISO14443Tag

NewMockISO14443Tag creates a new MockISO14443Tag with default values.

type MockManager

type MockManager struct {
	// DevicesList is the list of device strings returned by ListDevices()
	DevicesList []string

	// ListDevicesError, if set, will be returned by ListDevices()
	ListDevicesError error

	// MockDevice is the device returned by OpenDevice()
	// If nil, a new MockDevice will be created
	MockDevice *MockDevice

	// OpenDeviceError, if set, will be returned by OpenDevice()
	OpenDeviceError error

	// CallLog tracks all method calls for verification in tests
	CallLog []string
	// contains filtered or unexported fields
}

MockManager is a test implementation of Manager that simulates NFC device management.

MockManager allows testing device discovery and tag detection without physical hardware by providing configurable mock responses.

Example:

manager := &MockManager{
    DevicesList: []string{"mock:usb:001", "mock:usb:002"},
    MockDevice: NewMockDevice(),
}
devices, _ := manager.ListDevices()

func NewMockManager

func NewMockManager() *MockManager

NewMockManager creates a new MockManager with default values.

func (*MockManager) ClearCallLog

func (m *MockManager) ClearCallLog()

ClearCallLog clears the call log.

func (*MockManager) GetCallLog

func (m *MockManager) GetCallLog() []string

GetCallLog returns a copy of the call log for verification.

func (*MockManager) ListDevices

func (m *MockManager) ListDevices() ([]string, error)

ListDevices simulates listing available NFC devices.

func (*MockManager) OpenDevice

func (m *MockManager) OpenDevice(deviceStr string) (Device, error)

OpenDevice simulates opening an NFC device.

type MockNtagTag

type MockNtagTag struct {
	*MockTag

	// PageData stores data for each page (0-134 for NTAG215)
	PageData map[byte][4]byte

	// ReadPageError, if set, will be returned by ReadPage()
	ReadPageError error

	// WritePageError, if set, will be returned by WritePage()
	WritePageError error

	// MaxPages defines the maximum page number (default 135 for NTAG215)
	MaxPages byte
	// contains filtered or unexported fields
}

MockNtagTag is a test implementation of NtagTag for NTAG21x tags.

MockNtagTag simulates page-based memory operations for NTAG213/215/216 tags.

Example:

tag := NewMockNtagTag("04112233445566")
tag.Connect()
tag.WritePage(4, [4]byte{0x03, 0x04, 0xD1, 0x01})
data, _ := tag.ReadPage(4)

func NewMockNtagTag

func NewMockNtagTag(uid string) *MockNtagTag

NewMockNtagTag creates a new MockNtagTag with NTAG215 defaults.

func (*MockNtagTag) ClearPageData

func (m *MockNtagTag) ClearPageData()

ClearPageData clears all page data.

func (*MockNtagTag) GetPageData

func (m *MockNtagTag) GetPageData(page byte) ([4]byte, bool)

GetPageData retrieves the data for a specific page.

func (*MockNtagTag) ReadPage

func (m *MockNtagTag) ReadPage(page byte) ([4]byte, error)

ReadPage simulates reading a 4-byte page from the NTAG tag.

func (*MockNtagTag) SetPageData

func (m *MockNtagTag) SetPageData(page byte, data [4]byte)

SetPageData sets the data for a specific page (bypasses write protection for testing).

func (*MockNtagTag) WritePage

func (m *MockNtagTag) WritePage(page byte, data [4]byte) error

WritePage simulates writing a 4-byte page to the NTAG tag.

type MockTag

type MockTag struct {
	// TagUID is the UID returned by UID()
	TagUID string

	// TagType is the type string returned by Type()
	TagType string

	// TagNumericType is the numeric type returned by NumericType()
	TagNumericType int

	// Data is the data returned by ReadData()
	Data []byte

	// ReadDataFunc allows custom ReadData behavior (e.g. simulating a verification
	// mismatch or a transient read error). If nil, ReadData returns Data or
	// ReadDataError. Called with the mock's lock held; must not re-enter the mock.
	ReadDataFunc func() ([]byte, error)

	// WriteDataFunc allows custom WriteData behavior (e.g. failing a fixed number
	// of times before succeeding). If it returns nil, the data is still stored so
	// a subsequent ReadData reflects the write. If nil, WriteData stores Data or
	// returns WriteDataError. Called with the mock's lock held.
	WriteDataFunc func([]byte) error

	// ReadDataError, if set, will be returned by ReadData()
	ReadDataError error

	// WriteDataError, if set, will be returned by WriteData()
	WriteDataError error

	// TransceiveFunc allows custom transceive behavior
	// If nil, returns TransceiveResponse or TransceiveError
	TransceiveFunc func([]byte) ([]byte, error)

	// TransceiveResponse is the default response for Transceive calls
	TransceiveResponse []byte

	// TransceiveError, if set, will be returned by Transceive()
	TransceiveError error

	// ConnectError, if set, will be returned by Connect()
	ConnectError error

	// DisconnectError, if set, will be returned by Disconnect()
	DisconnectError error

	// IsConnected tracks whether the tag is currently connected
	IsConnected bool

	// IsReadOnly tracks whether the tag is in read-only mode
	IsReadOnly bool

	// IsWritableFunc allows custom IsWritable behavior
	// If nil, returns !IsReadOnly and IsWritableError
	IsWritableFunc func() (bool, error)

	// IsWritableError, if set, will be returned by IsWritable()
	IsWritableError error

	// MakeReadOnlyFunc allows custom MakeReadOnly behavior
	// If nil, sets IsReadOnly to true or returns MakeReadOnlyError
	MakeReadOnlyFunc func() error

	// MakeReadOnlyError, if set, will be returned by MakeReadOnly()
	MakeReadOnlyError error

	// CanMakeReadOnlyFunc allows custom CanMakeReadOnly behavior
	// If nil, returns !IsReadOnly and CanMakeReadOnlyError
	CanMakeReadOnlyFunc func() (bool, error)

	// CanMakeReadOnlyError, if set, will be returned by CanMakeReadOnly()
	CanMakeReadOnlyError error

	// CallLog tracks all method calls for verification in tests
	CallLog []string

	// MockCapabilities allows overriding the default capabilities
	// If nil, capabilities are inferred from TagType
	MockCapabilities *TagCapabilities
	// contains filtered or unexported fields
}

MockTag is a test implementation of Tag that simulates NFC tag behavior.

MockTag allows testing tag operations without physical tags by providing configurable mock responses for read/write operations.

Example:

tag := &MockTag{
    TagUID: "04A1B2C3",
    TagType: "MIFARE Classic 1K",
    Data: []byte{0x00, 0x01, 0x02},
}
data, _ := tag.ReadData()

func NewMockTag

func NewMockTag(uid string) *MockTag

NewMockTag creates a new MockTag with default values.

func (*MockTag) CanMakeReadOnly

func (m *MockTag) CanMakeReadOnly() (bool, error)

CanMakeReadOnly simulates checking if the tag can be made read-only.

func (*MockTag) Capabilities

func (m *MockTag) Capabilities() TagCapabilities

Capabilities returns the tag's capabilities. If MockCapabilities is set, returns that; otherwise infers from TagType.

func (*MockTag) ClearCallLog

func (m *MockTag) ClearCallLog()

ClearCallLog clears the call log.

func (*MockTag) Connect

func (m *MockTag) Connect() error

Connect simulates connecting to the tag.

func (*MockTag) Disconnect

func (m *MockTag) Disconnect() error

Disconnect simulates disconnecting from the tag.

func (*MockTag) GetCallLog

func (m *MockTag) GetCallLog() []string

GetCallLog returns a copy of the call log for verification.

func (*MockTag) IsWritable

func (m *MockTag) IsWritable() (bool, error)

IsWritable simulates checking if the tag is writable.

func (*MockTag) MakeReadOnly

func (m *MockTag) MakeReadOnly() error

MakeReadOnly simulates making the tag read-only.

func (*MockTag) NumericType

func (m *MockTag) NumericType() int

NumericType returns the tag's numeric type.

func (*MockTag) ReadData

func (m *MockTag) ReadData() ([]byte, error)

ReadData simulates reading data from the tag.

func (*MockTag) Transceive

func (m *MockTag) Transceive(data []byte) ([]byte, error)

Transceive simulates data exchange with the tag.

func (*MockTag) Type

func (m *MockTag) Type() string

Type returns the tag's type string.

func (*MockTag) UID

func (m *MockTag) UID() string

UID returns the tag's UID.

func (*MockTag) WriteData

func (m *MockTag) WriteData(data []byte) error

WriteData simulates writing data to the tag.

type NDEFEmpty

type NDEFEmpty struct{}

NDEFEmpty represents a high-level empty record.

func (*NDEFEmpty) ToRecord

func (e *NDEFEmpty) ToRecord() NDEFRecord

ToRecord converts NDEFEmpty to NDEFRecord.

type NDEFExternal

type NDEFExternal struct {
	Domain string // e.g., "example.com:myapp"
	Data   []byte
}

NDEFExternal represents a high-level external type record.

func (*NDEFExternal) ToRecord

func (e *NDEFExternal) ToRecord() NDEFRecord

ToRecord converts NDEFExternal to NDEFRecord.

type NDEFMIME

type NDEFMIME struct {
	Type string
	Data []byte
}

NDEFMIME represents a high-level MIME type record.

func (*NDEFMIME) ToRecord

func (m *NDEFMIME) ToRecord() NDEFRecord

ToRecord converts NDEFMIME to NDEFRecord.

type NDEFMessage

type NDEFMessage struct {
	// contains filtered or unexported fields
}

NDEFMessage represents a structured NDEF message with multiple records. This allows complex messages with multiple record types (text, URI, MIME, etc.)

func ConvertNDEFInput

func ConvertNDEFInput(data *protocol.NDEFMessageInput) (*NDEFMessage, error)

ConvertNDEFInput converts protocol NDEF format to internal NDEFMessage.

func DecodeNDEF

func DecodeNDEF(data []byte) (*NDEFMessage, error)

DecodeNDEF parses raw bytes into an NDEFMessage. Returns error if the data is not valid NDEF format.

func NewNDEFMessage

func NewNDEFMessage() *NDEFMessage

NewNDEFMessage creates a new empty NDEF message.

func (*NDEFMessage) AddRecord

func (m *NDEFMessage) AddRecord(record NDEFRecord) *NDEFMessage

AddRecord adds a raw NDEF record to the message.

func (*NDEFMessage) AddText

func (m *NDEFMessage) AddText(text, langCode string) *NDEFMessage

AddText adds an NDEF Text Record to the message.

func (*NDEFMessage) AddURI

func (m *NDEFMessage) AddURI(uri string) *NDEFMessage

AddURI adds an NDEF URI Record to the message.

func (*NDEFMessage) Encode

func (m *NDEFMessage) Encode() ([]byte, error)

Encode converts the NDEF message to bytes.

func (*NDEFMessage) GetText

func (m *NDEFMessage) GetText() (string, error)

GetText returns the text content from the first Text Record in the message.

func (*NDEFMessage) GetURI

func (m *NDEFMessage) GetURI() (string, error)

GetURI returns the URI from the first URI Record in the message.

func (*NDEFMessage) Records

func (m *NDEFMessage) Records() []NDEFRecord

Records returns the list of NDEF records in this message.

func (*NDEFMessage) ToBuilder

func (m *NDEFMessage) ToBuilder() *NDEFMessageBuilder

ToBuilder converts a low-level NDEFMessage into a high-level NDEFMessageBuilder. This allows editing existing messages in a declarative way.

Example:

// Read existing message
msg, _ := card.ReadMessage()
ndefMsg := msg.(*nfc.NDEFMessage)

// Convert to builder for editing
builder := ndefMsg.ToBuilder()
builder.Records = append(builder.Records, &nfc.NDEFText{Content: "New text"})

// Build and write back
updated := builder.MustBuild()
card.WriteMessage(updated)

func (*NDEFMessage) ToJSONMap

func (m *NDEFMessage) ToJSONMap() map[string]interface{}

ToJSONMap converts an NDEFMessage to a map suitable for JSON serialization. This is useful for building WebSocket/API responses.

func (*NDEFMessage) ToPayload

func (m *NDEFMessage) ToPayload() *NDEFMessagePayload

ToPayload converts an NDEFMessage to a JSON-friendly payload structure. This method extracts human-readable content (text, URI) from each record and returns a structure suitable for WebSocket/API responses.

func (*NDEFMessage) Type

func (m *NDEFMessage) Type() string

Type returns "ndef" for debugging.

type NDEFMessageBuilder

type NDEFMessageBuilder struct {
	Records []NDEFRecordBuilder
}

NDEFMessageBuilder provides a declarative way to construct NDEF messages.

Example:

msg := &nfc.NDEFMessageBuilder{
    Records: []nfc.NDEFRecordBuilder{
        &nfc.NDEFText{Content: "Hello World", Language: "en"},
        &nfc.NDEFURI{Content: "https://example.com"},
    },
}.Build()

func (*NDEFMessageBuilder) Build

func (b *NDEFMessageBuilder) Build() (*NDEFMessage, error)

Build transforms the high-level records into a low-level NDEFMessage.

func (*NDEFMessageBuilder) Encode

func (b *NDEFMessageBuilder) Encode() ([]byte, error)

func (*NDEFMessageBuilder) MustBuild

func (b *NDEFMessageBuilder) MustBuild() *NDEFMessage

MustBuild is like Build but panics on error.

func (*NDEFMessageBuilder) Type

func (b *NDEFMessageBuilder) Type() string

type NDEFMessagePayload

type NDEFMessagePayload struct {
	Type    string              `json:"type"`    // Message type: "ndef"
	Records []NDEFRecordPayload `json:"records"` // Array of NDEF records
}

NDEFMessagePayload represents an NDEF message in JSON-friendly format.

type NDEFRaw added in v1.0.3

type NDEFRaw struct {
	TNF     uint8
	Type    []byte
	ID      []byte
	Payload []byte
}

NDEFRaw represents a fully specified NDEF record for advanced or custom use cases where the caller provides the TNF, type, optional ID, and payload directly (e.g. proprietary external types or non-NDEF-Forum records).

func (*NDEFRaw) ToRecord added in v1.0.3

func (r *NDEFRaw) ToRecord() NDEFRecord

ToRecord converts NDEFRaw to NDEFRecord. The TNF is masked to its valid 3-bit range.

type NDEFRecord

type NDEFRecord struct {
	TNF     byte   // Type Name Format (0x00-0x07)
	Type    []byte // Record type (e.g., "T" for text, "U" for URI)
	ID      []byte // Optional record ID
	Payload []byte // Record payload data
}

NDEFRecord represents a single NDEF record within a message.

func ConvertNDEFRecordInput

func ConvertNDEFRecordInput(data protocol.NDEFRecordInput) (*NDEFRecord, error)

ConvertNDEFRecordInput converts protocol NDEF record to internal NDEFRecord.

func (*NDEFRecord) GetText

func (r *NDEFRecord) GetText() (string, bool)

GetText extracts text from a Text Record (TNF=0x01, Type='T'). Returns (text, true) if this is a text record, or ("", false) otherwise.

func (*NDEFRecord) GetURI

func (r *NDEFRecord) GetURI() (string, bool)

GetURI extracts URI from a URI Record (TNF=0x01, Type='U'). Returns (uri, true) if this is a URI record, or ("", false) otherwise.

func (*NDEFRecord) IsTextRecord

func (r *NDEFRecord) IsTextRecord() bool

IsTextRecord returns true if this is a Text Record.

func (*NDEFRecord) IsURIRecord

func (r *NDEFRecord) IsURIRecord() bool

IsURIRecord returns true if this is a URI Record.

type NDEFRecordBuilder

type NDEFRecordBuilder interface {
	ToRecord() NDEFRecord
}

NDEFRecordBuilder is an interface that can be converted to NDEFRecord.

type NDEFRecordPayload

type NDEFRecordPayload struct {
	Type     string `json:"type"`               // Record type: "text", "uri", etc. (human-readable)
	Content  string `json:"content,omitempty"`  // Decoded content (text or URI)
	Language string `json:"language,omitempty"` // Language code for text records
	TNF      uint8  `json:"tnf"`                // Type Name Format (technical detail)
	ID       string `json:"id,omitempty"`       // Record ID (optional)
	Payload  []byte `json:"payload"`            // Raw payload data
}

NDEFRecordPayload represents an NDEF record in JSON-friendly format. This structure is used for serialization to WebSocket clients and API responses.

type NDEFSmartPoster added in v1.0.3

type NDEFSmartPoster struct {
	URI      string
	Title    string // Optional display title
	Language string // Optional, defaults to "en" when Title is set
}

NDEFSmartPoster represents a high-level Smart Poster record: a URI with an optional human-readable title. It encodes as a Well Known "Sp" record whose payload is a nested NDEF message containing an optional Title (Text) record and a mandatory URI record. This is the most common "tap to open <label>" tag and is widely understood by phones.

func (*NDEFSmartPoster) ToRecord added in v1.0.3

func (s *NDEFSmartPoster) ToRecord() NDEFRecord

ToRecord converts NDEFSmartPoster to NDEFRecord.

type NDEFText

type NDEFText struct {
	Content  string
	Language string // Optional, defaults to "en"
}

NDEFText represents a high-level text record.

Example:

msg := &nfc.NDEFMessageBuilder{
    Records: []nfc.NDEFRecordBuilder{
        &nfc.NDEFText{Content: "Hello World", Language: "en"},
        &nfc.NDEFURI{Content: "https://example.com"},
    },
}

func (*NDEFText) ToRecord

func (t *NDEFText) ToRecord() NDEFRecord

ToRecord converts NDEFText to NDEFRecord.

type NDEFURI

type NDEFURI struct {
	Content string
}

NDEFURI represents a high-level URI record.

func (*NDEFURI) ToRecord

func (u *NDEFURI) ToRecord() NDEFRecord

ToRecord converts NDEFURI to NDEFRecord.

type NFCData

type NFCData struct {
	Card *Card // The detected card, nil if no card is present
	Err  error // Error that occurred during detection/reading
}

NFCData represents the data read from an NFC tag including any potential errors.

type NFCError

type NFCError struct {
	Code    ErrorCode
	Op      string // Operation that failed (e.g., "ReadData", "Transceive")
	TagUID  string // Optional: UID of tag involved
	Message string // Human-readable message
	Cause   error  // Underlying error
}

NFCError provides structured error information for programmatic handling.

func Errorf

func Errorf(code ErrorCode, op, format string, args ...any) *NFCError

Errorf creates an NFCError with a formatted message.

func NewAuthError

func NewAuthError(op, tagUID string, cause error) *NFCError

NewAuthError creates an error for authentication failures.

func NewCapacityExceededError added in v1.0.3

func NewCapacityExceededError(op, tagUID string, needed, available int) *NFCError

NewCapacityExceededError creates an error for when the data to write is larger than the tag's usable NDEF capacity.

func NewNotSupportedError

func NewNotSupportedError(op string) *NFCError

NewNotSupportedError creates an error for unsupported operations.

func NewReadError

func NewReadError(op string, cause error) *NFCError

NewReadError creates an error for read failures.

func NewReadOnlyError added in v1.0.3

func NewReadOnlyError(op, tagUID string, cause error) *NFCError

NewReadOnlyError creates an error for write attempts on a read-only tag.

func NewTagRemovedError

func NewTagRemovedError(op string, cause error) *NFCError

NewTagRemovedError creates an error for when a tag is removed mid-operation.

func NewTransceiveError

func NewTransceiveError(op string, cause error) *NFCError

NewTransceiveError creates an error for transceive failures.

func NewWriteError

func NewWriteError(op string, cause error) *NFCError

NewWriteError creates an error for write failures.

func WrapError

func WrapError(code ErrorCode, op, message string, cause error) *NFCError

WrapError wraps an existing error with NFC context.

func (*NFCError) Error

func (e *NFCError) Error() string

func (*NFCError) Is

func (e *NFCError) Is(target error) bool

func (*NFCError) Unwrap

func (e *NFCError) Unwrap() error

type NFCReader

type NFCReader struct {
	// contains filtered or unexported fields
}

NFCReader manages NFC device interactions and broadcasts tag data.

func NewNFCReader

func NewNFCReader(deviceStr string, manager Manager, opTimeout time.Duration) (*NFCReader, error)

NewNFCReader creates and initializes a new NFCReader instance with default ModeReadWrite.

func NewNFCReaderWithClock

func NewNFCReaderWithClock(deviceStr string, manager Manager, opTimeout time.Duration, clock Clock) (*NFCReader, error)

NewNFCReaderWithClock creates and initializes a new NFCReader with a custom clock. If clock is nil, uses RealClock.

func (*NFCReader) Close

func (r *NFCReader) Close()

Close releases resources. Does not stop the worker, use Stop() for that.

func (*NFCReader) Data

func (r *NFCReader) Data() <-chan NFCData

Data returns a channel that provides NFCData as tags are read.

func (*NFCReader) DevicePath

func (r *NFCReader) DevicePath() string

func (*NFCReader) EraseCard added in v1.0.3

func (r *NFCReader) EraseCard() (*WriteResult, error)

EraseCard overwrites the presented tag with an empty NDEF message, making it read as blank. This is reversible — the tag can be rewritten afterward. The write is verified like any other write.

func (*NFCReader) GetCapabilities added in v1.0.3

func (r *NFCReader) GetCapabilities() (*TagCapabilities, error)

GetCapabilities reports the capabilities of the tag currently presented to the reader — memory size, writability, lock and password support, and read-only state. It requires exactly one tag to be present, performs no write, and works regardless of reader mode (including read-only). This lets clients query what a tag supports before attempting a write or lock.

func (*NFCReader) GetDeviceStatus

func (r *NFCReader) GetDeviceStatus() DeviceStatus

GetDeviceStatus returns the current device status by querying live state.

func (*NFCReader) GetLastScannedData

func (r *NFCReader) GetLastScannedData() string

GetLastScannedData retrieves the last scanned UID from the cache.

func (*NFCReader) GetMode

func (r *NFCReader) GetMode() ReaderMode

GetMode returns the current reader mode.

func (*NFCReader) GetTags

func (r *NFCReader) GetTags() ([]Tag, error)

GetTags retrieves available tags from the connected NFC device.

func (*NFCReader) LockCard added in v1.0.3

func (r *NFCReader) LockCard() (*LockResult, error)

LockCard makes the currently presented tag permanently read-only. This is irreversible. Only tags that support locking (e.g. NTAG, Ultralight) succeed; others return a not-supported error.

func (*NFCReader) LogDeviceInfo

func (r *NFCReader) LogDeviceInfo()

LogDeviceInfo logs information about the connected NFC device.

func (*NFCReader) RemoveCardPassword added in v1.0.3

func (r *NFCReader) RemoveCardPassword(password []byte) (*PasswordResult, error)

RemoveCardPassword clears password protection from the presented tag.

Like SetCardPassword, this is gated off pending hardware validation and currently returns a not-supported error.

func (*NFCReader) SetCardPassword added in v1.0.3

func (r *NFCReader) SetCardPassword(password []byte, opts PasswordOptions) (*PasswordResult, error)

SetCardPassword configures password protection on the presented tag.

NOTE: password protection is NOT yet enabled in this build. The per-tag capability is reported (TagCapabilities.SupportsPassword) and this API contract is fixed, but the destructive configuration-page writes (PWD, PACK, AUTH0, ACCESS) are intentionally gated off pending validation on real hardware — a wrong AUTH0/ACCESS configuration can permanently lock a tag. This method currently returns a not-supported error for all tags.

func (*NFCReader) SetClassicKeys added in v1.0.3

func (r *NFCReader) SetClassicKeys(keys [][]byte)

SetClassicKeys configures additional 6-byte MIFARE Classic authentication keys to try when reading or writing Classic cards that don't use default keys. Keys are applied to each Classic tag the reader encounters, tried before the built-in defaults. Pass nil to clear.

func (*NFCReader) SetMode

func (r *NFCReader) SetMode(mode ReaderMode)

SetMode changes the reader's access mode at runtime.

func (*NFCReader) Start

func (r *NFCReader) Start()

Start begins the NFC reading process in a separate goroutine.

func (*NFCReader) StatusUpdates

func (r *NFCReader) StatusUpdates() <-chan DeviceStatus

StatusUpdates returns a channel that provides DeviceStatus updates.

func (*NFCReader) Stop

func (r *NFCReader) Stop()

Stop gracefully shuts down the NFCReader worker and waits for it to complete.

func (*NFCReader) Transceive added in v1.1.0

func (r *NFCReader) Transceive(data []byte) ([]byte, error)

Transceive exchanges raw bytes with the tag currently on the reader.

Deliberately not gated on ReaderMode here: the caller decides. A raw exchange is neither a read nor a write as far as this layer can tell — the same interface carries a SELECT and a write to a config page — so the policy call belongs where the request enters, not here.

func (*NFCReader) WriteCardData

func (r *NFCReader) WriteCardData(text string) error

WriteCardData attempts to write data to a detected NFC card using default options (overwrite mode).

func (*NFCReader) WriteMessageWithOptions

func (r *NFCReader) WriteMessageWithOptions(msg *NDEFMessage, opts WriteOptions) error

WriteMessageWithOptions writes an NDEF message to a detected NFC card with options for record manipulation. It performs a pre-flight capacity check, retries on transient failures, and (unless disabled) verifies the write by reading the data back. Use WriteMessageWithResult to obtain the WriteResult.

func (*NFCReader) WriteMessageWithResult added in v1.0.3

func (r *NFCReader) WriteMessageWithResult(msg *NDEFMessage, opts WriteOptions) (*WriteResult, error)

WriteMessageWithResult is like WriteMessageWithOptions but returns a WriteResult describing the outcome (verification status, attempts, and bytes written) so callers can surface real write confidence to the user.

type PasswordOptions added in v1.0.3

type PasswordOptions struct {
	// Pack is the 2-byte password acknowledge (PACK) returned by the tag on a
	// successful authentication. If empty, the implementation chooses a default.
	Pack []byte

	// ProtectRead, when true, requires the password for reads as well as
	// writes. When false (the default), only writes are password-protected.
	ProtectRead bool

	// StartPage is the first tag page protected by the password (NTAG AUTH0).
	// Implementations enforce a floor so the tag's own configuration pages can
	// never be locked out of reach, which would brick the tag.
	StartPage int
}

PasswordOptions configures how password protection is applied to a tag.

These fields define the contract the hardware implementation will honor. Password protection is currently gated off pending validation on real hardware (see SetCardPassword), so they are not yet acted upon.

type PasswordResult added in v1.0.3

type PasswordResult struct {
	// UID of the tag the operation targeted.
	UID string `json:"uid"`
	// TagType is the human-readable tag type string.
	TagType string `json:"tagType"`
	// Protected reports whether the tag is password-protected after the
	// operation (true after a successful set, false after a successful remove).
	Protected bool `json:"protected"`
}

PasswordResult describes the outcome of a password operation.

type ReaderLister added in v1.1.3

type ReaderLister interface {
	ListReaders() ([]string, error)
}

ReaderLister is implemented by a manager that holds others, so it can list only the devices eligible to be this agent's reader.

Optional: a manager that does not implement it lists readers from ListDevices.

type ReaderMode

type ReaderMode int

ReaderMode defines the access mode for the NFC reader.

const (
	// ModeReadWrite allows both read and write operations (default).
	ModeReadWrite ReaderMode = iota
	// ModeReadOnly allows only read operations.
	ModeReadOnly
	// ModeWriteOnly allows only write operations.
	ModeWriteOnly
)

type RealClock

type RealClock struct{}

RealClock implements Clock using actual time operations

func (*RealClock) After

func (rc *RealClock) After(d time.Duration) <-chan time.Time

func (*RealClock) NewTicker

func (rc *RealClock) NewTicker(d time.Duration) Ticker

func (*RealClock) NewTimer

func (rc *RealClock) NewTimer(d time.Duration) Timer

func (*RealClock) Now

func (rc *RealClock) Now() time.Time

func (*RealClock) Sleep

func (rc *RealClock) Sleep(d time.Duration)

type RemoteDeviceChecker added in v1.1.3

type RemoteDeviceChecker interface {
	RemoteDevice(devicePath string) bool
}

RemoteDeviceChecker is implemented by a manager that can recognize a device path as naming a remote device without connecting to it.

Optional: a manager that does not implement it is asked whether all of its devices are remote instead.

type RemoteManager added in v1.1.3

type RemoteManager interface {
	RemoteDevices() bool
}

RemoteManager is implemented by a manager whose devices are not readers attached to this machine. A phone reports the tags it scans over the device bridge, so it is never opened and polled the way a reader is — offering one as the agent's reader only produces a device that can never be connected.

Optional: a manager that does not implement it manages local readers.

type Tag

Tag represents an NFC tag at the hardware protocol level.

Tag provides a unified interface for reading and writing NDEF data regardless of the underlying tag technology (MIFARE Classic, ISO14443-4, etc.).

Not all tags support all operations. Use GetTagCapabilities(tag) to check what operations a specific tag supports before calling methods that may return "not supported" errors.

For most use cases, prefer using Card which provides a higher-level, io.Reader/Writer compatible API.

Example:

tags, _ := manager.GetTags(device)
for _, tag := range tags {
    caps := nfc.GetTagCapabilities(tag)
    if caps.CanRead {
        data, _ := tag.ReadData()
    }
}

func NewEmulatedTag added in v1.0.3

func NewEmulatedTag(transport CardTransport, uid string, kind DetectedTagType) Tag

NewEmulatedTag wraps a CardTransport in the production tag driver for the given tag kind, so a custom or in-memory transport is driven by the real tag I/O (page/block/APDU logic, TLV framing, lock bytes) rather than a stand-in.

This is the bridge that lets the nfctest emulators run production driver code without hardware: build an emulator (a CardTransport), wrap it here, and the returned Tag behaves as if it were a real card on a reader.

type TagCache

type TagCache struct {
	// contains filtered or unexported fields
}

TagCache provides thread-safe caching of the last scanned NFC tag UID.

func NewTagCache

func NewTagCache() *TagCache

NewTagCache creates and initializes a new TagCache instance.

func (*TagCache) Clear

func (c *TagCache) Clear()

Clear resets the cache to its initial state.

func (*TagCache) GetLastScanned

func (c *TagCache) GetLastScanned() string

GetLastScanned returns the UID of the last successfully scanned tag.

func (*TagCache) HasChanged

func (c *TagCache) HasChanged(uid string) bool

HasChanged checks if the given UID is new or different from the last scanned card. It returns true if this is a new card (different UID from last scan).

func (*TagCache) IsCardPresent

func (c *TagCache) IsCardPresent() bool

IsCardPresent checks if a card is still present based on the last seen time.

func (*TagCache) UpdateLastSeenTime

func (tc *TagCache) UpdateLastSeenTime(uid string)

UpdateLastSeenTime updates the global last seen time in the cache, indicating recent card activity. The uid parameter is currently not used for specific per-tag timestamping with the current cache structure but is retained from the original intended signature of ForceSeen.

type TagCapabilities

type TagCapabilities = protocol.TagCapabilities

TagCapabilities describes what operations a tag supports. Defined in the protocol package so external tools can consume it without pulling in this one.

func GetTagCapabilities

func GetTagCapabilities(tag Tag) TagCapabilities

GetTagCapabilities returns capabilities for any Tag. If the tag implements TagCapabilityProvider, it uses that. Otherwise, it infers capabilities from the tag type string.

func InferTagCapabilities

func InferTagCapabilities(tagType string) TagCapabilities

InferTagCapabilities infers capabilities from a tag type string. This is used as a fallback when the tag doesn't implement TagCapabilityProvider.

type TagCapabilityProvider

type TagCapabilityProvider interface {
	Capabilities() TagCapabilities
}

TagCapabilityProvider is an optional interface for tags to report their capabilities.

type TagConnection

type TagConnection interface {
	// Connect establishes a connection to the tag.
	Connect() error
	// Disconnect closes the connection to the tag.
	Disconnect() error
}

TagConnection manages the connection lifecycle to a tag. Tags that require explicit connection management implement this interface.

type TagIdentifier

type TagIdentifier interface {
	// UID returns the unique identifier of the tag.
	UID() string
	// Type returns a human-readable string describing the tag type.
	Type() string
	// NumericType returns a numeric type identifier (implementation-specific).
	NumericType() int
}

TagIdentifier provides basic tag identification. All tags implement this interface.

type TagLocker

type TagLocker interface {
	// IsWritable checks if the tag can be written to.
	IsWritable() (bool, error)
	// CanMakeReadOnly checks if the tag supports being made read-only.
	CanMakeReadOnly() (bool, error)
	// MakeReadOnly permanently locks the tag to prevent further writes.
	MakeReadOnly() error
}

TagLocker provides read-only locking capability. Tags that can be made permanently read-only implement this interface. Use GetTagCapabilities(tag).CanLock to check if a tag supports this.

type TagReader

type TagReader interface {
	// ReadData reads NDEF data from the tag.
	ReadData() ([]byte, error)
}

TagReader provides read capability for NDEF data. Tags that support reading implement this interface.

type TagTransceiver

type TagTransceiver interface {
	// Transceive sends raw data to the tag and returns the response.
	Transceive(data []byte) ([]byte, error)
}

TagTransceiver provides raw data exchange with the tag. Only some tag types (e.g., Type 4) support this. Use GetTagCapabilities(tag).CanTransceive to check if a tag supports this.

type TagType

type TagType string

TagType represents the type of NFC tag as a string.

const (
	TagTypeMifareClassic TagType = "MIFARE_Classic"
	TagTypeType4         TagType = "Type4"
	TagTypeUnknown       TagType = "Unknown"
)

Constants for common tag types

type TagWriteOptions

type TagWriteOptions struct {
	// ForceInitialize forces reinitialization of the tag even if it contains existing data.
	// WARNING: This will erase all existing data on the tag.
	// Only use this if you explicitly want to wipe and reinitialize the tag.
	ForceInitialize bool
}

TagWriteOptions defines options for tag write operations.

type TagWriter

type TagWriter interface {
	// WriteData writes NDEF data to the tag.
	WriteData(data []byte) error
}

TagWriter provides write capability for NDEF data. Tags that support writing implement this interface. Use GetTagCapabilities(tag).CanWrite to check if a tag supports this.

type TextMessage

type TextMessage struct {
	Data []byte // Raw bytes from the card
	Text string // Decoded text representation
}

TextMessage represents raw bytes from cards that don't support NDEF. This is a fallback message type that stores both the raw data and decoded text.

func DecodeText

func DecodeText(data []byte) *TextMessage

DecodeText creates a TextMessage from raw bytes (no parsing). This is used for cards that don't support NDEF.

func NewTextMessage

func NewTextMessage(data []byte) *TextMessage

NewTextMessage creates a new text message from raw bytes. It automatically decodes the bytes to a string.

func NewTextMessageFromString

func NewTextMessageFromString(text string) *TextMessage

NewTextMessageFromString creates a new text message from a string.

func (*TextMessage) Bytes

func (t *TextMessage) Bytes() []byte

Bytes returns the raw bytes.

func (*TextMessage) Encode

func (t *TextMessage) Encode() ([]byte, error)

Encode returns the raw bytes as-is (no encoding).

func (*TextMessage) String

func (t *TextMessage) String() string

String returns the decoded text.

func (*TextMessage) Type

func (t *TextMessage) Type() string

Type returns "raw" for debugging.

type Ticker

type Ticker interface {
	// C returns the channel on which ticks are delivered
	C() <-chan time.Time

	// Stop turns off the ticker
	Stop()

	// Reset stops a ticker and resets its period to the specified duration
	Reset(d time.Duration)
}

Ticker is an interface for time.Ticker to enable testing

type Timer

type Timer interface {
	// C returns the channel on which the timer value will be sent
	C() <-chan time.Time

	// Stop prevents the timer from firing
	Stop() bool

	// Reset changes the timer to expire after duration d
	Reset(d time.Duration) bool
}

Timer is an interface for time.Timer to enable testing

type WriteOptions

type WriteOptions struct {
	// Overwrite completely replaces card data. If false, performs partial update.
	// Partial updates only work if the card already contains valid NDEF data.
	Overwrite bool

	// Index specifies which record to update (for NDEF partial updates).
	// -1 means append, >= 0 means replace at that index.
	// Ignored if Overwrite is true or card doesn't support NDEF.
	Index int

	// ForceInitialize forces reinitialization of MIFARE Classic cards even if they
	// contain existing data. WARNING: This will erase all existing data on the card.
	// Only set this to true if you explicitly want to wipe and reinitialize the card.
	ForceInitialize bool

	// SkipVerify disables read-after-write verification. By default (false), the
	// reader re-reads the card after writing and confirms the data matches what
	// was written, retrying on mismatch.
	SkipVerify bool

	// MaxWriteAttempts caps the number of write+verify attempts on transient
	// failures (write error, verification mismatch, or transient read error).
	// If <= 0, DefaultMaxWriteAttempts is used. Permanent failures such as card
	// removal, read-only tags, and capacity overflow are never retried.
	MaxWriteAttempts int

	// SkipCapacityCheck disables the pre-flight check that the encoded NDEF
	// message fits within the tag's reported NDEF capacity.
	SkipCapacityCheck bool

	// Lock, when true, makes the tag permanently read-only after a successful
	// verified write. Only tags that support locking (e.g. NTAG, Ultralight)
	// honor this; others return an error. WARNING: locking is irreversible.
	Lock bool
}

WriteOptions controls how data is written to NFC cards at the reader level.

type WriteResult added in v1.0.3

type WriteResult struct {
	// UID of the tag that was written.
	UID string `json:"uid"`
	// TagType is the human-readable tag type string.
	TagType string `json:"tagType"`
	// BytesWritten is the size of the encoded NDEF message written to the tag.
	BytesWritten int `json:"bytesWritten"`
	// Verified is true when the write was confirmed by reading the data back and
	// comparing it to what was written.
	Verified bool `json:"verified"`
	// Attempts is the number of write attempts made before success.
	Attempts int `json:"attempts"`
	// Locked is true when the tag was made permanently read-only as part of the
	// write (see WriteOptions.Lock).
	Locked bool `json:"locked,omitempty"`
}

WriteResult describes the outcome of a successful write operation. It gives callers (and ultimately the frontend) the same confidence for writes that the read path already provides: confirmation that the bytes actually landed.

Directories

Path Synopsis
Package multimanager provides a multi-manager that aggregates multiple NFC Manager implementations.
Package multimanager provides a multi-manager that aggregates multiple NFC Manager implementations.
Package nfctest provides in-memory NFC tag emulators and a high-level façade for testing NFC code without hardware.
Package nfctest provides in-memory NFC tag emulators and a high-level façade for testing NFC code without hardware.

Jump to

Keyboard shortcuts

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