如何从C中读取数据包中的数据并将其转换为结构?我的意思是,有一个像
这样的结构|=======================================================================
|0123456701234567012345670123456701234567012345670123456701234567.......
| type | length | MSG HDR | data
进入像
这样的结构struct msg {
char type;
size_t length;
int hdr;
struct data * data;
};
以下代码是否正常?
bool parse_packet(char * packet, size_t packet_len, struct msg * result) {
if(packet_len < 5) return false;
result->type = *packet++;
result->length = ntohl(*(int*)packet);
packet+=4;
if(result->length + 4 + 5 > packet_len)
return false;
if(result->length < 2)
return false;
result->hdr = ntohs(*(short*)packet);
packet+=2;
return parse_data(result, packet);
}
答案 0 :(得分:1)
检查packet
和result
是否为空,通常是一种好习惯。
当标头为7个字节时,为什么要检查packet_len < 5
?为什么不确保数据包至少为7个字节并将其结束?或hdr
某些type
不存在if(result->length + 4 + 5 > packet_len)
result->hdr = ntohs(*(short*)packet);
packet+=2;
?
我不确定你用
尝试实现的目标packet_len
如果声明的消息长度加上9大于接收的消息长度,则从消息中读取另外两个字节。然后,无论数据的长度如何,您都要向指针添加两个并尝试解析其中的某些内容。如果result->length
为5且{{1}}为4294967295,该怎么办?您将在Heartbleed中读取缓冲区的末尾。您需要始终验证您的读取是否在边界内,并且永远不要相信数据包中声明的大小。
答案 1 :(得分:1)
你有一个完全标准的情况。这里没什么深刻或令人惊讶的。
以有线格式的规范开头。您可以使用伪代码或实际C类型,但暗示数据在线路上打包成字节:
struct Message // wire format, pseudo code
{
uint8_t type;
uint32_t length; // big-endian on the wire
uint8_t header[2];
uint8_t data[length];
};
现在开始解析:
// parses a Message from (buf, size)
// precondition: "buf" points to "size" bytes of data; "msg" points to Message
// returns true on success
// msg->data is malloc()ed and contains the data on success
bool parse_message(unsigned char * buf, std::size_t size, Message * msg)
{
if (size < 7) { return false; }
// parse length
uint32_t n;
memcpy(&n, buf + 1, 4);
n = ntohl(n); // convert big-endian (wire) to native
if (n > SIZE_MAX - 7)
{
// this is an implementation limit!
return false;
}
if (size != 7 + n) { return false; }
// copy data
unsigned char * p = malloc(n);
if (!p) { return false; }
memcpy(p, buf + 7, n);
// populate result
msg->type = buf[0];
msg->length = n;
msg->header[0] = buf[5];
msg->header[1] = buf[6];
msg->data = p;
return true;
}
解析长度的另一种方法是直接:
uint32_t n = (buf[1] << 24) + (buf[2] << 16) + (buf[1] << 8) + (buf[0]);
此代码假定buf
包含完全一条消息。如果您正在从流中接收消息,则需要修改代码(即if (size != 7 + n)
)以检查是否至少根据需要提供尽可能多的数据,并返回消费数据量也是如此,因此呼叫者可以相应地提前他们的流位置。 (在这种情况下,调用者可以计算被解析为msg->length + 7
的数据量,但依赖于该数据的数据量不可扩展。)
注意:正如@user指出的那样,如果您的size_t
不超过uint32_t
,那么此实现将错误地拒绝非常大的邮件。具体而言,7 + n > n
不被拒绝的消息。我对此(不太可能)的情况进行了动态检查。