Как вычислить интернет-контрольную сумму из байта [] в Java

Я пытаюсь выяснить, как вычислить контрольную сумму в Интернете на Java, и это не причиняет мне боли. (Я ужасен при манипуляции бит.) Я нашел версию в С# Вычислить контрольную сумму Интернета (так же IP, aka RFC791) в С#. Однако моя попытка конвертировать его в 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;
}

Ответы

Ответ 1

Отредактировано для применения комментариев от @Andy, @EJP, @RD et al и для добавления лишних тестовых случаев.

Я использовал комбинацию ответа @Andys (правильно идентифицируя местоположение проблемы) и обновил код, чтобы включить модульные тесты, предоставленные в связанном ответе, вместе с проверенным контрольная сумма сообщения дополнительный тестовый пример.

Сначала реализация

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 complement sum over all octets (adjacent 8-bit pairs [A,B], final odd length is [A,0])
   * 2) apply 1 complement to this final sum
   *
   * Notes:
   * 1 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 edits and various comments on Stack Overflow
      data = (((buf[i] << 8) & 0xFF00) | ((buf[i + 1]) & 0xFF));
      sum += data;
      // 1 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 edits and various comments on Stack Overflow
      sum += (buf[i] << 8 & 0xFF00);
      // 1 complement carry bit correction in 16-bits (detecting sign extension)
      if ((sum & 0xFFFF0000) > 0) {
        sum = sum & 0xFFFF;
        sum += 1;
      }
    }

    // Final 1 complement value correction to 16-bits
    sum = ~sum;
    sum = sum & 0xFFFF;
    return sum;

  }

}

Затем unit test в 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);

  }

}

Ответ 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;
}

Ответ 3

Я думаю, что это продвижение по типу, которое вызывает проблемы. Давайте посмотрим на data = (((buf[i]) << 8) | ((buf[i + 1]) & 0xFF)):

  • ((buf[i]) << 8) будет продвигать buf[i] до int, вызывая расширение знака
  • (buf[i + 1]) & 0xFF также продвигает buf[i + 1] до int, вызывая расширение знака. Но маскировка этого аргумента с помощью 0xff правильная вещь - в этом случае мы получаем правильный операнд.
  • Все выражение получает до long (еще раз, знак включен).

Проблема заключается в первом аргументе - она ​​должна быть замаскирована с помощью 0xff00, например: data = (((buf[i] << 8) & 0xFF00) | ((buf[i + 1]) & 0xFF)). Но я подозреваю, что для Java реализованы более эффективные алгоритмы, возможно, даже в стандартной библиотеке есть один. Вы можете взглянуть на MessageDigest, возможно, он имеет один.