summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar FearlessTobi2022-07-06 02:20:39 +0200
committerGravatar FearlessTobi2022-07-25 21:57:14 +0200
commitdcfe0a5febb252e3a4f40c1b25765740a269467f (patch)
tree3f06aa974d4bfba52df45697070ef112b701054b /src
parentMerge pull request #8564 from lat9nq/dinner-fork (diff)
downloadyuzu-dcfe0a5febb252e3a4f40c1b25765740a269467f.tar.gz
yuzu-dcfe0a5febb252e3a4f40c1b25765740a269467f.tar.xz
yuzu-dcfe0a5febb252e3a4f40c1b25765740a269467f.zip
network: Add initial files and enet dependency
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/network/CMakeLists.txt16
-rw-r--r--src/network/network.cpp50
-rw-r--r--src/network/network.h25
-rw-r--r--src/network/packet.cpp263
-rw-r--r--src/network/packet.h166
-rw-r--r--src/network/room.cpp1111
-rw-r--r--src/network/room.h173
-rw-r--r--src/network/room_member.cpp694
-rw-r--r--src/network/room_member.h327
-rw-r--r--src/network/verify_user.cpp18
-rw-r--r--src/network/verify_user.h46
12 files changed, 2890 insertions, 0 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 39ae573b2..9367f67c1 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -156,6 +156,7 @@ add_subdirectory(common)
156add_subdirectory(core) 156add_subdirectory(core)
157add_subdirectory(audio_core) 157add_subdirectory(audio_core)
158add_subdirectory(video_core) 158add_subdirectory(video_core)
159add_subdirectory(network)
159add_subdirectory(input_common) 160add_subdirectory(input_common)
160add_subdirectory(shader_recompiler) 161add_subdirectory(shader_recompiler)
161 162
diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt
new file mode 100644
index 000000000..382a69e2f
--- /dev/null
+++ b/src/network/CMakeLists.txt
@@ -0,0 +1,16 @@
1add_library(network STATIC
2 network.cpp
3 network.h
4 packet.cpp
5 packet.h
6 room.cpp
7 room.h
8 room_member.cpp
9 room_member.h
10 verify_user.cpp
11 verify_user.h
12)
13
14create_target_directory_groups(network)
15
16target_link_libraries(network PRIVATE common enet Boost::boost)
diff --git a/src/network/network.cpp b/src/network/network.cpp
new file mode 100644
index 000000000..51b5d6a9f
--- /dev/null
+++ b/src/network/network.cpp
@@ -0,0 +1,50 @@
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 "common/assert.h"
6#include "common/logging/log.h"
7#include "enet/enet.h"
8#include "network/network.h"
9
10namespace Network {
11
12static std::shared_ptr<RoomMember> g_room_member; ///< RoomMember (Client) for network games
13static std::shared_ptr<Room> g_room; ///< Room (Server) for network games
14// TODO(B3N30): Put these globals into a networking class
15
16bool Init() {
17 if (enet_initialize() != 0) {
18 LOG_ERROR(Network, "Error initalizing ENet");
19 return false;
20 }
21 g_room = std::make_shared<Room>();
22 g_room_member = std::make_shared<RoomMember>();
23 LOG_DEBUG(Network, "initialized OK");
24 return true;
25}
26
27std::weak_ptr<Room> GetRoom() {
28 return g_room;
29}
30
31std::weak_ptr<RoomMember> GetRoomMember() {
32 return g_room_member;
33}
34
35void Shutdown() {
36 if (g_room_member) {
37 if (g_room_member->IsConnected())
38 g_room_member->Leave();
39 g_room_member.reset();
40 }
41 if (g_room) {
42 if (g_room->GetState() == Room::State::Open)
43 g_room->Destroy();
44 g_room.reset();
45 }
46 enet_deinitialize();
47 LOG_DEBUG(Network, "shutdown OK");
48}
49
50} // namespace Network
diff --git a/src/network/network.h b/src/network/network.h
new file mode 100644
index 000000000..6d002d693
--- /dev/null
+++ b/src/network/network.h
@@ -0,0 +1,25 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include "network/room.h"
9#include "network/room_member.h"
10
11namespace Network {
12
13/// Initializes and registers the network device, the room, and the room member.
14bool Init();
15
16/// Returns a pointer to the room handle
17std::weak_ptr<Room> GetRoom();
18
19/// Returns a pointer to the room member handle
20std::weak_ptr<RoomMember> GetRoomMember();
21
22/// Unregisters the network device, the room, and the room member and shut them down.
23void Shutdown();
24
25} // namespace Network
diff --git a/src/network/packet.cpp b/src/network/packet.cpp
new file mode 100644
index 000000000..8fc5feabd
--- /dev/null
+++ b/src/network/packet.cpp
@@ -0,0 +1,263 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#ifdef _WIN32
6#include <winsock2.h>
7#else
8#include <arpa/inet.h>
9#endif
10#include <cstring>
11#include <string>
12#include "network/packet.h"
13
14namespace Network {
15
16#ifndef htonll
17u64 htonll(u64 x) {
18 return ((1 == htonl(1)) ? (x) : ((uint64_t)htonl((x)&0xFFFFFFFF) << 32) | htonl((x) >> 32));
19}
20#endif
21
22#ifndef ntohll
23u64 ntohll(u64 x) {
24 return ((1 == ntohl(1)) ? (x) : ((uint64_t)ntohl((x)&0xFFFFFFFF) << 32) | ntohl((x) >> 32));
25}
26#endif
27
28void Packet::Append(const void* in_data, std::size_t size_in_bytes) {
29 if (in_data && (size_in_bytes > 0)) {
30 std::size_t start = data.size();
31 data.resize(start + size_in_bytes);
32 std::memcpy(&data[start], in_data, size_in_bytes);
33 }
34}
35
36void Packet::Read(void* out_data, std::size_t size_in_bytes) {
37 if (out_data && CheckSize(size_in_bytes)) {
38 std::memcpy(out_data, &data[read_pos], size_in_bytes);
39 read_pos += size_in_bytes;
40 }
41}
42
43void Packet::Clear() {
44 data.clear();
45 read_pos = 0;
46 is_valid = true;
47}
48
49const void* Packet::GetData() const {
50 return !data.empty() ? &data[0] : nullptr;
51}
52
53void Packet::IgnoreBytes(u32 length) {
54 read_pos += length;
55}
56
57std::size_t Packet::GetDataSize() const {
58 return data.size();
59}
60
61bool Packet::EndOfPacket() const {
62 return read_pos >= data.size();
63}
64
65Packet::operator bool() const {
66 return is_valid;
67}
68
69Packet& Packet::operator>>(bool& out_data) {
70 u8 value;
71 if (*this >> value) {
72 out_data = (value != 0);
73 }
74 return *this;
75}
76
77Packet& Packet::operator>>(s8& out_data) {
78 Read(&out_data, sizeof(out_data));
79 return *this;
80}
81
82Packet& Packet::operator>>(u8& out_data) {
83 Read(&out_data, sizeof(out_data));
84 return *this;
85}
86
87Packet& Packet::operator>>(s16& out_data) {
88 s16 value;
89 Read(&value, sizeof(value));
90 out_data = ntohs(value);
91 return *this;
92}
93
94Packet& Packet::operator>>(u16& out_data) {
95 u16 value;
96 Read(&value, sizeof(value));
97 out_data = ntohs(value);
98 return *this;
99}
100
101Packet& Packet::operator>>(s32& out_data) {
102 s32 value;
103 Read(&value, sizeof(value));
104 out_data = ntohl(value);
105 return *this;
106}
107
108Packet& Packet::operator>>(u32& out_data) {
109 u32 value;
110 Read(&value, sizeof(value));
111 out_data = ntohl(value);
112 return *this;
113}
114
115Packet& Packet::operator>>(s64& out_data) {
116 s64 value;
117 Read(&value, sizeof(value));
118 out_data = ntohll(value);
119 return *this;
120}
121
122Packet& Packet::operator>>(u64& out_data) {
123 u64 value;
124 Read(&value, sizeof(value));
125 out_data = ntohll(value);
126 return *this;
127}
128
129Packet& Packet::operator>>(float& out_data) {
130 Read(&out_data, sizeof(out_data));
131 return *this;
132}
133
134Packet& Packet::operator>>(double& out_data) {
135 Read(&out_data, sizeof(out_data));
136 return *this;
137}
138
139Packet& Packet::operator>>(char* out_data) {
140 // First extract string length
141 u32 length = 0;
142 *this >> length;
143
144 if ((length > 0) && CheckSize(length)) {
145 // Then extract characters
146 std::memcpy(out_data, &data[read_pos], length);
147 out_data[length] = '\0';
148
149 // Update reading position
150 read_pos += length;
151 }
152
153 return *this;
154}
155
156Packet& Packet::operator>>(std::string& out_data) {
157 // First extract string length
158 u32 length = 0;
159 *this >> length;
160
161 out_data.clear();
162 if ((length > 0) && CheckSize(length)) {
163 // Then extract characters
164 out_data.assign(&data[read_pos], length);
165
166 // Update reading position
167 read_pos += length;
168 }
169
170 return *this;
171}
172
173Packet& Packet::operator<<(bool in_data) {
174 *this << static_cast<u8>(in_data);
175 return *this;
176}
177
178Packet& Packet::operator<<(s8 in_data) {
179 Append(&in_data, sizeof(in_data));
180 return *this;
181}
182
183Packet& Packet::operator<<(u8 in_data) {
184 Append(&in_data, sizeof(in_data));
185 return *this;
186}
187
188Packet& Packet::operator<<(s16 in_data) {
189 s16 toWrite = htons(in_data);
190 Append(&toWrite, sizeof(toWrite));
191 return *this;
192}
193
194Packet& Packet::operator<<(u16 in_data) {
195 u16 toWrite = htons(in_data);
196 Append(&toWrite, sizeof(toWrite));
197 return *this;
198}
199
200Packet& Packet::operator<<(s32 in_data) {
201 s32 toWrite = htonl(in_data);
202 Append(&toWrite, sizeof(toWrite));
203 return *this;
204}
205
206Packet& Packet::operator<<(u32 in_data) {
207 u32 toWrite = htonl(in_data);
208 Append(&toWrite, sizeof(toWrite));
209 return *this;
210}
211
212Packet& Packet::operator<<(s64 in_data) {
213 s64 toWrite = htonll(in_data);
214 Append(&toWrite, sizeof(toWrite));
215 return *this;
216}
217
218Packet& Packet::operator<<(u64 in_data) {
219 u64 toWrite = htonll(in_data);
220 Append(&toWrite, sizeof(toWrite));
221 return *this;
222}
223
224Packet& Packet::operator<<(float in_data) {
225 Append(&in_data, sizeof(in_data));
226 return *this;
227}
228
229Packet& Packet::operator<<(double in_data) {
230 Append(&in_data, sizeof(in_data));
231 return *this;
232}
233
234Packet& Packet::operator<<(const char* in_data) {
235 // First insert string length
236 u32 length = static_cast<u32>(std::strlen(in_data));
237 *this << length;
238
239 // Then insert characters
240 Append(in_data, length * sizeof(char));
241
242 return *this;
243}
244
245Packet& Packet::operator<<(const std::string& in_data) {
246 // First insert string length
247 u32 length = static_cast<u32>(in_data.size());
248 *this << length;
249
250 // Then insert characters
251 if (length > 0)
252 Append(in_data.c_str(), length * sizeof(std::string::value_type));
253
254 return *this;
255}
256
257bool Packet::CheckSize(std::size_t size) {
258 is_valid = is_valid && (read_pos + size <= data.size());
259
260 return is_valid;
261}
262
263} // namespace Network
diff --git a/src/network/packet.h b/src/network/packet.h
new file mode 100644
index 000000000..7bdc3da95
--- /dev/null
+++ b/src/network/packet.h
@@ -0,0 +1,166 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <vector>
9#include "common/common_types.h"
10
11namespace Network {
12
13/// A class that serializes data for network transfer. It also handles endianess
14class Packet {
15public:
16 Packet() = default;
17 ~Packet() = default;
18
19 /**
20 * Append data to the end of the packet
21 * @param data Pointer to the sequence of bytes to append
22 * @param size_in_bytes Number of bytes to append
23 */
24 void Append(const void* data, std::size_t size_in_bytes);
25
26 /**
27 * Reads data from the current read position of the packet
28 * @param out_data Pointer where the data should get written to
29 * @param size_in_bytes Number of bytes to read
30 */
31 void Read(void* out_data, std::size_t size_in_bytes);
32
33 /**
34 * Clear the packet
35 * After calling Clear, the packet is empty.
36 */
37 void Clear();
38
39 /**
40 * Ignores bytes while reading
41 * @param length THe number of bytes to ignore
42 */
43 void IgnoreBytes(u32 length);
44
45 /**
46 * Get a pointer to the data contained in the packet
47 * @return Pointer to the data
48 */
49 const void* GetData() const;
50
51 /**
52 * This function returns the number of bytes pointed to by
53 * what getData returns.
54 * @return Data size, in bytes
55 */
56 std::size_t GetDataSize() const;
57
58 /**
59 * This function is useful to know if there is some data
60 * left to be read, without actually reading it.
61 * @return True if all data was read, false otherwise
62 */
63 bool EndOfPacket() const;
64
65 explicit operator bool() const;
66
67 /// Overloads of operator >> to read data from the packet
68 Packet& operator>>(bool& out_data);
69 Packet& operator>>(s8& out_data);
70 Packet& operator>>(u8& out_data);
71 Packet& operator>>(s16& out_data);
72 Packet& operator>>(u16& out_data);
73 Packet& operator>>(s32& out_data);
74 Packet& operator>>(u32& out_data);
75 Packet& operator>>(s64& out_data);
76 Packet& operator>>(u64& out_data);
77 Packet& operator>>(float& out_data);
78 Packet& operator>>(double& out_data);
79 Packet& operator>>(char* out_data);
80 Packet& operator>>(std::string& out_data);
81 template <typename T>
82 Packet& operator>>(std::vector<T>& out_data);
83 template <typename T, std::size_t S>
84 Packet& operator>>(std::array<T, S>& out_data);
85
86 /// Overloads of operator << to write data into the packet
87 Packet& operator<<(bool in_data);
88 Packet& operator<<(s8 in_data);
89 Packet& operator<<(u8 in_data);
90 Packet& operator<<(s16 in_data);
91 Packet& operator<<(u16 in_data);
92 Packet& operator<<(s32 in_data);
93 Packet& operator<<(u32 in_data);
94 Packet& operator<<(s64 in_data);
95 Packet& operator<<(u64 in_data);
96 Packet& operator<<(float in_data);
97 Packet& operator<<(double in_data);
98 Packet& operator<<(const char* in_data);
99 Packet& operator<<(const std::string& in_data);
100 template <typename T>
101 Packet& operator<<(const std::vector<T>& in_data);
102 template <typename T, std::size_t S>
103 Packet& operator<<(const std::array<T, S>& data);
104
105private:
106 /**
107 * Check if the packet can extract a given number of bytes
108 * This function updates accordingly the state of the packet.
109 * @param size Size to check
110 * @return True if size bytes can be read from the packet
111 */
112 bool CheckSize(std::size_t size);
113
114 // Member data
115 std::vector<char> data; ///< Data stored in the packet
116 std::size_t read_pos = 0; ///< Current reading position in the packet
117 bool is_valid = true; ///< Reading state of the packet
118};
119
120template <typename T>
121Packet& Packet::operator>>(std::vector<T>& out_data) {
122 // First extract the size
123 u32 size = 0;
124 *this >> size;
125 out_data.resize(size);
126
127 // Then extract the data
128 for (std::size_t i = 0; i < out_data.size(); ++i) {
129 T character;
130 *this >> character;
131 out_data[i] = character;
132 }
133 return *this;
134}
135
136template <typename T, std::size_t S>
137Packet& Packet::operator>>(std::array<T, S>& out_data) {
138 for (std::size_t i = 0; i < out_data.size(); ++i) {
139 T character;
140 *this >> character;
141 out_data[i] = character;
142 }
143 return *this;
144}
145
146template <typename T>
147Packet& Packet::operator<<(const std::vector<T>& in_data) {
148 // First insert the size
149 *this << static_cast<u32>(in_data.size());
150
151 // Then insert the data
152 for (std::size_t i = 0; i < in_data.size(); ++i) {
153 *this << in_data[i];
154 }
155 return *this;
156}
157
158template <typename T, std::size_t S>
159Packet& Packet::operator<<(const std::array<T, S>& in_data) {
160 for (std::size_t i = 0; i < in_data.size(); ++i) {
161 *this << in_data[i];
162 }
163 return *this;
164}
165
166} // namespace Network
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
diff --git a/src/network/room.h b/src/network/room.h
new file mode 100644
index 000000000..a67984837
--- /dev/null
+++ b/src/network/room.h
@@ -0,0 +1,173 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <memory>
9#include <string>
10#include <vector>
11#include "common/common_types.h"
12#include "network/verify_user.h"
13
14namespace Network {
15
16constexpr u32 network_version = 4; ///< The version of this Room and RoomMember
17
18constexpr u16 DefaultRoomPort = 24872;
19
20constexpr u32 MaxMessageSize = 500;
21
22/// Maximum number of concurrent connections allowed to this room.
23static constexpr u32 MaxConcurrentConnections = 254;
24
25constexpr std::size_t NumChannels = 1; // Number of channels used for the connection
26
27struct RoomInformation {
28 std::string name; ///< Name of the server
29 std::string description; ///< Server description
30 u32 member_slots; ///< Maximum number of members in this room
31 u16 port; ///< The port of this room
32 std::string preferred_game; ///< Game to advertise that you want to play
33 u64 preferred_game_id; ///< Title ID for the advertised game
34 std::string host_username; ///< Forum username of the host
35 bool enable_citra_mods; ///< Allow Citra Moderators to moderate on this room
36};
37
38struct GameInfo {
39 std::string name{""};
40 u64 id{0};
41};
42
43using MacAddress = std::array<u8, 6>;
44/// A special MAC address that tells the room we're joining to assign us a MAC address
45/// automatically.
46constexpr MacAddress NoPreferredMac = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
47
48// 802.11 broadcast MAC address
49constexpr MacAddress BroadcastMac = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
50
51// The different types of messages that can be sent. The first byte of each packet defines the type
52enum RoomMessageTypes : u8 {
53 IdJoinRequest = 1,
54 IdJoinSuccess,
55 IdRoomInformation,
56 IdSetGameInfo,
57 IdWifiPacket,
58 IdChatMessage,
59 IdNameCollision,
60 IdMacCollision,
61 IdVersionMismatch,
62 IdWrongPassword,
63 IdCloseRoom,
64 IdRoomIsFull,
65 IdConsoleIdCollision,
66 IdStatusMessage,
67 IdHostKicked,
68 IdHostBanned,
69 /// Moderation requests
70 IdModKick,
71 IdModBan,
72 IdModUnban,
73 IdModGetBanList,
74 // Moderation responses
75 IdModBanListResponse,
76 IdModPermissionDenied,
77 IdModNoSuchUser,
78 IdJoinSuccessAsMod,
79};
80
81/// Types of system status messages
82enum StatusMessageTypes : u8 {
83 IdMemberJoin = 1, ///< Member joining
84 IdMemberLeave, ///< Member leaving
85 IdMemberKicked, ///< A member is kicked from the room
86 IdMemberBanned, ///< A member is banned from the room
87 IdAddressUnbanned, ///< A username / ip address is unbanned from the room
88};
89
90/// This is what a server [person creating a server] would use.
91class Room final {
92public:
93 enum class State : u8 {
94 Open, ///< The room is open and ready to accept connections.
95 Closed, ///< The room is not opened and can not accept connections.
96 };
97
98 struct Member {
99 std::string nickname; ///< The nickname of the member.
100 std::string username; ///< The web services username of the member. Can be empty.
101 std::string display_name; ///< The web services display name of the member. Can be empty.
102 std::string avatar_url; ///< Url to the member's avatar. Can be empty.
103 GameInfo game_info; ///< The current game of the member
104 MacAddress mac_address; ///< The assigned mac address of the member.
105 };
106
107 Room();
108 ~Room();
109
110 /**
111 * Gets the current state of the room.
112 */
113 State GetState() const;
114
115 /**
116 * Gets the room information of the room.
117 */
118 const RoomInformation& GetRoomInformation() const;
119
120 /**
121 * Gets the verify UID of this room.
122 */
123 std::string GetVerifyUID() const;
124
125 /**
126 * Gets a list of the mbmers connected to the room.
127 */
128 std::vector<Member> GetRoomMemberList() const;
129
130 /**
131 * Checks if the room is password protected
132 */
133 bool HasPassword() const;
134
135 using UsernameBanList = std::vector<std::string>;
136 using IPBanList = std::vector<std::string>;
137
138 using BanList = std::pair<UsernameBanList, IPBanList>;
139
140 /**
141 * Creates the socket for this room. Will bind to default address if
142 * server is empty string.
143 */
144 bool Create(const std::string& name, const std::string& description = "",
145 const std::string& server = "", u16 server_port = DefaultRoomPort,
146 const std::string& password = "",
147 const u32 max_connections = MaxConcurrentConnections,
148 const std::string& host_username = "", const std::string& preferred_game = "",
149 u64 preferred_game_id = 0,
150 std::unique_ptr<VerifyUser::Backend> verify_backend = nullptr,
151 const BanList& ban_list = {}, bool enable_citra_mods = false);
152
153 /**
154 * Sets the verification GUID of the room.
155 */
156 void SetVerifyUID(const std::string& uid);
157
158 /**
159 * Gets the ban list (including banned forum usernames and IPs) of the room.
160 */
161 BanList GetBanList() const;
162
163 /**
164 * Destroys the socket
165 */
166 void Destroy();
167
168private:
169 class RoomImpl;
170 std::unique_ptr<RoomImpl> room_impl;
171};
172
173} // namespace Network
diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp
new file mode 100644
index 000000000..e43004027
--- /dev/null
+++ b/src/network/room_member.cpp
@@ -0,0 +1,694 @@
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 <atomic>
6#include <list>
7#include <mutex>
8#include <set>
9#include <thread>
10#include "common/assert.h"
11#include "enet/enet.h"
12#include "network/packet.h"
13#include "network/room_member.h"
14
15namespace Network {
16
17constexpr u32 ConnectionTimeoutMs = 5000;
18
19class RoomMember::RoomMemberImpl {
20public:
21 ENetHost* client = nullptr; ///< ENet network interface.
22 ENetPeer* server = nullptr; ///< The server peer the client is connected to
23
24 /// Information about the clients connected to the same room as us.
25 MemberList member_information;
26 /// Information about the room we're connected to.
27 RoomInformation room_information;
28
29 /// The current game name, id and version
30 GameInfo current_game_info;
31
32 std::atomic<State> state{State::Idle}; ///< Current state of the RoomMember.
33 void SetState(const State new_state);
34 void SetError(const Error new_error);
35 bool IsConnected() const;
36
37 std::string nickname; ///< The nickname of this member.
38
39 std::string username; ///< The username of this member.
40 mutable std::mutex username_mutex; ///< Mutex for locking username.
41
42 MacAddress mac_address; ///< The mac_address of this member.
43
44 std::mutex network_mutex; ///< Mutex that controls access to the `client` variable.
45 /// Thread that receives and dispatches network packets
46 std::unique_ptr<std::thread> loop_thread;
47 std::mutex send_list_mutex; ///< Mutex that controls access to the `send_list` variable.
48 std::list<Packet> send_list; ///< A list that stores all packets to send the async
49
50 template <typename T>
51 using CallbackSet = std::set<CallbackHandle<T>>;
52 std::mutex callback_mutex; ///< The mutex used for handling callbacks
53
54 class Callbacks {
55 public:
56 template <typename T>
57 CallbackSet<T>& Get();
58
59 private:
60 CallbackSet<WifiPacket> callback_set_wifi_packet;
61 CallbackSet<ChatEntry> callback_set_chat_messages;
62 CallbackSet<StatusMessageEntry> callback_set_status_messages;
63 CallbackSet<RoomInformation> callback_set_room_information;
64 CallbackSet<State> callback_set_state;
65 CallbackSet<Error> callback_set_error;
66 CallbackSet<Room::BanList> callback_set_ban_list;
67 };
68 Callbacks callbacks; ///< All CallbackSets to all events
69
70 void MemberLoop();
71
72 void StartLoop();
73
74 /**
75 * Sends data to the room. It will be send on channel 0 with flag RELIABLE
76 * @param packet The data to send
77 */
78 void Send(Packet&& packet);
79
80 /**
81 * Sends a request to the server, asking for permission to join a room with the specified
82 * nickname and preferred mac.
83 * @params nickname The desired nickname.
84 * @params console_id_hash A hash of the Console ID.
85 * @params preferred_mac The preferred MAC address to use in the room, the NoPreferredMac tells
86 * @params password The password for the room
87 * the server to assign one for us.
88 */
89 void SendJoinRequest(const std::string& nickname, const std::string& console_id_hash,
90 const MacAddress& preferred_mac = NoPreferredMac,
91 const std::string& password = "", const std::string& token = "");
92
93 /**
94 * Extracts a MAC Address from a received ENet packet.
95 * @param event The ENet event that was received.
96 */
97 void HandleJoinPacket(const ENetEvent* event);
98 /**
99 * Extracts RoomInformation and MemberInformation from a received ENet packet.
100 * @param event The ENet event that was received.
101 */
102 void HandleRoomInformationPacket(const ENetEvent* event);
103
104 /**
105 * Extracts a WifiPacket from a received ENet packet.
106 * @param event The ENet event that was received.
107 */
108 void HandleWifiPackets(const ENetEvent* event);
109
110 /**
111 * Extracts a chat entry from a received ENet packet and adds it to the chat queue.
112 * @param event The ENet event that was received.
113 */
114 void HandleChatPacket(const ENetEvent* event);
115
116 /**
117 * Extracts a system message entry from a received ENet packet and adds it to the system message
118 * queue.
119 * @param event The ENet event that was received.
120 */
121 void HandleStatusMessagePacket(const ENetEvent* event);
122
123 /**
124 * Extracts a ban list request response from a received ENet packet.
125 * @param event The ENet event that was received.
126 */
127 void HandleModBanListResponsePacket(const ENetEvent* event);
128
129 /**
130 * Disconnects the RoomMember from the Room
131 */
132 void Disconnect();
133
134 template <typename T>
135 void Invoke(const T& data);
136
137 template <typename T>
138 CallbackHandle<T> Bind(std::function<void(const T&)> callback);
139};
140
141// RoomMemberImpl
142void RoomMember::RoomMemberImpl::SetState(const State new_state) {
143 if (state != new_state) {
144 state = new_state;
145 Invoke<State>(state);
146 }
147}
148
149void RoomMember::RoomMemberImpl::SetError(const Error new_error) {
150 Invoke<Error>(new_error);
151}
152
153bool RoomMember::RoomMemberImpl::IsConnected() const {
154 return state == State::Joining || state == State::Joined || state == State::Moderator;
155}
156
157void RoomMember::RoomMemberImpl::MemberLoop() {
158 // Receive packets while the connection is open
159 while (IsConnected()) {
160 std::lock_guard lock(network_mutex);
161 ENetEvent event;
162 if (enet_host_service(client, &event, 100) > 0) {
163 switch (event.type) {
164 case ENET_EVENT_TYPE_RECEIVE:
165 switch (event.packet->data[0]) {
166 case IdWifiPacket:
167 HandleWifiPackets(&event);
168 break;
169 case IdChatMessage:
170 HandleChatPacket(&event);
171 break;
172 case IdStatusMessage:
173 HandleStatusMessagePacket(&event);
174 break;
175 case IdRoomInformation:
176 HandleRoomInformationPacket(&event);
177 break;
178 case IdJoinSuccess:
179 case IdJoinSuccessAsMod:
180 // The join request was successful, we are now in the room.
181 // If we joined successfully, there must be at least one client in the room: us.
182 ASSERT_MSG(member_information.size() > 0,
183 "We have not yet received member information.");
184 HandleJoinPacket(&event); // Get the MAC Address for the client
185 if (event.packet->data[0] == IdJoinSuccessAsMod) {
186 SetState(State::Moderator);
187 } else {
188 SetState(State::Joined);
189 }
190 break;
191 case IdModBanListResponse:
192 HandleModBanListResponsePacket(&event);
193 break;
194 case IdRoomIsFull:
195 SetState(State::Idle);
196 SetError(Error::RoomIsFull);
197 break;
198 case IdNameCollision:
199 SetState(State::Idle);
200 SetError(Error::NameCollision);
201 break;
202 case IdMacCollision:
203 SetState(State::Idle);
204 SetError(Error::MacCollision);
205 break;
206 case IdConsoleIdCollision:
207 SetState(State::Idle);
208 SetError(Error::ConsoleIdCollision);
209 break;
210 case IdVersionMismatch:
211 SetState(State::Idle);
212 SetError(Error::WrongVersion);
213 break;
214 case IdWrongPassword:
215 SetState(State::Idle);
216 SetError(Error::WrongPassword);
217 break;
218 case IdCloseRoom:
219 SetState(State::Idle);
220 SetError(Error::LostConnection);
221 break;
222 case IdHostKicked:
223 SetState(State::Idle);
224 SetError(Error::HostKicked);
225 break;
226 case IdHostBanned:
227 SetState(State::Idle);
228 SetError(Error::HostBanned);
229 break;
230 case IdModPermissionDenied:
231 SetError(Error::PermissionDenied);
232 break;
233 case IdModNoSuchUser:
234 SetError(Error::NoSuchUser);
235 break;
236 }
237 enet_packet_destroy(event.packet);
238 break;
239 case ENET_EVENT_TYPE_DISCONNECT:
240 if (state == State::Joined || state == State::Moderator) {
241 SetState(State::Idle);
242 SetError(Error::LostConnection);
243 }
244 break;
245 case ENET_EVENT_TYPE_NONE:
246 break;
247 case ENET_EVENT_TYPE_CONNECT:
248 // The ENET_EVENT_TYPE_CONNECT event can not possibly happen here because we're
249 // already connected
250 ASSERT_MSG(false, "Received unexpected connect event while already connected");
251 break;
252 }
253 }
254 {
255 std::lock_guard lock(send_list_mutex);
256 for (const auto& packet : send_list) {
257 ENetPacket* enetPacket = enet_packet_create(packet.GetData(), packet.GetDataSize(),
258 ENET_PACKET_FLAG_RELIABLE);
259 enet_peer_send(server, 0, enetPacket);
260 }
261 enet_host_flush(client);
262 send_list.clear();
263 }
264 }
265 Disconnect();
266};
267
268void RoomMember::RoomMemberImpl::StartLoop() {
269 loop_thread = std::make_unique<std::thread>(&RoomMember::RoomMemberImpl::MemberLoop, this);
270}
271
272void RoomMember::RoomMemberImpl::Send(Packet&& packet) {
273 std::lock_guard lock(send_list_mutex);
274 send_list.push_back(std::move(packet));
275}
276
277void RoomMember::RoomMemberImpl::SendJoinRequest(const std::string& nickname,
278 const std::string& console_id_hash,
279 const MacAddress& preferred_mac,
280 const std::string& password,
281 const std::string& token) {
282 Packet packet;
283 packet << static_cast<u8>(IdJoinRequest);
284 packet << nickname;
285 packet << console_id_hash;
286 packet << preferred_mac;
287 packet << network_version;
288 packet << password;
289 packet << token;
290 Send(std::move(packet));
291}
292
293void RoomMember::RoomMemberImpl::HandleRoomInformationPacket(const ENetEvent* event) {
294 Packet packet;
295 packet.Append(event->packet->data, event->packet->dataLength);
296
297 // Ignore the first byte, which is the message id.
298 packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
299
300 RoomInformation info{};
301 packet >> info.name;
302 packet >> info.description;
303 packet >> info.member_slots;
304 packet >> info.port;
305 packet >> info.preferred_game;
306 packet >> info.host_username;
307 room_information.name = info.name;
308 room_information.description = info.description;
309 room_information.member_slots = info.member_slots;
310 room_information.port = info.port;
311 room_information.preferred_game = info.preferred_game;
312 room_information.host_username = info.host_username;
313
314 u32 num_members;
315 packet >> num_members;
316 member_information.resize(num_members);
317
318 for (auto& member : member_information) {
319 packet >> member.nickname;
320 packet >> member.mac_address;
321 packet >> member.game_info.name;
322 packet >> member.game_info.id;
323 packet >> member.username;
324 packet >> member.display_name;
325 packet >> member.avatar_url;
326
327 {
328 std::lock_guard lock(username_mutex);
329 if (member.nickname == nickname) {
330 username = member.username;
331 }
332 }
333 }
334 Invoke(room_information);
335}
336
337void RoomMember::RoomMemberImpl::HandleJoinPacket(const ENetEvent* event) {
338 Packet packet;
339 packet.Append(event->packet->data, event->packet->dataLength);
340
341 // Ignore the first byte, which is the message id.
342 packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
343
344 // Parse the MAC Address from the packet
345 packet >> mac_address;
346}
347
348void RoomMember::RoomMemberImpl::HandleWifiPackets(const ENetEvent* event) {
349 WifiPacket wifi_packet{};
350 Packet packet;
351 packet.Append(event->packet->data, event->packet->dataLength);
352
353 // Ignore the first byte, which is the message id.
354 packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
355
356 // Parse the WifiPacket from the packet
357 u8 frame_type;
358 packet >> frame_type;
359 WifiPacket::PacketType type = static_cast<WifiPacket::PacketType>(frame_type);
360
361 wifi_packet.type = type;
362 packet >> wifi_packet.channel;
363 packet >> wifi_packet.transmitter_address;
364 packet >> wifi_packet.destination_address;
365 packet >> wifi_packet.data;
366
367 Invoke<WifiPacket>(wifi_packet);
368}
369
370void RoomMember::RoomMemberImpl::HandleChatPacket(const ENetEvent* event) {
371 Packet packet;
372 packet.Append(event->packet->data, event->packet->dataLength);
373
374 // Ignore the first byte, which is the message id.
375 packet.IgnoreBytes(sizeof(u8));
376
377 ChatEntry chat_entry{};
378 packet >> chat_entry.nickname;
379 packet >> chat_entry.username;
380 packet >> chat_entry.message;
381 Invoke<ChatEntry>(chat_entry);
382}
383
384void RoomMember::RoomMemberImpl::HandleStatusMessagePacket(const ENetEvent* event) {
385 Packet packet;
386 packet.Append(event->packet->data, event->packet->dataLength);
387
388 // Ignore the first byte, which is the message id.
389 packet.IgnoreBytes(sizeof(u8));
390
391 StatusMessageEntry status_message_entry{};
392 u8 type{};
393 packet >> type;
394 status_message_entry.type = static_cast<StatusMessageTypes>(type);
395 packet >> status_message_entry.nickname;
396 packet >> status_message_entry.username;
397 Invoke<StatusMessageEntry>(status_message_entry);
398}
399
400void RoomMember::RoomMemberImpl::HandleModBanListResponsePacket(const ENetEvent* event) {
401 Packet packet;
402 packet.Append(event->packet->data, event->packet->dataLength);
403
404 // Ignore the first byte, which is the message id.
405 packet.IgnoreBytes(sizeof(u8));
406
407 Room::BanList ban_list = {};
408 packet >> ban_list.first;
409 packet >> ban_list.second;
410 Invoke<Room::BanList>(ban_list);
411}
412
413void RoomMember::RoomMemberImpl::Disconnect() {
414 member_information.clear();
415 room_information.member_slots = 0;
416 room_information.name.clear();
417
418 if (!server)
419 return;
420 enet_peer_disconnect(server, 0);
421
422 ENetEvent event;
423 while (enet_host_service(client, &event, ConnectionTimeoutMs) > 0) {
424 switch (event.type) {
425 case ENET_EVENT_TYPE_RECEIVE:
426 enet_packet_destroy(event.packet); // Ignore all incoming data
427 break;
428 case ENET_EVENT_TYPE_DISCONNECT:
429 server = nullptr;
430 return;
431 case ENET_EVENT_TYPE_NONE:
432 case ENET_EVENT_TYPE_CONNECT:
433 break;
434 }
435 }
436 // didn't disconnect gracefully force disconnect
437 enet_peer_reset(server);
438 server = nullptr;
439}
440
441template <>
442RoomMember::RoomMemberImpl::CallbackSet<WifiPacket>& RoomMember::RoomMemberImpl::Callbacks::Get() {
443 return callback_set_wifi_packet;
444}
445
446template <>
447RoomMember::RoomMemberImpl::CallbackSet<RoomMember::State>&
448RoomMember::RoomMemberImpl::Callbacks::Get() {
449 return callback_set_state;
450}
451
452template <>
453RoomMember::RoomMemberImpl::CallbackSet<RoomMember::Error>&
454RoomMember::RoomMemberImpl::Callbacks::Get() {
455 return callback_set_error;
456}
457
458template <>
459RoomMember::RoomMemberImpl::CallbackSet<RoomInformation>&
460RoomMember::RoomMemberImpl::Callbacks::Get() {
461 return callback_set_room_information;
462}
463
464template <>
465RoomMember::RoomMemberImpl::CallbackSet<ChatEntry>& RoomMember::RoomMemberImpl::Callbacks::Get() {
466 return callback_set_chat_messages;
467}
468
469template <>
470RoomMember::RoomMemberImpl::CallbackSet<StatusMessageEntry>&
471RoomMember::RoomMemberImpl::Callbacks::Get() {
472 return callback_set_status_messages;
473}
474
475template <>
476RoomMember::RoomMemberImpl::CallbackSet<Room::BanList>&
477RoomMember::RoomMemberImpl::Callbacks::Get() {
478 return callback_set_ban_list;
479}
480
481template <typename T>
482void RoomMember::RoomMemberImpl::Invoke(const T& data) {
483 std::lock_guard lock(callback_mutex);
484 CallbackSet<T> callback_set = callbacks.Get<T>();
485 for (auto const& callback : callback_set)
486 (*callback)(data);
487}
488
489template <typename T>
490RoomMember::CallbackHandle<T> RoomMember::RoomMemberImpl::Bind(
491 std::function<void(const T&)> callback) {
492 std::lock_guard lock(callback_mutex);
493 CallbackHandle<T> handle;
494 handle = std::make_shared<std::function<void(const T&)>>(callback);
495 callbacks.Get<T>().insert(handle);
496 return handle;
497}
498
499// RoomMember
500RoomMember::RoomMember() : room_member_impl{std::make_unique<RoomMemberImpl>()} {}
501
502RoomMember::~RoomMember() {
503 ASSERT_MSG(!IsConnected(), "RoomMember is being destroyed while connected");
504 if (room_member_impl->loop_thread) {
505 Leave();
506 }
507}
508
509RoomMember::State RoomMember::GetState() const {
510 return room_member_impl->state;
511}
512
513const RoomMember::MemberList& RoomMember::GetMemberInformation() const {
514 return room_member_impl->member_information;
515}
516
517const std::string& RoomMember::GetNickname() const {
518 return room_member_impl->nickname;
519}
520
521const std::string& RoomMember::GetUsername() const {
522 std::lock_guard lock(room_member_impl->username_mutex);
523 return room_member_impl->username;
524}
525
526const MacAddress& RoomMember::GetMacAddress() const {
527 ASSERT_MSG(IsConnected(), "Tried to get MAC address while not connected");
528 return room_member_impl->mac_address;
529}
530
531RoomInformation RoomMember::GetRoomInformation() const {
532 return room_member_impl->room_information;
533}
534
535void RoomMember::Join(const std::string& nick, const std::string& console_id_hash,
536 const char* server_addr, u16 server_port, u16 client_port,
537 const MacAddress& preferred_mac, const std::string& password,
538 const std::string& token) {
539 // If the member is connected, kill the connection first
540 if (room_member_impl->loop_thread && room_member_impl->loop_thread->joinable()) {
541 Leave();
542 }
543 // If the thread isn't running but the ptr still exists, reset it
544 else if (room_member_impl->loop_thread) {
545 room_member_impl->loop_thread.reset();
546 }
547
548 if (!room_member_impl->client) {
549 room_member_impl->client = enet_host_create(nullptr, 1, NumChannels, 0, 0);
550 ASSERT_MSG(room_member_impl->client != nullptr, "Could not create client");
551 }
552
553 room_member_impl->SetState(State::Joining);
554
555 ENetAddress address{};
556 enet_address_set_host(&address, server_addr);
557 address.port = server_port;
558 room_member_impl->server =
559 enet_host_connect(room_member_impl->client, &address, NumChannels, 0);
560
561 if (!room_member_impl->server) {
562 room_member_impl->SetState(State::Idle);
563 room_member_impl->SetError(Error::UnknownError);
564 return;
565 }
566
567 ENetEvent event{};
568 int net = enet_host_service(room_member_impl->client, &event, ConnectionTimeoutMs);
569 if (net > 0 && event.type == ENET_EVENT_TYPE_CONNECT) {
570 room_member_impl->nickname = nick;
571 room_member_impl->StartLoop();
572 room_member_impl->SendJoinRequest(nick, console_id_hash, preferred_mac, password, token);
573 SendGameInfo(room_member_impl->current_game_info);
574 } else {
575 enet_peer_disconnect(room_member_impl->server, 0);
576 room_member_impl->SetState(State::Idle);
577 room_member_impl->SetError(Error::CouldNotConnect);
578 }
579}
580
581bool RoomMember::IsConnected() const {
582 return room_member_impl->IsConnected();
583}
584
585void RoomMember::SendWifiPacket(const WifiPacket& wifi_packet) {
586 Packet packet;
587 packet << static_cast<u8>(IdWifiPacket);
588 packet << static_cast<u8>(wifi_packet.type);
589 packet << wifi_packet.channel;
590 packet << wifi_packet.transmitter_address;
591 packet << wifi_packet.destination_address;
592 packet << wifi_packet.data;
593 room_member_impl->Send(std::move(packet));
594}
595
596void RoomMember::SendChatMessage(const std::string& message) {
597 Packet packet;
598 packet << static_cast<u8>(IdChatMessage);
599 packet << message;
600 room_member_impl->Send(std::move(packet));
601}
602
603void RoomMember::SendGameInfo(const GameInfo& game_info) {
604 room_member_impl->current_game_info = game_info;
605 if (!IsConnected())
606 return;
607
608 Packet packet;
609 packet << static_cast<u8>(IdSetGameInfo);
610 packet << game_info.name;
611 packet << game_info.id;
612 room_member_impl->Send(std::move(packet));
613}
614
615void RoomMember::SendModerationRequest(RoomMessageTypes type, const std::string& nickname) {
616 ASSERT_MSG(type == IdModKick || type == IdModBan || type == IdModUnban,
617 "type is not a moderation request");
618 if (!IsConnected())
619 return;
620
621 Packet packet;
622 packet << static_cast<u8>(type);
623 packet << nickname;
624 room_member_impl->Send(std::move(packet));
625}
626
627void RoomMember::RequestBanList() {
628 if (!IsConnected())
629 return;
630
631 Packet packet;
632 packet << static_cast<u8>(IdModGetBanList);
633 room_member_impl->Send(std::move(packet));
634}
635
636RoomMember::CallbackHandle<RoomMember::State> RoomMember::BindOnStateChanged(
637 std::function<void(const RoomMember::State&)> callback) {
638 return room_member_impl->Bind(callback);
639}
640
641RoomMember::CallbackHandle<RoomMember::Error> RoomMember::BindOnError(
642 std::function<void(const RoomMember::Error&)> callback) {
643 return room_member_impl->Bind(callback);
644}
645
646RoomMember::CallbackHandle<WifiPacket> RoomMember::BindOnWifiPacketReceived(
647 std::function<void(const WifiPacket&)> callback) {
648 return room_member_impl->Bind(callback);
649}
650
651RoomMember::CallbackHandle<RoomInformation> RoomMember::BindOnRoomInformationChanged(
652 std::function<void(const RoomInformation&)> callback) {
653 return room_member_impl->Bind(callback);
654}
655
656RoomMember::CallbackHandle<ChatEntry> RoomMember::BindOnChatMessageRecieved(
657 std::function<void(const ChatEntry&)> callback) {
658 return room_member_impl->Bind(callback);
659}
660
661RoomMember::CallbackHandle<StatusMessageEntry> RoomMember::BindOnStatusMessageReceived(
662 std::function<void(const StatusMessageEntry&)> callback) {
663 return room_member_impl->Bind(callback);
664}
665
666RoomMember::CallbackHandle<Room::BanList> RoomMember::BindOnBanListReceived(
667 std::function<void(const Room::BanList&)> callback) {
668 return room_member_impl->Bind(callback);
669}
670
671template <typename T>
672void RoomMember::Unbind(CallbackHandle<T> handle) {
673 std::lock_guard lock(room_member_impl->callback_mutex);
674 room_member_impl->callbacks.Get<T>().erase(handle);
675}
676
677void RoomMember::Leave() {
678 room_member_impl->SetState(State::Idle);
679 room_member_impl->loop_thread->join();
680 room_member_impl->loop_thread.reset();
681
682 enet_host_destroy(room_member_impl->client);
683 room_member_impl->client = nullptr;
684}
685
686template void RoomMember::Unbind(CallbackHandle<WifiPacket>);
687template void RoomMember::Unbind(CallbackHandle<RoomMember::State>);
688template void RoomMember::Unbind(CallbackHandle<RoomMember::Error>);
689template void RoomMember::Unbind(CallbackHandle<RoomInformation>);
690template void RoomMember::Unbind(CallbackHandle<ChatEntry>);
691template void RoomMember::Unbind(CallbackHandle<StatusMessageEntry>);
692template void RoomMember::Unbind(CallbackHandle<Room::BanList>);
693
694} // namespace Network
diff --git a/src/network/room_member.h b/src/network/room_member.h
new file mode 100644
index 000000000..ee1c921d4
--- /dev/null
+++ b/src/network/room_member.h
@@ -0,0 +1,327 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <functional>
8#include <memory>
9#include <string>
10#include <vector>
11#include <boost/serialization/vector.hpp>
12#include "common/common_types.h"
13#include "network/room.h"
14
15namespace Network {
16
17/// Information about the received WiFi packets.
18/// Acts as our own 802.11 header.
19struct WifiPacket {
20 enum class PacketType : u8 {
21 Beacon,
22 Data,
23 Authentication,
24 AssociationResponse,
25 Deauthentication,
26 NodeMap
27 };
28 PacketType type; ///< The type of 802.11 frame.
29 std::vector<u8> data; ///< Raw 802.11 frame data, starting at the management frame header
30 /// for management frames.
31 MacAddress transmitter_address; ///< Mac address of the transmitter.
32 MacAddress destination_address; ///< Mac address of the receiver.
33 u8 channel; ///< WiFi channel where this frame was transmitted.
34
35private:
36 template <class Archive>
37 void serialize(Archive& ar, const unsigned int) {
38 ar& type;
39 ar& data;
40 ar& transmitter_address;
41 ar& destination_address;
42 ar& channel;
43 }
44 friend class boost::serialization::access;
45};
46
47/// Represents a chat message.
48struct ChatEntry {
49 std::string nickname; ///< Nickname of the client who sent this message.
50 /// Web services username of the client who sent this message, can be empty.
51 std::string username;
52 std::string message; ///< Body of the message.
53};
54
55/// Represents a system status message.
56struct StatusMessageEntry {
57 StatusMessageTypes type; ///< Type of the message
58 /// Subject of the message. i.e. the user who is joining/leaving/being banned, etc.
59 std::string nickname;
60 std::string username;
61};
62
63/**
64 * This is what a client [person joining a server] would use.
65 * It also has to be used if you host a game yourself (You'd create both, a Room and a
66 * RoomMembership for yourself)
67 */
68class RoomMember final {
69public:
70 enum class State : u8 {
71 Uninitialized, ///< Not initialized
72 Idle, ///< Default state (i.e. not connected)
73 Joining, ///< The client is attempting to join a room.
74 Joined, ///< The client is connected to the room and is ready to send/receive packets.
75 Moderator, ///< The client is connnected to the room and is granted mod permissions.
76 };
77
78 enum class Error : u8 {
79 // Reasons why connection was closed
80 LostConnection, ///< Connection closed
81 HostKicked, ///< Kicked by the host
82
83 // Reasons why connection was rejected
84 UnknownError, ///< Some error [permissions to network device missing or something]
85 NameCollision, ///< Somebody is already using this name
86 MacCollision, ///< Somebody is already using that mac-address
87 ConsoleIdCollision, ///< Somebody in the room has the same Console ID
88 WrongVersion, ///< The room version is not the same as for this RoomMember
89 WrongPassword, ///< The password doesn't match the one from the Room
90 CouldNotConnect, ///< The room is not responding to a connection attempt
91 RoomIsFull, ///< Room is already at the maximum number of players
92 HostBanned, ///< The user is banned by the host
93
94 // Reasons why moderation request failed
95 PermissionDenied, ///< The user does not have mod permissions
96 NoSuchUser, ///< The nickname the user attempts to kick/ban does not exist
97 };
98
99 struct MemberInformation {
100 std::string nickname; ///< Nickname of the member.
101 std::string username; ///< The web services username of the member. Can be empty.
102 std::string display_name; ///< The web services display name of the member. Can be empty.
103 std::string avatar_url; ///< Url to the member's avatar. Can be empty.
104 GameInfo game_info; ///< Name of the game they're currently playing, or empty if they're
105 /// not playing anything.
106 MacAddress mac_address; ///< MAC address associated with this member.
107 };
108 using MemberList = std::vector<MemberInformation>;
109
110 // The handle for the callback functions
111 template <typename T>
112 using CallbackHandle = std::shared_ptr<std::function<void(const T&)>>;
113
114 /**
115 * Unbinds a callback function from the events.
116 * @param handle The connection handle to disconnect
117 */
118 template <typename T>
119 void Unbind(CallbackHandle<T> handle);
120
121 RoomMember();
122 ~RoomMember();
123
124 /**
125 * Returns the status of our connection to the room.
126 */
127 State GetState() const;
128
129 /**
130 * Returns information about the members in the room we're currently connected to.
131 */
132 const MemberList& GetMemberInformation() const;
133
134 /**
135 * Returns the nickname of the RoomMember.
136 */
137 const std::string& GetNickname() const;
138
139 /**
140 * Returns the username of the RoomMember.
141 */
142 const std::string& GetUsername() const;
143
144 /**
145 * Returns the MAC address of the RoomMember.
146 */
147 const MacAddress& GetMacAddress() const;
148
149 /**
150 * Returns information about the room we're currently connected to.
151 */
152 RoomInformation GetRoomInformation() const;
153
154 /**
155 * Returns whether we're connected to a server or not.
156 */
157 bool IsConnected() const;
158
159 /**
160 * Attempts to join a room at the specified address and port, using the specified nickname.
161 * A console ID hash is passed in to check console ID conflicts.
162 * This may fail if the username or console ID is already taken.
163 */
164 void Join(const std::string& nickname, const std::string& console_id_hash,
165 const char* server_addr = "127.0.0.1", u16 server_port = DefaultRoomPort,
166 u16 client_port = 0, const MacAddress& preferred_mac = NoPreferredMac,
167 const std::string& password = "", const std::string& token = "");
168
169 /**
170 * Sends a WiFi packet to the room.
171 * @param packet The WiFi packet to send.
172 */
173 void SendWifiPacket(const WifiPacket& packet);
174
175 /**
176 * Sends a chat message to the room.
177 * @param message The contents of the message.
178 */
179 void SendChatMessage(const std::string& message);
180
181 /**
182 * Sends the current game info to the room.
183 * @param game_info The game information.
184 */
185 void SendGameInfo(const GameInfo& game_info);
186
187 /**
188 * Sends a moderation request to the room.
189 * @param type Moderation request type.
190 * @param nickname The subject of the request. (i.e. the user you want to kick/ban)
191 */
192 void SendModerationRequest(RoomMessageTypes type, const std::string& nickname);
193
194 /**
195 * Attempts to retrieve ban list from the room.
196 * If success, the ban list callback would be called. Otherwise an error would be emitted.
197 */
198 void RequestBanList();
199
200 /**
201 * Binds a function to an event that will be triggered every time the State of the member
202 * changed. The function wil be called every time the event is triggered. The callback function
203 * must not bind or unbind a function. Doing so will cause a deadlock
204 * @param callback The function to call
205 * @return A handle used for removing the function from the registered list
206 */
207 CallbackHandle<State> BindOnStateChanged(std::function<void(const State&)> callback);
208
209 /**
210 * Binds a function to an event that will be triggered every time an error happened. The
211 * function wil be called every time the event is triggered. The callback function must not bind
212 * or unbind a function. Doing so will cause a deadlock
213 * @param callback The function to call
214 * @return A handle used for removing the function from the registered list
215 */
216 CallbackHandle<Error> BindOnError(std::function<void(const Error&)> callback);
217
218 /**
219 * Binds a function to an event that will be triggered every time a WifiPacket is received.
220 * The function wil be called everytime the event is triggered.
221 * The callback function must not bind or unbind a function. Doing so will cause a deadlock
222 * @param callback The function to call
223 * @return A handle used for removing the function from the registered list
224 */
225 CallbackHandle<WifiPacket> BindOnWifiPacketReceived(
226 std::function<void(const WifiPacket&)> callback);
227
228 /**
229 * Binds a function to an event that will be triggered every time the RoomInformation changes.
230 * The function wil be called every time the event is triggered.
231 * The callback function must not bind or unbind a function. Doing so will cause a deadlock
232 * @param callback The function to call
233 * @return A handle used for removing the function from the registered list
234 */
235 CallbackHandle<RoomInformation> BindOnRoomInformationChanged(
236 std::function<void(const RoomInformation&)> callback);
237
238 /**
239 * Binds a function to an event that will be triggered every time a ChatMessage is received.
240 * The function wil be called every time the event is triggered.
241 * The callback function must not bind or unbind a function. Doing so will cause a deadlock
242 * @param callback The function to call
243 * @return A handle used for removing the function from the registered list
244 */
245 CallbackHandle<ChatEntry> BindOnChatMessageRecieved(
246 std::function<void(const ChatEntry&)> callback);
247
248 /**
249 * Binds a function to an event that will be triggered every time a StatusMessage is
250 * received. The function will be called every time the event is triggered. The callback
251 * function must not bind or unbind a function. Doing so will cause a deadlock
252 * @param callback The function to call
253 * @return A handle used for removing the function from the registered list
254 */
255 CallbackHandle<StatusMessageEntry> BindOnStatusMessageReceived(
256 std::function<void(const StatusMessageEntry&)> callback);
257
258 /**
259 * Binds a function to an event that will be triggered every time a requested ban list
260 * received. The function will be called every time the event is triggered. The callback
261 * function must not bind or unbind a function. Doing so will cause a deadlock
262 * @param callback The function to call
263 * @return A handle used for removing the function from the registered list
264 */
265 CallbackHandle<Room::BanList> BindOnBanListReceived(
266 std::function<void(const Room::BanList&)> callback);
267
268 /**
269 * Leaves the current room.
270 */
271 void Leave();
272
273private:
274 class RoomMemberImpl;
275 std::unique_ptr<RoomMemberImpl> room_member_impl;
276};
277
278inline const char* GetStateStr(const RoomMember::State& s) {
279 switch (s) {
280 case RoomMember::State::Uninitialized:
281 return "Uninitialized";
282 case RoomMember::State::Idle:
283 return "Idle";
284 case RoomMember::State::Joining:
285 return "Joining";
286 case RoomMember::State::Joined:
287 return "Joined";
288 case RoomMember::State::Moderator:
289 return "Moderator";
290 }
291 return "Unknown";
292}
293
294inline const char* GetErrorStr(const RoomMember::Error& e) {
295 switch (e) {
296 case RoomMember::Error::LostConnection:
297 return "LostConnection";
298 case RoomMember::Error::HostKicked:
299 return "HostKicked";
300 case RoomMember::Error::UnknownError:
301 return "UnknownError";
302 case RoomMember::Error::NameCollision:
303 return "NameCollision";
304 case RoomMember::Error::MacCollision:
305 return "MaxCollision";
306 case RoomMember::Error::ConsoleIdCollision:
307 return "ConsoleIdCollision";
308 case RoomMember::Error::WrongVersion:
309 return "WrongVersion";
310 case RoomMember::Error::WrongPassword:
311 return "WrongPassword";
312 case RoomMember::Error::CouldNotConnect:
313 return "CouldNotConnect";
314 case RoomMember::Error::RoomIsFull:
315 return "RoomIsFull";
316 case RoomMember::Error::HostBanned:
317 return "HostBanned";
318 case RoomMember::Error::PermissionDenied:
319 return "PermissionDenied";
320 case RoomMember::Error::NoSuchUser:
321 return "NoSuchUser";
322 default:
323 return "Unknown";
324 }
325}
326
327} // namespace Network
diff --git a/src/network/verify_user.cpp b/src/network/verify_user.cpp
new file mode 100644
index 000000000..d9d98e495
--- /dev/null
+++ b/src/network/verify_user.cpp
@@ -0,0 +1,18 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "network/verify_user.h"
6
7namespace Network::VerifyUser {
8
9Backend::~Backend() = default;
10
11NullBackend::~NullBackend() = default;
12
13UserData NullBackend::LoadUserData([[maybe_unused]] const std::string& verify_UID,
14 [[maybe_unused]] const std::string& token) {
15 return {};
16}
17
18} // namespace Network::VerifyUser
diff --git a/src/network/verify_user.h b/src/network/verify_user.h
new file mode 100644
index 000000000..01b9877c8
--- /dev/null
+++ b/src/network/verify_user.h
@@ -0,0 +1,46 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <string>
8#include "common/logging/log.h"
9
10namespace Network::VerifyUser {
11
12struct UserData {
13 std::string username;
14 std::string display_name;
15 std::string avatar_url;
16 bool moderator = false; ///< Whether the user is a Citra Moderator.
17};
18
19/**
20 * A backend used for verifying users and loading user data.
21 */
22class Backend {
23public:
24 virtual ~Backend();
25
26 /**
27 * Verifies the given token and loads the information into a UserData struct.
28 * @param verify_UID A GUID that may be used for verification.
29 * @param token A token that contains user data and verification data. The format and content is
30 * decided by backends.
31 */
32 virtual UserData LoadUserData(const std::string& verify_UID, const std::string& token) = 0;
33};
34
35/**
36 * A null backend where the token is ignored.
37 * No verification is performed here and the function returns an empty UserData.
38 */
39class NullBackend final : public Backend {
40public:
41 ~NullBackend();
42
43 UserData LoadUserData(const std::string& verify_UID, const std::string& token) override;
44};
45
46} // namespace Network::VerifyUser