• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

randombit / botan / 15810493175

22 Jun 2025 08:26PM UTC coverage: 90.553% (-0.007%) from 90.56%
15810493175

Pull #4660

github

web-flow
Merge 1f97ef242 into c80dbf800
Pull Request #4660: Consolidation and Enhancement of BSD Socket Layer

98795 of 109102 relevant lines covered (90.55%)

12436881.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.42
/src/lib/utils/socket/socket.cpp
1
/*
2
* (C) 2015,2016,2017 Jack Lloyd
3
* (C) 2016 Daniel Neus
4
*     2025 Kagan Can Sit
5
*
6
* Botan is released under the Simplified BSD License (see license.txt)
7
*/
8

9
#include <botan/internal/socket.h>
10

11
#include <botan/exceptn.h>
12
#include <botan/mem_ops.h>
13
#include <botan/internal/fmt.h>
14
#include <botan/internal/socket_platform.h>
15
#include <botan/internal/stl_util.h>
16
#include <botan/internal/target_info.h>
17
#include <chrono>
18

19
#if defined(BOTAN_HAS_BOOST_ASIO)
20
   /*
21
  * We don't need serial port support anyway, and asking for it causes
22
  * macro conflicts with termios.h when this file is included in the
23
  * amalgamation.
24
  */
25
   #define BOOST_ASIO_DISABLE_SERIAL_PORT
26
   #include <boost/asio.hpp>
27
   #include <boost/asio/system_timer.hpp>
28
#endif
29

