mirror of
https://github.com/esphome/esphome.git
synced 2026-09-15 17:18:40 +00:00
Merge remote-tracking branch 'origin/api/peel-first-write-iteration' into integration
This commit is contained in:
@@ -100,6 +100,12 @@ const LogString *api_error_to_logstr(APIError err) {
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
|
||||
#ifdef HELPER_LOG_PACKETS
|
||||
void APIFrameHelper::log_packet_sending_(const void *data, uint16_t len) {
|
||||
LOG_PACKET_SENDING(reinterpret_cast<const uint8_t *>(data), len);
|
||||
}
|
||||
#endif
|
||||
|
||||
APIError APIFrameHelper::drain_overflow_and_handle_errors_() {
|
||||
if (this->overflow_buf_.try_drain(this->socket_.get()) == -1) {
|
||||
int err = errno;
|
||||
@@ -115,7 +121,13 @@ APIError APIFrameHelper::drain_overflow_and_handle_errors_() {
|
||||
// Single-buffer write path: wraps in iovec and delegates.
|
||||
APIError APIFrameHelper::write_raw_buf_(const void *data, uint16_t len, ssize_t sent) {
|
||||
struct iovec iov = {const_cast<void *>(data), len};
|
||||
return this->write_raw_iov_(&iov, 1, len, sent);
|
||||
APIError err = this->write_raw_iov_(&iov, 1, len, sent);
|
||||
#ifdef HELPER_LOG_PACKETS
|
||||
// Log after write/enqueue so re-entrant log sends can't corrupt data before it's sent
|
||||
if (err == APIError::OK)
|
||||
LOG_PACKET_SENDING(reinterpret_cast<const uint8_t *>(data), len);
|
||||
#endif
|
||||
return err;
|
||||
}
|
||||
|
||||
// Handles partial writes, errors, and overflow buffering.
|
||||
@@ -152,7 +164,7 @@ 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_)
|
||||
// Full write completed (possible when called directly, not via write_raw_fast_buf_)
|
||||
if (sent == static_cast<ssize_t>(total_write_len))
|
||||
return APIError::OK;
|
||||
|
||||
|
||||
@@ -207,30 +207,25 @@ class APIFrameHelper {
|
||||
inline APIError ESPHOME_ALWAYS_INLINE write_raw_fast_buf_(const void *data, uint16_t len) {
|
||||
if (this->overflow_buf_.empty()) [[likely]] {
|
||||
ssize_t sent = this->socket_->write(data, len);
|
||||
if (sent == static_cast<ssize_t>(len)) [[likely]]
|
||||
if (sent == static_cast<ssize_t>(len)) [[likely]] {
|
||||
#ifdef HELPER_LOG_PACKETS
|
||||
this->log_packet_sending_(data, len);
|
||||
#endif
|
||||
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, WRITE_NOT_ATTEMPTED);
|
||||
}
|
||||
inline APIError ESPHOME_ALWAYS_INLINE write_raw_fast_iov_(const struct iovec *iov, int iovcnt,
|
||||
uint16_t total_write_len) {
|
||||
if (this->overflow_buf_.empty()) [[likely]] {
|
||||
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, WRITE_NOT_ATTEMPTED);
|
||||
}
|
||||
|
||||
// Out-of-line write paths: handle partial writes, errors, overflow buffering
|
||||
// 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);
|
||||
#ifdef HELPER_LOG_PACKETS
|
||||
void log_packet_sending_(const void *data, uint16_t len);
|
||||
#endif
|
||||
|
||||
// Socket ownership (4 bytes on 32-bit, 8 bytes on 64-bit)
|
||||
std::unique_ptr<socket::Socket> socket_;
|
||||
|
||||
@@ -47,15 +47,8 @@ static constexpr size_t API_MAX_LOG_BYTES = 168;
|
||||
format_hex_pretty_to(hex_buf_, (buffer).data(), \
|
||||
(buffer).size() < API_MAX_LOG_BYTES ? (buffer).size() : API_MAX_LOG_BYTES)); \
|
||||
} while (0)
|
||||
#define LOG_PACKET_SENDING(data, len) \
|
||||
do { \
|
||||
char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \
|
||||
ESP_LOGVV(TAG, "Sending raw: %s", \
|
||||
format_hex_pretty_to(hex_buf_, data, (len) < API_MAX_LOG_BYTES ? (len) : API_MAX_LOG_BYTES)); \
|
||||
} while (0)
|
||||
#else
|
||||
#define LOG_PACKET_RECEIVED(buffer) ((void) 0)
|
||||
#define LOG_PACKET_SENDING(data, len) ((void) 0)
|
||||
#endif
|
||||
|
||||
/// Convert a noise error code to a readable error
|
||||
@@ -452,10 +445,10 @@ APIError APINoiseFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
buffer->type = type;
|
||||
return APIError::OK;
|
||||
}
|
||||
// Encrypt a single noise message in place and populate the iovec.
|
||||
// Encrypt a single noise message in place and return the encrypted frame length.
|
||||
// Returns APIError::OK on success.
|
||||
APIError APINoiseFrameHelper::encrypt_noise_message_(uint8_t *buf_start, const MessageInfo &msg,
|
||||
struct iovec &iov_out) {
|
||||
uint16_t &encrypted_len_out) {
|
||||
// Write noise header
|
||||
buf_start[0] = 0x01; // indicator
|
||||
// buf_start[1], buf_start[2] to be set after encryption
|
||||
@@ -482,9 +475,7 @@ APIError APINoiseFrameHelper::encrypt_noise_message_(uint8_t *buf_start, const M
|
||||
buf_start[1] = static_cast<uint8_t>(mbuf.size >> 8);
|
||||
buf_start[2] = static_cast<uint8_t>(mbuf.size);
|
||||
|
||||
// Populate iovec for this encrypted message
|
||||
size_t msg_len = static_cast<size_t>(3 + mbuf.size); // indicator + size + encrypted data
|
||||
iov_out = {buf_start, msg_len};
|
||||
encrypted_len_out = static_cast<uint16_t>(3 + mbuf.size); // indicator + size + encrypted data
|
||||
return APIError::OK;
|
||||
}
|
||||
|
||||
@@ -499,13 +490,11 @@ APIError APINoiseFrameHelper::write_protobuf_packet(uint8_t type, ProtoWriteBuff
|
||||
|
||||
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);
|
||||
uint16_t encrypted_len;
|
||||
APIError aerr = this->encrypt_noise_message_(buf_start, msg, encrypted_len);
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
// 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));
|
||||
return this->write_raw_fast_buf_(buf_start, encrypted_len);
|
||||
}
|
||||
|
||||
APIError APINoiseFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffer, std::span<const MessageInfo> messages) {
|
||||
@@ -514,26 +503,23 @@ APIError APINoiseFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffer, s
|
||||
assert(!messages.empty());
|
||||
#endif
|
||||
|
||||
// Noise messages are already contiguous in the buffer:
|
||||
// HEADER_PADDING (7) exactly matches the fixed header size, and
|
||||
// footer space (16) is consumed by the encryption MAC.
|
||||
uint8_t *buffer_data = buffer.get_buffer()->data();
|
||||
StaticVector<struct iovec, MAX_MESSAGES_PER_BATCH> iovs;
|
||||
uint8_t *write_start = buffer_data + messages[0].offset;
|
||||
uint16_t total_write_len = 0;
|
||||
|
||||
for (const auto &msg : messages) {
|
||||
uint8_t *buf_start = buffer_data + msg.offset;
|
||||
struct iovec iov;
|
||||
APIError aerr = this->encrypt_noise_message_(buf_start, msg, iov);
|
||||
uint16_t encrypted_len;
|
||||
APIError aerr = this->encrypt_noise_message_(buf_start, msg, encrypted_len);
|
||||
if (aerr != APIError::OK)
|
||||
return aerr;
|
||||
iovs.push_back(iov);
|
||||
total_write_len += iov.iov_len;
|
||||
total_write_len += encrypted_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);
|
||||
return this->write_raw_fast_buf_(write_start, total_write_len);
|
||||
}
|
||||
|
||||
APIError APINoiseFrameHelper::write_frame_(const uint8_t *data, uint16_t len) {
|
||||
|
||||
@@ -31,7 +31,7 @@ class APINoiseFrameHelper final : public APIFrameHelper {
|
||||
APIError state_action_();
|
||||
APIError try_read_frame_();
|
||||
APIError write_frame_(const uint8_t *data, uint16_t len);
|
||||
APIError encrypt_noise_message_(uint8_t *buf_start, const MessageInfo &msg, struct iovec &iov_out);
|
||||
APIError encrypt_noise_message_(uint8_t *buf_start, const MessageInfo &msg, uint16_t &encrypted_len_out);
|
||||
APIError init_handshake_();
|
||||
APIError check_handshake_finished_();
|
||||
void send_explicit_handshake_reject_(const LogString *reason);
|
||||
|
||||
@@ -39,15 +39,8 @@ static constexpr size_t API_MAX_LOG_BYTES = 168;
|
||||
format_hex_pretty_to(hex_buf_, (buffer).data(), \
|
||||
(buffer).size() < API_MAX_LOG_BYTES ? (buffer).size() : API_MAX_LOG_BYTES)); \
|
||||
} while (0)
|
||||
#define LOG_PACKET_SENDING(data, len) \
|
||||
do { \
|
||||
char hex_buf_[format_hex_pretty_size(API_MAX_LOG_BYTES)]; \
|
||||
ESP_LOGVV(TAG, "Sending raw: %s", \
|
||||
format_hex_pretty_to(hex_buf_, data, (len) < API_MAX_LOG_BYTES ? (len) : API_MAX_LOG_BYTES)); \
|
||||
} while (0)
|
||||
#else
|
||||
#define LOG_PACKET_RECEIVED(buffer) ((void) 0)
|
||||
#define LOG_PACKET_SENDING(data, len) ((void) 0)
|
||||
#endif
|
||||
|
||||
/// Initialize the frame helper, returns OK if successful.
|
||||
@@ -234,14 +227,44 @@ APIError APIPlaintextFrameHelper::read_packet(ReadPacketBuffer *buffer) {
|
||||
buffer->type = this->rx_header_parsed_type_;
|
||||
return APIError::OK;
|
||||
}
|
||||
// Compute varint encoded length for a 16-bit value (1, 2, or 3 bytes).
|
||||
ESPHOME_ALWAYS_INLINE static inline uint8_t varint_encoded_length_16(uint16_t value) {
|
||||
return value < ProtoSize::VARINT_THRESHOLD_1_BYTE ? 1 : (value < ProtoSize::VARINT_THRESHOLD_2_BYTE ? 2 : 3);
|
||||
}
|
||||
|
||||
// Compute varint encoded length for an 8-bit value (1 or 2 bytes).
|
||||
ESPHOME_ALWAYS_INLINE static inline uint8_t varint_encoded_length_8(uint8_t value) {
|
||||
return value < ProtoSize::VARINT_THRESHOLD_1_BYTE ? 1 : 2;
|
||||
}
|
||||
|
||||
// Encode a 16-bit varint (1-3 bytes) using pre-computed length.
|
||||
ESPHOME_ALWAYS_INLINE static inline void encode_varint_16(uint16_t value, uint8_t varint_len, uint8_t *p) {
|
||||
if (varint_len >= 2) {
|
||||
*p++ = static_cast<uint8_t>(value | 0x80);
|
||||
value >>= 7;
|
||||
if (varint_len == 3) {
|
||||
*p++ = static_cast<uint8_t>(value | 0x80);
|
||||
value >>= 7;
|
||||
}
|
||||
}
|
||||
*p = static_cast<uint8_t>(value);
|
||||
}
|
||||
|
||||
// Encode an 8-bit varint (1-2 bytes) using pre-computed length.
|
||||
ESPHOME_ALWAYS_INLINE static inline void encode_varint_8(uint8_t value, uint8_t varint_len, uint8_t *p) {
|
||||
if (varint_len == 2) {
|
||||
*p++ = static_cast<uint8_t>(value | 0x80);
|
||||
*p = static_cast<uint8_t>(value >> 7);
|
||||
} else {
|
||||
*p = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Write plaintext header into pre-allocated padding before payload.
|
||||
// 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
|
||||
: (msg.payload_size < ProtoSize::VARINT_THRESHOLD_2_BYTE ? 2 : 3);
|
||||
uint8_t type_varint_len = msg.message_type < ProtoSize::VARINT_THRESHOLD_1_BYTE ? 1 : 2;
|
||||
uint8_t size_varint_len = varint_encoded_length_16(msg.payload_size);
|
||||
uint8_t type_varint_len = varint_encoded_length_8(msg.message_type);
|
||||
uint8_t total_header_len = 1 + size_varint_len + type_varint_len;
|
||||
|
||||
// Calculate where to start writing the header
|
||||
@@ -274,9 +297,9 @@ ESPHOME_ALWAYS_INLINE static inline uint8_t write_plaintext_header(uint8_t *buf_
|
||||
// Write the plaintext header
|
||||
buf_start[header_offset] = 0x00; // indicator
|
||||
|
||||
// Encode varints directly into buffer
|
||||
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);
|
||||
// Encode varints directly into buffer using pre-computed lengths
|
||||
encode_varint_16(msg.payload_size, size_varint_len, buf_start + header_offset + 1);
|
||||
encode_varint_8(msg.message_type, type_varint_len, buf_start + header_offset + 1 + size_varint_len);
|
||||
|
||||
return total_header_len;
|
||||
}
|
||||
@@ -291,7 +314,6 @@ APIError APIPlaintextFrameHelper::write_protobuf_packet(uint8_t type, ProtoWrite
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -302,23 +324,28 @@ APIError APIPlaintextFrameHelper::write_protobuf_messages(ProtoWriteBuffer buffe
|
||||
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;
|
||||
uint8_t *write_start = nullptr;
|
||||
uint8_t *write_end = nullptr;
|
||||
|
||||
// Write headers and compact messages to close 0-3 byte varint padding gaps.
|
||||
// First iteration records start position via continue; subsequent iterations
|
||||
// memmove to close gaps between messages.
|
||||
for (const auto &msg : messages) {
|
||||
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;
|
||||
uint8_t *src = buffer_data + msg.offset + HEADER_PADDING - header_len;
|
||||
uint16_t msg_len = header_len + msg.payload_size;
|
||||
if (write_start == nullptr) {
|
||||
write_start = src;
|
||||
write_end = src + msg_len;
|
||||
continue;
|
||||
}
|
||||
if (src != write_end) {
|
||||
memmove(write_end, src, msg_len);
|
||||
}
|
||||
write_end += 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);
|
||||
return this->write_raw_fast_buf_(write_start, static_cast<uint16_t>(write_end - write_start));
|
||||
}
|
||||
|
||||
} // namespace esphome::api
|
||||
|
||||
Reference in New Issue
Block a user