【问题标题】:How to calculate the internet checksum from a byte[] in Java如何从 Java 中的字节 [] 计算互联网校验和
【发布时间】:2011-05-06 01:27:27
【问题描述】:

我正试图弄清楚如何用 Java 计算 Internet 校验和,这给我带来了无尽的痛苦。 (我在位操作方面很糟糕。)我在 C#Calculate an Internet (aka IP, aka RFC791) checksum in C# 中找到了一个版本。但是,我尝试将其转换为 Java 并没有看到产生正确的结果。谁能看到我做错了什么?我怀疑是数据类型问题。

public long getValue() {
    byte[] buf = { (byte) 0xed, 0x2A, 0x44, 0x10, 0x03, 0x30};
    int length = buf.length;
    int i = 0;

    long sum = 0;
    long data = 0;
    while (length > 1) {
        data = 0;
        data = (((buf[i]) << 8) | ((buf[i + 1]) & 0xFF));

        sum += data;
        if ((sum & 0xFFFF0000) > 0) {
            sum = sum & 0xFFFF;
            sum += 1;
        }

        i += 2;
        length -= 2;
    }

    if (length > 0) {
        sum += (buf[i] << 8);
        // sum += buffer[i];
        if ((sum & 0xFFFF0000) > 0) {
            sum = sum & 0xFFFF;
            sum += 1;
        }
    }
    sum = ~sum;
    sum = sum & 0xFFFF;
    return sum;
}

