【问题标题】:Should I provide consistency checks in the Huffman tree building algorithm for DEFLATE?我应该在 DEFLATE 的 Huffman 树构建算法中提供一致性检查吗?
【发布时间】:2014-10-28 04:50:38
【问题描述】:

在 RFC-1951 中有一个简单的算法可以从代码长度列表中恢复霍夫曼树,描述如下:

     1)  Count the number of codes for each code length.  Let
         bl_count[N] be the number of codes of length N, N >= 1.

     2)  Find the numerical value of the smallest code for each
         code length:

            code = 0;
            bl_count[0] = 0;
            for (bits = 1; bits <= MAX_BITS; bits++) {
                code = (code + bl_count[bits-1]) << 1;
                next_code[bits] = code;
            }

     3)  Assign numerical values to all codes, using consecutive
         values for all codes of the same length with the base
         values determined at step 2. Codes that are never used
         (which have a bit length of zero) must not be assigned a
         value.

            for (n = 0;  n <= max_code; n++) {
                len = tree[n].Len;
                if (len != 0) {
                    tree[n].Code = next_code[len];
                    next_code[len]++;
                }

但是算法中没有任何数据一致性检查。另一方面,很明显长度列表可能是无效的。长度值,因为 4 位编码不能是无效的,但是,例如,可以有更多的代码比可以编码的代码长度。

提供数据验证的最少检查集是什么?或者由于某些我错过的原因不需要此类检查?

【问题讨论】:

    标签: algorithm language-agnostic deflate validation


    【解决方案1】:

    zlib 检查代码长度列表是否完整,即它是否用完所有位模式,并且它不会溢出位模式。一个允许的例外是当有一个长度为 1 的符号时,这种情况下允许代码不完整(0 位表示该符号,1 位未定义)。

    这有助于 zlib 在流中以更高的概率和更早的时间拒绝随机、损坏或编码不当的数据。这与此处另一个答案中建议的稳健性不同,您可以选择允许不完整的代码,并且仅在压缩数据中遇到未定义的代码时返回错误。

    要计算完整性,请从代码k=1 中的位数和可能代码n=2 的数量开始。有两种可能的一位代码。您从n 中减去长度为 1 的代码的数量,n -= a[k]。然后增加k 以查看两位代码,然后将n 加倍。减去两位代码的数量。完成后,n 应该为零。如果在任何时候n 变为负数,您可以停在那里,因为您有一组无效的代码长度。如果完成后n 大于零,那么您的代码不完整。

    【讨论】:

    • 我在这里有点困惑。 a[] 是否等于 RFC-1951 中的 bl_count[]?
    • 嗯,我测试过了,这个方法真的很简单。它只需要 5 条额外的汇编指令。无论如何,我应该处理你提到的 1bit 异常吗?为什么允许这个例外?
    • 这是允许的,因为 deflate 格式的原始 PKZIP 规范允许在单个距离代码的情况下这样做。我认为这是一个错误,因为如果只有一个可能的代码,那么您需要零位来编码它,而不是一个。然而,Phil Katz(PKZIP 中的 PK)决定将其设为一位而不是零位。
    • 这个异常比一般算法花费了我更多的代码... :*( 无论如何,感谢您的大力帮助。:)
    • 在执行时间方面,您可以等到确定代码不完整之后再检查异常。
    【解决方案2】:

    我认为检查next_code[len] 不会溢出其各自的位就足够了。所以在tree[n].Code = next_code[len];之后,可以做如下检查:

    if (tree[n].Code & ((1<<len)-1) == 0)
        print(Error)
    

    如果tree[n].Code &amp; ((1&lt;&lt;len)-1) 达到0,则表示长度为len 的代码比应有的多,因此长度列表中有错误。 另一方面,如果树的每个符号都分配了一个有效(唯一)代码,那么您就创建了一个正确的 Huffman 树。

    编辑:我突然明白了:您可以在第一步结束时简单地进行相同的检查:您只需检查所有1&lt;=j&lt;=N 和所有N &gt;=1bl_count[N] &lt;= 2^N - SUM((2^j)*bl_count[N-j]) (如果二叉树bl_count[N-1] 在级别 N-1 中有叶子,那么它在级别 N 中的叶子不能超过 2^N - 2*bl_count[N-1],级别 0 是根)。

    这保证您创建的代码是前缀代码,但不保证它与原始创建者的意图相同。例如,如果长度列表无效,但您仍然可以创建有效的前缀代码,那么您无法证明这是 Huffman 代码,因为您不知道每个符号的出现频率。

    【讨论】:

    • 是的,这种方法似乎还可以。但是是否可以将此检查移至算法的第 2 阶段或第 1 阶段。这不是那么重要,但在第 3 阶段,我已经为树分配了内存,并且在出错时必须释放它。
    • 您也可以在第 1 步结束时执行此操作,检查我的编辑
    • 嗯,这个总和有点像第 2 阶段循环中所做的事情……我需要稍微思考一下。 :)
    【解决方案3】:

    您需要确保没有输入会导致您的代码执行非法或未定义的行为,例如从数组末尾索引,因为此类非法输入可能会被用来攻击您的代码。

    在我看来,您应该尝试尽可能优雅地处理非法但不危险的输入,以便与其他人编写的程序互操作,这些程序可能以与您不同的方式解释规范,或者已经做出只有一种合理解释的小错误。这就是稳健性原则 - 您可以从 http://en.wikipedia.org/wiki/Robustness_principle 开始找到关于此的讨论。

    【讨论】:

    • 嗯,是的,这些是常见的、众所周知的规则。但是如何以最佳方式将这些规则应用于问题中的特定代码?
    猜你喜欢
    • 1970-01-01
    • 2011-10-06
    • 1970-01-01
    • 2011-09-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多