ESPHome 2026.2.1
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
4#include "esphome/core/hal.h"
6#include "esphome/core/log.h"
7#include "proto.h"
8#include <cstring>
9#include <cinttypes>
10
11namespace esphome::api {
12
13static const char *const TAG = "api.frame_helper";
14
15// Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512)
16static constexpr size_t API_MAX_LOG_BYTES = 168;
17
18#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
19#define HELPER_LOG(msg, ...) \
20 do { \
21 char peername_buf[socket::SOCKADDR_STR_LEN]; \
22 this->get_peername_to(peername_buf); \
23 ESP_LOGVV(TAG, "%s (%s): " msg, this->client_name_, peername_buf, ##__VA_ARGS__); \
24 } while (0)
25#else
26#define HELPER_LOG(msg, ...) ((void) 0)
27#endif
28
29#ifdef HELPER_LOG_PACKETS
30#define LOG_PACKET_RECEIVED(buffer) \
31 do { \
32 char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \
33 ESP_LOGVV(TAG, "Received frame: %s", \
34 format_hex_pretty_to(hex_buf_, (buffer).data(), \
35 (buffer).size() < API_MAX_LOG_BYTES ? (buffer).size() : API_MAX_LOG_BYTES)); \
36 } while (0)
37#define LOG_PACKET_SENDING(data, len) \
38 do { \
39 char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \
40 ESP_LOGVV(TAG, "Sending raw: %s", \
41 format_hex_pretty_to(hex_buf_, data, (len) < API_MAX_LOG_BYTES ? (len) : API_MAX_LOG_BYTES)); \
42 } while (0)
43#else
44#define LOG_PACKET_RECEIVED(buffer) ((void) 0)
45#define LOG_PACKET_SENDING(data, len) ((void) 0)
46#endif
47
48const LogString *api_error_to_logstr(APIError err) {
49 // not using switch to ensure compiler doesn't try to build a big table out of it
50 if (err == APIError::OK) {
51 return LOG_STR("OK");
52 } else if (err == APIError::WOULD_BLOCK) {
53 return LOG_STR("WOULD_BLOCK");
54 } else if (err == APIError::BAD_INDICATOR) {
55 return LOG_STR("BAD_INDICATOR");
56 } else if (err == APIError::BAD_DATA_PACKET) {
57 return LOG_STR("BAD_DATA_PACKET");
58 } else if (err == APIError::TCP_NODELAY_FAILED) {
59 return LOG_STR("TCP_NODELAY_FAILED");
60 } else if (err == APIError::TCP_NONBLOCKING_FAILED) {
61 return LOG_STR("TCP_NONBLOCKING_FAILED");
62 } else if (err == APIError::CLOSE_FAILED) {
63 return LOG_STR("CLOSE_FAILED");
64 } else if (err == APIError::SHUTDOWN_FAILED) {
65 return LOG_STR("SHUTDOWN_FAILED");
66 } else if (err == APIError::BAD_STATE) {
67 return LOG_STR("BAD_STATE");
68 } else if (err == APIError::BAD_ARG) {
69 return LOG_STR("BAD_ARG");
70 } else if (err == APIError::SOCKET_READ_FAILED) {
71 return LOG_STR("SOCKET_READ_FAILED");
72 } else if (err == APIError::SOCKET_WRITE_FAILED) {
73 return LOG_STR("SOCKET_WRITE_FAILED");
74 } else if (err == APIError::OUT_OF_MEMORY) {
75 return LOG_STR("OUT_OF_MEMORY");
76 } else if (err == APIError::CONNECTION_CLOSED) {
77 return LOG_STR("CONNECTION_CLOSED");
78 }
79#ifdef USE_API_NOISE
80 else if (err == APIError::BAD_HANDSHAKE_PACKET_LEN) {
81 return LOG_STR("BAD_HANDSHAKE_PACKET_LEN");
82 } else if (err == APIError::HANDSHAKESTATE_READ_FAILED) {
83 return LOG_STR("HANDSHAKESTATE_READ_FAILED");
84 } else if (err == APIError::HANDSHAKESTATE_WRITE_FAILED) {
85 return LOG_STR("HANDSHAKESTATE_WRITE_FAILED");
86 } else if (err == APIError::HANDSHAKESTATE_BAD_STATE) {
87 return LOG_STR("HANDSHAKESTATE_BAD_STATE");
88 } else if (err == APIError::CIPHERSTATE_DECRYPT_FAILED) {
89 return LOG_STR("CIPHERSTATE_DECRYPT_FAILED");
90 } else if (err == APIError::CIPHERSTATE_ENCRYPT_FAILED) {
91 return LOG_STR("CIPHERSTATE_ENCRYPT_FAILED");
92 } else if (err == APIError::HANDSHAKESTATE_SETUP_FAILED) {
93 return LOG_STR("HANDSHAKESTATE_SETUP_FAILED");
94 } else if (err == APIError::HANDSHAKESTATE_SPLIT_FAILED) {
95 return LOG_STR("HANDSHAKESTATE_SPLIT_FAILED");
96 } else if (err == APIError::BAD_HANDSHAKE_ERROR_BYTE) {
97 return LOG_STR("BAD_HANDSHAKE_ERROR_BYTE");
98 }
99#endif
100 return LOG_STR("UNKNOWN");
101}
102
103// Default implementation for loop - handles sending buffered data
105 if (this->tx_buf_count_ > 0) {
107 if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
108 return err;
109 }
110 }
111 return APIError::OK; // Convert WOULD_BLOCK to OK to avoid connection termination
112}
113
114// Common socket write error handling
116 if (errno == EWOULDBLOCK || errno == EAGAIN) {
118 }
119 HELPER_LOG("Socket write failed with errno %d", errno);
120 this->state_ = State::FAILED;
122}
123
124// Helper method to buffer data from IOVs
125void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len,
126 uint16_t offset) {
127 // Check if queue is full
128 if (this->tx_buf_count_ >= API_MAX_SEND_QUEUE) {
129 HELPER_LOG("Send queue full (%u buffers), dropping connection", this->tx_buf_count_);
130 this->state_ = State::FAILED;
131 return;
132 }
133
134 uint16_t buffer_size = total_write_len - offset;
135 auto &buffer = this->tx_buf_[this->tx_buf_tail_];
136 buffer = std::make_unique<SendBuffer>(SendBuffer{
137 .data = std::make_unique<uint8_t[]>(buffer_size),
138 .size = buffer_size,
139 .offset = 0,
140 });
141
142 uint16_t to_skip = offset;
143 uint16_t write_pos = 0;
144
145 for (int i = 0; i < iovcnt; i++) {
146 if (to_skip >= iov[i].iov_len) {
147 // Skip this entire segment
148 to_skip -= static_cast<uint16_t>(iov[i].iov_len);
149 } else {
150 // Include this segment (partially or fully)
151 const uint8_t *src = reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_skip;
152 uint16_t len = static_cast<uint16_t>(iov[i].iov_len) - to_skip;
153 std::memcpy(buffer->data.get() + write_pos, src, len);
154 write_pos += len;
155 to_skip = 0;
156 }
157 }
158
159 // Update circular buffer tracking
160 this->tx_buf_tail_ = (this->tx_buf_tail_ + 1) % API_MAX_SEND_QUEUE;
161 this->tx_buf_count_++;
162}
163
164// This method writes data to socket or buffers it
165APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, uint16_t total_write_len) {
166 // Returns APIError::OK if successful (or would block, but data has been buffered)
167 // Returns APIError::SOCKET_WRITE_FAILED if socket write failed, and sets state to FAILED
168
169 if (iovcnt == 0)
170 return APIError::OK; // Nothing to do, success
171
172#ifdef HELPER_LOG_PACKETS
173 for (int i = 0; i < iovcnt; i++) {
174 LOG_PACKET_SENDING(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len);
175 }
176#endif
177
178 // Try to send any existing buffered data first if there is any
179 if (this->tx_buf_count_ > 0) {
180 APIError send_result = try_send_tx_buf_();
181 // If real error occurred (not just WOULD_BLOCK), return it
182 if (send_result != APIError::OK && send_result != APIError::WOULD_BLOCK) {
183 return send_result;
184 }
185
186 // If there is still data in the buffer, we can't send, buffer
187 // the new data and return
188 if (this->tx_buf_count_ > 0) {
189 this->buffer_data_from_iov_(iov, iovcnt, total_write_len, 0);
190 return APIError::OK; // Success, data buffered
191 }
192 }
193
194 // Try to send directly if no buffered data
195 // Optimize for single iovec case (common for plaintext API)
196 ssize_t sent =
197 (iovcnt == 1) ? this->socket_->write(iov[0].iov_base, iov[0].iov_len) : this->socket_->writev(iov, iovcnt);
198
199 if (sent == -1) {
201 if (err == APIError::WOULD_BLOCK) {
202 // Socket would block, buffer the data
203 this->buffer_data_from_iov_(iov, iovcnt, total_write_len, 0);
204 return APIError::OK; // Success, data buffered
205 }
206 return err; // Socket write failed
207 } else if (static_cast<uint16_t>(sent) < total_write_len) {
208 // Partially sent, buffer the remaining data
209 this->buffer_data_from_iov_(iov, iovcnt, total_write_len, static_cast<uint16_t>(sent));
210 }
211
212 return APIError::OK; // Success, all data sent or buffered
213}
214
215// Common implementation for trying to send buffered data
216// IMPORTANT: Caller MUST ensure tx_buf_count_ > 0 before calling this method
218 // Try to send from tx_buf - we assume it's not empty as it's the caller's responsibility to check
219 while (this->tx_buf_count_ > 0) {
220 // Get the first buffer in the queue
221 SendBuffer *front_buffer = this->tx_buf_[this->tx_buf_head_].get();
222
223 // Try to send the remaining data in this buffer
224 ssize_t sent = this->socket_->write(front_buffer->current_data(), front_buffer->remaining());
225
226 if (sent == -1) {
227 return this->handle_socket_write_error_();
228 } else if (sent == 0) {
229 // Nothing sent but not an error
231 } else if (static_cast<uint16_t>(sent) < front_buffer->remaining()) {
232 // Partially sent, update offset
233 // Cast to ensure no overflow issues with uint16_t
234 front_buffer->offset += static_cast<uint16_t>(sent);
235 return APIError::WOULD_BLOCK; // Stop processing more buffers if we couldn't send a complete buffer
236 } else {
237 // Buffer completely sent, remove it from the queue
238 this->tx_buf_[this->tx_buf_head_].reset();
239 this->tx_buf_head_ = (this->tx_buf_head_ + 1) % API_MAX_SEND_QUEUE;
240 this->tx_buf_count_--;
241 // Continue loop to try sending the next buffer
242 }
243 }
244
245 return APIError::OK; // All buffers sent successfully
246}
247
248const char *APIFrameHelper::get_peername_to(std::span<char, socket::SOCKADDR_STR_LEN> buf) const {
249 if (this->socket_) {
250 this->socket_->getpeername_to(buf);
251 } else {
252 buf[0] = '\0';
253 }
254 return buf.data();
255}
256
258 if (state_ != State::INITIALIZE || this->socket_ == nullptr) {
259 HELPER_LOG("Bad state for init %d", (int) state_);
260 return APIError::BAD_STATE;
261 }
262 int err = this->socket_->setblocking(false);
263 if (err != 0) {
265 HELPER_LOG("Setting nonblocking failed with errno %d", errno);
267 }
268
269 int enable = 1;
270 err = this->socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
271 if (err != 0) {
273 HELPER_LOG("Setting nodelay failed with errno %d", errno);
275 }
276 return APIError::OK;
277}
278
280 if (received == -1) {
281 if (errno == EWOULDBLOCK || errno == EAGAIN) {
283 }
285 HELPER_LOG("Socket read failed with errno %d", errno);
287 } else if (received == 0) {
289 HELPER_LOG("Connection closed");
291 }
292 return APIError::OK;
293}
294
295} // namespace esphome::api
296#endif
APIError handle_socket_read_result_(ssize_t received)
std::array< std::unique_ptr< SendBuffer >, API_MAX_SEND_QUEUE > tx_buf_
void buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len, uint16_t offset)
const char * get_peername_to(std::span< char, socket::SOCKADDR_STR_LEN > buf) const
std::unique_ptr< socket::Socket > socket_
APIError write_raw_(const struct iovec *iov, int iovcnt, uint16_t total_write_len)
__int64 ssize_t
Definition httplib.h:178
const LogString * api_error_to_logstr(APIError err)
std::string size_t len
Definition helpers.h:692
void * iov_base
Definition headers.h:101
size_t iov_len
Definition headers.h:102