【问题讨论】:

    标签: java checksum ip-protocol


    【解决方案1】:

    编辑以应用来自@Andy、@EJP、@RD 等人的 cmets,并添加额外的测试用例以确保安全。

    我使用了@Andys 答案的组合(正确识别问题的位置)并更新了代码以包含链接答案中提供的单元测试以及verified message checksum 附加测试用例。

    首先实现

    package org.example.checksum;
    
    public class InternetChecksum {
    
      /**
       * Calculate the Internet Checksum of a buffer (RFC 1071 - http://www.faqs.org/rfcs/rfc1071.html)
       * Algorithm is
       * 1) apply a 16-bit 1's complement sum over all octets (adjacent 8-bit pairs [A,B], final odd length is [A,0])
       * 2) apply 1's complement to this final sum
       *
       * Notes:
       * 1's complement is bitwise NOT of positive value.
       * Ensure that any carry bits are added back to avoid off-by-one errors
       *
       *
       * @param buf The message
       * @return The checksum
       */
      public long calculateChecksum(byte[] buf) {
        int length = buf.length;
        int i = 0;
    
        long sum = 0;
        long data;
    
        // Handle all pairs
        while (length > 1) {
          // Corrected to include @Andy's edits and various comments on Stack Overflow
          data = (((buf[i] << 8) & 0xFF00) | ((buf[i + 1]) & 0xFF));
          sum += data;
          // 1's complement carry bit correction in 16-bits (detecting sign extension)
          if ((sum & 0xFFFF0000) > 0) {
            sum = sum & 0xFFFF;
            sum += 1;
          }
    
          i += 2;
          length -= 2;
        }
    
        // Handle remaining byte in odd length buffers
        if (length > 0) {
          // Corrected to include @Andy's edits and various comments on Stack Overflow
          sum += (buf[i] << 8 & 0xFF00);
          // 1's complement carry bit correction in 16-bits (detecting sign extension)
          if ((sum & 0xFFFF0000) > 0) {
            sum = sum & 0xFFFF;
            sum += 1;
          }
        }
    
        // Final 1's complement value correction to 16-bits
        sum = ~sum;
        sum = sum & 0xFFFF;
        return sum;
    
      }
    
    }
    

    然后是JUnit4中的单元测试

    package org.example.checksum;
    
    import org.junit.Test;
    
    import static junit.framework.Assert.assertEquals;
    
    public class InternetChecksumTest {
      @Test
      public void simplestValidValue() {
        InternetChecksum testObject = new InternetChecksum();
    
        byte[] buf = new byte[1]; // should work for any-length array of zeros
        long expected = 0xFFFF;
    
        long actual = testObject.calculateChecksum(buf);
    
        assertEquals(expected, actual);
      }
    
      @Test
      public void validSingleByteExtreme() {
        InternetChecksum testObject = new InternetChecksum();
    
        byte[] buf = new byte[]{(byte) 0xFF};
        long expected = 0xFF;
    
        long actual = testObject.calculateChecksum(buf);
    
        assertEquals(expected, actual);
      }
    
      @Test
      public void validMultiByteExtrema() {
        InternetChecksum testObject = new InternetChecksum();
    
        byte[] buf = new byte[]{0x00, (byte) 0xFF};
        long expected = 0xFF00;
    
        long actual = testObject.calculateChecksum(buf);
    
        assertEquals(expected, actual);
      }
    
      @Test
      public void validExampleMessage() {
        InternetChecksum testObject = new InternetChecksum();
    
        // Berkley example http://www.cs.berkeley.edu/~kfall/EE122/lec06/tsld023.htm
        // e3 4f 23 96 44 27 99 f3
        byte[] buf = {(byte) 0xe3, 0x4f, 0x23, (byte) 0x96, 0x44, 0x27, (byte) 0x99, (byte) 0xf3};
    
        long expected = 0x1aff;
    
        long actual = testObject.calculateChecksum(buf);
    
        assertEquals(expected, actual);
      }
    
      @Test
      public void validExampleEvenMessageWithCarryFromRFC1071() {
        InternetChecksum testObject = new InternetChecksum();
    
        // RFC1071 example http://www.ietf.org/rfc/rfc1071.txt
        // 00 01 f2 03 f4 f5 f6 f7
        byte[] buf = {(byte) 0x00, 0x01, (byte) 0xf2, (byte) 0x03, (byte) 0xf4, (byte) 0xf5, (byte) 0xf6, (byte) 0xf7};
    
        long expected = 0x220d;
    
        long actual = testObject.calculateChecksum(buf);
    
        assertEquals(expected, actual);
    
      }
    
    }
    

    【讨论】:

    • 感谢额外的单元测试。由于一些糟糕的测试,我得到了关于我的代码是否不正确的相互矛盾的答案。
    • @chotchki 我已经编辑了答案以包括来自@Andy 等人的 cmets
    • 为什么calculateChecksum 返回一个长值? UDP、TCP 和 IPv4 校验和占用两个字节。所以 int(最多 4 个字节)应该足够了
    • 代码是从 RFC1071 第 4.1 节中的示例修改的,该示例使用 long 作为其总和。在编写 RFC 时,可能是 32 位。
    • 这在很大程度上与避免在 Java 中进行类型提升有关。查看其他答案了解更多详情(@Andy 现在是 @Andrey Balaguta)
    【解决方案2】:

    一个更短的版本如下:

    long checksum(byte[] buf, int length) {
        int i = 0;
        long sum = 0;
        while (length > 0) {
            sum += (buf[i++]&0xff) << 8;
            if ((--length)==0) break;
            sum += (buf[i++]&0xff);
            --length;
        }
    
        return (~((sum & 0xFFFF)+(sum >> 16)))&0xFFFF;
    }
    

    【讨论】:

    • 我喜欢你的版本,但&lt;&lt; 8 应该在“偶数”字节上
    【解决方案3】:

    我认为是类型提升造成了麻烦。来看看data = (((buf[i]) &lt;&lt; 8) | ((buf[i + 1]) &amp; 0xFF))

    1. ((buf[i]) &lt;&lt; 8) 会将buf[i] 提升为int,导致符号扩展
    2. (buf[i + 1]) &amp; 0xFF 也会将buf[i + 1] 提升为int,从而导致符号扩展。但是用0xff 掩盖这个参数是正确的——在这种情况下我们得到了正确的操作数。
    3. 整个表达式被提升为long(再一次,包括符号)。

    问题在于第一个参数 - 它应该被 0xff00 屏蔽,就像这样:data = (((buf[i] &lt;&lt; 8) &amp; 0xFF00) | ((buf[i + 1]) &amp; 0xFF))。但我怀疑 Java 实现了更有效的算法,甚至标准库也有。你可以看看MessageDigest,也许它有一个。

    【讨论】:

    • #2 不正确。文字 0xFF 被视为一个 int,而不是一个字节。在第 3 步中,您将有一个简短的 | int,第一个参数被提升,而不是第二个。因此,问题不在于您的示例中的 0x00,0xFF,而是 0xFF,0x00(或任何 buf[i] > 0x7F)。当第一个参数从 short 扩大到 int 时,它会按照您的解释进行符号扩展。在原程序中,是0xed、0x2A的情况导致校验和不正确。
    • 这篇文章完全不正确。 #1、#2 和 #3 的结果是 int,除非其中任何一个参数是 long,在这种情况下它是 long。整个表达式的结果类似地是一个 int 并且当存储在一个中时它会扩大到一个 long。 (int) 强制转换完全没有必要。
    • @RD :老实说,我很困惑如何解决这个问题。
    • @EJP,@RD,感谢您的更正,抱歉误导。我已相应地更正了帖子。
    • 这是符号扩展,不是“符号扩展”。
    猜你喜欢
    • 2011-04-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-15
    • 2010-11-11
    • 1970-01-01
    • 2011-02-04
    相关资源
    最近更新 更多