The problem with your approach is that your are trying to simultaneously satisfy two contradictory requirements: produce a robust cross-platform implementation, and explicitly deal with different hosts.
A better approach is to avoid any knowledge of the host's endianness by using shifts and or's. It is, however, perfectly fine to rely on network byte order being big-endian as that is an invariant.
Consider the conversion to network order first. This is the simpler direction as it is always fixed but, in C at least, you will need to write routines to deal with the different sizes of int. For now, we will deal with
unsigned int only.
void uint_to_network(unsigned int value, unsigned char * buffer, size_t buffer_size)
size_t nw_length = sizeof(value);
if (buffer_size < nw_length) return; // Avoid potential overflow
// Note that this loops until all possible locations in the
// output stream are filled, automatically handling leading zeroes.
while(nw_length != 0)
// Pre-decrementing nw_length deals with the zero-based nature
// of C arrays and avoids us having to worry about the representation
// of -1 as an unsigned value.
buffer[nw_length] = value & 0xff;
value >>= 8;
Note that this code is written in terms of the network order, with no knowledge of the host's endianness. Thus, it is inherently cross-platform.
Converting from network to host follows a similar pattern.
unsigned int network_to_uint(unsigned char * buffer, size_t input_length)
unsigned int result = 0;
decode_length = (input_length > sizeof(unsigned int))
? sizeof(unsigned int)
for (size_t i = 0; i < decode_length; i++)
result <<= 8;
result |= (unsigned int) buffer[i];
Again, the code is written in terms of the known network order and makes no reliance on the host order.
Also note that, whenever dealing with network side data, I always use the
unsigned char format. This is because network data is basically a 'bit stream', and you should never try to deal with it as a different native data type. That way lies madness .. or at least very subtle bugs.
Now, in a production implementation where execution speed is all important, you would actually have two implementations of the routines. On a big-endian host they would be no-ops, or at worst a macro performing a cast. On a little-endian host, they would be heavily optimized based on the knowledge that conversion was always required. Then your build-script would link in the set of routines that made sense for the target: and you would have some intelligence in the build script if only to select the correct target-processor for the cross compiler.
But for a true cross-platform single-implementation solution, avoiding knowledge of the host's endianness is the only sure route to avoiding problems.
By the way, if you change the return type of
unsigned long long (or
unsigned long if your compiler is too old for
long long) it can deal with all of the unsigned integer types in the network data by passing in the correct value for
input_length. But you will still need a different routine to deal with signed data. And remember that, for an
unsigned int in the network data you need to specify the correct input length. A network
int may not be the same size as your host's.
For signed data, you have to be a little more careful. There are hosts out there which store data with a separate sign bit (i.e. they can represent both positive and negative zero) and C is designed to run on those as well.
And really, my
uint_to_network has the fault that it assumes
sizeof(unsigned int) on the host is the same as its representation in a network data stream. It really ought to take an
output_length argument instead of setting
sizeof(unsigned int). Then it can also be made to work for all unsigned data, because it only extracts the least significant bytes of the data.