NFC#10 NFC処理実装[後編] – NDEFデータ読み書き
前回はカード操作をするために必要な処理を実装しました。
今回は本題となる、実際のカード操作のための処理を実装していきます。
|
【注意事項】 管理者の許可なくデータの書き込みや改変を行う行為は、不正アクセス禁止法や刑法等の法令に違反する可能性があります。 |
・スマートフォンやICカードとの連携機能を実装したいエンジニア
・IoT機器や非接触決済の技術に関心のある開発者
開発環境
- OS: macOS Sonoma 14.5
- Xcode: Xcode 15.4 (15F31d)
NDEF(NFC Data Exchange Format)
NFCデバイスやNFCタグの間でデータ交換するために、 NDEF というデータフォーマットが規定されています。今回はNFCカードに書き込まれているNDEFデータの読み込みと読み書きを実装していきます。
NDEF は、1つ以上のペイロードを単一のメッセージ構造体にカプセル化するために設計されたメッセージフォーマットです。
ここで、ペイロードを含むデータを NDEFレコード、 メッセージ構造体を NDEFメッセージ と呼びます。
NDEFメッセージ

NDEFメッセージは、1個以上のNDEFレコードを含む、NDEFレコードのコンテナのようなものです。
NDEFの仕様上は、NDEFメッセージ内に格納できるNDEFレコードの最大数は無制限となっています。
また、NDEFレコードはインデックス番号を持たないため、NDEFメッセージに格納された順番によって暗黙的に割り当てられます。
NDEFレコード

