ラノベ日記

ラノベの感想用

カテゴリ: FeliCa/NFC

Android4.4から利用可能になったホストベースカードエミュレーション
早速使ってみました。
(会社のNexus7。個人持ちのXperiaVLが4.4になる事は……もう無いのでしょうね)

カードエミュレーションの特徴
・端末にISO14443Aカードの振りをさせられる
 (リーダーライターから見ると、ただのカードです)
・画面ロックを解除しなくても反応する
・送受信のタイミングをアプリで制御できる
 (AndroidBeamの場合、ユーザーのタッチにより送信)
・ディスプレイの電源が入っていない場合は反応しない
 (モバイルFeliCaはディスプレイや端末の電源が入っていなくても動作する)

ポーリング
ISO14443A 106kbpsポーリングで端末を捕捉できます。
(SELRESの6bit目によりカードエミュレーションの可否を判断できます)
※Android4.4未満の端末でも、SIMカードが挿入されている場合はSELRESの6bit目が1になります
その場合は、後述するSELECT FILEにより対応の確認を行います

ISO7816-4 SELECT FILEコマンドにより
自作のアプリ(Serviceとして実装)を起動できる

0x00 0xA4 0x04 0x00 0x07 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x01 0x00
0x00(CLA)
0xA4(SELECT FILE)
0x04(DF-name)
0x00(First record)
0x07(アプリ識別子の長さ)
0x01 〜0x01(アプリ識別子。これに一致するアプリが起動されます)
0x00(レスポンスデータ長)

・指定したアプリがインストールされていない場合
・Android4.4未満の場合
はFileNotFound(0x6A 0x82)がレスポンスされます。
※SIMカード内にアプリ識別子の一致するものがあれば応答するものと思われます

アプリが起動すると
リーダライターとアプリがシリアル通信可能な状態となる
(カードから命令を送ることはできませんから、常にリーダー側からコマンドを送る事になります)

ペイロード部がそのまま送受信されるため、READ、WRITEはあまり関係ありません

Android4.4の詳細が発表されました。
http://gigazine.net/news/20131101-new-function-android-os-kitkat/

OSのRAM使用量が減るんでしょ?
古い端末でも動くんだよね?
という前評判だったのですが、蓋を開けると驚きの新機能がありました。

NFCカードエミュレーション
え?
え??

スクリーンロック解除して、画面タッチするのがAndroidのNFC……
リーダーライター(レジや改札機)に翳すだけでよいのは、日本のおサイフケータイだけ!!

という住み分けではなかったですか?

え、Android4.4からは
おサイフケータイの機能を使わなくても、翳すだけで通信が可能なの?

これ……
FeliCaの優位性が失われてしまったのではないですか?


ちなみにおサイフケータイを管理しているFeliCaネットワークス社は
おサイフケータイ機能が使われる初回時(アプリをインストールして一回目)
にアプリの配信会社に120円〜250円を課金するビジネスをしています。

マクドナルドのおサイフケータイアプリは
1000万台にインストールされているそうですから
250円*1000万台=25億円
これが2〜3年周期で機種変更されると考えれば
年間10〜20億円の売上げ?になるのでしょうか。

ソニー、ドコモという大企業の子会社なので
大した痛手ではないのかもしれませんが。

おサイフケータイが不要になってしまうと、日本のNFC技術普及の一角を担ってきたFeliCaネットワークス社が終わってしまうかもしれません。

まぁ、アプリ開発者からすれば
無料アプリにもNFC機能を組み込めるようになるわけですから、良いことですけどね

以前より提供していたFeliCa、Mifareアクセスライブラリのソースコードを公開しました

公開されている仕様書を元にしたものなので、当然ライセンスフリーです。
(RC-S330のReadWithoutEncryption(WriteWithoutEncryption)については、
ソニー社からNDA事項として入手した情報が元になっていた事を思い出し、非公開としました。
RC-S380については、公開情報のみで実装しているので問題ありません)

ソースコードを見ればそのままなのですが……
http://www.nxp.com/documents/data_sheet/MF1S50YYX.pdf
とか
http://www.sony.co.jp/Products/felica/business/tech-support/data/card_usersmanual_2.0.pdf
これに書かれているコマンド・レスポンスをそのまま実装したものです。

