國際化域名編碼

國際化域名編碼（Punycode）是一種表示統一碼和ASCII碼的有限的字符集。例如中文「上海」會被編碼為「fhqz97e」。

國際化域名編碼的目的是在於國際化域名標籤（IDNA）的框架中，使這些（多語言）的域名可以編碼為ASCII。編碼語法在文檔 RFC 3492 中規定。

## 編碼過程

RFC 3492中說明：國際化域名編碼是一種稱為Bootstring的更普遍的演算法實例，它允許由部分基本的編碼集合組成的字串唯一表示由更大編碼集合組成的任何字串。配合統一碼文本的特性，國際化域名編碼定義了一般Bootstring演算法的參數。本節以德語字串"bücher"（書籍）為例，演示國際化域名編碼的編碼過程，該字串被編碼為"bcher-kva"。

### 分離ASCII字符

首先，字串中的所有ASCII字符將被直接從輸入複製到輸出，任何其他字符將被跳過。例如，"bücher"被複製為 "bcher"。如果有任何字符被複製（即輸入中至少有一個ASCII字符），則在輸出中附加一個ASCII連字符（例如，"bücher"複製為"bcher-"，其中"ü"不是ASCII字符，不發生複製）。

由於連字符本身是ASCII字符，因此可以出現在輸入中，並將被複製到輸出中。這並不會引起歧義，因為如果輸出包含連字符，被添加的連字符總是最後一個，這標誌著ASCII字符的結束。

### 編碼非ASCII字符

對於輸入的非ASCII字符，依統一碼中碼位由低至高順序，編碼器會模擬將非ASCII字符一一插入回去除非ASCII字符後的字串過程。

計算每個非ASCII字符對應的三個數字i、n和h：

- i 表示該非ASCII字符在輸入字串中的以0為開始索引的插入位置（類似於如C語言等的程式語言，用0來表示該非ASCII字符是輸入字串的第一個字符）。
- n 表示該非ASCII字符在統一碼中的碼位，減去127（127為最後一個ASCII字符）
- h 表示該非ASCII字符插入時可能有的插入位置。如"ü"要插入回"bcher"中，可能有"übcher"到"bcherü"6個位置可供插入。

編碼器隨後計算乘法n*h+i並將得到的數字編碼為一串36進位的可變長度數字，將這些數字轉化為ASCII碼，並將結果附加到輸出字串後。

每次計算後，該非ASCII字符會插入回去除非ASCII字符後的字串，因此h會增加1。

並且在計算下一個非ASCII字符的統一碼碼位時，該碼位需減去上一個非ASCII字符碼位。

編碼方式為：0 -> 'a', ..., 25 -> 'z', 26 -> '0', ..., 35 -> '9', 數字的數字按小端序排列。

此處的36進位的編碼並非通常意義下的下36進制整數，而是一種可變長度的整數。每個數字的最重要（most significant）的位（例如數字 "123 "中的 "1"）在沒有上下文的情況下是可以識別的。因此，多個數字可以無需分隔地連接，不會影響原始數字的識別和提取。但此處每位數字進位的門檻閥值不同，並非每位皆是36進位。

### 用於國際化域名的ACE前綴

為了防止非國際域名中的連字符被國際化域名編碼解碼，會在在國際化域名中的國際化域名編碼序列前加上字串`xn--`。這被稱為ACE（ASCII Compatible Encoding）。

因此，域名"bücher.tld"將會以ASCII形式表示為"xn--bcher-kva.tld"。

### 解碼器

解碼器是具有兩個狀態變量i和n的有限狀態機。

i是字串的索引，範圍從0（代表可能插入的開始）到擴展字串的當前長度（代表可能插入的結束）。i從0開始。

n從128開始（128表示第一個非ASCII的碼位）。

狀態遞增是一個單調函數。一個狀態轉移要麼增加i，如果i達到最大，就重置i為零並增加n。下一個狀態轉移時，繼續增加i。在每個狀態下，由n表示的碼位要麼被插入，要麼不被插入。

編碼器生成的數字代表在插入之前要跳過多少可能的結果。

在字串 "bcher "中，有六個位置可以插入一個字符（包括第一個字符前和最後一個字符後）。在最後一個ASCII碼位（127）和 "ü"（252，參見拉丁字母補充-1）之間有124個碼位。有一個 "ü "的插入位置必須跳過（位置0，即在'b'之前）。

因此，在到達正確位置前，解碼器將跳過共（6×124）+1=745個可能的插入位置。在該字符插入後，將有七個可能的地方插入下一個字符。

### 將碼號重新編碼為ASCII序列

國際化域名編碼使用通用變長整數來表示這些值。例如，"kva "將用來表示碼號745。

小端序的數字系統允許不需要單獨的分隔符的變長編碼：低於閾值的數字標誌著它是最重要的數字，即是數字的結束。為了提高效率，閾值取決於數字的位置和以前的插入。同時，數字的權重也會變化。

