PCSCでAndroidとかのスマホをうまく処理する

前置き

Androidスマホみたいに一つで複数のNFCサービスに対応しているような場合、
例えばTypeAとTypeF(FeliCa)に対応しているとか、FeliCa二つのシステムコードに対応しているとかあると、PCSCでアクセスしようとするとうまくいかないことがある。
これは、PCSCは通常だと対応しているカードを順番(例: TypeA -> TypeB -> FeliCa -> TypeA ・・・)に検出しようとしており、
最初に見つけたNFCサービスとして活性化されると、ほかのカード種別としては検出されなくなる(例えばTypeAで検出されたらFeliCaでは反応しなくなる)から。
加えてFeliCaはちょっと違っていて、TypeA/TypeBでは、

  1. カード検出
  2. アンチコリジョン (通信相手のカード一つを決定する)
  3. 活性化 (APDU通信できるようにする)

と順番に処理がなされ、全てが完了するとPCSCがカードを検出したと通知がある。
そうするとAPDUで通信できるようになっているので、自分のコードでSELECT AIDして対象のアプリを選択できる。
だがFeliCaはPolling一発で1~3をやってしまう上に、PCSCの場合はシステムコードFFFFhとして全てを検出しようとする。
よって複数システムコードがある場合どれが活性化するのか、指定できなくて困るということになる。

PCSCはちゃんとこれらに対処できる。

対処

参照ドキュメント

  • PCSC Specifications
  • 使うNFCリーダライタ(RW)のドキュメント (あったほうが良いかもしれない)

今回大事なPCSC SpecはPart3 sup2。Section 4以降に制御コマンドが書いてある。

制御手順

RW毎に動作差異があると思うが、大体下記の通り。

  1. カード検出を待たずにRWと直接接続して、Control()関数で以下コマンド制御する
  2. Manage Session Command - Start Transparent Session
  3. Switch Protocol Command - Switch protocol Data Object
  4. Transparent Exchange Command - Transceive
  5. 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が良いかもしれない

See also