mirror of
https://github.com/esphome/esphome.git
synced 2026-09-20 19:48:39 +00:00
Merge remote-tracking branch 'upstream/api/peel-first-write-iteration' into integration
This commit is contained in:
@@ -119,32 +119,29 @@ APIError APIFrameHelper::write_raw_buf_(const void *data, uint16_t len, ssize_t
|
||||
}
|
||||
|
||||
// Handles partial writes, errors, and overflow buffering.
|
||||
// Called when the inline fast path in the header couldn't complete the write,
|
||||
// Called when the inline fast path couldn't complete the write,
|
||||
// or directly from cold paths (handshake, error handling).
|
||||
// sent == -1 means either the fast path write returned -1, or there was overflow backlog.
|
||||
APIError APIFrameHelper::write_raw_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len, ssize_t sent) {
|
||||
#ifdef HELPER_LOG_PACKETS
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
LOG_PACKET_SENDING(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (sent == -1) {
|
||||
// Either the fast path got -1, or we were called with overflow backlog
|
||||
if (!this->overflow_buf_.empty()) {
|
||||
// Drain existing backlog first
|
||||
APIError err = this->drain_overflow_and_handle_errors_();
|
||||
if (err != APIError::OK)
|
||||
return err;
|
||||
// Try again after drain
|
||||
if (sent <= 0) {
|
||||
if (sent == WRITE_NOT_ATTEMPTED) {
|
||||
// Cold path: no write attempted yet, drain overflow and try
|
||||
if (!this->overflow_buf_.empty()) {
|
||||
APIError err = this->drain_overflow_and_handle_errors_();
|
||||
if (err != APIError::OK)
|
||||
return err;
|
||||
}
|
||||
if (this->overflow_buf_.empty()) {
|
||||
sent =
|
||||
(iovcnt == 1) ? this->socket_->write(iov[0].iov_base, iov[0].iov_len) : this->socket_->writev(iov, iovcnt);
|
||||
sent = this->write_iov_to_socket_(iov, iovcnt);
|
||||
if (sent == static_cast<ssize_t>(total_write_len))
|
||||
return APIError::OK;
|
||||
// Partial write or -1: fall through to error check / enqueue below
|
||||
} else {
|
||||
// Overflow backlog remains after drain; skip socket write, enqueue everything
|
||||
sent = 0;
|
||||
}
|
||||
}
|
||||
if (sent == -1) {
|
||||
// WRITE_FAILED (-1): fast path or retry write returned -1, check errno
|
||||
if (sent == WRITE_FAILED) {
|
||||
int err = errno;
|
||||
if (err != EWOULDBLOCK && err != EAGAIN) {
|
||||
this->state_ = State::FAILED;
|
||||
@@ -155,6 +152,10 @@ APIError APIFrameHelper::write_raw_iov_(const struct iovec *iov, int iovcnt, uin
|
||||
}
|
||||
}
|
||||
|
||||
// Full write completed (possible when called directly, not via write_raw_fast_iov_)
|
||||
if (sent == static_cast<ssize_t>(total_write_len))
|
||||
return APIError::OK;
|
||||
|
||||
// Queue unsent data into overflow buffer
|
||||
if (!this->overflow_buf_.enqueue_iov(iov, iovcnt, total_write_len, static_cast<uint16_t>(sent))) {
|
||||
HELPER_LOG("Overflow buffer full, dropping connection");
|
||||
|
||||
@@ -192,32 +192,45 @@ class APIFrameHelper {
|
||||
// Returns OK for transient errors (WOULD_BLOCK), SOCKET_WRITE_FAILED for hard errors.
|
||||
APIError drain_overflow_and_handle_errors_();
|
||||
|
||||
// Sentinel values for the sent parameter in write_raw_ methods
|
||||
static constexpr ssize_t WRITE_FAILED = -1; // Fast path: write()/writev() returned -1
|
||||
static constexpr ssize_t WRITE_NOT_ATTEMPTED = -2; // Cold path: no write attempted yet
|
||||
|
||||
// Dispatch to write() or writev() based on iovec count
|
||||
inline ssize_t ESPHOME_ALWAYS_INLINE write_iov_to_socket_(const struct iovec *iov, int iovcnt) {
|
||||
return (iovcnt == 1) ? this->socket_->write(iov[0].iov_base, iov[0].iov_len) : this->socket_->writev(iov, iovcnt);
|
||||
}
|
||||
|
||||
// Inlined write methods — used by hot paths (write_protobuf_packet, write_protobuf_messages)
|
||||
// These inline the fast path (overflow empty + full write) and tail-call the out-of-line
|
||||
// slow path only on failure/partial write.
|
||||
inline APIError ESPHOME_ALWAYS_INLINE write_raw_fast_buf_(const void *data, uint16_t len) {
|
||||
ssize_t sent = -1;
|
||||
if (this->overflow_buf_.empty()) [[likely]] {
|
||||
sent = this->socket_->write(data, len);
|
||||
ssize_t sent = this->socket_->write(data, len);
|
||||
if (sent == static_cast<ssize_t>(len)) [[likely]]
|
||||
return APIError::OK;
|
||||
// sent is -1 (WRITE_FAILED) or partial write count
|
||||
return this->write_raw_buf_(data, len, sent);
|
||||
}
|
||||
return this->write_raw_buf_(data, len, sent);
|
||||
return this->write_raw_buf_(data, len, WRITE_NOT_ATTEMPTED);
|
||||
}
|
||||
inline APIError ESPHOME_ALWAYS_INLINE write_raw_fast_iov_(const struct iovec *iov, int iovcnt,
|
||||
uint16_t total_write_len) {
|
||||
ssize_t sent = -1;
|
||||
if (this->overflow_buf_.empty()) [[likely]] {
|
||||
sent = this->socket_->writev(iov, iovcnt);
|
||||
ssize_t sent = this->socket_->writev(iov, iovcnt);
|
||||
if (sent == static_cast<ssize_t>(total_write_len)) [[likely]]
|
||||
return APIError::OK;
|
||||
// sent is -1 (WRITE_FAILED) or partial write count
|
||||
return this->write_raw_iov_(iov, iovcnt, total_write_len, sent);
|
||||
}
|
||||
return this->write_raw_iov_(iov, iovcnt, total_write_len, sent);
|
||||
return this->write_raw_iov_(iov, iovcnt, total_write_len, WRITE_NOT_ATTEMPTED);
|
||||
}
|
||||
|
||||
// Out-of-line write paths: handle partial writes, errors, overflow buffering
|
||||
APIError write_raw_buf_(const void *data, uint16_t len, ssize_t sent = -1);
|
||||
APIError write_raw_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len, ssize_t sent = -1);
|
||||
// sent: WRITE_NOT_ATTEMPTED (cold path), WRITE_FAILED (fast path write returned -1), or bytes sent (partial write)
|
||||
APIError write_raw_buf_(const void *data, uint16_t len, ssize_t sent = WRITE_NOT_ATTEMPTED);
|
||||
APIError write_raw_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len,
|
||||
ssize_t sent = WRITE_NOT_ATTEMPTED);
|
||||
|
||||
// Socket ownership (4 bytes on 32-bit, 8 bytes on 64-bit)
|
||||
std::unique_ptr<socket::Socket> socket_;
|
||||
|
||||
@@ -497,14 +497,15 @@ APIError APINoiseFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuff
|
||||
if (frame_footer_size_)
|
||||
buffer.get_buffer()->resize(buffer.get_buffer()->size() + frame_footer_size_);
|
||||
|
||||
MessageInfo msg{type, 0,
|
||||
static_cast<uint16_t>(buffer.get_buffer()->size() - frame_header_padding_ - frame_footer_size_)};
|
||||
MessageInfo msg{type, 0, static_cast<uint16_t>(buffer.get_buffer()->size() - HEADER_PADDING - frame_footer_size_)};
|
||||
uint8_t *buf_start = buffer.get_buffer()->data();
|
||||
struct iovec iov;
|
||||
APIError aerr = this->encrypt_noise_message_(buf_start, msg, iov);
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
return this->write_raw_fast_buf_(iov.iov_base, static_cast<uint16_t>(iov.iov_len));
|
||||
// buf_start and iov.iov_base point to the same location
|
||||
LOG_PACKET_SENDING(buf_start, iov.iov_len);
|
||||
return this->write_raw_fast_buf_(buf_start, static_cast<uint16_t>(iov.iov_len));
|
||||
}
|
||||
|
||||
APIError APINoiseFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffer, std::span<const MessageInfo> messages) {
|
||||
@@ -527,6 +528,11 @@ APIError APINoiseFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffer, s
|
||||
total_write_len += iov.iov_len;
|
||||
}
|
||||
|
||||
#ifdef HELPER_LOG_PACKETS
|
||||
for (const auto &iov : iovs) {
|
||||
LOG_PACKET_SENDING(reinterpret_cast<uint8_t *>(iov.iov_base), iov.iov_len);
|
||||
}
|
||||
#endif
|
||||
return this->write_raw_fast_iov_(iovs.data(), iovs.size(), total_write_len);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,14 +9,16 @@ namespace esphome::api {
|
||||
|
||||
class APINoiseFrameHelper final : public APIFrameHelper {
|
||||
public:
|
||||
// Noise header structure:
|
||||
// Pos 0: indicator (0x01)
|
||||
// Pos 1-2: encrypted payload size (16-bit big-endian)
|
||||
// Pos 3-6: encrypted type (16-bit) + data_len (16-bit)
|
||||
// Pos 7+: actual payload data
|
||||
static constexpr uint8_t HEADER_PADDING = 1 + 2 + 2 + 2; // indicator + size + type + data_len
|
||||
|
||||
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, APINoiseContext &ctx)
|
||||
: APIFrameHelper(std::move(socket)), ctx_(ctx) {
|
||||
// Noise header structure:
|
||||
// Pos 0: indicator (0x01)
|
||||
// Pos 1-2: encrypted payload size (16-bit big-endian)
|
||||
// Pos 3-6: encrypted type (16-bit) + data_len (16-bit)
|
||||
// Pos 7+: actual payload data
|
||||
frame_header_padding_ = 7;
|
||||
frame_header_padding_ = HEADER_PADDING;
|
||||
}
|
||||
~APINoiseFrameHelper() override;
|
||||
APIError init() override;
|
||||
|
||||
@@ -235,9 +235,8 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
return APIError::OK;
|
||||
}
|
||||
// Write plaintext header into pre-allocated padding before payload.
|
||||
// Returns pointer to start of frame (header + payload are contiguous).
|
||||
ESPHOME_ALWAYS_INLINE static inline uint8_t *write_plaintext_header(uint8_t *buf_start, const MessageInfo &msg,
|
||||
uint8_t frame_header_padding) {
|
||||
// Returns the total header length (indicator + varints).
|
||||
ESPHOME_ALWAYS_INLINE static inline uint8_t write_plaintext_header(uint8_t *buf_start, const MessageInfo &msg) {
|
||||
// Calculate varint sizes for header layout using inline ternary to avoid varint_slow call overhead
|
||||
uint8_t size_varint_len = msg.payload_size < ProtoSize::VARINT_THRESHOLD_1_BYTE
|
||||
? 1
|
||||
@@ -269,8 +268,8 @@ ESPHOME_ALWAYS_INLINE static inline uint8_t *write_plaintext_header(uint8_t *buf
|
||||
// [6...] - Actual payload data
|
||||
//
|
||||
// The message starts at offset + frame_header_padding
|
||||
// So we write the header starting at offset + frame_header_padding - total_header_len
|
||||
uint32_t header_offset = frame_header_padding - total_header_len;
|
||||
// So we write the header starting at offset + HEADER_PADDING - total_header_len
|
||||
uint32_t header_offset = APIPlaintextFrameHelper::HEADER_PADDING - total_header_len;
|
||||
|
||||
// Write the plaintext header
|
||||
buf_start[header_offset] = 0x00; // indicator
|
||||
@@ -279,7 +278,7 @@ ESPHOME_ALWAYS_INLINE static inline uint8_t *write_plaintext_header(uint8_t *buf
|
||||
encode_varint_to_buffer(msg.payload_size, buf_start + header_offset + 1);
|
||||
encode_varint_to_buffer(msg.message_type, buf_start + header_offset + 1 + size_varint_len);
|
||||
|
||||
return buf_start + header_offset;
|
||||
return total_header_len;
|
||||
}
|
||||
|
||||
APIError APIPlaintextFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuffer buffer) {
|
||||
@@ -287,11 +286,12 @@ APIError APIPlaintextFrameHelper::write_protobuf_packet(uint8_t type, ProtoWrite
|
||||
assert(this->state_ == State::DATA);
|
||||
#endif
|
||||
|
||||
MessageInfo msg{type, 0, static_cast<uint16_t>(buffer.get_buffer()->size() - frame_header_padding_)};
|
||||
MessageInfo msg{type, 0, static_cast<uint16_t>(buffer.get_buffer()->size() - HEADER_PADDING)};
|
||||
uint8_t *buffer_data = buffer.get_buffer()->data();
|
||||
uint8_t *msg_start = write_plaintext_header(buffer_data, msg, frame_header_padding_);
|
||||
uint8_t msg_header_len = static_cast<uint8_t>(buffer_data + frame_header_padding_ - msg_start);
|
||||
uint16_t msg_len = static_cast<uint16_t>(msg_header_len + msg.payload_size);
|
||||
uint8_t header_len = write_plaintext_header(buffer_data, msg);
|
||||
uint8_t *msg_start = buffer_data + HEADER_PADDING - header_len;
|
||||
uint16_t msg_len = static_cast<uint16_t>(header_len + msg.payload_size);
|
||||
LOG_PACKET_SENDING(msg_start, msg_len);
|
||||
return this->write_raw_fast_buf_(msg_start, msg_len);
|
||||
}
|
||||
|
||||
@@ -301,20 +301,23 @@ APIError APIPlaintextFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffe
|
||||
assert(this->state_ == State::DATA);
|
||||
assert(!messages.empty());
|
||||
#endif
|
||||
|
||||
uint8_t *buffer_data = buffer.get_buffer()->data();
|
||||
StaticVector<struct iovec, MAX_MESSAGES_PER_BATCH> iovs;
|
||||
uint16_t total_write_len = 0;
|
||||
const uint8_t padding = frame_header_padding_;
|
||||
|
||||
for (const auto &msg : messages) {
|
||||
uint8_t *msg_start = write_plaintext_header(buffer_data + msg.offset, msg, padding);
|
||||
uint8_t msg_header_len = static_cast<uint8_t>((buffer_data + msg.offset + padding) - msg_start);
|
||||
size_t msg_len = static_cast<size_t>(msg_header_len + msg.payload_size);
|
||||
uint8_t header_len = write_plaintext_header(buffer_data + msg.offset, msg);
|
||||
uint8_t *msg_start = buffer_data + msg.offset + HEADER_PADDING - header_len;
|
||||
size_t msg_len = static_cast<size_t>(header_len + msg.payload_size);
|
||||
iovs.push_back({msg_start, msg_len});
|
||||
total_write_len += msg_len;
|
||||
}
|
||||
|
||||
#ifdef HELPER_LOG_PACKETS
|
||||
for (const auto &iov : iovs) {
|
||||
LOG_PACKET_SENDING(reinterpret_cast<uint8_t *>(iov.iov_base), iov.iov_len);
|
||||
}
|
||||
#endif
|
||||
return this->write_raw_fast_iov_(iovs.data(), iovs.size(), total_write_len);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,13 +7,15 @@ namespace esphome::api {
|
||||
|
||||
class APIPlaintextFrameHelper final : public APIFrameHelper {
|
||||
public:
|
||||
// Plaintext header structure (worst case):
|
||||
// Pos 0: indicator (0x00)
|
||||
// Pos 1-3: payload size varint (up to 3 bytes)
|
||||
// Pos 4-5: message type varint (up to 2 bytes)
|
||||
// Pos 6+: actual payload data
|
||||
static constexpr uint8_t HEADER_PADDING = 1 + 3 + 2; // indicator + size varint + type varint
|
||||
|
||||
explicit APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket) : APIFrameHelper(std::move(socket)) {
|
||||
// Plaintext header structure (worst case):
|
||||
// Pos 0: indicator (0x00)
|
||||
// Pos 1-3: payload size varint (up to 3 bytes)
|
||||
// Pos 4-5: message type varint (up to 2 bytes)
|
||||
// Pos 6+: actual payload data
|
||||
frame_header_padding_ = 6;
|
||||
frame_header_padding_ = HEADER_PADDING;
|
||||
}
|
||||
~APIPlaintextFrameHelper() override = default;
|
||||
APIError init() override;
|
||||
|
||||
Reference in New Issue
Block a user