NDEFレコードは以下の要素で構成されます。
| No. | 名称 | サイズ | データ |
| 1-1 | MB (Message Begin) | 1ビット | NDEFメッセージの開始を示すフラグ |
| 1-2 | ME (Message End) | 1ビット | NDEFメッセージの終了を示すフラグ |
| 1-3 | CF (Chunk Flag) | 1ビット | レコードチャンクを示すフラグ |
| 1-4 | SR (Short Record) | 1ビット | 255バイト以下のペイロードをコンパクトにカプセル化するためのフラグ 1 をセットすることで、 PAYLOAD_LENGTH のサイズが1バイトとなる |
| 1-5 | IL | 1ビット | ID_LENGTH の有無を示すフラグ |
| 1-6 | TNF (Type Name Format) | 3ビット | TYPE の構造を示す特定の値 |
| 2 | TYPE_LENGTH | 1バイト | TYPE のバイトサイズ |
| 3 | PAYLOAD_LENGTH | 4バイト or 1バイト | PAYLOAD のバイトサイズ |
| 4 | ID_LENGTH | 1バイト or 0バイト | ID のバイトサイズ |
| 5 | TYPE | TYPE_LENGTH or 0バイト | ペイロードの種類を記述する識別子 |
| 6 | ID | ID_LENGTHor 0バイト | RFC 3986 の URI 形式によるペイロード識別子 |
| 7 | PAYLOAD | PAYLOAD_LENGTH or 0バイト | データ本体 |
MB , ME は、それぞれNDEFメッセージ中の開始レコード・終了レコードを示すフラグです。
NDEFメッセージ内の最初のレコードの場合は MB に 1 をセット、最後のレコードの場合は ME に 1 をセットします。
NDEFメッセージに1つのNDEFレコードのみの場合、 MB と ME の両方に 1 をセットします。
今回は割愛いたしますが、NDEFレコードに格納するペイロードは複数のレコードに分割することができます。このようなレコードはレコードチャンクと呼ばれます。
CF は、レコードチャンクに含まれるペイロードが最終データ以外の場合に 1 をセットします。
TYPE はペイロードの種類を記述する識別子で、 TNF の値によって決まる構造、符号化、および形式に従って設定される必要があります。 TYPE のサイズは TYPE_LENGTH で定められます。
TNF の値によって TYPE が不要となる場合、 TYPE は省略し、 TYPE_LENGTH には 00h を設定します。
ID はペイロードのIDを示します。フォーマットは RFC 3986 の URI 形式に準じます。 ID のサイズは ID_LENGTH で定められます。
ID はオプションで設定するものであり、 IL に 0 をセットすると、 ID 及び ID_LENGTH は省略されます。
PAYLOAD はデータ本体となります。 PAYLOAD のサイズは PAYLOAD_LENGTH で定められます。
PAYLOAD_LENGTH が 0 の場合、 PAYLOAD は省略されます。
ペイロードのサイズが 0 〜 255 バイトに収まる場合、 SR に 1 をセットすることで PAYLOAD_LENGTH のサイズが1バイトとなり、コンパクトにカプセル化することができます。
NDEFデータ読み書き手順(NFC フォーラム タイプ4タグの場合)
今回使用するカードが準拠する NFC フォーラム タイプ4タグ において、NDEFデータ読み書きのための手順は下表の通りです。
| 手順 | 操作 |
| 1 | SELECT: NDEF アプリケーション |
| 2 | SELECT: CC ファイル |
| 3 | READ BINARY: NDEF ファイル制御 TLV データの読み出し |
| 4 | SELECT: NDEF ファイル |
| 5 | READ BINARY / UPDATE BINARY : カードアクセス |
ここで、 SELECT, READ BINARY, UPDATE BINARY は ISO/IEC 7816-4 で定義されているコマンドです。
RC-S660/S 操作時と同様、 APDU によってコマンド/レスポンスが表現されます。
| コマンド | 機能 | CLA | INS |
| SELECT | ファイルやアプリケーションの選択 | 00h | A4h |
| READ BINARY | 選択したファイルのバイナリデータを読み取る | 00h | B0h or B1h |
| UPDATE BINARY | 選択したファイルにバイナリデータを書き込む | 00h | D6h or D7h |
仕様上、 READ BINARY , UPDATE BINARY は、 INS の最下位ビットと P1 の最上位ビットによって設定するデータが異なります。
このため、 INS は2つの値を取り得ます。
CC(Capability Container)ファイル
CC(Capability Container)ファイルは NFC フォーラム タイプ4タグ で定義されているファイルで、 NDEF 対応可否や最大 APDU サイズ、 NDEF ファイルの情報が格納されています。
NDEF ファイルの情報はTLV形式で格納されており、 NDEF ファイルを SELECT するための識別子や、ファイルの最大サイズ、アクセス条件が格納されています。
NDEFファイル
NDEF ファイルは、データ本体である NDEF メッセージと、そのサイズで構成されています。
先頭2バイトに NDEF メッセージのサイズ、それ以降に NDEF メッセージデータが格納されます。
各操作を実現するためのコマンドパケット
それでは実装を進めていきます。
前回の記事同様、データが変わるだけで送信・受信処理はほとんど変わりません。
また、使用する APDU も Tag : Transceive (Transmit and Receive) で共通のため、本記事ではカードコマンド部のみに注目して解説していきます。
(1)SELECT : NDEF アプリケーション
uint8_t sendTxRxSelectNDEFAppBuffer[] = {
// CCIDコマンド - APDUコマンド - Data In 2 - Length までは省略
// Value : カードコマンドAPDU
0x00, 0xa4, // CLA, INS : SELECT
0x04, // P1 : Select by DF name
0x00, // P2
0x07, // Lc
0xd2, 0x76, 0x00, 0x00,
0x85, 0x01, 0x01, // Data: NDEFアプリケーションID
0x00, // Le
// データチェックサム, ポストアンブル 省略
};
NFC フォーラム タイプ4タグ では、はじめにNDEFアプリケーションIDを選択する必要があります。
アプリケーションIDを選択するためには、 P1 には 04h (Select by DF name) を指定します。
また、NDEFアプリケーションIDは、NFC フォーラム タイプ4タグの仕様で D2760000850101h と定義されています。
(2)SELECT : CC ファイル
uint8_t sendTxRxSelectCCFileBuffer[] = {
// CCIDコマンド - APDUコマンド - Data In 2 - Length までは省略
// Value : カードコマンドAPDU
0x00, 0xa4, // CLA, INS : SELECT
0x00, // P1 : Select MF, DF or EF
0x0c, // P2
0x02, // Lc
0xe1, 0x03, // Data: CCファイルのファイルID
// データチェックサム, ポストアンブル 省略
};
次にCCファイルからNDEFファイルの情報を読み出すため、まずはCCファイルを選択します。
CCファイルのファイルIDは E103h で予約されているため、このファイルIDを指定して選択します。
ファイルIDによる指定のためには、 P1 には 00h (Select MF, DF or EF) を指定します。
(3)READ BINARY : NDEF ファイル制御 TLV データの読み出し
uint8_t sendTxRxReadCCFileBuffer[] = {
// CCIDコマンド - APDUコマンド - Data In 2 - Length までは省略
// Value : カードコマンドAPDU
0x00, 0xb0, // CLA, INS : READ BINARY
0x00, 0x00, // P1, P2 : オフセット
0x0f, // Le
// データチェックサム, ポストアンブル 省略
};
CCファイルを選択したら、CCファイルからNDEFファイルの情報を読み出します。 SELECT 後に READ BINARY することで、 SELECT で選択したファイルからデータを読み出すことができます。
INS の最下位ビット、 P1 の最上位ビットを共に 0 にしている場合、 P1, P2 は開始位置を示すオフセットとなります。
また、 Le によって読み出すサイズを指定します。今回は NDEF ファイル制御 TLV データを含めるように 15 バイトを指定しています。
読み出しが正常完了すると、以下のようなCCファイルのデータが返却されます。
00 11 20 00 3b 00 34 04 06 e1 04 1e 00 00 00
各データの意味は下表の通りです。
| オフセット | サイズ | 上例の該当データ | 概要 | ||
| 00h | 2バイト | 0011h | CCファイルサイズ | ||
| 02h | 1バイト | 20h | マッピングバージョン | ||
| 03h | 2バイト | 003Bh | レスポンスAPDUの最大サイズ | ||
| 05h | 2バイト | 0034h | コマンドAPDUの最大サイズ | ||
| 07h | 1バイト | 04h | NDEFファイル制御TLVデータ | Tag | |
| 08h | 1バイト | 06h | Length | ||
| 09h | 2バイト | E104h | Value | ファイル識別子 | |
| 0Bh | 2バイト | 1E00h | ファイル最大サイズ | ||
| 0Dh | 1バイト | 00h | 読み取りアクセス条件 | ||
| 0Eh | 1バイト | 00h | 書き込みアクセス条件 |
オフセット 07h から8バイトには、NDEFファイルの情報が記述されたNDEFファイル制御TLVデータが格納されています。
NDEFファイルにアクセスするためにはファイル識別子が必要なため、このNDEFファイル制御TLVデータを読み出し、ファイル識別子を確認します。
(4)SELECT : NDEF ファイル
uint8_t sendTxRxSelectNDEFFileBuffer[] = {
// Value : カードコマンドAPDU
0x00, 0xa4, // CLA, INS : SELECT
0x00, // P1 : Select MF, DF or EF
0x0c, // P2
0x02, // Lc
0xe1, 0x04, // Data: NDEFファイルのファイルID
};
CCファイルからNDEFファイルのファイル識別子を取得したら、そのファイルIDで SELECT します。
P1 はCCファイルと同様、 00h (Select MF, DF or EF) を指定します。
(5) カードアクセス
NDEFファイルを選択することで、カードのNDEFデータにアクセすることができます。
読み出しには READ BINARY コマンド、書き込みには UPDATE BINARY コマンドをそれぞれ使用します。
a. READ BINARY: データ読み出し
uint8_t sendTxRxReadNDEFSizeBuffer[] = {
// Value : カードコマンドAPDU
0x00, 0xb0, // CLA, INS : READ BINARY
0x00, 0x00, // P1, P2 : オフセット
0x02, // Le
};
NDEFファイルのデータ読み出しは、まずNDEFファイルの先頭2バイトを読み出し、NDEFファイルサイズを取得します。
uint8_t sendTxRxReadNDEFDataBuffer[] = {
// Value : カードコマンドAPDU
0x00, 0xb0, // CLA, INS : READ BINARY
0x00, 0x02, // P1, P2 : オフセット
0x10, // Le
};
その後、3バイト目(オフセット0002h)からファイルサイズ分だけ、NDEFデータを取得します。NDEFデータサイズがレスポンスAPDUの最大サイズを超える場合は、オフセットと取得データサイズを変えながら順に読み込みます。
b. UPDATE BINARY : データ書き込み
// Value : カードコマンドAPDU
0x00, 0xd6, // CLA, INS : UPDATE BINARY
0x00, 0x00, // P1, P2 : オフセット
0x13, // Lc
// Data : NDEFファイルデータ
0x00, 0x11, // NDEFメッセージサイズ
// NDEFメッセージ
0x80 | 0x40 | // MB | ME |
0x10 | 0x01, // SR | TNF (0x01 : NFC Forum well-known type)
0x01, // TYPE_LENGTH
0x0d, // PAYLOAD_LENGTH
0x54, // TYPE : TEXT
// PAYLOAD (TYPE が TEXT の場合)
0x00 | 0x02, // encoding : UTF-8, language length
'e', 'n', // language
'w', 'r', 'i', 't', 'e',
' ', 't', 'e', 's', 't',// data : 文字列データ
データ書き込みは、NDEFファイルデータをそのまま書き込みます。
上記はNDEFファイルデータ全体を一度で書き込む例ですが、 初期化 -> データ更新 -> サイズ更新 のように順々に書き込むことも可能です。
今回は文字列レコード1つのみを書き込みます。
レコードが1つのみなので、 MB と ME 両方をセットします。
また、書き込む文字列データを255バイト以下とし、 SR もセットします。
TNF として 01h を指定することで、 NFC Forum で規定されているレコードタイプ定義(RTD)を TYPE に指定します。
NFC RTD においては、 TYPE に T (54h) を指定すると、文字列レコードを示します。
文字列レコードのペイロードは以下の要素で構成されます。
| No. | 名称 | サイズ | データ |
| 1-1 | エンコード | 1ビット | 0: UTF-8, 1: UTF-16 |
| 1-2 | RFU | 1ビット | 将来予約 |
| 1-3 | 言語コード長 | 6ビット | 言語コードの長さ |
| 2 | 言語コード | 言語コード長 | 言語コード文字列(enなど) |
| 3 | 文字列データ | PAYLOAD_LENGTH – 2 バイト | 文字列データ本体 |
おわりに
前回と今回で、NFCカードにNDEFデータを読み書きするための処理を実装しました。
動作確認、及び受信パケットの解析は、次回まとめていきたいと思います。

