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