ESPHome 2025.12.0-dev
Loading...
Searching...
No Matches
api_frame_helper_noise.cpp
Go to the documentation of this file.
2#ifdef USE_API
3#ifdef USE_API_NOISE
4#include "api_connection.h" // For ClientInfo struct
6#include "esphome/core/hal.h"
8#include "esphome/core/log.h"
9#include "proto.h"
10#include <cstring>
11#include <cinttypes>
12
13#ifdef USE_ESP8266
14#include <pgmspace.h>
15#endif
16
17namespace esphome::api {
18
19static const char *const TAG = "api.noise";
20#ifdef USE_ESP8266
21static const char PROLOGUE_INIT[] PROGMEM = "NoiseAPIInit";
22#else
23static const char *const PROLOGUE_INIT = "NoiseAPIInit";
24#endif
25static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit")
26
27#define HELPER_LOG(msg, ...) \
28 ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__)
29
30#ifdef HELPER_LOG_PACKETS
31#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())
32#define LOG_PACKET_SENDING(data, len) ESP_LOGVV(TAG, "Sending raw: %s", format_hex_pretty(data, len).c_str())
33#else
34#define LOG_PACKET_RECEIVED(buffer) ((void) 0)
35#define LOG_PACKET_SENDING(data, len) ((void) 0)
36#endif
37
39const LogString *noise_err_to_logstr(int err) {
40 if (err == NOISE_ERROR_NO_MEMORY)
41 return LOG_STR("NO_MEMORY");
42 if (err == NOISE_ERROR_UNKNOWN_ID)
43 return LOG_STR("UNKNOWN_ID");
44 if (err == NOISE_ERROR_UNKNOWN_NAME)
45 return LOG_STR("UNKNOWN_NAME");
46 if (err == NOISE_ERROR_MAC_FAILURE)
47 return LOG_STR("MAC_FAILURE");
48 if (err == NOISE_ERROR_NOT_APPLICABLE)
49 return LOG_STR("NOT_APPLICABLE");
50 if (err == NOISE_ERROR_SYSTEM)
51 return LOG_STR("SYSTEM");
52 if (err == NOISE_ERROR_REMOTE_KEY_REQUIRED)
53 return LOG_STR("REMOTE_KEY_REQUIRED");
54 if (err == NOISE_ERROR_LOCAL_KEY_REQUIRED)
55 return LOG_STR("LOCAL_KEY_REQUIRED");
56 if (err == NOISE_ERROR_PSK_REQUIRED)
57 return LOG_STR("PSK_REQUIRED");
58 if (err == NOISE_ERROR_INVALID_LENGTH)
59 return LOG_STR("INVALID_LENGTH");
60 if (err == NOISE_ERROR_INVALID_PARAM)
61 return LOG_STR("INVALID_PARAM");
62 if (err == NOISE_ERROR_INVALID_STATE)
63 return LOG_STR("INVALID_STATE");
64 if (err == NOISE_ERROR_INVALID_NONCE)
65 return LOG_STR("INVALID_NONCE");
66 if (err == NOISE_ERROR_INVALID_PRIVATE_KEY)
67 return LOG_STR("INVALID_PRIVATE_KEY");
68 if (err == NOISE_ERROR_INVALID_PUBLIC_KEY)
69 return LOG_STR("INVALID_PUBLIC_KEY");
70 if (err == NOISE_ERROR_INVALID_FORMAT)
71 return LOG_STR("INVALID_FORMAT");
72 if (err == NOISE_ERROR_INVALID_SIGNATURE)
73 return LOG_STR("INVALID_SIGNATURE");
74 return LOG_STR("UNKNOWN");
75}
76
79 APIError err = init_common_();
80 if (err != APIError::OK) {
81 return err;
82 }
83
84 // init prologue
85 size_t old_size = prologue_.size();
86 prologue_.resize(old_size + PROLOGUE_INIT_LEN);
87#ifdef USE_ESP8266
88 memcpy_P(prologue_.data() + old_size, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
89#else
90 std::memcpy(prologue_.data() + old_size, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
91#endif
92
94 return APIError::OK;
95}
96// Helper for handling handshake frame errors
98 if (aerr == APIError::BAD_INDICATOR) {
99 send_explicit_handshake_reject_(LOG_STR("Bad indicator byte"));
100 } else if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) {
101 send_explicit_handshake_reject_(LOG_STR("Bad handshake packet len"));
102 }
103 return aerr;
104}
105
106// Helper for handling noise library errors
107APIError APINoiseFrameHelper::handle_noise_error_(int err, const LogString *func_name, APIError api_err) {
108 if (err != 0) {
110 HELPER_LOG("%s failed: %s", LOG_STR_ARG(func_name), LOG_STR_ARG(noise_err_to_logstr(err)));
111 return api_err;
112 }
113 return APIError::OK;
114}
115
118 // During handshake phase, process as many actions as possible until we can't progress
119 // socket_->ready() stays true until next main loop, but state_action() will return
120 // WOULD_BLOCK when no more data is available to read
121 while (state_ != State::DATA && this->socket_->ready()) {
122 APIError err = state_action_();
123 if (err == APIError::WOULD_BLOCK) {
124 break;
125 }
126 if (err != APIError::OK) {
127 return err;
128 }
129 }
130
131 // Use base class implementation for buffer sending
132 return APIFrameHelper::loop();
133}
134
145 // read header
146 if (rx_header_buf_len_ < 3) {
147 // no header information yet
148 uint8_t to_read = 3 - rx_header_buf_len_;
149 ssize_t received = this->socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read);
150 APIError err = handle_socket_read_result_(received);
151 if (err != APIError::OK) {
152 return err;
153 }
154 rx_header_buf_len_ += static_cast<uint8_t>(received);
155 if (static_cast<uint8_t>(received) != to_read) {
156 // not a full read
158 }
159
160 if (rx_header_buf_[0] != 0x01) {
162 HELPER_LOG("Bad indicator byte %u", rx_header_buf_[0]);
164 }
165 // header reading done
166 }
167
168 // read body
169 uint16_t msg_size = (((uint16_t) rx_header_buf_[1]) << 8) | rx_header_buf_[2];
170
171 // Check against size limits to prevent OOM: MAX_HANDSHAKE_SIZE for handshake, MAX_MESSAGE_SIZE for data
172 uint16_t limit = (state_ == State::DATA) ? MAX_MESSAGE_SIZE : MAX_HANDSHAKE_SIZE;
173 if (msg_size > limit) {
175 HELPER_LOG("Bad packet: message size %u exceeds maximum %u", msg_size, limit);
177 }
178
179 // Reserve space for body
180 if (this->rx_buf_.size() != msg_size) {
181 this->rx_buf_.resize(msg_size);
182 }
183
184 if (rx_buf_len_ < msg_size) {
185 // more data to read
186 uint16_t to_read = msg_size - rx_buf_len_;
187 ssize_t received = this->socket_->read(&rx_buf_[rx_buf_len_], to_read);
188 APIError err = handle_socket_read_result_(received);
189 if (err != APIError::OK) {
190 return err;
191 }
192 rx_buf_len_ += static_cast<uint16_t>(received);
193 if (static_cast<uint16_t>(received) != to_read) {
194 // not all read
196 }
197 }
198
199 LOG_PACKET_RECEIVED(this->rx_buf_);
200
201 // Clear state for next frame (rx_buf_ still contains data for caller)
202 this->rx_buf_len_ = 0;
203 this->rx_header_buf_len_ = 0;
204
205 return APIError::OK;
206}
207
218 int err;
219 APIError aerr;
220 if (state_ == State::INITIALIZE) {
221 HELPER_LOG("Bad state for method: %d", (int) state_);
222 return APIError::BAD_STATE;
223 }
225 // waiting for client hello
226 aerr = this->try_read_frame_();
227 if (aerr != APIError::OK) {
229 }
230 // ignore contents, may be used in future for flags
231 // Resize for: existing prologue + 2 size bytes + frame data
232 size_t old_size = this->prologue_.size();
233 this->prologue_.resize(old_size + 2 + this->rx_buf_.size());
234 this->prologue_[old_size] = (uint8_t) (this->rx_buf_.size() >> 8);
235 this->prologue_[old_size + 1] = (uint8_t) this->rx_buf_.size();
236 std::memcpy(this->prologue_.data() + old_size + 2, this->rx_buf_.data(), this->rx_buf_.size());
237
239 }
241 // send server hello
242 const std::string &name = App.get_name();
243 const std::string &mac = get_mac_address();
244
245 // Calculate positions and sizes
246 size_t name_len = name.size() + 1; // including null terminator
247 size_t mac_len = mac.size() + 1; // including null terminator
248 size_t name_offset = 1;
249 size_t mac_offset = name_offset + name_len;
250 size_t total_size = 1 + name_len + mac_len;
251
252 auto msg = std::make_unique<uint8_t[]>(total_size);
253
254 // chosen proto
255 msg[0] = 0x01;
256
257 // node name, terminated by null byte
258 std::memcpy(msg.get() + name_offset, name.c_str(), name_len);
259 // node mac, terminated by null byte
260 std::memcpy(msg.get() + mac_offset, mac.c_str(), mac_len);
261
262 aerr = write_frame_(msg.get(), total_size);
263 if (aerr != APIError::OK)
264 return aerr;
265
266 // start handshake
267 aerr = init_handshake_();
268 if (aerr != APIError::OK)
269 return aerr;
270
272 }
273 if (state_ == State::HANDSHAKE) {
274 int action = noise_handshakestate_get_action(handshake_);
275 if (action == NOISE_ACTION_READ_MESSAGE) {
276 // waiting for handshake msg
277 aerr = this->try_read_frame_();
278 if (aerr != APIError::OK) {
280 }
281
282 if (this->rx_buf_.empty()) {
283 send_explicit_handshake_reject_(LOG_STR("Empty handshake message"));
285 } else if (this->rx_buf_[0] != 0x00) {
286 HELPER_LOG("Bad handshake error byte: %u", this->rx_buf_[0]);
287 send_explicit_handshake_reject_(LOG_STR("Bad handshake error byte"));
289 }
290
291 NoiseBuffer mbuf;
292 noise_buffer_init(mbuf);
293 noise_buffer_set_input(mbuf, this->rx_buf_.data() + 1, this->rx_buf_.size() - 1);
294 err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr);
295 if (err != 0) {
296 // Special handling for MAC failure
297 send_explicit_handshake_reject_(err == NOISE_ERROR_MAC_FAILURE ? LOG_STR("Handshake MAC failure")
298 : LOG_STR("Handshake error"));
299 return handle_noise_error_(err, LOG_STR("noise_handshakestate_read_message"),
301 }
302
304 if (aerr != APIError::OK)
305 return aerr;
306 } else if (action == NOISE_ACTION_WRITE_MESSAGE) {
307 uint8_t buffer[65];
308 NoiseBuffer mbuf;
309 noise_buffer_init(mbuf);
310 noise_buffer_set_output(mbuf, buffer + 1, sizeof(buffer) - 1);
311
312 err = noise_handshakestate_write_message(handshake_, &mbuf, nullptr);
313 APIError aerr_write = handle_noise_error_(err, LOG_STR("noise_handshakestate_write_message"),
315 if (aerr_write != APIError::OK)
316 return aerr_write;
317 buffer[0] = 0x00; // success
318
319 aerr = write_frame_(buffer, mbuf.size + 1);
320 if (aerr != APIError::OK)
321 return aerr;
323 if (aerr != APIError::OK)
324 return aerr;
325 } else {
326 // bad state for action
328 HELPER_LOG("Bad action for handshake: %d", action);
330 }
331 }
333 return APIError::BAD_STATE;
334 }
335 return APIError::OK;
336}
338#ifdef USE_STORE_LOG_STR_IN_FLASH
339 // On ESP8266 with flash strings, we need to use PROGMEM-aware functions
340 size_t reason_len = strlen_P(reinterpret_cast<PGM_P>(reason));
341 size_t data_size = reason_len + 1;
342 auto data = std::make_unique<uint8_t[]>(data_size);
343 data[0] = 0x01; // failure
344
345 // Copy error message from PROGMEM
346 if (reason_len > 0) {
347 memcpy_P(data.get() + 1, reinterpret_cast<PGM_P>(reason), reason_len);
348 }
349#else
350 // Normal memory access
351 const char *reason_str = LOG_STR_ARG(reason);
352 size_t reason_len = strlen(reason_str);
353 size_t data_size = reason_len + 1;
354 auto data = std::make_unique<uint8_t[]>(data_size);
355 data[0] = 0x01; // failure
356
357 // Copy error message in bulk
358 if (reason_len > 0) {
359 std::memcpy(data.get() + 1, reason_str, reason_len);
360 }
361#endif
362
363 // temporarily remove failed state
364 auto orig_state = state_;
366 write_frame_(data.get(), data_size);
367 state_ = orig_state;
368}
370 APIError aerr = this->state_action_();
371 if (aerr != APIError::OK) {
372 return aerr;
373 }
374
375 if (this->state_ != State::DATA) {
377 }
378
379 aerr = this->try_read_frame_();
380 if (aerr != APIError::OK)
381 return aerr;
382
383 NoiseBuffer mbuf;
384 noise_buffer_init(mbuf);
385 noise_buffer_set_inout(mbuf, this->rx_buf_.data(), this->rx_buf_.size(), this->rx_buf_.size());
386 int err = noise_cipherstate_decrypt(this->recv_cipher_, &mbuf);
387 APIError decrypt_err =
388 handle_noise_error_(err, LOG_STR("noise_cipherstate_decrypt"), APIError::CIPHERSTATE_DECRYPT_FAILED);
389 if (decrypt_err != APIError::OK) {
390 return decrypt_err;
391 }
392
393 uint16_t msg_size = mbuf.size;
394 uint8_t *msg_data = this->rx_buf_.data();
395 if (msg_size < 4) {
396 this->state_ = State::FAILED;
397 HELPER_LOG("Bad data packet: size %d too short", msg_size);
399 }
400
401 uint16_t type = (((uint16_t) msg_data[0]) << 8) | msg_data[1];
402 uint16_t data_len = (((uint16_t) msg_data[2]) << 8) | msg_data[3];
403 if (data_len > msg_size - 4) {
404 this->state_ = State::FAILED;
405 HELPER_LOG("Bad data packet: data_len %u greater than msg_size %u", data_len, msg_size);
407 }
408
409 buffer->container = std::move(this->rx_buf_);
410 buffer->data_offset = 4;
411 buffer->data_len = data_len;
412 buffer->type = type;
413 return APIError::OK;
414}
416 // Resize to include MAC space (required for Noise encryption)
417 buffer.get_buffer()->resize(buffer.get_buffer()->size() + frame_footer_size_);
418 PacketInfo packet{type, 0,
419 static_cast<uint16_t>(buffer.get_buffer()->size() - frame_header_padding_ - frame_footer_size_)};
420 return write_protobuf_packets(buffer, std::span<const PacketInfo>(&packet, 1));
421}
422
423APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) {
424 APIError aerr = state_action_();
425 if (aerr != APIError::OK) {
426 return aerr;
427 }
428
429 if (state_ != State::DATA) {
431 }
432
433 if (packets.empty()) {
434 return APIError::OK;
435 }
436
437 uint8_t *buffer_data = buffer.get_buffer()->data();
438
439 this->reusable_iovs_.clear();
440 this->reusable_iovs_.reserve(packets.size());
441 uint16_t total_write_len = 0;
442
443 // We need to encrypt each packet in place
444 for (const auto &packet : packets) {
445 // The buffer already has padding at offset
446 uint8_t *buf_start = buffer_data + packet.offset;
447
448 // Write noise header
449 buf_start[0] = 0x01; // indicator
450 // buf_start[1], buf_start[2] to be set after encryption
451
452 // Write message header (to be encrypted)
453 const uint8_t msg_offset = 3;
454 buf_start[msg_offset] = static_cast<uint8_t>(packet.message_type >> 8); // type high byte
455 buf_start[msg_offset + 1] = static_cast<uint8_t>(packet.message_type); // type low byte
456 buf_start[msg_offset + 2] = static_cast<uint8_t>(packet.payload_size >> 8); // data_len high byte
457 buf_start[msg_offset + 3] = static_cast<uint8_t>(packet.payload_size); // data_len low byte
458 // payload data is already in the buffer starting at offset + 7
459
460 // Make sure we have space for MAC
461 // The buffer should already have been sized appropriately
462
463 // Encrypt the message in place
464 NoiseBuffer mbuf;
465 noise_buffer_init(mbuf);
466 noise_buffer_set_inout(mbuf, buf_start + msg_offset, 4 + packet.payload_size,
467 4 + packet.payload_size + frame_footer_size_);
468
469 int err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
470 APIError aerr =
471 handle_noise_error_(err, LOG_STR("noise_cipherstate_encrypt"), APIError::CIPHERSTATE_ENCRYPT_FAILED);
472 if (aerr != APIError::OK)
473 return aerr;
474
475 // Fill in the encrypted size
476 buf_start[1] = static_cast<uint8_t>(mbuf.size >> 8);
477 buf_start[2] = static_cast<uint8_t>(mbuf.size);
478
479 // Add iovec for this encrypted packet
480 size_t packet_len = static_cast<size_t>(3 + mbuf.size); // indicator + size + encrypted data
481 this->reusable_iovs_.push_back({buf_start, packet_len});
482 total_write_len += packet_len;
483 }
484
485 // Send all encrypted packets in one writev call
486 return this->write_raw_(this->reusable_iovs_.data(), this->reusable_iovs_.size(), total_write_len);
487}
488
489APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, uint16_t len) {
490 uint8_t header[3];
491 header[0] = 0x01; // indicator
492 header[1] = (uint8_t) (len >> 8);
493 header[2] = (uint8_t) len;
494
495 struct iovec iov[2];
496 iov[0].iov_base = header;
497 iov[0].iov_len = 3;
498 if (len == 0) {
499 return this->write_raw_(iov, 1, 3); // Just header
500 }
501 iov[1].iov_base = const_cast<uint8_t *>(data);
502 iov[1].iov_len = len;
503
504 return this->write_raw_(iov, 2, 3 + len); // Header + data
505}
506
512 int err;
513 memset(&nid_, 0, sizeof(nid_));
514 // const char *proto = "Noise_NNpsk0_25519_ChaChaPoly_SHA256";
515 // err = noise_protocol_name_to_id(&nid_, proto, strlen(proto));
516 nid_.pattern_id = NOISE_PATTERN_NN;
517 nid_.cipher_id = NOISE_CIPHER_CHACHAPOLY;
518 nid_.dh_id = NOISE_DH_CURVE25519;
519 nid_.prefix_id = NOISE_PREFIX_STANDARD;
520 nid_.hybrid_id = NOISE_DH_NONE;
521 nid_.hash_id = NOISE_HASH_SHA256;
522 nid_.modifier_ids[0] = NOISE_MODIFIER_PSK0;
523
524 err = noise_handshakestate_new_by_id(&handshake_, &nid_, NOISE_ROLE_RESPONDER);
525 APIError aerr =
526 handle_noise_error_(err, LOG_STR("noise_handshakestate_new_by_id"), APIError::HANDSHAKESTATE_SETUP_FAILED);
527 if (aerr != APIError::OK)
528 return aerr;
529
530 const auto &psk = ctx_->get_psk();
531 err = noise_handshakestate_set_pre_shared_key(handshake_, psk.data(), psk.size());
532 aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_set_pre_shared_key"),
534 if (aerr != APIError::OK)
535 return aerr;
536
537 err = noise_handshakestate_set_prologue(handshake_, prologue_.data(), prologue_.size());
538 aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_set_prologue"), APIError::HANDSHAKESTATE_SETUP_FAILED);
539 if (aerr != APIError::OK)
540 return aerr;
541 // set_prologue copies it into handshakestate, so we can get rid of it now
542 prologue_ = {};
543
544 err = noise_handshakestate_start(handshake_);
545 aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_start"), APIError::HANDSHAKESTATE_SETUP_FAILED);
546 if (aerr != APIError::OK)
547 return aerr;
548 return APIError::OK;
549}
550
552 assert(state_ == State::HANDSHAKE);
553
554 int action = noise_handshakestate_get_action(handshake_);
555 if (action == NOISE_ACTION_READ_MESSAGE || action == NOISE_ACTION_WRITE_MESSAGE)
556 return APIError::OK;
557 if (action != NOISE_ACTION_SPLIT) {
559 HELPER_LOG("Bad action for handshake: %d", action);
561 }
562 int err = noise_handshakestate_split(handshake_, &send_cipher_, &recv_cipher_);
563 APIError aerr =
564 handle_noise_error_(err, LOG_STR("noise_handshakestate_split"), APIError::HANDSHAKESTATE_SPLIT_FAILED);
565 if (aerr != APIError::OK)
566 return aerr;
567
568 frame_footer_size_ = noise_cipherstate_get_mac_length(send_cipher_);
569
570 HELPER_LOG("Handshake complete!");
571 noise_handshakestate_free(handshake_);
572 handshake_ = nullptr;
574 return APIError::OK;
575}
576
578 if (handshake_ != nullptr) {
579 noise_handshakestate_free(handshake_);
580 handshake_ = nullptr;
581 }
582 if (send_cipher_ != nullptr) {
583 noise_cipherstate_free(send_cipher_);
584 send_cipher_ = nullptr;
585 }
586 if (recv_cipher_ != nullptr) {
587 noise_cipherstate_free(recv_cipher_);
588 recv_cipher_ = nullptr;
589 }
590}
591
592extern "C" {
593// declare how noise generates random bytes (here with a good HWRNG based on the RF system)
594void noise_rand_bytes(void *output, size_t len) {
595 if (!esphome::random_bytes(reinterpret_cast<uint8_t *>(output), len)) {
596 ESP_LOGE(TAG, "Acquiring random bytes failed; rebooting");
597 arch_restart();
598 }
599}
600}
601
602} // namespace esphome::api
603#endif // USE_API_NOISE
604#endif // USE_API
const std::string & get_name() const
Get the name of this Application set by pre_setup().
APIError handle_socket_read_result_(ssize_t received)
std::vector< uint8_t > rx_buf_
std::vector< struct iovec > reusable_iovs_
APIError write_raw_(const struct iovec *iov, int iovcnt, uint16_t total_write_len)
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override
APIError read_packet(ReadPacketBuffer *buffer) override
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span< const PacketInfo > packets) override
APIError handle_noise_error_(int err, const LogString *func_name, APIError api_err)
APIError state_action_()
To be called from read/write methods.
APIError try_read_frame_()
Read a packet into the rx_buf_.
APIError loop() override
Run through handshake messages (if in that phase)
APIError handle_handshake_frame_error_(APIError aerr)
void send_explicit_handshake_reject_(const LogString *reason)
APIError write_frame_(const uint8_t *data, uint16_t len)
std::shared_ptr< APINoiseContext > ctx_
APIError init() override
Initialize the frame helper, returns OK if successful.
APIError init_handshake_()
Initiate the data structures for the handshake.
std::vector< uint8_t > * get_buffer() const
Definition proto.h:338
bool ready() const
Check if socket has data ready to read For loop-monitored sockets, checks with the Application's sele...
Definition socket.cpp:14
virtual ssize_t read(void *buf, size_t len)=0
uint16_t type
__int64 ssize_t
Definition httplib.h:178
void noise_rand_bytes(void *output, size_t len)
const LogString * noise_err_to_logstr(int err)
Convert a noise error code to a readable error.
bool random_bytes(uint8_t *data, size_t len)
Generate len number of random bytes.
Definition helpers.cpp:18
std::string size_t len
Definition helpers.h:500
std::string get_mac_address()
Get the device MAC address as a string, in lowercase hex notation.
Definition helpers.cpp:632
void arch_restart()
Definition core.cpp:34
Application App
Global storage of Application pointer - only one Application can exist.
std::vector< uint8_t > container
void * iov_base
Definition headers.h:101
size_t iov_len
Definition headers.h:102