2010-11-06 14 views
14

Próbuję dowiedzieć się, jak obliczyć Internet Checksum w Javie i powodując mi bez końca bólu. (Jestem okropny przy odrobinie manipulacji.) Znalazłem wersję w C# Calculate an Internet (aka IP, aka RFC791) checksum in C#. Jednak moja próba przekonwertowania go na Javę nie zapewnia poprawnych wyników. Czy ktoś może zobaczyć, co robię źle? Podejrzewam, że wystąpił problem z typem danych.Jak obliczyć sumę kontrolną Internetu z bajtu [] w Javie

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

Odpowiedz

14

Edytowane w celu zastosowania komentarzy z @Andy, @EJP, @RD i innych oraz dodania dodatkowych przypadków testowych.

Użyłem kombinacji odpowiedzi @Andys (poprawnie określającej lokalizację problemu) i zaktualizowałem kod tak, aby zawierał testy jednostkowe dostarczone w połączonej odpowiedzi wraz z dodatkowym testowym przypadkiem verified message checksum.

pierwsze wdrożenie

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; 

    } 

} 

Następnie testów jednostkowych w 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

Dziękujemy za dodatkowe testy jednostkowe. Otrzymywałem sprzeczne odpowiedzi na temat tego, czy mój kod był nieprawidłowy z powodu pewnych złych testów. – chotchki

+1

@chotchki Edytowałem odpowiedź na komentarze @Andy et al –

+0

Dlaczego funkcja 'calculateChecksum' zwraca długą wartość? Sumy kontrolne UDP, TCP i IPv4 zajmują dwa bajty. Tak więc int (maksymalnie 4 bajty) powinno wystarczyć. –

1

Myślę, że to rodzaj promocji powoduje kłopoty. Spójrzmy na data = (((buf[i]) << 8) | ((buf[i + 1]) & 0xFF)):

  1. ((buf[i]) << 8) będzie promować buf[i] do int, powodując ekspansję znak
  2. (buf[i + 1]) & 0xFF będzie również promować buf[i + 1] do int, powodując ekspansję migowego. Ale maskowanie tego argumentu za pomocą 0xff jest słuszne - w tym przypadku otrzymujemy poprawny operand.
  3. Całe wyrażenie zostaje awansowane na long (po raz kolejny dołączony znak).

Problem leży w pierwszym argumencie - należy go zamaskować na 0xff00, tak: data = (((buf[i] << 8) & 0xFF00) | ((buf[i + 1]) & 0xFF)). Ale podejrzewam, że dla Java są bardziej wydajne algorytmy, może nawet biblioteka standardowa je posiada. Możesz rzucić okiem na MessageDigest, może ma.

+2

# 2 jest niepoprawny. Dosłowne 0xFF jest traktowane jako int, nie bajt. W kroku 3 będziesz miał krótkie | int, gdzie PIERWSZY argument zostaje awansowany, a nie drugi. Dlatego problem nie występuje z 0x00,0xFF, jak w twoim przykładzie, ale z 0xFF, 0x00 (lub dowolnym buf [i]> 0x7F). Kiedy pierwszy parametr zostanie rozszerzony z krótkiego na int, zostanie rozszerzony o znak, jak wyjaśniłeś. W oryginalnym programie jest to 0xed, przypadek 0x2A, który powoduje, że suma kontrolna jest niepoprawna. –

+2

Ten wpis jest szalenie niepoprawny. Wynik # 1, # 2 i # 3 jest int, chyba że któryś z argumentów jest długi, w którym to przypadku jest długi. Wynik całego wyrażenia jest podobnie int i rozszerza się na długi, gdy jest przechowywany w jednym. Odlewanie (int) jest całkowicie niepotrzebne. – EJP

+0

@RD: Szczerze mówiąc, jestem zdezorientowany, jak rozwiązać ten problem. – chotchki

11

znacznie krótsza wersja jest następująca:

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; 
} 
Powiązane problemy