読者です 読者をやめる 読者になる 読者になる

kanetaiの二次記憶装置

プログラミングに関するやってみた、調べた系のものをQitaに移して、それ以外をはてブでやる運用にしようと思います。http://qiita.com/kanetai

NDEF(NFC Data Exchange Format)

とりあえず、直近で使いそうなRTD Text、AAR, Mime Recordについてだけ調べた。
他のレコードやAndroidBeamなどの内容はまた機会があったら調べようと思う。
Android NFCプログラミング完全ガイド買ってみた。

Android NFCプログラミング完全ガイド

Android NFCプログラミング完全ガイド

英語ドキュメント読むのが嫌な人はコレ読んでみたらいいと思う。
NDEF Recordの構成
7 6 5 4 3 3 1 0
MB ME CF SR IL TNF
TYPE LENGTH (1 Byte)
PAYLOAD LENGTH(1 Byte or 4 Byte)
ID LENGTH (0…1 Byte)
TYPE (0…n Byte)
ID (0…n Byte)
PAYLOAD (0…n Byte)

NDEF Recordの構成

  • MB(Message Begin: 1 bit): NDEFメッセージのはじめのNDEFレコードかどうかを示すフラグ。
  • ME(Message End: 1 bit): NDEFメッセージの終わりNDEFレコードかどうかを示すフラグ。
  • CF(Chunk Flag: 1 bit): 分割されたレコードの最初か中間であることを示すフラグ。
  • SR(Short Record: 1 bit): 要領を節約するフラグ。このフラグがたっているとPAYLOAD_LENGTHを1 Byteにできる。ただし、PAYLOADが255 Byte以下にする必要がある。このフラグがたっていない場合、PAYLOAD_LENGTHは4 Byte。
  • IL(ID_LENGTH field is present: 1 bit): ID_LENGTHとIDがあるかどうかのフラグ。0を指定するとID_LENGTHとIDは省略しなければならない。
  • TNF(Type Name Format: 3 bit): このレコードが持つTYPE, ID, PAYLOADにどのような種類のデータが入っているのかを示す。
  • TYPE LENGTH(1 Byte): Byte単位でTYPEの長さを示す。
  • PAYLOAD LENGTH(1 Byte or 4 Byte): Byte単位でPAYLOADの長さを示す。
  • ID_LENGTH(0..1 Byte): Byte単位でIDの長さを示す。
  • TYPE(Payload Type 0..n Byte): ペイロードがどのような種類であるかを示す。TNFによっていれる値が異なる。
  • ID(Payload ID: 0..n Byte): アプリケーションがペイロードを認識する場合に利用する。Optionalなフィールド。
  • PAYLOAD(0..n Byte)

Androidでは、TNF, Type, ID, Payloadを指定すると、残りのフィールドは自動的に補ってくれるので、この4つ意外はあまり意識しなくて良い。
 
