MobNfc.Hce (mob_nfc v0.1.0)

Copy Markdown View Source

Pure NFC Forum Type-4 Tag (T4T) APDU responder — the tested reference implementation of the Host Card Emulation state machine.

The Android MobNfcApduService (priv/native/android/MobNfcBridge.kt) is a faithful Kotlin mirror of this: identical Capability Container bytes, identical SELECT / READ BINARY / UPDATE BINARY handling, identical write-completion detection. Because an Android HostApduService must answer a reader even when the BEAM isn't running, the Kotlin can't delegate here at runtime — so this module exists to pin the protocol logic in host-runnable tests, and the Kotlin is reviewed against it.

handle_apdu/2 is pure:

{response_bytes, new_state, event} = MobNfc.Hce.handle_apdu(apdu, state)

where event is nil, :read (a reader finished reading the NDEF file), or {:written, ndef_bytes} (a reader wrote a new message into a writable tag).

A T4T read is: SELECT NDEF app → SELECT CC → READ CC → SELECT NDEF file → READ BINARY. A write (writable tag only) additionally issues UPDATE BINARY commands: NLEN=0, the message at offset 2, then the real NLEN at offset 0.

Summary

Functions

The 15-byte Capability Container. Write-access byte is 00 (writable) when writable, else FF (read-only); the NDEF File Control TLV points at file E104 with max size 1024.

Handle one command APDU. Returns {response_bytes, new_state, event}.

The NDEF file image: 2-byte NLEN (message length) followed by the message.

Initial responder state serving ndef, optionally writable.

Types

event()

@type event() :: nil | :read | {:written, binary()}

selected()

@type selected() :: :none | :cc | :ndef

state()

@type state() :: %{
  selected: selected(),
  ndef: binary(),
  writable: boolean(),
  write_buf: binary() | nil
}

Functions

cc(writable)

@spec cc(boolean()) :: binary()

The 15-byte Capability Container. Write-access byte is 00 (writable) when writable, else FF (read-only); the NDEF File Control TLV points at file E104 with max size 1024.

handle_apdu(apdu, state)

@spec handle_apdu(binary(), state()) :: {binary(), state(), event()}

Handle one command APDU. Returns {response_bytes, new_state, event}.

Unknown instructions get 6D00; malformed/oversized get 6F00; a SELECT of an unknown file id gets 6A82.

ndef_file(msg)

@spec ndef_file(binary()) :: binary()

The NDEF file image: 2-byte NLEN (message length) followed by the message.

new(ndef, writable \\ false)

@spec new(binary(), boolean()) :: state()

Initial responder state serving ndef, optionally writable.