Felica RC-S330からIDmを取得する(値のリトルエンディアン化)

以前のエントリで、大須にある佐古前装備さんで購入したカードリーダーFelica RC-S330RaspberryPiから使用してみたという内容を書いていました。

f:id:ueponx:20211209234351j:plain

結論から言えば、RaspberryPiからはnfcpyの使用はできず、libpafeをインストールすれば、使用できるというような結果でした。

その後、pythonのユーザープログラムからIDmを取得してみたところ、IDmの値の並び(エンディアン)が異なっていました。そこでlibpafeを使用した場合でもnfcpytagtool.pyと同じIDmの値を取得する方法を調べてみました。

参考

uepon.hatenadiary.com

uepon.hatenadiary.com

状況の確認

まずは状況の確認になります。libpafeのインストールを行ったときに同時にインストールされるfelica_dumpというコマンドを実行してみると、交通系Felicaから多くの情報を取得することができます。すると以下の様な表示が行われます。

f:id:ueponx:20211230154158p:plain

そして、nfcpyをインストールしてtagtool.pyコマンドからIDmを取得すると以下の様な表示が行われます。(注)RaspberryPiから使用できないので、Ubuntuからの実行結果を表示しています。

f:id:ueponx:20211230154205p:plain

このときのIDmの値は同じようです。

ですが、以下のような自作のプログラムを作成し、IDmを取得してみると…

IDm.py

# -*- coding: utf-8 -*-

from __future__ import print_function
from ctypes import *

FELICA_POLLING_ANY = 0xffff

if __name__ == '__main__':

    libpafe = cdll.LoadLibrary("/usr/local/lib/libpafe.so")

    libpafe.pasori_open.restype = c_void_p
    pasori = libpafe.pasori_open()

    libpafe.pasori_init(pasori)

    libpafe.felica_polling.restype = c_void_p
    felica = libpafe.felica_polling(pasori, FELICA_POLLING_ANY, 0, 0)

    idm = c_ulonglong() #←16桁受けとる
    libpafe.felica_get_idm.restype = c_void_p
    libpafe.felica_get_idm(felica, byref(idm))

    # IDm16進表記
    print("%016X" % idm.value) #←16桁の16進数文字列の表示

    # READMEより、felica_polling()使用後はfree()を使う必要あり
    libpafe.free(felica)

    libpafe.pasori_close(pasori)

実行結果は以下のようになってしまします。

f:id:ueponx:20211230153102p:plain

tagtool.pyなどから取得されるIDm(自分の実験カード)では 0112から始まり8F06で終わる16桁の値になっているのですが、 自作プログラムで取得した値は 068Fから始まり1201で終わる16桁になっています。

つまり、エンディアン表記が異なっている状況になっています。メモリやバイナリファイルなどでは比較的エンディアンの違いに気をつけるようにはしていましたが、NFCも同じようにエンディアン気をつける必要があるとは思っていませんでした。

そこで以下の情報を参考にソースを変更することにしました。

参考 16進文字列のリトルエンディアン化変換

qiita.com

具体的な内容としては

  1. 16進数文字列を2桁のバイト配列として変換
  2. 変換後の配列の順序を反転
  3. 再度文字列化する

配列化まで行えれば反転はpythonでは容易な処理ですね。

IDm_le.py

# -*- coding: utf-8 -*-

from __future__ import print_function
from ctypes import *

FELICA_POLLING_ANY = 0xffff

if __name__ == '__main__':

    libpafe = cdll.LoadLibrary("/usr/local/lib/libpafe.so")

    libpafe.pasori_open.restype = c_void_p
    pasori = libpafe.pasori_open()

    libpafe.pasori_init(pasori)

    libpafe.felica_polling.restype = c_void_p
    felica = libpafe.felica_polling(pasori, FELICA_POLLING_ANY, 0, 0)

    idm = c_ulonglong() #←16桁を受けとる
    libpafe.felica_get_idm.restype = c_void_p
    libpafe.felica_get_idm(felica, byref(idm))

    # IDmは16進表記(リトルエンディアン化処理も行う)
    # print("%016X" % idm.value) #←16桁の16進数文字列化
    hex_be = "%016X" % idm.value
    bytes_be = bytes.fromhex(hex_be)
    bytes_le = bytes_be[::-1]
    idm_le = bytes_le.hex()
    print(idm_le)

    # READMEより、felica_polling()使用後はfree()を使う必要あり
    libpafe.free(felica)

    libpafe.pasori_close(pasori)

このプログラムを実行すると以下のようになります。

f:id:ueponx:20220118000742p:plain

これでtagtool.pyと同じ実行結果になりました。

おわりに

久々にエンディアンを気をつける必要のあるコードを書いたような気がします。 個人的にはそこはビックエンディアンにしておいてよという気持ちでいっぱいですが致し方ないですね。 これでpythonからも容易にIDmが取得できるようになったので、いろいろな自作の工作にも応用できそうです。