数据包的校验和字段,它应该在头部还是尾部?
Checksum field of a packet, should it be at head or tail?
我正在做一个与 FPGA 板通信的小项目。它向 FPGA 发送数据,有时 FPGA 会发回数据。所以我和我的同事打算设计一个简单的通信协议。
我们决定在我们的协议中添加校验和字段。但我们对把它放在哪里有不同的想法。我建议放在数据包的头部,他更喜欢放在尾部。
我的建议是:
typedef struct {
uint32_t magic_number;
uint16_t frame_type;
uint16_t length;
uint16_t checksum;
uint8_t data[];
}blah_blah;
这是他的:
typedef struct {
uint32_t magic_number;
uint16_t frame_type;
uint16_t length;
uint8_t data[]; //let's assume it is a valid syntax
uint16_t checksum;
}blah_blah;
我的观点是我可以轻松地声明结构并直接将最后一个数据字段转换为我想要的另一个结构,前提是可变长度部分位于数据包的末尾。而他的说法是FPGA是按时钟读写数据的,所以每个时钟更新校验和,最后放在包尾或者校验比较方便。
我对数字设计的了解非常有限,所以我不知道哪个更好。我应该把校验和字段放在哪里?
编辑:有人提醒我在投射指针时严格别名。即使我使用 memcpy
来避免转换,我仍然更容易在头部获得校验和。如果校验和在尾部,那么我必须将缓冲区转换为 const char*
,然后添加长度偏移量,然后将其转换为 const uint16_t*
以获取它。
有多种方法可以做到这一点。
除了 uint8_t data[];
必须 在 struct
.
的末尾
因此,正如我上面所建议的,不要将校验和 放在 的 struct
中。您甚至可以将 data
排除在 struct
[或不] 之外。
正如我在顶部评论中所建议的那样,缓冲区 可能如下所示:
struct|data|csum
并且,如果数据包有效,校验和 所有,总和可能为零。
这是我见过的一种使用方式(例如 SDLC 等)的示例代码。它很粗糙,只检查一些错误。它允许数据负载位于不同的缓冲区中。
typedef struct {
uint32_t magic_number;
uint16_t frame_type;
uint16_t length;
uint8_t data[];
} blah_blah;
#define CSUMSEED 0x1234
#define MAGIC 0xDEADBEEF
uint16_t
calcsum(uint16_t csum,const void *ptr,uint16_t length)
{
uint8_t *data = ptr;
for (; length != 0; --length, ++data)
csum += *data;
return csum;
}
void
sendone(int socket,uint16_t type,const void *data,uint16_t length)
{
blah_blah hdr;
uint16_t csum = CSUMSEED;
hdr.magic_number = MAGIC;
hdr.frame_type = type;
hdr.length = length;
csum = calcsum(csum,&hdr,sizeof(hdr));
csum = calcsum(csum,data,length);
// convert csum to "inverse" ???
csum = ~csum;
// could use writev here instead ...
write(socket,&hdr,sizeof(hdr));
write(socket,data,length);
write(socket,&csum,sizeof(csum));
}
void
recvone(int socket,blah_blah *hdr,void *data)
// data -- must be large enough to hold _maximum_ payload
{
uint16_t csum = CSUMSEED;
ssize_t length;
// get the header struct
length = read(socket,hdr,sizeof(*hdr));
if (length != sizeof(*hdr))
error();
// magic number must match
if (hdr.magic != MAGIC)
error()
// get checksum for header
csum = calcsum(csum,hdr,sizeof(*hdr));
// get the data
length = read(socket,data,hdr->length + sizeof(uint16_t));
if (length != (hdr->length + sizeof(uint16_t))
error();
// get remaining checksum (including the trailing/appended CRC)
csum = calcsum(data,data,length);
if (csum != 0)
error();
}
如果您希望[如 struct
定义中所示]稍加修改,您可以将其调整为将数据嵌入到结构的末尾。
这里有一个附加校验和的小修改。接收方将从 read
获得的校验和与计算的校验和进行比较:
typedef struct {
uint32_t magic_number;
uint16_t frame_type;
uint16_t length;
uint8_t data[];
} blah_blah;
#define CSUMSEED 0x1234
#define MAGIC 0xDEADBEEF
uint16_t
calcsum(uint16_t csum,const void *ptr,uint16_t length)
{
uint8_t *data = ptr;
for (; length != 0; --length, ++data)
csum += *data;
return csum;
}
void
sendone(int socket,uint16_t type,const void *data,uint16_t length)
{
blah_blah hdr;
uint16_t csum = CSUMSEED;
hdr.magic_number = MAGIC;
hdr.frame_type = type;
hdr.length = length;
csum = calcsum(csum,&hdr,sizeof(hdr));
csum = calcsum(csum,data,length);
// could use writev here instead ...
write(socket,&hdr,sizeof(hdr));
write(socket,data,length);
write(socket,&csum,sizeof(csum));
}
void
recvone(int socket,blah_blah *hdr,void *data)
// data -- must be large enough to hold _maximum_ payload
{
uint16_t csum = CSUMSEED;
ssize_t length;
// get the header struct
length = read(socket,hdr,sizeof(*hdr));
if (length != sizeof(*hdr))
error();
// magic number must match
if (hdr.magic != MAGIC)
error()
// get checksum for header
csum = calcsum(csum,hdr,sizeof(*hdr));
// get the data
length = read(socket,data,hdr->length);
if (length != hdr->length)
error();
// get data checksum
csum = calcsum(data,data,length);
// get message checksum
uint16_t csum2;
length = read(socket,&csum2,sizeof(csum2));
if (length != sizeof(csum2))
error();
// validate the checksum
if (csum2 != csum)
error();
}
对于嵌入式设备,这取决于硬件。如果您通过中断一次一个字节地执行它,那么最后可能会更好。 (你边计算边计算 reading/writing RX/TX 注册然后添加它,或者检查它,这取决于你是在读还是写。)
如果它的 DMA 驱动和两个独立的块,或者其中只有一个是 DMA 驱动的,那么它可能无关紧要。在所有数据到达您之后,您将最终计算 checksum/crc。
然而,对于 FPGA,他们几乎肯定更容易在最后一个字节消失后 'tack on' 最后的校验和,而不是预先计算数据包上的校验和,然后将其插入在中间,然后发送数据。
我正在做一个与 FPGA 板通信的小项目。它向 FPGA 发送数据,有时 FPGA 会发回数据。所以我和我的同事打算设计一个简单的通信协议。
我们决定在我们的协议中添加校验和字段。但我们对把它放在哪里有不同的想法。我建议放在数据包的头部,他更喜欢放在尾部。
我的建议是:
typedef struct {
uint32_t magic_number;
uint16_t frame_type;
uint16_t length;
uint16_t checksum;
uint8_t data[];
}blah_blah;
这是他的:
typedef struct {
uint32_t magic_number;
uint16_t frame_type;
uint16_t length;
uint8_t data[]; //let's assume it is a valid syntax
uint16_t checksum;
}blah_blah;
我的观点是我可以轻松地声明结构并直接将最后一个数据字段转换为我想要的另一个结构,前提是可变长度部分位于数据包的末尾。而他的说法是FPGA是按时钟读写数据的,所以每个时钟更新校验和,最后放在包尾或者校验比较方便。
我对数字设计的了解非常有限,所以我不知道哪个更好。我应该把校验和字段放在哪里?
编辑:有人提醒我在投射指针时严格别名。即使我使用 memcpy
来避免转换,我仍然更容易在头部获得校验和。如果校验和在尾部,那么我必须将缓冲区转换为 const char*
,然后添加长度偏移量,然后将其转换为 const uint16_t*
以获取它。
有多种方法可以做到这一点。
除了 uint8_t data[];
必须 在 struct
.
因此,正如我上面所建议的,不要将校验和 放在 的 struct
中。您甚至可以将 data
排除在 struct
[或不] 之外。
正如我在顶部评论中所建议的那样,缓冲区 可能如下所示:
struct|data|csum
并且,如果数据包有效,校验和 所有,总和可能为零。
这是我见过的一种使用方式(例如 SDLC 等)的示例代码。它很粗糙,只检查一些错误。它允许数据负载位于不同的缓冲区中。
typedef struct {
uint32_t magic_number;
uint16_t frame_type;
uint16_t length;
uint8_t data[];
} blah_blah;
#define CSUMSEED 0x1234
#define MAGIC 0xDEADBEEF
uint16_t
calcsum(uint16_t csum,const void *ptr,uint16_t length)
{
uint8_t *data = ptr;
for (; length != 0; --length, ++data)
csum += *data;
return csum;
}
void
sendone(int socket,uint16_t type,const void *data,uint16_t length)
{
blah_blah hdr;
uint16_t csum = CSUMSEED;
hdr.magic_number = MAGIC;
hdr.frame_type = type;
hdr.length = length;
csum = calcsum(csum,&hdr,sizeof(hdr));
csum = calcsum(csum,data,length);
// convert csum to "inverse" ???
csum = ~csum;
// could use writev here instead ...
write(socket,&hdr,sizeof(hdr));
write(socket,data,length);
write(socket,&csum,sizeof(csum));
}
void
recvone(int socket,blah_blah *hdr,void *data)
// data -- must be large enough to hold _maximum_ payload
{
uint16_t csum = CSUMSEED;
ssize_t length;
// get the header struct
length = read(socket,hdr,sizeof(*hdr));
if (length != sizeof(*hdr))
error();
// magic number must match
if (hdr.magic != MAGIC)
error()
// get checksum for header
csum = calcsum(csum,hdr,sizeof(*hdr));
// get the data
length = read(socket,data,hdr->length + sizeof(uint16_t));
if (length != (hdr->length + sizeof(uint16_t))
error();
// get remaining checksum (including the trailing/appended CRC)
csum = calcsum(data,data,length);
if (csum != 0)
error();
}
如果您希望[如 struct
定义中所示]稍加修改,您可以将其调整为将数据嵌入到结构的末尾。
这里有一个附加校验和的小修改。接收方将从 read
获得的校验和与计算的校验和进行比较:
typedef struct {
uint32_t magic_number;
uint16_t frame_type;
uint16_t length;
uint8_t data[];
} blah_blah;
#define CSUMSEED 0x1234
#define MAGIC 0xDEADBEEF
uint16_t
calcsum(uint16_t csum,const void *ptr,uint16_t length)
{
uint8_t *data = ptr;
for (; length != 0; --length, ++data)
csum += *data;
return csum;
}
void
sendone(int socket,uint16_t type,const void *data,uint16_t length)
{
blah_blah hdr;
uint16_t csum = CSUMSEED;
hdr.magic_number = MAGIC;
hdr.frame_type = type;
hdr.length = length;
csum = calcsum(csum,&hdr,sizeof(hdr));
csum = calcsum(csum,data,length);
// could use writev here instead ...
write(socket,&hdr,sizeof(hdr));
write(socket,data,length);
write(socket,&csum,sizeof(csum));
}
void
recvone(int socket,blah_blah *hdr,void *data)
// data -- must be large enough to hold _maximum_ payload
{
uint16_t csum = CSUMSEED;
ssize_t length;
// get the header struct
length = read(socket,hdr,sizeof(*hdr));
if (length != sizeof(*hdr))
error();
// magic number must match
if (hdr.magic != MAGIC)
error()
// get checksum for header
csum = calcsum(csum,hdr,sizeof(*hdr));
// get the data
length = read(socket,data,hdr->length);
if (length != hdr->length)
error();
// get data checksum
csum = calcsum(data,data,length);
// get message checksum
uint16_t csum2;
length = read(socket,&csum2,sizeof(csum2));
if (length != sizeof(csum2))
error();
// validate the checksum
if (csum2 != csum)
error();
}
对于嵌入式设备,这取决于硬件。如果您通过中断一次一个字节地执行它,那么最后可能会更好。 (你边计算边计算 reading/writing RX/TX 注册然后添加它,或者检查它,这取决于你是在读还是写。)
如果它的 DMA 驱动和两个独立的块,或者其中只有一个是 DMA 驱动的,那么它可能无关紧要。在所有数据到达您之后,您将最终计算 checksum/crc。
然而,对于 FPGA,他们几乎肯定更容易在最后一个字节消失后 'tack on' 最后的校验和,而不是预先计算数据包上的校验和,然后将其插入在中间,然后发送数据。