NFCなんて簡単じゃないか!?
って思ってもらえれば幸いです。

今までもフリーソフトを公開していたのですが
ソースコードをzipファイルにし、dropboxの公開ディレクトリに置いていたため、ソースコードへの参照等はできませんでした。

今回、githubにアップロードしたことで、どなたにでも自由に編集(clone)して頂くことが可能になりました。
ソースコードに対するコメントも自由に行えます。

https://github.com/tijins/NfcLib

会社の携帯が借りられなくなったので(開発用に買ってあったNexusSが展示会デモへ)
私物をXperiaVLに機種変更してきました。

軽くて薄い。
保存領域16GB、RAM1GB。
このあたりのスペックは、とても満足。

で、機種変更の目的となったNFCと、Bluetooth4.0
→勤務先の新製品デバッグ用じゃないですか……

NFC
酷い。下調べしていたとおりP2P通信はNfcA/106kbpsのみ。

まぁ、これは予想通り。
もっと許せない事が分かってしまって絶望中


なんと
MifareClassic、MifareUltralight
に非対応。

ハードウェアなしでの対応が難しいMifareClassicは仕方ないとして
NFC Type2 tag と互換であるMifareaUltralightに対応してないというのは
明らかな怠慢じゃないですかね……

MifareはFeliCaの10倍以上出荷されている、最も一般的な非接触ICカードです。
Googleのリファレンス端末NexusSは当然としてGalaxuシリーズでも対応しています。

NDEFストレージとして認識されないのでは、mifareを利用したタグは配布できませんね。
くずい……

NDEF(NFC Data Exchange Format)は、非接触ICカードにデータを保存するための規格です。

AndroidスマートフォンやWindows8が正式対応しており、カードをかざすだけで、URLを開いたり、アドレス帳への登録が可能です。

さてNFCといえは、FeliCa Lite(Type3)や、Mifare Ultralight(Type2)が一般的なわけですが、Mifare Classicが使えないわけでもありません。

Mifare Classicの特徴
FeliCa LiteやMifare Ultralightにはない、鍵付き領域があります。
鍵(6Byte)を設定する事で、不正な読み書きを防止可能です。
(読み書き、読込みのみ等、柔軟な設定が可能)

●NDEFを保存する
MAD(Mifare Application Directory)
第0セクターに、MAD(カード内容を表すヘッダー)を書き込みます。
MADには、第1〜第15セクター、それぞれの利用用途(AID=Application ID)を記載します。

NDEFを表すAIDは0x03E1です。

NDEFセクター
AIDを0x03e1としたセクターには、ひとつ以上のTLVブロックを保管します。
TLV=Type 、Length、Value

TYPE
NDEFメッセージを表すTypeは0x03です。

LENGTH
格納するデータが0〜0xFEまでであれば1byteで、0xFF以上であれば3Byte(0xFF 0xSIZE 0xSIZE)で格納します。

Value
NDEF Forumで定義されるNDEF Messageを格納します。

ターミネーターTLV
最後のTLVブロックは、ターミネーターTLVにします。
T=0xFE
Lなし
Vなし


このフォーマットでカードに書き込む事で、Androidが認識します。

MADにてNDEF以外を設定したセクターには、鍵を設定した任意のデータが保存可能です。
これは、Type1〜Type3タグには無いメリットです。

調べてみると、かなりFeliCaよりな設計になっていることが分かります。
(NFC-ICのセキュリティエリア上に実装すれば単純な気がしますが……
 FelicaNetworks社専用品ということなので、また耐タンパ機能等に拘っているのでしょう)

FeliCaカードとして使う
カードそのものとして動作しますが……
Android端末自信がInitiatorとなっている期間は、外部のR/Wからの通信を受けません。

PN532を利用した場合、デフォルトのタイムアウト値(102ms)では、結構な頻度でInitiatorの期間と被り、ポーリングに失敗しました。
→タイムアウト値を25msにしたころ、ほとんど失敗しなくなりまいた。

NFCモード
NfcA(106kbps) Passiveのみ対応です。
※海外製のNexus7やGalaxyシリーズが、212kbps、424kbpsに対応していることを考えると、かなり残念です。

FeliCa/NFC関連のまとめ

