開発

NFC#10 NFC処理実装[後編] – NDEFデータ読み書き

NFC#10 NFC処理実装[後編] - NDEFデータ読み書き

前回はカード操作をするために必要な処理を実装しました。
今回は本題となる、実際のカード操作のための処理を実装していきます。

【注意事項】
本記事は、NFC技術の一般的な仕組みを解説するものです。
公共機関や第三者が管理・運用するNFCタグやICカードへの無断での書き込み・改変を推奨または容認するものではありません。

管理者の許可なくデータの書き込みや改変を行う行為は、不正アクセス禁止法や刑法等の法令に違反する可能性があります。
これらの行為により生じたいかなる損害・法的責任についても、当方は一切の責任を負いません。

・NFCを活用した新しいサービスや製品を企画・開発したい方
・スマートフォンやICカードとの連携機能を実装したいエンジニア
・IoT機器や非接触決済の技術に関心のある開発者

完成品画像NFC完成品紹介ページはこちら

開発環境

  • 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メッセージ構成

NDEFメッセージは、1個以上のNDEFレコードを含む、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メッセージ内の最初のレコードの場合は MB1 をセット、最後のレコードの場合は ME1 をセットします。
NDEFメッセージに1つのNDEFレコードのみの場合、 MBME の両方に 1 をセットします。

今回は割愛いたしますが、NDEFレコードに格納するペイロードは複数のレコードに分割することができます。このようなレコードはレコードチャンクと呼ばれます。
CF は、レコードチャンクに含まれるペイロードが最終データ以外の場合に 1 をセットします。

TYPE はペイロードの種類を記述する識別子で、 TNF の値によって決まる構造、符号化、および形式に従って設定される必要があります。 TYPE のサイズは TYPE_LENGTH で定められます。
TNF の値によって TYPE が不要となる場合、 TYPE は省略し、 TYPE_LENGTH には 00h を設定します。

ID はペイロードのIDを示します。フォーマットは RFC 3986 の URI 形式に準じます。 ID のサイズは ID_LENGTH で定められます。
ID はオプションで設定するものであり、 IL0 をセットすると、 ID 及び ID_LENGTH は省略されます。

PAYLOAD はデータ本体となります。 PAYLOAD のサイズは PAYLOAD_LENGTH で定められます。
PAYLOAD_LENGTH0 の場合、 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つのみなので、 MBME 両方をセットします。
また、書き込む文字列データを255バイト以下とし、 SR もセットします。

TNF として 01h を指定することで、 NFC Forum で規定されているレコードタイプ定義(RTD)を TYPE に指定します。
NFC RTD においては、 TYPET (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データを読み書きするための処理を実装しました。
動作確認、及び受信パケットの解析は、次回まとめていきたいと思います。

 


 

TOP