【问题标题】:Correctness of Fletcher32 checksum algorithmFletcher32校验和算法的正确性
【发布时间】:2016-10-26 19:15:14
【问题描述】:

我很难确定Fletcher checksum algorithm 的 32 位变体的哪个实现是正确的。维基百科提供了以下优化实现:

uint32_t fletcher32( uint16_t const *data, size_t words ) {
        uint32_t sum1 = 0xffff, sum2 = 0xffff;
        size_t tlen;

        while (words) {
                tlen = words >= 359 ? 359 : words;
                words -= tlen;
                do {
                        sum2 += sum1 += *data++;
                } while (--tlen);
                sum1 = (sum1 & 0xffff) + (sum1 >> 16);
                sum2 = (sum2 & 0xffff) + (sum2 >> 16);
        }
        /* Second reduction step to reduce sums to 16 bits */
        sum1 = (sum1 & 0xffff) + (sum1 >> 16);
        sum2 = (sum2 & 0xffff) + (sum2 >> 16);
        return sum2 << 16 | sum1;
}

此外,我已经改编了 Wikipedia 文章中未优化的 16 位示例来计算 32 位校验和:

uint32_t naive_fletcher32(uint16_t *data, int words) {
   uint32_t sum1 = 0;
   uint32_t sum2 = 0;

   int index;
   for( index = 0; index < words; ++index ) {
      sum1 = (sum1 + data[index]) % 0xffff;
      sum2 = (sum2 + sum1) % 0xffff;
   }
   return (sum2 << 16) | sum1;
}

这两种实现都产生相同的结果,例如0x56502d2a 用于字符串 abcdef。为了验证这确实是正确的,我试图找到该算法的其他实现:

所有这些似乎都同意 abcdef 的校验和是 0x8180255 而不是 Wikipedia 上的实现给出的值。我已将其范围缩小到实现操作的数据缓冲区。上述所有非维基百科实现一次操作一个字节,而维基百科实现使用 16 位字计算校验和。如果我修改上述“幼稚”的 Wikipedia 实现以改为按字节操作,它的内容如下:

uint32_t naive_fletcher32_per_byte(uint8_t *data, int words) {
   uint32_t sum1 = 0;
   uint32_t sum2 = 0;

   int index;
   for( index = 0; index < words; ++index ) {
      sum1 = (sum1 + data[index]) % 0xffff;
      sum2 = (sum2 + sum1) % 0xffff;
   }
   return (sum2 << 16) | sum1;
}

唯一改变的是签名,真的。所以这个修改后的朴素实现和上面提到的实现(除了维基百科)同意abcdef的校验和确实是0x8180255

我现在的问题是:哪个是正确的?

【问题讨论】:

  • naive_fletcher 中,循环中的% 0xffff 是不必要的。您可以在循环之后执行此操作。
  • 这就是为什么我认为它是幼稚的实现 :D 感谢您的提示,但问题并不是关于优化 :)
  • @PaulOgilvie:% 0xffff in the loop are not necessary只要没有溢出
  • @greybeard,溢出会发生什么?永远不会使用的位只会从寄存器中掉出来。
  • @PaulOgilvie: 0x10000%0xffff 是 1,而不是 0:携带需要计入。

标签: c algorithm checksum correctness


【解决方案1】:

根据standard,正确的方法是维基百科提供的方法——除了名称:

请注意,8 位 Fletcher 算法给出 16 位校验和,而 16 位算法给出 32 位校验和。

