EndianUtils.h (17555B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 /* Functions for reading and writing integers in various endiannesses. */ 8 9 /* 10 * The classes LittleEndian and BigEndian expose static methods for 11 * reading and writing 16-, 32-, and 64-bit signed and unsigned integers 12 * in their respective endianness. The addresses read from or written 13 * to may be misaligned (although misaligned accesses may incur 14 * architecture-specific performance costs). The naming scheme is: 15 * 16 * {Little,Big}Endian::{read,write}{Uint,Int}<bitsize> 17 * 18 * For instance, LittleEndian::readInt32 will read a 32-bit signed 19 * integer from memory in little endian format. Similarly, 20 * BigEndian::writeUint16 will write a 16-bit unsigned integer to memory 21 * in big-endian format. 22 * 23 * The class NativeEndian exposes methods for conversion of existing 24 * data to and from the native endianness. These methods are intended 25 * for cases where data needs to be transferred, serialized, etc. 26 * swap{To,From}{Little,Big}Endian byteswap a single value if necessary. 27 * Bulk conversion functions are also provided which optimize the 28 * no-conversion-needed case: 29 * 30 * - copyAndSwap{To,From}{Little,Big}Endian; 31 * - swap{To,From}{Little,Big}EndianInPlace. 32 * 33 * The *From* variants are intended to be used for reading data and the 34 * *To* variants for writing data. 35 * 36 * Methods on NativeEndian work with integer data of any type. 37 * Floating-point data is not supported. 38 * 39 * For clarity in networking code, "Network" may be used as a synonym 40 * for "Big" in any of the above methods or class names. 41 * 42 * As an example, reading a file format header whose fields are stored 43 * in big-endian format might look like: 44 * 45 * class ExampleHeader 46 * { 47 * private: 48 * uint32_t mMagic; 49 * uint32_t mLength; 50 * uint32_t mTotalRecords; 51 * uint64_t mChecksum; 52 * 53 * public: 54 * ExampleHeader(const void* data) 55 * { 56 * const uint8_t* ptr = static_cast<const uint8_t*>(data); 57 * mMagic = BigEndian::readUint32(ptr); ptr += sizeof(uint32_t); 58 * mLength = BigEndian::readUint32(ptr); ptr += sizeof(uint32_t); 59 * mTotalRecords = BigEndian::readUint32(ptr); ptr += sizeof(uint32_t); 60 * mChecksum = BigEndian::readUint64(ptr); 61 * } 62 * ... 63 * }; 64 */ 65 66 #ifndef mozilla_EndianUtils_h 67 #define mozilla_EndianUtils_h 68 69 #include "mozilla/Assertions.h" 70 #include "mozilla/DebugOnly.h" 71 72 #include <stdint.h> 73 #include <string.h> 74 75 #if defined(_MSC_VER) 76 # pragma intrinsic(_byteswap_ushort) 77 # pragma intrinsic(_byteswap_ulong) 78 # pragma intrinsic(_byteswap_uint64) 79 #endif 80 81 /* 82 * Our supported compilers provide architecture-independent macros for this. 83 * Yes, there are more than two values for __BYTE_ORDER__. 84 * 85 * FIXME: use std::endian once we move to C++20 86 */ 87 #if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && \ 88 defined(__ORDER_BIG_ENDIAN__) 89 # if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ 90 # define MOZ_LITTLE_ENDIAN() 1 91 # define MOZ_BIG_ENDIAN() 0 92 # elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ 93 # define MOZ_LITTLE_ENDIAN() 0 94 # define MOZ_BIG_ENDIAN() 1 95 # else 96 # error "Can't handle mixed-endian architectures" 97 # endif 98 #else 99 # error "Don't know how to determine endianness" 100 #endif 101 102 namespace mozilla { 103 104 /* FIXME: move to std::byteswap with C++23 105 */ 106 template <typename T> 107 T byteswap(T n) { 108 if constexpr (sizeof(T) == 2) { 109 return __builtin_bswap16(n); 110 } else if constexpr (sizeof(T) == 4) { 111 return __builtin_bswap32(n); 112 } else if constexpr (sizeof(T) == 8) { 113 return __builtin_bswap64(n); 114 } 115 } 116 117 namespace detail { 118 119 enum Endianness { Little, Big }; 120 121 #if MOZ_BIG_ENDIAN() 122 # define MOZ_NATIVE_ENDIANNESS detail::Big 123 #else 124 # define MOZ_NATIVE_ENDIANNESS detail::Little 125 #endif 126 127 class EndianUtils { 128 /** 129 * Assert that the memory regions [aDest, aDest+aCount) and 130 * [aSrc, aSrc+aCount] do not overlap. aCount is given in bytes. 131 */ 132 static void assertNoOverlap(const void* aDest, const void* aSrc, 133 size_t aCount) { 134 DebugOnly<const uint8_t*> byteDestPtr = static_cast<const uint8_t*>(aDest); 135 DebugOnly<const uint8_t*> byteSrcPtr = static_cast<const uint8_t*>(aSrc); 136 MOZ_ASSERT( 137 (byteDestPtr <= byteSrcPtr && byteDestPtr + aCount <= byteSrcPtr) || 138 (byteSrcPtr <= byteDestPtr && byteSrcPtr + aCount <= byteDestPtr)); 139 } 140 141 template <typename T> 142 static void assertAligned(T* aPtr) { 143 MOZ_ASSERT((uintptr_t(aPtr) % sizeof(T)) == 0, "Unaligned pointer!"); 144 } 145 146 protected: 147 /** 148 * Return |aValue| converted from SourceEndian encoding to DestEndian 149 * encoding. 150 */ 151 template <Endianness SourceEndian, Endianness DestEndian, typename T> 152 static inline T maybeSwap(T aValue) { 153 if constexpr (SourceEndian == DestEndian) { 154 return aValue; 155 } 156 return byteswap(aValue); 157 } 158 159 /** 160 * Convert |aCount| elements at |aPtr| from SourceEndian encoding to 161 * DestEndian encoding. 162 */ 163 template <Endianness SourceEndian, Endianness DestEndian, typename T> 164 static inline void maybeSwapInPlace(T* aPtr, size_t aCount) { 165 assertAligned(aPtr); 166 167 if constexpr (SourceEndian == DestEndian) { 168 return; 169 } 170 for (size_t i = 0; i < aCount; i++) { 171 aPtr[i] = byteswap(aPtr[i]); 172 } 173 } 174 175 /** 176 * Write |aCount| elements to the unaligned address |aDest| in DestEndian 177 * format, using elements found at |aSrc| in SourceEndian format. 178 */ 179 template <Endianness SourceEndian, Endianness DestEndian, typename T> 180 static void copyAndSwapTo(void* aDest, const T* aSrc, size_t aCount) { 181 assertNoOverlap(aDest, aSrc, aCount * sizeof(T)); 182 assertAligned(aSrc); 183 184 if constexpr (SourceEndian == DestEndian) { 185 memcpy(aDest, aSrc, aCount * sizeof(T)); 186 return; 187 } 188 189 uint8_t* byteDestPtr = static_cast<uint8_t*>(aDest); 190 for (size_t i = 0; i < aCount; ++i) { 191 const T Val = maybeSwap<SourceEndian, DestEndian>(aSrc[i]); 192 memcpy(byteDestPtr, static_cast<const void*>(&Val), sizeof(T)); 193 byteDestPtr += sizeof(T); 194 } 195 } 196 197 /** 198 * Write |aCount| elements to |aDest| in DestEndian format, using elements 199 * found at the unaligned address |aSrc| in SourceEndian format. 200 */ 201 template <Endianness SourceEndian, Endianness DestEndian, typename T> 202 static void copyAndSwapFrom(T* aDest, const void* aSrc, size_t aCount) { 203 assertNoOverlap(aDest, aSrc, aCount * sizeof(T)); 204 assertAligned(aDest); 205 206 if constexpr (SourceEndian == DestEndian) { 207 memcpy(aDest, aSrc, aCount * sizeof(T)); 208 return; 209 } 210 211 const uint8_t* byteSrcPtr = static_cast<const uint8_t*>(aSrc); 212 for (size_t i = 0; i < aCount; ++i) { 213 T Val; 214 memcpy(static_cast<void*>(&Val), byteSrcPtr, sizeof(T)); 215 aDest[i] = maybeSwap<SourceEndian, DestEndian>(Val); 216 byteSrcPtr += sizeof(T); 217 } 218 } 219 }; 220 221 template <Endianness ThisEndian> 222 class Endian : private EndianUtils { 223 protected: 224 /** Read a uint16_t in ThisEndian endianness from |aPtr| and return it. */ 225 [[nodiscard]] static uint16_t readUint16(const void* aPtr) { 226 return read<uint16_t>(aPtr); 227 } 228 229 /** Read a uint32_t in ThisEndian endianness from |aPtr| and return it. */ 230 [[nodiscard]] static uint32_t readUint32(const void* aPtr) { 231 return read<uint32_t>(aPtr); 232 } 233 234 /** Read a uint64_t in ThisEndian endianness from |aPtr| and return it. */ 235 [[nodiscard]] static uint64_t readUint64(const void* aPtr) { 236 return read<uint64_t>(aPtr); 237 } 238 239 /** Read a uintptr_t in ThisEndian endianness from |aPtr| and return it. */ 240 [[nodiscard]] static uintptr_t readUintptr(const void* aPtr) { 241 return read<uintptr_t>(aPtr); 242 } 243 244 /** Read an int16_t in ThisEndian endianness from |aPtr| and return it. */ 245 [[nodiscard]] static int16_t readInt16(const void* aPtr) { 246 return read<int16_t>(aPtr); 247 } 248 249 /** Read an int32_t in ThisEndian endianness from |aPtr| and return it. */ 250 [[nodiscard]] static int32_t readInt32(const void* aPtr) { 251 return read<uint32_t>(aPtr); 252 } 253 254 /** Read an int64_t in ThisEndian endianness from |aPtr| and return it. */ 255 [[nodiscard]] static int64_t readInt64(const void* aPtr) { 256 return read<int64_t>(aPtr); 257 } 258 259 /** Read an intptr_t in ThisEndian endianness from |aPtr| and return it. */ 260 [[nodiscard]] static intptr_t readIntptr(const void* aPtr) { 261 return read<intptr_t>(aPtr); 262 } 263 264 /** Write |aValue| to |aPtr| using ThisEndian endianness. */ 265 static void writeUint16(void* aPtr, uint16_t aValue) { write(aPtr, aValue); } 266 267 /** Write |aValue| to |aPtr| using ThisEndian endianness. */ 268 static void writeUint32(void* aPtr, uint32_t aValue) { write(aPtr, aValue); } 269 270 /** Write |aValue| to |aPtr| using ThisEndian endianness. */ 271 static void writeUint64(void* aPtr, uint64_t aValue) { write(aPtr, aValue); } 272 273 /** Write |aValue| to |aPtr| using ThisEndian endianness. */ 274 static void writeUintptr(void* aPtr, uintptr_t aValue) { 275 write(aPtr, aValue); 276 } 277 278 /** Write |aValue| to |aPtr| using ThisEndian endianness. */ 279 static void writeInt16(void* aPtr, int16_t aValue) { write(aPtr, aValue); } 280 281 /** Write |aValue| to |aPtr| using ThisEndian endianness. */ 282 static void writeInt32(void* aPtr, int32_t aValue) { write(aPtr, aValue); } 283 284 /** Write |aValue| to |aPtr| using ThisEndian endianness. */ 285 static void writeInt64(void* aPtr, int64_t aValue) { write(aPtr, aValue); } 286 287 /** Write |aValue| to |aPtr| using ThisEndian endianness. */ 288 static void writeIntptr(void* aPtr, intptr_t aValue) { write(aPtr, aValue); } 289 290 /* 291 * Converts a value of type T to little-endian format. 292 * 293 * This function is intended for cases where you have data in your 294 * native-endian format and you need it to appear in little-endian 295 * format for transmission. 296 */ 297 template <typename T> 298 [[nodiscard]] static T swapToLittleEndian(T aValue) { 299 return maybeSwap<ThisEndian, Little>(aValue); 300 } 301 302 /* 303 * Copies |aCount| values of type T starting at |aSrc| to |aDest|, converting 304 * them to little-endian format if ThisEndian is Big. |aSrc| as a typed 305 * pointer must be aligned; |aDest| need not be. 306 * 307 * As with memcpy, |aDest| and |aSrc| must not overlap. 308 */ 309 template <typename T> 310 static void copyAndSwapToLittleEndian(void* aDest, const T* aSrc, 311 size_t aCount) { 312 copyAndSwapTo<ThisEndian, Little>(aDest, aSrc, aCount); 313 } 314 315 /* 316 * Likewise, but converts values in place. 317 */ 318 template <typename T> 319 static void swapToLittleEndianInPlace(T* aPtr, size_t aCount) { 320 maybeSwapInPlace<ThisEndian, Little>(aPtr, aCount); 321 } 322 323 /* 324 * Converts a value of type T to big-endian format. 325 */ 326 template <typename T> 327 [[nodiscard]] static T swapToBigEndian(T aValue) { 328 return maybeSwap<ThisEndian, Big>(aValue); 329 } 330 331 /* 332 * Copies |aCount| values of type T starting at |aSrc| to |aDest|, converting 333 * them to big-endian format if ThisEndian is Little. |aSrc| as a typed 334 * pointer must be aligned; |aDest| need not be. 335 * 336 * As with memcpy, |aDest| and |aSrc| must not overlap. 337 */ 338 template <typename T> 339 static void copyAndSwapToBigEndian(void* aDest, const T* aSrc, 340 size_t aCount) { 341 copyAndSwapTo<ThisEndian, Big>(aDest, aSrc, aCount); 342 } 343 344 /* 345 * Likewise, but converts values in place. 346 */ 347 template <typename T> 348 static void swapToBigEndianInPlace(T* aPtr, size_t aCount) { 349 maybeSwapInPlace<ThisEndian, Big>(aPtr, aCount); 350 } 351 352 /* 353 * Synonyms for the big-endian functions, for better readability 354 * in network code. 355 */ 356 357 template <typename T> 358 [[nodiscard]] static T swapToNetworkOrder(T aValue) { 359 return swapToBigEndian(aValue); 360 } 361 362 template <typename T> 363 static void copyAndSwapToNetworkOrder(void* aDest, const T* aSrc, 364 size_t aCount) { 365 copyAndSwapToBigEndian(aDest, aSrc, aCount); 366 } 367 368 template <typename T> 369 static void swapToNetworkOrderInPlace(T* aPtr, size_t aCount) { 370 swapToBigEndianInPlace(aPtr, aCount); 371 } 372 373 /* 374 * Converts a value of type T from little-endian format. 375 */ 376 template <typename T> 377 [[nodiscard]] static T swapFromLittleEndian(T aValue) { 378 return maybeSwap<Little, ThisEndian>(aValue); 379 } 380 381 /* 382 * Copies |aCount| values of type T starting at |aSrc| to |aDest|, converting 383 * them to little-endian format if ThisEndian is Big. |aDest| as a typed 384 * pointer must be aligned; |aSrc| need not be. 385 * 386 * As with memcpy, |aDest| and |aSrc| must not overlap. 387 */ 388 template <typename T> 389 static void copyAndSwapFromLittleEndian(T* aDest, const void* aSrc, 390 size_t aCount) { 391 copyAndSwapFrom<Little, ThisEndian>(aDest, aSrc, aCount); 392 } 393 394 /* 395 * Likewise, but converts values in place. 396 */ 397 template <typename T> 398 static void swapFromLittleEndianInPlace(T* aPtr, size_t aCount) { 399 maybeSwapInPlace<Little, ThisEndian>(aPtr, aCount); 400 } 401 402 /* 403 * Converts a value of type T from big-endian format. 404 */ 405 template <typename T> 406 [[nodiscard]] static T swapFromBigEndian(T aValue) { 407 return maybeSwap<Big, ThisEndian>(aValue); 408 } 409 410 /* 411 * Copies |aCount| values of type T starting at |aSrc| to |aDest|, converting 412 * them to big-endian format if ThisEndian is Little. |aDest| as a typed 413 * pointer must be aligned; |aSrc| need not be. 414 * 415 * As with memcpy, |aDest| and |aSrc| must not overlap. 416 */ 417 template <typename T> 418 static void copyAndSwapFromBigEndian(T* aDest, const void* aSrc, 419 size_t aCount) { 420 copyAndSwapFrom<Big, ThisEndian>(aDest, aSrc, aCount); 421 } 422 423 /* 424 * Likewise, but converts values in place. 425 */ 426 template <typename T> 427 static void swapFromBigEndianInPlace(T* aPtr, size_t aCount) { 428 maybeSwapInPlace<Big, ThisEndian>(aPtr, aCount); 429 } 430 431 /* 432 * Synonyms for the big-endian functions, for better readability 433 * in network code. 434 */ 435 template <typename T> 436 [[nodiscard]] static T swapFromNetworkOrder(T aValue) { 437 return swapFromBigEndian(aValue); 438 } 439 440 template <typename T> 441 static void copyAndSwapFromNetworkOrder(T* aDest, const void* aSrc, 442 size_t aCount) { 443 copyAndSwapFromBigEndian(aDest, aSrc, aCount); 444 } 445 446 template <typename T> 447 static void swapFromNetworkOrderInPlace(T* aPtr, size_t aCount) { 448 swapFromBigEndianInPlace(aPtr, aCount); 449 } 450 451 private: 452 /** 453 * Read a value of type T, encoded in endianness ThisEndian from |aPtr|. 454 * Return that value encoded in native endianness. 455 */ 456 template <typename T> 457 static T read(const void* aPtr) { 458 T Val; 459 memcpy(static_cast<void*>(&Val), aPtr, sizeof(T)); 460 return maybeSwap<ThisEndian, MOZ_NATIVE_ENDIANNESS>(Val); 461 } 462 463 /** 464 * Write a value of type T, in native endianness, to |aPtr|, in ThisEndian 465 * endianness. 466 */ 467 template <typename T> 468 static void write(void* aPtr, T aValue) { 469 T tmp = maybeSwap<MOZ_NATIVE_ENDIANNESS, ThisEndian>(aValue); 470 memcpy(aPtr, &tmp, sizeof(T)); 471 } 472 473 Endian() = delete; 474 Endian(const Endian& aTther) = delete; 475 void operator=(const Endian& aOther) = delete; 476 }; 477 478 template <Endianness ThisEndian> 479 class EndianReadWrite : public Endian<ThisEndian> { 480 private: 481 typedef Endian<ThisEndian> super; 482 483 public: 484 using super::readInt16; 485 using super::readInt32; 486 using super::readInt64; 487 using super::readIntptr; 488 using super::readUint16; 489 using super::readUint32; 490 using super::readUint64; 491 using super::readUintptr; 492 using super::writeInt16; 493 using super::writeInt32; 494 using super::writeInt64; 495 using super::writeIntptr; 496 using super::writeUint16; 497 using super::writeUint32; 498 using super::writeUint64; 499 using super::writeUintptr; 500 }; 501 502 } /* namespace detail */ 503 504 class LittleEndian final : public detail::EndianReadWrite<detail::Little> {}; 505 506 class BigEndian final : public detail::EndianReadWrite<detail::Big> {}; 507 508 typedef BigEndian NetworkEndian; 509 510 class NativeEndian final : public detail::Endian<MOZ_NATIVE_ENDIANNESS> { 511 private: 512 typedef detail::Endian<MOZ_NATIVE_ENDIANNESS> super; 513 514 public: 515 /* 516 * These functions are intended for cases where you have data in your 517 * native-endian format and you need the data to appear in the appropriate 518 * endianness for transmission, serialization, etc. 519 */ 520 using super::copyAndSwapToBigEndian; 521 using super::copyAndSwapToLittleEndian; 522 using super::copyAndSwapToNetworkOrder; 523 using super::swapToBigEndian; 524 using super::swapToBigEndianInPlace; 525 using super::swapToLittleEndian; 526 using super::swapToLittleEndianInPlace; 527 using super::swapToNetworkOrder; 528 using super::swapToNetworkOrderInPlace; 529 530 /* 531 * These functions are intended for cases where you have data in the 532 * given endianness (e.g. reading from disk or a file-format) and you 533 * need the data to appear in native-endian format for processing. 534 */ 535 using super::copyAndSwapFromBigEndian; 536 using super::copyAndSwapFromLittleEndian; 537 using super::copyAndSwapFromNetworkOrder; 538 using super::swapFromBigEndian; 539 using super::swapFromBigEndianInPlace; 540 using super::swapFromLittleEndian; 541 using super::swapFromLittleEndianInPlace; 542 using super::swapFromNetworkOrder; 543 using super::swapFromNetworkOrderInPlace; 544 }; 545 546 #undef MOZ_NATIVE_ENDIANNESS 547 548 } /* namespace mozilla */ 549 550 #endif /* mozilla_EndianUtils_h */