TNF(Type Name Format)

  • 0x00(ENF_EMPTY)
  • 0x01(TNF_WELL_KNOWN): NFC Forum RTD(Record Type Definition)仕様に定義されているRTDタイプフォーマットに従う値がタイプに含まれていることを示す。
  • 0x02(TNF_MIME_MEDIA): RFC(Request for Comments)2046に定義されたメディアタイプに従う値がタイプに含まれることを示す。
  • 0x03(TNF_ABSOLUTE_URI): RFC 3986によって定義されている絶対URIに従う値がタイプに含まれることを示す。TNF_WELL_KNOWNにも定義されているため、あまり使うことがない。
  • 0x04(TNF EXTERNAL_TYPE): NFC Forum RTD仕様に定義されている外部タイプ名に従う値がタイプに含まれていることを示す。名前を一意に識別する必要がある場合に使用する(AAR etc.)
  • 0x05(TNF_UNKNOWN
  • 0x06(TNF_UNCHANGED): NDEFメッセージを複数に分割する場合に利用するTNF。分割されたペイロードの中間で利用する。
  • 0x07 Reserved:

 
TYPE(Payload Type)
指定したTNFによっていれる値は変わる。
 
TNFが0x01(TNF_WELL_KNOWN)の場合

  • "ac"(RTD_ALTERNATIVE_CARRIER)
  • "Hc"(RTD_HANDOVER_CARRIER)
  • "Hr"(RTD_HANDOVER_REQUEST)
  • "Hs"(RTD_HANDOVER_SELECT)
  • "Sp"(RTD_SMART_POSTER)
  • "T"(RTD_TEXT)
  • "U"(RTD_URI)

 
TNFが0x02(TNF_MIME_MEDIA)の場合
application/jsonとかimage/pngとか

NDEFデータの生成/パース

NdefRecordのコンストラクタ

public NdefRecord (short tnf, byte[] type, byte[] id, byte[] payload)

TNFにより作り方が異なる。
代表的レコードの生成/パースの仕方は次の通り。

RTD Text Record
  • TNF : 0x01(TNF_WELL_KNOWN)
  • TYPE : "T"(RTD_TEXT)

改行はCRLFでなければならないとか、ステータスバイトをつけないといけないとか、いろいろな決まりがある。
MIME-Typeでいうtext/plain; format=fixedになるようにする必要がある。
マークアップを含んでは行けない。含む場合は、MIME Type Recordを利用する。
改行(0x0D, 0x0A)とタブ(0x08)を除いた制御文字(UTF-8 0x00-0x1f)は表示前に削除するべき。

public static NdefRecord createRTDTextNdefRecord(String txt, String id, Locale locale, boolean encodeInUtf8) {
	//lang code to byte[]
	byte[] langBytes = locale.getLanguage().getBytes(Charset.forName("US-ASCII"));
	//text to byte[]
	txt = convertToCRLF(txt);
	Charset utfEncoding = encodeInUtf8 ? Charset.forName("UTF-8") : Charset.forName("UTF-16");
	byte[] textBytes = txt.getBytes(utfEncoding);
	//status byte
	int utfBit = encodeInUtf8 ? 0 : 0x80;
	byte status = (byte) (utfBit | langBytes.length);
	//append status byte and text byte
	byte[] payload = concat(new byte[] { status }, langBytes, textBytes);
	NdefRecord record = new NdefRecord(NdefRecord.TNF_WELL_KNOWN,
			NdefRecord.RTD_TEXT, (id == null ? new byte[0] : id.getBytes()), payload);
	return record;
}
public static TextRecord parseRTDTextRecord(NdefRecord record) throws RuntimeException {
	if (!isRTDTextType(record)) throw new RuntimeException(FormatError);
	
	byte[] payload = record.getPayload();
	byte status = payload[0];
	
	final String encoding = ((status & 0x80) == 0) ? "UTF-8" : "UTF-16";
	int langCodeLength = status & 0x3F;
	
	try {
		String text = new String(payload, langCodeLength + 1, payload.length - langCodeLength - 1, encoding);
		String langCode = new String(payload, 1, langCodeLength, Charset.forName("US-ASCII"));
		String id = new String(record.getId());
		text = convertToLF(text); //Androidの改行コードにあわせる
		return new TextRecord(text, id, langCode, encoding);
	} catch (UnsupportedEncodingException e) {
		throw new RuntimeException(e.toString());
	}
}
	
public static byte[] concat(byte[]... bytesArray) {
	byte[] ret = null;
	for (byte[] bytes: bytesArray) {
		if (ret == null) {
			ret = bytes;
		} else {
			byte[] c = new byte[ret.length + bytes.length];
			System.arraycopy(ret, 0, c, 0, ret.length);
			System.arraycopy(bytes, 0, c, ret.length, bytes.length);
			ret = c;
		}
	}
	return ret;
}
public static String convertToCRLF(String txt) {
	return txt.replaceAll("\r\n", "\n").replaceAll("\r", "\n").replace("\n", "\r\n");
}
public static String convertToLF(String txt) {
	return txt.replaceAll("\r\n", "\n").replaceAll("\r", "\n");
}
public static class TextRecord {
	public String text, id, langCode, encoding;
	public TextRecord(String text, String id, String langCode, String encoding) {
		this.text = text; this.id = id; this.langCode = langCode; this.encoding = encoding;
	}
}
MIME Record
  • TNF : 0x02(TNF_MIME_MEDIA)
  • TYPE : MIME Type
  • PAYLOAD : MIMEに対応するデータ

API Level 16ならNdefRecord.createMime(String mimeType, byte[] mimeData)で生成可能。

AAR(Android Application Record)
  • TNF : 0x04(TNF EXTERNAL_TYPE)
  • TYPE : android.com.pkg
  • PAYLOAD : パッケージ名

NDEFの末尾に含めることが推奨されている。
Android Gingerbread(API Level 10)では正しく認識されない。
API Level 16ならNdefRecord.createApplicationRecord(String packageName)で生成可能。