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