ESPHome 2026.7.4
Loading...
Searching...
No Matches
api_overflow_buffer.cpp
Go to the documentation of this file.
2#ifdef USE_API
3#include <cstring>
4
5namespace esphome::api {
6
8 for (auto *entry : this->queue_) {
9 if (entry != nullptr)
10 Entry::destroy(entry);
11 }
12}
13
15 // socket->write() can re-enter this function: a log message emitted from an
16 // lwip callback during the write goes out over the API and lands back in the
17 // frame helper's write/drain path. If a nested drain ran here it would send
18 // and free the entry the outer drain is still holding, causing a double free.
19 // Report "no progress" instead; the outer drain keeps draining, and the
20 // nested send is enqueued behind the existing backlog.
21 if (this->draining_)
22 return 0;
23
24 // RAII so the flag is cleared on every return path
25 struct DrainGuard {
26 explicit DrainGuard(bool &flag) : flag_(flag) { flag_ = true; }
27 ~DrainGuard() { this->flag_ = false; }
28 bool &flag_;
29 } guard(this->draining_);
30
31 while (this->count_ > 0) {
32 Entry *front = this->queue_[this->head_];
33
34 ssize_t sent = socket->write(front->current_data(), front->remaining());
35
36 if (sent <= 0) {
37 // -1 = error (caller checks errno for EWOULDBLOCK vs hard error)
38 // 0 = nothing sent (treat as no progress)
39 return sent;
40 }
41
42 if (static_cast<uint16_t>(sent) < front->remaining()) {
43 // Partially sent, update offset and stop
44 front->offset += static_cast<uint16_t>(sent);
45 return sent;
46 }
47
48 // Entry fully sent — unlink it before freeing so a freed pointer is never
49 // reachable from the queue
50 this->queue_[this->head_] = nullptr;
51 this->head_ = (this->head_ + 1) % API_MAX_SEND_QUEUE;
52 this->count_--;
53 Entry::destroy(front);
54 }
55
56 return 0; // All drained
57}
58
59bool APIOverflowBuffer::enqueue_iov(const struct iovec *iov, int iovcnt, uint16_t total_len, uint16_t skip) {
60 if (this->count_ >= API_MAX_SEND_QUEUE)
61 return false;
62
63 uint16_t buffer_size = total_len - skip;
64 // NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
65 auto *entry = new Entry{new uint8_t[buffer_size], buffer_size, 0};
66 this->queue_[this->tail_] = entry;
67
68 uint16_t to_skip = skip;
69 uint16_t write_pos = 0;
70
71 for (int i = 0; i < iovcnt; i++) {
72 if (to_skip >= iov[i].iov_len) {
73 to_skip -= static_cast<uint16_t>(iov[i].iov_len);
74 } else {
75 const uint8_t *src = reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_skip;
76 uint16_t len = static_cast<uint16_t>(iov[i].iov_len) - to_skip;
77 std::memcpy(entry->data + write_pos, src, len);
78 write_pos += len;
79 to_skip = 0;
80 }
81 }
82
83 this->tail_ = (this->tail_ + 1) % API_MAX_SEND_QUEUE;
84 this->count_++;
85 return true;
86}
87
88} // namespace esphome::api
89
90#endif // USE_API
std::array< Entry *, API_MAX_SEND_QUEUE > queue_
bool enqueue_iov(const struct iovec *iov, int iovcnt, uint16_t total_len, uint16_t skip)
Enqueue unsent IOV data into the backlog.
ssize_t try_drain(socket::Socket *socket)
Try to drain queued data to the socket.
__int64 ssize_t
Definition httplib.h:178
const void size_t len
Definition hal.h:64
const void * src
Definition hal.h:64
A single heap-allocated send-backlog entry.
uint16_t offset
uint16_t remaining() const
const uint8_t * current_data() const
static ESPHOME_ALWAYS_INLINE void destroy(Entry *entry)
Free this entry and its data buffer.
void * iov_base
Definition headers.h:103
size_t iov_len
Definition headers.h:104