ESPHome 2025.10.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, ...) ESP_LOGVV(TAG, "%s: " msg, this->client_info_->get_combined_info().c_str(), ##__VA_ARGS__)
28
29#ifdef HELPER_LOG_PACKETS
30#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())
31#define LOG_PACKET_SENDING(data, len) ESP_LOGVV(TAG, "Sending raw: %s", format_hex_pretty(data, len).c_str())
32#else
33#define LOG_PACKET_RECEIVED(buffer) ((void) 0)
34#define LOG_PACKET_SENDING(data, len) ((void) 0)
35#endif
36
38const LogString *noise_err_to_logstr(int err) {
39 if (err == NOISE_ERROR_NO_MEMORY)
40 return LOG_STR("NO_MEMORY");
41 if (err == NOISE_ERROR_UNKNOWN_ID)
42 return LOG_STR("UNKNOWN_ID");
43 if (err == NOISE_ERROR_UNKNOWN_NAME)
44 return LOG_STR("UNKNOWN_NAME");
45 if (err == NOISE_ERROR_MAC_FAILURE)
46 return LOG_STR("MAC_FAILURE");
47 if (err == NOISE_ERROR_NOT_APPLICABLE)
48 return LOG_STR("NOT_APPLICABLE");
49 if (err == NOISE_ERROR_SYSTEM)
50 return LOG_STR("SYSTEM");
51 if (err == NOISE_ERROR_REMOTE_KEY_REQUIRED)
52 return LOG_STR("REMOTE_KEY_REQUIRED");
53 if (err == NOISE_ERROR_LOCAL_KEY_REQUIRED)
54 return LOG_STR("LOCAL_KEY_REQUIRED");
55 if (err == NOISE_ERROR_PSK_REQUIRED)
56 return LOG_STR("PSK_REQUIRED");
57 if (err == NOISE_ERROR_INVALID_LENGTH)
58 return LOG_STR("INVALID_LENGTH");
59 if (err == NOISE_ERROR_INVALID_PARAM)
60 return LOG_STR("INVALID_PARAM");
61 if (err == NOISE_ERROR_INVALID_STATE)
62 return LOG_STR("INVALID_STATE");
63 if (err == NOISE_ERROR_INVALID_NONCE)
64 return LOG_STR("INVALID_NONCE");
65 if (err == NOISE_ERROR_INVALID_PRIVATE_KEY)
66 return LOG_STR("INVALID_PRIVATE_KEY");
67 if (err == NOISE_ERROR_INVALID_PUBLIC_KEY)
68 return LOG_STR("INVALID_PUBLIC_KEY");
69 if (err == NOISE_ERROR_INVALID_FORMAT)
70 return LOG_STR("INVALID_FORMAT");
71 if (err == NOISE_ERROR_INVALID_SIGNATURE)
72 return LOG_STR("INVALID_SIGNATURE");
73 return LOG_STR("UNKNOWN");
74}
75
78 APIError err = init_common_();
79 if (err != APIError::OK) {
80 return err;
81 }
82
83 // init prologue
84 size_t old_size = prologue_.size();
85 prologue_.resize(old_size + PROLOGUE_INIT_LEN);
86#ifdef USE_ESP8266
87 memcpy_P(prologue_.data() + old_size, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
88#else
89 std::memcpy(prologue_.data() + old_size, PROLOGUE_INIT, PROLOGUE_INIT_LEN);
90#endif
91
93 return APIError::OK;
94}
95// Helper for handling handshake frame errors
97 if (aerr == APIError::BAD_INDICATOR) {
98 send_explicit_handshake_reject_(LOG_STR("Bad indicator byte"));
99 } else if (aerr == APIError::BAD_HANDSHAKE_PACKET_LEN) {
100 send_explicit_handshake_reject_(LOG_STR("Bad handshake packet len"));
101 }
102 return aerr;
103}
104
105// Helper for handling noise library errors
106APIError APINoiseFrameHelper::handle_noise_error_(int err, const LogString *func_name, APIError api_err) {
107 if (err != 0) {
109 HELPER_LOG("%s failed: %s", LOG_STR_ARG(func_name), LOG_STR_ARG(noise_err_to_logstr(err)));
110 return api_err;
111 }
112 return APIError::OK;
113}
114
117 // During handshake phase, process as many actions as possible until we can't progress
118 // socket_->ready() stays true until next main loop, but state_action() will return
119 // WOULD_BLOCK when no more data is available to read
120 while (state_ != State::DATA && this->socket_->ready()) {
121 APIError err = state_action_();
122 if (err == APIError::WOULD_BLOCK) {
123 break;
124 }
125 if (err != APIError::OK) {
126 return err;
127 }
128 }
129
130 // Use base class implementation for buffer sending
131 return APIFrameHelper::loop();
132}
133
148APIError APINoiseFrameHelper::try_read_frame_(std::vector<uint8_t> *frame) {
149 if (frame == nullptr) {
150 HELPER_LOG("Bad argument for try_read_frame_");
151 return APIError::BAD_ARG;
152 }
153
154 // read header
155 if (rx_header_buf_len_ < 3) {
156 // no header information yet
157 uint8_t to_read = 3 - rx_header_buf_len_;
158 ssize_t received = this->socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read);
159 APIError err = handle_socket_read_result_(received);
160 if (err != APIError::OK) {
161 return err;
162 }
163 rx_header_buf_len_ += static_cast<uint8_t>(received);
164 if (static_cast<uint8_t>(received) != to_read) {
165 // not a full read
167 }
168
169 if (rx_header_buf_[0] != 0x01) {
171 HELPER_LOG("Bad indicator byte %u", rx_header_buf_[0]);
173 }
174 // header reading done
175 }
176
177 // read body
178 uint16_t msg_size = (((uint16_t) rx_header_buf_[1]) << 8) | rx_header_buf_[2];
179
180 if (state_ != State::DATA && msg_size > 128) {
181 // for handshake message only permit up to 128 bytes
183 HELPER_LOG("Bad packet len for handshake: %d", msg_size);
185 }
186
187 // reserve space for body
188 if (rx_buf_.size() != msg_size) {
189 rx_buf_.resize(msg_size);
190 }
191
192 if (rx_buf_len_ < msg_size) {
193 // more data to read
194 uint16_t to_read = msg_size - rx_buf_len_;
195 ssize_t received = this->socket_->read(&rx_buf_[rx_buf_len_], to_read);
196 APIError err = handle_socket_read_result_(received);
197 if (err != APIError::OK) {
198 return err;
199 }
200 rx_buf_len_ += static_cast<uint16_t>(received);
201 if (static_cast<uint16_t>(received) != to_read) {
202 // not all read
204 }
205 }
206
207 LOG_PACKET_RECEIVED(rx_buf_);
208 *frame = std::move(rx_buf_);
209 // consume msg
210 rx_buf_ = {};
211 rx_buf_len_ = 0;
213 return APIError::OK;
214}
215
226 int err;
227 APIError aerr;
228 if (state_ == State::INITIALIZE) {
229 HELPER_LOG("Bad state for method: %d", (int) state_);
230 return APIError::BAD_STATE;
231 }
233 // waiting for client hello
234 std::vector<uint8_t> frame;
235 aerr = try_read_frame_(&frame);
236 if (aerr != APIError::OK) {
238 }
239 // ignore contents, may be used in future for flags
240 // Resize for: existing prologue + 2 size bytes + frame data
241 size_t old_size = prologue_.size();
242 prologue_.resize(old_size + 2 + frame.size());
243 prologue_[old_size] = (uint8_t) (frame.size() >> 8);
244 prologue_[old_size + 1] = (uint8_t) frame.size();
245 std::memcpy(prologue_.data() + old_size + 2, frame.data(), frame.size());
246
248 }
250 // send server hello
251 const std::string &name = App.get_name();
252 const std::string &mac = get_mac_address();
253
254 std::vector<uint8_t> msg;
255 // Calculate positions and sizes
256 size_t name_len = name.size() + 1; // including null terminator
257 size_t mac_len = mac.size() + 1; // including null terminator
258 size_t name_offset = 1;
259 size_t mac_offset = name_offset + name_len;
260 size_t total_size = 1 + name_len + mac_len;
261
262 msg.resize(total_size);
263
264 // chosen proto
265 msg[0] = 0x01;
266
267 // node name, terminated by null byte
268 std::memcpy(msg.data() + name_offset, name.c_str(), name_len);
269 // node mac, terminated by null byte
270 std::memcpy(msg.data() + mac_offset, mac.c_str(), mac_len);
271
272 aerr = write_frame_(msg.data(), msg.size());
273 if (aerr != APIError::OK)
274 return aerr;
275
276 // start handshake
277 aerr = init_handshake_();
278 if (aerr != APIError::OK)
279 return aerr;
280
282 }
283 if (state_ == State::HANDSHAKE) {
284 int action = noise_handshakestate_get_action(handshake_);
285 if (action == NOISE_ACTION_READ_MESSAGE) {
286 // waiting for handshake msg
287 std::vector<uint8_t> frame;
288 aerr = try_read_frame_(&frame);
289 if (aerr != APIError::OK) {
291 }
292
293 if (frame.empty()) {
294 send_explicit_handshake_reject_(LOG_STR("Empty handshake message"));
296 } else if (frame[0] != 0x00) {
297 HELPER_LOG("Bad handshake error byte: %u", frame[0]);
298 send_explicit_handshake_reject_(LOG_STR("Bad handshake error byte"));
300 }
301
302 NoiseBuffer mbuf;
303 noise_buffer_init(mbuf);
304 noise_buffer_set_input(mbuf, frame.data() + 1, frame.size() - 1);
305 err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr);
306 if (err != 0) {
307 // Special handling for MAC failure
308 send_explicit_handshake_reject_(err == NOISE_ERROR_MAC_FAILURE ? LOG_STR("Handshake MAC failure")
309 : LOG_STR("Handshake error"));
310 return handle_noise_error_(err, LOG_STR("noise_handshakestate_read_message"),
312 }
313
315 if (aerr != APIError::OK)
316 return aerr;
317 } else if (action == NOISE_ACTION_WRITE_MESSAGE) {
318 uint8_t buffer[65];
319 NoiseBuffer mbuf;
320 noise_buffer_init(mbuf);
321 noise_buffer_set_output(mbuf, buffer + 1, sizeof(buffer) - 1);
322
323 err = noise_handshakestate_write_message(handshake_, &mbuf, nullptr);
324 APIError aerr_write = handle_noise_error_(err, LOG_STR("noise_handshakestate_write_message"),
326 if (aerr_write != APIError::OK)
327 return aerr_write;
328 buffer[0] = 0x00; // success
329
330 aerr = write_frame_(buffer, mbuf.size + 1);
331 if (aerr != APIError::OK)
332 return aerr;
334 if (aerr != APIError::OK)
335 return aerr;
336 } else {
337 // bad state for action
339 HELPER_LOG("Bad action for handshake: %d", action);
341 }
342 }
344 return APIError::BAD_STATE;
345 }
346 return APIError::OK;
347}
349#ifdef USE_STORE_LOG_STR_IN_FLASH
350 // On ESP8266 with flash strings, we need to use PROGMEM-aware functions
351 size_t reason_len = strlen_P(reinterpret_cast<PGM_P>(reason));
352 std::vector<uint8_t> data;
353 data.resize(reason_len + 1);
354 data[0] = 0x01; // failure
355
356 // Copy error message from PROGMEM
357 if (reason_len > 0) {
358 memcpy_P(data.data() + 1, reinterpret_cast<PGM_P>(reason), reason_len);
359 }
360#else
361 // Normal memory access
362 const char *reason_str = LOG_STR_ARG(reason);
363 size_t reason_len = strlen(reason_str);
364 std::vector<uint8_t> data;
365 data.resize(reason_len + 1);
366 data[0] = 0x01; // failure
367
368 // Copy error message in bulk
369 if (reason_len > 0) {
370 std::memcpy(data.data() + 1, reason_str, reason_len);
371 }
372#endif
373
374 // temporarily remove failed state
375 auto orig_state = state_;
377 write_frame_(data.data(), data.size());
378 state_ = orig_state;
379}
381 int err;
382 APIError aerr;
383 aerr = state_action_();
384 if (aerr != APIError::OK) {
385 return aerr;
386 }
387
388 if (state_ != State::DATA) {
390 }
391
392 std::vector<uint8_t> frame;
393 aerr = try_read_frame_(&frame);
394 if (aerr != APIError::OK)
395 return aerr;
396
397 NoiseBuffer mbuf;
398 noise_buffer_init(mbuf);
399 noise_buffer_set_inout(mbuf, frame.data(), frame.size(), frame.size());
400 err = noise_cipherstate_decrypt(recv_cipher_, &mbuf);
401 APIError decrypt_err =
402 handle_noise_error_(err, LOG_STR("noise_cipherstate_decrypt"), APIError::CIPHERSTATE_DECRYPT_FAILED);
403 if (decrypt_err != APIError::OK)
404 return decrypt_err;
405
406 uint16_t msg_size = mbuf.size;
407 uint8_t *msg_data = frame.data();
408 if (msg_size < 4) {
410 HELPER_LOG("Bad data packet: size %d too short", msg_size);
412 }
413
414 uint16_t type = (((uint16_t) msg_data[0]) << 8) | msg_data[1];
415 uint16_t data_len = (((uint16_t) msg_data[2]) << 8) | msg_data[3];
416 if (data_len > msg_size - 4) {
418 HELPER_LOG("Bad data packet: data_len %u greater than msg_size %u", data_len, msg_size);
420 }
421
422 buffer->container = std::move(frame);
423 buffer->data_offset = 4;
424 buffer->data_len = data_len;
425 buffer->type = type;
426 return APIError::OK;
427}
429 // Resize to include MAC space (required for Noise encryption)
430 buffer.get_buffer()->resize(buffer.get_buffer()->size() + frame_footer_size_);
431 PacketInfo packet{type, 0,
432 static_cast<uint16_t>(buffer.get_buffer()->size() - frame_header_padding_ - frame_footer_size_)};
433 return write_protobuf_packets(buffer, std::span<const PacketInfo>(&packet, 1));
434}
435
436APIError APINoiseFrameHelper::write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) {
437 APIError aerr = state_action_();
438 if (aerr != APIError::OK) {
439 return aerr;
440 }
441
442 if (state_ != State::DATA) {
444 }
445
446 if (packets.empty()) {
447 return APIError::OK;
448 }
449
450 std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
451 uint8_t *buffer_data = raw_buffer->data(); // Cache buffer pointer
452
453 this->reusable_iovs_.clear();
454 this->reusable_iovs_.reserve(packets.size());
455 uint16_t total_write_len = 0;
456
457 // We need to encrypt each packet in place
458 for (const auto &packet : packets) {
459 // The buffer already has padding at offset
460 uint8_t *buf_start = buffer_data + packet.offset;
461
462 // Write noise header
463 buf_start[0] = 0x01; // indicator
464 // buf_start[1], buf_start[2] to be set after encryption
465
466 // Write message header (to be encrypted)
467 const uint8_t msg_offset = 3;
468 buf_start[msg_offset] = static_cast<uint8_t>(packet.message_type >> 8); // type high byte
469 buf_start[msg_offset + 1] = static_cast<uint8_t>(packet.message_type); // type low byte
470 buf_start[msg_offset + 2] = static_cast<uint8_t>(packet.payload_size >> 8); // data_len high byte
471 buf_start[msg_offset + 3] = static_cast<uint8_t>(packet.payload_size); // data_len low byte
472 // payload data is already in the buffer starting at offset + 7
473
474 // Make sure we have space for MAC
475 // The buffer should already have been sized appropriately
476
477 // Encrypt the message in place
478 NoiseBuffer mbuf;
479 noise_buffer_init(mbuf);
480 noise_buffer_set_inout(mbuf, buf_start + msg_offset, 4 + packet.payload_size,
481 4 + packet.payload_size + frame_footer_size_);
482
483 int err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
484 APIError aerr =
485 handle_noise_error_(err, LOG_STR("noise_cipherstate_encrypt"), APIError::CIPHERSTATE_ENCRYPT_FAILED);
486 if (aerr != APIError::OK)
487 return aerr;
488
489 // Fill in the encrypted size
490 buf_start[1] = static_cast<uint8_t>(mbuf.size >> 8);
491 buf_start[2] = static_cast<uint8_t>(mbuf.size);
492
493 // Add iovec for this encrypted packet
494 size_t packet_len = static_cast<size_t>(3 + mbuf.size); // indicator + size + encrypted data
495 this->reusable_iovs_.push_back({buf_start, packet_len});
496 total_write_len += packet_len;
497 }
498
499 // Send all encrypted packets in one writev call
500 return this->write_raw_(this->reusable_iovs_.data(), this->reusable_iovs_.size(), total_write_len);
501}
502
503APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, uint16_t len) {
504 uint8_t header[3];
505 header[0] = 0x01; // indicator
506 header[1] = (uint8_t) (len >> 8);
507 header[2] = (uint8_t) len;
508
509 struct iovec iov[2];
510 iov[0].iov_base = header;
511 iov[0].iov_len = 3;
512 if (len == 0) {
513 return this->write_raw_(iov, 1, 3); // Just header
514 }
515 iov[1].iov_base = const_cast<uint8_t *>(data);
516 iov[1].iov_len = len;
517
518 return this->write_raw_(iov, 2, 3 + len); // Header + data
519}
520
526 int err;
527 memset(&nid_, 0, sizeof(nid_));
528 // const char *proto = "Noise_NNpsk0_25519_ChaChaPoly_SHA256";
529 // err = noise_protocol_name_to_id(&nid_, proto, strlen(proto));
530 nid_.pattern_id = NOISE_PATTERN_NN;
531 nid_.cipher_id = NOISE_CIPHER_CHACHAPOLY;
532 nid_.dh_id = NOISE_DH_CURVE25519;
533 nid_.prefix_id = NOISE_PREFIX_STANDARD;
534 nid_.hybrid_id = NOISE_DH_NONE;
535 nid_.hash_id = NOISE_HASH_SHA256;
536 nid_.modifier_ids[0] = NOISE_MODIFIER_PSK0;
537
538 err = noise_handshakestate_new_by_id(&handshake_, &nid_, NOISE_ROLE_RESPONDER);
539 APIError aerr =
540 handle_noise_error_(err, LOG_STR("noise_handshakestate_new_by_id"), APIError::HANDSHAKESTATE_SETUP_FAILED);
541 if (aerr != APIError::OK)
542 return aerr;
543
544 const auto &psk = ctx_->get_psk();
545 err = noise_handshakestate_set_pre_shared_key(handshake_, psk.data(), psk.size());
546 aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_set_pre_shared_key"),
548 if (aerr != APIError::OK)
549 return aerr;
550
551 err = noise_handshakestate_set_prologue(handshake_, prologue_.data(), prologue_.size());
552 aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_set_prologue"), APIError::HANDSHAKESTATE_SETUP_FAILED);
553 if (aerr != APIError::OK)
554 return aerr;
555 // set_prologue copies it into handshakestate, so we can get rid of it now
556 prologue_ = {};
557
558 err = noise_handshakestate_start(handshake_);
559 aerr = handle_noise_error_(err, LOG_STR("noise_handshakestate_start"), APIError::HANDSHAKESTATE_SETUP_FAILED);
560 if (aerr != APIError::OK)
561 return aerr;
562 return APIError::OK;
563}
564
566 assert(state_ == State::HANDSHAKE);
567
568 int action = noise_handshakestate_get_action(handshake_);
569 if (action == NOISE_ACTION_READ_MESSAGE || action == NOISE_ACTION_WRITE_MESSAGE)
570 return APIError::OK;
571 if (action != NOISE_ACTION_SPLIT) {
573 HELPER_LOG("Bad action for handshake: %d", action);
575 }
576 int err = noise_handshakestate_split(handshake_, &send_cipher_, &recv_cipher_);
577 APIError aerr =
578 handle_noise_error_(err, LOG_STR("noise_handshakestate_split"), APIError::HANDSHAKESTATE_SPLIT_FAILED);
579 if (aerr != APIError::OK)
580 return aerr;
581
582 frame_footer_size_ = noise_cipherstate_get_mac_length(send_cipher_);
583
584 HELPER_LOG("Handshake complete!");
585 noise_handshakestate_free(handshake_);
586 handshake_ = nullptr;
588 return APIError::OK;
589}
590
592 if (handshake_ != nullptr) {
593 noise_handshakestate_free(handshake_);
594 handshake_ = nullptr;
595 }
596 if (send_cipher_ != nullptr) {
597 noise_cipherstate_free(send_cipher_);
598 send_cipher_ = nullptr;
599 }
600 if (recv_cipher_ != nullptr) {
601 noise_cipherstate_free(recv_cipher_);
602 recv_cipher_ = nullptr;
603 }
604}
605
606extern "C" {
607// declare how noise generates random bytes (here with a good HWRNG based on the RF system)
608void noise_rand_bytes(void *output, size_t len) {
609 if (!esphome::random_bytes(reinterpret_cast<uint8_t *>(output), len)) {
610 ESP_LOGE(TAG, "Acquiring random bytes failed; rebooting");
611 arch_restart();
612 }
613}
614}
615
616} // namespace esphome::api
617#endif // USE_API_NOISE
618#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 try_read_frame_(std::vector< uint8_t > *frame)
Read a packet into the rx_buf_.
APIError handle_noise_error_(int err, const LogString *func_name, APIError api_err)
APIError state_action_()
To be called from read/write methods.
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:331
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:291
std::string get_mac_address()
Get the device MAC address as a string, in lowercase hex notation.
Definition helpers.cpp:590
void arch_restart()
Definition core.cpp:32
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