Pigweed
C/C++ API Reference
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
Loading...
Searching...
No Matches
endian.h
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
27namespace pw::bytes {
28namespace internal {
29
30// Use a struct rather than an alias to give the type a more reasonable name.
31template <typename T>
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
46template <typename T>
47using EquivalentUint = typename EquivalentUintImpl<T>::type;
48
49template <typename T>
50constexpr std::array<std::byte, sizeof(T)> CopyLittleEndian(T value) {
51 return CopyLittleEndian(static_cast<EquivalentUint<T>>(value));
52}
53
54template <>
55constexpr std::array<std::byte, 1> CopyLittleEndian<uint8_t>(uint8_t value) {
56 return MakeArray(value);
57}
58template <>
59constexpr std::array<std::byte, 2> CopyLittleEndian<uint16_t>(uint16_t value) {
60 return MakeArray(value & 0x00FF, (value & 0xFF00) >> 8);
61}
62
63template <>
64constexpr 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
71template <>
72constexpr 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
83template <typename T>
84constexpr 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.
118template <typename T>
119constexpr 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.
126template <typename T>
127constexpr 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.
132template <typename T>
133constexpr 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.
138template <typename T>
139constexpr 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.
148template <typename T>
149T 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)!
163template <typename T>
164T 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.
171template <typename T,
172 typename B,
173 size_t kBufferSize,
174 typename = std::enable_if_t<kBufferSize != dynamic_extent &&
175 sizeof(B) == sizeof(std::byte)>>
176T 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.
182template <typename T, typename B, size_t kBufferSize>
183T 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.
188template <typename T, typename B, size_t kBufferSize>
189T 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.
195template <typename T>
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