GET INPUT コマンドと TIMER MANAGEMENT コマンド
前回(下記)に引き続き、sysmoUSIM-SJS1 4FF を使って SIM ツールキット用 Java Card アプレットを作ってゆきます。
cheerio-the-bear.hatenablog.com
SET UP MENU コマンドに DISPLAY TEXT コマンド、SELECT ITEM コマンドとやってきましたが、今回は GET INPUT コマンドです。GET INPUT コマンドを SIM カードから受け取るためのメニュー構造を作っていましたので、これが今回の誰得 Java Card アプレット実装企画の本丸です。
GET INPUT コマンド
SIM カードから GET INPUT コマンドを受け取った端末は、そのコマンドの指示に従い、ユーザーに何らかの入力を促す画面を表示します。数字(と記号)のみモードとアルファベットも含むモードがありますが、詳しくは ETSI TS 102 223 のコマンド仕様をご覧ください。
sim.toolkit.ProactiveHandler には、GET INPUT コマンド用のヘルパーメソッドが用意されています。GSM 03.19 Annex A からその API 書式を引用したものが、こちらです。
public void initGetInput(byte qualifier, byte dcs, byte[] buffer, short offset, short length, short minRespLength, short maxRespLength) throws java.lang.NullPointerException, java.lang.ArrayIndexOutOfBoundsException, ToolkitException
引数の数は、そう多くありません。デフォルトテキストやアイコンを指定したい場合は、ETSI TS 102 223 に記載されている GET INPUT コマンドの BER-TLV データオブジェクトの構成要素を確認し、EditHandler.appendTLV() を使って個別に追加する必要があります。今回はそこまでやりませんが、必要になったときにやってみましょう。
ということで、GET INPUT コマンドをサポートするために作成したのが、以下のコミットです。SELECT ITEM コマンドで作られた二階層目のメニューで "Request (Sync)" が選択されると、その流れで SIM カードから GET INPUT コマンドを送信する仕組みにしています。
表示される画面のデザインは端末によって異なるものと思われますが、このような画面が表示されれば成功です。こんな画面でしたっけ ..
二階層目のメニュー選択の流れで、つまり SELECT ITEM コマンドに対する TERMINAL RESPONSE 返却の流れで、そのまま同期的に GET INPUT コマンドを SIM カードから端末に送信することができました。
GET INPUT コマンドの非同期実行
次は、それを非同期的に実現します。つまり、二階層目のメニューが選択された時点では新たなコマンドを送信せず、数秒後に時間差で GET INPUT コマンドを送信します。これにより、端末のホーム画面や通話画面にいるときに SIM カードから GET INPUT コマンドが送信される、といったような状況を生み出すことができるようになります。
まずは、実装済みの SELECT ITEM コマンドをほんの少し拡張し、GET INPUT コマンドの時間差送信を要求するために利用するメニュー項目を追加します。このスクリーンショットの三つ目、"Request (async)" がそれです。
端末と SIM カードとの間の通信においては、SIM カードは基本「受け身」です。実行したいコマンドがあったとしても、それを直ちに端末側に送信することはできず、端末側からのコンタクトを待つ必要があります。コンタクトされたときに「実行したいコマンドがあるんだけど」と控え目な反応をすることで、そのコマンドを端末側に拾ってもらえるような仕組みになっています。
しかし、その仕組みに頼っているだけでは、端末を操作している利用者に SIM ツールキットのメニュー画面を離れさせることができません。利用者が別の画面、電話帳やメーラーを操作しているときに、SIM カードから GET INPUT コマンドを送信させるにはどうすれば良いでしょう。
TIMER MANAGEMENT コマンド
幾つか方法がありそうですが、TIMER MANAGEMENT コマンドを使うのが最も素直でしょうか。ざっくり言ってしまえば、だいたい下記のような実装をすれば良さそうです。
- ToolkitRegistry.allocateTimer() を使ってタイマーリソースの割り当てを受ける。
- ProactiveHandler.init() と EditHandler.appendTLV() を使って TIMER MANAGEMENT コマンドを構築する。
- ProactiveHandler.send() を使って TIMER MANAGEMENT コマンドを端末に送信する。
- ToolkitInterface.processToolkit() に EVENT_TIMER_EXPIRATION が届けられるのを待つ。
- EVENT_TIMER_EXPIRATION を受け取った流れで GET INPUT コマンドを送信する。
二階層目のメニュー項目 "Request (async)" が選択され、SELECT ITEM コマンドの TERMINAL RESPONSE でそれが SIM カードに伝えられたときに、TIMER MANAGEMENT コマンドを端末側に送信しておきます。同コマンドを受け取った端末は、コマンドで指定された時間が経過した頃に ENVELOPE コマンドを送ってくるので、そのときに GET INPUT コマンドを送信してあげます。なるほど、TIMER MANAGEMENT コマンドってそんなときに使うんですね(気にしたことがなかった)。
こちらが、上記の処理を実装したときのコミットです。
TIMER MANAGEMENT コマンドで指定したタイマーの満了イベントを ENVELOPE コマンドで受け取るには、SIM カード側から事前に SET UP EVENT LIST コマンドを送信しておかないといけないはずです。その処理の実装を求められないということは、カード OS がその役割りを肩代わりしてくれているようです。実際に自分で実装してみることで理解が深まること、少しずつ出てきています。
当初はうまくいかず
TIMER MANAGEMENT コマンドを送信する処理を実装して試してみましたが、当初は何故かうまく所望の入力画面が端末上に表示されませんでした。DISPLAY TEXT コマンドを使ってエラーを端末側に通知させるようにしたところ、タイマーリソースの割り当てに失敗していたことがわかりました。このスクリーンショットです。
そう言えば、アプレットをインストールするときのパラメータにタイマー関連のものがあったはず。すぐそのことを思い出したので、長くはまることはありませんでした。ETSI TS 102 226 8.2.1.3.2.1 "Coding of the SIM File Access and Toolkit Application Specific Parameters" の表中にある、下記のパラメータです。
- Maximum number of timers allowed for this application instance
アプレットのロードやインストール時に使っている Python のスクリプトにも、そのパラメータがそのまま (--max-timers) 用意されていましたので、このアプレットにタイマーを最大ひとつまで割り当てるように指定しました。
$ python ../osmocom-sim-tools/shadysim/shadysim.py --pcsc -l ./build/javacard/com/github/cheeriotb/toolkit/input/javacard/input.cap -i ./build/javacard/com/github/cheeriotb/toolkit/input/javacard/input.cap --enable-sim-toolkit --module-aid D07002CA44900101 --instance-aid D07002CA44900101 --nonvolatile-memory-required 0100 --volatile-memory-for-install 0100 --max-timers 1 --max-menu-entry-text 15 --max-menu-entries 05 --kic 3F9EA33B0DBEFEEE6FA90533FE22113E --kid 62C724DFFD165CB9CFF78778B49AB003
はい、今度はタイマー 1 の割り当てに成功しました。このメッセージが表示された後、端末のホーム画面に移動してしばらく待っていると、SIM カードから GET INPUT コマンドが送信されたことを確認することができました。