2010-11-06 134 views
14

我想弄清楚如何计算Java中的互联网校验和及其导致我无尽的痛苦。 (我在位操作上很糟糕。)我在C#Calculate an Internet (aka IP, aka RFC791) checksum in C#中找到了一个版本。但是,我将它转换为Java的尝试没有看到产生正确的结果。任何人都可以看到我做错了什么?我怀疑数据类型问题。如何从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; 
} 

回答

14

修改为应用@Andy,@EJP,@RD等人的评论,并添加额外的测试用例来确保。

我已经使用@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); 

    } 

} 
+0

感谢您进行额外的单元测试。关于我的代码是否由于一些不好的测试而导致错误,我得到了相互矛盾的答案。 – chotchki 2010-11-06 20:40:54

+1

@chotchki我编辑了答案,以包含@Andy等人的评论 – 2010-11-07 09:08:09

+0

为什么'calculateChecksum'返回一个长整型值? UDP,TCP和IPv4校验和占用两个字节。所以int(最多4个字节)应该足够了 – 2015-05-13 19:04:59

1

我认为这是造成麻烦的类型促销。让我们看看data = (((buf[i]) << 8) | ((buf[i + 1]) & 0xFF))

  1. ((buf[i]) << 8)将促进buf[i]int,导致符号扩展
  2. (buf[i + 1]) & 0xFF也将促进buf[i + 1]int,导致符号扩展。但用0xff掩盖这个论点是正确的 - 在这种情况下我们得到正确的操作数。
  3. 整个表达式被提升为long(再一次,包含符号)。

问题在于第一个参数 - 它应该被0xff00掩盖,如下所示:data = (((buf[i] << 8) & 0xFF00) | ((buf[i + 1]) & 0xFF))。但我怀疑有更高效的Java算法,也许甚至标准库都有。你可以看看MessageDigest,也许它有一个。

+2

#2不正确。文字0xFF被视为一个int,而不是一个字节。在步骤#3中,您将有一个短暂的| int,其中第一个参数得到提升,而不是第二个。因此,问题不在于0x00,0xFF,而是在0xFF,0x00(或任何buf [i]> 0x7F)中。当第一个参数从short扩展到int时,它会按照您的解释进行符号扩展。在原始程序中,0xed,0x2A会导致校验和不正确。 – 2010-11-06 21:30:44

+2

这篇文章是非常不正确的。 #1,#2和#3的结果是一个整数,除非其中的任何一个参数很长,在这种情况下它是一个长整数。整个表达式的结果同样是一个整数,并且当存储在一个整数中时它会变宽。 (int)强制转换是完全不必要的。 – EJP 2010-11-06 23:04:22

+0

@RD:老实说,我很困惑如何解决这个问题。 – chotchki 2010-11-07 01:24:37

11

甲短得多版本如下:

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; 
}