◯PaSoRi
 ソニー製リーダーライター。RC-S380以降はNFC対応している
 .net framework対応ライブラリ(FeliCa、mifareへの読み書き対応)


◯ACR122U
 ACS社製リーダーライター
 AndroidBeamシミュレーター

 LLCPの実装方法1
 LLCPの実装方法2
 LLCPの実装方法3
 LLCPの実装方法4

◯Android
 FeliCa/Mifare/ISO15693への読み書き
 P2P通信はAndroidBeamとしてOSに制御されている
 カードエミュレーションは不可

送信・受信に対応したAndroidBeamシミュレーターを公開します。
https://dl.dropboxusercontent.com/u/55426081/SnepSender.zip

これは、Activeモード、Passiveモード、通信速度(106kbps,212kbps,424kbps)を設定してSNEP通信ができるアプリです。
※例によって、実行にはNFCリーダーライターACR122Uが必要です。
 ソースコードを添付しているので、他のリーダーライターへの対応も容易だと思います。

BeamReader


まぁ、見たまんまですが……
端末によって対応する通信速度に違いがあるので、確認するのには便利だと思います。

NexusSでは
 Activeモード:106kbps,212kbps,424kbps全て対応
 Passiveモード:212kbpsのみ対応
まだ検証出来ていないのですが、国内メーカーの端末では全く違う対応状況のようです。
どういう意図で実装されているのか全く分かりません。

受信はTargetモードにも対応しています。
→TargetモードでAndroid端末がInitiatorになるのを待ち受ける事で、端末から発せられている電波の種類を調べられます。
(NfcF/424kbps/Activeモード/ランダムID/DID無しだと思います)

受信側は公開していましたが、送信側のサンプルも公開します。
https://dl.dropbox.com/u/55426081/SnepSender.zip

2013/3/15追記
http://blog.livedoor.jp/esper776/archives/65847214.html
送受信対応版を公開しました


AndroidBeamに関する基本事項
・NfcA又はNfcFで接続する
・LLCP通信
・SNEPのPUTでNDEFファイルを送信する
これだけです。

メイン処理はこんな感じ。
LLCP_sender_diagram


ソース

private bool LlcpSession()
{
destCh = SnepHelper.DSAP;
state = LlcpStatus.WaitCC;
PDU sendTo = null;
Connect con = new Connect() { Dsap = destCh, Ssap = myCh };
byte[] ret = rw.InDataExchange(con.ToBytes());

while (!abort)
{
PDU pdu = LlcpHelper.GetPdu(ret);
if (pdu == null)
{
Release();
return false;
}

if (state == LlcpStatus.DISC)
return true;

switch (pdu.Kind)
{
case Symm.KIND:
sendTo = new Symm();
break;

case Ui.KIND:
case Connect.KIND:
case Disc.KIND:
case Frmr.KIND:
case Rnr.KIND:
default:
sendTo = new Dm() { Dsap = 0, Ssap = myCh };
abort = true;
break;

case Cc.KIND:
if (state == LlcpStatus.WaitCC)
{
destCh = pdu.Ssap;
vS = vSA = vR = vRA = 0;
sendIndex = 0;
sendTo = GetSendData();
state = LlcpStatus.SEND;
}
else
{
sendTo = new Dm() { Dsap = 0, Ssap = myCh };
abort = true;
}
break;

case Information.KIND:
if (state == LlcpStatus.SEND)
{
vR = ((Information)pdu).SeqS + 1;
vSA = ((Information)pdu).SeqR;
if (sendIndex == sendData.Length)
{
sendTo = new Rr() { Dsap = destCh, Ssap = myCh, SeqR = (byte)(vR % 0x0f) };
state = LlcpStatus.DISC;
}
else
{
sendTo = GetSendData();
}
}
else
{
sendTo = new Dm() { Dsap = 0, Ssap = myCh };
abort = true;
}
break;

case Rr.KIND:
if (state == LlcpStatus.SEND)
{
vSA = ((Rr)pdu).SeqR;
sendTo = new Symm();
}
else
{
sendTo = new Dm() { Dsap = 0, Ssap = myCh };
abort = true;
}
break;

}
ret = rw.InDataExchange(sendTo.ToBytes());
}
Debug.WriteLine("SNEP Push Complete");
Release();
return true;
}