はじめに
はじめまして、こんにちは。STORES 決済 でAndroidエンジニアをしている n-seki です。
というわけでAndroidの技術記事……を書こうと思ったのですが、決済に携わっているエンジニアとして、たまにはTLVについて書いてみようと思います。
TLVとはなにか?
TLVはデータ構造の一種で、Tag(またはType), Length, Value の頭文字をとった命名です。Tagはタグ、Lengthは長さ、Valueは値。そのままですね。
決済とTLVにどんな関係があるか、不思議に思われる方もいらっしゃるかもしれません。
一口に決済と言っても昨今はさまざまな決済手段が存在しますが、ここではクレジットカード決済にフォーカスします。さて、クレジットカード決済ではカードやカードリーダー、サーバーが通信を行うことで決済が成立します。決済データがネットワーク上を流れているわけですが、実は、クレジットカード決済においてはこの決済データがTLVの形式になっています。
TLVは汎用的なデータ構造なのでさまざまな分野で活用されていますが、本記事ではEMVにおける仕様について語っていきます。
TLVの仕様
さっそくTLVの仕様を見ていきましょう。例えば以下のような文字列が与えられたとしましょう。
9F0206000000000512
TLVはだいたいこういう形をしています。Tag, Length, Valueに相当する値が16進数に変換され、区切りなしの文字列データとして表現されます。どうパースするのでしょうか?
まずTagですが、これは所与の情報となります。つまり「このシステムにおいてはこのようなタグが存在し、それぞれこういう意味である」と取り決めがなければなりません。例えばクレジットカード決済の世界では国際規格であるEMV(Europay, Mastercard, and Visa)で数多くのタグが定義されています。
ここでは 9F02
というタグが定義されているとしましょう。
9F0206000000000512 Tag: 9F02 Length: ?? Value: ??
次にLengthです。Tagの次の1バイト分の情報がLengthにあたります。
9F0206000000000512 Tag: 9F02 Length: 06 Value: ??
最後はValueです。LengthはValueの長さを表しているので、今回の例では6バイト分です。
9F0206000000000512 Tag: 9F02 Length: 02 Value: 000000000512
TLVをパースできました!
例ではとてもシンプルなTLVでしたが、実際には複数のTLVがリスト状になっていたり、Valueの部分がTLVになっているという入れ子構造になっている場合もあります。
TLVのLの限界……?
勘の良い方は気がついたかもしれませんが、上記の説明だとTLVで表現できる情報量がかなり制限されてしまいます。具体的にはこの箇所ですね。
Tagの次の1バイト分の情報がLengthにあたります。
1バイトの最大値はFF
(255)なので、256バイト以上のValueをTLVで表現できないことになってしまいます。
というわけで、この情報は正しくありません。TLVはより柔軟な性質を持っています。
実は Length は可変長になっており、Valueの長さに応じて大きくなるため制約は実質存在しません。好きな長さの情報をTLVで表現できます。
しかし課題はまだ残されています。可変長である場合「Lengthは何バイトなのか」をどう判断すればいいのでしょうか?
限界を突破する
まずValueの長さが十分に小さく1バイトで表現できる場合には、その長さを直接1バイトで表します。
長さ = 14 16進数: 0E 2進数 : 0000 1111 長さ = 90 16進数: 5A 2進数 : 0101 1010 長さ = 127 16進数: 7F 2進数 : 0111 1111
ここで、127は2進数で 0111 1111
となり、次の値は 1000 0000
になります。このように最上位ビットが立っている場合には下位7ビットが「Lengthが何バイトであるか」を示します。ビット演算的に書くと 0111 1111
とAND演算をしたときの値が「Lengthの長さ」になります。これが限界を突破するための仕様です(この仕様においては 127 が1バイトで表現できる Length の最大値です)。
具体例で見ていきましょう。
9F498203E8......
このようなデータがあるとします。 9F49
がタグとして存在していることが分かっているとしましょう。
まずTagをパースします。
9F498203E8...... Tag: 9F49 Length: ?? Value: ??
次にLengthですが、Tagの次の1バイトを見てみましょう。
16進数: 82 2進数 : 1000 0010
最上位のビットが立っています。すなわち 82
はそのままLengthを表すのではなく「Lengthの長さ」を表します。
というわけで 0111 1111
とAND演算した結果である 2 がLengthの長さになります。
9F498203E8...... Tag: 9F49 Lengthの長さ: 2(0x82 = 1000 0010 で最上位ビットが立っているので下位7ビットが長さを表す) Length: ??
Lengthの長さが 2 であると分かったので、2バイト取り出すとそれがLengthです。
9F498203E8...... Tag: 9F49 Lengthの長さ: 82(0x82 = 1000 0010 で最上位ビットが立っているので下位7ビットが長さを表す) Length: 03E8
パースできました!
ちなみに 0x03E8
は10進数で表すと 1000 になります。つまりValueまで含めると、
9F498203E8...... Tag: 9F49 Lengthの長さ: 82(0x82 = 1000 0010 で最上位ビットが立っているので下位7ビットが長さを表す) Length: 03E8 Value: [1000バイトのデータ]
となります。無事に長さの上限(と思われていたもの)を突破できました。
さいごに
TLVの基本的な仕様から、少し発展的な内容を共有しました。
ちなみに STORES 決済 のモバイルチームでは、ここで解説したようなパース処理を社内ライブラリ化してiOSアプリとAndroidアプリで利用する、という設計にしています。プリミティブかつコアな実装をプラットフォーム間で共通化しているわけですね。
このあたりもブログで書きたいですが、またの機会に。
ありがとうございました!