1

I'm designing custom network protocol and I need to send uint64_t variable (representing file's length in bytes) through socket in portable and POSIX-compliant manner.

Unfortunately manual says that integer types with width 64 are not guaranteed to exist:

If an implementation provides integer types with width 64 that meet these requirements, then the following types are required: int64_t uint64_t

What's more there is no POSIX-compliant equivalent of htonl, htons, ntohl, ntohs (note that bswap_64 is not POSIX-compliant).

What is the best practice to send 64-bit variable through socket?

Community
  • 1
  • 1
patryk.beza
  • 4,876
  • 5
  • 37
  • 56
  • 1
    Specify the byte order, and send it byte-wise. The receiver has to follow the documented byte-order if the receiver want to use the value. And if the receiver is on a system without built-in support for 64-bit integers, there are libraries to use which can handle it. In short: Be well-documented in what you do, and let the other end worry about compatibility with you. – Some programmer dude Nov 10 '16 at 11:20
  • I think that using library for systems that doesn't support 64-bit integers is overkill. I'm writing protocol implementation in C which is quite [low-level](https://en.wikipedia.org/wiki/Low-level_programming_language) and I want to cleverly handle this situation using [bitwise operations](https://en.wikipedia.org/wiki/Bitwise_operation) and (possibly) macros. – patryk.beza Nov 10 '16 at 11:28
  • 1
    The question is not really about sockets but about serializing and unserializing 64 bit integers to binary in a portable way. It does not matter if this is done to send it over a socket, store it in a file or whatever. – Steffen Ullrich Nov 10 '16 at 11:38
  • 2
    Possible duplicate of [Portable C binary serialization primitives](http://stackoverflow.com/questions/11500425/portable-c-binary-serialization-primitives). Also [Is there any “standard” htonl-like function for 64 bits integers in C++?](http://stackoverflow.com/questions/3022552/is-there-any-standard-htonl-like-function-for-64-bits-integers-in-c) – Steffen Ullrich Nov 10 '16 at 11:40
  • @SteffenUllrich I linked your [second link](http://stackoverflow.com/questions/3022552/is-there-any-standard-htonl-like-function-for-64-bits-integers-in-c) in my original question. The [first link](http://stackoverflow.com/questions/11500425/portable-c-binary-serialization-primitives) doesn't answer my question. The question has socket context because I hoped that it is easier for you to answer what is best practice for handling this situation in socket-related programming. BTW: [related question](http://stackoverflow.com/questions/9073667/where-to-find-the-complete-definition-of-off-t-type). – patryk.beza Nov 10 '16 at 13:43
  • 2
    Also see http://stackoverflow.com/questions/809902/64-bit-ntohl-in-c – chux - Reinstate Monica Nov 10 '16 at 16:49

3 Answers3

3

You can just apply htonl() twice, of course:

const uint64_t x = ...
const uint32_t upper_be = htonl(x >> 32);
const uint32_t lower_be = htonl((uint32_t) x);

This will give you two 32-bit variables containing big-endian versions of the upper and lower 32-bit halves of the 64-bit variable x.

If you are strict POSIX, you can't use uint64_t since it's not guaranteed to exist. Then you can do something like:

typedef struct {
 uint32_t upper;
 uint32_t lower;
} my_uint64;

And just htonl() those directly, of course.

unwind
  • 391,730
  • 64
  • 469
  • 606
1

My personal favorite is a macro... mine looks similar to this and checks for local byte ordering before deciding how to handle the byte ordering:

// clang-format off
#if !defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__)
#   if defined(__has_include)
#     if __has_include(<endian.h>)
#      include <endian.h>
#     elif __has_include(<sys/endian.h>)
#      include <sys/endian.h>
#     endif
#   endif
#   if !defined(__LITTLE_ENDIAN__) &&                                             \
                (defined(__BIG_ENDIAN__) || __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
#      define __BIG_ENDIAN__
#      define bswap64(i)   (i) // do nothing
#   else
#      define __LITTLE_ENDIAN__
#      define bswap64(i) ((((i)&0xFFULL) << 56) | (((i)&0xFF00ULL) << 40) |     \
          (((i)&0xFF0000ULL) << 24) | (((i)&0xFF000000ULL) << 8) |              \
          (((i)&0xFF00000000ULL) >> 8) | (((i)&0xFF0000000000ULL) >> 24) |      \
          (((i)&0xFF000000000000ULL) >> 40) |                                   \
          (((i)&0xFF00000000000000ULL) >> 56)) 
#   endif
#endif
Myst
  • 18,516
  • 2
  • 45
  • 67
1

Assuming a POSIX platform with C99 or greater, {u,}int64_t are not required to exist but {u,}int_{least,fast}64_t are.

Additionally, POSIX requires {u,}int{8,16,32}_t.

So what you can do is:

#include <stdint.h>

//host-to-network (native endian to big endian)
void hton64(unsigned char *B, uint_least64_t X)
{

    B[0]=X>>56&0xFF;
    B[1]=X>>48&0xFF;
    B[2]=X>>40&0xFF;
    B[3]=X>>32&0xFF;
    B[4]=X>>24&0xFF;
    B[5]=X>>16&0xFF;
    B[6]=X>>8&0xFF;
    B[7]=X>>0&0xFF;
}

//network-to-host (big endian to native endian)
uint_least64_t ntoh64(unsigned char const *B)
{
    return (uint_least64_t)B[0]<<56|
           (uint_least64_t)B[1]<<48|
           (uint_least64_t)B[2]<<40|
           (uint_least64_t)B[3]<<32|
           (uint_least64_t)B[4]<<24|
           (uint_least64_t)B[5]<<16|
           (uint_least64_t)B[6]<<8|
           (uint_least64_t)B[7]<<0;
}

If the machine has uint64_t, then uint_least64_t will be (due to requirements imposed by the C standard) identical to uint64_t.

If it doesn't, then uint_least64_t might not be 2's-complement or it might have more value bits (I have no idea if there are such architectures), but regardless of that, the above routines will send or receive exactly (if there's more) 64 lower-order bits of it (to or from a buffer).

(Anyway, this solutionshould be good as a generic backend, but if you want to be slightly more optimal, then you can try to first detect your endianness and do nothing if it's a big endian platform; if it's a little endian and sizeof(uint_least64_t)*CHAR_BIT==64, then if you can detect you have byteswap.h with bswap_64, then you should use that as it's likely to compile down to a single instruction. If all else fails, I'd use something like the above.)

Petr Skocik
  • 58,047
  • 6
  • 95
  • 142