room_member.cpp
1 // Copyright 2017 Citra Emulator Project 2 // Licensed under GPLv2 or any later version 3 // Refer to the license.txt file included. 4 5 #include <algorithm> 6 #include <atomic> 7 #include <list> 8 #include <mutex> 9 #include <set> 10 #include <thread> 11 #include "common/assert.h" 12 #include "enet/enet.h" 13 #include "network/packet.h" 14 #include "network/room_member.h" 15 16 namespace Network { 17 18 constexpr u32 ConnectionTimeoutMs = 5000; 19 20 class RoomMember::RoomMemberImpl { 21 public: 22 ENetHost* client = nullptr; ///< ENet network interface. 23 ENetPeer* server = nullptr; ///< The server peer the client is connected to 24 25 /// Information about the clients connected to the same room as us. 26 MemberList member_information; 27 /// Information about the room we're connected to. 28 RoomInformation room_information; 29 30 /// The current game name, id and version 31 GameInfo current_game_info; 32 33 std::atomic<State> state{State::Idle}; ///< Current state of the RoomMember. 34 void SetState(const State new_state); 35 void SetError(const Error new_error); 36 bool IsConnected() const; 37 38 std::string nickname; ///< The nickname of this member. 39 40 std::string username; ///< The username of this member. 41 mutable std::mutex username_mutex; ///< Mutex for locking username. 42 43 MacAddress mac_address; ///< The mac_address of this member. 44 45 std::mutex network_mutex; ///< Mutex that controls access to the `client` variable. 46 /// Thread that receives and dispatches network packets 47 std::unique_ptr<std::thread> loop_thread; 48 std::mutex send_list_mutex; ///< Mutex that controls access to the `send_list` variable. 49 std::list<Packet> send_list; ///< A list that stores all packets to send the async 50 51 template <typename T> 52 using CallbackSet = std::set<CallbackHandle<T>>; 53 std::mutex callback_mutex; ///< The mutex used for handling callbacks 54 55 class Callbacks { 56 public: 57 template <typename T> 58 CallbackSet<T>& Get(); 59 60 private: 61 CallbackSet<WifiPacket> callback_set_wifi_packet; 62 CallbackSet<ChatEntry> callback_set_chat_messages; 63 CallbackSet<StatusMessageEntry> callback_set_status_messages; 64 CallbackSet<RoomInformation> callback_set_room_information; 65 CallbackSet<State> callback_set_state; 66 CallbackSet<Error> callback_set_error; 67 CallbackSet<Room::BanList> callback_set_ban_list; 68 }; 69 Callbacks callbacks; ///< All CallbackSets to all events 70 71 void MemberLoop(); 72 73 void StartLoop(); 74 75 /** 76 * Sends data to the room. It will be send on channel 0 with flag RELIABLE 77 * @param packet The data to send 78 */ 79 void Send(Packet&& packet); 80 81 /** 82 * Sends a request to the server, asking for permission to join a room with the specified 83 * nickname and preferred mac. 84 * @params nickname The desired nickname. 85 * @params console_id_hash A hash of the Console ID. 86 * @params preferred_mac The preferred MAC address to use in the room, the NoPreferredMac tells 87 * @params password The password for the room 88 * the server to assign one for us. 89 */ 90 void SendJoinRequest(const std::string& nickname, const std::string& console_id_hash, 91 const MacAddress& preferred_mac = NoPreferredMac, 92 const std::string& password = "", const std::string& token = ""); 93 94 /** 95 * Extracts a MAC Address from a received ENet packet. 96 * @param event The ENet event that was received. 97 */ 98 void HandleJoinPacket(const ENetEvent* event); 99 /** 100 * Extracts RoomInformation and MemberInformation from a received ENet packet. 101 * @param event The ENet event that was received. 102 */ 103 void HandleRoomInformationPacket(const ENetEvent* event); 104 105 /** 106 * Extracts a WifiPacket from a received ENet packet. 107 * @param event The ENet event that was received. 108 */ 109 void HandleWifiPackets(const ENetEvent* event); 110 111 /** 112 * Extracts a chat entry from a received ENet packet and adds it to the chat queue. 113 * @param event The ENet event that was received. 114 */ 115 void HandleChatPacket(const ENetEvent* event); 116 117 /** 118 * Extracts a system message entry from a received ENet packet and adds it to the system message 119 * queue. 120 * @param event The ENet event that was received. 121 */ 122 void HandleStatusMessagePacket(const ENetEvent* event); 123 124 /** 125 * Extracts a ban list request response from a received ENet packet. 126 * @param event The ENet event that was received. 127 */ 128 void HandleModBanListResponsePacket(const ENetEvent* event); 129 130 /** 131 * Disconnects the RoomMember from the Room 132 */ 133 void Disconnect(); 134 135 template <typename T> 136 void Invoke(const T& data); 137 138 template <typename T> 139 CallbackHandle<T> Bind(std::function<void(const T&)> callback); 140 }; 141 142 // RoomMemberImpl 143 void RoomMember::RoomMemberImpl::SetState(const State new_state) { 144 if (state != new_state) { 145 state = new_state; 146 Invoke<State>(state); 147 } 148 } 149 150 void RoomMember::RoomMemberImpl::SetError(const Error new_error) { 151 Invoke<Error>(new_error); 152 } 153 154 bool RoomMember::RoomMemberImpl::IsConnected() const { 155 return state == State::Joining || state == State::Joined || state == State::Moderator; 156 } 157 158 void RoomMember::RoomMemberImpl::MemberLoop() { 159 // Receive packets while the connection is open 160 while (IsConnected()) { 161 std::lock_guard network_lock(network_mutex); 162 ENetEvent event; 163 if (enet_host_service(client, &event, 16) > 0) { 164 switch (event.type) { 165 case ENET_EVENT_TYPE_RECEIVE: 166 switch (event.packet->data[0]) { 167 case IdWifiPacket: 168 HandleWifiPackets(&event); 169 break; 170 case IdChatMessage: 171 HandleChatPacket(&event); 172 break; 173 case IdStatusMessage: 174 HandleStatusMessagePacket(&event); 175 break; 176 case IdRoomInformation: 177 HandleRoomInformationPacket(&event); 178 break; 179 case IdJoinSuccess: 180 case IdJoinSuccessAsMod: 181 // The join request was successful, we are now in the room. 182 // If we joined successfully, there must be at least one client in the room: us. 183 ASSERT_MSG(member_information.size() > 0, 184 "We have not yet received member information."); 185 HandleJoinPacket(&event); // Get the MAC Address for the client 186 if (event.packet->data[0] == IdJoinSuccessAsMod) { 187 SetState(State::Moderator); 188 } else { 189 SetState(State::Joined); 190 } 191 break; 192 case IdModBanListResponse: 193 HandleModBanListResponsePacket(&event); 194 break; 195 case IdRoomIsFull: 196 SetState(State::Idle); 197 SetError(Error::RoomIsFull); 198 break; 199 case IdNameCollision: 200 SetState(State::Idle); 201 SetError(Error::NameCollision); 202 break; 203 case IdMacCollision: 204 SetState(State::Idle); 205 SetError(Error::MacCollision); 206 break; 207 case IdConsoleIdCollision: 208 SetState(State::Idle); 209 SetError(Error::ConsoleIdCollision); 210 break; 211 case IdVersionMismatch: 212 SetState(State::Idle); 213 SetError(Error::WrongVersion); 214 break; 215 case IdWrongPassword: 216 SetState(State::Idle); 217 SetError(Error::WrongPassword); 218 break; 219 case IdCloseRoom: 220 SetState(State::Idle); 221 SetError(Error::LostConnection); 222 break; 223 case IdHostKicked: 224 SetState(State::Idle); 225 SetError(Error::HostKicked); 226 break; 227 case IdHostBanned: 228 SetState(State::Idle); 229 SetError(Error::HostBanned); 230 break; 231 case IdModPermissionDenied: 232 SetError(Error::PermissionDenied); 233 break; 234 case IdModNoSuchUser: 235 SetError(Error::NoSuchUser); 236 break; 237 } 238 enet_packet_destroy(event.packet); 239 break; 240 case ENET_EVENT_TYPE_DISCONNECT: 241 if (state == State::Joined || state == State::Moderator) { 242 SetState(State::Idle); 243 SetError(Error::LostConnection); 244 } 245 break; 246 case ENET_EVENT_TYPE_NONE: 247 break; 248 case ENET_EVENT_TYPE_CONNECT: 249 // The ENET_EVENT_TYPE_CONNECT event can not possibly happen here because we're 250 // already connected 251 ASSERT_MSG(false, "Received unexpected connect event while already connected"); 252 break; 253 } 254 } 255 256 std::list<Packet> packets; 257 { 258 std::lock_guard send_list_lock(send_list_mutex); 259 packets.swap(send_list); 260 } 261 for (const auto& packet : packets) { 262 ENetPacket* enetPacket = enet_packet_create(packet.GetData(), packet.GetDataSize(), 263 ENET_PACKET_FLAG_RELIABLE); 264 enet_peer_send(server, 0, enetPacket); 265 } 266 enet_host_flush(client); 267 } 268 Disconnect(); 269 }; 270 271 void RoomMember::RoomMemberImpl::StartLoop() { 272 loop_thread = std::make_unique<std::thread>(&RoomMember::RoomMemberImpl::MemberLoop, this); 273 } 274 275 void RoomMember::RoomMemberImpl::Send(Packet&& packet) { 276 std::lock_guard lock(send_list_mutex); 277 send_list.push_back(std::move(packet)); 278 } 279 280 void RoomMember::RoomMemberImpl::SendJoinRequest(const std::string& nickname, 281 const std::string& console_id_hash, 282 const MacAddress& preferred_mac, 283 const std::string& password, 284 const std::string& token) { 285 Packet packet; 286 packet << static_cast<u8>(IdJoinRequest); 287 packet << nickname; 288 packet << console_id_hash; 289 packet << preferred_mac; 290 packet << network_version; 291 packet << password; 292 packet << token; 293 Send(std::move(packet)); 294 } 295 296 void RoomMember::RoomMemberImpl::HandleRoomInformationPacket(const ENetEvent* event) { 297 Packet packet; 298 packet.Append(event->packet->data, event->packet->dataLength); 299 300 // Ignore the first byte, which is the message id. 301 packet.IgnoreBytes(sizeof(u8)); // Ignore the message type 302 303 RoomInformation info{}; 304 packet >> info.name; 305 packet >> info.description; 306 packet >> info.member_slots; 307 packet >> info.port; 308 packet >> info.preferred_game; 309 packet >> info.host_username; 310 room_information.name = info.name; 311 room_information.description = info.description; 312 room_information.member_slots = info.member_slots; 313 room_information.port = info.port; 314 room_information.preferred_game = info.preferred_game; 315 room_information.host_username = info.host_username; 316 317 u32 num_members; 318 packet >> num_members; 319 member_information.resize(num_members); 320 321 for (auto& member : member_information) { 322 packet >> member.nickname; 323 packet >> member.mac_address; 324 packet >> member.game_info.name; 325 packet >> member.game_info.id; 326 packet >> member.username; 327 packet >> member.display_name; 328 packet >> member.avatar_url; 329 330 { 331 std::lock_guard lock(username_mutex); 332 if (member.nickname == nickname) { 333 username = member.username; 334 } 335 } 336 } 337 Invoke(room_information); 338 } 339 340 void RoomMember::RoomMemberImpl::HandleJoinPacket(const ENetEvent* event) { 341 Packet packet; 342 packet.Append(event->packet->data, event->packet->dataLength); 343 344 // Ignore the first byte, which is the message id. 345 packet.IgnoreBytes(sizeof(u8)); // Ignore the message type 346 347 // Parse the MAC Address from the packet 348 packet >> mac_address; 349 } 350 351 void RoomMember::RoomMemberImpl::HandleWifiPackets(const ENetEvent* event) { 352 WifiPacket wifi_packet{}; 353 Packet packet; 354 packet.Append(event->packet->data, event->packet->dataLength); 355 356 // Ignore the first byte, which is the message id. 357 packet.IgnoreBytes(sizeof(u8)); // Ignore the message type 358 359 // Parse the WifiPacket from the packet 360 u8 frame_type; 361 packet >> frame_type; 362 WifiPacket::PacketType type = static_cast<WifiPacket::PacketType>(frame_type); 363 364 wifi_packet.type = type; 365 packet >> wifi_packet.channel; 366 packet >> wifi_packet.transmitter_address; 367 packet >> wifi_packet.destination_address; 368 packet >> wifi_packet.data; 369 370 Invoke<WifiPacket>(wifi_packet); 371 } 372 373 void RoomMember::RoomMemberImpl::HandleChatPacket(const ENetEvent* event) { 374 Packet packet; 375 packet.Append(event->packet->data, event->packet->dataLength); 376 377 // Ignore the first byte, which is the message id. 378 packet.IgnoreBytes(sizeof(u8)); 379 380 ChatEntry chat_entry{}; 381 packet >> chat_entry.nickname; 382 packet >> chat_entry.username; 383 packet >> chat_entry.message; 384 chat_entry.message.resize(std::min(chat_entry.message.find('\0'), chat_entry.message.size())); 385 Invoke<ChatEntry>(chat_entry); 386 } 387 388 void RoomMember::RoomMemberImpl::HandleStatusMessagePacket(const ENetEvent* event) { 389 Packet packet; 390 packet.Append(event->packet->data, event->packet->dataLength); 391 392 // Ignore the first byte, which is the message id. 393 packet.IgnoreBytes(sizeof(u8)); 394 395 StatusMessageEntry status_message_entry{}; 396 u8 type{}; 397 packet >> type; 398 status_message_entry.type = static_cast<StatusMessageTypes>(type); 399 packet >> status_message_entry.nickname; 400 packet >> status_message_entry.username; 401 Invoke<StatusMessageEntry>(status_message_entry); 402 } 403 404 void RoomMember::RoomMemberImpl::HandleModBanListResponsePacket(const ENetEvent* event) { 405 Packet packet; 406 packet.Append(event->packet->data, event->packet->dataLength); 407 408 // Ignore the first byte, which is the message id. 409 packet.IgnoreBytes(sizeof(u8)); 410 411 Room::BanList ban_list = {}; 412 packet >> ban_list.first; 413 packet >> ban_list.second; 414 Invoke<Room::BanList>(ban_list); 415 } 416 417 void RoomMember::RoomMemberImpl::Disconnect() { 418 member_information.clear(); 419 room_information.member_slots = 0; 420 room_information.name.clear(); 421 422 if (!server) 423 return; 424 enet_peer_disconnect(server, 0); 425 426 ENetEvent event; 427 while (enet_host_service(client, &event, ConnectionTimeoutMs) > 0) { 428 switch (event.type) { 429 case ENET_EVENT_TYPE_RECEIVE: 430 enet_packet_destroy(event.packet); // Ignore all incoming data 431 break; 432 case ENET_EVENT_TYPE_DISCONNECT: 433 server = nullptr; 434 return; 435 case ENET_EVENT_TYPE_NONE: 436 case ENET_EVENT_TYPE_CONNECT: 437 break; 438 } 439 } 440 // didn't disconnect gracefully force disconnect 441 enet_peer_reset(server); 442 server = nullptr; 443 } 444 445 template <> 446 RoomMember::RoomMemberImpl::CallbackSet<WifiPacket>& RoomMember::RoomMemberImpl::Callbacks::Get() { 447 return callback_set_wifi_packet; 448 } 449 450 template <> 451 RoomMember::RoomMemberImpl::CallbackSet<RoomMember::State>& 452 RoomMember::RoomMemberImpl::Callbacks::Get() { 453 return callback_set_state; 454 } 455 456 template <> 457 RoomMember::RoomMemberImpl::CallbackSet<RoomMember::Error>& 458 RoomMember::RoomMemberImpl::Callbacks::Get() { 459 return callback_set_error; 460 } 461 462 template <> 463 RoomMember::RoomMemberImpl::CallbackSet<RoomInformation>& 464 RoomMember::RoomMemberImpl::Callbacks::Get() { 465 return callback_set_room_information; 466 } 467 468 template <> 469 RoomMember::RoomMemberImpl::CallbackSet<ChatEntry>& RoomMember::RoomMemberImpl::Callbacks::Get() { 470 return callback_set_chat_messages; 471 } 472 473 template <> 474 RoomMember::RoomMemberImpl::CallbackSet<StatusMessageEntry>& 475 RoomMember::RoomMemberImpl::Callbacks::Get() { 476 return callback_set_status_messages; 477 } 478 479 template <> 480 RoomMember::RoomMemberImpl::CallbackSet<Room::BanList>& 481 RoomMember::RoomMemberImpl::Callbacks::Get() { 482 return callback_set_ban_list; 483 } 484 485 template <typename T> 486 void RoomMember::RoomMemberImpl::Invoke(const T& data) { 487 std::lock_guard lock(callback_mutex); 488 CallbackSet<T> callback_set = callbacks.Get<T>(); 489 for (auto const& callback : callback_set) 490 (*callback)(data); 491 } 492 493 template <typename T> 494 RoomMember::CallbackHandle<T> RoomMember::RoomMemberImpl::Bind( 495 std::function<void(const T&)> callback) { 496 std::lock_guard lock(callback_mutex); 497 CallbackHandle<T> handle; 498 handle = std::make_shared<std::function<void(const T&)>>(callback); 499 callbacks.Get<T>().insert(handle); 500 return handle; 501 } 502 503 // RoomMember 504 RoomMember::RoomMember() : room_member_impl{std::make_unique<RoomMemberImpl>()} {} 505 506 RoomMember::~RoomMember() { 507 ASSERT_MSG(!IsConnected(), "RoomMember is being destroyed while connected"); 508 if (room_member_impl->loop_thread) { 509 Leave(); 510 } 511 } 512 513 RoomMember::State RoomMember::GetState() const { 514 return room_member_impl->state; 515 } 516 517 const RoomMember::MemberList& RoomMember::GetMemberInformation() const { 518 return room_member_impl->member_information; 519 } 520 521 const std::string& RoomMember::GetNickname() const { 522 return room_member_impl->nickname; 523 } 524 525 const std::string& RoomMember::GetUsername() const { 526 std::lock_guard lock(room_member_impl->username_mutex); 527 return room_member_impl->username; 528 } 529 530 const MacAddress& RoomMember::GetMacAddress() const { 531 ASSERT_MSG(IsConnected(), "Tried to get MAC address while not connected"); 532 return room_member_impl->mac_address; 533 } 534 535 RoomInformation RoomMember::GetRoomInformation() const { 536 return room_member_impl->room_information; 537 } 538 539 void RoomMember::Join(const std::string& nick, const std::string& console_id_hash, 540 const char* server_addr, u16 server_port, u16 client_port, 541 const MacAddress& preferred_mac, const std::string& password, 542 const std::string& token) { 543 // If the member is connected, kill the connection first 544 if (room_member_impl->loop_thread && room_member_impl->loop_thread->joinable()) { 545 Leave(); 546 } 547 // If the thread isn't running but the ptr still exists, reset it 548 else if (room_member_impl->loop_thread) { 549 room_member_impl->loop_thread.reset(); 550 } 551 552 if (!room_member_impl->client) { 553 room_member_impl->client = enet_host_create(nullptr, 1, NumChannels, 0, 0); 554 ASSERT_MSG(room_member_impl->client != nullptr, "Could not create client"); 555 } 556 557 room_member_impl->SetState(State::Joining); 558 559 ENetAddress address{}; 560 enet_address_set_host(&address, server_addr); 561 address.port = server_port; 562 room_member_impl->server = 563 enet_host_connect(room_member_impl->client, &address, NumChannels, 0); 564 565 if (!room_member_impl->server) { 566 room_member_impl->SetState(State::Idle); 567 room_member_impl->SetError(Error::UnknownError); 568 return; 569 } 570 571 ENetEvent event{}; 572 int net = enet_host_service(room_member_impl->client, &event, ConnectionTimeoutMs); 573 if (net > 0 && event.type == ENET_EVENT_TYPE_CONNECT) { 574 room_member_impl->nickname = nick; 575 room_member_impl->StartLoop(); 576 room_member_impl->SendJoinRequest(nick, console_id_hash, preferred_mac, password, token); 577 SendGameInfo(room_member_impl->current_game_info); 578 } else { 579 enet_peer_disconnect(room_member_impl->server, 0); 580 room_member_impl->SetState(State::Idle); 581 room_member_impl->SetError(Error::CouldNotConnect); 582 } 583 } 584 585 bool RoomMember::IsConnected() const { 586 return room_member_impl->IsConnected(); 587 } 588 589 void RoomMember::SendWifiPacket(const WifiPacket& wifi_packet) { 590 Packet packet; 591 packet << static_cast<u8>(IdWifiPacket); 592 packet << static_cast<u8>(wifi_packet.type); 593 packet << wifi_packet.channel; 594 packet << wifi_packet.transmitter_address; 595 packet << wifi_packet.destination_address; 596 packet << wifi_packet.data; 597 room_member_impl->Send(std::move(packet)); 598 } 599 600 void RoomMember::SendChatMessage(const std::string& message) { 601 Packet packet; 602 packet << static_cast<u8>(IdChatMessage); 603 packet << message; 604 room_member_impl->Send(std::move(packet)); 605 } 606 607 void RoomMember::SendGameInfo(const GameInfo& game_info) { 608 room_member_impl->current_game_info = game_info; 609 if (!IsConnected()) 610 return; 611 612 Packet packet; 613 packet << static_cast<u8>(IdSetGameInfo); 614 packet << game_info.name; 615 packet << game_info.id; 616 room_member_impl->Send(std::move(packet)); 617 } 618 619 void RoomMember::SendModerationRequest(RoomMessageTypes type, const std::string& nickname) { 620 ASSERT_MSG(type == IdModKick || type == IdModBan || type == IdModUnban, 621 "type is not a moderation request"); 622 if (!IsConnected()) 623 return; 624 625 Packet packet; 626 packet << static_cast<u8>(type); 627 packet << nickname; 628 room_member_impl->Send(std::move(packet)); 629 } 630 631 void RoomMember::RequestBanList() { 632 if (!IsConnected()) 633 return; 634 635 Packet packet; 636 packet << static_cast<u8>(IdModGetBanList); 637 room_member_impl->Send(std::move(packet)); 638 } 639 640 RoomMember::CallbackHandle<RoomMember::State> RoomMember::BindOnStateChanged( 641 std::function<void(const RoomMember::State&)> callback) { 642 return room_member_impl->Bind(callback); 643 } 644 645 RoomMember::CallbackHandle<RoomMember::Error> RoomMember::BindOnError( 646 std::function<void(const RoomMember::Error&)> callback) { 647 return room_member_impl->Bind(callback); 648 } 649 650 RoomMember::CallbackHandle<WifiPacket> RoomMember::BindOnWifiPacketReceived( 651 std::function<void(const WifiPacket&)> callback) { 652 return room_member_impl->Bind(callback); 653 } 654 655 RoomMember::CallbackHandle<RoomInformation> RoomMember::BindOnRoomInformationChanged( 656 std::function<void(const RoomInformation&)> callback) { 657 return room_member_impl->Bind(callback); 658 } 659 660 RoomMember::CallbackHandle<ChatEntry> RoomMember::BindOnChatMessageRecieved( 661 std::function<void(const ChatEntry&)> callback) { 662 return room_member_impl->Bind(callback); 663 } 664 665 RoomMember::CallbackHandle<StatusMessageEntry> RoomMember::BindOnStatusMessageReceived( 666 std::function<void(const StatusMessageEntry&)> callback) { 667 return room_member_impl->Bind(callback); 668 } 669 670 RoomMember::CallbackHandle<Room::BanList> RoomMember::BindOnBanListReceived( 671 std::function<void(const Room::BanList&)> callback) { 672 return room_member_impl->Bind(callback); 673 } 674 675 template <typename T> 676 void RoomMember::Unbind(CallbackHandle<T> handle) { 677 std::lock_guard lock(room_member_impl->callback_mutex); 678 room_member_impl->callbacks.Get<T>().erase(handle); 679 } 680 681 void RoomMember::Leave() { 682 room_member_impl->SetState(State::Idle); 683 room_member_impl->loop_thread->join(); 684 room_member_impl->loop_thread.reset(); 685 686 enet_host_destroy(room_member_impl->client); 687 room_member_impl->client = nullptr; 688 } 689 690 template void RoomMember::Unbind(CallbackHandle<WifiPacket>); 691 template void RoomMember::Unbind(CallbackHandle<RoomMember::State>); 692 template void RoomMember::Unbind(CallbackHandle<RoomMember::Error>); 693 template void RoomMember::Unbind(CallbackHandle<RoomInformation>); 694 template void RoomMember::Unbind(CallbackHandle<ChatEntry>); 695 template void RoomMember::Unbind(CallbackHandle<StatusMessageEntry>); 696 template void RoomMember::Unbind(CallbackHandle<Room::BanList>); 697 698 } // namespace Network