国际化域名编码

国际化域名编码（）是一种表示Unicode码和ASCII码的有限的字符集。例如中文“上海”会被编码为“fhqz97e”。

Punycode的目的是在于国际化域名标签（IDNA）的框架中，使这些（多语言）的域名可以编码为ASCII。编码语法在文档 RFC 3492 中规定。

编码过程
RFC 3492中说明：Punycode是一种称为Bootstring的更普遍的算法实例，它允许由部分基本的编码集合组成的字符串唯一表示由更大编码集合组成的任何字符串。配合Unicode文本的特性，Punycode定义了一般Bootstring算法的参数。本节以德语字符串"bücher"（书籍）为例，演示Punycode的编码过程，该字符串被编码为"bcher-kva"。

分离ASCII字符
首先，字符串中的所有ASCII字符将被直接从输入复制到输出，任何其他字符将被跳过。例如，"bücher"被复制为 "bcher"。如果有任何字符被复制（即输入中至少有一个ASCII字符），则在输出中附加一个ASCII连字符（例如，"bücher"复制为"bcher-"，其中"ü"不是ASCII字符，不发生复制）。

由于连字符本身是ASCII字符，因此可以出现在输入中，并将被复制到输出中。这并不会引起歧义，因为如果输出包含连字符，被添加的连字符总是最后一个，这标志着ASCII字符的结束。

编码非ASCII字符
对于输入的非ASCII字符，依Unicode中碼位由低至高順序，编码器会模擬將非ASCII字符一一插入回去除非ASCII字符後的字串過程，
计算每個非ASCII字符對應的三个数字i、n和h：

 i 表示该非ASCII字符在输入字符串中的以0为开始索引的插入位置（类似于如C语言等的编程语言，用0来表示该非ASCII字符是输入字符串的第一个字符）。
 n 表示该非ASCII字符在Unicode中的码位，减去127（127为最后一个ASCII字符）
 h 表示該非ASCII字符插入時可能有的插入位置。如"ü"要插入回"bcher"中，可能有"übcher"到"bcherü"6個位置可供插入。

编码器随后计算乘法n*h+i并将得到的数字编码为一串36进制的可變長度數字，将这些数字转化为ASCII码，并将结果附加到输出字符串后。
每次計算後，該非ASCII字符會插入回去除非ASCII字符後的字串，因此h會增加1。
並且在計算下一個非ASCII字符Unicode碼位時，該碼位需減去上一個非ASCII字符碼位。

编码方式为：0 -> 'a', ..., 25 -> 'z', 26 -> '0', ..., 35 -> '9', 数字的数字按小端序排列。

此處的36进制的编码並非通常意義下的下36進制整數，而是一種可变长度的整数。每个数字的最重要（most significant）的位（例如数字 "123 "中的 "1"）在没有上下文的情况下是可以识别的。因此，多个数字可以无需分隔地连接，不会影响原始数字的识别和提取。但此處每位數字進位的門檻閥值不同，並非每位皆是36進位。

用于国际化域名的ACE前缀 

为了防止非国际域名中的连字符被Punycode解码，会在在国际化域名中的Punycode序列前加上字符串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序列 
Punycode使用通用变长整数来表示这些值。例如，"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ü "之后是代表插入ý的编码，即ü之后的Unicode字符，从编码为 "bcher-kvaf "的 "ýbücher "开始（与编码为 "bcher-jvab "的 "übücher "不同），等等。

为了使编码、解码算法简单，编码时并没有试图阻止一些编码不允许的Unicode值：然而，解码时应该检查这些值。

Punycode的设计使其成可以在所有脚本中工作，并通过在操作过程中尝试适应字符串中的字符集范围进行自我优化。它针对字符串由包含0个或更多ASCII字符和来自其他脚本系统的字符组成的情况进行了优化，同时也能处理任意的Unicode字符串。在使用DNS时需要注意，域名字符串被认为已经使用进行了规范化处理，并且顶级域名在被punycode编码之前被假设已经根据官方注册的语言表进行了处理。同时，DNS协议对输出Punycode字符串的可接受长度也有所限制。

例子 
下表展示了不同类型输入的国际化域名编码的例子。

参考文献 

Unicode轉換格式
国际化域名