STORES Product Blog

こだわりを持ったお商売を支える「STORES」のテクノロジー部門のメンバーによるブログです。

たまにはTLVのLについて語る

はじめに

はじめまして、こんにちは。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アプリで利用する、という設計にしています。プリミティブかつコアな実装をプラットフォーム間で共通化しているわけですね。

このあたりもブログで書きたいですが、またの機会に。

ありがとうございました!