1 #ifndef _ServerNetworking_h_
2 #define _ServerNetworking_h_
3 
4 #include "../network/Message.h"
5 
6 #include <boost/asio.hpp>
7 #include <boost/iterator/filter_iterator.hpp>
8 #include <boost/signals2/signal.hpp>
9 #include <boost/functional/hash.hpp>
10 
11 #include <functional>
12 #include <memory>
13 #include <queue>
14 #include <set>
15 #include <unordered_map>
16 
17 class DiscoveryServer;
18 class PlayerConnection;
19 
20 typedef std::shared_ptr<PlayerConnection> PlayerConnectionPtr;
21 typedef std::function<void (Message, PlayerConnectionPtr)> MessageAndConnectionFn;
22 typedef std::function<void (PlayerConnectionPtr)> ConnectionFn;
23 typedef std::function<void ()> NullaryFn;
24 
25 /** Data associated with cookie */
26 struct CookieData {
27     std::string                 player_name;
28     boost::posix_time::ptime    expired;
29     Networking::AuthRoles       roles;
30     bool                        authenticated;
31 
CookieDataCookieData32     CookieData(const std::string& player_name_,
33                const boost::posix_time::ptime& expired_,
34                const Networking::AuthRoles& roles_,
35                bool authenticated_) :
36         player_name(player_name_),
37         expired(expired_),
38         roles(roles_),
39         authenticated(authenticated_)
40     {}
41 };
42 
43 
44 /** In Boost 1.66, io_service was replaced with a typedef of io_context.
45   * That typedef was removed in Boost 1.70 along with other interface changes.
46   * This code uses io_context for future compatibility and adds the typedef
47   * here for old versions of Boost. */
48 #if BOOST_VERSION < 106600
49 namespace boost { namespace asio {
50     typedef io_service io_context;
51 }}
52 #endif
53 
54 
55 /** Encapsulates the networking facilities of the server.  This class listens
56     for incoming UDP LAN server-discovery requests and TCP player connections.
57     The server also sends and receives messages over the TCP player
58     connections. */
59 class ServerNetworking {
60 private:
61     typedef std::set<PlayerConnectionPtr> PlayerConnections;
62     struct EstablishedPlayer
63     { bool operator()(const PlayerConnectionPtr& player_connection) const; };
64 
65 public:
66     typedef std::set<PlayerConnectionPtr>::iterator                                         iterator;
67     typedef std::set<PlayerConnectionPtr>::const_iterator                                   const_iterator;
68     typedef boost::filter_iterator<EstablishedPlayer, PlayerConnections::iterator>          established_iterator;
69     typedef boost::filter_iterator<EstablishedPlayer, PlayerConnections::const_iterator>    const_established_iterator;
70 
71     /** \name Structors */ //@{
72     ServerNetworking(boost::asio::io_context& io_context,
73                      MessageAndConnectionFn nonplayer_message_callback,
74                      MessageAndConnectionFn player_message_callback,
75                      ConnectionFn disconnected_callback);
76 
77     ~ServerNetworking();
78     //@}
79 
80     /** \name Accessors */ //@{
81     /** Returns true if size() == 0. */
82     bool empty() const;
83 
84     /** Returns the \a total number of PlayerConnections (not just established
85         ones). */
86     std::size_t size() const;
87 
88     /** Returns an iterator to the first PlayerConnection object. */
89     const_iterator begin() const;
90 
91     /** Returns an iterator to the one-past-the-last PlayerConnection object. */
92     const_iterator end() const;
93 
94     /** Returns the number of established-player PlayerConnections. */
95     std::size_t NumEstablishedPlayers() const;
96 
97     /** Returns an iterator to the established PlayerConnection object with ID
98         \a id, or established_end() if none is found. */
99     const_established_iterator GetPlayer(int id) const;
100 
101     /** Returns an iterator to the first \a established PlayerConnection object. */
102     const_established_iterator established_begin() const;
103 
104     /** Returns an iterator to the one-past-the-last \a established
105         PlayerConnection object. */
106     const_established_iterator established_end() const;
107 
108     /** Returns the ID number for new player, which will be larger than the ID of all the established players. */
109     int NewPlayerID() const;
110 
111     /** Returns the ID of the host player, or INVALID_PLAYER_ID if there is no host player. */
112     int HostPlayerID() const;
113 
114     /** Returns whether the indicated player ID is the host. */
115     bool PlayerIsHost(int player_id) const;
116 
117     /** Returns whether there are any moderators in the game. */
118     bool ModeratorsInGame() const;
119 
120     /** Returns whether there no non-expired cookie with this player name. */
121     bool IsAvailableNameInCookies(const std::string& player_name) const;
122 
123     /** Returns whether player have non-expired cookie with this player name.
124       * Fills roles and authentication status on success. */
125     bool CheckCookie(boost::uuids::uuid cookie,
126                      const std::string& player_name,
127                      Networking::AuthRoles& roles,
128                      bool& authenticated) const;
129 
130     /** Returns count of stored cookies so we don't collide with reserved player names. */
131     int GetCookiesSize() const;
132     //@}
133 
134     /** \name Mutators */ //@{
135     /** Sends a synchronous message \a message to the all established players. */
136     void SendMessageAll(const Message& message);
137 
138     /** Disconnects the server from player \a id. */
139     void Disconnect(int id);
140 
141     /** Disconnects the server from the client represented by \a player_connection. */
142     void Disconnect(PlayerConnectionPtr player_connection);
143 
144     /** Disconnects the server from all clients. */
145     void DisconnectAll();
146 
147     /** Returns an iterator to the first PlayerConnection object. */
148     iterator begin();
149 
150     /** Returns an iterator to the one-past-the-last PlayerConnection object. */
151     iterator end();
152 
153     /** Returns an iterator to the established PlayerConnection object with ID
154         \a id, or end() if none is found. */
155     established_iterator GetPlayer(int id);
156 
157     /** Returns an iterator to the first established PlayerConnection
158         object. */
159     established_iterator established_begin();
160 
161     /** Returns an iterator to the one-past-the-last established
162         PlayerConnection object. */
163     established_iterator established_end();
164 
165     /** Dequeues and executes the next event in the queue.  Results in a noop
166         if the queue is empty. */
167     void HandleNextEvent();
168 
169     /** Sets Host player ID. */
170     void SetHostPlayerID(int host_player_id);
171 
172     /** Generate cookies for player's name, roles, and authentication status. */
173     boost::uuids::uuid GenerateCookie(const std::string& player_name,
174                                       const Networking::AuthRoles& roles,
175                                       bool authenticated);
176 
177     /** Bump cookie's expired date. */
178     void UpdateCookie(boost::uuids::uuid cookie);
179 
180     /** Clean up expired cookies. */
181     void CleanupCookies();
182     //@}
183 
184 private:
185     void Init();
186     void AcceptNextMessagingConnection();
187     void AcceptPlayerMessagingConnection(PlayerConnectionPtr player_connection,
188                                          const boost::system::error_code& error);
189     void DisconnectImpl(PlayerConnectionPtr player_connection);
190     void EnqueueEvent(const NullaryFn& fn);
191 
192     int                             m_host_player_id;
193 
194     DiscoveryServer*                m_discovery_server;
195 #if BOOST_VERSION >= 107000
196     boost::asio::basic_socket_acceptor<boost::asio::ip::tcp, boost::asio::io_context::executor_type>
197                                     m_player_connection_acceptor;
198 #else
199     boost::asio::ip::tcp::acceptor  m_player_connection_acceptor;
200 #endif
201     PlayerConnections               m_player_connections;
202     std::queue<NullaryFn>           m_event_queue;
203     std::unordered_map<boost::uuids::uuid, CookieData, boost::hash<boost::uuids::uuid>> m_cookies;
204 
205     MessageAndConnectionFn          m_nonplayer_message_callback;
206     MessageAndConnectionFn          m_player_message_callback;
207     ConnectionFn                    m_disconnected_callback;
208 };
209 
210 /** Encapsulates the connection to a single player.  This object should have
211     nearly the same lifetime as the socket it represents, except that the
212     object is constructed just before the socket connection is made.  A
213     newly-constructed PlayerConnection has no associated player ID, player
214     name, nor host-player status.  Once a PlayerConnection is accepted by the
215     server as an actual player in a game, EstablishPlayer() should be called.
216     This establishes the aforementioned properties. */
217 class PlayerConnection :
218     public std::enable_shared_from_this<PlayerConnection>
219 {
220 public:
221     /** \name Structors */ //@{
222     ~PlayerConnection(); ///< Dtor.
223     //@}
224 
225     /** \name Accessors */ //@{
226     /** Returns true if EstablishPlayer() successfully has been called on this
227         connection. */
228     bool EstablishedPlayer() const;
229 
230     /** Returns the ID of the player associated with this connection, if
231         any. */
232     int PlayerID() const;
233 
234     /** Returns the name of the player associated with this connection, if
235         any. */
236     const std::string& PlayerName() const;
237 
238     /** Returns the type of client associated with this connection (AI client,
239       * human client, ...) */
240     Networking::ClientType GetClientType() const;
241 
242     /** Returns the version string the client provided when joining. */
243     const std::string& ClientVersionString() const;
244 
245     /** Checks if the server will enable binary serialization for this client's connection. */
246     bool IsBinarySerializationUsed() const;
247 
248     /** Checks if client associated with this connection runs on the same
249         physical machine as the server */
250     bool IsLocalConnection() const;
251 
252     /** Checks if the player is established, has a valid name, id and client type. */
253     bool IsEstablished() const;
254 
255     /** Checks if the player was authenticated. */
256     bool IsAuthenticated() const;
257 
258     /** Checks if the player has a some role */
259     bool HasAuthRole(Networking::RoleType role) const;
260 
261     /** Get cookie associated with this connection. */
262     boost::uuids::uuid Cookie() const;
263     //@}
264 
265     /** \name Mutators */ //@{
266     /** Starts the connection reading incoming messages on its socket. */
267     void Start();
268 
269     /** Sends \a synchronous message to out on the connection. */
270     void SendMessage(const Message& message);
271 
272     /** Set player properties to use them after authentication successed. */
273     void AwaitPlayer(Networking::ClientType client_type,
274                      const std::string& client_version_string);
275 
276     /** Establishes a connection as a player with a specific name and id.
277         This function must only be called once. */
278     void EstablishPlayer(int id, const std::string& player_name, Networking::ClientType client_type,
279                          const std::string& client_version_string);
280 
281     /** Sets this connection's client type. Useful for already-connected players
282       * changing type such as in the multiplayer lobby. */
283     void SetClientType(Networking::ClientType client_type);
284 
285     /** Sets authenticated status for connection. */
286     void SetAuthenticated();
287 
288     /** Sets authorization roles and send message to client. */
289     void SetAuthRoles(const std::initializer_list<Networking::RoleType>& roles);
290 
291     void SetAuthRoles(const Networking::AuthRoles& roles);
292 
293     /** Sets or unset authorizaion role and send message to client. */
294     void SetAuthRole(Networking::RoleType role, bool value = true);
295 
296     /** Sets cookie value to this connection to update expire date. */
297     void SetCookie(boost::uuids::uuid cookie);
298     //@}
299 
300     mutable boost::signals2::signal<void (const NullaryFn&)> EventSignal;
301 
302     /** Creates a new PlayerConnection and returns it as a shared_ptr. */
303     static PlayerConnectionPtr
304     NewConnection(boost::asio::io_context& io_context, MessageAndConnectionFn nonplayer_message_callback,
305                   MessageAndConnectionFn player_message_callback, ConnectionFn disconnected_callback);
306 
307 private:
308 
309     PlayerConnection(boost::asio::io_context& io_context, MessageAndConnectionFn nonplayer_message_callback,
310                      MessageAndConnectionFn player_message_callback, ConnectionFn disconnected_callback);
311     void HandleMessageBodyRead(boost::system::error_code error, std::size_t bytes_transferred);
312     void HandleMessageHeaderRead(boost::system::error_code error, std::size_t bytes_transferred);
313     void AsyncReadMessage();
314     void AsyncWriteMessage();
315     static void HandleMessageWrite(PlayerConnectionPtr self,
316                                    boost::system::error_code error,
317                                    std::size_t bytes_transferred);
318 
319     /** Places message to the end of sending queue and start asynchronous write if \a message was
320         first in the queue. */
321     static void SendMessageImpl(PlayerConnectionPtr self, Message message);
322     static void AsyncErrorHandler(PlayerConnectionPtr self, boost::system::error_code handled_error, boost::system::error_code error);
323 
324     boost::asio::io_context&        m_service;
325     boost::asio::ip::tcp::socket    m_socket;
326     Message::HeaderBuffer           m_incoming_header_buffer;
327     Message                         m_incoming_message;
328     Message::HeaderBuffer           m_outgoing_header;
329     std::list<Message>              m_outgoing_messages;
330     int                             m_ID = Networking::INVALID_PLAYER_ID;
331     std::string                     m_player_name;
332     bool                            m_new_connection = true;
333     Networking::ClientType          m_client_type = Networking::INVALID_CLIENT_TYPE;
334     std::string                     m_client_version_string;
335     bool                            m_authenticated = false;
336     Networking::AuthRoles           m_roles;
337     boost::uuids::uuid              m_cookie;
338     bool                            m_valid = true;
339 
340     MessageAndConnectionFn          m_nonplayer_message_callback;
341     MessageAndConnectionFn          m_player_message_callback;
342     ConnectionFn                    m_disconnected_callback;
343 
344     friend class ServerNetworking;
345 };
346 
347 #endif // _ServerNetworking_h_
348