半熟前端

軟體工程師 / 台灣人 / 在前端的路上一邊探索其他領域的可能性

iOS

How to read SUICA information with CoreNFC (FeliCa)

Preface

iOS 11.0 can read and write NFC Tag through CoreNFC , but it can't read information such as IC cards. IC cards can only be read by iOS device after iOS 13.

I have been interested in NFC for a long time, I also want to read the information of Suica (Japanese transportation IC card) by myself so I can directly check the balance with my mobile phone (I know there are already serveral apps doing this, but I want to do it for study purpose)

There are relatively few Chinese, English resources on the Internet about Suica, so I spent a few days researching the FeliCa documentation and implemented it with CoreNFC.

This article will start from NFC protocol first, then talk about FeliCa, which is widely used in Japanese transportation IC cards, and finally, the implementation in swift.

While I just start learning swift, there may have some places that are not well written, feel free to pin me on twitter.

What is NFC?

NFC (Near Field Communication) is a communication protocol, also known as RFID wireless communication technology.

This agreement mainly defines as such:

  • Communication agreement: how to communicate between sender and receiver
  • Data exchange: how exchange data between sender and receiver

While it is possible to write data into card(if you have key), this article will only focus on how to read the data in FeliCa.

The Problems solved by NFC

In wireless communication we can use Bluetooth, wifi, etc. to communicate, but the biggest problem is about security and matching.

Bluetooth need to be paired before communicating with each other. It might take time to match, and this communication is more complicated.

If the reader can detect card information from 20 meters and directly request payment, there will be big problems.

Therefore, in NFC, the detection distance is usually within a few centimeters not only to ensure the security, it can also reduce the noise interference.

FeliCa

Felica is a IC card technology developed by Sony in 2001. Compared with NFC Type-A and Type-B, it is faster. The reason why it's so fast may because of the terrible commute traffic in Japan.

Youyou card in Taiwan is an IC card made by Mifare designed by Philips. At the same time, Mifare is also a non-contact IC card widely used around the world.

It seems that only FeliCa is widely used in Japan currently.

FeliCa is really fast. If you use public transportation in Japan, you should find that you don't have to stop your step when entering the platform

It currently widely used transportation IC cards such as Suica, ICOCA, は や か け ん, etc. all of this card use FeliCa technology.

Not only in transportation, you can also use your transportation IC card to pay in convenience store.

Data security

In NFC, some data can be read, some data cannot, and some cards need to be decrypted with the correct key to read and write data.

Although Android and iOS can now read NFC cards, if you want to modify the balance and other data, you must have the correct key.

FeliCa architecture

In FeliCa's architecture, it can be divided into two major part: private domain (プ ラ イ ベ ー ト) and common domain.

The common domain stores some information that can be read, while the private domain stores some information such as personal data or control balances, which must be encrypted and decrypted to operate.

In the common field, it can be divided into several parts:

  • System: indicates the unit of the card. Each card will have a system code (2 byte). According to law of JIS 6319-4 , this value will be between FE00 ~ AA00 . Cards like Suica are 0003(this value is important, we'll use it later)
  • Area: Contains information such as storage space, number of blocks stored by each service, and so on.
  • Service: Store data in blocks for external access. Each service will have a service code (2 bytes), such as the service code of the entry record 090f. service can be divided into random service, cyclic service, pass service
  • Block: Where data is stored, each block is 16 bytes, and the number of blocks required will vary depending on the service.

If you only want to read the information inside the card, you will encounter two parts: service and block.

Service Type

As mentioned earlier, services are divided into random, cyclic, and pass, which are mainly distinguished by data access.

  • Random Service: data that can be read and written freely, determined by the manufacturer
  • Cyclic Service: A place where you can store records like historical data.
  • Pass Service: A place to manage things like balances and deductions

Command

There are many kinds of instructions in FeliCa , which can be found in the FeliCa document . To read FeliCa data, you need several command:

  • polling
  • request service
  • Read without encryption

Each command has a corresponding request pocket, which specifies what should be in the request. This part already provides corresponding functions in CoreNFC so we don't have to worry about that.

Communication process in FeliCa

  1. Capture cards with polling command.
  2. Using the Request Service command and passing in the service code list, the card will confirm whether the service code is correct or readable. If service does not exist or error, it will return0xFFFF
  3. Use the read without encryption command and pass in the service code to return the corresponding data in blocks. (Up to 16 services)

    1. This command will return status1 and status2. If both are 0, it means no problem.
    2. If it is not 0, it means there is an error. You can find out error code status in FeliCa documentation

iOS implementation

You can refer to the examples on the apple developer to find out how to read the NFC Tag, here we are just focusing on how to read FeliCaTag

To implement NFC, you must first have a physical iPhone. There is no way to read NFC in simulator.

You can use NFCTagReaderSession.isReadingAvailable to determine if NFC data is readable in your device.


First refer to the CoreNFC file and add the corresponding info.plist and entitlement.

1. Add Near Field Communication Tag Reading in Capabilities

ios-capability

2. Add ISO18092 system codes for NFC Tag Reader Session to Info.plist

plist

  • Corresponding to the previous system code, if you want to read a card like Suica, it should be 0003
  • Add Privacy-NFC Scan Usage Description

CoreNFC implementation

In NFCTagReaderSession , polling, requestService, and readWithoutEncryption already have corresponding function to use, so we don't have to handle lower level data communication.

However it's the general process for handling NFC data:

  1. Establish NFCTagReadersession and set pollingOption and delegate
  2. call session.begin()
  3. After reading the card, the session will call the corresponding delegate method
  4. Implement your main reading logic in func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag])

    1. Call requestService here
    2. After confirming the service response, call readWithoutEncryption

