libdoip  0.1.0
DoIP (Diagnostics over Internet Protocol) ISO 13400 C++17 Library
ByteArray.h
Go to the documentation of this file.
1 /**
2  * @file ByteArray.h
3  * @brief Defines the ByteArray type and utility functions for byte manipulation
4  *
5  * This file provides a ByteArray type (based on std::vector<uint8_t>) with
6  * convenient methods for reading and writing multi-byte integer values in
7  * big-endian format, commonly used in network protocols.
8  */
9 
10 #ifndef BYTEARRAY_H
11 #define BYTEARRAY_H
12 
13 #include <iomanip>
14 #include <ostream>
15 #include <stdint.h>
16 #include <type_traits>
17 #include <vector>
18 
19 namespace doip {
20 
21 namespace util {
22 
23 /**
24  * @brief Reads a 16-bit unsigned integer in big-endian format from a byte array
25  *
26  * @param data Pointer to the byte array
27  * @param index Starting index in the array
28  * @return uint16_t The 16-bit value read from the array
29  */
30 inline uint16_t readU16BE(const uint8_t *data, size_t index) {
31  return (static_cast<uint16_t>(data[index]) << 8) |
32  static_cast<uint16_t>(data[index + 1]);
33 }
34 
35 /**
36  * @brief Reads a 32-bit unsigned integer in big-endian format from a byte array
37  *
38  * @param data Pointer to the byte array
39  * @param index Starting index in the array
40  * @return uint32_t The 32-bit value read from the array
41  */
42 inline uint32_t readU32BE(const uint8_t *data, size_t index) {
43  return (static_cast<uint32_t>(data[index]) << 24) |
44  (static_cast<uint32_t>(data[index + 1]) << 16) |
45  (static_cast<uint32_t>(data[index + 2]) << 8) |
46  static_cast<uint32_t>(data[index + 3]);
47 }
48 
49 } // namespace util
50 
51 /**
52  * @brief A dynamic array of bytes with utility methods for network protocol handling
53  *
54  * ByteArray extends std::vector<uint8_t> with convenient methods for reading
55  * and writing multi-byte integer values in big-endian format (network byte order).
56  * This is commonly used in DoIP and other network protocols.
57  *
58  * @note All multi-byte read/write operations use big-endian (network) byte order
59  */
60 struct ByteArray : std::vector<uint8_t> {
61  /**
62  * @brief Default constructor - creates an empty ByteArray
63  */
64  ByteArray() = default;
65 
66  /**
67  * @brief Constructs a ByteArray from a raw byte array
68  *
69  * @param data Pointer to the source byte array
70  * @param size Number of bytes to copy from the source array
71  */
72  explicit ByteArray(const uint8_t *data, size_t size) : std::vector<uint8_t>(data, data + size) {}
73 
74  /**
75  * @brief Constructs a ByteArray from an initializer list
76  *
77  * @param init_list Initializer list of bytes
78  *
79  * @example
80  * ByteArray arr = {0x01, 0x02, 0x03, 0xFF};
81  */
82  ByteArray(const std::initializer_list<uint8_t> &init_list) : std::vector<uint8_t>(init_list) {}
83 
84  /**
85  * @brief Writes a 16-bit unsigned integer in big-endian format at a specific index
86  *
87  * Overwrites 2 bytes starting at the specified index with the value in
88  * big-endian (network) byte order.
89  *
90  * @param index Starting index where to write (must be < size() - 1)
91  * @param value The 16-bit value to write
92  * @throws std::out_of_range if index + 1 >= size()
93  */
94  void writeU16At(size_t index, uint16_t value) {
95  if (index + 1 >= this->size()) {
96  throw std::out_of_range("Index out of range for writeU16BE");
97  }
98  (*this)[index] = static_cast<uint8_t>((value >> 8) & 0xFF);
99  (*this)[index + 1] = static_cast<uint8_t>(value & 0xFF);
100  }
101 
102  /**
103  * @brief Appends a 16-bit unsigned integer in big-endian format to the end
104  *
105  * Adds 2 bytes to the end of the ByteArray representing the value in
106  * big-endian (network) byte order.
107  *
108  * @param value The 16-bit value to append
109  */
110  void writeU16BE(uint16_t value) {
111  emplace_back(static_cast<uint8_t>((value >> 8) & 0xFF));
112  emplace_back(static_cast<uint8_t>(value & 0xFF));
113  }
114 
115  /**
116  * @brief Writes a 32-bit unsigned integer in big-endian format at a specific index
117  *
118  * Overwrites 4 bytes starting at the specified index with the value in
119  * big-endian (network) byte order.
120  *
121  * @param index Starting index where to write (must be < size() - 3)
122  * @param value The 32-bit value to write
123  * @throws std::out_of_range if index + 3 >= size()
124  */
125  void writeU32At(size_t index, uint32_t value) {
126  if (index + 3 >= this->size()) {
127  throw std::out_of_range("Index out of range for writeU32BE");
128  }
129  (*this)[index] = static_cast<uint8_t>((value >> 24) & 0xFF);
130  (*this)[index + 1] = static_cast<uint8_t>((value >> 16) & 0xFF);
131  (*this)[index + 2] = static_cast<uint8_t>((value >> 8) & 0xFF);
132  (*this)[index + 3] = static_cast<uint8_t>(value & 0xFF);
133  }
134 
135  /**
136  * @brief Appends a 32-bit unsigned integer in big-endian format to the end
137  *
138  * Adds 4 bytes to the end of the ByteArray representing the value in
139  * big-endian (network) byte order.
140  *
141  * @param value The 32-bit value to append
142  */
143  void writeU32BE(uint32_t value) {
144  emplace_back(static_cast<uint8_t>((value >> 24) & 0xFF));
145  emplace_back(static_cast<uint8_t>((value >> 16) & 0xFF));
146  emplace_back(static_cast<uint8_t>((value >> 8) & 0xFF));
147  emplace_back(static_cast<uint8_t>(value & 0xFF));
148  }
149 
150  /**
151  * @brief Appends an enum class value as its underlying integral type
152  *
153  * Automatically detects the underlying type of the enum and writes it
154  * in big-endian format. Supports 8-bit, 16-bit, and 32-bit enums.
155  *
156  * @tparam E Enum class type
157  * @param value The enum value to append
158  *
159  * @example
160  * enum class Status : uint16_t { OK = 0x0100, ERROR = 0x0200 };
161  * ByteArray arr;
162  * arr.writeEnum(Status::OK); // Appends 0x01, 0x00
163  */
164  template<typename E>
165  void writeEnum(E value) {
166  static_assert(std::is_enum_v<E>, "writeEnum requires an enum type");
167 
168  using UnderlyingType = std::underlying_type_t<E>;
169  auto integral_value = static_cast<UnderlyingType>(value);
170 
171  if constexpr (sizeof(UnderlyingType) == 1) {
172  emplace_back(static_cast<uint8_t>(integral_value));
173  } else if constexpr (sizeof(UnderlyingType) == 2) {
174  writeU16BE(static_cast<uint16_t>(integral_value));
175  } else if constexpr (sizeof(UnderlyingType) == 4) {
176  writeU32BE(static_cast<uint32_t>(integral_value));
177  } else {
178  static_assert(sizeof(UnderlyingType) <= 4, "Enum underlying type too large (max 32-bit supported)");
179  }
180  }
181 
182  /**
183  * @brief Writes an enum class value at a specific index as its underlying integral type
184  *
185  * Overwrites bytes starting at the specified index with the enum value
186  * converted to its underlying integral type in big-endian format.
187  *
188  * @tparam E Enum class type
189  * @param index Starting index where to write
190  * @param value The enum value to write
191  * @throws std::out_of_range if not enough space at index
192  */
193  template<typename E>
194  void writeEnumAt(size_t index, E value) {
195  static_assert(std::is_enum_v<E>, "writeEnumAt requires an enum type");
196 
197  using UnderlyingType = std::underlying_type_t<E>;
198  auto integral_value = static_cast<UnderlyingType>(value);
199 
200  if constexpr (sizeof(UnderlyingType) == 1) {
201  if (index >= this->size()) {
202  throw std::out_of_range("Index out of range for writeEnumAt");
203  }
204  (*this)[index] = static_cast<uint8_t>(integral_value);
205  } else if constexpr (sizeof(UnderlyingType) == 2) {
206  writeU16At(index, static_cast<uint16_t>(integral_value));
207  } else if constexpr (sizeof(UnderlyingType) == 4) {
208  writeU32At(index, static_cast<uint32_t>(integral_value));
209  } else {
210  static_assert(sizeof(UnderlyingType) <= 4, "Enum underlying type too large (max 32-bit supported)");
211  }
212  }
213 
214  /**
215  * @brief Reads a 16-bit unsigned integer in big-endian format from a specific index
216  *
217  * Reads 2 bytes starting at the specified index and interprets them as a
218  * 16-bit unsigned integer in big-endian (network) byte order.
219  *
220  * @param index Starting index to read from (must be < size() - 1)
221  * @return uint16_t The 16-bit value read from the array
222  * @throws std::out_of_range if index + 1 >= size()
223  */
224  uint16_t readU16BE(size_t index) const {
225  if (index + 1 >= this->size()) {
226  throw std::out_of_range("Index out of range for readU16BE");
227  }
228  return util::readU16BE(this->data(), index);
229  }
230 
231  /**
232  * @brief Reads a 32-bit unsigned integer in big-endian format from a specific index
233  *
234  * Reads 4 bytes starting at the specified index and interprets them as a
235  * 32-bit unsigned integer in big-endian (network) byte order.
236  *
237  * @param index Starting index to read from (must be < size() - 3)
238  * @return uint32_t The 32-bit value read from the array
239  * @throws std::out_of_range if index + 3 >= size()
240  */
241  uint32_t readU32BE(size_t index) const {
242  if (index + 3 >= this->size()) {
243  throw std::out_of_range("Index out of range for readU32BE");
244  }
245  return util::readU32BE(this->data(), index);
246  }
247 
248  /**
249  * @brief Reads an enum class value from a specific index
250  *
251  * Reads bytes starting at the specified index and interprets them as
252  * the underlying integral type of the enum in big-endian format.
253  *
254  * @tparam E Enum class type to read
255  * @param index Starting index to read from
256  * @return E The enum value read from the array
257  * @throws std::out_of_range if not enough bytes available
258  *
259  * @example
260  * enum class Status : uint16_t { OK = 0x0100, ERROR = 0x0200 };
261  * ByteArray arr = {0x01, 0x00};
262  * auto status = arr.readEnum<Status>(0); // Returns Status::OK
263  */
264  template<typename E>
265  E readEnum(size_t index) const {
266  static_assert(std::is_enum_v<E>, "readEnum requires an enum type");
267 
268  using UnderlyingType = std::underlying_type_t<E>;
269 
270  if constexpr (sizeof(UnderlyingType) == 1) {
271  if (index >= this->size()) {
272  throw std::out_of_range("Index out of range for readEnum");
273  }
274  return static_cast<E>((*this)[index]);
275  } else if constexpr (sizeof(UnderlyingType) == 2) {
276  return static_cast<E>(readU16BE(index));
277  } else if constexpr (sizeof(UnderlyingType) == 4) {
278  return static_cast<E>(readU32BE(index));
279  } else {
280  static_assert(sizeof(UnderlyingType) <= 4, "Enum underlying type too large (max 32-bit supported)");
281  }
282  }
283 };
284 
285 /**
286  * @brief Reference to raw array of bytes.
287  *
288  * A pair containing a pointer to a byte array and its size.
289  * Useful for passing byte array references without copying.
290  */
291 using ByteArrayRef = std::pair<const uint8_t *, size_t>;
292 
293 /**
294  * @brief Stream operator for ByteArray
295  *
296  * Prints each byte as a two-digit hex value separated by dots.
297  * Example: {0x01, 0x02, 0xFF} prints as "01.02.FF"
298  *
299  * @param os Output stream
300  * @param arr ByteArray to print
301  * @return std::ostream& Reference to the output stream
302  */
303 inline std::ostream &operator<<(std::ostream &os, const ByteArray &arr) {
304  std::ios_base::fmtflags flags(os.flags());
305 
306  for (size_t i = 0; i < arr.size(); ++i) {
307  if (i > 0) {
308  os << '.';
309  }
310  os << std::hex << std::uppercase << std::setw(2) << std::setfill('0')
311  << static_cast<unsigned int>(arr[i]);
312  }
313 
314  os.flags(flags);
315  return os;
316 }
317 
318 } // namespace doip
319 
320 #endif /* BYTEARRAY_H */
uint32_t readU32BE(const uint8_t *data, size_t index)
Reads a 32-bit unsigned integer in big-endian format from a byte array.
Definition: ByteArray.h:42
uint16_t readU16BE(const uint8_t *data, size_t index)
Reads a 16-bit unsigned integer in big-endian format from a byte array.
Definition: ByteArray.h:30
Definition: AnsiColors.h:3
std::pair< const uint8_t *, size_t > ByteArrayRef
Reference to raw array of bytes.
Definition: ByteArray.h:291
std::ostream & operator<<(std::ostream &os, const ByteArray &arr)
Stream operator for ByteArray.
Definition: ByteArray.h:303
A dynamic array of bytes with utility methods for network protocol handling.
Definition: ByteArray.h:60
ByteArray(const std::initializer_list< uint8_t > &init_list)
Definition: ByteArray.h:82
void writeEnum(E value)
Definition: ByteArray.h:165
ByteArray()=default
Default constructor - creates an empty ByteArray.
E readEnum(size_t index) const
Definition: ByteArray.h:265
uint16_t readU16BE(size_t index) const
Reads a 16-bit unsigned integer in big-endian format from a specific index.
Definition: ByteArray.h:224
ByteArray(const uint8_t *data, size_t size)
Constructs a ByteArray from a raw byte array.
Definition: ByteArray.h:72
void writeEnumAt(size_t index, E value)
Writes an enum class value at a specific index as its underlying integral type.
Definition: ByteArray.h:194
void writeU16At(size_t index, uint16_t value)
Writes a 16-bit unsigned integer in big-endian format at a specific index.
Definition: ByteArray.h:94
void writeU32BE(uint32_t value)
Appends a 32-bit unsigned integer in big-endian format to the end.
Definition: ByteArray.h:143
void writeU32At(size_t index, uint32_t value)
Writes a 32-bit unsigned integer in big-endian format at a specific index.
Definition: ByteArray.h:125
uint32_t readU32BE(size_t index) const
Reads a 32-bit unsigned integer in big-endian format from a specific index.
Definition: ByteArray.h:241
void writeU16BE(uint16_t value)
Appends a 16-bit unsigned integer in big-endian format to the end.
Definition: ByteArray.h:110