From 73021c52140a011034896c0a5e609d63a381986a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ka=C4=9Fan=20Can=20=C5=9Eit?= Date: Thu, 13 Mar 2025 00:14:30 +0300 Subject: [PATCH] Cleanup in socket/asio code --- src/lib/utils/socket/info.txt | 1 + src/lib/utils/socket/socket.cpp | 105 +------------------ src/lib/utils/socket/socket_asio.h | 151 ++++++++++++++++++++++++++++ src/lib/utils/socket/socket_udp.cpp | 107 ++------------------ 4 files changed, 161 insertions(+), 203 deletions(-) create mode 100644 src/lib/utils/socket/socket_asio.h diff --git a/src/lib/utils/socket/info.txt b/src/lib/utils/socket/info.txt index 38a44c73195..aacdb68c300 100644 --- a/src/lib/utils/socket/info.txt +++ b/src/lib/utils/socket/info.txt @@ -10,6 +10,7 @@ name -> "Socket" uri.h socket.h socket_udp.h +socket_asio.h diff --git a/src/lib/utils/socket/socket.cpp b/src/lib/utils/socket/socket.cpp index 77d604ac0a9..f16f6307ced 100644 --- a/src/lib/utils/socket/socket.cpp +++ b/src/lib/utils/socket/socket.cpp @@ -10,20 +10,11 @@ #include #include #include +#include #include #include -#if defined(BOTAN_HAS_BOOST_ASIO) - /* - * We don't need serial port support anyway, and asking for it causes - * macro conflicts with termios.h when this file is included in the - * amalgamation. - */ - #define BOOST_ASIO_DISABLE_SERIAL_PORT - #include - #include - -#elif defined(BOTAN_TARGET_OS_HAS_SOCKETS) +#if defined(BOTAN_TARGET_OS_HAS_SOCKETS) #include #include #include @@ -41,97 +32,7 @@ namespace Botan { namespace { -#if defined(BOTAN_HAS_BOOST_ASIO) - -class Asio_Socket final : public OS::Socket { - public: - Asio_Socket(std::string_view hostname, std::string_view service, std::chrono::milliseconds timeout) : - m_timeout(timeout), m_timer(m_io), m_tcp(m_io) { - m_timer.expires_after(m_timeout); - check_timeout(); - - boost::asio::ip::tcp::resolver resolver(m_io); - boost::asio::ip::tcp::resolver::results_type dns_iter = - resolver.resolve(std::string{hostname}, std::string{service}); - - boost::system::error_code ec = boost::asio::error::would_block; - - auto connect_cb = [&ec](const boost::system::error_code& e, const auto&) { ec = e; }; - - boost::asio::async_connect(m_tcp, dns_iter.begin(), dns_iter.end(), connect_cb); - - while(ec == boost::asio::error::would_block) { - m_io.run_one(); - } - - if(ec) { - throw boost::system::system_error(ec); - } - if(m_tcp.is_open() == false) { - throw System_Error(fmt("Connection to host {} failed", hostname)); - } - } - - void write(const uint8_t buf[], size_t len) override { - m_timer.expires_after(m_timeout); - - boost::system::error_code ec = boost::asio::error::would_block; - - m_tcp.async_send(boost::asio::buffer(buf, len), [&ec](boost::system::error_code e, size_t) { ec = e; }); - - while(ec == boost::asio::error::would_block) { - m_io.run_one(); - } - - if(ec) { - throw boost::system::system_error(ec); - } - } - - size_t read(uint8_t buf[], size_t len) override { - m_timer.expires_after(m_timeout); - - boost::system::error_code ec = boost::asio::error::would_block; - size_t got = 0; - - m_tcp.async_read_some(boost::asio::buffer(buf, len), [&](boost::system::error_code cb_ec, size_t cb_got) { - ec = cb_ec; - got = cb_got; - }); - - while(ec == boost::asio::error::would_block) { - m_io.run_one(); - } - - if(ec) { - if(ec == boost::asio::error::eof) { - return 0; - } - throw boost::system::system_error(ec); // Some other error. - } - - return got; - } - - private: - void check_timeout() { - if(m_tcp.is_open() && m_timer.expiry() < std::chrono::system_clock::now()) { - boost::system::error_code err; - - // NOLINTNEXTLINE(bugprone-unused-return-value,cert-err33-c) - m_tcp.close(err); - } - - m_timer.async_wait(std::bind(&Asio_Socket::check_timeout, this)); - } - - const std::chrono::milliseconds m_timeout; - boost::asio::io_context m_io; - boost::asio::system_timer m_timer; - boost::asio::ip::tcp::socket m_tcp; -}; - -#elif defined(BOTAN_TARGET_OS_HAS_SOCKETS) || defined(BOTAN_TARGET_OS_HAS_WINSOCK2) +#if defined(BOTAN_TARGET_OS_HAS_SOCKETS) || defined(BOTAN_TARGET_OS_HAS_WINSOCK2) class BSD_Socket final : public OS::Socket { private: diff --git a/src/lib/utils/socket/socket_asio.h b/src/lib/utils/socket/socket_asio.h new file mode 100644 index 00000000000..5916eda29b2 --- /dev/null +++ b/src/lib/utils/socket/socket_asio.h @@ -0,0 +1,151 @@ +/* +* (C) 2015,2016,2017 Jack Lloyd +* (C) 2016 Daniel Neus +* (C) 2019 Nuno Goncalves +* (C) 2025 Kagan Can Sit +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#ifndef BOTAN_SOCKET_ASIO_H_ +#define BOTAN_SOCKET_ASIO_H_ + +#include +#include +#include +#include +#include + +#if defined(BOTAN_HAS_BOOST_ASIO) + /* + * We don't need serial port support anyway, and asking for it causes + * macro conflicts with termios.h when this file is included in the + * amalgamation. + */ + #define BOOST_ASIO_DISABLE_SERIAL_PORT + #include + #include +#endif + +namespace Botan::OS { + +#if defined(BOTAN_HAS_BOOST_ASIO) + +/* + * A template-based implementation of Asio sockets that can be used + * for both TCP and UDP protocols, reducing code duplication. + * + * @param BaseSocketType The base socket class (Socket for TCP, SocketUDP for UDP) + * @param ProtocolType The Asio protocol type (boost::asio::ip::tcp or boost::asio::ip::udp) + */ +template +class Asio_Socket_Base : public BaseSocketType { + public: + Asio_Socket_Base(std::string_view hostname, std::string_view service, std::chrono::milliseconds timeout) : + // Convert milliseconds to microseconds for consistent timing operations + m_timeout(std::chrono::duration_cast(timeout)), m_timer(m_io), m_socket(m_io) { + m_timer.expires_after(m_timeout); + check_timeout(); + + // Resolve the DNS + typename ProtocolType::resolver resolver(m_io); + auto endPoints = resolver.resolve(std::string{hostname}, std::string{service}); + + boost::system::error_code ec = boost::asio::error::would_block; + + auto connect_cb = [&ec](const boost::system::error_code& e, const typename ProtocolType::endpoint&) { + ec = e; + }; + + boost::asio::async_connect(m_socket, endPoints, connect_cb); + + while(ec == boost::asio::error::would_block) { + m_io.run_one(); + } + + if(ec) { + throw boost::system::system_error(ec); + } + + if(m_socket.is_open() == false) { + throw System_Error(fmt("Connection to host {} failed", hostname)); + } + } + + void write(const uint8_t buf[], size_t len) override { + m_timer.expires_after(m_timeout); + + boost::system::error_code ec = boost::asio::error::would_block; + + m_socket.async_send(boost::asio::buffer(buf, len), [&ec](boost::system::error_code e, size_t) { ec = e; }); + + while(ec == boost::asio::error::would_block) { + m_io.run_one(); + } + + if(ec) { + throw boost::system::system_error(ec); + } + } + + size_t read(uint8_t buf[], size_t len) override { + m_timer.expires_after(m_timeout); + + boost::system::error_code ec = boost::asio::error::would_block; + size_t got = 0; + + // Use different read methods based on protocol type + if constexpr(std::is_same_v) { + m_socket.async_read_some(boost::asio::buffer(buf, len), + [&](boost::system::error_code cb_ec, size_t cb_got) { + ec = cb_ec; + got = cb_got; + }); + } else if constexpr(std::is_same_v) { + m_socket.async_receive(boost::asio::buffer(buf, len), [&](boost::system::error_code cb_ec, size_t cb_got) { + ec = cb_ec; + got = cb_got; + }); + } + + while(ec == boost::asio::error::would_block) { + m_io.run_one(); + } + + if(ec) { + if(ec == boost::asio::error::eof) { + return 0; + } + throw boost::system::system_error(ec); // Some other error. + } + + return got; + } + + private: + void check_timeout() { + if(m_socket.is_open() && m_timer.expiry() < std::chrono::system_clock::now()) { + boost::system::error_code err; + + // NOLINTNEXTLINE(bugprone-unused-return-value,cert-err33-c) + m_socket.close(err); + } + + m_timer.async_wait(std::bind(&Asio_Socket_Base::check_timeout, this)); + } + + const std::chrono::microseconds m_timeout; + boost::asio::io_context m_io; + boost::asio::system_timer m_timer; + typename ProtocolType::socket m_socket; +}; + +// Convenience type aliases for common socket types +using Asio_Socket = Asio_Socket_Base; +using Asio_SocketUDP = Asio_Socket_Base; + +#endif // BOTAN_HAS_BOOST_ASIO + +} // namespace Botan::OS + +#endif // BOTAN_SOCKET_ASIO_H_ diff --git a/src/lib/utils/socket/socket_udp.cpp b/src/lib/utils/socket/socket_udp.cpp index cd268bd6d76..c755c446e9f 100644 --- a/src/lib/utils/socket/socket_udp.cpp +++ b/src/lib/utils/socket/socket_udp.cpp @@ -11,20 +11,12 @@ #include #include #include +#include #include #include #include -#if defined(BOTAN_HAS_BOOST_ASIO) - /* - * We don't need serial port support anyway, and asking for it - * causes macro conflicts with Darwin's termios.h when this - * file is included in the amalgamation. GH #350 - */ - #define BOOST_ASIO_DISABLE_SERIAL_PORT - #include - #include -#elif defined(BOTAN_TARGET_OS_HAS_SOCKETS) +#if defined(BOTAN_TARGET_OS_HAS_SOCKETS) #include #include #include @@ -42,96 +34,7 @@ namespace Botan { namespace { -#if defined(BOTAN_HAS_BOOST_ASIO) -class Asio_SocketUDP final : public OS::SocketUDP { - public: - Asio_SocketUDP(std::string_view hostname, std::string_view service, std::chrono::microseconds timeout) : - m_timeout(timeout), m_timer(m_io), m_udp(m_io) { - m_timer.expires_after(m_timeout); - check_timeout(); - - boost::asio::ip::udp::resolver resolver(m_io); - boost::asio::ip::udp::resolver::results_type dns_iter = - resolver.resolve(std::string{hostname}, std::string{service}); - - boost::system::error_code ec = boost::asio::error::would_block; - - auto connect_cb = [&ec](const boost::system::error_code& e, - const boost::asio::ip::udp::resolver::results_type::iterator&) { ec = e; }; - - boost::asio::async_connect(m_udp, dns_iter.begin(), dns_iter.end(), connect_cb); - - while(ec == boost::asio::error::would_block) { - m_io.run_one(); - } - - if(ec) { - throw boost::system::system_error(ec); - } - if(m_udp.is_open() == false) { - throw System_Error(fmt("Connection to host {} failed", hostname)); - } - } - - void write(const uint8_t buf[], size_t len) override { - m_timer.expires_after(m_timeout); - - boost::system::error_code ec = boost::asio::error::would_block; - - m_udp.async_send(boost::asio::buffer(buf, len), [&ec](boost::system::error_code e, size_t) { ec = e; }); - - while(ec == boost::asio::error::would_block) { - m_io.run_one(); - } - - if(ec) { - throw boost::system::system_error(ec); - } - } - - size_t read(uint8_t buf[], size_t len) override { - m_timer.expires_after(m_timeout); - - boost::system::error_code ec = boost::asio::error::would_block; - size_t got = 0; - - m_udp.async_receive(boost::asio::buffer(buf, len), [&](boost::system::error_code cb_ec, size_t cb_got) { - ec = cb_ec; - got = cb_got; - }); - - while(ec == boost::asio::error::would_block) { - m_io.run_one(); - } - - if(ec) { - if(ec == boost::asio::error::eof) { - return 0; - } - throw boost::system::system_error(ec); // Some other error. - } - - return got; - } - - private: - void check_timeout() { - if(m_udp.is_open() && m_timer.expiry() < std::chrono::system_clock::now()) { - boost::system::error_code err; - - // NOLINTNEXTLINE(bugprone-unused-return-value,cert-err33-c) - m_udp.close(err); - } - - m_timer.async_wait(std::bind(&Asio_SocketUDP::check_timeout, this)); - } - - const std::chrono::microseconds m_timeout; - boost::asio::io_context m_io; - boost::asio::system_timer m_timer; - boost::asio::ip::udp::socket m_udp; -}; -#elif defined(BOTAN_TARGET_OS_HAS_SOCKETS) || defined(BOTAN_TARGET_OS_HAS_WINSOCK2) +#if defined(BOTAN_TARGET_OS_HAS_SOCKETS) || defined(BOTAN_TARGET_OS_HAS_WINSOCK2) class BSD_SocketUDP final : public OS::SocketUDP { public: BSD_SocketUDP(std::string_view hostname, std::string_view service, std::chrono::microseconds timeout) : @@ -319,7 +222,9 @@ std::unique_ptr OS::open_socket_udp(std::string_view hostname, std::string_view service, std::chrono::microseconds timeout) { #if defined(BOTAN_HAS_BOOST_ASIO) - return std::make_unique(hostname, service, timeout); + // In the old implementation, the parameter taken for UDP was microseconds. Homewer template calls are in milliseconds. + return std::make_unique( + hostname, service, std::chrono::duration_cast(timeout)); #elif defined(BOTAN_TARGET_OS_HAS_SOCKETS) || defined(BOTAN_TARGET_OS_HAS_WINSOCK2) return std::make_unique(hostname, service, timeout); #else