pw_bytes: Functions for converting endianness

- Create pw_bytes/endian.h, which provides functions for converting
  integer byte order.
- Update pw_bytes docs.

Change-Id: Icacc91d8c5d4f185c562f3b20b7249572ad84f5d
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/17464
Commit-Queue: Wyatt Hepler <hepler@google.com>
Reviewed-by: Keir Mierle <keir@google.com>
diff --git a/pw_bytes/BUILD b/pw_bytes/BUILD
index 292e953..293ae83 100644
--- a/pw_bytes/BUILD
+++ b/pw_bytes/BUILD
@@ -30,6 +30,7 @@
     hdrs = [
         "public/pw_bytes/array.h",
         "public/pw_bytes/byte_builder.h",
+        "public/pw_bytes/endian.h",
         "public/pw_bytes/span.h",
     ],
     includes = ["public"],
@@ -57,3 +58,12 @@
         "//pw_unit_test",
     ],
 )
+
+pw_cc_test(
+    name = "endian_test",
+    srcs = ["endian_test.cc"],
+    deps = [
+        ":pw_bytes",
+        "//pw_unit_test",
+    ],
+)
diff --git a/pw_bytes/BUILD.gn b/pw_bytes/BUILD.gn
index 74c966b..84448c8 100644
--- a/pw_bytes/BUILD.gn
+++ b/pw_bytes/BUILD.gn
@@ -29,6 +29,7 @@
   public = [
     "public/pw_bytes/array.h",
     "public/pw_bytes/byte_builder.h",
+    "public/pw_bytes/endian.h",
     "public/pw_bytes/span.h",
   ]
   sources = [ "byte_builder.cc" ]
@@ -41,8 +42,9 @@
 
 pw_test_group("tests") {
   tests = [
-    ":array",
+    ":array_test",
     ":byte_builder_test",
+    ":endian_test",
   ]
   group_deps = [
     "$dir_pw_preprocessor:tests",
@@ -51,14 +53,19 @@
   ]
 }
 
+pw_test("array_test") {
+  deps = [ ":pw_bytes" ]
+  sources = [ "array_test.cc" ]
+}
+
 pw_test("byte_builder_test") {
   deps = [ ":pw_bytes" ]
   sources = [ "byte_builder_test.cc" ]
 }
 
