我相信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)
。
答案 0 :(得分:2)
声明*(uint8_t *)(&amp; answer)= *(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
,end-around carry expression (sum & 0xffff) + (sum >> 16)
缩小sum
模65535
。这是必要的,可以将任何(最终)结果带回到结果总和中。
答案 1 :(得分:1)
*(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.
左侧的转换需要支持大端系统......我想。答案 2 :(得分:1)
校验和功能似乎仅适用于大端处理器。
第一个while循环针对速度进行了优化。
&answer
技巧将最后一个字节(如果有奇数个字节)加载到answer
的高字节中,使低字节为零,类似于您的代码对{{ 1}}。它的工作方式是这个
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
为32位的机器上运行。因此,int
是16位校验和,(sum & 0xffff)
是需要重新加入的进位(如果有的话)。因此,行
(sum >> 16)
调整总和以包含进位。但是,该行代码本身可以生成另一个进位。所以下一行sum = (sum >> 16) + (sum & 0xffff);
将携带(如果有的话)添加回校验和。
最后,采取答案的补码。请注意,未使用sum += (sum >> 16)
,因为整个函数隐式假定它在大端处理器上运行。
答案 3 :(得分:1)