国際化ドメイン名(IDN)では、マルチバイト文字のドメイン名がDNSプロトコル上ではASCII文字に変換されてやりとりされます。たとえば「あ.jp」であれば「xn--l8j.jp」のように変換されます。この表現におけるハイフン以降の部分が本稿のテーマで、これには「一般化可変長整数」という仕組みが利用されています。
エンコード後のデータ「l8j」などを見るとa-z,0-9まで使った36進数なのかな?と思うかもしれませんが、実際はもっとトリッキーなエンコーディングになっています。論より証拠、まずはルールがわかるかどうか見てみましょう。下記はIDNのマルチバイト文字1バイト目の場合に対応する整数とエンコード結果の表になります(注意:2文字目以降は前の文字によってルールが変わりますが、ここではいったん考えないことにしましょう)。
数値 | エンコード結果 |
---|---|
0 | a |
1 | ba |
2 | ca |
... | ... |
34 | 8a |
35 | 9a |
36 | bba |
37 | cba |
... | ... |
70 | 9ba |
71 | bca |
72 | cca |
... | ... |
1260 | 99a |
1261 | bbb |
1262 | cbb |
... | ... |
31885 | 99z |
31886 | bb0a |
... | ... |
44135 | 999a |
44136 | bb0b |
... | ... |
これを見て、どういうルールかパッとわかるでしょうか?
この世界ではaは0を意味します。zが25、0が26、9が35となります。ですから、表の35までは36進数として解釈しても矛盾がありません。
ところで、上の表で1260から1261で2桁から3桁になっているのは36進数にしては変です(36^2=1296なら理解できますが)。さらに言えば44135から44136で3桁から4桁になっている部分もキリが悪すぎます(36^3=46656からかけ離れている)。
また、最後(リトルエンディアンなので最上位桁)にaが付くか付かないかのルールも一貫性がないように見えます。
種明かし
一般化可変長整数というのは、桁ごとに「しきい値」が設定されており、1桁の重みが可変であるような整数列のエンコーディング方法です。
上の例で言えば、文字列から整数への変換は次の式で表現できます。
整数=(1桁目)+(2桁目)*35+(3桁目)*1225+(4桁目)*12250
たとえば、「あ.jp」に登場した「l8j」の例なら、11+34*35+9*1225=12226
となります。1文字目は128からの差分で文字を表現するルールなので、128+12226=12354=0x3042
より、あ(U+3042)を表現していることがわかります。
また、各桁に次の文字が使われていたら、そこで数値の終了を意味します。
何桁目か | 数値の終了を意味する文字範囲 |
---|---|
1 | a |
2 | a |
3 | a-z |
4 | a-z |
逆に言うと、各桁が最上位桁でない場合に使える文字範囲は次のようになります。
何桁目か | 最上位桁でない場合に使える文字範囲 |
---|---|
1 | b-z,0-9 |
2 | b-z,0-9 |
3 | 0-9 |
4 | 0-9 |
ザックリまとめると、上の表は1桁目2桁目が35進、3桁目以降が10進であるような表現を変形したようなものになっています。これにより数値の大きさと切れ目を一意に表現できるようになっています。
ちなみに、一般化可変長整数では桁ごとの重みを任意に変えても表現の一意性が担保できます。上のルール以外にも無限にルールを作れるのが一般化可変長整数ということです。
さらに恐ろしい話
実際、IDNのマルチバイト文字2文字目以降は桁の重み付けルールが変わります。具体的には、直近で近くの文字を使っていたら、次も近くの文字が採用されるという前提でエンコード後の表現ができるだけ短くなるように「数値への変換式」「終了を意味する文字範囲」「最上位桁でない場合に使える文字範囲」が書き換わります。
そんなわけで、IDNのマルチバイト2文字目以降を人力でデコードするのは非常に困難でしょう。aは全ての場合において最上位桁になるので、aが登場したところで1文字分が終わっているんだな、くらいのことは言えますが、それ以上まで読み解くのはかなり厳しいと思います。