【讨论】:

    【解决方案2】:

    这些是 test vectors,它们使用 16 位和 32 位校验和的两种不同实现进行交叉检查:

    8-bit implementation (16-bit checksum)
     "abcde" -> 51440 (0xC8F0)
     "abcdef" -> 8279 (0x2057)
     "abcdefgh" -> 1575 (0x0627)
    
    16-bit implementation (32-bit checksum)
     "abcde" -> 4031760169 (0xF04FC729)
     "abcdef" -> 1448095018 (0x56502D2A)
     "abcdefgh" -> 3957429649 (0xEBE19591)
    

    【讨论】:

      【解决方案3】:

      TCP 备用校验和选项描述了用于 TCP 的 Fletcher 校验和算法:RFC 1146,日期为 1990 年 3 月。

      讨论了给出 16 位校验和的 8 位 Fletcher 算法和给出 32 位校验和的 16 位算法。

      8 位 Fletcher 校验和算法是在一个序列上计算的 通过维持 2 个数据八位位组(称它们为 D[1] 到 D[N]) 无符号 1 的补码 8 位累加器 A 和 B,其内容为 最初为零,并执行以下循环,其中 i 范围为 1到N:

             A := A + D[i]
             B := B + A
      

      16 位 Fletcher 校验和算法以完全相同的方式进行 方式为 8 位校验和算法,除了 A、B 和 D[i] 是 16 位量。这是必要的(因为它与 标准 TCP 校验和算法)填充包含奇数的数据报 八位字节数为零的八位字节数。

      这与Wikipedia 算法一致。简单的测试程序确认引用的结果:

          #include <stdio.h>
          #include <string.h>
          #include <stdint.h> // for uint32_t
          
          uint32_t fletcher32_1(const uint16_t *data, size_t len)
          {
                  uint32_t c0, c1;
                  unsigned int i;
          
                  for (c0 = c1 = 0; len >= 360; len -= 360) {
                          for (i = 0; i < 360; ++i) {
                                  c0 = c0 + *data++;
                                  c1 = c1 + c0;
                          }
                          c0 = c0 % 65535;
                          c1 = c1 % 65535;
                  }
                  for (i = 0; i < len; ++i) {
                          c0 = c0 + *data++;
                          c1 = c1 + c0;
                  }
                  c0 = c0 % 65535;
                  c1 = c1 % 65535;
                  return (c1 << 16 | c0);
          }
          
          uint32_t fletcher32_2(const uint16_t *data, size_t l)
          {
              uint32_t sum1 = 0xffff, sum2 = 0xffff;
          
              while (l) {
                  unsigned tlen = l > 359 ? 359 : l;
                  l -= tlen;
                  do {
                      sum2 += sum1 += *data++;
                  } while (--tlen);
                  sum1 = (sum1 & 0xffff) + (sum1 >> 16);
                  sum2 = (sum2 & 0xffff) + (sum2 >> 16);
              }
              /* Second reduction step to reduce sums to 16 bits */
              sum1 = (sum1 & 0xffff) + (sum1 >> 16);
              sum2 = (sum2 & 0xffff) + (sum2 >> 16);
              return (sum2 << 16) | sum1;
          }
          
          int main()
          {
              char *str1 = "abcde";  
              char *str2 = "abcdef";
              
              size_t len1 = (strlen(str1)+1) / 2; //  '\0' will be used for padding 
              size_t len2 = (strlen(str2)+1) / 2; // 
              
              uint32_t f1 = fletcher32_1(str1,  len1);
              uint32_t f2 = fletcher32_2(str1,  len1);
          
              printf("%u %X \n",    f1,f1);
              printf("%u %X \n\n",  f2,f2);
             
              f1 = fletcher32_1(str2,  len2);
              f2 = fletcher32_2(str2,  len2);
          
              printf("%u %X \n",f1,f1);
              printf("%u %X \n",f2,f2);
             
              return 0;
          }
          
      

      输出:

      4031760169 F04FC729                                                                                                                                                                                                                              
      4031760169 F04FC729                                                                                                                                                                                                                              
                                                                                                                                                                                                                                                       
      1448095018 56502D2A                                                                                                                                                                                                                              
      1448095018 56502D2A 
      

      【讨论】:

      • 但是,fletcher32_1 和 fletcher32_2 并不总是产生相同的结果(参见 Sven 的分析。)例如,fletcher32_1(0,0) 给出 0x0 和 fletcher32_2(0,0)=0xffffffffffffffff。一般来说,在维基百科上发现的任何与参考相比都经过修改的代码都应该被视为非常可疑。
      【解决方案4】:

      我的答案集中在s = (s &amp; 0xffff) + (s &gt;&gt; 16); 的正确性上。 这显然应该取代模运算。现在模运算的大问题是需要执行的除法。诀窍是不做除法并估计floor(s / 65535)。所以不是计算s - floor(s/65535)*65535,这与模数相同,我们计算s - floor(s/65536)*65535。这显然不等于做模。但是快速减小s的大小已经足够了。

      现在我们有

        s - floor(s / 65536) * 65535
      = s - (s >> 16) * 65535
      = s - (s >> 16) * (65536 - 1)
      = s - (s >> 16) * 65536 + (s >> 16)
      = (s & 0xffff) + (s >> 16)
      

      由于(s &amp; 0xffff) + (s &gt;&gt; 16) 不等于做模运算,所以使用这个公式是不够的。如果s == 65535s % 65535 将产生零。但是,前一个公式产生65535。所以这里发布的优化的 Wikipedia 实现显然是错误的!最后3行需要改成

              /* Second reduction step to reduce sums to 16 bits */
              sum1 = (sum1 & 0xffff) + (sum1 >> 16);
              sum2 = (sum2 & 0xffff) + (sum2 >> 16);
              if (sum1 >= 65535) { sum1 -= 65535; }
              if (sum2 >= 65535) { sum2 -= 65535; }
              return (sum2 << 16) | sum1;
      

      值得注意的是,我在 Wikipedia 页面上再也找不到优化的实现了(2020 年 2 月)。

      附录: 想象一下s 将等于最大的无符号 32 位值,即0xFFFF_FFFF。然后公式(s &amp; 0xffff) + (s &gt;&gt; 16); 产生0x1FFFE。那正好是 65535 的两倍。所以修正步骤if (s &gt;= 65535) { s -= 65535; } 将不起作用,因为它最多减去 65535 一次。所以我们希望在循环中保持sum1sum2 严格小于0xFFFF_FFFF。然后该公式最多产生 2*65535-1 并且校正步骤将起作用。以下简单的 python 程序确定,sum2 在 360 次迭代后会变得太大。所以一次最多处理 359 个 16 位字是完全正确的。

      s1 = 0x1FFFD
      s2 = 0x1FFFD
      for i in range(1,1000):
          s1 += 0xFFFF
          s2 += s1
          if s2 >= 0xFFFFFFFF:
              print(i)
              break
      

      【讨论】:

      • 修正后似乎不清楚优化版本是否更快。 (这可能取决于硬件。)请注意,此更改仅涉及循环外部的优化,对吧?
      • 我没有计算 359 是否是正确的界限。除此之外,循环是正确的。循环内不需要我的更正。最后只需要一次。
      • 我添加了对数字 359 的解释。
      【解决方案5】:

      在 HideFromKGB 的答案中引用的standard 中,算法很简单:8 位版本仅使用 8 位累加器(“ints”),产生 8 位结果 A 和 B,而 16 位版本使用 16 位“整数”,产生 16 位结果 A 和 B。

      需要注意的是,维基百科所说的“32 位 Fletcher”实际上是“16 位 Fletcher”。名称中的位数在标准中是指每个 D[i] 以及 A 和 B 中的位数,但在 Wikipedia 上是指“堆叠结果”中的位数,即在 @ 32 位结果为 987654322@。

      我没有实现这一点,但也许这可以解释差异。我倾向于说你的解释(实现)是正确的。

      注意:还请注意,有必要用零填充 data 到适当的字节数。

      【讨论】:

      • 感谢您的回答!对我来说,这仍然留下了一个问题,为什么我发现的所有其他实现都不符合这个标准。也就是说,没有人实施的标准是毫无价值的,但是我也没有在 TCP 中看到实施,也许我应该检查一下。
      • 我在您的第一个参考资料中留下了澄清请求。第二个和第三个似乎很不官方,没有选择要求澄清。在您的第三个参考文献中,我没有找到对 Fletcher 或其 RFC 的参考。
      猜你喜欢
      • 2012-09-03
      • 1970-01-01
      • 1970-01-01
      • 2015-04-14
      • 1970-01-01
      • 2015-06-16
      • 2017-08-26
      • 2016-05-31
      • 1970-01-01
      相关资源
      最近更新 更多