2010-11-06 26 views
14

Estoy tratando de averiguar cómo calcular la suma de comprobación de Internet en Java y me causa infinitos problemas. (Soy horrible en un poco de manipulación.) Encontré una versión en C# Calculate an Internet (aka IP, aka RFC791) checksum in C#. Sin embargo, mi intento de convertirlo a Java no produce los resultados correctos. ¿Alguien puede ver lo que estoy haciendo mal? Sospecho que hay un problema con el tipo de datos.Cómo calcular la suma de comprobación de Internet desde un byte [] en 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; 
} 

Respuesta

14

Editado para aplicar comentarios de @Andy, @EJP, @RD y otros y agregar casos de prueba adicionales solo para estar seguro.

He utilizado una combinación de respuesta @Andys (identificar correctamente la ubicación del problema) y se actualiza el código para incluir las pruebas unitarias previstas en la respuesta relacionado con un caso de prueba adicional verified message checksum.

En primer lugar la aplicación

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; 

    } 

} 

A continuación, la unidad de prueba en 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

Gracias por las pruebas de unidad adicionales. Obtuve respuestas contradictorias sobre si mi código era incorrecto debido a algunas malas pruebas. – chotchki

+1

@chotchki He editado la respuesta para incluir comentarios de @Andy et al –

+0

¿Por qué 'calculateChecksum' devuelve un valor largo? Las sumas de comprobación UDP, TCP e IPv4 toman dos bytes. Así que int (como máximo 4 bytes) debería ser suficiente –

1

Creo que es un tipo de promoción que está causando problemas. Veamos data = (((buf[i]) << 8) | ((buf[i + 1]) & 0xFF)):

  1. ((buf[i]) << 8) promovería buf[i] a int, causando la expansión signo
  2. (buf[i + 1]) & 0xFF también promovería buf[i + 1] a int, causando la expansión de signos. Pero enmascarar este argumento con 0xff es lo correcto: obtenemos el operando correcto en este caso.
  3. Toda la expresión se promueve a long (una vez más, signo incluido).

El problema radica en que el primer argumento - que debe ser enmascarada con 0xff00, así: data = (((buf[i] << 8) & 0xFF00) | ((buf[i + 1]) & 0xFF)). Pero sospecho que hay algoritmos más eficientes implementados para Java, tal vez incluso la biblioteca estándar tiene uno. Puede echar un vistazo al MessageDigest, tal vez tenga uno.

+2

# 2 es incorrecta. El literal 0xFF se trata como un int, no como un byte. En el paso # 3 tendrás un corto | int, donde se promueve el PRIMER argumento, no el segundo. Por lo tanto, el problema no es con 0x00,0xFF como en su ejemplo, sino con 0xFF, 0x00 (o cualquier buf [i]> 0x7F). Cuando el primer parámetro se amplía de corto a int, se amplía como se explicó. En el programa original, es el caso 0xed, 0x2A lo que hace que la suma de comprobación sea incorrecta. –

+2

Esta publicación es muy incorrecta. El resultado de # 1, # 2 y # 3 es un int a menos que alguno de los argumentos sea largo, en cuyo caso es largo. El resultado de toda la expresión es similarmente int y se ensancha a largo cuando se almacena en uno. El (int) elenco es completamente innecesario. – EJP

+0

@RD: Honestamente, estoy confundido sobre cómo solucionar ese problema. – chotchki

11

Una versión mucho más corta es la siguiente:

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