FeliCa が遅いしフルスキャンも不可能 前編【iOS 13 Core NFC】

気づけば Advent Calendar も後半戦…。
私はこれまで FeliCa を iOS 13 の Core NFC で読み取るサンプルを紹介してきました。

- [iPhone で電子マネー（楽天Edy、nanaco、WAON）を読み取ってみよう！](https://qiita.com/treastrain/items/f5c518486d19ba3573d3)
- [iPhone で交通系IC（Suica、PASMO、ICOCA、…etc.）を読み取ってみよう！](https://qiita.com/treastrain/items/d54ef2c2abbcd40fb9f1)

そして、前回の

- [FeliCa システムコードの切り替えは Polling コマンドのみで【iOS 13 Core NFC】](https://qiita.com/treastrain/items/04f50a91f70fd6480fc0)

では iOS での FeliCa の取り扱いの制限（システムの切り替えを IDm で行うことができない）について述べました。

…ですが、現在の iOS 13 での FeliCa に関する制限はまだまだあるのです…。

# この記事で紹介する iOS での FeliCa の制限
まとめを先に書いておきます。

- FeliCa の読み取りで得られる最初の `currentSystemCode` は Info.plist での FeliCa システムコードの**記述する順番に依存**する
- FeliCa の読み取り速度は Info.plist での FeliCa システムコードの**記述する順番に依存**する
- ~~FeliCa のフルスキャンを iOS で行いたい場合、「じゃあ Info.plist に存在する全ての FeliCa システムコードを記載すればいいのでは…？」と考えつくが、**それは現実的な解決策ではない**~~ → [後編につづく](https://qiita.com/treastrain/items/107f6b77183617251c20)

# FeliCa の読み取りで得られる最初の `currentSystemCode` は Info.plist での FeliCa システムコードの記述する順番に依存する

## 異なる FeliCa システムコードを持つカードを読み取ろうとしてみる

これまでに書いた[「楽天Edy、nanaco、WAON」を読み取る方法](https://qiita.com/treastrain/items/f5c518486d19ba3573d3)と、[「交通系IC（Suica、PASMO、ICOCA、…etc.）」を読み取る方法](https://qiita.com/treastrain/items/d54ef2c2abbcd40fb9f1)で、**あらかじめ 使う FeliCa システムコードを Info.plist に追加しておく必要がある** と記載してきました。楽天Edy、nanaco、WAON で使われている FeliCa システムコードは `0xFE00`、交通系IC で主に使われている FeliCa システムコードは `0x0003` になります。

では、異なる FeliCa システムコードを持つ 楽天Edy と 交通系IC の残高を読み取るための iOS App を開発するにはどうすればいいでしょうか。
Capability と Entitlements の設定は済ませておくとして、まずは使う FeliCa システムコードを Info.plist に追加します。

<img width="506" alt="スクリーンショット 2019-12-18 11.30.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90125/b9d01cd0-b3e8-64d9-2f9a-4cfe0c8e5e5f.png">

こんな感じで **Item 0** に `0xFE00`、**Item 1** に `0x0003` を指定しました。
そして、`NFCTagReaderSessionDelegate` の `tagReaderSession(_:didDetect:)` を書いて検出された FeliCa タグに接続、データの読み取る準備をします。

```swift
func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
    let tag = tags.first!
    session.connect(to: tag) { (error) in
        if let error = error {
            session.invalidate(errorMessage: error.localizedDescription)
            return
        }
        
        guard case NFCTag.feliCa(let feliCaTag) = tag else {
            session.invalidate(errorMessage: "FeliCa ではない")
            return
        }
        
        session.alertMessage = "カードを読み取っています…"
        
        var serviceCode: Data!
        var startIndex = 0
        var endIndex = 0
        switch feliCaTag.currentSystemCode {
        case Data([0x00, 0x03]):
            // 交通系IC
            serviceCode = Data([0x00, 0x8B].reversed())
            startIndex = 11
            endIndex = 12
            break
        case Data([0xFE, 0x00]):
            // 楽天Edy
            serviceCode = Data([0x13, 0x17].reversed())
            startIndex = 0
            endIndex = 3
            break
        default:
            fatalError()
        }
        
        let blockList = [Data([0x80, 0x00])]
        feliCaTag.readWithoutEncryption(serviceCodeList: [serviceCode], blockList: blockList) { (status1, status2, blockData, error) in
            if let error = error {
                session.invalidate(errorMessage: error.localizedDescription)
                return
            }

            guard status1 == 0x00, status2 == 0x00 else {
                print("ステータスフラグがエラーを示しています", status1, status2)
                session.invalidate(errorMessage: "ステータスフラグがエラーを示しています s1:\(status1), s2:\(status2)")
                return
            }

            let data = blockData.first!
            let balance = data.toIntReversed(startIndex, endIndex)

            print(data as NSData)
            print("残高: ¥\(balance)")
            session.alertMessage = "残高: ¥\(balance)"
            session.invalidate()
        }
    }
}
```

こちらに示した `tagReaderSession(_:didDetect:)` 以外のコードは全て[前々回の記事などと同じですので、そちらを参考にしてください。](https://qiita.com/treastrain/items/d54ef2c2abbcd40fb9f1#直接-core-nfc-を使う場合)

検出された FeliCa カードの `currentSystemCode` が交通系ICのものか、楽天Edy のものかによって `switch` し、それぞれの `serviceCode` 等を指定しています。

ではビルドして実機で動作を確認してみましょう。

### 楽天Edy（カード型）を読み取った場合
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90125/849526ba-4d31-3f9a-2209-5076ddafd587.jpeg">
残高が正しく表示されました。

### Suica（Apple Pay）を読み取った場合
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90125/8fd7d4dc-231b-a52d-fe0a-b122dfd43972.jpeg">
カード型でない、Suica が入っている Apple Watch に iPhone を近づけると…これも正しく残高を読み取ることができました。

### Suica（カード型）を読み取った場合
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90125/290e3663-8417-2dee-f617-d6819a0cffef.jpeg">
では普通のカード型の Suica を読み取ると……と、**ステータスフラグがエラー**……？

`status1` が `1`、`status2` が `166` (`0xA6`)というのは、"サービスコードリスト不正" にあたるので、サービスコードの指定が間違っていることになります。**どうして Apple Pay の Suica は読み取れたのに、カードの Suica はダメだったんだろう…？**

## それぞれが持つ FeliCa システムコードを調べる
では、[Japan NFC Reader](https://apps.apple.com/jp/app/id1480265213) を用いて Apple Pay の Suica とカード型の Suica が持つ FeliCa システムコードを調べてみましょう。
「その他」にある「IDm・PMm を読み取る」からカードをスキャンすると、入っている FeliCa システムコードを調べることができます。

### 楽天Edy（カード型）が持つ FeliCa システムコード
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90125/d6b9f1bb-5998-7d47-8b64-27ac154ddcc5.jpeg">
カード型の楽天Edyは `0x8B61` と `0xFE00` の2つの FeliCa システムコードを持っていることがわかります。

### Apple Pay の Suica が持つ FeliCa システムコード
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90125/e4ac2e10-c1df-8fc6-83c0-ec3a1e6f2f9f.jpeg">
Apple Pay の Suica には確かに `0x0003` があることを確認できますね。

### Suica（カード型）が持つ FeliCa システムコード
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90125/2c1780b7-f831-8d9f-6e94-47430b13bea6.jpeg">
先ほど 読み取りに失敗したカード型の Suica は `0x0003` の他に… **`0xFE00` も持っている！？** [^1]

[^1]: Suica や PASMO 等、カードの発行時期などによってカードに含まれる FeliCa システムコードの数が異なります。

## FeliCa の読み取りで得られる最初の `currentSystemCode` は Info.plist での FeliCa システムコードの記述する順番に依存する

なぜこのような問題が起きたのでしょうか。
今回、`switch` で `currentSystemCode` が何かによって Suica か 楽天Edy かを判別しました。そして、`0x0003` のみを持つ Apple Pay の Suica は `currentSystemCode` が `0x0003` であったため、無事に Suica と判別できましたが、カード型の Suica には `0x0003` の他に `0xFE00` も持っていたため、`currentSystemCode` が `0xFE00` となり 楽天Edyルートに入ることになってしまいました。

もし、FeliCa システムコードを用いてカードを判別しようとするとき、**Info.plist に記述する FeliCa システムコードの順番が鍵となる**場合があります。

<img width="500" alt="スクリーンショット 2019-12-18 11.30.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90125/b9d01cd0-b3e8-64d9-2f9a-4cfe0c8e5e5f.png">

いまは **Item 0** に `0xFE00`、**Item 1** に `0x0003` を指定しました。この順番を逆にします。

<img width="500" alt="スクリーンショット 2019-12-19 5.02.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90125/6cf5f338-5374-dd11-846e-070dac782010.png">

この Info.plist 以外はコードの変更を行わないまま、ビルドしてもう一度カード型の Suica をスキャンしてみましょう。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90125/45c4756d-b05f-99a1-6c62-69d8144bedd9.jpeg">

無事に正しい残高が表示されました！

# FeliCa の読み取り速度は Info.plist での FeliCa システムコードの**記述する順番に依存**する
PaSoRi などでワイルドカードを使用して FeliCa カードに Polling をした場合、FeliCa に搭載されている `0` 番目のシステムが検出されるかと思いますが、iOS 13 の Core NFC の場合は**検出された FeliCa カードにあるシステムのうち、Info.plist に記載され 最も順番が前にある FeliCa システムコードに一致するシステム** が検出、`currentSystemCode` および `currentIDm` にはそれが入ることになります。

そのため、多数の FeliCa システムコードに対応しようとした場合、**Info.plist に記載する順番が非常に重要になります。**

<img width="500" alt="スクリーンショット 2019-12-19 5.21.09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90125/2d2bb845-8f80-734b-daff-c0b9a0be0545.png">

取り上げたサンプルでは、FeliCa システムコードが2つだったので特に問題はありませんでしたが、上記スクリーンショットにある Item 4 の `0x8008` が検出されるまでには前4つ（`0xFE00`、`0x0003`、`0x865E`、`0x8592`）に比べて時間がかかります。

これが iOS 13 Core NFC で FeliCa を扱う上でまた足を引っ張ることに………。

[**後編に続く**](https://qiita.com/treastrain/items/107f6b77183617251c20)