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