ESPHome 2025.10.4
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 std::vector<uint8_t> container;
39 uint16_t type;
40 uint16_t data_offset;
41 uint16_t data_len;
42};
43
44// Packed packet info structure to minimize memory usage
45struct PacketInfo {
46 uint16_t offset; // Offset in buffer where message starts
47 uint16_t payload_size; // Size of the message payload
48 uint8_t message_type; // Message type (0-255)
49
50 PacketInfo(uint8_t type, uint16_t off, uint16_t size) : offset(off), payload_size(size), message_type(type) {}
51};
52
53enum class APIError : uint16_t {
54 OK = 0,
55 WOULD_BLOCK = 1001,
56 BAD_INDICATOR = 1003,
57 BAD_DATA_PACKET = 1004,
58 TCP_NODELAY_FAILED = 1005,
60 CLOSE_FAILED = 1007,
61 SHUTDOWN_FAILED = 1008,
62 BAD_STATE = 1009,
63 BAD_ARG = 1010,
64 SOCKET_READ_FAILED = 1011,
66 OUT_OF_MEMORY = 1018,
67 CONNECTION_CLOSED = 1022,
68#ifdef USE_API_NOISE
78#endif
79};
80
81const LogString *api_error_to_logstr(APIError err);
82
84 public:
85 APIFrameHelper() = default;
86 explicit APIFrameHelper(std::unique_ptr<socket::Socket> socket, const ClientInfo *client_info)
87 : socket_owned_(std::move(socket)), client_info_(client_info) {
88 socket_ = socket_owned_.get();
89 }
90 virtual ~APIFrameHelper() = default;
91 virtual APIError init() = 0;
92 virtual APIError loop();
94 bool can_write_without_blocking() { return this->state_ == State::DATA && this->tx_buf_count_ == 0; }
95 std::string getpeername() { return socket_->getpeername(); }
96 int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return socket_->getpeername(addr, addrlen); }
99 int err = this->socket_->close();
100 if (err == -1)
102 return APIError::OK;
103 }
105 int err = this->socket_->shutdown(how);
106 if (err == -1)
108 if (how == SHUT_RDWR) {
110 }
111 return APIError::OK;
112 }
114 // Write multiple protobuf packets in a single operation
115 // packets contains (message_type, offset, length) for each message in the buffer
116 // The buffer contains all messages with appropriate padding before each
117 virtual APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) = 0;
118 // Get the frame header padding required by this protocol
119 uint8_t frame_header_padding() const { return frame_header_padding_; }
120 // Get the frame footer size required by this protocol
121 uint8_t frame_footer_size() const { return frame_footer_size_; }
122 // Check if socket has data ready to read
123 bool is_socket_ready() const { return socket_ != nullptr && socket_->ready(); }
124
125 protected:
126 // Buffer containing data to be sent
127 struct SendBuffer {
128 std::unique_ptr<uint8_t[]> data;
129 uint16_t size{0}; // Total size of the buffer
130 uint16_t offset{0}; // Current offset within the buffer
131
132 // Using uint16_t reduces memory usage since ESPHome API messages are limited to UINT16_MAX (65535) bytes
133 uint16_t remaining() const { return size - offset; }
134 const uint8_t *current_data() const { return data.get() + offset; }
135 };
136
137 // Common implementation for writing raw data to socket
138 APIError write_raw_(const struct iovec *iov, int iovcnt, uint16_t total_write_len);
139
140 // Try to send data from the tx buffer
142
143 // Helper method to buffer data from IOVs
144 void buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len, uint16_t offset);
145
146 // Common socket write error handling
148 template<typename StateEnum>
149 APIError write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf,
150 const std::string &info, StateEnum &state, StateEnum failed_state);
151
152 // Pointers first (4 bytes each)
154 std::unique_ptr<socket::Socket> socket_owned_;
155
156 // Common state enum for all frame helpers
157 // Note: Not all states are used by all implementations
158 // - INITIALIZE: Used by both Noise and Plaintext
159 // - CLIENT_HELLO, SERVER_HELLO, HANDSHAKE: Only used by Noise protocol
160 // - DATA: Used by both Noise and Plaintext
161 // - CLOSED: Used by both Noise and Plaintext
162 // - FAILED: Used by both Noise and Plaintext
163 // - EXPLICIT_REJECT: Only used by Noise protocol
164 enum class State : uint8_t {
165 INITIALIZE = 1,
166 CLIENT_HELLO = 2, // Noise only
167 SERVER_HELLO = 3, // Noise only
168 HANDSHAKE = 4, // Noise only
169 DATA = 5,
170 CLOSED = 6,
171 FAILED = 7,
172 EXPLICIT_REJECT = 8, // Noise only
173 };
174
175 // Containers (size varies, but typically 12+ bytes on 32-bit)
176 std::array<std::unique_ptr<SendBuffer>, API_MAX_SEND_QUEUE> tx_buf_;
177 std::vector<struct iovec> reusable_iovs_;
178 std::vector<uint8_t> rx_buf_;
179
180 // Pointer to client info (4 bytes on 32-bit)
181 // Note: The pointed-to ClientInfo object must outlive this APIFrameHelper instance.
182 const ClientInfo *client_info_{nullptr};
183
184 // Group smaller types together
185 uint16_t rx_buf_len_ = 0;
189 uint8_t tx_buf_head_{0};
190 uint8_t tx_buf_tail_{0};
191 uint8_t tx_buf_count_{0};
192 // 8 bytes total, 0 bytes padding
193
194 // Common initialization for both plaintext and noise protocols
196
197 // Helper method to handle socket read results
199};
200
201} // namespace esphome::api
202
203#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::unique_ptr< socket::Socket > socket_owned_
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
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
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
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)
std::vector< uint8_t > container