Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to set endianness when converting to or from hex strings

To convert an integer to a hex formatted string I am using ToString("X4") like so:

int target = 250;    
string hexString = target.ToString("X4");

To get an integer value from a hex formatted string I use the Parse method:

int answer = int.Parse(data, System.Globalization.NumberStyles.HexNumber);

However the machine that I'm exchanging data with puts the bytes in reverse order.

To keep with the sample data, If I want to send the value 250 I need a string of "FA00" (not 00FA which is what hexString is) Likewise if I get "FA00" I need to convert that to 250 not 64000.

How do I set the endianness of these two converstion methods?

like image 564
Ralph Shillington Avatar asked Jan 21 '23 15:01

Ralph Shillington


1 Answers

Marc's answer seems, by virtue of having been accepted, to have addressed the OP's original issue. However, it's not really clear to me from the question text why. That still seems to require swapping of bytes, not pairs of bytes as Marc's answer does. I'm not aware of any reasonably common scenario where swapping bits 16 at a time makes sense or is useful.

For the stated requirements, IMHO it would make more sense to write this:

int target = 250; // 0x00FA

// swap the bytes of target
target = ((target << 8) | (target >> 8)) & 0xFFFF;

// target now is 0xFA00
string hexString = target.ToString("X4");

Note that the above assumes we're actually dealing with 16-bit values, stored in a 32-bit int variable. It will handle any input in the 16-bit range (note the need to mask off the upper 16 bits, as they get set to non-zero values by the << operator).

If swapping 32-bit values, one would need something like this:

int target = 250; // 0x00FA

// swap the bytes of target
target = (int)((int)((target << 24) & 0xff000000) |
    ((target << 8) & 0xff0000) |
    ((target >> 8) & 0xff00) |
    ((target >> 24) & 0xff));

// target now is 0xFA000000
string hexString = target.ToString("X8");

Again, masking is required to isolate the bits we are moving to specific positions. Casting the << 24 result back to int before or-ing with the other three bytes is needed because 0xff000000 is a uint (UInt32) literal and causes the & expression to be extended to long (Int64). Otherwise, you'll get compiler warnings with each of the | operators.


In any case, as this comes up most often in networking scenarios, it is worth noting that .NET provides helper methods that can assist with this operation: HostToNetworkOrder() and NetworkToHostOrder(). In this context, "network order" is always big-endian, and "host order" is whatever byte order is used on the computer hosting the current process.

If you know that you are receiving data that's big-endian, and you want to be able to interpret in as correct values in your process, you can call NetworkToHostOrder(). Likewise, if you need to send data in a context where big-endian is expected, you can call HostToNetworkOrder().

These methods work only with the three basic integer types: Int16, Int32, and Int64 (in C#, short, int, and long, respectively). They also return the same type passed to them, so you have to be careful about sign extension. The original example in the question could be solved like this:

int target = 250; // 0x00FA

// swap the bytes of target
target = IPAddress.HostToNetworkOrder((short)target) & 0xFFFF;

// target now is 0xFA00
string hexString = target.ToString("X4");

Once again, masking is required because otherwise the short value returned by the method will be sign-extended to 32 bits. If bit 15 (i.e. 0x8000) is set in the result, then the final int value would otherwise have its highest 16 bits set as well. This could be addressed without masking simply by using more appropriate data types for the variables (e.g. short when the data is expected to be signed 16-bit values).

Finally, I will note that the HostToNetworkOrder() and NetworkToHostOrder() methods, since they are only ever swapping bytes, are equivalent to each other. They both swap bytes, when the machine architecture is little-endian . And indeed, the .NET implementation is simply for the NetworkToHostOrder() to call HostToNetworkOrder(). There are two methods mainly so that the .NET API matches the original BSD sockets API, which included functions like htons() and ntohs(), and that API in turn included functions for both directions of conversion mainly so that it was clear in code whether one was receiving data from the network or sending data to the network.


† And do nothing when the machine architecture is big-endian…they aren't useful as generalized byte-swapping functions. Rather, the expectation is that the network protocol will always be big-endian, and these functions are used to ensure the data bytes are swapped to match whatever the machine architecture is.

like image 70
Peter Duniho Avatar answered Jan 30 '23 13:01

Peter Duniho