summaryrefslogtreecommitdiff
path: root/src/network/room.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/network/room.cpp')
-rw-r--r--src/network/room.cpp1111
1 files changed, 1111 insertions, 0 deletions
diff --git a/src/network/room.cpp b/src/network/room.cpp
new file mode 100644
index 000000000..cd0c0ebc4
--- /dev/null
+++ b/src/network/room.cpp
@@ -0,0 +1,1111 @@
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 <iomanip>
8#include <mutex>
9#include <random>
10#include <regex>
11#include <sstream>
12#include <thread>
13#include "common/logging/log.h"
14#include "enet/enet.h"
15#include "network/packet.h"
16#include "network/room.h"
17#include "network/verify_user.h"
18
19namespace Network {
20
21class Room::RoomImpl {
22public:
23 // This MAC address is used to generate a 'Nintendo' like Mac address.
24 const MacAddress NintendoOUI;
25 std::mt19937 random_gen; ///< Random number generator. Used for GenerateMacAddress
26
27 ENetHost* server = nullptr; ///< Network interface.
28
29 std::atomic<State> state{State::Closed}; ///< Current state of the room.
30 RoomInformation room_information; ///< Information about this room.
31
32 std::string verify_UID; ///< A GUID which may be used for verfication.
33 mutable std::mutex verify_UID_mutex; ///< Mutex for verify_UID
34
35 std::string password; ///< The password required to connect to this room.
36
37 struct Member {
38 std::string nickname; ///< The nickname of the member.
39 std::string console_id_hash; ///< A hash of the console ID of the member.
40 GameInfo game_info; ///< The current game of the member
41 MacAddress mac_address; ///< The assigned mac address of the member.
42 /// Data of the user, often including authenticated forum username.
43 VerifyUser::UserData user_data;
44 ENetPeer* peer; ///< The remote peer.
45 };
46 using MemberList = std::vector<Member>;
47 MemberList members; ///< Information about the members of this room
48 mutable std::mutex member_mutex; ///< Mutex for locking the members list
49 /// This should be a std::shared_mutex as soon as C++17 is supported
50
51 UsernameBanList username_ban_list; ///< List of banned usernames
52 IPBanList ip_ban_list; ///< List of banned IP addresses
53 mutable std::mutex ban_list_mutex; ///< Mutex for the ban lists
54
55 RoomImpl()
56 : NintendoOUI{0x00, 0x1F, 0x32, 0x00, 0x00, 0x00}, random_gen(std::random_device()()) {}
57
58 /// Thread that receives and dispatches network packets
59 std::unique_ptr<std::thread> room_thread;
60
61 /// Verification backend of the room
62 std::unique_ptr<VerifyUser::Backend> verify_backend;
63
64 /// Thread function that will receive and dispatch messages until the room is destroyed.
65 void ServerLoop();
66 void StartLoop();
67
68 /**
69 * Parses and answers a room join request from a client.
70 * Validates the uniqueness of the username and assigns the MAC address
71 * that the client will use for the remainder of the connection.
72 */
73 void HandleJoinRequest(const ENetEvent* event);
74
75 /**
76 * Parses and answers a kick request from a client.
77 * Validates the permissions and that the given user exists and then kicks the member.
78 */
79 void HandleModKickPacket(const ENetEvent* event);
80
81 /**
82 * Parses and answers a ban request from a client.
83 * Validates the permissions and bans the user (by forum username or IP).
84 */
85 void HandleModBanPacket(const ENetEvent* event);
86
87 /**
88 * Parses and answers a unban request from a client.
89 * Validates the permissions and unbans the address.
90 */
91 void HandleModUnbanPacket(const ENetEvent* event);
92
93 /**
94 * Parses and answers a get ban list request from a client.
95 * Validates the permissions and returns the ban list.
96 */
97 void HandleModGetBanListPacket(const ENetEvent* event);
98
99 /**
100 * Returns whether the nickname is valid, ie. isn't already taken by someone else in the room.
101 */
102 bool IsValidNickname(const std::string& nickname) const;
103
104 /**
105 * Returns whether the MAC address is valid, ie. isn't already taken by someone else in the
106 * room.
107 */
108 bool IsValidMacAddress(const MacAddress& address) const;
109
110 /**
111 * Returns whether the console ID (hash) is valid, ie. isn't already taken by someone else in
112 * the room.
113 */
114 bool IsValidConsoleId(const std::string& console_id_hash) const;
115
116 /**
117 * Returns whether a user has mod permissions.
118 */
119 bool HasModPermission(const ENetPeer* client) const;
120
121 /**
122 * Sends a ID_ROOM_IS_FULL message telling the client that the room is full.
123 */
124 void SendRoomIsFull(ENetPeer* client);
125
126 /**
127 * Sends a ID_ROOM_NAME_COLLISION message telling the client that the name is invalid.
128 */
129 void SendNameCollision(ENetPeer* client);
130
131 /**
132 * Sends a ID_ROOM_MAC_COLLISION message telling the client that the MAC is invalid.
133 */
134 void SendMacCollision(ENetPeer* client);
135
136 /**
137 * Sends a IdConsoleIdCollison message telling the client that another member with the same
138 * console ID exists.
139 */
140 void SendConsoleIdCollision(ENetPeer* client);
141
142 /**
143 * Sends a ID_ROOM_VERSION_MISMATCH message telling the client that the version is invalid.
144 */
145 void SendVersionMismatch(ENetPeer* client);
146
147 /**
148 * Sends a ID_ROOM_WRONG_PASSWORD message telling the client that the password is wrong.
149 */
150 void SendWrongPassword(ENetPeer* client);
151
152 /**
153 * Notifies the member that its connection attempt was successful,
154 * and it is now part of the room.
155 */
156 void SendJoinSuccess(ENetPeer* client, MacAddress mac_address);
157
158 /**
159 * Notifies the member that its connection attempt was successful,
160 * and it is now part of the room, and it has been granted mod permissions.
161 */
162 void SendJoinSuccessAsMod(ENetPeer* client, MacAddress mac_address);
163
164 /**
165 * Sends a IdHostKicked message telling the client that they have been kicked.
166 */
167 void SendUserKicked(ENetPeer* client);
168
169 /**
170 * Sends a IdHostBanned message telling the client that they have been banned.
171 */
172 void SendUserBanned(ENetPeer* client);
173
174 /**
175 * Sends a IdModPermissionDenied message telling the client that they do not have mod
176 * permission.
177 */
178 void SendModPermissionDenied(ENetPeer* client);
179
180 /**
181 * Sends a IdModNoSuchUser message telling the client that the given user could not be found.
182 */
183 void SendModNoSuchUser(ENetPeer* client);
184
185 /**
186 * Sends the ban list in response to a client's request for getting ban list.
187 */
188 void SendModBanListResponse(ENetPeer* client);
189
190 /**
191 * Notifies the members that the room is closed,
192 */
193 void SendCloseMessage();
194
195 /**
196 * Sends a system message to all the connected clients.
197 */
198 void SendStatusMessage(StatusMessageTypes type, const std::string& nickname,
199 const std::string& username, const std::string& ip);
200
201 /**
202 * Sends the information about the room, along with the list of members
203 * to every connected client in the room.
204 * The packet has the structure:
205 * <MessageID>ID_ROOM_INFORMATION
206 * <String> room_name
207 * <String> room_description
208 * <u32> member_slots: The max number of clients allowed in this room
209 * <String> uid
210 * <u16> port
211 * <u32> num_members: the number of currently joined clients
212 * This is followed by the following three values for each member:
213 * <String> nickname of that member
214 * <MacAddress> mac_address of that member
215 * <String> game_name of that member
216 */
217 void BroadcastRoomInformation();
218
219 /**
220 * Generates a free MAC address to assign to a new client.
221 * The first 3 bytes are the NintendoOUI 0x00, 0x1F, 0x32
222 */
223 MacAddress GenerateMacAddress();
224
225 /**
226 * Broadcasts this packet to all members except the sender.
227 * @param event The ENet event containing the data
228 */
229 void HandleWifiPacket(const ENetEvent* event);
230
231 /**
232 * Extracts a chat entry from a received ENet packet and adds it to the chat queue.
233 * @param event The ENet event that was received.
234 */
235 void HandleChatPacket(const ENetEvent* event);
236
237 /**
238 * Extracts the game name from a received ENet packet and broadcasts it.
239 * @param event The ENet event that was received.
240 */
241 void HandleGameNamePacket(const ENetEvent* event);
242
243 /**
244 * Removes the client from the members list if it was in it and announces the change
245 * to all other clients.
246 */
247 void HandleClientDisconnection(ENetPeer* client);
248};
249
250// RoomImpl
251void Room::RoomImpl::ServerLoop() {
252 while (state != State::Closed) {
253 ENetEvent event;
254 if (enet_host_service(server, &event, 50) > 0) {
255 switch (event.type) {
256 case ENET_EVENT_TYPE_RECEIVE:
257 switch (event.packet->data[0]) {
258 case IdJoinRequest:
259 HandleJoinRequest(&event);
260 break;
261 case IdSetGameInfo:
262 HandleGameNamePacket(&event);
263 break;
264 case IdWifiPacket:
265 HandleWifiPacket(&event);
266 break;
267 case IdChatMessage:
268 HandleChatPacket(&event);
269 break;
270 // Moderation
271 case IdModKick:
272 HandleModKickPacket(&event);
273 break;
274 case IdModBan:
275 HandleModBanPacket(&event);
276 break;
277 case IdModUnban:
278 HandleModUnbanPacket(&event);
279 break;
280 case IdModGetBanList:
281 HandleModGetBanListPacket(&event);
282 break;
283 }
284 enet_packet_destroy(event.packet);
285 break;
286 case ENET_EVENT_TYPE_DISCONNECT:
287 HandleClientDisconnection(event.peer);
288 break;
289 case ENET_EVENT_TYPE_NONE:
290 case ENET_EVENT_TYPE_CONNECT:
291 break;
292 }
293 }
294 }
295 // Close the connection to all members:
296 SendCloseMessage();
297}
298
299void Room::RoomImpl::StartLoop() {
300 room_thread = std::make_unique<std::thread>(&Room::RoomImpl::ServerLoop, this);
301}
302
303void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) {
304 {
305 std::lock_guard lock(member_mutex);
306 if (members.size() >= room_information.member_slots) {
307 SendRoomIsFull(event->peer);
308 return;
309 }
310 }
311 Packet packet;
312 packet.Append(event->packet->data, event->packet->dataLength);
313 packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
314 std::string nickname;
315 packet >> nickname;
316
317 std::string console_id_hash;
318 packet >> console_id_hash;
319
320 MacAddress preferred_mac;
321 packet >> preferred_mac;
322
323 u32 client_version;
324 packet >> client_version;
325
326 std::string pass;
327 packet >> pass;
328
329 std::string token;
330 packet >> token;
331
332 if (pass != password) {
333 SendWrongPassword(event->peer);
334 return;
335 }
336
337 if (!IsValidNickname(nickname)) {
338 SendNameCollision(event->peer);
339 return;
340 }
341
342 if (preferred_mac != NoPreferredMac) {
343 // Verify if the preferred mac is available
344 if (!IsValidMacAddress(preferred_mac)) {
345 SendMacCollision(event->peer);
346 return;
347 }
348 } else {
349 // Assign a MAC address of this client automatically
350 preferred_mac = GenerateMacAddress();
351 }
352
353 if (!IsValidConsoleId(console_id_hash)) {
354 SendConsoleIdCollision(event->peer);
355 return;
356 }
357
358 if (client_version != network_version) {
359 SendVersionMismatch(event->peer);
360 return;
361 }
362
363 // At this point the client is ready to be added to the room.
364 Member member{};
365 member.mac_address = preferred_mac;
366 member.console_id_hash = console_id_hash;
367 member.nickname = nickname;
368 member.peer = event->peer;
369
370 std::string uid;
371 {
372 std::lock_guard lock(verify_UID_mutex);
373 uid = verify_UID;
374 }
375 member.user_data = verify_backend->LoadUserData(uid, token);
376
377 std::string ip;
378 {
379 std::lock_guard lock(ban_list_mutex);
380
381 // Check username ban
382 if (!member.user_data.username.empty() &&
383 std::find(username_ban_list.begin(), username_ban_list.end(),
384 member.user_data.username) != username_ban_list.end()) {
385
386 SendUserBanned(event->peer);
387 return;
388 }
389
390 // Check IP ban
391 char ip_raw[256];
392 enet_address_get_host_ip(&event->peer->address, ip_raw, sizeof(ip_raw) - 1);
393 ip = ip_raw;
394
395 if (std::find(ip_ban_list.begin(), ip_ban_list.end(), ip) != ip_ban_list.end()) {
396 SendUserBanned(event->peer);
397 return;
398 }
399 }
400
401 // Notify everyone that the user has joined.
402 SendStatusMessage(IdMemberJoin, member.nickname, member.user_data.username, ip);
403
404 {
405 std::lock_guard lock(member_mutex);
406 members.push_back(std::move(member));
407 }
408
409 // Notify everyone that the room information has changed.
410 BroadcastRoomInformation();
411 if (HasModPermission(event->peer)) {
412 SendJoinSuccessAsMod(event->peer, preferred_mac);
413 } else {
414 SendJoinSuccess(event->peer, preferred_mac);
415 }
416}
417
418void Room::RoomImpl::HandleModKickPacket(const ENetEvent* event) {
419 if (!HasModPermission(event->peer)) {
420 SendModPermissionDenied(event->peer);
421 return;
422 }
423
424 Packet packet;
425 packet.Append(event->packet->data, event->packet->dataLength);
426 packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
427
428 std::string nickname;
429 packet >> nickname;
430
431 std::string username, ip;
432 {
433 std::lock_guard lock(member_mutex);
434 const auto target_member =
435 std::find_if(members.begin(), members.end(),
436 [&nickname](const auto& member) { return member.nickname == nickname; });
437 if (target_member == members.end()) {
438 SendModNoSuchUser(event->peer);
439 return;
440 }
441
442 // Notify the kicked member
443 SendUserKicked(target_member->peer);
444
445 username = target_member->user_data.username;
446
447 char ip_raw[256];
448 enet_address_get_host_ip(&target_member->peer->address, ip_raw, sizeof(ip_raw) - 1);
449 ip = ip_raw;
450
451 enet_peer_disconnect(target_member->peer, 0);
452 members.erase(target_member);
453 }
454
455 // Announce the change to all clients.
456 SendStatusMessage(IdMemberKicked, nickname, username, ip);
457 BroadcastRoomInformation();
458}
459
460void Room::RoomImpl::HandleModBanPacket(const ENetEvent* event) {
461 if (!HasModPermission(event->peer)) {
462 SendModPermissionDenied(event->peer);
463 return;
464 }
465
466 Packet packet;
467 packet.Append(event->packet->data, event->packet->dataLength);
468 packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
469
470 std::string nickname;
471 packet >> nickname;
472
473 std::string username, ip;
474 {
475 std::lock_guard lock(member_mutex);
476 const auto target_member =
477 std::find_if(members.begin(), members.end(),
478 [&nickname](const auto& member) { return member.nickname == nickname; });
479 if (target_member == members.end()) {
480 SendModNoSuchUser(event->peer);
481 return;
482 }
483
484 // Notify the banned member
485 SendUserBanned(target_member->peer);
486
487 nickname = target_member->nickname;
488 username = target_member->user_data.username;
489
490 char ip_raw[256];
491 enet_address_get_host_ip(&target_member->peer->address, ip_raw, sizeof(ip_raw) - 1);
492 ip = ip_raw;
493
494 enet_peer_disconnect(target_member->peer, 0);
495 members.erase(target_member);
496 }
497
498 {
499 std::lock_guard lock(ban_list_mutex);
500
501 if (!username.empty()) {
502 // Ban the forum username
503 if (std::find(username_ban_list.begin(), username_ban_list.end(), username) ==
504 username_ban_list.end()) {
505
506 username_ban_list.emplace_back(username);
507 }
508 }
509
510 // Ban the member's IP as well
511 if (std::find(ip_ban_list.begin(), ip_ban_list.end(), ip) == ip_ban_list.end()) {
512 ip_ban_list.emplace_back(ip);
513 }
514 }
515
516 // Announce the change to all clients.
517 SendStatusMessage(IdMemberBanned, nickname, username, ip);
518 BroadcastRoomInformation();
519}
520
521void Room::RoomImpl::HandleModUnbanPacket(const ENetEvent* event) {
522 if (!HasModPermission(event->peer)) {
523 SendModPermissionDenied(event->peer);
524 return;
525 }
526
527 Packet packet;
528 packet.Append(event->packet->data, event->packet->dataLength);
529 packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
530
531 std::string address;
532 packet >> address;
533
534 bool unbanned = false;
535 {
536 std::lock_guard lock(ban_list_mutex);
537
538 auto it = std::find(username_ban_list.begin(), username_ban_list.end(), address);
539 if (it != username_ban_list.end()) {
540 unbanned = true;
541 username_ban_list.erase(it);
542 }
543
544 it = std::find(ip_ban_list.begin(), ip_ban_list.end(), address);
545 if (it != ip_ban_list.end()) {
546 unbanned = true;
547 ip_ban_list.erase(it);
548 }
549 }
550
551 if (unbanned) {
552 SendStatusMessage(IdAddressUnbanned, address, "", "");
553 } else {
554 SendModNoSuchUser(event->peer);
555 }
556}
557
558void Room::RoomImpl::HandleModGetBanListPacket(const ENetEvent* event) {
559 if (!HasModPermission(event->peer)) {
560 SendModPermissionDenied(event->peer);
561 return;
562 }
563
564 SendModBanListResponse(event->peer);
565}
566
567bool Room::RoomImpl::IsValidNickname(const std::string& nickname) const {
568 // A nickname is valid if it matches the regex and is not already taken by anybody else in the
569 // room.
570 const std::regex nickname_regex("^[ a-zA-Z0-9._-]{4,20}$");
571 if (!std::regex_match(nickname, nickname_regex))
572 return false;
573
574 std::lock_guard lock(member_mutex);
575 return std::all_of(members.begin(), members.end(),
576 [&nickname](const auto& member) { return member.nickname != nickname; });
577}
578
579bool Room::RoomImpl::IsValidMacAddress(const MacAddress& address) const {
580 // A MAC address is valid if it is not already taken by anybody else in the room.
581 std::lock_guard lock(member_mutex);
582 return std::all_of(members.begin(), members.end(),
583 [&address](const auto& member) { return member.mac_address != address; });
584}
585
586bool Room::RoomImpl::IsValidConsoleId(const std::string& console_id_hash) const {
587 // A Console ID is valid if it is not already taken by anybody else in the room.
588 std::lock_guard lock(member_mutex);
589 return std::all_of(members.begin(), members.end(), [&console_id_hash](const auto& member) {
590 return member.console_id_hash != console_id_hash;
591 });
592}
593
594bool Room::RoomImpl::HasModPermission(const ENetPeer* client) const {
595 std::lock_guard lock(member_mutex);
596 const auto sending_member =
597 std::find_if(members.begin(), members.end(),
598 [client](const auto& member) { return member.peer == client; });
599 if (sending_member == members.end()) {
600 return false;
601 }
602 if (room_information.enable_citra_mods &&
603 sending_member->user_data.moderator) { // Community moderator
604
605 return true;
606 }
607 if (!room_information.host_username.empty() &&
608 sending_member->user_data.username == room_information.host_username) { // Room host
609
610 return true;
611 }
612 return false;
613}
614
615void Room::RoomImpl::SendNameCollision(ENetPeer* client) {
616 Packet packet;
617 packet << static_cast<u8>(IdNameCollision);
618
619 ENetPacket* enet_packet =
620 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
621 enet_peer_send(client, 0, enet_packet);
622 enet_host_flush(server);
623}
624
625void Room::RoomImpl::SendMacCollision(ENetPeer* client) {
626 Packet packet;
627 packet << static_cast<u8>(IdMacCollision);
628
629 ENetPacket* enet_packet =
630 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
631 enet_peer_send(client, 0, enet_packet);
632 enet_host_flush(server);
633}
634
635void Room::RoomImpl::SendConsoleIdCollision(ENetPeer* client) {
636 Packet packet;
637 packet << static_cast<u8>(IdConsoleIdCollision);
638
639 ENetPacket* enet_packet =
640 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
641 enet_peer_send(client, 0, enet_packet);
642 enet_host_flush(server);
643}
644
645void Room::RoomImpl::SendWrongPassword(ENetPeer* client) {
646 Packet packet;
647 packet << static_cast<u8>(IdWrongPassword);
648
649 ENetPacket* enet_packet =
650 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
651 enet_peer_send(client, 0, enet_packet);
652 enet_host_flush(server);
653}
654
655void Room::RoomImpl::SendRoomIsFull(ENetPeer* client) {
656 Packet packet;
657 packet << static_cast<u8>(IdRoomIsFull);
658
659 ENetPacket* enet_packet =
660 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
661 enet_peer_send(client, 0, enet_packet);
662 enet_host_flush(server);
663}
664
665void Room::RoomImpl::SendVersionMismatch(ENetPeer* client) {
666 Packet packet;
667 packet << static_cast<u8>(IdVersionMismatch);
668 packet << network_version;
669
670 ENetPacket* enet_packet =
671 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
672 enet_peer_send(client, 0, enet_packet);
673 enet_host_flush(server);
674}
675
676void Room::RoomImpl::SendJoinSuccess(ENetPeer* client, MacAddress mac_address) {
677 Packet packet;
678 packet << static_cast<u8>(IdJoinSuccess);
679 packet << mac_address;
680 ENetPacket* enet_packet =
681 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
682 enet_peer_send(client, 0, enet_packet);
683 enet_host_flush(server);
684}
685
686void Room::RoomImpl::SendJoinSuccessAsMod(ENetPeer* client, MacAddress mac_address) {
687 Packet packet;
688 packet << static_cast<u8>(IdJoinSuccessAsMod);
689 packet << mac_address;
690 ENetPacket* enet_packet =
691 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
692 enet_peer_send(client, 0, enet_packet);
693 enet_host_flush(server);
694}
695
696void Room::RoomImpl::SendUserKicked(ENetPeer* client) {
697 Packet packet;
698 packet << static_cast<u8>(IdHostKicked);
699
700 ENetPacket* enet_packet =
701 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
702 enet_peer_send(client, 0, enet_packet);
703 enet_host_flush(server);
704}
705
706void Room::RoomImpl::SendUserBanned(ENetPeer* client) {
707 Packet packet;
708 packet << static_cast<u8>(IdHostBanned);
709
710 ENetPacket* enet_packet =
711 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
712 enet_peer_send(client, 0, enet_packet);
713 enet_host_flush(server);
714}
715
716void Room::RoomImpl::SendModPermissionDenied(ENetPeer* client) {
717 Packet packet;
718 packet << static_cast<u8>(IdModPermissionDenied);
719
720 ENetPacket* enet_packet =
721 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
722 enet_peer_send(client, 0, enet_packet);
723 enet_host_flush(server);
724}
725
726void Room::RoomImpl::SendModNoSuchUser(ENetPeer* client) {
727 Packet packet;
728 packet << static_cast<u8>(IdModNoSuchUser);
729
730 ENetPacket* enet_packet =
731 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
732 enet_peer_send(client, 0, enet_packet);
733 enet_host_flush(server);
734}
735
736void Room::RoomImpl::SendModBanListResponse(ENetPeer* client) {
737 Packet packet;
738 packet << static_cast<u8>(IdModBanListResponse);
739 {
740 std::lock_guard lock(ban_list_mutex);
741 packet << username_ban_list;
742 packet << ip_ban_list;
743 }
744
745 ENetPacket* enet_packet =
746 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
747 enet_peer_send(client, 0, enet_packet);
748 enet_host_flush(server);
749}
750
751void Room::RoomImpl::SendCloseMessage() {
752 Packet packet;
753 packet << static_cast<u8>(IdCloseRoom);
754 std::lock_guard lock(member_mutex);
755 if (!members.empty()) {
756 ENetPacket* enet_packet =
757 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
758 for (auto& member : members) {
759 enet_peer_send(member.peer, 0, enet_packet);
760 }
761 }
762 enet_host_flush(server);
763 for (auto& member : members) {
764 enet_peer_disconnect(member.peer, 0);
765 }
766}
767
768void Room::RoomImpl::SendStatusMessage(StatusMessageTypes type, const std::string& nickname,
769 const std::string& username, const std::string& ip) {
770 Packet packet;
771 packet << static_cast<u8>(IdStatusMessage);
772 packet << static_cast<u8>(type);
773 packet << nickname;
774 packet << username;
775 std::lock_guard lock(member_mutex);
776 if (!members.empty()) {
777 ENetPacket* enet_packet =
778 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
779 for (auto& member : members) {
780 enet_peer_send(member.peer, 0, enet_packet);
781 }
782 }
783 enet_host_flush(server);
784
785 const std::string display_name =
786 username.empty() ? nickname : fmt::format("{} ({})", nickname, username);
787
788 switch (type) {
789 case IdMemberJoin:
790 LOG_INFO(Network, "[{}] {} has joined.", ip, display_name);
791 break;
792 case IdMemberLeave:
793 LOG_INFO(Network, "[{}] {} has left.", ip, display_name);
794 break;
795 case IdMemberKicked:
796 LOG_INFO(Network, "[{}] {} has been kicked.", ip, display_name);
797 break;
798 case IdMemberBanned:
799 LOG_INFO(Network, "[{}] {} has been banned.", ip, display_name);
800 break;
801 case IdAddressUnbanned:
802 LOG_INFO(Network, "{} has been unbanned.", display_name);
803 break;
804 }
805}
806
807void Room::RoomImpl::BroadcastRoomInformation() {
808 Packet packet;
809 packet << static_cast<u8>(IdRoomInformation);
810 packet << room_information.name;
811 packet << room_information.description;
812 packet << room_information.member_slots;
813 packet << room_information.port;
814 packet << room_information.preferred_game;
815 packet << room_information.host_username;
816
817 packet << static_cast<u32>(members.size());
818 {
819 std::lock_guard lock(member_mutex);
820 for (const auto& member : members) {
821 packet << member.nickname;
822 packet << member.mac_address;
823 packet << member.game_info.name;
824 packet << member.game_info.id;
825 packet << member.user_data.username;
826 packet << member.user_data.display_name;
827 packet << member.user_data.avatar_url;
828 }
829 }
830
831 ENetPacket* enet_packet =
832 enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
833 enet_host_broadcast(server, 0, enet_packet);
834 enet_host_flush(server);
835}
836
837MacAddress Room::RoomImpl::GenerateMacAddress() {
838 MacAddress result_mac =
839 NintendoOUI; // The first three bytes of each MAC address will be the NintendoOUI
840 std::uniform_int_distribution<> dis(0x00, 0xFF); // Random byte between 0 and 0xFF
841 do {
842 for (std::size_t i = 3; i < result_mac.size(); ++i) {
843 result_mac[i] = dis(random_gen);
844 }
845 } while (!IsValidMacAddress(result_mac));
846 return result_mac;
847}
848
849void Room::RoomImpl::HandleWifiPacket(const ENetEvent* event) {
850 Packet in_packet;
851 in_packet.Append(event->packet->data, event->packet->dataLength);
852 in_packet.IgnoreBytes(sizeof(u8)); // Message type
853 in_packet.IgnoreBytes(sizeof(u8)); // WifiPacket Type
854 in_packet.IgnoreBytes(sizeof(u8)); // WifiPacket Channel
855 in_packet.IgnoreBytes(sizeof(MacAddress)); // WifiPacket Transmitter Address
856 MacAddress destination_address;
857 in_packet >> destination_address;
858
859 Packet out_packet;
860 out_packet.Append(event->packet->data, event->packet->dataLength);
861 ENetPacket* enet_packet = enet_packet_create(out_packet.GetData(), out_packet.GetDataSize(),
862 ENET_PACKET_FLAG_RELIABLE);
863
864 if (destination_address == BroadcastMac) { // Send the data to everyone except the sender
865 std::lock_guard lock(member_mutex);
866 bool sent_packet = false;
867 for (const auto& member : members) {
868 if (member.peer != event->peer) {
869 sent_packet = true;
870 enet_peer_send(member.peer, 0, enet_packet);
871 }
872 }
873
874 if (!sent_packet) {
875 enet_packet_destroy(enet_packet);
876 }
877 } else { // Send the data only to the destination client
878 std::lock_guard lock(member_mutex);
879 auto member = std::find_if(members.begin(), members.end(),
880 [destination_address](const Member& member) -> bool {
881 return member.mac_address == destination_address;
882 });
883 if (member != members.end()) {
884 enet_peer_send(member->peer, 0, enet_packet);
885 } else {
886 LOG_ERROR(Network,
887 "Attempting to send to unknown MAC address: "
888 "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
889 destination_address[0], destination_address[1], destination_address[2],
890 destination_address[3], destination_address[4], destination_address[5]);
891 enet_packet_destroy(enet_packet);
892 }
893 }
894 enet_host_flush(server);
895}
896
897void Room::RoomImpl::HandleChatPacket(const ENetEvent* event) {
898 Packet in_packet;
899 in_packet.Append(event->packet->data, event->packet->dataLength);
900
901 in_packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
902 std::string message;
903 in_packet >> message;
904 auto CompareNetworkAddress = [event](const Member member) -> bool {
905 return member.peer == event->peer;
906 };
907
908 std::lock_guard lock(member_mutex);
909 const auto sending_member = std::find_if(members.begin(), members.end(), CompareNetworkAddress);
910 if (sending_member == members.end()) {
911 return; // Received a chat message from a unknown sender
912 }
913
914 // Limit the size of chat messages to MaxMessageSize
915 message.resize(std::min(static_cast<u32>(message.size()), MaxMessageSize));
916
917 Packet out_packet;
918 out_packet << static_cast<u8>(IdChatMessage);
919 out_packet << sending_member->nickname;
920 out_packet << sending_member->user_data.username;
921 out_packet << message;
922
923 ENetPacket* enet_packet = enet_packet_create(out_packet.GetData(), out_packet.GetDataSize(),
924 ENET_PACKET_FLAG_RELIABLE);
925 bool sent_packet = false;
926 for (const auto& member : members) {
927 if (member.peer != event->peer) {
928 sent_packet = true;
929 enet_peer_send(member.peer, 0, enet_packet);
930 }
931 }
932
933 if (!sent_packet) {
934 enet_packet_destroy(enet_packet);
935 }
936
937 enet_host_flush(server);
938
939 if (sending_member->user_data.username.empty()) {
940 LOG_INFO(Network, "{}: {}", sending_member->nickname, message);
941 } else {
942 LOG_INFO(Network, "{} ({}): {}", sending_member->nickname,
943 sending_member->user_data.username, message);
944 }
945}
946
947void Room::RoomImpl::HandleGameNamePacket(const ENetEvent* event) {
948 Packet in_packet;
949 in_packet.Append(event->packet->data, event->packet->dataLength);
950
951 in_packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
952 GameInfo game_info;
953 in_packet >> game_info.name;
954 in_packet >> game_info.id;
955
956 {
957 std::lock_guard lock(member_mutex);
958 auto member =
959 std::find_if(members.begin(), members.end(), [event](const Member& member) -> bool {
960 return member.peer == event->peer;
961 });
962 if (member != members.end()) {
963 member->game_info = game_info;
964
965 const std::string display_name =
966 member->user_data.username.empty()
967 ? member->nickname
968 : fmt::format("{} ({})", member->nickname, member->user_data.username);
969
970 if (game_info.name.empty()) {
971 LOG_INFO(Network, "{} is not playing", display_name);
972 } else {
973 LOG_INFO(Network, "{} is playing {}", display_name, game_info.name);
974 }
975 }
976 }
977 BroadcastRoomInformation();
978}
979
980void Room::RoomImpl::HandleClientDisconnection(ENetPeer* client) {
981 // Remove the client from the members list.
982 std::string nickname, username, ip;
983 {
984 std::lock_guard lock(member_mutex);
985 auto member = std::find_if(members.begin(), members.end(), [client](const Member& member) {
986 return member.peer == client;
987 });
988 if (member != members.end()) {
989 nickname = member->nickname;
990 username = member->user_data.username;
991
992 char ip_raw[256];
993 enet_address_get_host_ip(&member->peer->address, ip_raw, sizeof(ip_raw) - 1);
994 ip = ip_raw;
995
996 members.erase(member);
997 }
998 }
999
1000 // Announce the change to all clients.
1001 enet_peer_disconnect(client, 0);
1002 if (!nickname.empty())
1003 SendStatusMessage(IdMemberLeave, nickname, username, ip);
1004 BroadcastRoomInformation();
1005}
1006
1007// Room
1008Room::Room() : room_impl{std::make_unique<RoomImpl>()} {}
1009
1010Room::~Room() = default;
1011
1012bool Room::Create(const std::string& name, const std::string& description,
1013 const std::string& server_address, u16 server_port, const std::string& password,
1014 const u32 max_connections, const std::string& host_username,
1015 const std::string& preferred_game, u64 preferred_game_id,
1016 std::unique_ptr<VerifyUser::Backend> verify_backend,
1017 const Room::BanList& ban_list, bool enable_citra_mods) {
1018 ENetAddress address;
1019 address.host = ENET_HOST_ANY;
1020 if (!server_address.empty()) {
1021 enet_address_set_host(&address, server_address.c_str());
1022 }
1023 address.port = server_port;
1024
1025 // In order to send the room is full message to the connecting client, we need to leave one
1026 // slot open so enet won't reject the incoming connection without telling us
1027 room_impl->server = enet_host_create(&address, max_connections + 1, NumChannels, 0, 0);
1028 if (!room_impl->server) {
1029 return false;
1030 }
1031 room_impl->state = State::Open;
1032
1033 room_impl->room_information.name = name;
1034 room_impl->room_information.description = description;
1035 room_impl->room_information.member_slots = max_connections;
1036 room_impl->room_information.port = server_port;
1037 room_impl->room_information.preferred_game = preferred_game;
1038 room_impl->room_information.preferred_game_id = preferred_game_id;
1039 room_impl->room_information.host_username = host_username;
1040 room_impl->room_information.enable_citra_mods = enable_citra_mods;
1041 room_impl->password = password;
1042 room_impl->verify_backend = std::move(verify_backend);
1043 room_impl->username_ban_list = ban_list.first;
1044 room_impl->ip_ban_list = ban_list.second;
1045
1046 room_impl->StartLoop();
1047 return true;
1048}
1049
1050Room::State Room::GetState() const {
1051 return room_impl->state;
1052}
1053
1054const RoomInformation& Room::GetRoomInformation() const {
1055 return room_impl->room_information;
1056}
1057
1058std::string Room::GetVerifyUID() const {
1059 std::lock_guard lock(room_impl->verify_UID_mutex);
1060 return room_impl->verify_UID;
1061}
1062
1063Room::BanList Room::GetBanList() const {
1064 std::lock_guard lock(room_impl->ban_list_mutex);
1065 return {room_impl->username_ban_list, room_impl->ip_ban_list};
1066}
1067
1068std::vector<Room::Member> Room::GetRoomMemberList() const {
1069 std::vector<Room::Member> member_list;
1070 std::lock_guard lock(room_impl->member_mutex);
1071 for (const auto& member_impl : room_impl->members) {
1072 Member member;
1073 member.nickname = member_impl.nickname;
1074 member.username = member_impl.user_data.username;
1075 member.display_name = member_impl.user_data.display_name;
1076 member.avatar_url = member_impl.user_data.avatar_url;
1077 member.mac_address = member_impl.mac_address;
1078 member.game_info = member_impl.game_info;
1079 member_list.push_back(member);
1080 }
1081 return member_list;
1082}
1083
1084bool Room::HasPassword() const {
1085 return !room_impl->password.empty();
1086}
1087
1088void Room::SetVerifyUID(const std::string& uid) {
1089 std::lock_guard lock(room_impl->verify_UID_mutex);
1090 room_impl->verify_UID = uid;
1091}
1092
1093void Room::Destroy() {
1094 room_impl->state = State::Closed;
1095 room_impl->room_thread->join();
1096 room_impl->room_thread.reset();
1097
1098 if (room_impl->server) {
1099 enet_host_destroy(room_impl->server);
1100 }
1101 room_impl->room_information = {};
1102 room_impl->server = nullptr;
1103 {
1104 std::lock_guard lock(room_impl->member_mutex);
1105 room_impl->members.clear();
1106 }
1107 room_impl->room_information.member_slots = 0;
1108 room_impl->room_information.name.clear();
1109}
1110
1111} // namespace Network