NFCTagReaderSession

To make the class have the ability to read NFC, you must first implement NFCTagReaderSessionDelegate

public protocol NFCTagReaderSessionDelegate : NSObjectProtocol {
    // call when session is readable
    func tagReaderSessionDidBecomeActive(_ session: NFCTagReaderSession)

    // call when session is not readable
    func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error)

  	// call when session detect tag.
    func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag])
}

You can find the current tag types on apple developer, both MiFare and FeliCa is supported

Block List

block list is which part to read in a service. In readWithoutEncryption blockList is required as a parameter, in CoreNFC you can write like this:

let blockList = (0..<UInt8(10)).map { Data([0x80, $0]) }

10 here means 10 blocks are returned.

FeliCa Reader

To read FeliCa data, you first need to know what's the meaning of each service code, for example card balance or your usage history. You can find it on this website (Japanese) :

When the System Code is 0003:

  • service code: 008B (1 block) record card type and balance
  • service code: 090F (20 block) ride history (20 records)
  • service code: 108F (3 blocks)

Among them, the ride history and balance should be the most interesting part to me. Let's see what information is recorded in the content of this block:

A block has 16 bytes, and each byte stores the corresponding data. The following lists some according to the documents on the website. The number represents the number of bytes. The brackets represent the length of this data.

  • 0 (1 byte): Machine category
  • 1 (1 byte): usage category (actuarial, new rules, auto-reload, etc.)
  • 2 (1 byte): payment method (credit card, mobile, etc.)
  • 3 (1 byte): type of entry (entrance, entry, etc.)
  • 4 to 5 (2 byte): year, month, day (year 7 bit, month 4 bit, day 5 bit). The year is based on 2000, so 19 represents 2019.
  • 6 ~ 9 (4 byte): station code for entrance and exit, vending machine information
  • A ~ B (2 byte): balance (Little Endian)
  • C: unknown
  • F: Local code (Kanto private railway, Chubu private railway, Okinawa private railway, etc.)

After understanding this kind of information, we can use for data in dataList read the data!

For example, the time of entry and exit can be:

let year = data[4] >> 1 // I'm not sure why it's required to shift one bit
let month = UInt(bytes: data[4...5]) >> 5 & 0b1111 // get month bit
let date = data[5] & 0b11111 // get date bit

let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
formatter.locale = Locale(identifier: "en_US_POSIX")
return formatter.date(from: "20\(year)-\(month)-\(date)")!

Demo

You can see the source code on github .

Thought

Although the information was successfully read, some numbers are different from what I expected.

For example, the station code has not been found after searching for a long time on the Internet. I don't even know if I parsed station code in correct way.

Although it is said that the history record can get 20 blocks, some cards will fail to read because of this, the safer range is 10. (Still testing)

Looking at the records inside (my card is a regular ticket), it seems that not every entry and exit will be recorded. If you want to use cards to collect your own inbound and outbound information, I am afraid it is not so easy to use (such as visualization).

Regarding FeliCa reading, on iOS, Japanese developers have written a set of library TRETJapanNFCReader which is quite easy to use . If you want to use it directly, you can refer to it. In addition to SUICA, he also supports quite a few types of IC cards. Even the driver's license can be read.

When I saw the CoreNFC SDK, I wanted to have a try to read FeliCa information, but there are not many Chinese resources on the Internet.

There are quite a lot of Japanese implementations. but I can just see the service code, blockList and so on without knowing the meaning of that. So it took a day to read the FeliCa documentation and try to implement by myself.

Also quite troublesome on the swift conversion of another type, such as the Data converted into UInt and so on, and got this code from the Internet, but do not really understand what he was doing XD, I can only know UInt(bytes: Data)the way for Conversion.

import Foundation

extension FixedWidthInteger {
    init(bytes: UInt8...) {
        self.init(bytes: bytes)
    }

    init<T: DataProtocol>(bytes: T) {
        let count = bytes.count - 1
        self = bytes.enumerated().reduce(into: 0) { (result, item) in
            result += Self(item.element) << (8 * (count - item.offset))
        }
    }
}

It was a long journey to read all of this, isn't it? I wish you could understand how NFC works, what FeliCa is and more importantly, how to use it combine with CoreNFC in Swift!