Merge branch 'get_peername_stack_save_ram' into integration

This commit is contained in:
J. Nick Koston
2025-12-27 18:18:55 -10:00
9 changed files with 43 additions and 53 deletions

View File

@@ -101,16 +101,14 @@ APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *pa
#if defined(USE_API_PLAINTEXT) && defined(USE_API_NOISE)
auto &noise_ctx = parent->get_noise_ctx();
if (noise_ctx.has_psk()) {
this->helper_ =
std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), noise_ctx, &this->client_info_)};
this->helper_ = std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), noise_ctx)};
} else {
this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock), &this->client_info_)};
this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
}
#elif defined(USE_API_PLAINTEXT)
this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock), &this->client_info_)};
this->helper_ = std::unique_ptr<APIFrameHelper>{new APIPlaintextFrameHelper(std::move(sock))};
#elif defined(USE_API_NOISE)
this->helper_ = std::unique_ptr<APIFrameHelper>{
new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx(), &this->client_info_)};
this->helper_ = std::unique_ptr<APIFrameHelper>{new APINoiseFrameHelper(std::move(sock), parent->get_noise_ctx())};
#else
#error "No frame helper defined"
#endif
@@ -133,8 +131,8 @@ void APIConnection::start() {
}
// Initialize client name with peername (IP address) until Hello message provides actual name
char peername[socket::PEERNAME_MAX_LEN];
this->helper_->getpeername_to(peername);
strncpy(this->client_info_.name, peername, sizeof(this->client_info_.name) - 1);
size_t len = this->helper_->getpeername_to(peername);
this->helper_->set_client_name(peername, len);
}
APIConnection::~APIConnection() {
@@ -1507,8 +1505,10 @@ void APIConnection::complete_authentication_() {
this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::AUTHENTICATED);
this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("connected"));
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
// Trigger expects std::string, get fresh peername from socket
this->parent_->get_client_connected_trigger()->trigger(this->client_info_.name, this->helper_->getpeername());
char peername_buf[socket::PEERNAME_MAX_LEN];
this->helper_->getpeername_to(peername_buf);
this->parent_->get_client_connected_trigger()->trigger(std::string(this->helper_->get_client_name()),
std::string(peername_buf));
#endif
#ifdef USE_HOMEASSISTANT_TIME
if (homeassistant::global_homeassistant_time != nullptr) {
@@ -1523,16 +1523,14 @@ void APIConnection::complete_authentication_() {
}
bool APIConnection::send_hello_response(const HelloRequest &msg) {
// Copy client name with truncation if needed
size_t copy_len = std::min(msg.client_info.size(), sizeof(this->client_info_.name) - 1);
memcpy(this->client_info_.name, msg.client_info.c_str(), copy_len);
this->client_info_.name[copy_len] = '\0';
// Copy client name with truncation if needed (set_client_name handles truncation)
this->helper_->set_client_name(reinterpret_cast<const char *>(msg.client_info), msg.client_info_len);
this->client_api_version_major_ = msg.api_version_major;
this->client_api_version_minor_ = msg.api_version_minor;
char peername[socket::PEERNAME_MAX_LEN];
this->helper_->getpeername_to(peername);
ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu32 ".%" PRIu32, this->client_info_.name, peername,
this->client_api_version_major_, this->client_api_version_minor_);
ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu32 ".%" PRIu32, this->helper_->get_client_name(),
peername, this->client_api_version_major_, this->client_api_version_minor_);
HelloResponse resp;
resp.api_version_major = 1;
@@ -2110,14 +2108,14 @@ void APIConnection::process_state_subscriptions_() {
void APIConnection::log_client_(int level, const LogString *message) {
char peername[socket::PEERNAME_MAX_LEN];
this->helper_->getpeername_to(peername);
esp_log_printf_(level, TAG, __LINE__, ESPHOME_LOG_FORMAT("%s (%s): %s"), this->client_info_.name, peername,
esp_log_printf_(level, TAG, __LINE__, ESPHOME_LOG_FORMAT("%s (%s): %s"), this->helper_->get_client_name(), peername,
LOG_STR_ARG(message));
}
void APIConnection::log_warning_(const LogString *message, APIError err) {
char peername[socket::PEERNAME_MAX_LEN];
this->helper_->getpeername_to(peername);
ESP_LOGW(TAG, "%s (%s): %s %s errno=%d", this->client_info_.name, peername, LOG_STR_ARG(message),
ESP_LOGW(TAG, "%s (%s): %s %s errno=%d", this->helper_->get_client_name(), peername, LOG_STR_ARG(message),
LOG_STR_ARG(api_error_to_logstr(err)), errno);
}

View File

@@ -16,16 +16,6 @@
namespace esphome::api {
// Client information structure - uses fixed buffer to avoid std::string heap allocation
// Max client name length (e.g., "Home Assistant 2026.1.0.dev0" = 28 chars)
static constexpr size_t CLIENT_INFO_NAME_MAX_LEN = 32;
struct ClientInfo {
char name[CLIENT_INFO_NAME_MAX_LEN]{}; // Client name from Hello message
// Note: peername (IP address) is not stored here to save memory.
// Use helper_->getpeername_to() when needed.
};
// Keepalive timeout in milliseconds
static constexpr uint32_t KEEPALIVE_TIMEOUT_MS = 60000;
// Maximum number of entities to process in a single batch during initial state/info sending
@@ -294,7 +284,7 @@ class APIConnection final : public APIServerConnection {
bool try_to_clear_buffer(bool log_out_of_space);
bool send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) override;
StringRef get_name() const { return StringRef(this->client_info_.name); }
const char *get_name() const { return this->helper_->get_client_name(); }
/// Get peer name (IP address) into a stack buffer - avoids heap allocation
size_t get_peername_to(std::span<char, socket::PEERNAME_MAX_LEN> buf) const {
return this->helper_->getpeername_to(buf);
@@ -532,10 +522,7 @@ class APIConnection final : public APIServerConnection {
std::unique_ptr<camera::CameraImageReader> image_reader_;
#endif
// Group 3: Client info struct (24 bytes on 32-bit: 2 strings × 12 bytes each)
ClientInfo client_info_;
// Group 4: 4-byte types
// Group 3: 4-byte types
uint32_t last_traffic_;
#ifdef USE_API_HOMEASSISTANT_STATES
int state_subs_at_ = -1;

View File

@@ -1,6 +1,5 @@
#include "api_frame_helper.h"
#ifdef USE_API
#include "api_connection.h" // For ClientInfo struct
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
@@ -18,7 +17,7 @@ static const char *const TAG = "api.frame_helper";
do { \
char peername__[socket::PEERNAME_MAX_LEN]; \
this->socket_->getpeername_to(peername__); \
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name, peername__, ##__VA_ARGS__); \
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_name_, peername__, ##__VA_ARGS__); \
} while (0)
#else
#define HELPER_LOG(msg, ...) ((void) 0)

View File

@@ -29,11 +29,11 @@ static constexpr uint16_t MAX_MESSAGE_SIZE = 8192; // 8 KiB for ESP8266
static constexpr uint16_t MAX_MESSAGE_SIZE = 32768; // 32 KiB for ESP32 and other platforms
#endif
// Forward declaration
struct ClientInfo;
class ProtoWriteBuffer;
// Max client name length (e.g., "Home Assistant 2026.1.0.dev0" = 28 chars)
static constexpr size_t CLIENT_INFO_NAME_MAX_LEN = 32;
struct ReadPacketBuffer {
const uint8_t *data; // Points directly into frame helper's rx_buf_ (valid until next read_packet call)
uint16_t data_len;
@@ -82,8 +82,16 @@ const LogString *api_error_to_logstr(APIError err);
class APIFrameHelper {
public:
APIFrameHelper() = default;
explicit APIFrameHelper(std::unique_ptr<socket::Socket> socket, const ClientInfo *client_info)
: socket_(std::move(socket)), client_info_(client_info) {}
explicit APIFrameHelper(std::unique_ptr<socket::Socket> socket) : socket_(std::move(socket)) {}
// Get client name (null-terminated)
const char *get_client_name() const { return this->client_name_; }
// Set client name from buffer with length (truncates if needed)
void set_client_name(const char *name, size_t len) {
size_t copy_len = std::min(len, sizeof(this->client_name_) - 1);
memcpy(this->client_name_, name, copy_len);
this->client_name_[copy_len] = '\0';
}
virtual ~APIFrameHelper() = default;
virtual APIError init() = 0;
virtual APIError loop();
@@ -190,9 +198,8 @@ class APIFrameHelper {
std::vector<struct iovec> reusable_iovs_;
std::vector<uint8_t> rx_buf_;
// Pointer to client info (4 bytes on 32-bit)
// Note: The pointed-to ClientInfo object must outlive this APIFrameHelper instance.
const ClientInfo *client_info_{nullptr};
// Client name buffer - stores name from Hello message or initial peername
char client_name_[CLIENT_INFO_NAME_MAX_LEN]{};
// Group smaller types together
uint16_t rx_buf_len_ = 0;

View File

@@ -29,7 +29,7 @@ static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit")
do { \
char peername__[socket::PEERNAME_MAX_LEN]; \
this->socket_->getpeername_to(peername__); \
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name, peername__, ##__VA_ARGS__); \
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_name_, peername__, ##__VA_ARGS__); \
} while (0)
#else
#define HELPER_LOG(msg, ...) ((void) 0)

View File

@@ -9,8 +9,8 @@ namespace esphome::api {
class APINoiseFrameHelper final : public APIFrameHelper {
public:
APINoiseFrameHelper(std::unique_ptr<socket::Socket> socket, APINoiseContext &ctx, const ClientInfo *client_info)
: APIFrameHelper(std::move(socket), client_info), ctx_(ctx) {
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)

View File

@@ -1,7 +1,6 @@
#include "api_frame_helper_plaintext.h"
#ifdef USE_API
#ifdef USE_API_PLAINTEXT
#include "api_connection.h" // For ClientInfo struct
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
@@ -23,7 +22,7 @@ static const char *const TAG = "api.plaintext";
do { \
char peername__[socket::PEERNAME_MAX_LEN]; \
this->socket_->getpeername_to(peername__); \
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name, peername__, ##__VA_ARGS__); \
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_name_, peername__, ##__VA_ARGS__); \
} while (0)
#else
#define HELPER_LOG(msg, ...) ((void) 0)

View File

@@ -7,8 +7,7 @@ namespace esphome::api {
class APIPlaintextFrameHelper final : public APIFrameHelper {
public:
APIPlaintextFrameHelper(std::unique_ptr<socket::Socket> socket, const ClientInfo *client_info)
: APIFrameHelper(std::move(socket), client_info) {
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)

View File

@@ -187,13 +187,14 @@ void APIServer::loop() {
// Rare case: handle disconnection
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
// Trigger expects std::string, get fresh peername from socket
this->client_disconnected_trigger_->trigger(client->client_info_.name, client->get_peername());
char peername_buf[socket::PEERNAME_MAX_LEN];
client->get_peername_to(peername_buf);
this->client_disconnected_trigger_->trigger(std::string(client->get_name()), std::string(peername_buf));
#endif
#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES
this->unregister_active_action_calls_for_connection(client.get());
#endif
ESP_LOGV(TAG, "Remove connection %s", client->client_info_.name);
ESP_LOGV(TAG, "Remove connection %s", client->get_name());
// Swap with the last element and pop (avoids expensive vector shifts)
if (client_index < this->clients_.size() - 1) {