在使用36個符號的數字系統中，不區分大小寫的'a'-'z'表示十進位數字0-25，'0'到'9'表示十進位數字26-35。因此，"kva"，表示十進位數字序列"10 21 0"。

為了解碼這串符號，需要閾值序列。本例中，閾值序列為（1，1，26，26，...）。最不重要的數字（least-significant digit）的權重（或位置值）總是1，具有權重1的'k'等於10（1×10=10）。下一個數字的權重取決於第一個閾值：一般來說，對於任意n，第n+1個數字的權重是第n個數字的權重乘以（36-第n個數字的閾值）。所以第二個符號的權重是36減去前一個閾值，即為35（36-1=35）。因此，前兩個符號'k'和'v'之和為10×1+21×35。由於第二個符號不小於其閾值1，因此還有更多符號。然而，由於本例中第三個符號是'a'（a=0），可以忽略其權重的計算。因此，"kva "代表十進位數字（10×1）+（21×35）=745。閾值本身由一個演算法為每個編碼字符序列決定，使其保持在1和26之間（包括1和26）。字符的大小寫可以用來提供關於原始字串的大小寫資訊。由於特殊字符按法編碼算的碼位排序，在插入"bücher"中特殊字符"ü"時，第一種可能是編碼為 "bcher-kvaa "的 "büücher"，第二種可能是編碼為 "bcher-kvab "的 "bücüher"，等等等等。在編碼為 "bcher-kvae "的 "bücherü "之後是代表插入ý的編碼，即ü之後的統一碼字符，從編碼為 "bcher-kvaf "的 "ýbücher "開始（與編碼為 "bcher-jvab "的 "übücher "不同），等等。

為了使編碼、解碼演算法簡單，編碼時並沒有試圖阻止一些編碼不允許的統一碼值：然而，解碼時應該檢查這些值。

國際化域名編碼的設計使其成可以在所有腳本中工作，並通過在操作過程中嘗試適應字串中的字符集範圍進行自我優化。它針對字串由包含0個或更多ASCII字符和來自其他腳本系統的字符組成的情況進行了優化，同時也能處理任意的統一碼字串。在使用DNS時需要注意，域名字串被認為已經使用nameprep進行了規範化處理，並且頂級域名在被國際化域名編碼之前被假設已經根據官方註冊的語言表進行了處理。同時，DNS協議對輸出國際化域名編碼字串的可接受長度也有所限制。

## 例子

下表展示了不同類型輸入的國際化域名編碼的例子。

輸入｜輸入的國際化域名編碼｜對輸入的描述

空字符

a｜a-｜僅有ASCII字符，一個小寫字母

A｜A-｜僅有ASCII字符，一個大寫字母

3｜3-｜僅有ASCII字符，一個數字

僅有ASCII字符，一個連字符

--｜僅有ASCII字符，兩個連字符

London｜London-｜僅有ASCII字符，多於一個，沒有連字符

Lloyd-Atkinson｜Lloyd-Atkinson-｜僅有ASCII字符，一個連字符

This has spaces｜This has spaces-｜僅有ASCII字符，包含空格

-> $1.00 $1.00 <--｜僅有ASCII字符，多種符號混合

а｜80a｜沒有ASCII字符，一個西里爾字母

ü｜沒有ASCII字符，一個拉丁字母補充-1中的字符

α｜沒有ASCII字符，一個希臘字母

例｜沒有ASCII字符，一個CJK字符

😉｜n28h｜沒有ASCII字符，一個emoji字符

αβγ｜mxacd｜沒有ASCII字符，多於一個字符

München｜Mnchen-3ya｜混合字符，包含一個非ASCII字符

Mnchen-3ya｜Mnchen-3ya-｜München的兩次國際化域名編碼

München-Ost｜Mnchen-Ost-9db｜混合字符串，包括一個非ASCII字符和一個連字符

Bahnhof München-Ost｜Bahnhof Mnchen-Ost-u6b｜混合字符串，包括一個空格，一個連字符和一個非ASCII字符

abæcdöef｜abcdef-qua4k｜混合字符串，包括兩個非ASCII字符

правда｜80aafi6cg｜俄語字符，不包括ASCII字符

ยจฆฟคฏข｜22cdfh1b8fsa｜泰語字符，不包括ASCII字符

도메인｜hq1bm8jm9l｜韓語字符，不包括ASCII字符

ドメイン名例｜eckwd4c7cu47r2wf｜日語字符，不包括ASCII字符

MajiでKoiする5秒前｜MajiKoi5-783gue6qz075azm5e｜日語字符，以及ASCII字符

「bücher」｜bcher-kva8445foa｜混合的非ASCII字符（拉丁字母補充-1及CJK字符）