diff --git a/node/Tests.cpp b/node/Tests.cpp index 3e3186255..dbc57e5e0 100644 --- a/node/Tests.cpp +++ b/node/Tests.cpp @@ -313,21 +313,85 @@ extern "C" const char *ZTT_general() } { - ZT_T_PRINTF("[general] Testing hton/ntoh byte order converters... "); - uint64_t a = Utils::hton((uint64_t)1); - uint32_t b = Utils::hton((uint32_t)1); - uint16_t c = Utils::hton((uint16_t)1); + ZT_T_PRINTF("[general] Testing byte order loading, storing, and conversion... "); + uint64_t a = Utils::hton((uint64_t)0x0807060504030201ULL); + uint32_t b = Utils::hton((uint32_t)0x04030201); + uint16_t c = Utils::hton((uint16_t)0x0201); + uint8_t t[8]; if ( (reinterpret_cast(&a)[7] != 1)|| (reinterpret_cast(&b)[3] != 1)|| (reinterpret_cast(&c)[1] != 1)|| - (Utils::ntoh(a) != 1)|| - (Utils::ntoh(b) != 1)|| - (Utils::ntoh(c) != 1) + (Utils::ntoh(a) != 0x0807060504030201ULL)|| + (Utils::ntoh(b) != 0x04030201)|| + (Utils::ntoh(c) != 0x0201) ) { - ZT_T_PRINTF("FAILED" ZT_EOL_S); - return "Utils::hton/ntoh not working properly"; + ZT_T_PRINTF("FAILED (hton/ntoh)" ZT_EOL_S); + return "Utils::hton() or ntoh() broken"; } +#if __BYTE_ORDER == __LITTLE_ENDIAN + if (Utils::loadAsIsEndian(&a) != 0x0102030405060708ULL) { + ZT_T_PRINTF("FAILED (loadAsIsEndian)" ZT_EOL_S); + return "Utils::loadAsIsEndian() broken"; + } + if (Utils::loadAsIsEndian(&b) != 0x01020304) { + ZT_T_PRINTF("FAILED (loadAsIsEndian)" ZT_EOL_S); + return "Utils::loadAsIsEndian() broken"; + } + if (Utils::loadAsIsEndian(&c) != 0x0102) { + ZT_T_PRINTF("FAILED (loadAsIsEndian)" ZT_EOL_S); + return "Utils::loadAsIsEndian() broken"; + } + memset(t,0,sizeof(t)); + Utils::storeAsIsEndian(t,0x0807060504030201ULL); + if (t[0] != 1) { + ZT_T_PRINTF("FAILED (storeAsIsEndian)" ZT_EOL_S); + return "Utils::storeAsIsEndian() broken"; + } + memset(t,0,sizeof(t)); + Utils::storeAsIsEndian(t,0x04030201); + if (t[0] != 1) { + ZT_T_PRINTF("FAILED (storeAsIsEndian)" ZT_EOL_S); + return "Utils::storeAsIsEndian() broken"; + } + memset(t,0,sizeof(t)); + Utils::storeAsIsEndian(t,0x0201); + if (t[0] != 1) { + ZT_T_PRINTF("FAILED (storeAsIsEndian)" ZT_EOL_S); + return "Utils::storeAsIsEndian() broken"; + } +#else + if (Utils::loadAsIsEndian(&a) != 0x0807060504030201ULL) { + ZT_T_PRINTF("FAILED (loadAsIsEndian)" ZT_EOL_S); + return "Utils::loadAsIsEndian() broken"; + } + if (Utils::loadAsIsEndian(&b) != 0x04030201) { + ZT_T_PRINTF("FAILED (loadAsIsEndian)" ZT_EOL_S); + return "Utils::loadAsIsEndian() broken"; + } + if (Utils::loadAsIsEndian(&c) != 0x0201) { + ZT_T_PRINTF("FAILED (loadAsIsEndian)" ZT_EOL_S); + return "Utils::loadAsIsEndian() broken"; + } + memset(t,0,sizeof(t)); + Utils::storeAsIsEndian(t,0x0807060504030201ULL); + if (t[0] != 8) { + ZT_T_PRINTF("FAILED (storeAsIsEndian)" ZT_EOL_S); + return "Utils::storeAsIsEndian() broken"; + } + memset(t,0,sizeof(t)); + Utils::storeAsIsEndian(t,0x04030201); + if (t[0] != 4) { + ZT_T_PRINTF("FAILED (storeAsIsEndian)" ZT_EOL_S); + return "Utils::storeAsIsEndian() broken"; + } + memset(t,0,sizeof(t)); + Utils::storeAsIsEndian(t,0x0201); + if (t[0] != 2) { + ZT_T_PRINTF("FAILED (storeAsIsEndian)" ZT_EOL_S); + return "Utils::storeAsIsEndian() broken"; + } +#endif ZT_T_PRINTF("OK" ZT_EOL_S); } ZT_T_PRINTF("[general] Utils::hash64() samples for 1, 2, int64_max, uint64_max: %.16llx %.16llx %.16llx %.16llx" ZT_EOL_S,Utils::hash64(1),Utils::hash64(2),Utils::hash64(0xffffffffffffffffULL),Utils::hash64(0x7fffffffffffffffULL)); @@ -597,7 +661,6 @@ extern "C" const char *ZTT_general() ZT_T_PRINTF("FAILED (v0 marshal)" ZT_EOL_S); return "Identity test failed: v0 marshal"; } - ZT_T_PRINTF("(marshal: %d bytes) ",ms); Identity id2; if (id2.unmarshal(idm,ms) <= 0) { ZT_T_PRINTF("FAILED (v0 unmarshal)" ZT_EOL_S); @@ -607,6 +670,16 @@ extern "C" const char *ZTT_general() ZT_T_PRINTF("FAILED (v0 unmarshal !=)" ZT_EOL_S); return "Identity test failed: v0 unmarshal !="; } + ms = id.marshal(idm,false); + if (ms <= 0) { + ZT_T_PRINTF("FAILED (v0 marshal)" ZT_EOL_S); + return "Identity test failed: v0 marshal"; + } + if (id2.unmarshal(idm,ms) <= 0) { + ZT_T_PRINTF("FAILED (v0 unmarshal)" ZT_EOL_S); + return "Identity test failed: v0 unmarshal"; + } + ZT_T_PRINTF("(marshalled size: %d bytes) ",ms); if (!id.fromString(IDENTITY_V0_KNOWN_BAD_0)) { ZT_T_PRINTF("FAILED (error parsing test identity #2)" ZT_EOL_S); @@ -638,7 +711,6 @@ extern "C" const char *ZTT_general() ZT_T_PRINTF("FAILED (v1 marshal)" ZT_EOL_S); return "Identity test failed: v1 marshal"; } - ZT_T_PRINTF("(marshal: %d bytes) ",ms); if (id2.unmarshal(idm,ms) <= 0) { ZT_T_PRINTF("FAILED (v1 unmarshal)" ZT_EOL_S); return "Identity test failed: v1 unmarshal"; @@ -647,6 +719,16 @@ extern "C" const char *ZTT_general() ZT_T_PRINTF("FAILED (v1 unmarshal !=)" ZT_EOL_S); return "Identity test failed: v1 unmarshal !="; } + ms = id.marshal(idm,false); + if (ms <= 0) { + ZT_T_PRINTF("FAILED (v1 marshal)" ZT_EOL_S); + return "Identity test failed: v1 marshal"; + } + if (id2.unmarshal(idm,ms) <= 0) { + ZT_T_PRINTF("FAILED (v1 unmarshal)" ZT_EOL_S); + return "Identity test failed: v1 unmarshal"; + } + ZT_T_PRINTF("(marshalled size: %d bytes) ",ms); if (!id.fromString(IDENTITY_V1_KNOWN_BAD_0)) { ZT_T_PRINTF("FAILED (error parsing test identity #2)" ZT_EOL_S); diff --git a/node/Utils.hpp b/node/Utils.hpp index b40bd42f8..70585201b 100644 --- a/node/Utils.hpp +++ b/node/Utils.hpp @@ -361,17 +361,38 @@ static ZT_ALWAYS_INLINE uint16_t swapBytes(const uint16_t n) noexcept #endif } -// ZZvvvzvzvvzzz... moooooo... http://www.catb.org/~esr/jargon/html/Y/yak-shaving.html -#if __BYTE_ORDER == __LITTLE_ENDIAN +// These are helper adapters to load and swap integer types special cased by size +// to work with all typedef'd variants, signed/unsigned, etc. template -class _hton_impl { public: static ZT_ALWAYS_INLINE I hton(const I n) { return n; } }; +class _swap_bytes_bysize; template -class _hton_impl { public: static ZT_ALWAYS_INLINE I hton(const I n) { return (I)swapBytes((uint16_t)n); } }; +class _swap_bytes_bysize { public: static ZT_ALWAYS_INLINE I s(const I n) noexcept { return n; } }; template -class _hton_impl { public: static ZT_ALWAYS_INLINE I hton(const I n) { return (I)swapBytes((uint32_t)n); } }; +class _swap_bytes_bysize { public: static ZT_ALWAYS_INLINE I s(const I n) noexcept { return (I)swapBytes((uint16_t)n); } }; template -class _hton_impl { public: static ZT_ALWAYS_INLINE I hton(const I n) { return (I)swapBytes((uint64_t)n); } }; -#endif +class _swap_bytes_bysize { public: static ZT_ALWAYS_INLINE I s(const I n) noexcept { return (I)swapBytes((uint32_t)n); } }; +template +class _swap_bytes_bysize { public: static ZT_ALWAYS_INLINE I s(const I n) noexcept { return (I)swapBytes((uint64_t)n); } }; +template +class _load_be_bysize; +template +class _load_be_bysize { public: static ZT_ALWAYS_INLINE I l(const uint8_t *const p) noexcept { return p[0]; }}; +template +class _load_be_bysize { public: static ZT_ALWAYS_INLINE I l(const uint8_t *const p) noexcept { return (I)(((unsigned int)p[0] << 8U) | (unsigned int)p[1]); }}; +template +class _load_be_bysize { public: static ZT_ALWAYS_INLINE I l(const uint8_t *const p) noexcept { return (I)(((uint32_t)p[0] << 24U) | ((uint32_t)p[1] << 16U) | ((uint32_t)p[2] << 8U) | (uint32_t)p[3]); }}; +template +class _load_be_bysize { public: static ZT_ALWAYS_INLINE I l(const uint8_t *const p) noexcept { return (I)(((uint64_t)p[0] << 56U) | ((uint64_t)p[1] << 48U) | ((uint64_t)p[2] << 40U) | ((uint64_t)p[3] << 32U) | ((uint64_t)p[4] << 24U) | ((uint64_t)p[5] << 16U) | ((uint64_t)p[6] << 8U) | (uint64_t)p[7]); }}; +template +class _load_le_bysize; +template +class _load_le_bysize { public: static ZT_ALWAYS_INLINE I l(const uint8_t *const p) noexcept { return p[0]; }}; +template +class _load_le_bysize { public: static ZT_ALWAYS_INLINE I l(const uint8_t *const p) noexcept { return (I)((unsigned int)p[0] | ((unsigned int)p[1] << 8U)); }}; +template +class _load_le_bysize { public: static ZT_ALWAYS_INLINE I l(const uint8_t *const p) noexcept { return (I)((uint32_t)p[0] | ((uint32_t)p[1] << 8U) | ((uint32_t)p[2] << 16U) | ((uint32_t)p[3] << 24U)); }}; +template +class _load_le_bysize { public: static ZT_ALWAYS_INLINE I l(const uint8_t *const p) noexcept { return (I)((uint64_t)p[0] | ((uint64_t)p[1] << 8U) | ((uint64_t)p[2] << 16U) | ((uint64_t)p[3] << 24U) | ((uint64_t)p[4] << 32U) | ((uint64_t)p[5] << 40U) | ((uint64_t)p[6] << 48U) | ((uint64_t)p[7]) << 56U); }}; /** * Convert any signed or unsigned integer type to big-endian ("network") byte order @@ -384,7 +405,7 @@ template static ZT_ALWAYS_INLINE I hton(const I n) noexcept { #if __BYTE_ORDER == __LITTLE_ENDIAN - return _hton_impl::hton(n); + return _swap_bytes_bysize::s(n); #else return n; #endif @@ -401,168 +422,12 @@ template static ZT_ALWAYS_INLINE I ntoh(const I n) noexcept { #if __BYTE_ORDER == __LITTLE_ENDIAN - return _hton_impl::hton(n); + return _swap_bytes_bysize::s(n); #else return n; #endif } -/** - * Decode a big-endian value from a byte stream - * - * @tparam I Type to decode (should be unsigned e.g. uint32_t or uint64_t) - * @param p Byte stream, must be at least sizeof(I) in size - * @return Decoded integer - */ -template -static ZT_ALWAYS_INLINE I loadBigEndian(const void *const p) noexcept -{ -#ifdef ZT_NO_UNALIGNED_ACCESS - if (sizeof(I) == 8) { - return (I)( - ((uint64_t)reinterpret_cast(p)[0] << 56U) | - ((uint64_t)reinterpret_cast(p)[1] << 48U) | - ((uint64_t)reinterpret_cast(p)[2] << 40U) | - ((uint64_t)reinterpret_cast(p)[3] << 32U) | - ((uint64_t)reinterpret_cast(p)[4] << 24U) | - ((uint64_t)reinterpret_cast(p)[5] << 16U) | - ((uint64_t)reinterpret_cast(p)[6] << 8U) | - (uint64_t)reinterpret_cast(p)[7] - ); - } else if (sizeof(I) == 4) { - return (I)( - ((uint32_t)reinterpret_cast(p)[0] << 24U) | - ((uint32_t)reinterpret_cast(p)[1] << 16U) | - ((uint32_t)reinterpret_cast(p)[2] << 8U) | - (uint32_t)reinterpret_cast(p)[3] - ); - } else if (sizeof(I) == 2) { - return (I)( - ((unsigned int)reinterpret_cast(p)[0] << 8U) | - (unsigned int)reinterpret_cast(p)[1] - ); - } else { - return (I)reinterpret_cast(p)[0]; - } -#else - return ntoh(*reinterpret_cast(p)); -#endif -} - -/** - * Save an integer in big-endian format - * - * @tparam I Integer type to store (usually inferred) - * @param p Byte stream to write (must be at least sizeof(I)) - * #param i Integer to write - */ -template -static ZT_ALWAYS_INLINE void storeBigEndian(void *const p,I i) noexcept -{ -#ifdef ZT_NO_UNALIGNED_ACCESS - if (sizeof(I) == 8) { - reinterpret_cast(p)[0] = (uint8_t)(reinterpret_cast(i) >> 56U); - reinterpret_cast(p)[1] = (uint8_t)(reinterpret_cast(i) >> 48U); - reinterpret_cast(p)[2] = (uint8_t)(reinterpret_cast(i) >> 40U); - reinterpret_cast(p)[3] = (uint8_t)(reinterpret_cast(i) >> 32U); - reinterpret_cast(p)[4] = (uint8_t)(reinterpret_cast(i) >> 24U); - reinterpret_cast(p)[5] = (uint8_t)(reinterpret_cast(i) >> 16U); - reinterpret_cast(p)[6] = (uint8_t)(reinterpret_cast(i) >> 8U); - reinterpret_cast(p)[7] = (uint8_t)reinterpret_cast(i); - } else if (sizeof(I) == 4) { - reinterpret_cast(p)[0] = (uint8_t)(reinterpret_cast(i) >> 24U); - reinterpret_cast(p)[1] = (uint8_t)(reinterpret_cast(i) >> 16U); - reinterpret_cast(p)[2] = (uint8_t)(reinterpret_cast(i) >> 8U); - reinterpret_cast(p)[3] = (uint8_t)reinterpret_cast(i); - } else if (sizeof(I) == 2) { - reinterpret_cast(p)[0] = (uint8_t)(reinterpret_cast(i) >> 8U); - reinterpret_cast(p)[1] = (uint8_t)reinterpret_cast(i); - } else { - reinterpret_cast(p)[0] = (uint8_t)i; - } -#else - *reinterpret_cast(p) = hton(i); -#endif -} - -/** - * Decode a little-endian value from a byte stream - * - * @tparam I Type to decode - * @param p Byte stream, must be at least sizeof(I) in size - * @return Decoded integer - */ -template -static ZT_ALWAYS_INLINE I loadLittleEndian(const void *const p) noexcept -{ -#if __BYTE_ORDER == __BIG_ENDIAN || defined(ZT_NO_UNALIGNED_ACCESS) - if (sizeof(I) == 8) { - return (I)( - (uint64_t)reinterpret_cast(p)[0] | - ((uint64_t)reinterpret_cast(p)[1] << 8U) | - ((uint64_t)reinterpret_cast(p)[2] << 16U) | - ((uint64_t)reinterpret_cast(p)[3] << 24U) | - ((uint64_t)reinterpret_cast(p)[4] << 32U) | - ((uint64_t)reinterpret_cast(p)[5] << 40U) | - ((uint64_t)reinterpret_cast(p)[6] << 48U) | - ((uint64_t)reinterpret_cast(p)[7] << 56U) - ); - } else if (sizeof(I) == 4) { - return (I)( - (uint32_t)reinterpret_cast(p)[0] | - ((uint32_t)reinterpret_cast(p)[1] << 8U) | - ((uint32_t)reinterpret_cast(p)[2] << 16U) | - ((uint32_t)reinterpret_cast(p)[3] << 24U) - ); - } else if (sizeof(I) == 2) { - return (I)( - (unsigned int)reinterpret_cast(p)[0] | - ((unsigned int)reinterpret_cast(p)[1] << 8U) - ); - } else { - return (I)reinterpret_cast(p)[0]; - } -#else - return *reinterpret_cast(p); -#endif -} - -/** - * Save an integer in little-endian format - * - * @tparam I Integer type to store (usually inferred) - * @param p Byte stream to write (must be at least sizeof(I)) - * #param i Integer to write - */ -template -static ZT_ALWAYS_INLINE void storeLittleEndian(void *const p,const I i) noexcept -{ -#if __BYTE_ORDER == __BIG_ENDIAN || defined(ZT_NO_UNALIGNED_ACCESS) - if (sizeof(I) == 8) { - reinterpret_cast(p)[0] = (uint8_t)reinterpret_cast(i); - reinterpret_cast(p)[1] = (uint8_t)(reinterpret_cast(i) >> 8U); - reinterpret_cast(p)[2] = (uint8_t)(reinterpret_cast(i) >> 16U); - reinterpret_cast(p)[3] = (uint8_t)(reinterpret_cast(i) >> 24U); - reinterpret_cast(p)[4] = (uint8_t)(reinterpret_cast(i) >> 32U); - reinterpret_cast(p)[5] = (uint8_t)(reinterpret_cast(i) >> 40U); - reinterpret_cast(p)[6] = (uint8_t)(reinterpret_cast(i) >> 48U); - reinterpret_cast(p)[7] = (uint8_t)(reinterpret_cast(i) >> 56U); - } else if (sizeof(I) == 4) { - reinterpret_cast(p)[0] = (uint8_t)reinterpret_cast(i); - reinterpret_cast(p)[1] = (uint8_t)(reinterpret_cast(i) >> 8U); - reinterpret_cast(p)[2] = (uint8_t)(reinterpret_cast(i) >> 16U); - reinterpret_cast(p)[3] = (uint8_t)(reinterpret_cast(i) >> 24U); - } else if (sizeof(I) == 2) { - reinterpret_cast(p)[0] = (uint8_t)reinterpret_cast(i); - reinterpret_cast(p)[1] = (uint8_t)(reinterpret_cast(i) >> 8U); - } else { - reinterpret_cast(p)[0] = (uint8_t)i; - } -#else - *reinterpret_cast(p) = i; -#endif -} - /** * Copy bits from memory into an integer type without modifying their order * @@ -574,10 +439,11 @@ template static ZT_ALWAYS_INLINE I loadAsIsEndian(const void *const p) noexcept { #ifdef ZT_NO_UNALIGNED_ACCESS - I x = (I)0; - for(unsigned int k=0;k(&x)[k] = reinterpret_cast(p)[k]; - return x; +#if __BYTE_ORDER == __LITTLE_ENDIAN + return _load_le_bysize::l(reinterpret_cast(p)); +#else + return _load_be_bysize::l(reinterpret_cast(p)); +#endif #else return *reinterpret_cast(p); #endif @@ -601,6 +467,74 @@ static ZT_ALWAYS_INLINE void storeAsIsEndian(void *const p,const I i) noexcept #endif } +/** + * Decode a big-endian value from a byte stream + * + * @tparam I Type to decode (should be unsigned e.g. uint32_t or uint64_t) + * @param p Byte stream, must be at least sizeof(I) in size + * @return Decoded integer + */ +template +static ZT_ALWAYS_INLINE I loadBigEndian(const void *const p) noexcept +{ +#ifdef ZT_NO_UNALIGNED_ACCESS + return _load_be_bysize::l(reinterpret_cast(p)); +#else + return ntoh(*reinterpret_cast(p)); +#endif +} + +/** + * Save an integer in big-endian format + * + * @tparam I Integer type to store (usually inferred) + * @param p Byte stream to write (must be at least sizeof(I)) + * #param i Integer to write + */ +template +static ZT_ALWAYS_INLINE void storeBigEndian(void *const p,I i) noexcept +{ +#ifdef ZT_NO_UNALIGNED_ACCESS + storeAsIsEndian(p,hton(i)); +#else + *reinterpret_cast(p) = hton(i); +#endif +} + +/** + * Decode a little-endian value from a byte stream + * + * @tparam I Type to decode + * @param p Byte stream, must be at least sizeof(I) in size + * @return Decoded integer + */ +template +static ZT_ALWAYS_INLINE I loadLittleEndian(const void *const p) noexcept +{ +#if __BYTE_ORDER == __BIG_ENDIAN || defined(ZT_NO_UNALIGNED_ACCESS) + return _load_le_bysize::l(reinterpret_cast(p)); +#else + return *reinterpret_cast(p); +#endif +} + +/** + * Save an integer in little-endian format + * + * @tparam I Integer type to store (usually inferred) + * @param p Byte stream to write (must be at least sizeof(I)) + * #param i Integer to write + */ +template +static ZT_ALWAYS_INLINE void storeLittleEndian(void *const p,const I i) noexcept +{ +#if __BYTE_ORDER == __BIG_ENDIAN || defined(ZT_NO_UNALIGNED_ACCESS) + storeAsIsEndian(p,_swap_bytes_bysize::s(i)); +#else + *reinterpret_cast(p) = i; +#endif +} + } // namespace Utils } // namespace ZeroTier