ESPHome 2025.9.0-dev
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 <span>
6#include <utility>
7#include <vector>
8
10#ifdef USE_API
13#include "esphome/core/log.h"
14
15namespace esphome::api {
16
17// uncomment to log raw packets
18//#define HELPER_LOG_PACKETS
19
20// Forward declaration
21struct ClientInfo;
22
23class ProtoWriteBuffer;
24
26 std::vector<uint8_t> container;
27 uint16_t type;
28 uint16_t data_offset;
29 uint16_t data_len;
30};
31
32// Packed packet info structure to minimize memory usage
33struct PacketInfo {
34 uint16_t offset; // Offset in buffer where message starts
35 uint16_t payload_size; // Size of the message payload
36 uint8_t message_type; // Message type (0-255)
37
38 PacketInfo(uint8_t type, uint16_t off, uint16_t size) : offset(off), payload_size(size), message_type(type) {}
39};
40
41enum class APIError : uint16_t {
42 OK = 0,
43 WOULD_BLOCK = 1001,
44 BAD_INDICATOR = 1003,
45 BAD_DATA_PACKET = 1004,
46 TCP_NODELAY_FAILED = 1005,
48 CLOSE_FAILED = 1007,
49 SHUTDOWN_FAILED = 1008,
50 BAD_STATE = 1009,
51 BAD_ARG = 1010,
52 SOCKET_READ_FAILED = 1011,
54 OUT_OF_MEMORY = 1018,
55 CONNECTION_CLOSED = 1022,
56#ifdef USE_API_NOISE
66#endif
67};
68
69const char *api_error_to_str(APIError err);
70
72 public:
73 APIFrameHelper() = default;
74 explicit APIFrameHelper(std::unique_ptr<socket::Socket> socket, const ClientInfo *client_info)
75 : socket_owned_(std::move(socket)), client_info_(client_info) {
76 socket_ = socket_owned_.get();
77 }
78 virtual ~APIFrameHelper() = default;
79 virtual APIError init() = 0;
80 virtual APIError loop();
82 bool can_write_without_blocking() { return state_ == State::DATA && tx_buf_.empty(); }
83 std::string getpeername() { return socket_->getpeername(); }
84 int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return socket_->getpeername(addr, addrlen); }
87 int err = this->socket_->close();
88 if (err == -1)
90 return APIError::OK;
91 }
92 APIError shutdown(int how) {
93 int err = this->socket_->shutdown(how);
94 if (err == -1)
96 if (how == SHUT_RDWR) {
98 }
99 return APIError::OK;
100 }
102 // Write multiple protobuf packets in a single operation
103 // packets contains (message_type, offset, length) for each message in the buffer
104 // The buffer contains all messages with appropriate padding before each
105 virtual APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span<const PacketInfo> packets) = 0;
106 // Get the frame header padding required by this protocol
107 virtual uint8_t frame_header_padding() = 0;
108 // Get the frame footer size required by this protocol
109 virtual uint8_t frame_footer_size() = 0;
110 // Check if socket has data ready to read
111 bool is_socket_ready() const { return socket_ != nullptr && socket_->ready(); }
112
113 protected:
114 // Buffer containing data to be sent
115 struct SendBuffer {
116 std::unique_ptr<uint8_t[]> data;
117 uint16_t size{0}; // Total size of the buffer
118 uint16_t offset{0}; // Current offset within the buffer
119
120 // Using uint16_t reduces memory usage since ESPHome API messages are limited to UINT16_MAX (65535) bytes
121 uint16_t remaining() const { return size - offset; }
122 const uint8_t *current_data() const { return data.get() + offset; }
123 };
124
125 // Common implementation for writing raw data to socket
126 APIError write_raw_(const struct iovec *iov, int iovcnt, uint16_t total_write_len);
127
128 // Try to send data from the tx buffer
130
131 // Helper method to buffer data from IOVs
132 void buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len, uint16_t offset);
133
134 // Common socket write error handling
136 template<typename StateEnum>
137 APIError write_raw_(const struct iovec *iov, int iovcnt, socket::Socket *socket, std::vector<uint8_t> &tx_buf,
138 const std::string &info, StateEnum &state, StateEnum failed_state);
139
140 // Pointers first (4 bytes each)
142 std::unique_ptr<socket::Socket> socket_owned_;
143
144 // Common state enum for all frame helpers
145 // Note: Not all states are used by all implementations
146 // - INITIALIZE: Used by both Noise and Plaintext
147 // - CLIENT_HELLO, SERVER_HELLO, HANDSHAKE: Only used by Noise protocol
148 // - DATA: Used by both Noise and Plaintext
149 // - CLOSED: Used by both Noise and Plaintext
150 // - FAILED: Used by both Noise and Plaintext
151 // - EXPLICIT_REJECT: Only used by Noise protocol
152 enum class State : uint8_t {
153 INITIALIZE = 1,
154 CLIENT_HELLO = 2, // Noise only
155 SERVER_HELLO = 3, // Noise only
156 HANDSHAKE = 4, // Noise only
157 DATA = 5,
158 CLOSED = 6,
159 FAILED = 7,
160 EXPLICIT_REJECT = 8, // Noise only
161 };
162
163 // Containers (size varies, but typically 12+ bytes on 32-bit)
164 std::deque<SendBuffer> tx_buf_;
165 std::vector<struct iovec> reusable_iovs_;
166 std::vector<uint8_t> rx_buf_;
167
168 // Pointer to client info (4 bytes on 32-bit)
169 // Note: The pointed-to ClientInfo object must outlive this APIFrameHelper instance.
170 const ClientInfo *client_info_{nullptr};
171
172 // Group smaller types together
173 uint16_t rx_buf_len_ = 0;
177 // 5 bytes total, 3 bytes padding
178
179 // Common initialization for both plaintext and noise protocols
181
182 // Helper method to handle socket read results
184};
185
186} // namespace esphome::api
187
188#endif // USE_API
APIError handle_socket_read_result_(ssize_t received)
APIFrameHelper(std::unique_ptr< socket::Socket > socket, const ClientInfo *client_info)
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 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 uint8_t frame_header_padding()=0
std::deque< SendBuffer > tx_buf_
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
uint8_t type
bool state
Definition fan.h:0
uint32_t socklen_t
Definition headers.h:97
__int64 ssize_t
Definition httplib.h:175
const char * api_error_to_str(APIError err)
PacketInfo(uint8_t type, uint16_t off, uint16_t size)
std::vector< uint8_t > container