开发者

Deserialization of structure data sent by Big Endian system in Little Endian system

I have a C program that receives data from a mainframe in a UDP packet over sockets. The host of the C program is changing from Unix (big endian) to Linux (little endian) and the program no longer works. I do not currently have the option of changing the mainframe client program.

The program performs a recvfrom and receives data into a char array. Previously we were able to simply cast this buffer to a structure matching what was passed from MF and everything worked perfect. Now, due to the different byte alignments the mapping to the structure fails. Here is the structure and some code.

struct CCClntPkt
{
    unsigned short packet_type;
    unsigned short reply_socket;
    unsigned long  msg_ID;
    unsigned short msg_length;
    unsigned char  client_string[250];
};

The code previously used to cast the received data buffer to this structure looks something like:

char BCpacket_in[MAX_PACKET];
struct CCClntPkt *pClntPkt;

<snip>

rcv_cnt = recvfrom(BCServerSocket, BCpacket_in,
                sizeof(BCpacket_in),0x0,(struct sockaddr *)&from,
                &fromlen);

if (rcv_cnt > 0)
{
    pClntPkt = (struct CCClntPkt *) &BCpacket_in;
}

I was able to get the correct values for packet_type and reply_socket by using ntohs but the character field client_string is mangled. I also tried putting a pragma pack(1) before and pragma pack(0) after the structure but there still seems to be an alignment problem.

I also tried bit shifting values from BCpacket_in and was able to get the correct values for packet_type and reply_socket but cannot figure out how to pull out the ulong msg_ID. Code for that was:

packet_type = BCpacket_in[0] << 8;
packet_type |= BCpacket_in[1];

reply_to_socket = BCpacket_in[2] << 8;
reply_to_socket |= BCpacket_in[3];

/*
msg_ID = BCpacket_in[4] << 24;
msg_ID |= BCpacket_in[5] << 16;
msg_ID |= BCpacket_in[6] << 8;
msg_ID |= BCpacket_in[7];
*/

I am pretty stumped at this point so any help is appreciated. I am not the original author of this program and my C knowledge is pretty limited. I don't mind doing the work though so I would appreciat开发者_StackOverflow中文版e being provided any relevant references as well. Thanks!


You'll have to parse the received packet (BCpacket_in) into a struct CCClntPkt packet manually, that's the only portable way of doing this. Endianness translations are properly handled with the ntohl (network-to-host long) family of functions; see the manpages byteorder(3) and endian(3).

These functions assume that all packets are sent over the wire as big-endian, because that's the internet standard.


It's possible that the sizes of the various types differ from your big endian host to your new little endian host.

If you compile this program on your both of your hosts, it will show you the sizes and layout of the struct:

#include <stddef.h>
#include <stdio.h>

struct CCClntPkt
{
    unsigned short packet_type;
    unsigned short reply_socket;
    unsigned long  msg_ID;
    unsigned short msg_length;
    unsigned char  client_string[250];
};

int main()
{
    printf("sizeof(unsigned short) = %u\n", (unsigned)sizeof(unsigned short));
    printf("sizeof(unsigned long) = %u\n", (unsigned)sizeof(unsigned long));

    printf("offsetof(struct CCClntPkt, reply_socket) = %u\n", (unsigned)offsetof(struct CCClntPkt, reply_socket));
    printf("offsetof(struct CCClntPkt, msg_ID) = %u\n", (unsigned)offsetof(struct CCClntPkt, msg_ID));
    printf("offsetof(struct CCClntPkt, msg_length) = %u\n", (unsigned)offsetof(struct CCClntPkt, msg_length));
    printf("offsetof(struct CCClntPkt, client_string) = %u\n", (unsigned)offsetof(struct CCClntPkt, client_string));

    return 0;
}

In particular, it's quite possible that long is longer on your new host than your old one. This is likely to be a good place to use the C99 exact-width types from <stdint.h> - if, on your original host, short is a 16 bit type and long is a 32 bit type, replace those with uint16_t and uint32_t respectively.

You can then use ntohs() and ntohl() to perform the endianness corrections.


msg_ID = BCpacket_in[4] << 24;
msg_ID |= BCpacket_in[5] << 16;
msg_ID |= BCpacket_in[6] << 8;
msg_ID |= BCpacket_in[7];

This seems correct to me.

Try to use unsigned char for your buffer to protect yourself from signedness problem.

BTW, is msg_id in big endian, and are you sure of the offset: as you said "packing" didn't work on client side, so one can conclude that the structure is sent to the wire using the packing rules of the mainframe.


This is what I usually do for packed structures sent/received to/from the network:

#define PACKED __attribute__((__packed__))
struct PACKED message { ... };

This is GCC-specific, see here. Then you have to figure out what size the long is here. It's different on 32- and 64-bit platforms. You might want to look into using stdint.h types instead. Also look info __builtin_bswap32() and __builtin_bswap64() GCC intrinsics.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