1 // Copyright 2020 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 #pragma once
15
16 #include <array>
17 #include <bit>
18 #include <cstring>
19 #include <span>
20 #include <type_traits>
21
22 #include "pw_bytes/array.h"
23 #include "pw_bytes/span.h"
24
25 namespace pw::bytes {
26 namespace internal {
27
28 // Use a struct rather than an alias to give the type a more reasonable name.
29 template <typename T>
30 struct EquivalentUintImpl
31 : std::conditional<
32 sizeof(T) == 1,
33 uint8_t,
34 std::conditional_t<
35 sizeof(T) == 2,
36 uint16_t,
37 std::conditional_t<
38 sizeof(T) == 4,
39 uint32_t,
40 std::conditional_t<sizeof(T) == 8, uint64_t, void>>>> {
41 static_assert(std::is_integral_v<T>);
42 };
43
44 template <typename T>
45 using EquivalentUint = typename EquivalentUintImpl<T>::type;
46
47 template <typename T>
CopyLittleEndian(T value)48 constexpr std::array<std::byte, sizeof(T)> CopyLittleEndian(T value) {
49 return CopyLittleEndian(static_cast<EquivalentUint<T>>(value));
50 }
51
52 template <>
53 constexpr std::array<std::byte, 1> CopyLittleEndian<uint8_t>(uint8_t value) {
54 return MakeArray(value);
55 }
56 template <>
57 constexpr std::array<std::byte, 2> CopyLittleEndian<uint16_t>(uint16_t value) {
58 return MakeArray(value & 0x00FF, (value & 0xFF00) >> 8);
59 }
60
61 template <>
62 constexpr std::array<std::byte, 4> CopyLittleEndian<uint32_t>(uint32_t value) {
63 return MakeArray((value & 0x000000FF) >> 0 * 8,
64 (value & 0x0000FF00) >> 1 * 8,
65 (value & 0x00FF0000) >> 2 * 8,
66 (value & 0xFF000000) >> 3 * 8);
67 }
68
69 template <>
70 constexpr std::array<std::byte, 8> CopyLittleEndian<uint64_t>(uint64_t value) {
71 return MakeArray((value & 0x00000000000000FF) >> 0 * 8,
72 (value & 0x000000000000FF00) >> 1 * 8,
73 (value & 0x0000000000FF0000) >> 2 * 8,
74 (value & 0x00000000FF000000) >> 3 * 8,
75 (value & 0x000000FF00000000) >> 4 * 8,
76 (value & 0x0000FF0000000000) >> 5 * 8,
77 (value & 0x00FF000000000000) >> 6 * 8,
78 (value & 0xFF00000000000000) >> 7 * 8);
79 }
80
81 template <typename T>
ReverseBytes(T value)82 constexpr T ReverseBytes(T value) {
83 EquivalentUint<T> uint = static_cast<EquivalentUint<T>>(value);
84
85 if constexpr (sizeof(uint) == 1) {
86 return static_cast<T>(uint);
87 } else if constexpr (sizeof(uint) == 2) {
88 return static_cast<T>(((uint & 0x00FF) << 8) | ((uint & 0xFF00) >> 8));
89 } else if constexpr (sizeof(uint) == 4) {
90 return static_cast<T>(((uint & 0x000000FF) << 3 * 8) | //
91 ((uint & 0x0000FF00) << 1 * 8) | //
92 ((uint & 0x00FF0000) >> 1 * 8) | //
93 ((uint & 0xFF000000) >> 3 * 8));
94 } else {
95 static_assert(sizeof(uint) == 8);
96 return static_cast<T>(((uint & 0x00000000000000FF) << 7 * 8) | //
97 ((uint & 0x000000000000FF00) << 5 * 8) | //
98 ((uint & 0x0000000000FF0000) << 3 * 8) | //
99 ((uint & 0x00000000FF000000) << 1 * 8) | //
100 ((uint & 0x000000FF00000000) >> 1 * 8) | //
101 ((uint & 0x0000FF0000000000) >> 3 * 8) | //
102 ((uint & 0x00FF000000000000) >> 5 * 8) | //
103 ((uint & 0xFF00000000000000) >> 7 * 8));
104 }
105 }
106
107 } // namespace internal
108
109 // Functions for reordering bytes in the provided integral value to match the
110 // specified byte order. These functions are similar to the htonl() family of
111 // functions.
112 //
113 // If the value is converted to non-system endianness, it must NOT be used
114 // directly, since the value will be meaningless. Such values are only suitable
115 // to memcpy'd or sent to a different device.
116 template <typename T>
ConvertOrder(std::endian from,std::endian to,T value)117 constexpr T ConvertOrder(std::endian from, std::endian to, T value) {
118 return from == to ? value : internal::ReverseBytes(value);
119 }
120
121 // Converts a value from native byte order to the specified byte order. Since
122 // this function changes the value's endianness, the result should only be used
123 // to memcpy the bytes to a buffer or send to a different device.
124 template <typename T>
ConvertOrderTo(std::endian to_endianness,T value)125 constexpr T ConvertOrderTo(std::endian to_endianness, T value) {
126 return ConvertOrder(std::endian::native, to_endianness, value);
127 }
128
129 // Converts a value from the specified byte order to the native byte order.
130 template <typename T>
ConvertOrderFrom(std::endian from_endianness,T value)131 constexpr T ConvertOrderFrom(std::endian from_endianness, T value) {
132 return ConvertOrder(from_endianness, std::endian::native, value);
133 }
134
135 // Copies the value to a std::array with the specified endianness.
136 template <typename T>
CopyInOrder(std::endian order,T value)137 constexpr auto CopyInOrder(std::endian order, T value) {
138 return internal::CopyLittleEndian(ConvertOrderTo(order, value));
139 }
140
141 // Reads a value from a buffer with the specified endianness.
142 //
143 // The buffer **MUST** be at least sizeof(T) bytes large! If you are not
144 // absolutely certain the input buffer is large enough, use the ReadInOrder
145 // overload that returns bool, which checks the buffer size at runtime.
146 template <typename T>
ReadInOrder(std::endian order,const void * buffer)147 T ReadInOrder(std::endian order, const void* buffer) {
148 T value;
149 std::memcpy(&value, buffer, sizeof(value));
150 return ConvertOrderFrom(order, value);
151 }
152
153 // ReadInOrder from a static-extent span, with compile-time bounds checking.
154 template <typename T,
155 typename B,
156 size_t kBufferSize,
157 typename = std::enable_if_t<kBufferSize != std::dynamic_extent &&
158 sizeof(B) == sizeof(std::byte)>>
ReadInOrder(std::endian order,std::span<B,kBufferSize> buffer)159 T ReadInOrder(std::endian order, std::span<B, kBufferSize> buffer) {
160 static_assert(kBufferSize >= sizeof(T));
161 return ReadInOrder<T>(order, buffer.data());
162 }
163
164 // ReadInOrder from a std::array, with compile-time bounds checking.
165 template <typename T, typename B, size_t kBufferSize>
ReadInOrder(std::endian order,const std::array<B,kBufferSize> & buffer)166 T ReadInOrder(std::endian order, const std::array<B, kBufferSize>& buffer) {
167 return ReadInOrder<T>(order, std::span(buffer));
168 }
169
170 // ReadInOrder from a C array, with compile-time bounds checking.
171 template <typename T, typename B, size_t kBufferSize>
ReadInOrder(std::endian order,const B (& buffer)[kBufferSize])172 T ReadInOrder(std::endian order, const B (&buffer)[kBufferSize]) {
173 return ReadInOrder<T>(order, std::span(buffer));
174 }
175
176 // Reads a value with the specified endianness from the buffer, with bounds
177 // checking. Returns true if successful, false if buffer is too small for a T.
178 template <typename T>
ReadInOrder(std::endian order,ConstByteSpan buffer,T & value)179 [[nodiscard]] bool ReadInOrder(std::endian order,
180 ConstByteSpan buffer,
181 T& value) {
182 if (buffer.size() < sizeof(T)) {
183 return false;
184 }
185
186 value = ReadInOrder<T>(order, buffer.data());
187 return true;
188 }
189
190 } // namespace pw::bytes
191