Я считаю, что функция контрольной суммы TCP делает следующее:
Звучит достаточно просто. Поэтому я написал свой собственный универсальный checksum
функция:
#include <inttypes.h>
#include <arpa/inet.h>
uint16_t checksum(uint16_t * data, int size) {
uint16_t sum = 0;
int i = 0, length = size / 2;
while (i < length) sum += data[i++];
if (size % 2) sum += data[i] & 0xFF00;
return htons(~sum);
}
Однако другие люди написали checksum
функции, которые кажутся более сложными. Например:
uint16_t checksum(uint16_t * addr, int len) {
int nleft = len;
int sum = 0;
uint16_t * w = addr;
uint16_t answer = 0;
while (nleft > 1) {
sum += *w++;
nleft -= sizeof(uint16_t);
}
if (nleft == 1) {
*(uint8_t *) (&answer) = *(uint8_t *) w;
sum += answer;
}
sum = (sum >> 16) + (sum & 0xFFFF);
sum += (sum >> 16);
answer = ~sum;
return (answer);
}
У меня есть несколько вопросов относительно этого кода:
*(uint8_t *) (&answer) = *(uint8_t *) w;
на самом деле делать?Почему мы берем сумму как:
sum = (sum >> 16) + (sum & 0xFFFF);
sum += (sum >> 16);
Способ вычисления контрольной суммы TCP изменился?
Я действительно не понимаю, почему мы делаем sum = (sum >> 16) + (sum & 0xFFFF)
, Рассматривать sum
является 0xABCD
:
0xABCD >> 16 == 0x0000
0xABCD & 0xFFFF == 0xABCD
0x0000 + 0xABCD == 0xABCD
Это кажется лишним шагом. То же самое касается следующего утверждения sum += (sum >> 16)
,
Функция контрольной суммы предназначена только для процессоров с прямым порядком байтов.
Первый цикл while оптимизирован по скорости.
&answer
трюк загружает последний байт (если было нечетное количество байтов) в старший байт answer
оставляя младший байт ноль, аналогично тому, что делает ваш код data[i] & 0xff00
, Вот как это работает
1) take the address of answer (&answer)
2) convert that to a byte pointer (uint8_t *)
2a) on a big endian processor the first byte of a 16-bit quantity is the high byte
3) overwrite the high byte with the last byte of the data
Предполагается, что контрольная сумма вычисляется с добавлением переносов обратно. Здесь предполагается, что этот код выполняется на машине, где int
32-битный Следовательно, (sum & 0xffff)
это 16-битная контрольная сумма, и (sum >> 16)
биты переноса (если таковые имеются), которые должны быть добавлены обратно. Следовательно, строка
sum = (sum >> 16) + (sum & 0xffff);
корректирует сумму, чтобы включить переносы. Однако эта строка кода может сама генерировать еще один бит переноса. Итак, следующая строка sum += (sum >> 16)
добавляет, что переносить (если таковые имеются) обратно в контрольную сумму.
Наконец, возьмите с собой дополнение к ответу. Обратите внимание, что htons
не используется, поскольку вся функция неявно предполагает, что она работает на процессоре с прямым порядком байтов.
Что значит утверждение * (uint8_t *) (&ответ) = * (uint8_t *) w;
на самом деле делать?
Это бросает uint16_t
в uint8_t
только так 8 наиболее правильные биты копируются из w
в answer
, Рассматривать:
uint16_t x = 0x1234;
uint16_t* w = &x; // *w = // 0001001000110100
*(uint16_t *) (&answer) = *(uint16_t *) w; // answer = 0001001000110100
*(uint8_t *) (&answer) = *(uint8_t *) w; // answer = 0000000000110100
Почему мы берем сумму как:
sum = (sum >> 16) + (sum & 0xFFFF);
sum += (sum >> 16);
answer = ~sum;
sum
является 32 биты. 65536 ≡ 1 mod 65535
, Итак Выражение переноса (sum & 0xffff) + (sum >> 16)
уменьшает sum
по модулю 65535
, Это необходимо для добавления любого (возможного) результирующего переноса в итоговую сумму.
*(uint8_t *) (&answer) = *(uint8_t *) w;
На правой стороне, он преобразует w
к uint8_t*
и разыменовывает это. Усекает данные мусора, которые будут прочитаны при разыменовании uint16_t*
указывая на последний байт. С левой стороны, он принимает адрес (указатель) answer
и преобразует его в uint8_t*
и разыменовывает это. Таким образом, он занимает первый байт, указанный w
и присваивает значение первому байту ответа. По сути, эта строка делает 2. Add a one byte padding of 0s to the end of the last block if it's not 2 bytes long, to make it 2 bytes.
Преобразования на левой стороне необходимы для поддержки систем с прямым порядком байтов … Я думаю.Это утверждение учитывает случай (см. RFC793 или RFC1701) где пакет имеет нечетное число байтов: [A,B] + [C,D] + ... + [Z,0]
включив в сумму количество (answer
) с 2 наиболее значимыми байтами как Z
и 2 младших байта как 0. Помните +
здесь всегда 1 дополнение дополнения.
sum
32-разрядный аккумулятор Чтобы добавить дополнение 1, мы добавляем перенос после накопления битов. 2 наиболее значимых байта sum
содержит биты переноса, если таковые имеются.
Если вы посмотрите на RFC1701 вверху вы можете увидеть, какие RFC обновляют его. Нет никого, кто бы это заменил.