以前、 Python + nfcpy + PaSoRi RC-S380 + launchdで、macにログイン後「FeliCaのIDmを通知センターへ出力する」処理を自動起動してみたことがありました。この時はRC-S380がmacに正式対応していないため、macで nfcpy を使ってFeliCaを読み込んでいました。
その後、macに正式対応したPaSoRiがほしくなり、RC-S300を購入しました。
ただ、RC-S300は nfcpy 経由ではFeliCaを読み込むことができないようでした。
New Sony NFC Reader/Writer device RC-S300 is not supported · Issue #214 · nfcpy/nfcpy
プルリクエストはあるものの、 nfcpy 自体が2022年から動きがないようです。
Add support for Sony RC-S300/S1 · Issue #240 · nfcpy/nfcpy
そこで別の方法がないかを調べたところ、PC/SC(Personal Computer/Smart Card)というインタフェースに従って、APDU (Application Protocol Data Unit) というプロトコルで通信すればFeliCaを読み込むことができそうと分かりました。
そこで、いつも開発環境として使っているWSL2 (Ubuntu) にて、 pcsc-rust を使ってFeliCaを読み込んでみたことから、メモを残します。
目次
環境
- Ubuntu on WSL2
- usbipd-win 4.3.0
- Rust 1.83.0
- pcsc-rust 2.9.0
環境構築
usbipd-win を設定し、WSL2でUSBデバイスを認識させる
WSL2のデフォルトでは、Windowsに接続したUSBデバイスを認識できません。
Microsoftのドキュメントによると、WSL2でUSBデバイスを認識させるには usbipd-win を使うとのことでした。
USB デバイスを接続する | Microsoft Learn
また、 usbipd-win のREADMEやWikiにも記載がありました。
- dorssel/usbipd-win: Windows software for sharing locally connected USB devices to other machines, including Hyper-V guests and WSL 2.
- WSL support · dorssel/usbipd-win Wiki
そこで usbipd-win の設定を行うことにしました。
まず初めにWSLのバージョンを確認します。PowerShellにて wsl --version を実行したところ、エラーになりました。versionオプションがないようです。
> wsl --version コマンド ライン オプションが無効です: --version
wsl コマンドのバージョンが古そうだったため、 wsl コマンドを更新します。
> wsl --update インストール中: Linux 用 Windows サブシステム Linux 用 Windows サブシステム はインストールされました。
改めて wsl --version を実行したところ、今度はバージョンが表示されました。
> wsl --version WSL バージョン: 2.3.26.0 カーネル バージョン: 5.15.167.4-1 WSLg バージョン: 1.0.65 MSRDC バージョン: 1.2.5620 Direct3D バージョン: 1.611.1-81528511 DXCore バージョン: 10.0.26100.1-240331-1435.ge-release Windows バージョン: 10.0.22631.4602
続いて、WSL上のUbuntuのターミナルで、現在のLinuxカーネルのバージョンを確認します。先ほど wsl --update したこともあり、usbipd-winが要求するバージョンは満たしてそうです。
$ uname -a Linux LAPTOP-JEC6S15S 5.15.167.4-microsoft-standard-WSL2 #1 SMP Tue Nov 5 00:21:55 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
再びPowerShellに戻り win-get を使って usbipd-win をインストールすることにします。
WinGet を使用してアプリケーションをインストールおよび管理する | Microsoft Learn
まずは win-get が使えるかを確認します。良さそうです。
> winget -v v1.9.25200
Microsoftの記事にある通り、win-getでインストールします。この時、即座に再起動されないよう、 --interactive を削除して実行します。なお、手元のPCでは再起動の要求がありませんでした。
> winget install --exact dorssel.usbipd-win ... Downloading https://github.com/dorssel/usbipd-win/releases/download/v4.3.0/usbipd-win_4.3.0.msi ██████████████████████████████ 4.55 MB / 4.55 MB Successfully verified installer hash Starting package install... Successfully installed
次に、PowerShellにてStart-Processし、別のPowerShellを管理者モードで起動します。
例 5: 管理者として PowerShell を起動する | Start-Process (Microsoft.PowerShell.Management) - PowerShell | Microsoft Learn
> Start-Process -FilePath "powershell" -Verb RunAs
新しく起動した管理者モードのPowerShellにて、Windowsに接続されているUSB機器の一覧を確認します。これより、BUSID 2-2 がRC-S300だと分かりました。
PS C:\WINDOWS\system32> usbipd list Connected: BUSID VID:PID DEVICE STATE 1-4 04f2:b724 Integrated Camera, Camera DFU Device Not shared 2-2 054c:0dc9 Microsoft Usbccid Smartcard Reader (WUDF), FeliCa Port/Pa... Not shared ... Persisted: GUID DEVICE
PowerShellで usbipd bind コマンドを使い、先ほど確認したBUSID 2-2 にあるRC-S300をWSLへ接続できるようにします。
> usbipd bind --busid 2-2
再度PowerShellでUSB機器の一覧を確認すると、STATEが Shared になりました。
> usbipd list Connected: BUSID VID:PID DEVICE STATE 1-4 04f2:b724 Integrated Camera, Camera DFU Device Not shared 2-2 054c:0dc9 Microsoft Usbccid Smartcard Reader (WUDF), FeliCa Port/Pa... Shared
なお、Sharedになったからといって、まだWSL側ではUSB機器を認識していません。試しにWSL上のターミナルで lsusb しても何も表示されません。
$ lsusb Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
続いて、PowerShellでの usbipd attach により、RC-S300をWSLへ接続します。
> usbipd attach --wsl --busid 2-2 usbipd: info: Using WSL distribution 'Ubuntu-22.04' to attach; the device will be available in all WSL 2 distributions. usbipd: info: Using IP address 172.17.240.1 to reach the host.
PowerShellでUSB機器を確認すると、STATEが Attached になりました。
> usbipd list Connected: BUSID VID:PID DEVICE STATE 1-4 04f2:b724 Integrated Camera, Camera DFU Device Not shared 2-2 054c:0dc9 Microsoft Usbccid Smartcard Reader (WUDF), FeliCa Port/Pa... Attached
WSL上のターミナルで確認すると、RC-S300が認識されていました。
$ lsusb Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub Bus 001 Device 002: ID 054c:0dc9 Sony Corp. FeliCa Port/PaSoRi 4.0 Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
なお、RC-S300の接続を解除したり、PCを再起動したりすると、RC-S300のSTATEは Shared に戻ります。この場合は再度 attach する必要があります。
もし自動で接続したい場合は usbipd attach --help にある通り、 -a オプションを使えば良さそうです。
-a, --auto-attach Automatically re-attach when the device is detached or unplugged
systemdを有効にする
LinuxにてPC/SCによる通信を行うには pcscd の設定が必要です。
Ubuntu Manpage: pcscd - PC/SC Smart Card Daemon
また、pcscdはsystemdでデーモン化しておくと扱いやすそうです。
手元のWSLを見たところ、2022年にsystemdを有効化していた記録があります。
ただ、記事を作成した後にMicrosoftから「WSLでsystemdが正式サポート」というブログ記事が出ました。
Systemd support is now available in WSL! - Windows Command Line
この時系列から、手元のWSLのsystemd設定は古い可能性がありそうでした。
そこで、上記ブログ記事に従い、 systemdの設定を確認します。
先ほど wsl --update はすでに実施済のため、続く「 /etc/wsl.conf の編集」から作業をします。
ファイルを開くと、以前の作業で command 行を追加した跡がありました。そこで、 command 行はコメントアウトし、 systemd=true を追加することにしました。
$ cat /etc/wsl.conf [boot] # command=/usr/libexec/wsl-systemd systemd=true
続いて、PowerShellにて wsl --shutdown します。
その後、WSLを起動して systemd の動作確認を行ったところ、問題なくsystemdが起動していました。
$ systemctl list-unit-files --type=service UNIT FILE STATE VENDOR PRESET apparmor.service enabled enabled
pcscd を有効化する
systemdの設定が終わったので、次は pcscd の設定を行います。
最初に、WSLのターミナルで必要なものを追加します。なお、動作確認のため、 pcsc-tools もインストールします。
$ sudo apt install pcscd pcsc-tools
systemdで確認したところ、pcscdが表示されました。
$ sudo systemctl status pcscd
● pcscd.service - PC/SC Smart Card Daemon
Loaded: loaded (/lib/systemd/system/pcscd.service; indirect; vendor preset: enabled)
Active: active (running) since Wed 2025-01-01 14:02:58 JST; 52s ago
TriggeredBy: ● pcscd.socket
Docs: man:pcscd(8)
Main PID: 5956 (pcscd)
Tasks: 3 (limit: 26310)
Memory: 664.0K
CGroup: /system.slice/pcscd.service
└─5956 /usr/sbin/pcscd --foreground --auto-exit
1月 01 14:02:58 LAPTOP-JEC6S15S systemd[1]: Started PC/SC Smart Card Daemon.
動作確認をします。
WindowsにRC-S300を接続して pcsc_scan を実行します。認識されているようです。
$ pcsc_scan Using reader plug'n play mechanism Scanning present readers... 0: SONY FeliCa RC-S300/P (0323657) 00 00 Wed Jan 1 14:11:17 2025 Reader 0: SONY FeliCa RC-S300/P (0323657) 00 00 Event number: 0 Card state: Card removed,
この状態でFeliCaを置いたところ、反応がありました。
Wed Jan 1 14:11:36 2025
Reader 0: SONY FeliCa RC-S300/P (0323657) 00 00
Event number: 1
Card state: Card inserted,
ATR: 3B 8F 80 01 80 4F 0C A0 00 00 03 06 11 00 3B 00 00 00 00 42
...
Possibly identified card (using /usr/share/pcsc/smartcard_list.txt):
3B 8F 80 01 80 4F 0C A0 00 00 03 06 11 00 3B 00 00 00 00 42
3B 8F 80 01 80 4F 0C A0 00 00 03 06 .. 00 3B 00 00 00 00 ..
FeliCa (as per PCSC std part3)
3B 8F 80 01 80 4F 0C A0 00 00 03 06 11 00 3B 00 00 00 00 42
RFID - FeliCa (generic) (as per PCSC std part3)
Suica public transit card (Japan IC system)
(also: Hayakaken, ICOCA, Kitaca, manaca, nimoca, PASMO, PiTaPa, SUGOCA, TOICA)
https://en.wikipedia.org/wiki/Suica
Octopus, MTR network from Hong Kong, 2014
動作確認がOKとなったことから、環境構築はこれで終わりです。
pcsc-rust を使ってFeliCaを読む
次は、RC-S300と接続してFeliCaを読むプログラムを作成します。
LinuxでRC-S300を使った情報がないかを調べたところ、以下の記事がありました。とても参考になりました。ありがとうございました。
RC-S300をLinuxで使う - Zenn
この記事を参考にして、Rust + pcsc-rust を使った実装を進めます。
pcsc-rust を使うために必要なライブラリをインストール
最初にCargo.tomlに pcsc を追加してビルドしたところ、以下のエラーが出ました。
If you are using Debian (or a derivative), try: $ sudo apt install pkgconf libpcsclite-dev
また、pcsc-rust が依存しているcrateのドキュメントを見たところ
On Linux, BSDs and (hopefully) other systems, the PCSC lite library and pcscd daemon. See pcsclite for documentation of the implemented API.
pcsclite is detected at build time using pkg-config. See the pkg-config crate for more information.
とありました。
そこで、pkgconf と libpcsclite-dev をインストールすることにしました。
$ sudo apt install pkgconf libpcsclite-dev
pcsc-rust に関する調査
上記zennの記事や pcsc-rust のREADME にあるサンプルコードを見たところ、分からないことがあったため、事前に調査を行います。
PCSCコマンドの FF CA 00 00 00 について
まずzennの記事にある hex!("FF CA 00 00 00") についてです。
こちらはAPDUコマンドを表しています。APDUのコマンドやフォーマット、レスポンスについては以下が詳しいです。
- コマンドとレスポンス - EternalWindows
- https://gist.github.com/hemantvallabh/d24d71a933e7319727cd3daa50ad9f2c
- APDUのリスト
- https://www.eftlab.com/knowledge-base/complete-list-of-apdu-responses
- APDUのレスポンスのリスト
- 日本語だとこちら
これらの情報をもとに FF CA 00 00 00 という値をフォーマットに従って読んでみると、内容が理解できました。
- 先頭の
FFはCLAを表す- ISO7816 part 4 section 5 APDU level data structures
- 今回はFeliCa向けなので
Reserved for PTSであるFFを使う...のかな (分からなかった)
- 続く
CAはGet DataというAPDUコマンド - その後の
00 00 00は、Get Dataでパラメータがないのですべて00で良さそう
&rapdu[len-2..len] について
こちらもzennの記事にある実装です。
これについては
card.transmitの戻り値として、変数 rapdu_buf と同じ値が返ってくる- printデバッグしてみると、APDUの取得データと同じ値だった
- 取得データは、Data (nバイト) + SW1 (1バイト) + SW2 (1バイト) というフォーマット
- SW1とSW2はステータスワードと呼ばれるもの
より、「ステータスワードであるSW1とSW2の値を取得して、処理が成功しているかどうかを判定に使う」と分かりました。
実装
調査が終わったので、zennの記事とpcsc-rustのREADMEをベースに実装します。
use pcsc::{Context, Error, Protocols, Scope, ShareMode, MAX_BUFFER_SIZE}; use hex_literal::hex; fn main() { let ctx = match Context::establish(Scope::User) { Ok(ctx) => ctx, Err(err) => { eprintln!("Failed to establish context: {}", err); std::process::exit(1); } }; // List available readers. let mut readers_buf = [0; 2048]; let mut readers = match ctx.list_readers(&mut readers_buf) { Ok(readers) => readers, Err(err) => { eprintln!("Failed to list readers: {}", err); std::process::exit(1); } }; // Use the first reader. let reader = match readers.next() { Some(reader) => reader, None => { println!("No readers are connected."); return; } }; println!("Using reader: {:?}", reader); // Connect to the card. let card = match ctx.connect(reader, ShareMode::Shared, Protocols::ANY) { Ok(card) => card, Err(Error::NoSmartcard) => { println!("A smartcard is not present in the reader."); return; } Err(err) => { eprintln!("Failed to connect to card: {}", err); std::process::exit(1); } }; // Class byteはこちら // https://cardwerk.com/smart-card-standard-iso7816-4-section-5-basic-organizations/?elementor-preview&1514396438071#chap5_4_1 let apdu_command_of_get_data = hex!("FF CA 00 00 00"); let mut rapdu_buf = [0; MAX_BUFFER_SIZE]; // transmitで、 rapdu_buf に値が設定される // 構造は、 DataField + SW1 + SW2 // https://cardwerk.com/smart-card-standard-iso7816-4-section-6-basic-interindustry-commands/ // DataFieldの長さは、戻り値のlenを取れば出てくる // SW1とSW2は、ステータスバイトなので、それぞれ1バイトずつの合計2バイト let rapdu = card.transmit(&apdu_command_of_get_data, &mut rapdu_buf).unwrap(); // rapduとrapdu_bufには同じ値が入ってきてるっぽい感じがある println!("{rapdu:02X?}"); // データ部とステータス部を分割する // ステータス部は最低2バイトあるので、その長さ未満の場合は、なにかおかしいと判断する let response_length = rapdu.len(); if response_length < 2 { println!("レスポンスが不正です"); return } // ステータス部は2バイトで固定のため、それ以外の部分はデータ部として判断する let (data, sw) = rapdu.split_at(response_length - 2); if sw[0] == 0x90 && sw[1] == 0x00 { println!("IDmを取得できました"); println!("{data:02X?}"); } else { println!("IDmを取得できませんでした"); } }
動作確認
実装が終わったので、次は動作確認です。
まずは準備として以下を行います。
- WindowsにRC-S300を接続する
- PowerShellにてRC-S300をattach
- RC-S300の上にFeliCaを置く
続いてRust製のプログラムを実行したところ、IDmが読めました。良さそうです。
Using reader: "SONY FeliCa RC-S300/P (0323657) 00 00" [01, 14, C4, 26, 33, 13, 8E, 12, 90, 00] IDmを取得できました [01, 14, C4, 26, 33, 13, 8E, 12]
その他資料
Smart cardと通信するライブラリはRust以外にも実装例がありますので、資料としてまとめておきます。
- PC/SC sample in different languages | Ludovic Rousseau's blog
- 言語ごとの実装がまとめられている
- pyscard user’s guide — pyscard 2.2.0 documentation
- Python向けのライブラリ
pyscardのドキュメント
- Python向けのライブラリ
- https://github.com/danm-de/pcsc-sharp
- C#向けのライブラリ
PCSC-sharp。このライブラリを作った記事もよく見かけました
- C#向けのライブラリ
また、上記のLudovic RousseauさんのBlogはSmartCardに詳しかったです。必要に応じて読み返したいです。
Ludovic Rousseau's blog
ソースコード
GitHubに上げました。
https://github.com/thinkAmi-sandbox/read_felica_on_rc_s300-example
今回のプルリクはこちら。
https://github.com/thinkAmi-sandbox/read_felica_on_rc_s300-example/pull/1