From c2d494137fc9fbe9e3f88a81d842b0bfceea00ef Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Fri, 14 Aug 2026 20:04:49 -0500 Subject: [PATCH 1/4] feat(socket): generalize Windows guards and add socket-option API Replace all MSVC-specific preprocessor guards (_MSC_VER) in the socket component with the Windows-platform guard _WIN32, so MinGW / clang-cl Windows builds are covered too. Every guard was Windows-platform intent (Winsock2, closesocket vs close, WSAStartup, INVALID_SOCKET, ioctlsocket, SO_* char* casts) - none were MSVC-compiler-only pragmas. Add socket-option-setting support to the base espp::Socket so callers no longer reach for the raw native handle: - set_option(int level, int option_name, const void *value, size_t size) - template set_option(int level, int option_name, const T&) - set_receive_buffer_size(size_t) / set_send_buffer_size(size_t) - set_reuse_address(bool) - get_receive_buffer_size() -> std::optional The Windows const char* setsockopt cast is handled under _WIN32. Migrate the RTPS EsppTransport SO_RCVBUF setup to use Socket::set_receive_buffer_size() instead of a raw setsockopt call. Co-Authored-By: Claude Opus 4.8 --- .../src/communication/EsppTransport.cpp | 7 +- components/socket/include/socket.hpp | 57 ++++++++++++++- components/socket/include/socket_msvc.hpp | 2 +- components/socket/include/tcp_socket.hpp | 4 +- components/socket/src/socket.cpp | 73 +++++++++++++++---- components/socket/src/socket_reactor.cpp | 10 +-- components/socket/src/udp_socket.cpp | 4 +- 7 files changed, 127 insertions(+), 30 deletions(-) diff --git a/components/rtps_embedded/src/communication/EsppTransport.cpp b/components/rtps_embedded/src/communication/EsppTransport.cpp index 09e8fac2f6..b21d8cafc3 100644 --- a/components/rtps_embedded/src/communication/EsppTransport.cpp +++ b/components/rtps_embedded/src/communication/EsppTransport.cpp @@ -175,11 +175,8 @@ EsppTransport::Channel *EsppTransport::createChannel(Ip4Port_t receivePort, bool // large (fragmented) sample is not dropped before the reactor drains it. // Best-effort: some stacks clamp SO_RCVBUF, so failure is ignored. Only // compiled when fragmentation is enabled (never on the ESP32 default build). - { - int rcvbuf = 4 * 1024 * 1024; // request 4 MB (kernel may clamp) - ::setsockopt(channel.socket->native_handle(), SOL_SOCKET, SO_RCVBUF, - reinterpret_cast(&rcvbuf), sizeof(rcvbuf)); - } + // request 4 MB (kernel may clamp) + (void)channel.socket->set_receive_buffer_size(4 * 1024 * 1024); #endif if (!allow_reuse && !channel.socket->disable_reuse()) { diff --git a/components/socket/include/socket.hpp b/components/socket/include/socket.hpp index b350430a25..75758a2e6b 100644 --- a/components/socket/include/socket.hpp +++ b/components/socket/include/socket.hpp @@ -2,7 +2,7 @@ #include "socket_msvc.hpp" -#ifdef _MSC_VER +#ifdef _WIN32 typedef unsigned int sock_type_t; #else /* Assume that any non-Windows platform uses POSIX-style sockets instead. */ @@ -177,6 +177,61 @@ class Socket : public BaseComponent { */ bool set_receive_timeout(const std::chrono::duration &timeout); + /** + * @brief Generic wrapper around setsockopt() so callers don't have to touch + * the raw native handle (or worry about the Windows const char* cast). + * @param level protocol level of the option (e.g. SOL_SOCKET, IPPROTO_IP). + * @param option_name option to set (e.g. SO_RCVBUF). + * @param value pointer to the option value. + * @param size size of the option value in bytes. + * @return true if setsockopt() succeeded, false otherwise (logs on failure). + */ + bool set_option(int level, int option_name, const void *value, size_t size); + + /** + * @brief Convenience wrapper around set_option() for a trivially-copyable + * option value. + * @param level protocol level of the option (e.g. SOL_SOCKET). + * @param option_name option to set (e.g. SO_RCVBUF). + * @param value option value; its address and size are forwarded to + * setsockopt(). + * @return true if setsockopt() succeeded, false otherwise (logs on failure). + */ + template bool set_option(int level, int option_name, const T &value) { + return set_option(level, option_name, &value, sizeof(value)); + } + + /** + * @brief Set the size of the kernel receive buffer (SO_RCVBUF). + * @note The kernel may clamp or double the requested value. + * @param bytes requested receive buffer size in bytes. + * @return true if SO_RCVBUF was successfully set. + */ + bool set_receive_buffer_size(size_t bytes); + + /** + * @brief Set the size of the kernel send buffer (SO_SNDBUF). + * @note The kernel may clamp or double the requested value. + * @param bytes requested send buffer size in bytes. + * @return true if SO_SNDBUF was successfully set. + */ + bool set_send_buffer_size(size_t bytes); + + /** + * @brief Set (or clear) SO_REUSEADDR on the socket. + * @note Unlike enable_reuse()/disable_reuse(), this only touches SO_REUSEADDR + * (not SO_REUSEPORT / SO_BROADCAST). + * @param enable true to allow address reuse, false to disallow it. + * @return true if SO_REUSEADDR was successfully set. + */ + bool set_reuse_address(bool enable); + + /** + * @brief Get the size of the kernel receive buffer (SO_RCVBUF). + * @return the receive buffer size in bytes, or std::nullopt on failure. + */ + std::optional get_receive_buffer_size(); + /** * @brief Allow others to use this address/port combination after we're done * with it. diff --git a/components/socket/include/socket_msvc.hpp b/components/socket/include/socket_msvc.hpp index f63882b291..30dd786817 100644 --- a/components/socket/include/socket_msvc.hpp +++ b/components/socket/include/socket_msvc.hpp @@ -1,4 +1,4 @@ -#ifdef _MSC_VER +#ifdef _WIN32 extern "C" { // if we don't define NOMINMAX, windows.h will define min and max as macros // which will conflict with std::min and std::max diff --git a/components/socket/include/tcp_socket.hpp b/components/socket/include/tcp_socket.hpp index b2583138c0..5a5aca2d30 100644 --- a/components/socket/include/tcp_socket.hpp +++ b/components/socket/include/tcp_socket.hpp @@ -2,9 +2,9 @@ #include "socket_msvc.hpp" -#ifndef _MSC_VER +#ifndef _WIN32 #include -#endif // _MSC_VER +#endif // _WIN32 #include #include diff --git a/components/socket/src/socket.cpp b/components/socket/src/socket.cpp index 71d9abfbdd..e1c89b41aa 100644 --- a/components/socket/src/socket.cpp +++ b/components/socket/src/socket.cpp @@ -76,7 +76,7 @@ void Socket::Info::from_sockaddr(const struct sockaddr_in6 &source_address) { [[maybe_unused]] static bool _socket_initialized = false; Socket::Socket(sock_type_t socket_fd, const Logger::Config &logger_config) : BaseComponent(logger_config) { -#ifdef _MSC_VER +#ifdef _WIN32 if (!_socket_initialized) { logger_.debug("Initializing Winsock"); WSADATA wsa_data; @@ -92,7 +92,7 @@ Socket::Socket(sock_type_t socket_fd, const Logger::Config &logger_config) Socket::Socket(Type type, const Logger::Config &logger_config) : BaseComponent(logger_config) { -#ifdef _MSC_VER +#ifdef _WIN32 if (!_socket_initialized) { logger_.debug("Initializing Winsock"); WSADATA wsa_data; @@ -109,7 +109,7 @@ Socket::Socket(Type type, const Logger::Config &logger_config) Socket::~Socket() { cleanup(); } bool Socket::is_valid() const { -#ifdef _MSC_VER +#ifdef _WIN32 return socket_ != INVALID_SOCKET; #else return socket_ >= 0; @@ -117,7 +117,7 @@ bool Socket::is_valid() const { } bool Socket::is_valid_fd(sock_type_t socket_fd) { -#ifdef _MSC_VER +#ifdef _WIN32 return socket_fd != INVALID_SOCKET; #else return socket_fd >= 0; @@ -162,6 +162,51 @@ bool Socket::set_receive_timeout(const std::chrono::duration &timeout) { return true; } +bool Socket::set_option(int level, int option_name, const void *value, size_t size) { +#if defined(_WIN32) + int err = setsockopt(socket_, level, option_name, reinterpret_cast(value), + static_cast(size)); +#else + int err = setsockopt(socket_, level, option_name, value, static_cast(size)); +#endif + if (err < 0) { + logger_.error("Couldn't set socket option (level={}, option={}): {}", level, option_name, + error_string()); + return false; + } + return true; +} + +bool Socket::set_receive_buffer_size(size_t bytes) { + int value = static_cast(bytes); + return set_option(SOL_SOCKET, SO_RCVBUF, value); +} + +bool Socket::set_send_buffer_size(size_t bytes) { + int value = static_cast(bytes); + return set_option(SOL_SOCKET, SO_SNDBUF, value); +} + +bool Socket::set_reuse_address(bool enable) { + int value = enable ? 1 : 0; + return set_option(SOL_SOCKET, SO_REUSEADDR, value); +} + +std::optional Socket::get_receive_buffer_size() { + int value = 0; + socklen_t len = sizeof(value); +#if defined(_WIN32) + int err = getsockopt(socket_, SOL_SOCKET, SO_RCVBUF, reinterpret_cast(&value), &len); +#else + int err = getsockopt(socket_, SOL_SOCKET, SO_RCVBUF, &value, &len); +#endif + if (err < 0) { + logger_.error("Couldn't get SO_RCVBUF: {}", error_string()); + return {}; + } + return static_cast(value); +} + bool Socket::disable_reuse() { #if !CONFIG_LWIP_SO_REUSE && defined(ESP_PLATFORM) // reuse is not compiled into lwip, so it is already effectively disabled @@ -175,14 +220,14 @@ bool Socket::disable_reuse() { fmt::print(fg(fmt::color::red), "Couldn't clear SO_REUSEADDR: {}\n", error_string()); return false; } -#if !defined(ESP_PLATFORM) && !defined(_MSC_VER) +#if !defined(ESP_PLATFORM) && !defined(_WIN32) err = setsockopt(socket_, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast(&disabled), sizeof(disabled)); if (err < 0) { fmt::print(fg(fmt::color::red), "Couldn't clear SO_REUSEPORT: {}\n", error_string()); return false; } -#endif // !defined(ESP_PLATFORM) && !defined(_MSC_VER) +#endif // !defined(ESP_PLATFORM) && !defined(_WIN32) return true; #endif // !CONFIG_LWIP_SO_REUSE && defined(ESP_PLATFORM) } @@ -201,7 +246,7 @@ bool Socket::enable_reuse() { return false; } #if !defined(ESP_PLATFORM) -#ifdef _MSC_VER +#ifdef _WIN32 // NOTE: according to stackoverflow, we have to set broadcast instead of reuseport err = setsockopt(socket_, SOL_SOCKET, SO_BROADCAST, reinterpret_cast(&enabled), sizeof(enabled)); @@ -216,7 +261,7 @@ bool Socket::enable_reuse() { fmt::print(fg(fmt::color::red), "Couldn't set SO_REUSEPORT: {}\n", error_string()); return false; } -#endif // _MSC_VER +#endif // _WIN32 #endif // !defined(ESP_PLATFORM) return true; #endif // !CONFIG_LWIP_SO_REUSE && defined(ESP_PLATFORM) @@ -233,7 +278,7 @@ static bool resolve_interface_address(const std::string &interface_address, stru #endif return true; } -#ifdef _MSC_VER +#ifdef _WIN32 return inet_pton(AF_INET, interface_address.c_str(), &out) == 1; #else return inet_aton(interface_address.c_str(), &out) == 1; @@ -297,11 +342,11 @@ bool Socket::add_multicast_group(const std::string &multicast_group, #if defined(ESP_PLATFORM) err = inet_aton(multicast_group.c_str(), &imreq.imr_multiaddr.s_addr); #else -#ifdef _MSC_VER +#ifdef _WIN32 err = inet_pton(AF_INET, multicast_group.c_str(), &imreq.imr_multiaddr); #else err = inet_aton(multicast_group.c_str(), &imreq.imr_multiaddr); -#endif // _MSC_VER +#endif // _WIN32 #endif // defined(ESP_PLATFORM) if (err != 1 || !IN_MULTICAST(ntohl(imreq.imr_multiaddr.s_addr))) { @@ -373,7 +418,7 @@ bool Socket::init(Socket::Type type) { } std::string Socket::error_string() const { -#ifdef _MSC_VER +#ifdef _WIN32 int err = WSAGetLastError(); return error_string(err); #else @@ -382,7 +427,7 @@ std::string Socket::error_string() const { } std::string Socket::error_string(int err) const { -#ifdef _MSC_VER +#ifdef _WIN32 if (err == WSAEWOULDBLOCK) { return "WSAEWOULDBLOCK"; } else if (err == WSAECONNRESET) { @@ -410,7 +455,7 @@ std::string Socket::error_string(int err) const { void Socket::cleanup() { if (is_valid()) { auto socket_fd = socket_; -#ifdef _MSC_VER +#ifdef _WIN32 socket_ = INVALID_SOCKET; int status = shutdown(socket_fd, SD_BOTH); if (status != 0) { diff --git a/components/socket/src/socket_reactor.cpp b/components/socket/src/socket_reactor.cpp index b4ad3c0d60..337245ec43 100644 --- a/components/socket/src/socket_reactor.cpp +++ b/components/socket/src/socket_reactor.cpp @@ -3,7 +3,7 @@ #include #include -#ifndef _MSC_VER +#ifndef _WIN32 #include #endif @@ -26,7 +26,7 @@ struct DispatchGuard { // Close a socket with the platform-correct call (Winsock sockets must use // closesocket(), not ::close()). void close_socket(sock_type_t fd) { -#if defined(_MSC_VER) +#if defined(_WIN32) closesocket(fd); #else ::close(fd); @@ -144,7 +144,7 @@ bool SocketReactor::check_fd(sock_type_t fd) const { logger_.error("register: invalid socket fd"); return false; } -#if !defined(_MSC_VER) +#if !defined(_WIN32) // The select() backend uses fd_set, a bitmap indexed by fd value on // POSIX/lwip; FD_SET(fd) with fd >= FD_SETSIZE is undefined behavior. (On // Winsock, fd_set is a bounded array of SOCKETs, so the value is not the @@ -458,7 +458,7 @@ bool SocketReactor::create_wakeup_socket() { logger_.error("Could not create wakeup socket"); return false; } -#if !defined(_MSC_VER) +#if !defined(_WIN32) // The wakeup fd is FD_SET into the select set every iteration, so it too must // be below FD_SETSIZE (see check_fd). It is created before any registrations, // so on lwip it takes a low-offset fd - but guard anyway. @@ -505,7 +505,7 @@ void SocketReactor::close_wakeup_socket() { } bool SocketReactor::set_nonblocking(sock_type_t fd) { -#if defined(_MSC_VER) +#if defined(_WIN32) u_long mode = 1; return ioctlsocket(fd, FIONBIO, &mode) == 0; #else diff --git a/components/socket/src/udp_socket.cpp b/components/socket/src/udp_socket.cpp index a098589632..b0b38cbfdb 100644 --- a/components/socket/src/udp_socket.cpp +++ b/components/socket/src/udp_socket.cpp @@ -5,14 +5,14 @@ using namespace espp; namespace { -#ifdef _MSC_VER +#ifdef _WIN32 int last_socket_error() { return WSAGetLastError(); } #else int last_socket_error() { return errno; } #endif bool is_transient_send_error(int err) { -#ifdef _MSC_VER +#ifdef _WIN32 return err == WSAEWOULDBLOCK || err == WSAENOBUFS; #else return err == EAGAIN || err == EWOULDBLOCK || err == ENOBUFS || err == ENOMEM; From b16f4e43d79c6a27197c08705e0275a8a622cd78 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Fri, 14 Aug 2026 22:25:06 -0500 Subject: [PATCH 2/4] =?UTF-8?q?fix(socket):=20address=20Copilot=20review?= =?UTF-8?q?=20=E2=80=94=20Win64=20handle=20type,=20narrowing=20guards,=20W?= =?UTF-8?q?SAStartup=20race,=20header=20rename?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - socket.hpp: sock_type_t is now `typedef SOCKET sock_type_t` under _WIN32 (was `unsigned int`, which truncates the pointer-sized SOCKET/UINT_PTR on 64-bit Windows). INVALID_SOCKET sentinel usage lines up unchanged. - socket.hpp: template set_option now static_asserts std::is_trivially_copyable_v since it forwards raw object bytes to setsockopt(); added . - socket.cpp: guard size_t->int narrowing in set_option (reject size > INT_MAX) and in set_receive_buffer_size / set_send_buffer_size (reject bytes > INT_MAX), logging + returning false; added . - socket.cpp: replace the non-atomic _socket_initialized check-then-act WSAStartup init with a std::once_flag + std::call_once helper (initialize_winsock); added . Fixes the race when Sockets are constructed concurrently. - rename socket_msvc.hpp -> socket_win32.hpp (guard is _WIN32, applies to any Windows toolchain) and update all #include references (socket, rtsp, lib umbrella). Co-Authored-By: Claude Opus 4.8 --- components/rtsp/include/rtsp_client.hpp | 2 +- components/rtsp/include/rtsp_server.hpp | 2 +- components/rtsp/include/rtsp_session.hpp | 2 +- components/socket/include/socket.hpp | 20 +++++++- components/socket/include/socket_reactor.hpp | 2 +- .../{socket_msvc.hpp => socket_win32.hpp} | 0 components/socket/include/tcp_socket.hpp | 2 +- components/socket/include/udp_socket.hpp | 2 +- components/socket/src/socket.cpp | 49 +++++++++++++------ lib/include/espp.hpp | 2 +- 10 files changed, 59 insertions(+), 24 deletions(-) rename components/socket/include/{socket_msvc.hpp => socket_win32.hpp} (100%) diff --git a/components/rtsp/include/rtsp_client.hpp b/components/rtsp/include/rtsp_client.hpp index 451ca5bb92..6097b8c94f 100755 --- a/components/rtsp/include/rtsp_client.hpp +++ b/components/rtsp/include/rtsp_client.hpp @@ -1,6 +1,6 @@ #pragma once -#include "socket_msvc.hpp" +#include "socket_win32.hpp" #include #include diff --git a/components/rtsp/include/rtsp_server.hpp b/components/rtsp/include/rtsp_server.hpp index dcad63e571..a6ad2f451a 100644 --- a/components/rtsp/include/rtsp_server.hpp +++ b/components/rtsp/include/rtsp_server.hpp @@ -1,6 +1,6 @@ #pragma once -#include "socket_msvc.hpp" +#include "socket_win32.hpp" #include #include diff --git a/components/rtsp/include/rtsp_session.hpp b/components/rtsp/include/rtsp_session.hpp index 2e0e3a9308..18ab35f3b3 100644 --- a/components/rtsp/include/rtsp_session.hpp +++ b/components/rtsp/include/rtsp_session.hpp @@ -1,6 +1,6 @@ #pragma once -#include "socket_msvc.hpp" +#include "socket_win32.hpp" #include #include diff --git a/components/socket/include/socket.hpp b/components/socket/include/socket.hpp index 75758a2e6b..b0a8ae908a 100644 --- a/components/socket/include/socket.hpp +++ b/components/socket/include/socket.hpp @@ -1,9 +1,12 @@ #pragma once -#include "socket_msvc.hpp" +#include "socket_win32.hpp" #ifdef _WIN32 -typedef unsigned int sock_type_t; +/* Windows SOCKET is UINT_PTR (pointer-sized on 64-bit); use the real type so + * handles aren't truncated. SOCKET comes from , included above via + * socket_win32.hpp. */ +typedef SOCKET sock_type_t; #else /* Assume that any non-Windows platform uses POSIX-style sockets instead. */ #include @@ -18,6 +21,7 @@ typedef int sock_type_t; #include #include #include +#include #include #include @@ -198,6 +202,9 @@ class Socket : public BaseComponent { * @return true if setsockopt() succeeded, false otherwise (logs on failure). */ template bool set_option(int level, int option_name, const T &value) { + static_assert(std::is_trivially_copyable_v, + "set_option forwards the raw object bytes to setsockopt(); T must be " + "trivially copyable"); return set_option(level, option_name, &value, sizeof(value)); } @@ -319,6 +326,15 @@ class Socket : public BaseComponent { */ void cleanup(); +#ifdef _WIN32 + /** + * @brief Initialize Winsock (WSAStartup) exactly once for the process. + * @note Thread-safe: uses std::call_once so concurrent Socket construction + * cannot race the one-time initialization. + */ + void initialize_winsock(); +#endif + static constexpr int address_family_{AF_INET}; static constexpr int ip_protocol_{IPPROTO_IP}; diff --git a/components/socket/include/socket_reactor.hpp b/components/socket/include/socket_reactor.hpp index c81e972ea3..4efbfbe4c1 100644 --- a/components/socket/include/socket_reactor.hpp +++ b/components/socket/include/socket_reactor.hpp @@ -1,6 +1,6 @@ #pragma once -#include "socket_msvc.hpp" +#include "socket_win32.hpp" #include #include diff --git a/components/socket/include/socket_msvc.hpp b/components/socket/include/socket_win32.hpp similarity index 100% rename from components/socket/include/socket_msvc.hpp rename to components/socket/include/socket_win32.hpp diff --git a/components/socket/include/tcp_socket.hpp b/components/socket/include/tcp_socket.hpp index 5a5aca2d30..0dc2103c22 100644 --- a/components/socket/include/tcp_socket.hpp +++ b/components/socket/include/tcp_socket.hpp @@ -1,6 +1,6 @@ #pragma once -#include "socket_msvc.hpp" +#include "socket_win32.hpp" #ifndef _WIN32 #include diff --git a/components/socket/include/udp_socket.hpp b/components/socket/include/udp_socket.hpp index 9c97c664f0..9f9629cd57 100644 --- a/components/socket/include/udp_socket.hpp +++ b/components/socket/include/udp_socket.hpp @@ -1,6 +1,6 @@ #pragma once -#include "socket_msvc.hpp" +#include "socket_win32.hpp" #include #include diff --git a/components/socket/src/socket.cpp b/components/socket/src/socket.cpp index e1c89b41aa..ff7fed3313 100644 --- a/components/socket/src/socket.cpp +++ b/components/socket/src/socket.cpp @@ -1,5 +1,8 @@ #include "socket.hpp" +#include +#include + using namespace espp; void Socket::Info::init_ipv4(const std::string &addr, size_t prt) { @@ -73,19 +76,26 @@ void Socket::Info::from_sockaddr(const struct sockaddr_in6 &source_address) { } #endif // !defined(ESP_PLATFORM) || LWIP_IPV6 -[[maybe_unused]] static bool _socket_initialized = false; -Socket::Socket(sock_type_t socket_fd, const Logger::Config &logger_config) - : BaseComponent(logger_config) { #ifdef _WIN32 - if (!_socket_initialized) { +// Ensure Winsock is initialized exactly once, even if multiple Sockets are +// constructed concurrently (std::call_once serializes the check-then-act). +void Socket::initialize_winsock() { + static std::once_flag winsock_once; + std::call_once(winsock_once, [this]() { logger_.debug("Initializing Winsock"); WSADATA wsa_data; int err = WSAStartup(MAKEWORD(1, 1), &wsa_data); if (err != 0) { logger_.error("WSAStartup failed: {}", error_string(err)); } - _socket_initialized = true; - } + }); +} +#endif + +Socket::Socket(sock_type_t socket_fd, const Logger::Config &logger_config) + : BaseComponent(logger_config) { +#ifdef _WIN32 + initialize_winsock(); #endif socket_ = socket_fd; } @@ -93,15 +103,7 @@ Socket::Socket(sock_type_t socket_fd, const Logger::Config &logger_config) Socket::Socket(Type type, const Logger::Config &logger_config) : BaseComponent(logger_config) { #ifdef _WIN32 - if (!_socket_initialized) { - logger_.debug("Initializing Winsock"); - WSADATA wsa_data; - int err = WSAStartup(MAKEWORD(1, 1), &wsa_data); - if (err != 0) { - logger_.error("WSAStartup failed: {}", error_string(err)); - } - _socket_initialized = true; - } + initialize_winsock(); #endif init(type); } @@ -163,6 +165,13 @@ bool Socket::set_receive_timeout(const std::chrono::duration &timeout) { } bool Socket::set_option(int level, int option_name, const void *value, size_t size) { + // setsockopt's optlen is a signed int (Windows) / socklen_t; guard against a + // size_t that would narrow/overflow when cast. + if (size > static_cast(INT_MAX)) { + logger_.error("set_option size too large (level={}, option={}): {} > {}", level, option_name, + size, INT_MAX); + return false; + } #if defined(_WIN32) int err = setsockopt(socket_, level, option_name, reinterpret_cast(value), static_cast(size)); @@ -178,11 +187,21 @@ bool Socket::set_option(int level, int option_name, const void *value, size_t si } bool Socket::set_receive_buffer_size(size_t bytes) { + // SO_RCVBUF takes an int; reject values that would overflow the cast. + if (bytes > static_cast(INT_MAX)) { + logger_.error("set_receive_buffer_size too large: {} > {}", bytes, INT_MAX); + return false; + } int value = static_cast(bytes); return set_option(SOL_SOCKET, SO_RCVBUF, value); } bool Socket::set_send_buffer_size(size_t bytes) { + // SO_SNDBUF takes an int; reject values that would overflow the cast. + if (bytes > static_cast(INT_MAX)) { + logger_.error("set_send_buffer_size too large: {} > {}", bytes, INT_MAX); + return false; + } int value = static_cast(bytes); return set_option(SOL_SOCKET, SO_SNDBUF, value); } diff --git a/lib/include/espp.hpp b/lib/include/espp.hpp index f4c6d0de01..7b2adc3f7e 100644 --- a/lib/include/espp.hpp +++ b/lib/include/espp.hpp @@ -1,6 +1,6 @@ #pragma once -#include "socket_msvc.hpp" +#include "socket_win32.hpp" #ifdef _MSC_VER // windows.h is a C++ header and must not be wrapped in extern "C"; only the C From 623f362bd70b8767651edfe85c044d4b860bb820 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Sat, 15 Aug 2026 08:44:56 -0500 Subject: [PATCH 3/4] fix(socket): _WIN32 guard in espp.hpp + Winsock getsockopt optlen type Address PR #714 review: - lib/include/espp.hpp: gate the windows.h/wcswidth include on _WIN32 (not _MSC_VER) so MinGW/clang-cl Windows builds pull it in too. - socket.cpp get_receive_buffer_size: Winsock's getsockopt takes optlen as int*, not socklen_t*; use int on _WIN32, socklen_t elsewhere. Co-Authored-By: Claude Opus 4.8 --- components/socket/src/socket.cpp | 4 +++- lib/include/espp.hpp | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/components/socket/src/socket.cpp b/components/socket/src/socket.cpp index ff7fed3313..584ba1e276 100644 --- a/components/socket/src/socket.cpp +++ b/components/socket/src/socket.cpp @@ -213,10 +213,12 @@ bool Socket::set_reuse_address(bool enable) { std::optional Socket::get_receive_buffer_size() { int value = 0; - socklen_t len = sizeof(value); #if defined(_WIN32) + // Winsock's getsockopt takes the optlen as int*, not socklen_t*. + int len = sizeof(value); int err = getsockopt(socket_, SOL_SOCKET, SO_RCVBUF, reinterpret_cast(&value), &len); #else + socklen_t len = sizeof(value); int err = getsockopt(socket_, SOL_SOCKET, SO_RCVBUF, &value, &len); #endif if (err < 0) { diff --git a/lib/include/espp.hpp b/lib/include/espp.hpp index 7b2adc3f7e..07db0b04c4 100644 --- a/lib/include/espp.hpp +++ b/lib/include/espp.hpp @@ -2,9 +2,10 @@ #include "socket_win32.hpp" -#ifdef _MSC_VER +#ifdef _WIN32 // windows.h is a C++ header and must not be wrapped in extern "C"; only the C -// header (wcswidth) needs it. +// header (wcswidth) needs it. Guard on _WIN32 (not _MSC_VER) so MinGW / clang-cl +// Windows builds also pull it in. #include extern "C" { // NOTE: needed for tabulate From ff4b29261bae27bf53edff6ff96af7233476e2f0 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Sat, 15 Aug 2026 09:33:54 -0500 Subject: [PATCH 4/4] refactor(socket): scope PR #714 to the socket component only Per review, keep this PR to the socket layer. Revert the changes that belong to a follow-up (after this + the cmake PR merge): - components/rtps_embedded/.../EsppTransport.cpp: restore the original raw setsockopt SO_RCVBUF call (the migration to Socket::set_receive_buffer_size will follow separately). - undo the socket_msvc.hpp -> socket_win32.hpp rename (it forced include changes in rtsp/ and lib/espp.hpp); keep the file as socket_msvc.hpp with the generalized _WIN32 guards. The rename can follow with the caller migration. Net effect: PR #714 now touches only components/socket/ (the new set_option / buffer-size API, _MSC_VER->_WIN32 guards, sock_type_t=SOCKET, narrowing guards, WSAStartup call_once, getsockopt optlen type). Co-Authored-By: Claude Opus 4.8 --- .../rtps_embedded/src/communication/EsppTransport.cpp | 7 +++++-- components/rtsp/include/rtsp_client.hpp | 2 +- components/rtsp/include/rtsp_server.hpp | 2 +- components/rtsp/include/rtsp_session.hpp | 2 +- components/socket/include/socket.hpp | 4 ++-- .../socket/include/{socket_win32.hpp => socket_msvc.hpp} | 0 components/socket/include/socket_reactor.hpp | 2 +- components/socket/include/tcp_socket.hpp | 2 +- components/socket/include/udp_socket.hpp | 2 +- lib/include/espp.hpp | 7 +++---- 10 files changed, 16 insertions(+), 14 deletions(-) rename components/socket/include/{socket_win32.hpp => socket_msvc.hpp} (100%) diff --git a/components/rtps_embedded/src/communication/EsppTransport.cpp b/components/rtps_embedded/src/communication/EsppTransport.cpp index b21d8cafc3..09e8fac2f6 100644 --- a/components/rtps_embedded/src/communication/EsppTransport.cpp +++ b/components/rtps_embedded/src/communication/EsppTransport.cpp @@ -175,8 +175,11 @@ EsppTransport::Channel *EsppTransport::createChannel(Ip4Port_t receivePort, bool // large (fragmented) sample is not dropped before the reactor drains it. // Best-effort: some stacks clamp SO_RCVBUF, so failure is ignored. Only // compiled when fragmentation is enabled (never on the ESP32 default build). - // request 4 MB (kernel may clamp) - (void)channel.socket->set_receive_buffer_size(4 * 1024 * 1024); + { + int rcvbuf = 4 * 1024 * 1024; // request 4 MB (kernel may clamp) + ::setsockopt(channel.socket->native_handle(), SOL_SOCKET, SO_RCVBUF, + reinterpret_cast(&rcvbuf), sizeof(rcvbuf)); + } #endif if (!allow_reuse && !channel.socket->disable_reuse()) { diff --git a/components/rtsp/include/rtsp_client.hpp b/components/rtsp/include/rtsp_client.hpp index 6097b8c94f..451ca5bb92 100755 --- a/components/rtsp/include/rtsp_client.hpp +++ b/components/rtsp/include/rtsp_client.hpp @@ -1,6 +1,6 @@ #pragma once -#include "socket_win32.hpp" +#include "socket_msvc.hpp" #include #include diff --git a/components/rtsp/include/rtsp_server.hpp b/components/rtsp/include/rtsp_server.hpp index a6ad2f451a..dcad63e571 100644 --- a/components/rtsp/include/rtsp_server.hpp +++ b/components/rtsp/include/rtsp_server.hpp @@ -1,6 +1,6 @@ #pragma once -#include "socket_win32.hpp" +#include "socket_msvc.hpp" #include #include diff --git a/components/rtsp/include/rtsp_session.hpp b/components/rtsp/include/rtsp_session.hpp index 18ab35f3b3..2e0e3a9308 100644 --- a/components/rtsp/include/rtsp_session.hpp +++ b/components/rtsp/include/rtsp_session.hpp @@ -1,6 +1,6 @@ #pragma once -#include "socket_win32.hpp" +#include "socket_msvc.hpp" #include #include diff --git a/components/socket/include/socket.hpp b/components/socket/include/socket.hpp index b0a8ae908a..b6b5506f32 100644 --- a/components/socket/include/socket.hpp +++ b/components/socket/include/socket.hpp @@ -1,11 +1,11 @@ #pragma once -#include "socket_win32.hpp" +#include "socket_msvc.hpp" #ifdef _WIN32 /* Windows SOCKET is UINT_PTR (pointer-sized on 64-bit); use the real type so * handles aren't truncated. SOCKET comes from , included above via - * socket_win32.hpp. */ + * socket_msvc.hpp. */ typedef SOCKET sock_type_t; #else /* Assume that any non-Windows platform uses POSIX-style sockets instead. */ diff --git a/components/socket/include/socket_win32.hpp b/components/socket/include/socket_msvc.hpp similarity index 100% rename from components/socket/include/socket_win32.hpp rename to components/socket/include/socket_msvc.hpp diff --git a/components/socket/include/socket_reactor.hpp b/components/socket/include/socket_reactor.hpp index 4efbfbe4c1..c81e972ea3 100644 --- a/components/socket/include/socket_reactor.hpp +++ b/components/socket/include/socket_reactor.hpp @@ -1,6 +1,6 @@ #pragma once -#include "socket_win32.hpp" +#include "socket_msvc.hpp" #include #include diff --git a/components/socket/include/tcp_socket.hpp b/components/socket/include/tcp_socket.hpp index 0dc2103c22..5a5aca2d30 100644 --- a/components/socket/include/tcp_socket.hpp +++ b/components/socket/include/tcp_socket.hpp @@ -1,6 +1,6 @@ #pragma once -#include "socket_win32.hpp" +#include "socket_msvc.hpp" #ifndef _WIN32 #include diff --git a/components/socket/include/udp_socket.hpp b/components/socket/include/udp_socket.hpp index 9f9629cd57..9c97c664f0 100644 --- a/components/socket/include/udp_socket.hpp +++ b/components/socket/include/udp_socket.hpp @@ -1,6 +1,6 @@ #pragma once -#include "socket_win32.hpp" +#include "socket_msvc.hpp" #include #include diff --git a/lib/include/espp.hpp b/lib/include/espp.hpp index 07db0b04c4..f4c6d0de01 100644 --- a/lib/include/espp.hpp +++ b/lib/include/espp.hpp @@ -1,11 +1,10 @@ #pragma once -#include "socket_win32.hpp" +#include "socket_msvc.hpp" -#ifdef _WIN32 +#ifdef _MSC_VER // windows.h is a C++ header and must not be wrapped in extern "C"; only the C -// header (wcswidth) needs it. Guard on _WIN32 (not _MSC_VER) so MinGW / clang-cl -// Windows builds also pull it in. +// header (wcswidth) needs it. #include extern "C" { // NOTE: needed for tabulate