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