mirror of
https://github.com/esphome/esphome.git
synced 2026-01-08 03:00:48 -07:00
[api] Eliminate std::string from ClientInfo struct (#12566)
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
This commit is contained in:
@@ -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
|
||||
@@ -131,8 +129,9 @@ void APIConnection::start() {
|
||||
this->fatal_error_with_log_(LOG_STR("Helper init failed"), err);
|
||||
return;
|
||||
}
|
||||
this->client_info_.peername = helper_->getpeername();
|
||||
this->client_info_.name = this->client_info_.peername;
|
||||
// Initialize client name with peername (IP address) until Hello message provides actual name
|
||||
const char *peername = this->helper_->get_client_peername();
|
||||
this->helper_->set_client_name(peername, strlen(peername));
|
||||
}
|
||||
|
||||
APIConnection::~APIConnection() {
|
||||
@@ -252,8 +251,7 @@ void APIConnection::loop() {
|
||||
// Disconnect if not responded within 2.5*keepalive
|
||||
if (now - this->last_traffic_ > KEEPALIVE_DISCONNECT_TIMEOUT) {
|
||||
on_fatal_error();
|
||||
ESP_LOGW(TAG, "%s (%s) is unresponsive; disconnecting", this->client_info_.name.c_str(),
|
||||
this->client_info_.peername.c_str());
|
||||
this->log_client_(ESPHOME_LOG_LEVEL_WARN, LOG_STR("is unresponsive; disconnecting"));
|
||||
}
|
||||
} else if (now - this->last_traffic_ > KEEPALIVE_TIMEOUT_MS && !this->flags_.remove) {
|
||||
// Only send ping if we're not disconnecting
|
||||
@@ -287,7 +285,7 @@ bool APIConnection::send_disconnect_response(const DisconnectRequest &msg) {
|
||||
// remote initiated disconnect_client
|
||||
// don't close yet, we still need to send the disconnect response
|
||||
// close will happen on next loop
|
||||
ESP_LOGD(TAG, "%s (%s) disconnected", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
|
||||
this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("disconnected"));
|
||||
this->flags_.next_close = true;
|
||||
DisconnectResponse resp;
|
||||
return this->send_message(resp, DisconnectResponse::MESSAGE_TYPE);
|
||||
@@ -1504,9 +1502,10 @@ void APIConnection::complete_authentication_() {
|
||||
}
|
||||
|
||||
this->flags_.connection_state = static_cast<uint8_t>(ConnectionState::AUTHENTICATED);
|
||||
ESP_LOGD(TAG, "%s (%s) connected", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
|
||||
this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("connected"));
|
||||
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER
|
||||
this->parent_->get_client_connected_trigger()->trigger(this->client_info_.name, this->client_info_.peername);
|
||||
this->parent_->get_client_connected_trigger()->trigger(std::string(this->helper_->get_client_name()),
|
||||
std::string(this->helper_->get_client_peername()));
|
||||
#endif
|
||||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
if (homeassistant::global_homeassistant_time != nullptr) {
|
||||
@@ -1521,12 +1520,12 @@ void APIConnection::complete_authentication_() {
|
||||
}
|
||||
|
||||
bool APIConnection::send_hello_response(const HelloRequest &msg) {
|
||||
this->client_info_.name.assign(msg.client_info.c_str(), msg.client_info.size());
|
||||
this->client_info_.peername = this->helper_->getpeername();
|
||||
// Copy client name with truncation if needed (set_client_name handles truncation)
|
||||
this->helper_->set_client_name(msg.client_info.c_str(), msg.client_info.size());
|
||||
this->client_api_version_major_ = msg.api_version_major;
|
||||
this->client_api_version_minor_ = msg.api_version_minor;
|
||||
ESP_LOGV(TAG, "Hello from client: '%s' | %s | API Version %" PRIu32 ".%" PRIu32, this->client_info_.name.c_str(),
|
||||
this->client_info_.peername.c_str(), 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(),
|
||||
this->helper_->get_client_peername(), this->client_api_version_major_, this->client_api_version_minor_);
|
||||
|
||||
HelloResponse resp;
|
||||
resp.api_version_major = 1;
|
||||
@@ -1836,7 +1835,7 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint8_t message_type) {
|
||||
}
|
||||
void APIConnection::on_no_setup_connection() {
|
||||
this->on_fatal_error();
|
||||
ESP_LOGD(TAG, "%s (%s) no connection setup", this->client_info_.name.c_str(), this->client_info_.peername.c_str());
|
||||
this->log_client_(ESPHOME_LOG_LEVEL_DEBUG, LOG_STR("no connection setup"));
|
||||
}
|
||||
void APIConnection::on_fatal_error() {
|
||||
this->helper_->close();
|
||||
@@ -2084,8 +2083,13 @@ void APIConnection::process_state_subscriptions_() {
|
||||
}
|
||||
#endif // USE_API_HOMEASSISTANT_STATES
|
||||
|
||||
void APIConnection::log_client_(int level, const LogString *message) {
|
||||
esp_log_printf_(level, TAG, __LINE__, ESPHOME_LOG_FORMAT("%s (%s): %s"), this->helper_->get_client_name(),
|
||||
this->helper_->get_client_peername(), LOG_STR_ARG(message));
|
||||
}
|
||||
|
||||
void APIConnection::log_warning_(const LogString *message, APIError err) {
|
||||
ESP_LOGW(TAG, "%s (%s): %s %s errno=%d", this->client_info_.name.c_str(), this->client_info_.peername.c_str(),
|
||||
ESP_LOGW(TAG, "%s (%s): %s %s errno=%d", this->helper_->get_client_name(), this->helper_->get_client_peername(),
|
||||
LOG_STR_ARG(message), LOG_STR_ARG(api_error_to_logstr(err)), errno);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,18 +9,13 @@
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/string_ref.h"
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome::api {
|
||||
|
||||
// Client information structure
|
||||
struct ClientInfo {
|
||||
std::string name; // Client name from Hello message
|
||||
std::string peername; // IP:port from socket
|
||||
};
|
||||
|
||||
// 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
|
||||
@@ -279,8 +274,9 @@ 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;
|
||||
|
||||
const std::string &get_name() const { return this->client_info_.name; }
|
||||
const std::string &get_peername() const { return this->client_info_.peername; }
|
||||
const char *get_name() const { return this->helper_->get_client_name(); }
|
||||
/// Get peer name (IP address) - cached at connection init time
|
||||
const char *get_peername() const { return this->helper_->get_client_peername(); }
|
||||
|
||||
protected:
|
||||
// Helper function to handle authentication completion
|
||||
@@ -526,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;
|
||||
@@ -756,6 +749,8 @@ class APIConnection final : public APIServerConnection {
|
||||
return this->schedule_batch_();
|
||||
}
|
||||
|
||||
// Helper function to log client messages with name and peername
|
||||
void log_client_(int level, const LogString *message);
|
||||
// Helper function to log API errors with errno
|
||||
void log_warning_(const LogString *message, APIError err);
|
||||
// Helper to handle fatal errors with logging
|
||||
|
||||
@@ -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"
|
||||
@@ -16,8 +15,11 @@ static const char *const TAG = "api.frame_helper";
|
||||
// Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512)
|
||||
static constexpr size_t API_MAX_LOG_BYTES = 168;
|
||||
|
||||
#define HELPER_LOG(msg, ...) \
|
||||
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__)
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s (%s): " msg, this->client_name_, this->client_peername_, ##__VA_ARGS__)
|
||||
#else
|
||||
#define HELPER_LOG(msg, ...) ((void) 0)
|
||||
#endif
|
||||
|
||||
#ifdef HELPER_LOG_PACKETS
|
||||
#define LOG_PACKET_RECEIVED(buffer) \
|
||||
@@ -243,6 +245,8 @@ APIError APIFrameHelper::init_common_() {
|
||||
HELPER_LOG("Bad state for init %d", (int) state_);
|
||||
return APIError::BAD_STATE;
|
||||
}
|
||||
// Cache peername now while socket is valid - needed for error logging after socket failure
|
||||
this->socket_->getpeername_to(this->client_peername_);
|
||||
int err = this->socket_->setblocking(false);
|
||||
if (err != 0) {
|
||||
state_ = State::FAILED;
|
||||
|
||||
@@ -33,11 +33,11 @@ static constexpr uint16_t MAX_MESSAGE_SIZE = 32768; // 32 KiB for ESP32 and oth
|
||||
// Must be >= MAX_INITIAL_PER_BATCH in api_connection.h (enforced by static_assert there)
|
||||
static constexpr size_t MAX_MESSAGES_PER_BATCH = 34;
|
||||
|
||||
// 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;
|
||||
@@ -86,14 +86,23 @@ 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_; }
|
||||
// Get client peername/IP (null-terminated, cached at init time for availability after socket failure)
|
||||
const char *get_client_peername() const { return this->client_peername_; }
|
||||
// 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();
|
||||
virtual APIError read_packet(ReadPacketBuffer *buffer) = 0;
|
||||
bool can_write_without_blocking() { return this->state_ == State::DATA && this->tx_buf_count_ == 0; }
|
||||
std::string getpeername() { return socket_->getpeername(); }
|
||||
int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return socket_->getpeername(addr, addrlen); }
|
||||
APIError close() {
|
||||
state_ = State::CLOSED;
|
||||
@@ -186,9 +195,10 @@ class APIFrameHelper {
|
||||
std::array<std::unique_ptr<SendBuffer>, API_MAX_SEND_QUEUE> tx_buf_;
|
||||
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]{};
|
||||
// Cached peername/IP address - captured at init time for availability after socket failure
|
||||
char client_peername_[socket::SOCKADDR_STR_LEN]{};
|
||||
|
||||
// Group smaller types together
|
||||
uint16_t rx_buf_len_ = 0;
|
||||
|
||||
@@ -27,8 +27,11 @@ static constexpr size_t PROLOGUE_INIT_LEN = 12; // strlen("NoiseAPIInit")
|
||||
// Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512)
|
||||
static constexpr size_t API_MAX_LOG_BYTES = 168;
|
||||
|
||||
#define HELPER_LOG(msg, ...) \
|
||||
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__)
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s (%s): " msg, this->client_name_, this->client_peername_, ##__VA_ARGS__)
|
||||
#else
|
||||
#define HELPER_LOG(msg, ...) ((void) 0)
|
||||
#endif
|
||||
|
||||
#ifdef HELPER_LOG_PACKETS
|
||||
#define LOG_PACKET_RECEIVED(buffer) \
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
@@ -21,8 +20,11 @@ static const char *const TAG = "api.plaintext";
|
||||
// Maximum bytes to log in hex format (168 * 3 = 504, under TX buffer size of 512)
|
||||
static constexpr size_t API_MAX_LOG_BYTES = 168;
|
||||
|
||||
#define HELPER_LOG(msg, ...) \
|
||||
ESP_LOGVV(TAG, "%s (%s): " msg, this->client_info_->name.c_str(), this->client_info_->peername.c_str(), ##__VA_ARGS__)
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
#define HELPER_LOG(msg, ...) ESP_LOGVV(TAG, "%s (%s): " msg, this->client_name_, this->client_peername_, ##__VA_ARGS__)
|
||||
#else
|
||||
#define HELPER_LOG(msg, ...) ((void) 0)
|
||||
#endif
|
||||
|
||||
#ifdef HELPER_LOG_PACKETS
|
||||
#define LOG_PACKET_RECEIVED(buffer) \
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -125,15 +125,18 @@ void APIServer::loop() {
|
||||
if (!sock)
|
||||
break;
|
||||
|
||||
char peername[socket::SOCKADDR_STR_LEN];
|
||||
sock->getpeername_to(peername);
|
||||
|
||||
// Check if we're at the connection limit
|
||||
if (this->clients_.size() >= this->max_connections_) {
|
||||
ESP_LOGW(TAG, "Max connections (%d), rejecting %s", this->max_connections_, sock->getpeername().c_str());
|
||||
ESP_LOGW(TAG, "Max connections (%d), rejecting %s", this->max_connections_, peername);
|
||||
// Immediately close - socket destructor will handle cleanup
|
||||
sock.reset();
|
||||
continue;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Accept %s", sock->getpeername().c_str());
|
||||
ESP_LOGD(TAG, "Accept %s", peername);
|
||||
|
||||
auto *conn = new APIConnection(std::move(sock), this);
|
||||
this->clients_.emplace_back(conn);
|
||||
@@ -166,8 +169,7 @@ void APIServer::loop() {
|
||||
// Network is down - disconnect all clients
|
||||
for (auto &client : this->clients_) {
|
||||
client->on_fatal_error();
|
||||
ESP_LOGW(TAG, "%s (%s): Network down; disconnect", client->client_info_.name.c_str(),
|
||||
client->client_info_.peername.c_str());
|
||||
client->log_client_(ESPHOME_LOG_LEVEL_WARN, LOG_STR("Network down; disconnect"));
|
||||
}
|
||||
// Continue to process and clean up the clients below
|
||||
}
|
||||
@@ -185,12 +187,12 @@ void APIServer::loop() {
|
||||
|
||||
// Rare case: handle disconnection
|
||||
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
|
||||
this->client_disconnected_trigger_->trigger(client->client_info_.name, client->client_info_.peername);
|
||||
this->client_disconnected_trigger_->trigger(std::string(client->get_name()), std::string(client->get_peername()));
|
||||
#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.c_str());
|
||||
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) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "esphome/components/sha256/sha256.h"
|
||||
#endif
|
||||
#include "esphome/components/network/util.h"
|
||||
#include "esphome/components/socket/socket.h"
|
||||
#include "esphome/components/ota/ota_backend.h"
|
||||
#include "esphome/components/ota/ota_backend_esp8266.h"
|
||||
#include "esphome/components/ota/ota_backend_arduino_libretiny.h"
|
||||
@@ -443,7 +444,9 @@ void ESPHomeOTAComponent::log_socket_error_(const LogString *msg) {
|
||||
void ESPHomeOTAComponent::log_read_error_(const LogString *what) { ESP_LOGW(TAG, "Read %s failed", LOG_STR_ARG(what)); }
|
||||
|
||||
void ESPHomeOTAComponent::log_start_(const LogString *phase) {
|
||||
ESP_LOGD(TAG, "Starting %s from %s", LOG_STR_ARG(phase), this->client_->getpeername().c_str());
|
||||
char peername[socket::SOCKADDR_STR_LEN];
|
||||
this->client_->getpeername_to(peername);
|
||||
ESP_LOGD(TAG, "Starting %s from %s", LOG_STR_ARG(phase), peername);
|
||||
}
|
||||
|
||||
void ESPHomeOTAComponent::log_remote_closed_(const LogString *during) {
|
||||
|
||||
@@ -14,31 +14,7 @@
|
||||
|
||||
namespace esphome::socket {
|
||||
|
||||
std::string format_sockaddr(const struct sockaddr_storage &storage) {
|
||||
if (storage.ss_family == AF_INET) {
|
||||
const struct sockaddr_in *addr = reinterpret_cast<const struct sockaddr_in *>(&storage);
|
||||
char buf[INET_ADDRSTRLEN];
|
||||
if (inet_ntop(AF_INET, &addr->sin_addr, buf, sizeof(buf)) != nullptr)
|
||||
return std::string{buf};
|
||||
}
|
||||
#if LWIP_IPV6
|
||||
else if (storage.ss_family == AF_INET6) {
|
||||
const struct sockaddr_in6 *addr = reinterpret_cast<const struct sockaddr_in6 *>(&storage);
|
||||
char buf[INET6_ADDRSTRLEN];
|
||||
// Format IPv4-mapped IPv6 addresses as regular IPv4 addresses
|
||||
if (addr->sin6_addr.un.u32_addr[0] == 0 && addr->sin6_addr.un.u32_addr[1] == 0 &&
|
||||
addr->sin6_addr.un.u32_addr[2] == htonl(0xFFFF) &&
|
||||
inet_ntop(AF_INET, &addr->sin6_addr.un.u32_addr[3], buf, sizeof(buf)) != nullptr) {
|
||||
return std::string{buf};
|
||||
}
|
||||
if (inet_ntop(AF_INET6, &addr->sin6_addr, buf, sizeof(buf)) != nullptr)
|
||||
return std::string{buf};
|
||||
}
|
||||
#endif
|
||||
return {};
|
||||
}
|
||||
|
||||
class BSDSocketImpl : public Socket {
|
||||
class BSDSocketImpl final : public Socket {
|
||||
public:
|
||||
BSDSocketImpl(int fd, bool monitor_loop = false) : fd_(fd) {
|
||||
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||
@@ -93,23 +69,9 @@ class BSDSocketImpl : public Socket {
|
||||
int getpeername(struct sockaddr *addr, socklen_t *addrlen) override {
|
||||
return ::getpeername(this->fd_, addr, addrlen);
|
||||
}
|
||||
std::string getpeername() override {
|
||||
struct sockaddr_storage storage;
|
||||
socklen_t len = sizeof(storage);
|
||||
if (::getpeername(this->fd_, (struct sockaddr *) &storage, &len) != 0)
|
||||
return {};
|
||||
return format_sockaddr(storage);
|
||||
}
|
||||
int getsockname(struct sockaddr *addr, socklen_t *addrlen) override {
|
||||
return ::getsockname(this->fd_, addr, addrlen);
|
||||
}
|
||||
std::string getsockname() override {
|
||||
struct sockaddr_storage storage;
|
||||
socklen_t len = sizeof(storage);
|
||||
if (::getsockname(this->fd_, (struct sockaddr *) &storage, &len) != 0)
|
||||
return {};
|
||||
return format_sockaddr(storage);
|
||||
}
|
||||
int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override {
|
||||
return ::getsockopt(this->fd_, level, optname, optval, optlen);
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ class LWIPRawImpl : public Socket {
|
||||
errno = EINVAL;
|
||||
return nullptr;
|
||||
}
|
||||
int bind(const struct sockaddr *name, socklen_t addrlen) override {
|
||||
int bind(const struct sockaddr *name, socklen_t addrlen) final {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = EBADF;
|
||||
return -1;
|
||||
@@ -135,7 +135,7 @@ class LWIPRawImpl : public Socket {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
int close() override {
|
||||
int close() final {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = ECONNRESET;
|
||||
return -1;
|
||||
@@ -152,7 +152,7 @@ class LWIPRawImpl : public Socket {
|
||||
pcb_ = nullptr;
|
||||
return 0;
|
||||
}
|
||||
int shutdown(int how) override {
|
||||
int shutdown(int how) final {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = ECONNRESET;
|
||||
return -1;
|
||||
@@ -178,7 +178,7 @@ class LWIPRawImpl : public Socket {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int getpeername(struct sockaddr *name, socklen_t *addrlen) override {
|
||||
int getpeername(struct sockaddr *name, socklen_t *addrlen) final {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = ECONNRESET;
|
||||
return -1;
|
||||
@@ -189,14 +189,7 @@ class LWIPRawImpl : public Socket {
|
||||
}
|
||||
return this->ip2sockaddr_(&pcb_->remote_ip, pcb_->remote_port, name, addrlen);
|
||||
}
|
||||
std::string getpeername() override {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = ECONNRESET;
|
||||
return "";
|
||||
}
|
||||
return this->format_ip_address_(pcb_->remote_ip);
|
||||
}
|
||||
int getsockname(struct sockaddr *name, socklen_t *addrlen) override {
|
||||
int getsockname(struct sockaddr *name, socklen_t *addrlen) final {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = ECONNRESET;
|
||||
return -1;
|
||||
@@ -207,14 +200,7 @@ class LWIPRawImpl : public Socket {
|
||||
}
|
||||
return this->ip2sockaddr_(&pcb_->local_ip, pcb_->local_port, name, addrlen);
|
||||
}
|
||||
std::string getsockname() override {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = ECONNRESET;
|
||||
return "";
|
||||
}
|
||||
return this->format_ip_address_(pcb_->local_ip);
|
||||
}
|
||||
int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override {
|
||||
int getsockopt(int level, int optname, void *optval, socklen_t *optlen) final {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = ECONNRESET;
|
||||
return -1;
|
||||
@@ -248,7 +234,7 @@ class LWIPRawImpl : public Socket {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
int setsockopt(int level, int optname, const void *optval, socklen_t optlen) override {
|
||||
int setsockopt(int level, int optname, const void *optval, socklen_t optlen) final {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = ECONNRESET;
|
||||
return -1;
|
||||
@@ -282,7 +268,7 @@ class LWIPRawImpl : public Socket {
|
||||
errno = EOPNOTSUPP;
|
||||
return -1;
|
||||
}
|
||||
ssize_t read(void *buf, size_t len) override {
|
||||
ssize_t read(void *buf, size_t len) final {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = ECONNRESET;
|
||||
return -1;
|
||||
@@ -340,7 +326,7 @@ class LWIPRawImpl : public Socket {
|
||||
|
||||
return read;
|
||||
}
|
||||
ssize_t readv(const struct iovec *iov, int iovcnt) override {
|
||||
ssize_t readv(const struct iovec *iov, int iovcnt) final {
|
||||
ssize_t ret = 0;
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
ssize_t err = read(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len);
|
||||
@@ -358,7 +344,7 @@ class LWIPRawImpl : public Socket {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ssize_t recvfrom(void *buf, size_t len, sockaddr *addr, socklen_t *addr_len) override {
|
||||
ssize_t recvfrom(void *buf, size_t len, sockaddr *addr, socklen_t *addr_len) final {
|
||||
errno = ENOTSUP;
|
||||
return -1;
|
||||
}
|
||||
@@ -412,7 +398,7 @@ class LWIPRawImpl : public Socket {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
ssize_t write(const void *buf, size_t len) override {
|
||||
ssize_t write(const void *buf, size_t len) final {
|
||||
ssize_t written = internal_write(buf, len);
|
||||
if (written == -1)
|
||||
return -1;
|
||||
@@ -427,7 +413,7 @@ class LWIPRawImpl : public Socket {
|
||||
}
|
||||
return written;
|
||||
}
|
||||
ssize_t writev(const struct iovec *iov, int iovcnt) override {
|
||||
ssize_t writev(const struct iovec *iov, int iovcnt) final {
|
||||
ssize_t written = 0;
|
||||
for (int i = 0; i < iovcnt; i++) {
|
||||
ssize_t err = internal_write(reinterpret_cast<uint8_t *>(iov[i].iov_base), iov[i].iov_len);
|
||||
@@ -453,12 +439,12 @@ class LWIPRawImpl : public Socket {
|
||||
}
|
||||
return written;
|
||||
}
|
||||
ssize_t sendto(const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) override {
|
||||
ssize_t sendto(const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) final {
|
||||
// return ::sendto(fd_, buf, len, flags, to, tolen);
|
||||
errno = ENOSYS;
|
||||
return -1;
|
||||
}
|
||||
int setblocking(bool blocking) override {
|
||||
int setblocking(bool blocking) final {
|
||||
if (pcb_ == nullptr) {
|
||||
errno = ECONNRESET;
|
||||
return -1;
|
||||
@@ -517,19 +503,6 @@ class LWIPRawImpl : public Socket {
|
||||
}
|
||||
|
||||
protected:
|
||||
std::string format_ip_address_(const ip_addr_t &ip) {
|
||||
char buffer[50] = {};
|
||||
if (IP_IS_V4_VAL(ip)) {
|
||||
inet_ntoa_r(ip, buffer, sizeof(buffer));
|
||||
}
|
||||
#if LWIP_IPV6
|
||||
else if (IP_IS_V6_VAL(ip)) {
|
||||
inet6_ntoa_r(ip, buffer, sizeof(buffer));
|
||||
}
|
||||
#endif
|
||||
return std::string(buffer);
|
||||
}
|
||||
|
||||
int ip2sockaddr_(ip_addr_t *ip, uint16_t port, struct sockaddr *name, socklen_t *addrlen) {
|
||||
if (family_ == AF_INET) {
|
||||
if (*addrlen < sizeof(struct sockaddr_in)) {
|
||||
@@ -584,7 +557,7 @@ class LWIPRawImpl : public Socket {
|
||||
|
||||
// Listening socket class - only allocates accept queue when needed (for bind+listen sockets)
|
||||
// This saves 16 bytes (12 bytes array + 1 byte count + 3 bytes padding) for regular connected sockets on ESP8266/RP2040
|
||||
class LWIPRawListenImpl : public LWIPRawImpl {
|
||||
class LWIPRawListenImpl final : public LWIPRawImpl {
|
||||
public:
|
||||
LWIPRawListenImpl(sa_family_t family, struct tcp_pcb *pcb) : LWIPRawImpl(family, pcb) {}
|
||||
|
||||
|
||||
@@ -9,29 +9,7 @@
|
||||
|
||||
namespace esphome::socket {
|
||||
|
||||
std::string format_sockaddr(const struct sockaddr_storage &storage) {
|
||||
if (storage.ss_family == AF_INET) {
|
||||
const struct sockaddr_in *addr = reinterpret_cast<const struct sockaddr_in *>(&storage);
|
||||
char buf[INET_ADDRSTRLEN];
|
||||
const char *ret = lwip_inet_ntop(AF_INET, &addr->sin_addr, buf, sizeof(buf));
|
||||
if (ret == nullptr)
|
||||
return {};
|
||||
return std::string{buf};
|
||||
}
|
||||
#if LWIP_IPV6
|
||||
else if (storage.ss_family == AF_INET6) {
|
||||
const struct sockaddr_in6 *addr = reinterpret_cast<const struct sockaddr_in6 *>(&storage);
|
||||
char buf[INET6_ADDRSTRLEN];
|
||||
const char *ret = lwip_inet_ntop(AF_INET6, &addr->sin6_addr, buf, sizeof(buf));
|
||||
if (ret == nullptr)
|
||||
return {};
|
||||
return std::string{buf};
|
||||
}
|
||||
#endif
|
||||
return {};
|
||||
}
|
||||
|
||||
class LwIPSocketImpl : public Socket {
|
||||
class LwIPSocketImpl final : public Socket {
|
||||
public:
|
||||
LwIPSocketImpl(int fd, bool monitor_loop = false) : fd_(fd) {
|
||||
#ifdef USE_SOCKET_SELECT_SUPPORT
|
||||
@@ -88,23 +66,9 @@ class LwIPSocketImpl : public Socket {
|
||||
int getpeername(struct sockaddr *addr, socklen_t *addrlen) override {
|
||||
return lwip_getpeername(this->fd_, addr, addrlen);
|
||||
}
|
||||
std::string getpeername() override {
|
||||
struct sockaddr_storage storage;
|
||||
socklen_t len = sizeof(storage);
|
||||
if (lwip_getpeername(this->fd_, (struct sockaddr *) &storage, &len) != 0)
|
||||
return {};
|
||||
return format_sockaddr(storage);
|
||||
}
|
||||
int getsockname(struct sockaddr *addr, socklen_t *addrlen) override {
|
||||
return lwip_getsockname(this->fd_, addr, addrlen);
|
||||
}
|
||||
std::string getsockname() override {
|
||||
struct sockaddr_storage storage;
|
||||
socklen_t len = sizeof(storage);
|
||||
if (lwip_getsockname(this->fd_, (struct sockaddr *) &storage, &len) != 0)
|
||||
return {};
|
||||
return format_sockaddr(storage);
|
||||
}
|
||||
int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override {
|
||||
return lwip_getsockopt(this->fd_, level, optname, optval, optlen);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,87 @@ namespace esphome::socket {
|
||||
|
||||
Socket::~Socket() {}
|
||||
|
||||
// Platform-specific inet_ntop wrappers
|
||||
#if defined(USE_SOCKET_IMPL_LWIP_TCP)
|
||||
// LWIP raw TCP (ESP8266) uses inet_ntoa_r which takes struct by value
|
||||
static inline const char *esphome_inet_ntop4(const void *addr, char *buf, size_t size) {
|
||||
inet_ntoa_r(*reinterpret_cast<const struct in_addr *>(addr), buf, size);
|
||||
return buf;
|
||||
}
|
||||
#if USE_NETWORK_IPV6
|
||||
static inline const char *esphome_inet_ntop6(const void *addr, char *buf, size_t size) {
|
||||
inet6_ntoa_r(*reinterpret_cast<const ip6_addr_t *>(addr), buf, size);
|
||||
return buf;
|
||||
}
|
||||
#endif
|
||||
#elif defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
|
||||
// LWIP sockets (LibreTiny, ESP32 Arduino)
|
||||
static inline const char *esphome_inet_ntop4(const void *addr, char *buf, size_t size) {
|
||||
return lwip_inet_ntop(AF_INET, addr, buf, size);
|
||||
}
|
||||
#if USE_NETWORK_IPV6
|
||||
static inline const char *esphome_inet_ntop6(const void *addr, char *buf, size_t size) {
|
||||
return lwip_inet_ntop(AF_INET6, addr, buf, size);
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
// BSD sockets (host, ESP32-IDF)
|
||||
static inline const char *esphome_inet_ntop4(const void *addr, char *buf, size_t size) {
|
||||
return inet_ntop(AF_INET, addr, buf, size);
|
||||
}
|
||||
#if USE_NETWORK_IPV6
|
||||
static inline const char *esphome_inet_ntop6(const void *addr, char *buf, size_t size) {
|
||||
return inet_ntop(AF_INET6, addr, buf, size);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Format sockaddr into caller-provided buffer, returns length written (excluding null)
|
||||
static size_t format_sockaddr_to(const struct sockaddr_storage &storage, std::span<char, SOCKADDR_STR_LEN> buf) {
|
||||
if (storage.ss_family == AF_INET) {
|
||||
const auto *addr = reinterpret_cast<const struct sockaddr_in *>(&storage);
|
||||
if (esphome_inet_ntop4(&addr->sin_addr, buf.data(), buf.size()) != nullptr)
|
||||
return strlen(buf.data());
|
||||
}
|
||||
#if USE_NETWORK_IPV6
|
||||
else if (storage.ss_family == AF_INET6) {
|
||||
const auto *addr = reinterpret_cast<const struct sockaddr_in6 *>(&storage);
|
||||
#ifndef USE_SOCKET_IMPL_LWIP_TCP
|
||||
// Format IPv4-mapped IPv6 addresses as regular IPv4 (not supported on ESP8266 raw TCP)
|
||||
if (addr->sin6_addr.un.u32_addr[0] == 0 && addr->sin6_addr.un.u32_addr[1] == 0 &&
|
||||
addr->sin6_addr.un.u32_addr[2] == htonl(0xFFFF) &&
|
||||
esphome_inet_ntop4(&addr->sin6_addr.un.u32_addr[3], buf.data(), buf.size()) != nullptr) {
|
||||
return strlen(buf.data());
|
||||
}
|
||||
#endif
|
||||
if (esphome_inet_ntop6(&addr->sin6_addr, buf.data(), buf.size()) != nullptr)
|
||||
return strlen(buf.data());
|
||||
}
|
||||
#endif
|
||||
buf[0] = '\0';
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t Socket::getpeername_to(std::span<char, SOCKADDR_STR_LEN> buf) {
|
||||
struct sockaddr_storage storage;
|
||||
socklen_t len = sizeof(storage);
|
||||
if (this->getpeername(reinterpret_cast<struct sockaddr *>(&storage), &len) != 0) {
|
||||
buf[0] = '\0';
|
||||
return 0;
|
||||
}
|
||||
return format_sockaddr_to(storage, buf);
|
||||
}
|
||||
|
||||
size_t Socket::getsockname_to(std::span<char, SOCKADDR_STR_LEN> buf) {
|
||||
struct sockaddr_storage storage;
|
||||
socklen_t len = sizeof(storage);
|
||||
if (this->getsockname(reinterpret_cast<struct sockaddr *>(&storage), &len) != 0) {
|
||||
buf[0] = '\0';
|
||||
return 0;
|
||||
}
|
||||
return format_sockaddr_to(storage, buf);
|
||||
}
|
||||
|
||||
std::unique_ptr<Socket> socket_ip(int type, int protocol) {
|
||||
#if USE_NETWORK_IPV6
|
||||
return socket(AF_INET6, type, protocol);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string>
|
||||
|
||||
#include "esphome/core/optional.h"
|
||||
@@ -8,6 +9,15 @@
|
||||
#if defined(USE_SOCKET_IMPL_LWIP_TCP) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS) || defined(USE_SOCKET_IMPL_BSD_SOCKETS)
|
||||
namespace esphome::socket {
|
||||
|
||||
// Maximum length for formatted socket address string (IP address without port)
|
||||
// IPv4: "255.255.255.255" = 15 chars + null = 16
|
||||
// IPv6: full address = 45 chars + null = 46
|
||||
#if USE_NETWORK_IPV6
|
||||
static constexpr size_t SOCKADDR_STR_LEN = 46; // INET6_ADDRSTRLEN
|
||||
#else
|
||||
static constexpr size_t SOCKADDR_STR_LEN = 16; // INET_ADDRSTRLEN
|
||||
#endif
|
||||
|
||||
class Socket {
|
||||
public:
|
||||
Socket() = default;
|
||||
@@ -31,9 +41,15 @@ class Socket {
|
||||
virtual int shutdown(int how) = 0;
|
||||
|
||||
virtual int getpeername(struct sockaddr *addr, socklen_t *addrlen) = 0;
|
||||
virtual std::string getpeername() = 0;
|
||||
virtual int getsockname(struct sockaddr *addr, socklen_t *addrlen) = 0;
|
||||
virtual std::string getsockname() = 0;
|
||||
|
||||
/// Format peer address into a fixed-size buffer (no heap allocation)
|
||||
/// Non-virtual wrapper around getpeername() - can be optimized away if unused
|
||||
/// Returns number of characters written (excluding null terminator), or 0 on error
|
||||
size_t getpeername_to(std::span<char, SOCKADDR_STR_LEN> buf);
|
||||
/// Format local address into a fixed-size buffer (no heap allocation)
|
||||
/// Non-virtual wrapper around getsockname() - can be optimized away if unused
|
||||
size_t getsockname_to(std::span<char, SOCKADDR_STR_LEN> buf);
|
||||
virtual int getsockopt(int level, int optname, void *optval, socklen_t *optlen) = 0;
|
||||
virtual int setsockopt(int level, int optname, const void *optval, socklen_t optlen) = 0;
|
||||
virtual int listen(int backlog) = 0;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#ifdef USE_VOICE_ASSISTANT
|
||||
|
||||
#include "esphome/components/socket/socket.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <cinttypes>
|
||||
@@ -433,8 +434,8 @@ void VoiceAssistant::client_subscription(api::APIConnection *client, bool subscr
|
||||
"Multiple API Clients attempting to connect to Voice Assistant\n"
|
||||
"Current client: %s (%s)\n"
|
||||
"New client: %s (%s)",
|
||||
this->api_client_->get_name().c_str(), this->api_client_->get_peername().c_str(),
|
||||
client->get_name().c_str(), client->get_peername().c_str());
|
||||
this->api_client_->get_name(), this->api_client_->get_peername(), client->get_name(),
|
||||
client->get_peername());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user