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