前置き
Androidスマホみたいに一つで複数のNFCサービスに対応しているような場合、
例えばTypeAとTypeF(FeliCa)に対応しているとか、FeliCa二つのシステムコードに対応しているとかあると、PCSCでアクセスしようとするとうまくいかないことがある。
これは、PCSCは通常だと対応しているカードを順番(例: TypeA -> TypeB -> FeliCa -> TypeA ・・・)に検出しようとしており、
最初に見つけたNFCサービスとして活性化されると、ほかのカード種別としては検出されなくなる(例えばTypeAで検出されたらFeliCaでは反応しなくなる)から。
加えてFeliCaはちょっと違っていて、TypeA/TypeBでは、
- カード検出
- アンチコリジョン (通信相手のカード一つを決定する)
- 活性化 (APDU通信できるようにする)
と順番に処理がなされ、全てが完了するとPCSCがカードを検出したと通知がある。
そうするとAPDUで通信できるようになっているので、自分のコードでSELECT AIDして対象のアプリを選択できる。
だがFeliCaはPolling一発で1~3をやってしまう上に、PCSCの場合はシステムコードFFFFhとして全てを検出しようとする。
よって複数システムコードがある場合どれが活性化するのか、指定できなくて困るということになる。
PCSCはちゃんとこれらに対処できる。
対処
参照ドキュメント
- PCSC Specifications
- 使うNFCリーダライタ(RW)のドキュメント (あったほうが良いかもしれない)
今回大事なPCSC SpecはPart3 sup2。Section 4以降に制御コマンドが書いてある。
制御手順
RW毎に動作差異があると思うが、大体下記の通り。
- カード検出を待たずにRWと直接接続して、Control()関数で以下コマンド制御する
- Manage Session Command - Start Transparent Session
- Switch Protocol Command - Switch protocol Data Object
- Transparent Exchange Command - Transceive
- Manage Session Command - End Transparent Session
概説
- (あたりまえだけど)カード検出前でもリーダライタと通信できる
- カード検出前なのでTransmit()は使えない。Control()を使う
- Control()に渡す制御コードはRWマニュアル参照(たぶん同じ値と思うが)
- 一度活性化されたカードは搬送波の外に出して確実にリセットしたほうが良いので、Switch Protocol前後でRF Off -> 適切なWait -> Rf Onしたほうが良いかもしれない
- Switch Protocolのパラメータ例が Section 8 にある。前置きの通りにするならTypeA/TypeBは活性化(0004h/0104h)まで、FeliCaの場合は変調(0300h)のみにする、
TypeA/TypeBをより細かく制御したければすれば良し - Transparent Exchange Command で最初に投げるコマンドは、
TypeA/TypeBなら SELECT AID、FeliCaならPolling(もちろん好きなシステムコードで)
うまくいっていれば正常応答が得られる。以降、後続の必要なコマンド処理をすれば良し
心構え
- うまく動かないときはエラーコードをきちんと確認する。
大体のサンプルコード
C#で。NugetにPCSCパッケージあるのでそれを使う。
using PCSC;
そして、例えばFeliCaオンリーなら以下コードとなる。
(エラー処理、レスポンス確認してないけど)
var contextFactory = ContextFactory.Instance;
using (var context = contextFactory.Establish(SCardScope.System))
{
var readerName = context.GetReaders()[0];
using (var reader = context.ConnectReader(readerName, SCardShareMode.Direct, SCardProtocol.Unset))
{
var response = new byte[1024];
// command packet
var commandSessionStart = new byte[] { 0xFF, 0xC2, 0x00, 0x00, 0x02, 0x81, 0x00, 0x00 };
var commandSessionEnd = new byte[] { 0xFF, 0xC2, 0x00, 0x00, 0x02, 0x82, 0x00, 0x00 };
var commandSwitchFeliCa = new byte[] { 0xFF, 0xC2, 0x00, 0x02, 0x04, 0x8F, 0x02, 0x03, 0x00, 0x00 };
// Android HCE-F 自由に使ってよい System Code = 4000h
var commandFeliCaPolling = new byte[] { 0xFF, 0xC2, 0x00, 0x01, 0x08, 0x95, 0x06, 0x06, 0x00, 0x40, 0x00, 0x00, 0x03, 0x00 };
// SCARD_CTL_CODE(3500)
var controlCode = new IntPtr(0x003136B0);
// Command Sequence
reader.Control(controlCode, commandSessionStart, response);
reader.Control(controlCode, commandSwitchFeliCa, response);
// このPollingを繰り返せば、カード捕捉ループとなる
reader.Control(controlCode, commandFeliCaPolling, response);
// 以下、必要なFeliCa処理が続く
reader.Control(controlCode, commandSessionEnd, response);
}
}
その他
- Android持っていないので実際の動作確認はしていないが、RC-S380使用してTypeA/B変調にしたらはFeliCaカードを検出しなくなったので動く思う
- TypeA/B の下位層の詳細が知りたければISO14443を参照。あるいはEMVCoのLevel 1 Specが良いかもしれない