ESPHome 2025.9.0-dev
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, ...) ESP_LOGVV(TAG, "%s: " msg, this->client_info_->get_combined_info().c_str(), ##__VA_ARGS__)
17
18#ifdef HELPER_LOG_PACKETS
19#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())
20#define LOG_PACKET_SENDING(data, len) ESP_LOGVV(TAG, "Sending raw: %s", format_hex_pretty(data, len).c_str())
21#else
22#define LOG_PACKET_RECEIVED(buffer) ((void) 0)
23#define LOG_PACKET_SENDING(data, len) ((void) 0)
24#endif
25
26const char *api_error_to_str(APIError err) {
27 // not using switch to ensure compiler doesn't try to build a big table out of it
28 if (err == APIError::OK) {
29 return "OK";
30 } else if (err == APIError::WOULD_BLOCK) {
31 return "WOULD_BLOCK";
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::OUT_OF_MEMORY) {
53 return "OUT_OF_MEMORY";
54 } else if (err == APIError::CONNECTION_CLOSED) {
55 return "CONNECTION_CLOSED";
56 }
57#ifdef USE_API_NOISE
58 else if (err == APIError::BAD_HANDSHAKE_PACKET_LEN) {
59 return "BAD_HANDSHAKE_PACKET_LEN";
60 } else if (err == APIError::HANDSHAKESTATE_READ_FAILED) {
61 return "HANDSHAKESTATE_READ_FAILED";
62 } else if (err == APIError::HANDSHAKESTATE_WRITE_FAILED) {
63 return "HANDSHAKESTATE_WRITE_FAILED";
64 } else if (err == APIError::HANDSHAKESTATE_BAD_STATE) {
65 return "HANDSHAKESTATE_BAD_STATE";
66 } else if (err == APIError::CIPHERSTATE_DECRYPT_FAILED) {
67 return "CIPHERSTATE_DECRYPT_FAILED";
68 } else if (err == APIError::CIPHERSTATE_ENCRYPT_FAILED) {
69 return "CIPHERSTATE_ENCRYPT_FAILED";
70 } else if (err == APIError::HANDSHAKESTATE_SETUP_FAILED) {
71 return "HANDSHAKESTATE_SETUP_FAILED";
72 } else if (err == APIError::HANDSHAKESTATE_SPLIT_FAILED) {
73 return "HANDSHAKESTATE_SPLIT_FAILED";
74 } else if (err == APIError::BAD_HANDSHAKE_ERROR_BYTE) {
75 return "BAD_HANDSHAKE_ERROR_BYTE";
76 }
77#endif
78 return "UNKNOWN";
79}
80
81// Default implementation for loop - handles sending buffered data
83 if (!this->tx_buf_.empty()) {
85 if (err != APIError::OK && err != APIError::WOULD_BLOCK) {
86 return err;
87 }
88 }
89 return APIError::OK; // Convert WOULD_BLOCK to OK to avoid connection termination
90}
91
92// Common socket write error handling
94 if (errno == EWOULDBLOCK || errno == EAGAIN) {
96 }
97 HELPER_LOG("Socket write failed with errno %d", errno);
98 this->state_ = State::FAILED;
100}
101
102// Helper method to buffer data from IOVs
103void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len,
104 uint16_t offset) {
105 SendBuffer buffer;
106 buffer.size = total_write_len - offset;
107 buffer.data = std::make_unique<uint8_t[]>(buffer.size);
108
109 uint16_t to_skip = offset;
110 uint16_t write_pos = 0;
111
112 for (int i = 0; i < iovcnt; i++) {
113 if (to_skip >= iov[i].iov_len) {
114 // Skip this entire segment
115 to_skip -= static_cast<uint16_t>(iov[i].iov_len);
116 } else {
117 // Include this segment (partially or fully)
118 const uint8_t *src = reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_skip;
119 uint16_t len = static_cast<uint16_t>(iov[i].iov_len) - to_skip;
120 std::memcpy(buffer.data.get() + write_pos, src, len);
121 write_pos += len;
122 to_skip = 0;
123 }
124 }
125 this->tx_buf_.push_back(std::move(buffer));
126}
127
128// This method writes data to socket or buffers it
129APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, uint16_t total_write_len) {
130 // Returns APIError::OK if successful (or would block, but data has been buffered)
131 // Returns APIError::SOCKET_WRITE_FAILED if socket write failed, and sets state to FAILED
132
133 if (iovcnt == 0)
134 return APIError::OK; // Nothing to do, success
135
136#ifdef HELPER_LOG_PACKETS
137 for (int i = 0; i < iovcnt; i++) {
138 LOG_PACKET_SENDING(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len);
139 }
140#endif
141
142 // Try to send any existing buffered data first if there is any
143 if (!this->tx_buf_.empty()) {
144 APIError send_result = try_send_tx_buf_();
145 // If real error occurred (not just WOULD_BLOCK), return it
146 if (send_result != APIError::OK && send_result != APIError::WOULD_BLOCK) {
147 return send_result;
148 }
149
150 // If there is still data in the buffer, we can't send, buffer
151 // the new data and return
152 if (!this->tx_buf_.empty()) {
153 this->buffer_data_from_iov_(iov, iovcnt, total_write_len, 0);
154 return APIError::OK; // Success, data buffered
155 }
156 }
157
158 // Try to send directly if no buffered data
159 // Optimize for single iovec case (common for plaintext API)
160 ssize_t sent =
161 (iovcnt == 1) ? this->socket_->write(iov[0].iov_base, iov[0].iov_len) : this->socket_->writev(iov, iovcnt);
162
163 if (sent == -1) {
165 if (err == APIError::WOULD_BLOCK) {
166 // Socket would block, buffer the data
167 this->buffer_data_from_iov_(iov, iovcnt, total_write_len, 0);
168 return APIError::OK; // Success, data buffered
169 }
170 return err; // Socket write failed
171 } else if (static_cast<uint16_t>(sent) < total_write_len) {
172 // Partially sent, buffer the remaining data
173 this->buffer_data_from_iov_(iov, iovcnt, total_write_len, static_cast<uint16_t>(sent));
174 }
175
176 return APIError::OK; // Success, all data sent or buffered
177}
178
179// Common implementation for trying to send buffered data
180// IMPORTANT: Caller MUST ensure tx_buf_ is not empty before calling this method
182 // Try to send from tx_buf - we assume it's not empty as it's the caller's responsibility to check
183 bool tx_buf_empty = false;
184 while (!tx_buf_empty) {
185 // Get the first buffer in the queue
186 SendBuffer &front_buffer = this->tx_buf_.front();
187
188 // Try to send the remaining data in this buffer
189 ssize_t sent = this->socket_->write(front_buffer.current_data(), front_buffer.remaining());
190
191 if (sent == -1) {
192 return this->handle_socket_write_error_();
193 } else if (sent == 0) {
194 // Nothing sent but not an error
196 } else if (static_cast<uint16_t>(sent) < front_buffer.remaining()) {
197 // Partially sent, update offset
198 // Cast to ensure no overflow issues with uint16_t
199 front_buffer.offset += static_cast<uint16_t>(sent);
200 return APIError::WOULD_BLOCK; // Stop processing more buffers if we couldn't send a complete buffer
201 } else {
202 // Buffer completely sent, remove it from the queue
203 this->tx_buf_.pop_front();
204 // Update empty status for the loop condition
205 tx_buf_empty = this->tx_buf_.empty();
206 // Continue loop to try sending the next buffer
207 }
208 }
209
210 return APIError::OK; // All buffers sent successfully
211}
212
214 if (state_ != State::INITIALIZE || this->socket_ == nullptr) {
215 HELPER_LOG("Bad state for init %d", (int) state_);
216 return APIError::BAD_STATE;
217 }
218 int err = this->socket_->setblocking(false);
219 if (err != 0) {
221 HELPER_LOG("Setting nonblocking failed with errno %d", errno);
223 }
224
225 int enable = 1;
226 err = this->socket_->setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(int));
227 if (err != 0) {
229 HELPER_LOG("Setting nodelay failed with errno %d", errno);
231 }
232 return APIError::OK;
233}
234
236 if (received == -1) {
237 if (errno == EWOULDBLOCK || errno == EAGAIN) {
239 }
241 HELPER_LOG("Socket read failed with errno %d", errno);
243 } else if (received == 0) {
245 HELPER_LOG("Connection closed");
247 }
248 return APIError::OK;
249}
250
251} // namespace esphome::api
252#endif
APIError handle_socket_read_result_(ssize_t received)
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)
std::deque< SendBuffer > tx_buf_
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:175
const char * api_error_to_str(APIError err)
std::string size_t len
Definition helpers.h:279
void * iov_base
Definition headers.h:101
size_t iov_len
Definition headers.h:102