ESPHome 2026.9.0-dev
Loading...
Searching...
No Matches
api_overflow_buffer.cpp
Go to the documentation of this file.
2#ifdef USE_API
3#include <cstring>
4#include <new>
5
6namespace esphome::api {
7
9 for (auto *entry : this->queue_) {
10 if (entry != nullptr)
11 Entry::destroy(entry);
12 }
13}
14
16 // socket->write() can re-enter this function: a log message emitted from an
17 // lwip callback during the write goes out over the API and lands back in the
18 // frame helper's write/drain path. If a nested drain ran here it would send
19 // and free the entry the outer drain is still holding, causing a double free.
20 // Report "no progress" instead; the outer drain keeps draining, and the
21 // nested send is enqueued behind the existing backlog.
22 if (this->draining_)
23 return 0;
24
25 // RAII so the flag is cleared on every return path
26 struct DrainGuard {
27 explicit DrainGuard(bool &flag) : flag_(flag) { flag_ = true; }
28 ~DrainGuard() { this->flag_ = false; }
29 bool &flag_;
30 } guard(this->draining_);
31
32 while (this->count_ > 0) {
33 Entry *front = this->queue_[this->head_];
34
35 ssize_t sent = socket->write(front->current_data(), front->remaining());
36
37 if (sent <= 0) {
38 // -1 = error (caller checks errno for EWOULDBLOCK vs hard error)
39 // 0 = nothing sent (treat as no progress)
40 return sent;
41 }
42
43 if (static_cast<uint16_t>(sent) < front->remaining()) {
44 // Partially sent, update offset and stop
45 front->offset += static_cast<uint16_t>(sent);
46 return sent;
47 }
48
49 // Entry fully sent — unlink it before freeing so a freed pointer is never
50 // reachable from the queue
51 this->queue_[this->head_] = nullptr;
52 this->head_ = (this->head_ + 1) % API_MAX_SEND_QUEUE;
53 this->count_--;
54 Entry::destroy(front);
55 }
56
57 return 0; // All drained
58}
59
60bool APIOverflowBuffer::enqueue_iov(const struct iovec *iov, int iovcnt, uint16_t total_len, uint16_t skip) {
61 if (this->count_ >= API_MAX_SEND_QUEUE)
62 return false;
63
64 uint16_t buffer_size = total_len - skip;
65 // nothrow: a failed allocation returns nullptr so the connection is dropped
66 // cleanly instead of plain new's crash or abort on OOM
67 // NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
68 auto *data = new (std::nothrow) uint8_t[buffer_size];
69 if (data == nullptr)
70 return false;
71 // NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
72 auto *entry = new (std::nothrow) Entry{data, buffer_size, 0};
73 if (entry == nullptr) {
74 delete[] data;
75 return false;
76 }
77
78 uint16_t to_skip = skip;
79 uint16_t write_pos = 0;
80
81 for (int i = 0; i < iovcnt; i++) {
82 if (to_skip >= iov[i].iov_len) {
83 to_skip -= static_cast<uint16_t>(iov[i].iov_len);
84 } else {
85 const uint8_t *src = reinterpret_cast<uint8_t *>(iov[i].iov_base) + to_skip;
86 uint16_t len = static_cast<uint16_t>(iov[i].iov_len) - to_skip;
87 std::memcpy(entry->data + write_pos, src, len);
88 write_pos += len;
89 to_skip = 0;
90 }
91 }
92
93 // Publish only after the copy completes so a half-built entry is never reachable
94 this->queue_[this->tail_] = entry;
95 this->tail_ = (this->tail_ + 1) % API_MAX_SEND_QUEUE;
96 this->count_++;
97 return true;
98}
99
100} // namespace esphome::api
101
102#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