ESPHome 2025.5.0
Loading...
Searching...
No Matches
api_frame_helper.cpp
Go to the documentation of this file.
1#include "api_frame_helper.h"
2#ifdef USE_API
3#include "esphome/core/log.h"
4#include "esphome/core/hal.h"
7#include "proto.h"
8#include "api_pb2_size.h"
9#include <cstring>
10
11namespace esphome {
12namespace api {
13
14static const char *const TAG = "api.socket";
15
18 if (ret == -1) {
19 return errno == EWOULDBLOCK || errno == EAGAIN;
20 }
21 return ret == 0;
22}
23
24const char *api_error_to_str(APIError err) {
25 // not using switch to ensure compiler doesn't try to build a big table out of it
26 if (err == APIError::OK) {
27 return "OK";
28 } else if (err == APIError::WOULD_BLOCK) {
29 return "WOULD_BLOCK";
30 } else if (err == APIError::BAD_HANDSHAKE_PACKET_LEN) {
31 return "BAD_HANDSHAKE_PACKET_LEN";
32 } else if (err == APIError::BAD_INDICATOR) {
33 return "BAD_INDICATOR";
34 } else if (err == APIError::BAD_DATA_PACKET) {
35 return "BAD_DATA_PACKET";
36 } else if (err == APIError::TCP_NODELAY_FAILED) {
37 return "TCP_NODELAY_FAILED";
38 } else if (err == APIError::TCP_NONBLOCKING_FAILED) {
39 return "TCP_NONBLOCKING_FAILED";
40 } else if (err == APIError::CLOSE_FAILED) {
41 return "CLOSE_FAILED";
42 } else if (err == APIError::SHUTDOWN_FAILED) {
43 return "SHUTDOWN_FAILED";
44 } else if (err == APIError::BAD_STATE) {
45 return "BAD_STATE";
46 } else if (err == APIError::BAD_ARG) {
47 return "BAD_ARG";
48 } else if (err == APIError::SOCKET_READ_FAILED) {
49 return "SOCKET_READ_FAILED";
50 } else if (err == APIError::SOCKET_WRITE_FAILED) {
51 return "SOCKET_WRITE_FAILED";
52 } else if (err == APIError::HANDSHAKESTATE_READ_FAILED) {
53 return "HANDSHAKESTATE_READ_FAILED";
54 } else if (err == APIError::HANDSHAKESTATE_WRITE_FAILED) {
55 return "HANDSHAKESTATE_WRITE_FAILED";
56 } else if (err == APIError::HANDSHAKESTATE_BAD_STATE) {
57 return "HANDSHAKESTATE_BAD_STATE";
58 } else if (err == APIError::CIPHERSTATE_DECRYPT_FAILED) {
59 return "CIPHERSTATE_DECRYPT_FAILED";
60 } else if (err == APIError::CIPHERSTATE_ENCRYPT_FAILED) {
61 return "CIPHERSTATE_ENCRYPT_FAILED";
62 } else if (err == APIError::OUT_OF_MEMORY) {
63 return "OUT_OF_MEMORY";
64 } else if (err == APIError::HANDSHAKESTATE_SETUP_FAILED) {
65 return "HANDSHAKESTATE_SETUP_FAILED";
66 } else if (err == APIError::HANDSHAKESTATE_SPLIT_FAILED) {
67 return "HANDSHAKESTATE_SPLIT_FAILED";
68 } else if (err == APIError::BAD_HANDSHAKE_ERROR_BYTE) {
69 return "BAD_HANDSHAKE_ERROR_BYTE";
70 } else if (err == APIError::CONNECTION_CLOSED) {
71 return "CONNECTION_CLOSED";
72 }
73 return "UNKNOWN";
74}
75
76// Common implementation for writing raw data to socket
77template<typename StateEnum>
78APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket,
79 std::vector<uint8_t> &tx_buf, const std::string &info, StateEnum &state,
80 StateEnum failed_state) {
81 // This method writes data to socket or buffers it
82 // Returns APIError::OK if successful (or would block, but data has been buffered)
83 // Returns APIError::SOCKET_WRITE_FAILED if socket write failed, and sets state to failed_state
84
85 if (iovcnt == 0)
86 return APIError::OK; // Nothing to do, success
87
88 size_t total_write_len = 0;
89 for (int i = 0; i < iovcnt; i++) {
90#ifdef HELPER_LOG_PACKETS
91 ESP_LOGVV(TAG, "Sending raw: %s",
92 format_hex_pretty(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len).c_str());
93#endif
94 total_write_len += iov[i].iov_len;
95 }
96
97 if (!tx_buf.empty()) {
98 // try to empty tx_buf first
99 while (!tx_buf.empty()) {
100 ssize_t sent = socket->write(tx_buf.data(), tx_buf.size());
101 if (is_would_block(sent)) {
102 break;
103 } else if (sent == -1) {
104 ESP_LOGVV(TAG, "%s: Socket write failed with errno %d", info.c_str(), errno);
105 state = failed_state;
106 return APIError::SOCKET_WRITE_FAILED; // Socket write failed
107 }
108 // TODO: inefficient if multiple packets in txbuf
109 // replace with deque of buffers
110 tx_buf.erase(tx_buf.begin(), tx_buf.begin() + sent);
111 }
112 }
113
114 if (!tx_buf.empty()) {
115 // tx buf not empty, can't write now because then stream would be inconsistent
116 // Reserve space upfront to avoid multiple reallocations
117 tx_buf.reserve(tx_buf.size() + total_write_len);
118 for (int i = 0; i < iovcnt; i++) {
119 tx_buf.insert(tx_buf.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
120 reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
121 }
122 return APIError::OK; // Success, data buffered
123 }
124
125 ssize_t sent = socket->writev(iov, iovcnt);
126 if (is_would_block(sent)) {
127 // operation would block, add buffer to tx_buf
128 // Reserve space upfront to avoid multiple reallocations
129 tx_buf.reserve(tx_buf.size() + total_write_len);
130 for (int i = 0; i < iovcnt; i++) {
131 tx_buf.insert(tx_buf.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base),
132 reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
133 }
134 return APIError::OK; // Success, data buffered
135 } else if (sent == -1) {
136 // an error occurred
137 ESP_LOGVV(TAG, "%s: Socket write failed with errno %d", info.c_str(), errno);
138 state = failed_state;
139 return APIError::SOCKET_WRITE_FAILED; // Socket write failed
140 } else if ((size_t) sent != total_write_len) {
141 // partially sent, add end to tx_buf
142 size_t remaining = total_write_len - sent;
143 // Reserve space upfront to avoid multiple reallocations
144 tx_buf.reserve(tx_buf.size() + remaining);
145
146 size_t to_consume = sent;
147 for (int i = 0; i < iovcnt; i++) {
148 if (to_consume >= iov[i].iov_len) {
149 to_consume -= iov[i].iov_len;
150 } else {
151 tx_buf.insert(tx_buf.end(), reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_consume,
152 reinterpret_cast<uint8_t *>(iov[i].iov_base) + iov[i].iov_len);
153 to_consume = 0;
154 }
155 }
156 return APIError::OK; // Success, data buffered
157 }
158 return APIError::OK; // Success, all data sent
159}
160
161#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, info_.c_str(), ##__VA_ARGS__)
162// uncomment to log raw packets
163//#define HELPER_LOG_PACKETS
164
165#ifdef USE_API_NOISE
166static const char *const PROLOGUE_INIT = "NoiseAPIInit";
167
169std::string noise_err_to_str(int err) {
170 if (err == NOISE_ERROR_NO_MEMORY)
171 return "NO_MEMORY";
172 if (err == NOISE_ERROR_UNKNOWN_ID)
173 return "UNKNOWN_ID";
174 if (err == NOISE_ERROR_UNKNOWN_NAME)
175 return "UNKNOWN_NAME";
176 if (err == NOISE_ERROR_MAC_FAILURE)
177 return "MAC_FAILURE";
178 if (err == NOISE_ERROR_NOT_APPLICABLE)
179 return "NOT_APPLICABLE";
180 if (err == NOISE_ERROR_SYSTEM)
181 return "SYSTEM";
182 if (err == NOISE_ERROR_REMOTE_KEY_REQUIRED)
183 return "REMOTE_KEY_REQUIRED";
184 if (err == NOISE_ERROR_LOCAL_KEY_REQUIRED)
185 return "LOCAL_KEY_REQUIRED";
186 if (err == NOISE_ERROR_PSK_REQUIRED)
187 return "PSK_REQUIRED";
188 if (err == NOISE_ERROR_INVALID_LENGTH)
189 return "INVALID_LENGTH";
190 if (err == NOISE_ERROR_INVALID_PARAM)
191 return "INVALID_PARAM";
192 if (err == NOISE_ERROR_INVALID_STATE)
193 return "INVALID_STATE";
194 if (err == NOISE_ERROR_INVALID_NONCE)
195 return "INVALID_NONCE";
196 if (err == NOISE_ERROR_INVALID_PRIVATE_KEY)
197 return "INVALID_PRIVATE_KEY";
198 if (err == NOISE_ERROR_INVALID_PUBLIC_KEY)
199 return "INVALID_PUBLIC_KEY";
200 if (err == NOISE_ERROR_INVALID_FORMAT)
201 return "INVALID_FORMAT";
202 if (err == NOISE_ERROR_INVALID_SIGNATURE)
203 return "INVALID_SIGNATURE";
204 return to_string(err);
205}
206
209 if (state_ != State::INITIALIZE || socket_ == nullptr) {
210 HELPER_LOG("Bad state for init %d", (int) state_);
211 return APIError::BAD_STATE;
212 }
213 int err = socket_->setblocking(false);
214 if (err != 0) {
216 HELPER_LOG("Setting nonblocking failed with errno %d", errno);
218 }
219
220 int enable = 1;
221 err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
222 if (err != 0) {
224 HELPER_LOG("Setting nodelay failed with errno %d", errno);
226 }
227
228 // init prologue
229 prologue_.insert(prologue_.end(), PROLOGUE_INIT, PROLOGUE_INIT + strlen(PROLOGUE_INIT));
230
232 return APIError::OK;
233}
236 APIError err = state_action_();
237 if (err == APIError::WOULD_BLOCK)
238 return APIError::OK;
239 if (err != APIError::OK)
240 return err;
241 if (!tx_buf_.empty()) {
242 err = try_send_tx_buf_();
243 if (err != APIError::OK) {
244 return err;
245 }
246 }
247 return APIError::OK;
248}
249
265 if (frame == nullptr) {
266 HELPER_LOG("Bad argument for try_read_frame_");
267 return APIError::BAD_ARG;
268 }
269
270 // read header
271 if (rx_header_buf_len_ < 3) {
272 // no header information yet
273 size_t to_read = 3 - rx_header_buf_len_;
274 ssize_t received = socket_->read(&rx_header_buf_[rx_header_buf_len_], to_read);
275 if (received == -1) {
276 if (errno == EWOULDBLOCK || errno == EAGAIN) {
278 }
280 HELPER_LOG("Socket read failed with errno %d", errno);
282 } else if (received == 0) {
284 HELPER_LOG("Connection closed");
286 }
287 rx_header_buf_len_ += received;
288 if ((size_t) received != to_read) {
289 // not a full read
291 }
292
293 // header reading done
294 }
295
296 // read body
297 uint8_t indicator = rx_header_buf_[0];
298 if (indicator != 0x01) {
300 HELPER_LOG("Bad indicator byte %u", indicator);
302 }
303
304 uint16_t msg_size = (((uint16_t) rx_header_buf_[1]) << 8) | rx_header_buf_[2];
305
306 if (state_ != State::DATA && msg_size > 128) {
307 // for handshake message only permit up to 128 bytes
309 HELPER_LOG("Bad packet len for handshake: %d", msg_size);
311 }
312
313 // reserve space for body
314 if (rx_buf_.size() != msg_size) {
315 rx_buf_.resize(msg_size);
316 }
317
318 if (rx_buf_len_ < msg_size) {
319 // more data to read
320 size_t to_read = msg_size - rx_buf_len_;
321 ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read);
322 if (received == -1) {
323 if (errno == EWOULDBLOCK || errno == EAGAIN) {
325 }
327 HELPER_LOG("Socket read failed with errno %d", errno);
329 } else if (received == 0) {
331 HELPER_LOG("Connection closed");
333 }
334 rx_buf_len_ += received;
335 if ((size_t) received != to_read) {
336 // not all read
338 }
339 }
340
341 // uncomment for even more debugging
342#ifdef HELPER_LOG_PACKETS
343 ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(rx_buf_).c_str());
344#endif
345 frame->msg = std::move(rx_buf_);
346 // consume msg
347 rx_buf_ = {};
348 rx_buf_len_ = 0;
350 return APIError::OK;
351}
352
363 int err;
364 APIError aerr;
365 if (state_ == State::INITIALIZE) {
366 HELPER_LOG("Bad state for method: %d", (int) state_);
367 return APIError::BAD_STATE;
368 }
370 // waiting for client hello
371 ParsedFrame frame;
372 aerr = try_read_frame_(&frame);
373 if (aerr == APIError::BAD_INDICATOR) {
374 send_explicit_handshake_reject_("Bad indicator byte");
375 return aerr;
376 }
378 send_explicit_handshake_reject_("Bad handshake packet len");
379 return aerr;
380 }
381 if (aerr != APIError::OK)
382 return aerr;
383 // ignore contents, may be used in future for flags
384 prologue_.push_back((uint8_t) (frame.msg.size() >> 8));
385 prologue_.push_back((uint8_t) frame.msg.size());
386 prologue_.insert(prologue_.end(), frame.msg.begin(), frame.msg.end());
387
389 }
391 // send server hello
392 std::vector<uint8_t> msg;
393 // chosen proto
394 msg.push_back(0x01);
395
396 // node name, terminated by null byte
397 const std::string &name = App.get_name();
398 const uint8_t *name_ptr = reinterpret_cast<const uint8_t *>(name.c_str());
399 msg.insert(msg.end(), name_ptr, name_ptr + name.size() + 1);
400 // node mac, terminated by null byte
401 const std::string &mac = get_mac_address();
402 const uint8_t *mac_ptr = reinterpret_cast<const uint8_t *>(mac.c_str());
403 msg.insert(msg.end(), mac_ptr, mac_ptr + mac.size() + 1);
404
405 aerr = write_frame_(msg.data(), msg.size());
406 if (aerr != APIError::OK)
407 return aerr;
408
409 // start handshake
410 aerr = init_handshake_();
411 if (aerr != APIError::OK)
412 return aerr;
413
415 }
416 if (state_ == State::HANDSHAKE) {
417 int action = noise_handshakestate_get_action(handshake_);
418 if (action == NOISE_ACTION_READ_MESSAGE) {
419 // waiting for handshake msg
420 ParsedFrame frame;
421 aerr = try_read_frame_(&frame);
422 if (aerr == APIError::BAD_INDICATOR) {
423 send_explicit_handshake_reject_("Bad indicator byte");
424 return aerr;
425 }
427 send_explicit_handshake_reject_("Bad handshake packet len");
428 return aerr;
429 }
430 if (aerr != APIError::OK)
431 return aerr;
432
433 if (frame.msg.empty()) {
434 send_explicit_handshake_reject_("Empty handshake message");
436 } else if (frame.msg[0] != 0x00) {
437 HELPER_LOG("Bad handshake error byte: %u", frame.msg[0]);
438 send_explicit_handshake_reject_("Bad handshake error byte");
440 }
441
442 NoiseBuffer mbuf;
443 noise_buffer_init(mbuf);
444 noise_buffer_set_input(mbuf, frame.msg.data() + 1, frame.msg.size() - 1);
445 err = noise_handshakestate_read_message(handshake_, &mbuf, nullptr);
446 if (err != 0) {
448 HELPER_LOG("noise_handshakestate_read_message failed: %s", noise_err_to_str(err).c_str());
449 if (err == NOISE_ERROR_MAC_FAILURE) {
450 send_explicit_handshake_reject_("Handshake MAC failure");
451 } else {
452 send_explicit_handshake_reject_("Handshake error");
453 }
455 }
456
458 if (aerr != APIError::OK)
459 return aerr;
460 } else if (action == NOISE_ACTION_WRITE_MESSAGE) {
461 uint8_t buffer[65];
462 NoiseBuffer mbuf;
463 noise_buffer_init(mbuf);
464 noise_buffer_set_output(mbuf, buffer + 1, sizeof(buffer) - 1);
465
466 err = noise_handshakestate_write_message(handshake_, &mbuf, nullptr);
467 if (err != 0) {
469 HELPER_LOG("noise_handshakestate_write_message failed: %s", noise_err_to_str(err).c_str());
471 }
472 buffer[0] = 0x00; // success
473
474 aerr = write_frame_(buffer, mbuf.size + 1);
475 if (aerr != APIError::OK)
476 return aerr;
478 if (aerr != APIError::OK)
479 return aerr;
480 } else {
481 // bad state for action
483 HELPER_LOG("Bad action for handshake: %d", action);
485 }
486 }
488 return APIError::BAD_STATE;
489 }
490 return APIError::OK;
491}
493 std::vector<uint8_t> data;
494 data.resize(reason.length() + 1);
495 data[0] = 0x01; // failure
496
497 // Copy error message in bulk
498 if (!reason.empty()) {
499 std::memcpy(data.data() + 1, reason.c_str(), reason.length());
500 }
501
502 // temporarily remove failed state
503 auto orig_state = state_;
505 write_frame_(data.data(), data.size());
506 state_ = orig_state;
507}
508
510 int err;
511 APIError aerr;
512 aerr = state_action_();
513 if (aerr != APIError::OK) {
514 return aerr;
515 }
516
517 if (state_ != State::DATA) {
519 }
520
521 ParsedFrame frame;
522 aerr = try_read_frame_(&frame);
523 if (aerr != APIError::OK)
524 return aerr;
525
526 NoiseBuffer mbuf;
527 noise_buffer_init(mbuf);
528 noise_buffer_set_inout(mbuf, frame.msg.data(), frame.msg.size(), frame.msg.size());
529 err = noise_cipherstate_decrypt(recv_cipher_, &mbuf);
530 if (err != 0) {
532 HELPER_LOG("noise_cipherstate_decrypt failed: %s", noise_err_to_str(err).c_str());
534 }
535
536 size_t msg_size = mbuf.size;
537 uint8_t *msg_data = frame.msg.data();
538 if (msg_size < 4) {
540 HELPER_LOG("Bad data packet: size %d too short", msg_size);
542 }
543
544 // uint16_t type;
545 // uint16_t data_len;
546 // uint8_t *data;
547 // uint8_t *padding; zero or more bytes to fill up the rest of the packet
548 uint16_t type = (((uint16_t) msg_data[0]) << 8) | msg_data[1];
549 uint16_t data_len = (((uint16_t) msg_data[2]) << 8) | msg_data[3];
550 if (data_len > msg_size - 4) {
552 HELPER_LOG("Bad data packet: data_len %u greater than msg_size %u", data_len, msg_size);
554 }
555
556 buffer->container = std::move(frame.msg);
557 buffer->data_offset = 4;
558 buffer->data_len = data_len;
559 buffer->type = type;
560 return APIError::OK;
561}
564 int err;
565 APIError aerr;
566 aerr = state_action_();
567 if (aerr != APIError::OK) {
568 return aerr;
569 }
570
571 if (state_ != State::DATA) {
573 }
574
575 std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
576 // Message data starts after padding
577 size_t payload_len = raw_buffer->size() - frame_header_padding_;
578 size_t padding = 0;
579 size_t msg_len = 4 + payload_len + padding;
580
581 // We need to resize to include MAC space, but we already reserved it in create_buffer
582 raw_buffer->resize(raw_buffer->size() + frame_footer_size_);
583
584 // Write the noise header in the padded area
585 // Buffer layout:
586 // [0] - 0x01 indicator byte
587 // [1-2] - Size of encrypted payload (filled after encryption)
588 // [3-4] - Message type (encrypted)
589 // [5-6] - Payload length (encrypted)
590 // [7...] - Actual payload data (encrypted)
591 uint8_t *buf_start = raw_buffer->data();
592 buf_start[0] = 0x01; // indicator
593 // buf_start[1], buf_start[2] to be set later after encryption
594 const uint8_t msg_offset = 3;
595 buf_start[msg_offset + 0] = (uint8_t) (type >> 8); // type high byte
596 buf_start[msg_offset + 1] = (uint8_t) type; // type low byte
597 buf_start[msg_offset + 2] = (uint8_t) (payload_len >> 8); // data_len high byte
598 buf_start[msg_offset + 3] = (uint8_t) payload_len; // data_len low byte
599 // payload data is already in the buffer starting at position 7
600
601 NoiseBuffer mbuf;
602 noise_buffer_init(mbuf);
603 // The capacity parameter should be msg_len + frame_footer_size_ (MAC length) to allow space for encryption
604 noise_buffer_set_inout(mbuf, buf_start + msg_offset, msg_len, msg_len + frame_footer_size_);
605 err = noise_cipherstate_encrypt(send_cipher_, &mbuf);
606 if (err != 0) {
608 HELPER_LOG("noise_cipherstate_encrypt failed: %s", noise_err_to_str(err).c_str());
610 }
611
612 size_t total_len = 3 + mbuf.size;
613 buf_start[1] = (uint8_t) (mbuf.size >> 8);
614 buf_start[2] = (uint8_t) mbuf.size;
615
616 struct iovec iov;
617 // Point iov_base to the beginning of the buffer (no unused padding in Noise)
618 // We send the entire frame: indicator + size + encrypted(type + data_len + payload + MAC)
619 iov.iov_base = buf_start;
620 iov.iov_len = total_len;
621
622 // write raw to not have two packets sent if NAGLE disabled
623 return write_raw_(&iov, 1);
624}
626 // try send from tx_buf
627 while (state_ != State::CLOSED && !tx_buf_.empty()) {
628 ssize_t sent = socket_->write(tx_buf_.data(), tx_buf_.size());
629 if (sent == -1) {
630 if (errno == EWOULDBLOCK || errno == EAGAIN)
631 break;
633 HELPER_LOG("Socket write failed with errno %d", errno);
635 } else if (sent == 0) {
636 break;
637 }
638 // TODO: inefficient if multiple packets in txbuf
639 // replace with deque of buffers
640 tx_buf_.erase(tx_buf_.begin(), tx_buf_.begin() + sent);
641 }
642
643 return APIError::OK;
644}
645APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, size_t len) {
646 uint8_t header[3];
647 header[0] = 0x01; // indicator
648 header[1] = (uint8_t) (len >> 8);
649 header[2] = (uint8_t) len;
650
651 struct iovec iov[2];
652 iov[0].iov_base = header;
653 iov[0].iov_len = 3;
654 if (len == 0) {
655 return write_raw_(iov, 1);
656 }
657 iov[1].iov_base = const_cast<uint8_t *>(data);
658 iov[1].iov_len = len;
659
660 return write_raw_(iov, 2);
661}
662
668 int err;
669 memset(&nid_, 0, sizeof(nid_));
670 // const char *proto = "Noise_NNpsk0_25519_ChaChaPoly_SHA256";
671 // err = noise_protocol_name_to_id(&nid_, proto, strlen(proto));
672 nid_.pattern_id = NOISE_PATTERN_NN;
673 nid_.cipher_id = NOISE_CIPHER_CHACHAPOLY;
674 nid_.dh_id = NOISE_DH_CURVE25519;
675 nid_.prefix_id = NOISE_PREFIX_STANDARD;
676 nid_.hybrid_id = NOISE_DH_NONE;
677 nid_.hash_id = NOISE_HASH_SHA256;
678 nid_.modifier_ids[0] = NOISE_MODIFIER_PSK0;
679
680 err = noise_handshakestate_new_by_id(&handshake_, &nid_, NOISE_ROLE_RESPONDER);
681 if (err != 0) {
683 HELPER_LOG("noise_handshakestate_new_by_id failed: %s", noise_err_to_str(err).c_str());
685 }
686
687 const auto &psk = ctx_->get_psk();
688 err = noise_handshakestate_set_pre_shared_key(handshake_, psk.data(), psk.size());
689 if (err != 0) {
691 HELPER_LOG("noise_handshakestate_set_pre_shared_key failed: %s", noise_err_to_str(err).c_str());
693 }
694
695 err = noise_handshakestate_set_prologue(handshake_, prologue_.data(), prologue_.size());
696 if (err != 0) {
698 HELPER_LOG("noise_handshakestate_set_prologue failed: %s", noise_err_to_str(err).c_str());
700 }
701 // set_prologue copies it into handshakestate, so we can get rid of it now
702 prologue_ = {};
703
704 err = noise_handshakestate_start(handshake_);
705 if (err != 0) {
707 HELPER_LOG("noise_handshakestate_start failed: %s", noise_err_to_str(err).c_str());
709 }
710 return APIError::OK;
711}
712
714 assert(state_ == State::HANDSHAKE);
715
716 int action = noise_handshakestate_get_action(handshake_);
717 if (action == NOISE_ACTION_READ_MESSAGE || action == NOISE_ACTION_WRITE_MESSAGE)
718 return APIError::OK;
719 if (action != NOISE_ACTION_SPLIT) {
721 HELPER_LOG("Bad action for handshake: %d", action);
723 }
724 int err = noise_handshakestate_split(handshake_, &send_cipher_, &recv_cipher_);
725 if (err != 0) {
727 HELPER_LOG("noise_handshakestate_split failed: %s", noise_err_to_str(err).c_str());
729 }
730
731 frame_footer_size_ = noise_cipherstate_get_mac_length(send_cipher_);
732
733 HELPER_LOG("Handshake complete!");
734 noise_handshakestate_free(handshake_);
735 handshake_ = nullptr;
737 return APIError::OK;
738}
739
741 if (handshake_ != nullptr) {
742 noise_handshakestate_free(handshake_);
743 handshake_ = nullptr;
744 }
745 if (send_cipher_ != nullptr) {
746 noise_cipherstate_free(send_cipher_);
747 send_cipher_ = nullptr;
748 }
749 if (recv_cipher_ != nullptr) {
750 noise_cipherstate_free(recv_cipher_);
751 recv_cipher_ = nullptr;
752 }
753}
754
757 int err = socket_->close();
758 if (err == -1)
760 return APIError::OK;
761}
763 int err = socket_->shutdown(how);
764 if (err == -1)
766 if (how == SHUT_RDWR) {
768 }
769 return APIError::OK;
770}
771extern "C" {
772// declare how noise generates random bytes (here with a good HWRNG based on the RF system)
773void noise_rand_bytes(void *output, size_t len) {
774 if (!esphome::random_bytes(reinterpret_cast<uint8_t *>(output), len)) {
775 ESP_LOGE(TAG, "Failed to acquire random bytes, rebooting!");
776 arch_restart();
777 }
778}
779}
780
781// Explicit template instantiation for Noise
782template APIError APIFrameHelper::write_raw_<APINoiseFrameHelper::State>(
783 const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf_, const std::string &info,
785#endif // USE_API_NOISE
786
787#ifdef USE_API_PLAINTEXT
788
791 if (state_ != State::INITIALIZE || socket_ == nullptr) {
792 HELPER_LOG("Bad state for init %d", (int) state_);
793 return APIError::BAD_STATE;
794 }
795 int err = socket_->setblocking(false);
796 if (err != 0) {
798 HELPER_LOG("Setting nonblocking failed with errno %d", errno);
800 }
801 int enable = 1;
802 err = socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
803 if (err != 0) {
805 HELPER_LOG("Setting nodelay failed with errno %d", errno);
807 }
808
810 return APIError::OK;
811}
814 if (state_ != State::DATA) {
815 return APIError::BAD_STATE;
816 }
817 // try send pending TX data
818 if (!tx_buf_.empty()) {
820 if (err != APIError::OK) {
821 return err;
822 }
823 }
824 return APIError::OK;
825}
826
837 if (frame == nullptr) {
838 HELPER_LOG("Bad argument for try_read_frame_");
839 return APIError::BAD_ARG;
840 }
841
842 // read header
843 while (!rx_header_parsed_) {
844 uint8_t data;
845 // Reading one byte at a time is fastest in practice for ESP32 when
846 // there is no data on the wire (which is the common case).
847 // This results in faster failure detection compared to
848 // attempting to read multiple bytes at once.
849 ssize_t received = socket_->read(&data, 1);
850 if (received == -1) {
851 if (errno == EWOULDBLOCK || errno == EAGAIN) {
853 }
855 HELPER_LOG("Socket read failed with errno %d", errno);
857 } else if (received == 0) {
859 HELPER_LOG("Connection closed");
861 }
862
863 // Successfully read a byte
864
865 // Process byte according to current buffer position
866 if (rx_header_buf_pos_ == 0) { // Case 1: First byte (indicator byte)
867 if (data != 0x00) {
869 HELPER_LOG("Bad indicator byte %u", data);
871 }
872 // We don't store the indicator byte, just increment position
873 rx_header_buf_pos_ = 1; // Set to 1 directly
874 continue; // Need more bytes before we can parse
875 }
876
877 // Check buffer overflow before storing
878 if (rx_header_buf_pos_ == 5) { // Case 2: Buffer would overflow (5 bytes is max allowed)
880 HELPER_LOG("Header buffer overflow");
882 }
883
884 // Store byte in buffer (adjust index to account for skipped indicator byte)
886
887 // Increment position after storing
889
890 // Case 3: If we only have one varint byte, we need more
891 if (rx_header_buf_pos_ == 2) { // Have read indicator + 1 byte
892 continue; // Need more bytes before we can parse
893 }
894
895 // At this point, we have at least 3 bytes total:
896 // - Validated indicator byte (0x00) but not stored
897 // - At least 2 bytes in the buffer for the varints
898 // Buffer layout:
899 // First 1-3 bytes: Message size varint (variable length)
900 // - 2 bytes would only allow up to 16383, which is less than noise's 65535
901 // - 3 bytes allows up to 2097151, ensuring we support at least as much as noise
902 // Remaining 1-2 bytes: Message type varint (variable length)
903 // We now attempt to parse both varints. If either is incomplete,
904 // we'll continue reading more bytes.
905
906 uint32_t consumed = 0;
907 auto msg_size_varint = ProtoVarInt::parse(&rx_header_buf_[0], rx_header_buf_pos_ - 1, &consumed);
908 if (!msg_size_varint.has_value()) {
909 // not enough data there yet
910 continue;
911 }
912
913 rx_header_parsed_len_ = msg_size_varint->as_uint32();
914
915 auto msg_type_varint = ProtoVarInt::parse(&rx_header_buf_[consumed], rx_header_buf_pos_ - 1 - consumed, &consumed);
916 if (!msg_type_varint.has_value()) {
917 // not enough data there yet
918 continue;
919 }
920 rx_header_parsed_type_ = msg_type_varint->as_uint32();
921 rx_header_parsed_ = true;
922 }
923 // header reading done
924
925 // reserve space for body
926 if (rx_buf_.size() != rx_header_parsed_len_) {
928 }
929
931 // more data to read
932 size_t to_read = rx_header_parsed_len_ - rx_buf_len_;
933 ssize_t received = socket_->read(&rx_buf_[rx_buf_len_], to_read);
934 if (received == -1) {
935 if (errno == EWOULDBLOCK || errno == EAGAIN) {
937 }
939 HELPER_LOG("Socket read failed with errno %d", errno);
941 } else if (received == 0) {
943 HELPER_LOG("Connection closed");
945 }
946 rx_buf_len_ += received;
947 if ((size_t) received != to_read) {
948 // not all read
950 }
951 }
952
953 // uncomment for even more debugging
954#ifdef HELPER_LOG_PACKETS
955 ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(rx_buf_).c_str());
956#endif
957 frame->msg = std::move(rx_buf_);
958 // consume msg
959 rx_buf_ = {};
960 rx_buf_len_ = 0;
962 rx_header_parsed_ = false;
963 return APIError::OK;
964}
965
967 APIError aerr;
968
969 if (state_ != State::DATA) {
971 }
972
973 ParsedFrame frame;
974 aerr = try_read_frame_(&frame);
975 if (aerr != APIError::OK) {
976 if (aerr == APIError::BAD_INDICATOR) {
977 // Make sure to tell the remote that we don't
978 // understand the indicator byte so it knows
979 // we do not support it.
980 struct iovec iov[1];
981 // The \x00 first byte is the marker for plaintext.
982 //
983 // The remote will know how to handle the indicator byte,
984 // but it likely won't understand the rest of the message.
985 //
986 // We must send at least 3 bytes to be read, so we add
987 // a message after the indicator byte to ensures its long
988 // enough and can aid in debugging.
989 const char msg[] = "\x00"
990 "Bad indicator byte";
991 iov[0].iov_base = (void *) msg;
992 iov[0].iov_len = 19;
993 write_raw_(iov, 1);
994 }
995 return aerr;
996 }
997
998 buffer->container = std::move(frame.msg);
999 buffer->data_offset = 0;
1001 buffer->type = rx_header_parsed_type_;
1002 return APIError::OK;
1003}
1006 if (state_ != State::DATA) {
1007 return APIError::BAD_STATE;
1008 }
1009
1010 std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
1011 // Message data starts after padding (frame_header_padding_ = 6)
1012 size_t payload_len = raw_buffer->size() - frame_header_padding_;
1013
1014 // Calculate varint sizes for header components
1015 size_t size_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(payload_len));
1016 size_t type_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(type));
1017 size_t total_header_len = 1 + size_varint_len + type_varint_len;
1018
1019 if (total_header_len > frame_header_padding_) {
1020 // Header is too large to fit in the padding
1021 return APIError::BAD_ARG;
1022 }
1023
1024 // Calculate where to start writing the header
1025 // The header starts at the latest possible position to minimize unused padding
1026 //
1027 // Example 1 (small values): total_header_len = 3, header_offset = 6 - 3 = 3
1028 // [0-2] - Unused padding
1029 // [3] - 0x00 indicator byte
1030 // [4] - Payload size varint (1 byte, for sizes 0-127)
1031 // [5] - Message type varint (1 byte, for types 0-127)
1032 // [6...] - Actual payload data
1033 //
1034 // Example 2 (medium values): total_header_len = 4, header_offset = 6 - 4 = 2
1035 // [0-1] - Unused padding
1036 // [2] - 0x00 indicator byte
1037 // [3-4] - Payload size varint (2 bytes, for sizes 128-16383)
1038 // [5] - Message type varint (1 byte, for types 0-127)
1039 // [6...] - Actual payload data
1040 //
1041 // Example 3 (large values): total_header_len = 6, header_offset = 6 - 6 = 0
1042 // [0] - 0x00 indicator byte
1043 // [1-3] - Payload size varint (3 bytes, for sizes 16384-2097151)
1044 // [4-5] - Message type varint (2 bytes, for types 128-32767)
1045 // [6...] - Actual payload data
1046 uint8_t *buf_start = raw_buffer->data();
1047 size_t header_offset = frame_header_padding_ - total_header_len;
1048
1049 // Write the plaintext header
1050 buf_start[header_offset] = 0x00; // indicator
1051
1052 // Encode size varint directly into buffer
1053 ProtoVarInt(payload_len).encode_to_buffer_unchecked(buf_start + header_offset + 1, size_varint_len);
1054
1055 // Encode type varint directly into buffer
1056 ProtoVarInt(type).encode_to_buffer_unchecked(buf_start + header_offset + 1 + size_varint_len, type_varint_len);
1057
1058 struct iovec iov;
1059 // Point iov_base to the beginning of our header (skip unused padding)
1060 // This ensures we only send the actual header and payload, not the empty padding bytes
1061 iov.iov_base = buf_start + header_offset;
1062 iov.iov_len = total_header_len + payload_len;
1063
1064 return write_raw_(&iov, 1);
1065}
1067 // try send from tx_buf
1068 while (state_ != State::CLOSED && !tx_buf_.empty()) {
1069 ssize_t sent = socket_->write(tx_buf_.data(), tx_buf_.size());
1070 if (is_would_block(sent)) {
1071 break;
1072 } else if (sent == -1) {
1074 HELPER_LOG("Socket write failed with errno %d", errno);
1076 }
1077 // TODO: inefficient if multiple packets in txbuf
1078 // replace with deque of buffers
1079 tx_buf_.erase(tx_buf_.begin(), tx_buf_.begin() + sent);
1080 }
1081
1082 return APIError::OK;
1083}
1084
1087 int err = socket_->close();
1088 if (err == -1)
1090 return APIError::OK;
1091}
1093 int err = socket_->shutdown(how);
1094 if (err == -1)
1096 if (how == SHUT_RDWR) {
1098 }
1099 return APIError::OK;
1100}
1101
1102// Explicit template instantiation for Plaintext
1103template APIError APIFrameHelper::write_raw_<APIPlaintextFrameHelper::State>(
1104 const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf_, const std::string &info,
1106#endif // USE_API_PLAINTEXT
1107
1108} // namespace api
1109} // namespace esphome
1110#endif
const std::string & get_name() const
Get the name of this Application set by pre_setup().
APIError write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector< uint8_t > &tx_buf, const std::string &info, StateEnum &state, StateEnum failed_state)
APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override
APIError read_packet(ReadPacketBuffer *buffer) override
APIError try_read_frame_(ParsedFrame *frame)
Read a packet into the rx_buf_.
enum esphome::api::APINoiseFrameHelper::State state_
APIError state_action_()
To be called from read/write methods.
APIError write_frame_(const uint8_t *data, size_t len)
APIError loop() override
Run through handshake messages (if in that phase)
std::shared_ptr< APINoiseContext > ctx_
APIError write_raw_(const struct iovec *iov, int iovcnt)
APIError init() override
Initialize the frame helper, returns OK if successful.
std::unique_ptr< socket::Socket > socket_
void send_explicit_handshake_reject_(const std::string &reason)
APIError init_handshake_()
Initiate the data structures for the handshake.
APIError shutdown(int how) override
enum esphome::api::APIPlaintextFrameHelper::State state_
APIError init() override
Initialize the frame helper, returns OK if successful.
std::unique_ptr< socket::Socket > socket_
APIError loop() override
Not used for plaintext.
APIError read_packet(ReadPacketBuffer *buffer) override
APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override
APIError try_read_frame_(ParsedFrame *frame)
Read a packet into the rx_buf_.
APIError write_raw_(const struct iovec *iov, int iovcnt)
static uint32_t varint(uint32_t value)
ProtoSize class for Protocol Buffer serialization size calculation.
Representation of a VarInt - in ProtoBuf should be 64bit but we only use 32bit.
Definition proto.h:17
void encode_to_buffer_unchecked(uint8_t *buffer, size_t len)
Encode the varint value to a pre-allocated buffer without bounds checking.
Definition proto.h:97
static optional< ProtoVarInt > parse(const uint8_t *buffer, uint32_t len, uint32_t *consumed)
Definition proto.h:22
std::vector< uint8_t > * get_buffer() const
Definition proto.h:320
uint8_t type
bool state
Definition fan.h:0
__int64 ssize_t
Definition httplib.h:175
void noise_rand_bytes(void *output, size_t len)
std::string noise_err_to_str(int err)
Convert a noise error code to a readable error.
bool is_would_block(ssize_t ret)
Is the given return value (from write syscalls) a wouldblock error?
const char * api_error_to_str(APIError err)
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
bool random_bytes(uint8_t *data, size_t len)
Generate len number of random bytes.
Definition helpers.cpp:220
std::string size_t len
Definition helpers.h:301
std::string to_string(int value)
Definition helpers.cpp:82
std::string get_mac_address()
Get the device MAC address as a string, in lowercase hex notation.
Definition helpers.cpp:726
void arch_restart()
Definition core.cpp:31
Application App
Global storage of Application pointer - only one Application can exist.
std::string format_hex_pretty(const uint8_t *data, size_t length)
Format the byte array data of length len in pretty-printed, human-readable hex.
Definition helpers.cpp:372
std::vector< uint8_t > container
void * iov_base
Definition headers.h:101
size_t iov_len
Definition headers.h:102