-pw_test("array") {
+pw_test("endian_test") {
   deps = [ ":pw_bytes" ]
-  sources = [ "array_test.cc" ]
+  sources = [ "endian_test.cc" ]
 }
 
 pw_doc_group("docs") {
diff --git a/pw_bytes/docs.rst b/pw_bytes/docs.rst
index d8a26ca..39c5c5d 100644
--- a/pw_bytes/docs.rst
+++ b/pw_bytes/docs.rst
@@ -22,22 +22,27 @@
 Features
 ========
 
-pw::ByteBuilder
------------------
-ByteBuilder is a utility class which facilitates the creation and
-building of formatted bytes in a fixed-size buffer.
+pw_bytes/array.h
+----------------
+Functions for working with byte arrays, primarily for building fixed-size byte
+arrays at compile time.
 
-Utilities for building byte arrays at run time
-------------------------------------------------
--``PutInt8``, ``PutUInt8``: Inserts 8-bit integers.
--``PutInt16``, ``PutInt16``: Inserts 16-bit integers in little/big endian.
--``PutInt32``, ``PutUInt32``: Inserts 32-bit integers in little/big endian.
--``PutInt64``, ``PutInt64``: Inserts 64-bit integers in little/big endian.
+pw_bytes/byte_builder.h
+-----------------------
+.. cpp:class:: ByteBuilder
+
+  ``ByteBuilder`` is a class that facilitates building or reading arrays of
+  bytes in a fixed-size buffer. ByteBuilder handles reading and writing integers
+  with varying endianness.
+
+.. cpp:class:: template <size_t max_size> ByteBuffer
+
+  ``ByteBuilder`` with an internally allocated buffer.
 
 Size report: using ByteBuffer
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 .. include:: byte_builder_size_report
 
-Future work
-^^^^^^^^^^^
-- Utilities for building byte arrays at compile time.
+pw_bytes/endian.h
+-----------------
+Functions for converting the endianness of integral values.
diff --git a/pw_bytes/endian_test.cc b/pw_bytes/endian_test.cc
new file mode 100644
index 0000000..c58c256
--- /dev/null
+++ b/pw_bytes/endian_test.cc
@@ -0,0 +1,164 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "pw_bytes/endian.h"
+
+#include <array>
+#include <cstddef>
+
+#include "gtest/gtest.h"
+
+namespace pw::bytes {
+namespace {
+
+constexpr std::endian kNonNative = (std::endian::native == std::endian::little)
+                                       ? std::endian::big
+                                       : std::endian::little;
+
+// ConvertOrderTo/From
+//
+// ConvertOrderTo and ConvertOrderFrom are implemented identically, but are
+// provided as separate functions to improve readability where they are used.
+//
+// clang-format off
+
+// Native endianess conversions (should do nothing)
+
+// Convert unsigned to native endianness
+static_assert(ConvertOrderTo(std::endian::native, uint8_t{0x12}) == uint8_t{0x12});
+static_assert(ConvertOrderTo(std::endian::native, uint16_t{0x0011}) == uint16_t{0x0011});
+static_assert(ConvertOrderTo(std::endian::native, uint32_t{0x33221100}) == uint32_t{0x33221100});
+static_assert(ConvertOrderTo(std::endian::native, uint64_t{0x0011223344556677}) == uint64_t{0x0011223344556677});
+
+// Convert signed to native endianness
+static_assert(ConvertOrderTo(std::endian::native, int8_t{0x12}) == int8_t{0x12});
+static_assert(ConvertOrderTo(std::endian::native, int16_t{0x0011}) == int16_t{0x0011});
+static_assert(ConvertOrderTo(std::endian::native, int32_t{0x33221100}) == int32_t{0x33221100});
+static_assert(ConvertOrderTo(std::endian::native, int64_t{0x0011223344556677}) == int64_t{0x0011223344556677});
+
+// Convert unsigned from native endianness
+static_assert(ConvertOrderFrom(std::endian::native, uint8_t{0x12}) == uint8_t{0x12});
+static_assert(ConvertOrderFrom(std::endian::native, uint16_t{0x0011}) == uint16_t{0x0011});
+static_assert(ConvertOrderFrom(std::endian::native, uint32_t{0x33221100}) == uint32_t{0x33221100});
+static_assert(ConvertOrderFrom(std::endian::native, uint64_t{0x0011223344556677}) == uint64_t{0x0011223344556677});
+
+// Convert signed from native endianness
+static_assert(ConvertOrderFrom(std::endian::native, int8_t{0x12}) == int8_t{0x12});
+static_assert(ConvertOrderFrom(std::endian::native, int16_t{0x0011}) == int16_t{0x0011});
+static_assert(ConvertOrderFrom(std::endian::native, int32_t{0x33221100}) == int32_t{0x33221100});
+static_assert(ConvertOrderFrom(std::endian::native, int64_t{0x0011223344556677}) == int64_t{0x0011223344556677});
+
+// Non-native endianess conversions (should reverse byte order)
+
+// Convert unsigned to non-native endianness
+static_assert(ConvertOrderTo(kNonNative, uint8_t{0x12}) == uint8_t{0x12});
+static_assert(ConvertOrderTo(kNonNative, uint16_t{0x0011}) == uint16_t{0x1100});
+static_assert(ConvertOrderTo(kNonNative, uint32_t{0x33221100}) == uint32_t{0x00112233});
+static_assert(ConvertOrderTo(kNonNative, uint64_t{0x0011223344556677}) == uint64_t{0x7766554433221100});
+
+// Convert signed to non-native endianness
+static_assert(ConvertOrderTo(kNonNative, int8_t{0x12}) == int8_t{0x12});
+static_assert(ConvertOrderTo(kNonNative, int16_t{0x0011}) == int16_t{0x1100});
+static_assert(ConvertOrderTo(kNonNative, int32_t{0x33221100}) == int32_t{0x00112233});
+static_assert(ConvertOrderTo(kNonNative, int64_t{0x0011223344556677}) == int64_t{0x7766554433221100});
+
+// Convert unsigned from non-native endianness
+static_assert(ConvertOrderFrom(kNonNative, uint8_t{0x12}) == uint8_t{0x12});
+static_assert(ConvertOrderFrom(kNonNative, uint16_t{0x0011}) == uint16_t{0x1100});
+static_assert(ConvertOrderFrom(kNonNative, uint32_t{0x33221100}) == uint32_t{0x00112233});
+static_assert(ConvertOrderFrom(kNonNative, uint64_t{0x0011223344556677}) == uint64_t{0x7766554433221100});
+
+// Convert signed from non-native endianness
+static_assert(ConvertOrderFrom(kNonNative, int8_t{0x12}) == int8_t{0x12});
+static_assert(ConvertOrderFrom(kNonNative, int16_t{0x0011}) == int16_t{0x1100});
+static_assert(ConvertOrderFrom(kNonNative, int32_t{0x33221100}) == int32_t{0x00112233});
+static_assert(ConvertOrderFrom(kNonNative, int64_t{0x0011223344556677}) == int64_t{0x7766554433221100});
+
+// clang-format on
+
+template <typename T, typename U>
+constexpr bool Equal(const T& lhs, const U& rhs) {
+  if (sizeof(lhs) != sizeof(rhs) || std::size(lhs) != std::size(rhs)) {
+    return false;
+  }
+
+  for (size_t i = 0; i < std::size(lhs); ++i) {
+    if (lhs[i] != rhs[i]) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+// CopyInOrder copies a value to a std::array with the specified endianness.
+//
+// clang-format off
+
+// 8-bit little
+static_assert(Equal(CopyInOrder(std::endian::little, '?'),
+                    Array<'?'>()));
+static_assert(Equal(CopyInOrder(std::endian::little, uint8_t{0x10}),
+                    Array<0x10>()));
+static_assert(Equal(CopyInOrder(std::endian::little, static_cast<int8_t>(0x10)),
+                    Array<0x10>()));
+
+// 8-bit big
+static_assert(Equal(CopyInOrder(std::endian::big, '?'),
+                    Array<'?'>()));
+static_assert(Equal(CopyInOrder(std::endian::big, static_cast<uint8_t>(0x10)),
+                    Array<0x10>()));
+static_assert(Equal(CopyInOrder(std::endian::big, static_cast<int8_t>(0x10)),
+                    Array<0x10>()));
+
+// 16-bit little
+static_assert(Equal(CopyInOrder(std::endian::little, uint16_t{0xAB12}),
+                    Array<0x12, 0xAB>()));
+static_assert(Equal(CopyInOrder(std::endian::little, static_cast<int16_t>(0xAB12)),
+                    Array<0x12, 0xAB>()));
+
+// 16-bit big
+static_assert(Equal(CopyInOrder(std::endian::big, uint16_t{0xAB12}),
+                    Array<0xAB, 0x12>()));
+static_assert(Equal(CopyInOrder(std::endian::big, static_cast<int16_t>(0xAB12)),
+                    Array<0xAB, 0x12>()));
+
+// 32-bit little
+static_assert(Equal(CopyInOrder(std::endian::little, uint32_t{0xAABBCCDD}),
+                    Array<0xDD, 0xCC, 0xBB, 0xAA>()));
+static_assert(Equal(CopyInOrder(std::endian::little, static_cast<int32_t>(0xAABBCCDD)),
+                    Array<0xDD, 0xCC, 0xBB, 0xAA>()));
+
+// 32-bit big
+static_assert(Equal(CopyInOrder(std::endian::big, uint32_t{0xAABBCCDD}),
+                    Array<0xAA, 0xBB, 0xCC, 0xDD>()));
+static_assert(Equal(CopyInOrder(std::endian::big, static_cast<int32_t>(0xAABBCCDD)),
+                    Array<0xAA, 0xBB, 0xCC, 0xDD>()));
+
+// 64-bit little
+static_assert(Equal(CopyInOrder(std::endian::little, uint64_t{0xAABBCCDD11223344}),
+                    Array<0x44, 0x33, 0x22, 0x11, 0xDD, 0xCC, 0xBB, 0xAA>()));
+static_assert(Equal(CopyInOrder(std::endian::little, static_cast<int64_t>(0xAABBCCDD11223344ull)),
+                    Array<0x44, 0x33, 0x22, 0x11, 0xDD, 0xCC, 0xBB, 0xAA>()));
+
+// 64-bit big
+static_assert(Equal(CopyInOrder(std::endian::big, uint64_t{0xAABBCCDD11223344}),
+                    Array<0xAA, 0xBB, 0xCC, 0xDD, 0x11, 0x22, 0x33, 0x44>()));
+static_assert(Equal(CopyInOrder(std::endian::big, static_cast<int64_t>(0xAABBCCDD11223344ull)),
+                    Array<0xAA, 0xBB, 0xCC, 0xDD, 0x11, 0x22, 0x33, 0x44>()));
+
+// clang-format on
+
+}  // namespace
+}  // namespace pw::bytes
diff --git a/pw_bytes/public/pw_bytes/byte_builder.h b/pw_bytes/public/pw_bytes/byte_builder.h
index bcb0b5f..893efa6 100644
--- a/pw_bytes/public/pw_bytes/byte_builder.h
+++ b/pw_bytes/public/pw_bytes/byte_builder.h
@@ -19,6 +19,7 @@
 #include <cstddef>
 #include <cstring>
 
+#include "pw_bytes/endian.h"
 #include "pw_bytes/span.h"
 #include "pw_preprocessor/compiler.h"
 #include "pw_status/status.h"
@@ -191,18 +192,7 @@
     T GetInteger(std::endian order = std::endian::little) const {
       T value;
       std::memcpy(&value, byte_, sizeof(T));
-      if (std::endian::native != order) {
-        if constexpr (sizeof(T) == 1) {
-          return value;
-        } else if constexpr (sizeof(T) == 2) {
-          return Reverse2Bytes(value);
-        } else if constexpr (sizeof(T) == 4) {
-          return Reverse4Bytes(value);
-        } else if constexpr (sizeof(T) == 8) {
-          return Reverse8Bytes(value);
-        }
-      }
-      return value;
+      return bytes::ConvertOrderFrom(order, value);
     }
 
     const std::byte* byte_;
@@ -311,10 +301,7 @@
   // Put methods for inserting different 16-bit ints
   ByteBuilder& PutUint16(uint16_t value,
                          std::endian order = std::endian::little) {
-    if (std::endian::native != order) {
-      value = Reverse2Bytes(value);
-    }
-    return WriteInOrder(value);
+    return WriteInOrder(bytes::ConvertOrderTo(order, value));
   }
 
   ByteBuilder& PutInt16(int16_t value,
@@ -325,10 +312,7 @@
   // Put methods for inserting different 32-bit ints
   ByteBuilder& PutUint32(uint32_t value,
                          std::endian order = std::endian::little) {
-    if (std::endian::native != order) {
-      value = Reverse4Bytes(value);
-    }
-    return WriteInOrder(value);
+    return WriteInOrder(bytes::ConvertOrderTo(order, value));
   }
 
   ByteBuilder& PutInt32(int32_t value,
@@ -339,10 +323,7 @@
   // Put methods for inserting different 64-bit ints
   ByteBuilder& PutUint64(uint64_t value,
                          std::endian order = std::endian::little) {
-    if (std::endian::native != order) {
-      value = Reverse8Bytes(value);
-    }
-    return WriteInOrder(value);
+    return WriteInOrder(bytes::ConvertOrderTo(order, value));
   }
 
   ByteBuilder& PutInt64(int64_t value,
@@ -361,28 +342,6 @@
   };
 
  private:
-  static constexpr uint16_t Reverse2Bytes(uint16_t value) {
-    return uint16_t(((value & 0x00FF) << 8) | ((value & 0xFF00) >> 8));
-  }
-
-  static constexpr uint32_t Reverse4Bytes(uint32_t value) {
-    return uint32_t(((value & 0x000000FF) << 3 * 8) |  //
-                    ((value & 0x0000FF00) << 1 * 8) |  //
-                    ((value & 0x00FF0000) >> 1 * 8) |  //
-                    ((value & 0xFF000000) >> 3 * 8));
-  }
-
-  static constexpr uint64_t Reverse8Bytes(uint64_t value) {
-    return uint64_t(((value & 0x00000000000000FF) << 7 * 8) |  //
-                    ((value & 0x000000000000FF00) << 5 * 8) |  //
-                    ((value & 0x0000000000FF0000) << 3 * 8) |  //
-                    ((value & 0x00000000FF000000) << 1 * 8) |  //
-                    ((value & 0x000000FF00000000) >> 1 * 8) |  //
-                    ((value & 0x0000FF0000000000) >> 3 * 8) |  //
-                    ((value & 0x00FF000000000000) >> 5 * 8) |  //
-                    ((value & 0xFF00000000000000) >> 7 * 8));
-  }
-
   template <typename T>
   ByteBuilder& WriteInOrder(T value) {
     return append(&value, sizeof(value));
diff --git a/pw_bytes/public/pw_bytes/endian.h b/pw_bytes/public/pw_bytes/endian.h
new file mode 100644
index 0000000..0ff8548
--- /dev/null
+++ b/pw_bytes/public/pw_bytes/endian.h
@@ -0,0 +1,139 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#pragma once
+
+#include <array>
+#include <bit>
+#include <cstring>
+#include <type_traits>
+
+#include "pw_bytes/array.h"
+
+namespace pw::bytes {
+namespace internal {
+
+// Use a struct rather than an alias to give the type a more reasonable name.
+template <typename T>
+struct EquivalentUintImpl
+    : std::conditional<
+          sizeof(T) == 1,
+          uint8_t,
+          std::conditional_t<
+              sizeof(T) == 2,
+              uint16_t,
+              std::conditional_t<
+                  sizeof(T) == 4,
+                  uint32_t,
+                  std::conditional_t<sizeof(T) == 8, uint64_t, void>>>> {
+  static_assert(std::is_integral_v<T>);
+};
+
+template <typename T>
+using EquivalentUint = typename EquivalentUintImpl<T>::type;
+
+template <typename T>
+constexpr std::array<std::byte, sizeof(T)> CopyLittleEndian(T value) {
+  return CopyLittleEndian(static_cast<EquivalentUint<T>>(value));
+}
+
+template <>
+constexpr std::array<std::byte, 1> CopyLittleEndian<uint8_t>(uint8_t value) {
+  return MakeArray(value);
+}
+template <>
+constexpr std::array<std::byte, 2> CopyLittleEndian<uint16_t>(uint16_t value) {
+  return MakeArray(value & 0x00FF, (value & 0xFF00) >> 8);
+}
+
+template <>
+constexpr std::array<std::byte, 4> CopyLittleEndian<uint32_t>(uint32_t value) {
+  return MakeArray((value & 0x000000FF) >> 0 * 8,
+                   (value & 0x0000FF00) >> 1 * 8,
+                   (value & 0x00FF0000) >> 2 * 8,
+                   (value & 0xFF000000) >> 3 * 8);
+}
+
+template <>
+constexpr std::array<std::byte, 8> CopyLittleEndian<uint64_t>(uint64_t value) {
+  return MakeArray((value & 0x00000000000000FF) >> 0 * 8,
+                   (value & 0x000000000000FF00) >> 1 * 8,
+                   (value & 0x0000000000FF0000) >> 2 * 8,
+                   (value & 0x00000000FF000000) >> 3 * 8,
+                   (value & 0x000000FF00000000) >> 4 * 8,
+                   (value & 0x0000FF0000000000) >> 5 * 8,
+                   (value & 0x00FF000000000000) >> 6 * 8,
+                   (value & 0xFF00000000000000) >> 7 * 8);
+}
+
+template <typename T>
+constexpr T ReverseBytes(T value) {
+  EquivalentUint<T> uint = static_cast<EquivalentUint<T>>(value);
+
+  if constexpr (sizeof(uint) == 1) {
+    return static_cast<T>(uint);
+  } else if constexpr (sizeof(uint) == 2) {
+    return static_cast<T>(((uint & 0x00FF) << 8) | ((uint & 0xFF00) >> 8));
+  } else if constexpr (sizeof(uint) == 4) {
+    return static_cast<T>(((uint & 0x000000FF) << 3 * 8) |  //
+                          ((uint & 0x0000FF00) << 1 * 8) |  //
+                          ((uint & 0x00FF0000) >> 1 * 8) |  //
+                          ((uint & 0xFF000000) >> 3 * 8));
+  } else {
+    static_assert(sizeof(uint) == 8);
+    return static_cast<T>(((uint & 0x00000000000000FF) << 7 * 8) |  //
+                          ((uint & 0x000000000000FF00) << 5 * 8) |  //
+                          ((uint & 0x0000000000FF0000) << 3 * 8) |  //
+                          ((uint & 0x00000000FF000000) << 1 * 8) |  //
+                          ((uint & 0x000000FF00000000) >> 1 * 8) |  //
+                          ((uint & 0x0000FF0000000000) >> 3 * 8) |  //
+                          ((uint & 0x00FF000000000000) >> 5 * 8) |  //
+                          ((uint & 0xFF00000000000000) >> 7 * 8));
+  }
+}
+
+}  // namespace internal
+
+// Functions for reordering bytes in the provided integral value to match the
+// specified byte order. These functions are similar to the htonl() family of
+// functions.
+//
+// If the value is converted to non-system endianness, it must NOT be used
+// directly, since the value will be meaningless. Such values are only suitable
+// to memcpy'd or sent to a different device.
+template <typename T>
+constexpr T ConvertOrder(std::endian from, std::endian to, T value) {
+  return from == to ? value : internal::ReverseBytes(value);
+}
+
+// Converts a value from native byte order to the specified byte order. Since
+// this function changes the value's endianness, the result should only be used
+// to memcpy the bytes to a buffer or send to a different device.
+template <typename T>
+constexpr T ConvertOrderTo(std::endian to_endianness, T value) {
+  return ConvertOrder(std::endian::native, to_endianness, value);
+}
+
+// Converts a value from the specified byte order to the native byte order.
+template <typename T>
+constexpr T ConvertOrderFrom(std::endian from_endianness, T value) {
+  return ConvertOrder(from_endianness, std::endian::native, value);
+}
+
+// Copies the value to a std::array with the specified endianness.
+template <typename T>
+constexpr auto CopyInOrder(std::endian order, T value) {
+  return internal::CopyLittleEndian(ConvertOrderTo(order, value));
+}
+
+}  // namespace pw::bytes