1 #define WIN32_LEAN_AND_MEAN
2 
3 #include "ClientNetworking.h"
4 
5 #include "../network/Message.h"
6 #include "../network/MessageQueue.h"
7 
8 // boost::asio pulls in windows.h which in turn defines the macros GetMessage,
9 // SendMessage, min and max. Disabling the generation of the min and max macros
10 // and undefining those should avoid name collisions with std c++ library and
11 // FreeOrion function names.
12 #define NOMINMAX
13 #include <boost/asio.hpp>
14 #include <boost/asio/high_resolution_timer.hpp>
15 #ifdef FREEORION_WIN32
16 #   undef GetMessage
17 #   undef SendMessage
18 #endif
19 
20 #include "../network/Networking.h"
21 #include "../util/Logger.h"
22 #include "../util/OptionsDB.h"
23 
24 #include <boost/algorithm/string/predicate.hpp>
25 #include <boost/algorithm/string/erase.hpp>
26 #include <boost/thread/condition.hpp>
27 #include <boost/thread/thread.hpp>
28 #include <boost/optional/optional.hpp>
29 
30 #include <thread>
31 
32 using boost::asio::ip::tcp;
33 using namespace Networking;
34 
35 /** In Boost 1.66, io_service was replaced with a typedef of io_context.
36   * That typedef was removed in Boost 1.70 along with other interface changes.
37   * This code uses io_context for future compatibility and adds the typedef
38   * here for old versions of Boost. */
39 #if BOOST_VERSION < 106600
40 namespace boost { namespace asio {
41     typedef io_service io_context;
42 }}
43 #endif
44 
45 namespace {
46     DeclareThreadSafeLogger(network);
47 
48     /** A simple client that broadcasts UDP datagrams on the local network for
49         FreeOrion servers, and reports any it finds. */
50     class ServerDiscoverer {
51     public:
52         using ServerList = std::vector<std::pair<boost::asio::ip::address, std::string>>;
53 
ServerDiscoverer(boost::asio::io_context & io_context)54         ServerDiscoverer(boost::asio::io_context& io_context) :
55             m_io_context(&io_context),
56             m_timer(io_context),
57             m_socket(io_context),
58             m_recv_buf(),
59             m_receive_successful(false),
60             m_server_name()
61         {}
62 
Servers() const63         const ServerList& Servers() const
64         { return m_servers; }
65 
DiscoverServers()66         void DiscoverServers() {
67             using namespace boost::asio::ip;
68             udp::resolver resolver(*m_io_context);
69             udp::resolver::query query(udp::v4(), "255.255.255.255",
70                                        std::to_string(Networking::DiscoveryPort()),
71                                        resolver_query_base::address_configured |
72                                        resolver_query_base::numeric_service);
73             udp::resolver::iterator end_it;
74             for (udp::resolver::iterator it = resolver.resolve(query); it != end_it; ++it) {
75                 udp::endpoint receiver_endpoint = *it;
76 
77                 m_socket.close();
78                 m_socket.open(udp::v4());
79                 m_socket.set_option(boost::asio::socket_base::broadcast(true));
80 
81                 m_socket.send_to(boost::asio::buffer(DISCOVERY_QUESTION),
82                                  receiver_endpoint);
83 
84                 m_socket.async_receive_from(
85                     boost::asio::buffer(m_recv_buf),
86                     m_sender_endpoint,
87                     boost::bind(&ServerDiscoverer::HandleReceive,
88                                 this,
89                                 boost::asio::placeholders::error,
90                                 boost::asio::placeholders::bytes_transferred));
91 
92 #if BOOST_VERSION >= 106600
93                 m_timer.expires_after(std::chrono::seconds(2));
94 #else
95                 m_timer.expires_from_now(std::chrono::seconds(2));
96 #endif
97                 m_timer.async_wait(boost::bind(&ServerDiscoverer::CloseSocket, this));
98                 m_io_context->run();
99                 m_io_context->reset();
100                 if (m_receive_successful) {
101                     boost::asio::ip::address address = m_server_name == "localhost" ?
102                         boost::asio::ip::address::from_string("127.0.0.1") :
103                         m_sender_endpoint.address();
104                     m_servers.push_back({address, m_server_name});
105                 }
106                 m_receive_successful = false;
107                 m_server_name.clear();
108             }
109         }
110 
111     private:
HandleReceive(const boost::system::error_code & error,std::size_t length)112         void HandleReceive(const boost::system::error_code& error, std::size_t length) {
113             if (error == boost::asio::error::message_size) {
114                 m_socket.async_receive_from(
115                     boost::asio::buffer(m_recv_buf),
116                     m_sender_endpoint,
117                     boost::bind(&ServerDiscoverer::HandleReceive,
118                                 this,
119                                 boost::asio::placeholders::error,
120                                 boost::asio::placeholders::bytes_transferred));
121 
122             } else if (!error) {
123                 std::string buffer_string(m_recv_buf.begin(), m_recv_buf.begin() + length);
124                 if (boost::algorithm::starts_with(buffer_string, DISCOVERY_ANSWER)) {
125                     m_receive_successful = true;
126                     m_server_name =
127                         boost::algorithm::erase_first_copy(buffer_string, DISCOVERY_ANSWER);
128                     if (m_server_name == boost::asio::ip::host_name())
129                         m_server_name = "localhost";
130                     m_timer.cancel();
131                     m_socket.close();
132                 }
133             }
134         }
135 
CloseSocket()136         void CloseSocket()
137         { m_socket.close(); }
138 
139         boost::asio::io_context*            m_io_context;
140         boost::asio::high_resolution_timer  m_timer;
141         boost::asio::ip::udp::socket        m_socket;
142 
143         std::array<char, 1024>              m_recv_buf;
144 
145         boost::asio::ip::udp::endpoint      m_sender_endpoint;
146         bool                                m_receive_successful;
147         std::string                         m_server_name;
148         ServerList                          m_servers;
149     };
150 }
151 
152 
153 
154 class ClientNetworking::Impl {
155 public:
156     /** The type of list returned by a call to DiscoverLANServers(). */
157     using ServerList = std::vector<std::pair<boost::asio::ip::address, std::string>>;
158 
159     /** \name Structors */ //@{
160     Impl();
161     //@}
162 
163     /** \name Accessors */ //@{
164     /** Returns true iff the client is full duplex connected to the server. */
165     bool IsConnected() const;
166 
167     /** Returns true iff the client is connected to receive from the server. */
168     bool IsRxConnected() const;
169 
170     /** Returns true iff the client is connected to send to the server. */
171     bool IsTxConnected() const;
172 
173     /** Returns the ID of the player on this client. */
174     int PlayerID() const;
175 
176     /** Returns the ID of the host player, or INVALID_PLAYER_ID if there is no host player. */
177     int HostPlayerID() const;
178 
179     /** Returns whether the indicated player ID is the host. */
180     bool PlayerIsHost(int player_id) const;
181 
182     /** Checks if the client has some authorization \a role. */
183     bool HasAuthRole(Networking::RoleType role) const;
184 
185     /** Returns destination address of server. */
186     const std::string& Destination() const;
187     //@}
188 
189     /** \name Mutators */ //@{
190     /** Returns a list of the addresses and names of all servers on the Local
191         Area Network. */
192     ClientNetworking::ServerNames DiscoverLANServerNames();
193 
194     /** Connects to the server at \a ip_address.  On failure, repeated
195         attempts will be made until \a timeout seconds has elapsed. If \p
196         expect_timeout is true, timeout is not reported as an error. */
197     bool ConnectToServer(const ClientNetworking* const self,
198                          const std::string& ip_address,
199                          const std::chrono::milliseconds& timeout = std::chrono::seconds(10),
200                          bool expect_timeout = false);
201 
202     /** Connects to the server on the client's host.  On failure, repeated
203         attempts will be made until \a timeout seconds has elapsed. If \p
204         expect_timeout is true, timeout is not reported as an error.*/
205     bool ConnectToLocalHostServer(
206         const ClientNetworking* const self,
207         const std::chrono::milliseconds& timeout = std::chrono::seconds(10),
208         bool expect_timeout = false);
209 
210     /** Sends \a message to the server.  This function actually just enqueues
211         the message for sending and returns immediately. */
212     void SendMessage(const Message& message);
213 
214     /** Return the next incoming message from the server if available or boost::none.
215         Remove the message from the incoming message queue. */
216     boost::optional<Message> GetMessage();
217 
218     /** Disconnects the client from the server. */
219     void DisconnectFromServer();
220 
221     /** Sets player ID for this client. */
222     void SetPlayerID(int player_id);
223 
224     /** Sets Host player ID. */
225     void SetHostPlayerID(int host_player_id);
226 
227     /** Get authorization roles access. */
228     Networking::AuthRoles& AuthorizationRoles();
229     //@}
230 
231 private:
232     void HandleException(const boost::system::system_error& error);
233     void HandleConnection(boost::asio::ip::tcp::resolver::iterator* it,
234                           const boost::system::error_code& error);
235 
236     void NetworkingThread(const std::shared_ptr<const ClientNetworking> self);
237     void HandleMessageBodyRead(const std::shared_ptr<const ClientNetworking>& keep_alive,
238                                boost::system::error_code error, std::size_t bytes_transferred);
239     void HandleMessageHeaderRead(const std::shared_ptr<const ClientNetworking>& keep_alive,
240                                  boost::system::error_code error, std::size_t bytes_transferred);
241     void AsyncReadMessage(const std::shared_ptr<const ClientNetworking>& keep_alive);
242     void HandleMessageWrite(boost::system::error_code error, std::size_t bytes_transferred);
243     void AsyncWriteMessage();
244     void SendMessageImpl(Message message);
245     void DisconnectFromServerImpl();
246 
247     int                             m_player_id;
248     int                             m_host_player_id;
249     Networking::AuthRoles           m_roles;
250 
251     boost::asio::io_context         m_io_context;
252     boost::asio::ip::tcp::socket    m_socket;
253 
254     // m_mutex guards m_incoming_message, m_rx_connected and m_tx_connected which are written by
255     // the networking thread and read by the main thread to check incoming messages and connection
256     // status. As those read and write operations are not atomic, shared access has to be
257     // protected to prevent unpredictable results.
258     mutable boost::mutex            m_mutex;
259 
260     bool                            m_rx_connected;      // accessed from multiple threads
261     bool                            m_tx_connected;      // accessed from multiple threads
262 
263     MessageQueue                    m_incoming_messages; // accessed from multiple threads, but its interface is threadsafe
264     std::list<Message>              m_outgoing_messages;
265 
266     Message::HeaderBuffer           m_incoming_header;
267     Message                         m_incoming_message;
268     Message::HeaderBuffer           m_outgoing_header;
269 
270     std::string                     m_destination;
271 };
272 
273 
274 ////////////////////////////////////////////////
275 // ClientNetworking Impl
276 ////////////////////////////////////////////////
Impl()277 ClientNetworking::Impl::Impl() :
278     m_player_id(Networking::INVALID_PLAYER_ID),
279     m_host_player_id(Networking::INVALID_PLAYER_ID),
280     m_io_context(),
281     m_socket(m_io_context),
282     m_rx_connected(false),
283     m_tx_connected(false),
284     m_incoming_messages(m_mutex)
285 {}
286 
IsConnected() const287 bool ClientNetworking::Impl::IsConnected() const {
288     boost::mutex::scoped_lock lock(m_mutex);
289     return m_rx_connected && m_tx_connected;
290 }
291 
IsRxConnected() const292 bool ClientNetworking::Impl::IsRxConnected() const {
293     boost::mutex::scoped_lock lock(m_mutex);
294     return m_rx_connected;
295 }
296 
IsTxConnected() const297 bool ClientNetworking::Impl::IsTxConnected() const {
298     boost::mutex::scoped_lock lock(m_mutex);
299     return m_tx_connected;
300 }
301 
PlayerID() const302 int ClientNetworking::Impl::PlayerID() const
303 { return m_player_id; }
304 
HostPlayerID() const305 int ClientNetworking::Impl::HostPlayerID() const
306 { return m_host_player_id; }
307 
PlayerIsHost(int player_id) const308 bool ClientNetworking::Impl::PlayerIsHost(int player_id) const {
309     if (player_id == Networking::INVALID_PLAYER_ID)
310         return false;
311     return player_id == m_host_player_id;
312 }
313 
HasAuthRole(Networking::RoleType role) const314 bool ClientNetworking::Impl::HasAuthRole(Networking::RoleType role) const
315 { return m_roles.HasRole(role); }
316 
DiscoverLANServerNames()317 ClientNetworking::ServerNames ClientNetworking::Impl::DiscoverLANServerNames() {
318     if (!IsConnected())
319         return ServerNames();
320     ServerDiscoverer discoverer(m_io_context);
321     discoverer.DiscoverServers();
322     ServerNames names;
323     for (const auto& server : discoverer.Servers()) {
324         names.push_back(server.second);
325     }
326     return names;
327 }
328 
Destination() const329 const std::string& ClientNetworking::Impl::Destination() const
330 { return m_destination; }
331 
ConnectToServer(const ClientNetworking * const self,const std::string & ip_address,const std::chrono::milliseconds & timeout,bool expect_timeout)332 bool ClientNetworking::Impl::ConnectToServer(
333     const ClientNetworking* const self,
334     const std::string& ip_address,
335     const std::chrono::milliseconds& timeout/* = std::chrono::seconds(10)*/,
336     bool expect_timeout /*=false*/)
337 {
338     using Clock = std::chrono::high_resolution_clock;
339     Clock::time_point start_time = Clock::now();
340     auto deadline = start_time + timeout;
341 
342     using namespace boost::asio::ip;
343     tcp::resolver resolver(m_io_context);
344     tcp::resolver::query query(ip_address,
345                                std::to_string(Networking::MessagePort()),
346                                boost::asio::ip::resolver_query_base::numeric_service);
347 
348     tcp::resolver::iterator end_it;
349 
350     DebugLogger(network) << "Attempt to connect to server at one of these addresses:";
351     for (tcp::resolver::iterator it = resolver.resolve(query); it != end_it; ++it) {
352         DebugLogger(network) << "  tcp::resolver::iterator host_name: " << it->host_name()
353                              << "  address: " << it->endpoint().address()
354                              << "  port: " << it->endpoint().port();
355     }
356 
357     try {
358         while(!IsConnected() && Clock::now() < deadline) {
359             for (tcp::resolver::iterator it = resolver.resolve(query); it != end_it; ++it) {
360                 try {
361                     m_socket.close();
362                 } catch (const std::exception& e) {
363                     ErrorLogger(network) << "ConnectToServer() : unable to close socket due to exception: " << e.what();
364                     m_socket = boost::asio::ip::tcp::socket(m_io_context);
365                 }
366 
367                 m_socket.async_connect(*it, boost::bind(&ClientNetworking::Impl::HandleConnection, this,
368                                                         &it,
369                                                         boost::asio::placeholders::error));
370                 m_io_context.run();
371                 m_io_context.reset();
372 
373                 auto connection_time = Clock::now() - start_time;
374 
375                 if (IsConnected()) {
376                     DebugLogger(network) << "Connected to server at host_name: " << it->host_name()
377                                          << "  address: " << it->endpoint().address()
378                                          << "  port: " << it->endpoint().port();
379 
380                     //DebugLogger(network) << "ConnectToServer() : Client using "
381                     //                     << ((GetOptionsDB().Get<bool>("save.format.binary.enabled")) ? "binary": "xml")
382                     //                     << " serialization.";
383 
384                     // Prepare the socket
385 
386                     // linger option has different meanings on different platforms.  It affects the
387                     // behavior of the socket.close().  It can do the following:
388                     // - close both send and receive immediately,
389                     // - finish sending any pending send packets and wait up to SOCKET_LINGER_TIME for
390                     // ACKs,
391                     // - finish sending pending sent packets and wait up to SOCKET_LINGER_TIME for ACKs
392                     // and for the other side of the connection to close,
393                     // linger may/may not cause close() to block until the linger time has elapsed.
394                     m_socket.set_option(boost::asio::socket_base::linger(true, SOCKET_LINGER_TIME));
395 
396                     // keep alive is an OS dependent option that will keep the TCP connection alive and
397                     // then deliver an OS dependent error when/if the other side of the connection
398                     // times out or closes.
399                     m_socket.set_option(boost::asio::socket_base::keep_alive(true));
400                     DebugLogger(network) << "Connecting to server took "
401                                          << std::chrono::duration_cast<std::chrono::milliseconds>(connection_time).count() << " ms.";
402 
403                     DebugLogger(network) << "ConnectToServer() : starting networking thread";
404                     boost::thread(boost::bind(&ClientNetworking::Impl::NetworkingThread, this, self->shared_from_this()));
405                     break;
406                 } else {
407                     TraceLogger(network) << "Failed to connect to host_name: " << it->host_name()
408                                          << "  address: " << it->endpoint().address()
409                                          << "  port: " << it->endpoint().port();
410                     if (timeout < connection_time && !expect_timeout) {
411                         ErrorLogger(network) << "Timed out ("
412                                              << std::chrono::duration_cast<std::chrono::milliseconds>(connection_time).count() << " ms."
413                                              << ") attempting to connect to server.";
414                     }
415                 }
416             }
417         }
418         if (!IsConnected())
419             DebugLogger(network) << "ConnectToServer() : failed to connect to server.";
420 
421     } catch (const std::exception& e) {
422         ErrorLogger(network) << "ConnectToServer() : unable to connect to server at "
423                              << ip_address << " due to exception: " << e.what();
424     }
425     if (IsConnected())
426         m_destination = ip_address;
427     return IsConnected();
428 }
429 
ConnectToLocalHostServer(const ClientNetworking * const self,const std::chrono::milliseconds & timeout,bool expect_timeout)430 bool ClientNetworking::Impl::ConnectToLocalHostServer(
431     const ClientNetworking* const self,
432     const std::chrono::milliseconds& timeout/* = std::chrono::seconds(10)*/,
433     bool expect_timeout /*=false*/)
434 {
435     bool retval = false;
436 #if FREEORION_WIN32
437     try {
438 #endif
439         retval = ConnectToServer(self, "127.0.0.1", timeout, expect_timeout);
440 #if FREEORION_WIN32
441     } catch (const boost::system::system_error& e) {
442         if (e.code().value() != WSAEADDRNOTAVAIL)
443             throw;
444     }
445 #endif
446     return retval;
447 }
448 
DisconnectFromServer()449 void ClientNetworking::Impl::DisconnectFromServer() {
450     bool is_open(false);
451 
452     { // Create a scope for the mutex
453         boost::mutex::scoped_lock lock(m_mutex);
454         is_open = m_rx_connected || m_tx_connected;
455     }
456 
457     if (is_open)
458         m_io_context.post(boost::bind(&ClientNetworking::Impl::DisconnectFromServerImpl, this));
459 }
460 
SetPlayerID(int player_id)461 void ClientNetworking::Impl::SetPlayerID(int player_id) {
462     DebugLogger(network) << "ClientNetworking::SetPlayerID: player id set to: " << player_id;
463     m_player_id = player_id;
464 }
465 
SetHostPlayerID(int host_player_id)466 void ClientNetworking::Impl::SetHostPlayerID(int host_player_id)
467 { m_host_player_id = host_player_id; }
468 
AuthorizationRoles()469 Networking::AuthRoles& ClientNetworking::Impl::AuthorizationRoles()
470 { return m_roles; }
471 
SendMessage(const Message & message)472 void ClientNetworking::Impl::SendMessage(const Message& message) {
473     if (!IsTxConnected()) {
474         ErrorLogger(network) << "ClientNetworking::SendMessage can't send message when not transmit connected";
475         return;
476     }
477     TraceLogger(network) << "ClientNetworking::SendMessage() : sending message " << message;
478     m_io_context.post(boost::bind(&ClientNetworking::Impl::SendMessageImpl, this, message));
479 }
480 
GetMessage()481 boost::optional<Message> ClientNetworking::Impl::GetMessage() {
482     const auto message = m_incoming_messages.PopFront();
483     if (message)
484         TraceLogger(network) << "ClientNetworking::GetMessage() : received message "
485                              << *message;
486     return message;
487 }
488 
HandleConnection(tcp::resolver::iterator * it,const boost::system::error_code & error)489 void ClientNetworking::Impl::HandleConnection(tcp::resolver::iterator* it,
490                                               const boost::system::error_code& error)
491 {
492     if (error) {
493         TraceLogger(network) << "ClientNetworking::HandleConnection : connection "
494                              << "error #"<<error.value()<<" \"" << error.message() << "\""
495                              << "... retrying";
496     } else {
497         TraceLogger(network) << "ClientNetworking::HandleConnection : connected";
498 
499         boost::mutex::scoped_lock lock(m_mutex);
500         m_rx_connected = true;
501         m_tx_connected = true;
502     }
503 }
504 
HandleException(const boost::system::system_error & error)505 void ClientNetworking::Impl::HandleException(const boost::system::system_error& error) {
506     if (error.code() == boost::asio::error::eof) {
507         DebugLogger(network) << "Client connection disconnected by EOF from server.";
508         m_socket.close();
509     }
510     else if (error.code() == boost::asio::error::connection_reset)
511         DebugLogger(network) << "Client connection disconnected, due to connection reset from server.";
512     else if (error.code() == boost::asio::error::operation_aborted)
513         DebugLogger(network) << "Client connection closed by client.";
514     else {
515         ErrorLogger(network) << "ClientNetworking::NetworkingThread() : Networking thread will be terminated "
516                              << "due to unhandled exception error #" << error.code().value() << " \""
517                              << error.code().message() << "\"";
518     }
519 }
520 
NetworkingThread(const std::shared_ptr<const ClientNetworking> self)521 void ClientNetworking::Impl::NetworkingThread(const std::shared_ptr<const ClientNetworking> self) {
522     auto protect_from_destruction_in_other_thread = self;
523     try {
524         if (!m_outgoing_messages.empty())
525             AsyncWriteMessage();
526         AsyncReadMessage(protect_from_destruction_in_other_thread);
527         m_io_context.run();
528     } catch (const boost::system::system_error& error) {
529         HandleException(error);
530     }
531     m_outgoing_messages.clear();
532     m_io_context.reset();
533     { // Mutex scope
534         boost::mutex::scoped_lock lock(m_mutex);
535         m_rx_connected = false;
536         m_tx_connected = false;
537     }
538     TraceLogger(network) << "ClientNetworking::NetworkingThread() : Networking thread terminated.";
539 }
540 
HandleMessageBodyRead(const std::shared_ptr<const ClientNetworking> & keep_alive,boost::system::error_code error,std::size_t bytes_transferred)541 void ClientNetworking::Impl::HandleMessageBodyRead(const std::shared_ptr<const ClientNetworking>& keep_alive,
542                                                    boost::system::error_code error, std::size_t bytes_transferred)
543 {
544     if (error)
545         throw boost::system::system_error(error);
546 
547     assert(static_cast<int>(bytes_transferred) <= m_incoming_header[Message::Parts::SIZE]);
548     if (static_cast<int>(bytes_transferred) == m_incoming_header[Message::Parts::SIZE]) {
549         m_incoming_messages.PushBack(m_incoming_message);
550         AsyncReadMessage(keep_alive);
551     }
552 }
553 
HandleMessageHeaderRead(const std::shared_ptr<const ClientNetworking> & keep_alive,boost::system::error_code error,std::size_t bytes_transferred)554 void ClientNetworking::Impl::HandleMessageHeaderRead(const std::shared_ptr<const ClientNetworking>& keep_alive,
555                                                      boost::system::error_code error, std::size_t bytes_transferred)
556 {
557     if (error)
558         throw boost::system::system_error(error);
559     assert(bytes_transferred <= Message::HeaderBufferSize);
560     if (bytes_transferred != Message::HeaderBufferSize)
561         return;
562 
563     BufferToHeader(m_incoming_header, m_incoming_message);
564     m_incoming_message.Resize(m_incoming_header[Message::Parts::SIZE]);
565     // Intentionally not checked for open.  We expect (header, body) pairs.
566     boost::asio::async_read(
567         m_socket,
568         boost::asio::buffer(m_incoming_message.Data(), m_incoming_message.Size()),
569         boost::bind(&ClientNetworking::Impl::HandleMessageBodyRead,
570                     this, keep_alive,
571                     boost::asio::placeholders::error,
572                     boost::asio::placeholders::bytes_transferred));
573 }
574 
AsyncReadMessage(const std::shared_ptr<const ClientNetworking> & keep_alive)575 void ClientNetworking::Impl::AsyncReadMessage(const std::shared_ptr<const ClientNetworking>& keep_alive) {
576     // If keep_alive's count < 2 the networking thread is orphaned so shut down
577     if (keep_alive.use_count() < 2)
578         DisconnectFromServerImpl();
579 
580     if (m_socket.is_open())
581         boost::asio::async_read(
582             m_socket, boost::asio::buffer(m_incoming_header),
583             boost::bind(&ClientNetworking::Impl::HandleMessageHeaderRead, this, keep_alive,
584                         boost::asio::placeholders::error,
585                         boost::asio::placeholders::bytes_transferred));
586 }
587 
HandleMessageWrite(boost::system::error_code error,std::size_t bytes_transferred)588 void ClientNetworking::Impl::HandleMessageWrite(boost::system::error_code error, std::size_t bytes_transferred) {
589     if (error) {
590         throw boost::system::system_error(error);
591         return;
592     }
593 
594     assert(static_cast<int>(bytes_transferred) <= static_cast<int>(Message::HeaderBufferSize) + m_outgoing_header[Message::Parts::SIZE]);
595     if (static_cast<int>(bytes_transferred) != static_cast<int>(Message::HeaderBufferSize) + m_outgoing_header[Message::Parts::SIZE])
596         return;
597 
598     m_outgoing_messages.pop_front();
599     if (!m_outgoing_messages.empty())
600         AsyncWriteMessage();
601 
602     // Check if finished sending last pending write while shutting down.
603     else {
604         bool should_shutdown(false);
605         { // Scope for the mutex
606             boost::mutex::scoped_lock lock(m_mutex);
607             should_shutdown = !m_tx_connected;
608         }
609         if (should_shutdown) {
610             DisconnectFromServerImpl();
611         }
612     }
613 }
614 
AsyncWriteMessage()615 void ClientNetworking::Impl::AsyncWriteMessage() {
616     if (!m_socket.is_open()) {
617         ErrorLogger(network) << "Socket is closed. Dropping message.";
618         return;
619     }
620 
621     HeaderToBuffer(m_outgoing_messages.front(), m_outgoing_header);
622     std::vector<boost::asio::const_buffer> buffers;
623     buffers.push_back(boost::asio::buffer(m_outgoing_header));
624     buffers.push_back(boost::asio::buffer(m_outgoing_messages.front().Data(),
625                                           m_outgoing_messages.front().Size()));
626     boost::asio::async_write(m_socket, buffers,
627                              boost::bind(&ClientNetworking::Impl::HandleMessageWrite, this,
628                                          boost::asio::placeholders::error,
629                                          boost::asio::placeholders::bytes_transferred));
630 }
631 
SendMessageImpl(Message message)632 void ClientNetworking::Impl::SendMessageImpl(Message message) {
633     bool start_write = m_outgoing_messages.empty();
634     m_outgoing_messages.push_back(Message());
635     swap(m_outgoing_messages.back(), message);
636     if (start_write)
637         AsyncWriteMessage();
638 }
639 
DisconnectFromServerImpl()640 void ClientNetworking::Impl::DisconnectFromServerImpl() {
641     // Depending behavior of linger on OS's of the sending and receiving machines this call to close could
642     // - immediately disconnect both send and receive channels
643     // - immediately disconnect send, but continue receiving until all pending sent packets are
644     //   received and acknowledged.
645     // - send pending packets and wait for the receive side to terminate the connection.
646 
647     // The shutdown steps:
648     // 1. Finish sending pending tx messages.  These may include final panic messages
649     // 2. After the final tx call DisconnectFromServerImpl again from the tx handler.
650     // 3. shutdown the connection
651     // 4. server end acknowledges with a 0 length packet
652     // 5. close the connection in the rx handler
653 
654     // Stop sending new packets
655     { // Scope for the mutex
656         boost::mutex::scoped_lock lock(m_mutex);
657         m_tx_connected = false;
658         m_rx_connected = m_socket.is_open();
659     }
660 
661     if (!m_outgoing_messages.empty()) {
662         return;
663     }
664 
665     // Note: m_socket.is_open() may be independently true/false on each of these checks.
666     if (m_socket.is_open())
667         m_socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both);
668 }
669 
670 
671 
672 ////////////////////////////////////////////////
673 // ClientNetworking
674 ////////////////////////////////////////////////
ClientNetworking()675 ClientNetworking::ClientNetworking() :
676     m_impl(new ClientNetworking::Impl())
677 {}
678 
679 ClientNetworking::~ClientNetworking() = default;
680 
IsConnected() const681 bool ClientNetworking::IsConnected() const
682 { return m_impl->IsConnected(); }
683 
IsRxConnected() const684 bool ClientNetworking::IsRxConnected() const
685 { return m_impl->IsRxConnected(); }
686 
IsTxConnected() const687 bool ClientNetworking::IsTxConnected() const
688 { return m_impl->IsTxConnected(); }
689 
PlayerID() const690 int ClientNetworking::PlayerID() const
691 { return m_impl->PlayerID(); }
692 
HostPlayerID() const693 int ClientNetworking::HostPlayerID() const
694 { return m_impl->HostPlayerID(); }
695 
PlayerIsHost(int player_id) const696 bool ClientNetworking::PlayerIsHost(int player_id) const
697 { return m_impl->PlayerIsHost(player_id); }
698 
HasAuthRole(Networking::RoleType role) const699 bool ClientNetworking::HasAuthRole(Networking::RoleType role) const
700 { return m_impl->HasAuthRole(role); }
701 
Destination() const702 const std::string& ClientNetworking::Destination() const
703 { return m_impl->Destination(); }
704 
DiscoverLANServerNames()705 ClientNetworking::ServerNames ClientNetworking::DiscoverLANServerNames()
706 { return m_impl->DiscoverLANServerNames(); }
707 
ConnectToServer(const std::string & ip_address,const std::chrono::milliseconds & timeout)708 bool ClientNetworking::ConnectToServer(
709     const std::string& ip_address,
710     const std::chrono::milliseconds& timeout/* = std::chrono::seconds(10)*/)
711 { return m_impl->ConnectToServer(this, ip_address, timeout); }
712 
ConnectToLocalHostServer(const std::chrono::milliseconds & timeout)713 bool ClientNetworking::ConnectToLocalHostServer(
714     const std::chrono::milliseconds& timeout/* = std::chrono::seconds(10)*/)
715 { return m_impl->ConnectToLocalHostServer(this, timeout); }
716 
PingServer(const std::string & ip_address,const std::chrono::milliseconds & timeout)717 bool ClientNetworking::PingServer(
718     const std::string& ip_address,
719     const std::chrono::milliseconds& timeout/* = std::chrono::seconds(10)*/)
720 { return m_impl->ConnectToServer(this, ip_address, timeout, true /*expect_timeout*/); }
721 
PingLocalHostServer(const std::chrono::milliseconds & timeout)722 bool ClientNetworking::PingLocalHostServer(
723     const std::chrono::milliseconds& timeout/* = std::chrono::seconds(10)*/)
724 { return m_impl->ConnectToLocalHostServer(this, timeout, true /*expect_timeout*/); }
725 
DisconnectFromServer()726 void ClientNetworking::DisconnectFromServer()
727 { return m_impl->DisconnectFromServer(); }
728 
SetPlayerID(int player_id)729 void ClientNetworking::SetPlayerID(int player_id)
730 { return m_impl->SetPlayerID(player_id); }
731 
SetHostPlayerID(int host_player_id)732 void ClientNetworking::SetHostPlayerID(int host_player_id)
733 { return m_impl->SetHostPlayerID(host_player_id); }
734 
AuthorizationRoles()735 Networking::AuthRoles& ClientNetworking::AuthorizationRoles()
736 { return m_impl->AuthorizationRoles(); }
737 
SendMessage(const Message & message)738 void ClientNetworking::SendMessage(const Message& message)
739 { return m_impl->SendMessage(message); }
740 
GetMessage()741 boost::optional<Message> ClientNetworking::GetMessage()
742 { return m_impl->GetMessage(); }
743