ESPHome 2025.10.0-dev
Loading...
Searching...
No Matches
api_frame_helper_plaintext.cpp
Go to the documentation of this file.
2#ifdef USE_API
3#ifdef USE_API_PLAINTEXT
4#include "api_connection.h" // For ClientInfo struct
6#include "esphome/core/hal.h"
8#include "esphome/core/log.h"
9#include "proto.h"
10#include <cstring>
11#include <cinttypes>
12
13#ifdef USE_ESP8266
14#include <pgmspace.h>
15#endif
16
17namespace esphome::api {
18
19static const char *const TAG = "api.plaintext";
20
21#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s: " msg, this->client_info_->get_combined_info().c_str(), ##__VA_ARGS__)
22
23#ifdef HELPER_LOG_PACKETS
24#define LOG_PACKET_RECEIVED(buffer) ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(buffer).c_str())
25#define LOG_PACKET_SENDING(data, len) ESP_LOGVV(TAG, "Sending raw: %s", format_hex_pretty(data, len).c_str())
26#else
27#define LOG_PACKET_RECEIVED(buffer) ((void) 0)
28#define LOG_PACKET_SENDING(data, len) ((void) 0)
29#endif
30
33 APIError err = init_common_();
34 if (err != APIError::OK) {
35 return err;
36 }
37
39 return APIError::OK;
40}
42 if (state_ != State::DATA) {
44 }
45 // Use base class implementation for buffer sending
46 return APIFrameHelper::loop();
47}
48
59 if (frame == nullptr) {
60 HELPER_LOG("Bad argument for try_read_frame_");
61 return APIError::BAD_ARG;
62 }
63
64 // read header
65 while (!rx_header_parsed_) {
66 // Now that we know when the socket is ready, we can read up to 3 bytes
67 // into the rx_header_buf_ before we have to switch back to reading
68 // one byte at a time to ensure we don't read past the message and
69 // into the next one.
70
71 // Read directly into rx_header_buf_ at the current position
72 // Try to get to at least 3 bytes total (indicator + 2 varint bytes), then read one byte at a time
73 ssize_t received =
74 this->socket_->read(&rx_header_buf_[rx_header_buf_pos_], rx_header_buf_pos_ < 3 ? 3 - rx_header_buf_pos_ : 1);
76 if (err != APIError::OK) {
77 return err;
78 }
79
80 // If this was the first read, validate the indicator byte
81 if (rx_header_buf_pos_ == 0 && received > 0) {
82 if (rx_header_buf_[0] != 0x00) {
84 HELPER_LOG("Bad indicator byte %u", rx_header_buf_[0]);
86 }
87 }
88
89 rx_header_buf_pos_ += received;
90
91 // Check for buffer overflow
92 if (rx_header_buf_pos_ >= sizeof(rx_header_buf_)) {
94 HELPER_LOG("Header buffer overflow");
96 }
97
98 // Need at least 3 bytes total (indicator + 2 varint bytes) before trying to parse
99 if (rx_header_buf_pos_ < 3) {
100 continue;
101 }
102
103 // At this point, we have at least 3 bytes total:
104 // - Validated indicator byte (0x00) stored at position 0
105 // - At least 2 bytes in the buffer for the varints
106 // Buffer layout:
107 // [0]: indicator byte (0x00)
108 // [1-3]: Message size varint (variable length)
109 // - 2 bytes would only allow up to 16383, which is less than noise's UINT16_MAX (65535)
110 // - 3 bytes allows up to 2097151, ensuring we support at least as much as noise
111 // [2-5]: Message type varint (variable length)
112 // We now attempt to parse both varints. If either is incomplete,
113 // we'll continue reading more bytes.
114
115 // Skip indicator byte at position 0
116 uint8_t varint_pos = 1;
117 uint32_t consumed = 0;
118
119 auto msg_size_varint = ProtoVarInt::parse(&rx_header_buf_[varint_pos], rx_header_buf_pos_ - varint_pos, &consumed);
120 if (!msg_size_varint.has_value()) {
121 // not enough data there yet
122 continue;
123 }
124
125 if (msg_size_varint->as_uint32() > std::numeric_limits<uint16_t>::max()) {
127 HELPER_LOG("Bad packet: message size %" PRIu32 " exceeds maximum %u", msg_size_varint->as_uint32(),
128 std::numeric_limits<uint16_t>::max());
130 }
131 rx_header_parsed_len_ = msg_size_varint->as_uint16();
132
133 // Move to next varint position
134 varint_pos += consumed;
135
136 auto msg_type_varint = ProtoVarInt::parse(&rx_header_buf_[varint_pos], rx_header_buf_pos_ - varint_pos, &consumed);
137 if (!msg_type_varint.has_value()) {
138 // not enough data there yet
139 continue;
140 }
141 if (msg_type_varint->as_uint32() > std::numeric_limits<uint16_t>::max()) {
143 HELPER_LOG("Bad packet: message type %" PRIu32 " exceeds maximum %u", msg_type_varint->as_uint32(),
144 std::numeric_limits<uint16_t>::max());
146 }
147 rx_header_parsed_type_ = msg_type_varint->as_uint16();
148 rx_header_parsed_ = true;
149 }
150 // header reading done
151
152 // reserve space for body
153 if (rx_buf_.size() != rx_header_parsed_len_) {
155 }
156
158 // more data to read
159 uint16_t to_read = rx_header_parsed_len_ - rx_buf_len_;
160 ssize_t received = this->socket_->read(&rx_buf_[rx_buf_len_], to_read);
161 APIError err = handle_socket_read_result_(received);
162 if (err != APIError::OK) {
163 return err;
164 }
165 rx_buf_len_ += static_cast<uint16_t>(received);
166 if (static_cast<uint16_t>(received) != to_read) {
167 // not all read
169 }
170 }
171
172 LOG_PACKET_RECEIVED(rx_buf_);
173 *frame = std::move(rx_buf_);
174 // consume msg
175 rx_buf_ = {};
176 rx_buf_len_ = 0;
178 rx_header_parsed_ = false;
179 return APIError::OK;
180}
182 APIError aerr;
183
184 if (state_ != State::DATA) {
186 }
187
188 std::vector<uint8_t> frame;
189 aerr = try_read_frame_(&frame);
190 if (aerr != APIError::OK) {
191 if (aerr == APIError::BAD_INDICATOR) {
192 // Make sure to tell the remote that we don't
193 // understand the indicator byte so it knows
194 // we do not support it.
195 struct iovec iov[1];
196 // The \x00 first byte is the marker for plaintext.
197 //
198 // The remote will know how to handle the indicator byte,
199 // but it likely won't understand the rest of the message.
200 //
201 // We must send at least 3 bytes to be read, so we add
202 // a message after the indicator byte to ensures its long
203 // enough and can aid in debugging.
204 static constexpr uint8_t INDICATOR_MSG_SIZE = 19;
205#ifdef USE_ESP8266
206 static const char MSG_PROGMEM[] PROGMEM = "\x00"
207 "Bad indicator byte";
208 char msg[INDICATOR_MSG_SIZE];
209 memcpy_P(msg, MSG_PROGMEM, INDICATOR_MSG_SIZE);
210 iov[0].iov_base = (void *) msg;
211#else
212 static const char MSG[] = "\x00"
213 "Bad indicator byte";
214 iov[0].iov_base = (void *) MSG;
215#endif
216 iov[0].iov_len = INDICATOR_MSG_SIZE;
217 this->write_raw_(iov, 1, INDICATOR_MSG_SIZE);
218 }
219 return aerr;
220 }
221
222 buffer->container = std::move(frame);
223 buffer->data_offset = 0;
226 return APIError::OK;
227}
229 PacketInfo packet{type, 0, static_cast<uint16_t>(buffer.get_buffer()->size() - frame_header_padding_)};
230 return write_protobuf_packets(buffer, std::span<const PacketInfo>(&packet, 1));
231}
232
234 if (state_ != State::DATA) {
235 return APIError::BAD_STATE;
236 }
237
238 if (packets.empty()) {
239 return APIError::OK;
240 }
241
242 std::vector<uint8_t> *raw_buffer = buffer.get_buffer();
243 uint8_t *buffer_data = raw_buffer->data(); // Cache buffer pointer
244
245 this->reusable_iovs_.clear();
246 this->reusable_iovs_.reserve(packets.size());
247 uint16_t total_write_len = 0;
248
249 for (const auto &packet : packets) {
250 // Calculate varint sizes for header layout
251 uint8_t size_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(packet.payload_size));
252 uint8_t type_varint_len = api::ProtoSize::varint(static_cast<uint32_t>(packet.message_type));
253 uint8_t total_header_len = 1 + size_varint_len + type_varint_len;
254
255 // Calculate where to start writing the header
256 // The header starts at the latest possible position to minimize unused padding
257 //
258 // Example 1 (small values): total_header_len = 3, header_offset = 6 - 3 = 3
259 // [0-2] - Unused padding
260 // [3] - 0x00 indicator byte
261 // [4] - Payload size varint (1 byte, for sizes 0-127)
262 // [5] - Message type varint (1 byte, for types 0-127)
263 // [6...] - Actual payload data
264 //
265 // Example 2 (medium values): total_header_len = 4, header_offset = 6 - 4 = 2
266 // [0-1] - Unused padding
267 // [2] - 0x00 indicator byte
268 // [3-4] - Payload size varint (2 bytes, for sizes 128-16383)
269 // [5] - Message type varint (1 byte, for types 0-127)
270 // [6...] - Actual payload data
271 //
272 // Example 3 (large values): total_header_len = 6, header_offset = 6 - 6 = 0
273 // [0] - 0x00 indicator byte
274 // [1-3] - Payload size varint (3 bytes, for sizes 16384-2097151)
275 // [4-5] - Message type varint (2 bytes, for types 128-32767)
276 // [6...] - Actual payload data
277 //
278 // The message starts at offset + frame_header_padding_
279 // So we write the header starting at offset + frame_header_padding_ - total_header_len
280 uint8_t *buf_start = buffer_data + packet.offset;
281 uint32_t header_offset = frame_header_padding_ - total_header_len;
282
283 // Write the plaintext header
284 buf_start[header_offset] = 0x00; // indicator
285
286 // Encode varints directly into buffer
287 ProtoVarInt(packet.payload_size).encode_to_buffer_unchecked(buf_start + header_offset + 1, size_varint_len);
288 ProtoVarInt(packet.message_type)
289 .encode_to_buffer_unchecked(buf_start + header_offset + 1 + size_varint_len, type_varint_len);
290
291 // Add iovec for this packet (header + payload)
292 size_t packet_len = static_cast<size_t>(total_header_len + packet.payload_size);
293 this->reusable_iovs_.push_back({buf_start + header_offset, packet_len});
294 total_write_len += packet_len;
295 }
296
297 // Send all packets in one writev call
298 return write_raw_(this->reusable_iovs_.data(), this->reusable_iovs_.size(), total_write_len);
299}
300
301} // namespace esphome::api
302#endif // USE_API_PLAINTEXT
303#endif // USE_API
APIError handle_socket_read_result_(ssize_t received)
std::vector< uint8_t > rx_buf_
std::vector< struct iovec > reusable_iovs_
APIError write_raw_(const struct iovec *iov, int iovcnt, uint16_t total_write_len)
APIError try_read_frame_(std::vector< uint8_t > *frame)
Read a packet into the rx_buf_.
APIError init() override
Initialize the frame helper, returns OK if successful.
APIError write_protobuf_packets(ProtoWriteBuffer buffer, std::span< const PacketInfo > packets) override
APIError write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) override
APIError read_packet(ReadPacketBuffer *buffer) override
static constexpr uint32_t varint(uint32_t value)
Calculates the size in bytes needed to encode a uint32_t value as a varint.
Definition proto.h:399
Representation of a VarInt - in ProtoBuf should be 64bit but we only use 32bit.
Definition proto.h:66
void encode_to_buffer_unchecked(uint8_t *buffer, size_t len)
Encode the varint value to a pre-allocated buffer without bounds checking.
Definition proto.h:138
static optional< ProtoVarInt > parse(const uint8_t *buffer, uint32_t len, uint32_t *consumed)
Definition proto.h:71
std::vector< uint8_t > * get_buffer() const
Definition proto.h:331
virtual ssize_t read(void *buf, size_t len)=0
uint16_t type
__int64 ssize_t
Definition httplib.h:178
std::vector< uint8_t > container
void * iov_base
Definition headers.h:101
size_t iov_len
Definition headers.h:102
const uint8_t ESPHOME_WEBSERVER_INDEX_HTML[] PROGMEM
Definition web_server.h:24