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