tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 */