ESPHome 2025.6.0
Loading...
Searching...
No Matches
api_frame_helper.h
Go to the documentation of this file.
1#pragma once
2#include <cstdint>
3#include <deque>
4#include <limits>
5#include <utility>
6#include <vector>
7
9#ifdef USE_API
10#ifdef USE_API_NOISE
11#include "noise/protocol.h"
12#endif
13
14#include "api_noise_context.h"
17
18namespace esphome {
19namespace api {
20
21class ProtoWriteBuffer;
22
24 std::vector<uint8_t> container;
25 uint16_t type;
26 uint16_t data_offset;
27 uint16_t data_len;
28};
29
30// Packed packet info structure to minimize memory usage
31struct PacketInfo {
32 uint16_t message_type; // 2 bytes
33 uint16_t offset; // 2 bytes (sufficient for packet size ~1460 bytes)
34 uint16_t payload_size; // 2 bytes (up to 65535 bytes)
35 uint16_t padding; // 2 byte (for alignment)
36
37 PacketInfo(uint16_t type, uint16_t off, uint16_t size)
38 : message_type(type), offset(off), payload_size(size), padding(0) {}
39};
40
41enum class APIError : int {
42 OK = 0,
43 WOULD_BLOCK = 1001,
45 BAD_INDICATOR = 1003,
46 BAD_DATA_PACKET = 1004,
47 TCP_NODELAY_FAILED = 1005,
49 CLOSE_FAILED = 1007,
50 SHUTDOWN_FAILED = 1008,
51 BAD_STATE = 1009,
52 BAD_ARG = 1010,
53 SOCKET_READ_FAILED = 1011,
60 OUT_OF_MEMORY = 1018,
64 CONNECTION_CLOSED = 1022,
65};
66
67const char *api_error_to_str(APIError err);
68
70 public:
71 APIFrameHelper() = default;
72 explicit APIFrameHelper(std::unique_ptr<socket::Socket> socket) : socket_owned_(std::move(socket)) {
73 socket_ = socket_owned_.get();
74 }
75 virtual ~APIFrameHelper() = default;
76 virtual APIError init() = 0;
77 virtual APIError loop() = 0;
79 bool can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
80 std::string getpeername() { return socket_->getpeername(); }
81 int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return socket_->getpeername(addr, addrlen); }
84 int err = this->socket_->close();
85 if (err == -1)
87 return APIError::OK;
88 }
89 APIError shutdown(int how) {
90 int err = this->socket_->shutdown(how);
91 if (err == -1)
93 if (how == SHUT_RDWR) {
95 }
96 return APIError::OK;
97 }
98 // Give this helper a name for logging
99 void set_log_info(std::string info) { info_ = std::move(info); }
101 // Write multiple protobuf packets in a single operation
102 // packets contains (message_type, offset, length) for each message in the buffer
103 // The buffer contains all messages with appropriate padding before each
104 virtual APIError write_protobuf_packets(ProtoWriteBuffer buffer, const std::vector<PacketInfo> &packets) = 0;
105 // Get the frame header padding required by this protocol
106 virtual uint8_t frame_header_padding() = 0;
107 // Get the frame footer size required by this protocol
108 virtual uint8_t frame_footer_size() = 0;
109 // Check if socket has data ready to read
110 bool is_socket_ready() const { return socket_ != nullptr && socket_->ready(); }
111
112 protected:
113 // Struct for holding parsed frame data
114 struct ParsedFrame {
115 std::vector<uint8_t> msg;
116 };
117
118 // Buffer containing data to be sent
119 struct SendBuffer {
120 std::vector<uint8_t> data;
121 uint16_t offset{0}; // Current offset within the buffer (uint16_t to reduce memory usage)
122
123 // Using uint16_t reduces memory usage since ESPHome API messages are limited to UINT16_MAX (65535) bytes
124 uint16_t remaining() const { return static_cast<uint16_t>(data.size()) - offset; }
125 const uint8_t *current_data() const { return data.data() + offset; }
126 };
127
128 // Queue of data buffers to be sent
129 std::deque<SendBuffer> tx_buf_;
130
131 // Common state enum for all frame helpers
132 // Note: Not all states are used by all implementations
133 // - INITIALIZE: Used by both Noise and Plaintext
134 // - CLIENT_HELLO, SERVER_HELLO, HANDSHAKE: Only used by Noise protocol
135 // - DATA: Used by both Noise and Plaintext
136 // - CLOSED: Used by both Noise and Plaintext
137 // - FAILED: Used by both Noise and Plaintext
138 // - EXPLICIT_REJECT: Only used by Noise protocol
139 enum class State {
140 INITIALIZE = 1,
141 CLIENT_HELLO = 2, // Noise only
142 SERVER_HELLO = 3, // Noise only
143 HANDSHAKE = 4, // Noise only
144 DATA = 5,
145 CLOSED = 6,
146 FAILED = 7,
147 EXPLICIT_REJECT = 8, // Noise only
148 };
149
150 // Current state of the frame helper
152
153 // Helper name for logging
154 std::string info_;
155
156 // Socket for communication
158 std::unique_ptr<socket::Socket> socket_owned_;
159
160 // Common implementation for writing raw data to socket
161 APIError write_raw_(const struct iovec *iov, int iovcnt);
162
163 // Try to send data from the tx buffer
165
166 // Helper method to buffer data from IOVs
167 void buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len);
168 template<typename StateEnum>
169 APIError write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf,
170 const std::string &info, StateEnum &state, StateEnum failed_state);
171
174
175 // Reusable IOV array for write_protobuf_packets to avoid repeated allocations
176 std::vector<struct iovec> reusable_iovs_;
177
178 // Receive buffer for reading frame data
179 std::vector<uint8_t> rx_buf_;
180 uint16_t rx_buf_len_ = 0;
181
182 // Common initialization for both plaintext and noise protocols
184};
185
186#ifdef USE_API_NOISE
188 public:
189 APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, std::shared_ptr<APINoiseContext> ctx)
190 : APIFrameHelper(std::move(socket)), ctx_(std::move(ctx)) {
191 // Noise header structure:
192 // Pos 0: indicator (0x01)
193 // Pos 1-2: encrypted payload size (16-bit big-endian)
194 // Pos 3-6: encrypted type (16-bit) + data_len (16-bit)
195 // Pos 7+: actual payload data
197 }
198 ~APINoiseFrameHelper() override;
199 APIError init() override;
200 APIError loop() override;
201 APIError read_packet(ReadPacketBuffer *buffer) override;
202 APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override;
203 APIError write_protobuf_packets(ProtoWriteBuffer buffer, const std::vector<PacketInfo> &packets) override;
204 // Get the frame header padding required by this protocol
205 uint8_t frame_header_padding() override { return frame_header_padding_; }
206 // Get the frame footer size required by this protocol
207 uint8_t frame_footer_size() override { return frame_footer_size_; }
208
209 protected:
211 APIError try_read_frame_(ParsedFrame *frame);
212 APIError write_frame_(const uint8_t *data, uint16_t len);
215 void send_explicit_handshake_reject_(const std::string &reason);
216 // Fixed-size header buffer for noise protocol:
217 // 1 byte for indicator + 2 bytes for message size (16-bit value, not varint)
218 // Note: Maximum message size is UINT16_MAX (65535), with a limit of 128 bytes during handshake phase
219 uint8_t rx_header_buf_[3];
221
222 std::vector<uint8_t> prologue_;
223
224 std::shared_ptr<APINoiseContext> ctx_;
225 NoiseHandshakeState *handshake_{nullptr};
226 NoiseCipherState *send_cipher_{nullptr};
227 NoiseCipherState *recv_cipher_{nullptr};
228 NoiseProtocolId nid_;
229};
230#endif // USE_API_NOISE
231
232#ifdef USE_API_PLAINTEXT
234 public:
235 APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket) : APIFrameHelper(std::move(socket)) {
236 // Plaintext header structure (worst case):
237 // Pos 0: indicator (0x00)
238 // Pos 1-3: payload size varint (up to 3 bytes)
239 // Pos 4-5: message type varint (up to 2 bytes)
240 // Pos 6+: actual payload data
242 }
243 ~APIPlaintextFrameHelper() override = default;
244 APIError init() override;
245 APIError loop() override;
246 APIError read_packet(ReadPacketBuffer *buffer) override;
247 APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer) override;
248 APIError write_protobuf_packets(ProtoWriteBuffer buffer, const std::vector<PacketInfo> &packets) override;
249 uint8_t frame_header_padding() override { return frame_header_padding_; }
250 // Get the frame footer size required by this protocol
251 uint8_t frame_footer_size() override { return frame_footer_size_; }
252
253 protected:
254 APIError try_read_frame_(ParsedFrame *frame);
255 // Fixed-size header buffer for plaintext protocol:
256 // We now store the indicator byte + the two varints.
257 // To match noise protocol's maximum message size (UINT16_MAX = 65535), we need:
258 // 1 byte for indicator + 3 bytes for message size varint (supports up to 2097151) + 2 bytes for message type varint
259 //
260 // While varints could theoretically be up to 10 bytes each for 64-bit values,
261 // attempting to process messages with headers that large would likely crash the
262 // ESP32 due to memory constraints.
263 uint8_t rx_header_buf_[6]; // 1 byte indicator + 5 bytes for varints (3 for size + 2 for type)
265 bool rx_header_parsed_ = false;
268};
269#endif
270
271} // namespace api
272} // namespace esphome
273#endif
virtual uint8_t frame_footer_size()=0
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::unique_ptr< socket::Socket > socket_owned_
void set_log_info(std::string info)
int getpeername(struct sockaddr *addr, socklen_t *addrlen)
virtual APIError loop()=0
std::vector< struct iovec > reusable_iovs_
virtual APIError init()=0
virtual APIError write_protobuf_packets(ProtoWriteBuffer buffer, const std::vector< PacketInfo > &packets)=0
void buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len)
APIFrameHelper(std::unique_ptr< socket::Socket > socket)
APIError write_raw_(const struct iovec *iov, int iovcnt)
virtual uint8_t frame_header_padding()=0
virtual APIError write_protobuf_packet(uint16_t type, ProtoWriteBuffer buffer)=0
std::deque< SendBuffer > tx_buf_
virtual ~APIFrameHelper()=default
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_.
APIError write_protobuf_packets(ProtoWriteBuffer buffer, const std::vector< PacketInfo > &packets) override
APIError state_action_()
To be called from read/write methods.
APIError loop() override
Run through handshake messages (if in that phase)
APIError write_frame_(const uint8_t *data, uint16_t len)
std::shared_ptr< APINoiseContext > ctx_
APIError init() override
Initialize the frame helper, returns OK if successful.
void send_explicit_handshake_reject_(const std::string &reason)
APIError init_handshake_()
Initiate the data structures for the handshake.
APINoiseFrameHelper(std::unique_ptr< socket::Socket > socket, std::shared_ptr< APINoiseContext > ctx)
APIPlaintextFrameHelper(std::unique_ptr< socket::Socket > socket)
APIError write_protobuf_packets(ProtoWriteBuffer buffer, const std::vector< PacketInfo > &packets) override
APIError init() override
Initialize the frame helper, returns OK if successful.
APIError loop() override
Not used for plaintext.
~APIPlaintextFrameHelper() override=default
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_.
bool ready() const
Check if socket has data ready to read For loop-monitored sockets, checks with the Application's sele...
Definition socket.cpp:14
virtual int shutdown(int how)=0
virtual int getpeername(struct sockaddr *addr, socklen_t *addrlen)=0
virtual int close()=0
uint8_t type
bool state
Definition fan.h:0
uint32_t socklen_t
Definition headers.h:97
const char * api_error_to_str(APIError err)
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
std::string size_t len
Definition helpers.h:302
PacketInfo(uint16_t type, uint16_t off, uint16_t size)
std::vector< uint8_t > container