• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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