30
namespace Botan {
31
namespace {
32

33
#if defined(BOTAN_HAS_BOOST_ASIO)
34
class Asio_Socket final : public OS::Socket {
×
35
   public:
36
      Asio_Socket(std::string_view hostname, std::string_view service, std::chrono::milliseconds timeout) :
1✔
37
            m_timeout(timeout), m_timer(m_io), m_tcp(m_io) {
2✔
38
         m_timer.expires_after(m_timeout);
1✔
39
         check_timeout();
1✔
40

41
         boost::asio::ip::tcp::resolver resolver(m_io);
1✔
42
         boost::asio::ip::tcp::resolver::results_type dns_iter =
1✔
43
            resolver.resolve(std::string{hostname}, std::string{service});
3✔
44

45
         boost::system::error_code ec = boost::asio::error::would_block;
1✔
46

47
         auto connect_cb = [&ec](const boost::system::error_code& e, const auto&) { ec = e; };
1✔
48

49
         boost::asio::async_connect(m_tcp, dns_iter.begin(), dns_iter.end(), connect_cb);
2✔
50

51
         while(ec == boost::asio::error::would_block) {
2✔
52
            m_io.run_one();
1✔
53
         }
54

55
         if(ec) {
1✔
56
            throw boost::system::system_error(ec);
×
57
         }
58
         if(m_tcp.is_open() == false) {
1✔
59
            throw System_Error(fmt("Connection to host {} failed", hostname));
×
60
         }
61
      }
1✔
62

63
      void write(const uint8_t buf[], size_t len) override {
1✔
64
         m_timer.expires_after(m_timeout);
1✔
65

66
         boost::system::error_code ec = boost::asio::error::would_block;
1✔
67

68
         m_tcp.async_send(boost::asio::buffer(buf, len), [&ec](boost::system::error_code e, size_t) { ec = e; });
2✔
69

70
         while(ec == boost::asio::error::would_block) {
3✔
71
            m_io.run_one();
2✔
72
         }
73

74
         if(ec) {
1✔
75
            throw boost::system::system_error(ec);
×
76
         }
77
      }
1✔
78

79
      size_t read(uint8_t buf[], size_t len) override {
2✔
80
         m_timer.expires_after(m_timeout);
2✔
81

82
         boost::system::error_code ec = boost::asio::error::would_block;
2✔
83
         size_t got = 0;
2✔
84

85
         m_tcp.async_read_some(boost::asio::buffer(buf, len), [&](boost::system::error_code cb_ec, size_t cb_got) {
2✔
86
            ec = cb_ec;
2✔
87
            got = cb_got;
2✔
88
         });
89

90
         while(ec == boost::asio::error::would_block) {
6✔
91
            m_io.run_one();
4✔
92
         }
93

94
         if(ec) {
2✔
95
            if(ec == boost::asio::error::eof) {
1✔
96
               return 0;
97
            }
98
            throw boost::system::system_error(ec);  // Some other error.
×
99
         }
100

101
         return got;
1✔
102
      }
103

104
   private:
105
      void check_timeout() {
4✔
106
         if(m_tcp.is_open() && m_timer.expiry() < std::chrono::system_clock::now()) {
4✔
107
            boost::system::error_code err;
×
108

109
            // NOLINTNEXTLINE(bugprone-unused-return-value,cert-err33-c)
110
            m_tcp.close(err);
×
111
         }
112

113
         m_timer.async_wait(std::bind(&Asio_Socket::check_timeout, this));
4✔
114
      }
4✔
115

116
      const std::chrono::milliseconds m_timeout;
117
      boost::asio::io_context m_io;
118
      boost::asio::system_timer m_timer;
119
      boost::asio::ip::tcp::socket m_tcp;
120
};
121

122
#elif defined(BOTAN_TARGET_OS_HAS_SOCKETS) || defined(BOTAN_TARGET_OS_HAS_WINSOCK2)
123

124
class BSD_Socket final : public OS::Socket {
125
   public:
126
      BSD_Socket(std::string_view hostname, std::string_view service, std::chrono::microseconds timeout) :
127
            m_timeout(timeout) {
128
         Botan::OS::Socket_Platform::socket_init();
129
         m_socket = Botan::OS::Socket_Platform::invalid_socket();
130

131
         addrinfo hints;
132
         Botan::clear_mem(&hints, 1);
133
         hints.ai_family = AF_UNSPEC;
134
         hints.ai_socktype = SOCK_STREAM;
135

136
         Botan::OS::Socket_Platform::unique_addrinfo_ptr res = nullptr;
137
         int rc =
138
            ::getaddrinfo(std::string(hostname).c_str(), std::string(service).c_str(), &hints, Botan::out_ptr(res));
139

140
         if(rc != 0) {
141
            throw System_Error(fmt("Name resolution failed for {}", hostname), rc);
142
         }
143

144
         for(addrinfo* rp = res.get(); (m_socket == Botan::OS::Socket_Platform::invalid_socket()) && rp != nullptr;
145
             rp = rp->ai_next) {
146
            if(rp->ai_family != AF_INET && rp->ai_family != AF_INET6) {
147
               continue;
148
            }
149

150
            m_socket = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
151

152
            if(m_socket == Botan::OS::Socket_Platform::invalid_socket()) {
153
               // unsupported socket type?
154
               continue;
155
            }
156

157
            Botan::OS::Socket_Platform::set_nonblocking(m_socket);
158

159
            int err = ::connect(m_socket, rp->ai_addr, static_cast<socklen_type>(rp->ai_addrlen));
160

161
            if(err == -1) {
162
               int active = 0;
163
               if(Botan::OS::Socket_Platform::nonblocking_connect_in_progress()) {
164
                  struct timeval timeout_tv = make_timeout_tv();
165
                  fd_set write_set;
166
                  FD_ZERO(&write_set);
167
                  // Weirdly, Winsock uses a SOCKET type but wants FD_SET to get an int instead
168
                  FD_SET(static_cast<int>(m_socket), &write_set);
169

170
                  active = ::select(static_cast<int>(m_socket + 1), nullptr, &write_set, nullptr, &timeout_tv);
171

172
                  if(active) {
173
                     int socket_error = 0;
174
                     socklen_t len = sizeof(socket_error);
175

176
                     if(::getsockopt(m_socket, SOL_SOCKET, SO_ERROR, reinterpret_cast<char*>(&socket_error), &len) <
177
                        0) {
178
                        throw System_Error("Error calling getsockopt", errno);
179
                     }
180

181
                     if(socket_error != 0) {
182
                        active = 0;
183
                     }
184
                  }
185
               }
186

187
               if(active == 0) {
188
                  Botan::OS::Socket_Platform::close_socket(m_socket);
189
                  m_socket = Botan::OS::Socket_Platform::invalid_socket();
190
                  continue;
191
               }
192
            }
193
         }
194

195
         if(m_socket == Botan::OS::Socket_Platform::invalid_socket()) {
196
            throw System_Error(fmt("Connection to {} for service {} failed with errno", hostname, service, errno),
197
                               errno);
198
         }
199
      }
200

201
      ~BSD_Socket() override {
202
         Botan::OS::Socket_Platform::close_socket(m_socket);
203
         m_socket = Botan::OS::Socket_Platform::invalid_socket();
204
         Botan::OS::Socket_Platform::socket_fini();
205
      }
206

207
      BSD_Socket(const BSD_Socket& other) = delete;
208
      BSD_Socket(BSD_Socket&& other) = delete;
209
      BSD_Socket& operator=(const BSD_Socket& other) = delete;
210
      BSD_Socket& operator=(BSD_Socket&& other) = delete;
211

212
      void write(const uint8_t buf[], size_t len) override {
213
         fd_set write_set;
214
         FD_ZERO(&write_set);
215
         FD_SET(m_socket, &write_set);
216

217
         size_t sent_so_far = 0;
218
         while(sent_so_far != len) {
219
            struct timeval timeout = make_timeout_tv();
220
            int active = ::select(static_cast<int>(m_socket + 1), nullptr, &write_set, nullptr, &timeout);
221

222
            if(active == 0) {
223
               throw System_Error("Timeout during socket write");
224
            }
225

226
            const size_t left = len - sent_so_far;
227
            socket_op_ret_type sent =
228
               ::send(m_socket, cast_uint8_ptr_to_char(&buf[sent_so_far]), static_cast<sendrecv_len_type>(left), 0);
229
            if(sent < 0) {
230
               throw System_Error("Socket write failed", errno);
231
            } else {
232
               sent_so_far += static_cast<size_t>(sent);
233
            }
234
         }
235
      }
236

237
      size_t read(uint8_t buf[], size_t len) override {
238
         fd_set read_set;
239
         FD_ZERO(&read_set);
240
         FD_SET(m_socket, &read_set);
241

242
         struct timeval timeout = make_timeout_tv();
243
         int active = ::select(static_cast<int>(m_socket + 1), &read_set, nullptr, nullptr, &timeout);
244

245
         if(active == 0) {
246
            throw System_Error("Timeout during socket read");
247
         }
248

249
         socket_op_ret_type got = ::recv(m_socket, cast_uint8_ptr_to_char(buf), static_cast<sendrecv_len_type>(len), 0);
250
         if(got < 0) {
251
            throw System_Error("Socket read failed", errno);
252
         }
253

254
         return static_cast<size_t>(got);
255
      }
256

257
   private:
258
      // Import socket operation types from Socket_Platform namespace
259
      using socket_type = Botan::OS::Socket_Platform::socket_type;
260
      using socket_op_ret_type = Botan::OS::Socket_Platform::socket_op_ret_type;
261
      using socklen_type = Botan::OS::Socket_Platform::socklen_type;
262
      using sendrecv_len_type = Botan::OS::Socket_Platform::sendrecv_len_type;
263

264
      const std::chrono::microseconds m_timeout;
265
      socket_type m_socket;
266

267
      struct timeval make_timeout_tv() const {
268
         struct timeval tv;
269
         tv.tv_sec = static_cast<decltype(timeval::tv_sec)>(m_timeout.count() / 1000000);
270
         tv.tv_usec = static_cast<decltype(timeval::tv_usec)>(m_timeout.count() % 1000000);
271
         return tv;
272
      }
273
};
274

275
#endif
276

277
}  // namespace
278

279
std::unique_ptr<OS::Socket> OS::open_socket(std::string_view hostname,
1✔
280
                                            std::string_view service,
281
                                            std::chrono::milliseconds timeout) {
282
#if defined(BOTAN_HAS_BOOST_ASIO)
283
   return std::make_unique<Asio_Socket>(hostname, service, timeout);
1✔
284

285
#elif defined(BOTAN_TARGET_OS_HAS_SOCKETS) || defined(BOTAN_TARGET_OS_HAS_WINSOCK2)
286
   return std::make_unique<BSD_Socket>(hostname, service, timeout);
287

288
#else
289
   BOTAN_UNUSED(hostname, service, timeout);
290
   // No sockets for you
291
   return std::unique_ptr<Socket>();
292
#endif
293
}
294

295
}  // namespace Botan
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc