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.
nw_length--;
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;
size_t decode_length;
decode_length = (input_length > sizeof(unsigned int))
? sizeof(unsigned int)
: input_length;
for (size_t i = 0; i < decode_length; i++)
{
result <<= 8;
result |= (unsigned int) buffer[i];
}
return result;
}
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 network_to_uint
to 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 nw_length
to 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.