ESPHome 2026.1.0-dev
Loading...
Searching...
No Matches
api_frame_helper.h
Go to the documentation of this file.
1#pragma once
2#include <array>
3#include <cstdint>
4#include <limits>
5#include <memory>
6#include <span>
7#include <utility>
8#include <vector>
9
11#ifdef USE_API
14#include "esphome/core/log.h"
15
16namespace esphome::api {
17
18// uncomment to log raw packets
19//#define HELPER_LOG_PACKETS
20
21// Maximum message size limits to prevent OOM on constrained devices
22// Handshake messages are limited to a small size for security
23static constexpr uint16_t MAX_HANDSHAKE_SIZE = 128;
24
25// Data message limits vary by platform based on available memory
26#ifdef USE_ESP8266
27static constexpr uint16_t MAX_MESSAGE_SIZE = 8192; // 8 KiB for ESP8266
28#else
29static constexpr uint16_t MAX_MESSAGE_SIZE = 32768; // 32 KiB for ESP32 and other platforms
30#endif
31
32// Forward declaration
33struct ClientInfo;
34
35class ProtoWriteBuffer;
36
38 const uint8_t *data; // Points directly into frame helper's rx_buf_ (valid until next read_packet call)
39 uint16_t data_len;
40 uint16_t type;
41};
42
43// Packed packet info structure to minimize memory usage
44struct PacketInfo {
45 uint16_t offset; // Offset in buffer where message starts
46 uint16_t payload_size; // Size of the message payload
47 uint8_t message_type; // Message type (0-255)
48
49 PacketInfo(uint8_t type, uint16_t off, uint16_t size) : offset(off), payload_size(size), message_type(type) {}
50};
51
52enum class APIError : uint16_t {
53 OK = 0,
54 WOULD_BLOCK = 1001,
55 BAD_INDICATOR = 1003,
56 BAD_DATA_PACKET = 1004,
57 TCP_NODELAY_FAILED = 1005,
59 CLOSE_FAILED = 1007,
60 SHUTDOWN_FAILED = 1008,
61 BAD_STATE = 1009,
62 BAD_ARG = 1010,
63 SOCKET_READ_FAILED = 1011,
65 OUT_OF_MEMORY = 1018,
66 CONNECTION_CLOSED = 1022,
67#ifdef USE_API_NOISE
77#endif
78};
79
80const LogString *api_error_to_logstr(APIError err);
81
83 public:
84 APIFrameHelper() = default;
85 explicit APIFrameHelper(std::unique_ptr<socket::Socket> socket, const ClientInfo *client_info)
86 : socket_(std::move(socket)), client_info_(client_info) {}
87 virtual ~APIFrameHelper() = default;
88 virtual APIError init() = 0;
89 virtual APIError loop();
91 bool can_write_without_blocking() { return this->state_ == State::DATA && this->tx_buf_count_ == 0; }
92 std::string getpeername() { return socket_->getpeername(); }
93 int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return socket_->getpeername(addr, addrlen); }
96 int err = this->socket_->close();
97 if (err == -1)
99 return APIError::OK;
100 }
102 int err = this->socket_->shutdown(how);
103 if (err == -1)
105 if (how == SHUT_RDWR) {
107 }
108 return APIError::OK;
109 }
111 // Write multiple protobuf packets in a single operation
112 // packets contains (message_type, offset, length) for each message in the buffer
113 // The buffer contains all messages with appropriate padding before each
114 virtual APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) = 0;
115 // Get the frame header padding required by this protocol
116 uint8_t frame_header_padding() const { return frame_header_padding_; }
117 // Get the frame footer size required by this protocol
118 uint8_t frame_footer_size() const { return frame_footer_size_; }
119 // Check if socket has data ready to read
120 bool is_socket_ready() const { return socket_ != nullptr && socket_->ready(); }
121 // Release excess memory from internal buffers after initial sync
123 // rx_buf_: Safe to clear only if no partial read in progress.
124 // rx_buf_len_ tracks bytes read so far; if non-zero, we're mid-frame
125 // and clearing would lose partially received data.
126 if (this->rx_buf_len_ == 0) {
127 // Use swap trick since shrink_to_fit() is non-binding and may be ignored
128 std::vector<uint8_t>().swap(this->rx_buf_);
129 }
130 // reusable_iovs_: Safe to release unconditionally.
131 // Only used within write_protobuf_packets() calls - cleared at start,
132 // populated with pointers, used for writev(), then function returns.
133 // The iovecs contain stale pointers after the call (data was either sent
134 // or copied to tx_buf_), and are cleared on next write_protobuf_packets().
135 std::vector<struct iovec>().swap(this->reusable_iovs_);
136 }
137
138 protected:
139 // Buffer containing data to be sent
140 struct SendBuffer {
141 std::unique_ptr<uint8_t[]> data;
142 uint16_t size{0}; // Total size of the buffer
143 uint16_t offset{0}; // Current offset within the buffer
144
145 // Using uint16_t reduces memory usage since ESPHome API messages are limited to UINT16_MAX (65535) bytes
146 uint16_t remaining() const { return size - offset; }
147 const uint8_t *current_data() const { return data.get() + offset; }
148 };
149
150 // Common implementation for writing raw data to socket
151 APIError write_raw_(const struct iovec *iov, int iovcnt, uint16_t total_write_len);
152
153 // Try to send data from the tx buffer
155
156 // Helper method to buffer data from IOVs
157 void buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len, uint16_t offset);
158
159 // Common socket write error handling
161 template<typename StateEnum>
162 APIError write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf,
163 const std::string &info, StateEnum &state, StateEnum failed_state);
164
165 // Socket ownership (4 bytes on 32-bit, 8 bytes on 64-bit)
166 std::unique_ptr<socket::Socket> socket_;
167
168 // Common state enum for all frame helpers
169 // Note: Not all states are used by all implementations
170 // - INITIALIZE: Used by both Noise and Plaintext
171 // - CLIENT_HELLO, SERVER_HELLO, HANDSHAKE: Only used by Noise protocol
172 // - DATA: Used by both Noise and Plaintext
173 // - CLOSED: Used by both Noise and Plaintext
174 // - FAILED: Used by both Noise and Plaintext
175 // - EXPLICIT_REJECT: Only used by Noise protocol
176 enum class State : uint8_t {
177 INITIALIZE = 1,
178 CLIENT_HELLO = 2, // Noise only
179 SERVER_HELLO = 3, // Noise only
180 HANDSHAKE = 4, // Noise only
181 DATA = 5,
182 CLOSED = 6,
183 FAILED = 7,
184 EXPLICIT_REJECT = 8, // Noise only
185 };
186
187 // Containers (size varies, but typically 12+ bytes on 32-bit)
188 std::array<std::unique_ptr<SendBuffer>, API_MAX_SEND_QUEUE> tx_buf_;
189 std::vector<struct iovec> reusable_iovs_;
190 std::vector<uint8_t> rx_buf_;
191
192 // Pointer to client info (4 bytes on 32-bit)
193 // Note: The pointed-to ClientInfo object must outlive this APIFrameHelper instance.
194 const ClientInfo *client_info_{nullptr};
195
196 // Group smaller types together
197 uint16_t rx_buf_len_ = 0;
201 uint8_t tx_buf_head_{0};
202 uint8_t tx_buf_tail_{0};
203 uint8_t tx_buf_count_{0};
204 // 8 bytes total, 0 bytes padding
205
206 // Common initialization for both plaintext and noise protocols
208
209 // Helper method to handle socket read results
211};
212
213} // namespace esphome::api
214
215#endif // USE_API
APIError handle_socket_read_result_(ssize_t received)
APIFrameHelper(std::unique_ptr< socket::Socket > socket, const ClientInfo *client_info)
std::vector< uint8_t > rx_buf_
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)
virtual APIError read_packet(ReadPacketBuffer *buffer)=0
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)
int getpeername(struct sockaddr *addr, socklen_t *addrlen)
std::vector< struct iovec > reusable_iovs_
virtual APIError init()=0
std::unique_ptr< socket::Socket > socket_
virtual APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span< const PacketInfo > packets)=0
virtual APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer)=0
APIError write_raw_(const struct iovec *iov, int iovcnt, uint16_t total_write_len)
virtual ~APIFrameHelper()=default
uint16_t type
bool state
Definition fan.h:0
uint32_t socklen_t
Definition headers.h:97
__int64 ssize_t
Definition httplib.h:178
const LogString * api_error_to_logstr(APIError err)
PacketInfo(uint8_t type, uint16_t off, uint16_t size)