network/room_member: Add moderation functions

To allow for passing moderation errors around without impacting the State, this commit also separates the previous State enum into two enums: State, and Error. The State enum now only contains generic states like disconnected or connected, and the Error enum describes the specific error happened.

citra_qt/multiplayer/{state, message} is changed accordingly.
This commit is contained in:
zhupengfei 2018-11-24 16:13:46 +08:00
parent 38f86cce94
commit 7acd2664dd
No known key found for this signature in database
GPG key ID: DD129E108BD09378
6 changed files with 251 additions and 56 deletions

View file

@ -31,6 +31,7 @@ public:
std::atomic<State> state{State::Idle}; ///< Current state of the RoomMember.
void SetState(const State new_state);
void SetError(const Error new_error);
bool IsConnected() const;
std::string nickname; ///< The nickname of this member.
@ -61,6 +62,8 @@ public:
CallbackSet<StatusMessageEntry> callback_set_status_messages;
CallbackSet<RoomInformation> callback_set_room_information;
CallbackSet<State> callback_set_state;
CallbackSet<Error> callback_set_error;
CallbackSet<Room::BanList> callback_set_ban_list;
};
Callbacks callbacks; ///< All CallbackSets to all events
@ -117,6 +120,12 @@ public:
*/
void HandleStatusMessagePacket(const ENetEvent* event);
/**
* Extracts a ban list request response from a received ENet packet.
* @param event The ENet event that was received.
*/
void HandleModBanListResponsePacket(const ENetEvent* event);
/**
* Disconnects the RoomMember from the Room
*/
@ -137,6 +146,10 @@ void RoomMember::RoomMemberImpl::SetState(const State new_state) {
}
}
void RoomMember::RoomMemberImpl::SetError(const Error new_error) {
Invoke<Error>(new_error);
}
bool RoomMember::RoomMemberImpl::IsConnected() const {
return state == State::Joining || state == State::Joined;
}
@ -170,32 +183,59 @@ void RoomMember::RoomMemberImpl::MemberLoop() {
HandleJoinPacket(&event); // Get the MAC Address for the client
SetState(State::Joined);
break;
case IdModBanListResponse:
HandleModBanListResponsePacket(&event);
break;
case IdRoomIsFull:
SetState(State::RoomIsFull);
SetState(State::Idle);
SetError(Error::RoomIsFull);
break;
case IdNameCollision:
SetState(State::NameCollision);
SetState(State::Idle);
SetError(Error::NameCollision);
break;
case IdMacCollision:
SetState(State::MacCollision);
SetState(State::Idle);
SetError(Error::MacCollision);
break;
case IdConsoleIdCollision:
SetState(State::ConsoleIdCollision);
SetState(State::Idle);
SetError(Error::ConsoleIdCollision);
break;
case IdVersionMismatch:
SetState(State::WrongVersion);
SetState(State::Idle);
SetError(Error::WrongVersion);
break;
case IdWrongPassword:
SetState(State::WrongPassword);
SetState(State::Idle);
SetError(Error::WrongPassword);
break;
case IdCloseRoom:
SetState(State::LostConnection);
SetState(State::Idle);
SetError(Error::LostConnection);
break;
case IdHostKicked:
SetState(State::Idle);
SetError(Error::HostKicked);
break;
case IdHostBanned:
SetState(State::Idle);
SetError(Error::HostBanned);
break;
case IdModPermissionDenied:
SetError(Error::PermissionDenied);
break;
case IdModNoSuchUser:
SetError(Error::NoSuchUser);
break;
}
enet_packet_destroy(event.packet);
break;
case ENET_EVENT_TYPE_DISCONNECT:
SetState(State::LostConnection);
if (state == State::Joined) {
SetState(State::Idle);
SetError(Error::LostConnection);
}
break;
}
}
@ -251,11 +291,13 @@ void RoomMember::RoomMemberImpl::HandleRoomInformationPacket(const ENetEvent* ev
packet >> info.member_slots;
packet >> info.port;
packet >> info.preferred_game;
packet >> info.host_username;
room_information.name = info.name;
room_information.description = info.description;
room_information.member_slots = info.member_slots;
room_information.port = info.port;
room_information.preferred_game = info.preferred_game;
room_information.host_username = info.host_username;
u32 num_members;
packet >> num_members;
@ -344,6 +386,19 @@ void RoomMember::RoomMemberImpl::HandleStatusMessagePacket(const ENetEvent* even
Invoke<StatusMessageEntry>(status_message_entry);
}
void RoomMember::RoomMemberImpl::HandleModBanListResponsePacket(const ENetEvent* event) {
Packet packet;
packet.Append(event->packet->data, event->packet->dataLength);
// Ignore the first byte, which is the message id.
packet.IgnoreBytes(sizeof(u8));
Room::BanList ban_list = {};
packet >> ban_list.first;
packet >> ban_list.second;
Invoke<Room::BanList>(ban_list);
}
void RoomMember::RoomMemberImpl::Disconnect() {
member_information.clear();
room_information.member_slots = 0;
@ -383,6 +438,12 @@ RoomMember::RoomMemberImpl::Callbacks::Get() {
return callback_set_state;
}
template <>
RoomMember::RoomMemberImpl::CallbackSet<RoomMember::Error>&
RoomMember::RoomMemberImpl::Callbacks::Get() {
return callback_set_error;
}
template <>
RoomMember::RoomMemberImpl::CallbackSet<RoomInformation>&
RoomMember::RoomMemberImpl::Callbacks::Get() {
@ -400,6 +461,12 @@ RoomMember::RoomMemberImpl::Callbacks::Get() {
return callback_set_status_messages;
}
template <>
RoomMember::RoomMemberImpl::CallbackSet<Room::BanList>&
RoomMember::RoomMemberImpl::Callbacks::Get() {
return callback_set_ban_list;
}
template <typename T>
void RoomMember::RoomMemberImpl::Invoke(const T& data) {
std::lock_guard<std::mutex> lock(callback_mutex);
@ -481,7 +548,8 @@ void RoomMember::Join(const std::string& nick, const std::string& console_id_has
enet_host_connect(room_member_impl->client, &address, NumChannels, 0);
if (!room_member_impl->server) {
room_member_impl->SetState(State::Error);
room_member_impl->SetState(State::Idle);
room_member_impl->SetError(Error::UnknownError);
return;
}
@ -494,7 +562,8 @@ void RoomMember::Join(const std::string& nick, const std::string& console_id_has
SendGameInfo(room_member_impl->current_game_info);
} else {
enet_peer_disconnect(room_member_impl->server, 0);
room_member_impl->SetState(State::CouldNotConnect);
room_member_impl->SetState(State::Idle);
room_member_impl->SetError(Error::CouldNotConnect);
}
}
@ -532,11 +601,37 @@ void RoomMember::SendGameInfo(const GameInfo& game_info) {
room_member_impl->Send(std::move(packet));
}
void RoomMember::SendModerationRequest(RoomMessageTypes type, const std::string& nickname) {
ASSERT_MSG(type == IdModKick || type == IdModBan || type == IdModUnban,
"type is not a moderation request");
if (!IsConnected())
return;
Packet packet;
packet << static_cast<u8>(type);
packet << nickname;
room_member_impl->Send(std::move(packet));
}
void RoomMember::RequestBanList() {
if (!IsConnected())
return;
Packet packet;
packet << static_cast<u8>(IdModGetBanList);
room_member_impl->Send(std::move(packet));
}
RoomMember::CallbackHandle<RoomMember::State> RoomMember::BindOnStateChanged(
std::function<void(const RoomMember::State&)> callback) {
return room_member_impl->Bind(callback);
}
RoomMember::CallbackHandle<RoomMember::Error> RoomMember::BindOnError(
std::function<void(const RoomMember::Error&)> callback) {
return room_member_impl->Bind(callback);
}
RoomMember::CallbackHandle<WifiPacket> RoomMember::BindOnWifiPacketReceived(
std::function<void(const WifiPacket&)> callback) {
return room_member_impl->Bind(callback);
@ -557,6 +652,11 @@ RoomMember::CallbackHandle<StatusMessageEntry> RoomMember::BindOnStatusMessageRe
return room_member_impl->Bind(callback);
}
RoomMember::CallbackHandle<Room::BanList> RoomMember::BindOnBanListReceived(
std::function<void(const Room::BanList&)> callback) {
return room_member_impl->Bind(callback);
}
template <typename T>
void RoomMember::Unbind(CallbackHandle<T> handle) {
std::lock_guard<std::mutex> lock(room_member_impl->callback_mutex);
@ -574,8 +674,10 @@ void RoomMember::Leave() {
template void RoomMember::Unbind(CallbackHandle<WifiPacket>);
template void RoomMember::Unbind(CallbackHandle<RoomMember::State>);
template void RoomMember::Unbind(CallbackHandle<RoomMember::Error>);
template void RoomMember::Unbind(CallbackHandle<RoomInformation>);
template void RoomMember::Unbind(CallbackHandle<ChatEntry>);
template void RoomMember::Unbind(CallbackHandle<StatusMessageEntry>);
template void RoomMember::Unbind(CallbackHandle<Room::BanList>);
} // namespace Network

View file

@ -57,20 +57,30 @@ class RoomMember final {
public:
enum class State : u8 {
Uninitialized, ///< Not initialized
Idle, ///< Default state
Error, ///< Some error [permissions to network device missing or something]
Idle, ///< Default state (i.e. not connected)
Joining, ///< The client is attempting to join a room.
Joined, ///< The client is connected to the room and is ready to send/receive packets.
};
enum class Error : u8 {
// Reasons why connection was closed
LostConnection, ///< Connection closed
HostKicked, ///< Kicked by the host
// Reasons why connection was rejected
UnknownError, ///< Some error [permissions to network device missing or something]
NameCollision, ///< Somebody is already using this name
MacCollision, ///< Somebody is already using that mac-address
ConsoleIdCollision, ///< Somebody in the room has the same Console ID
WrongVersion, ///< The room version is not the same as for this RoomMember
WrongPassword, ///< The password doesn't match the one from the Room
CouldNotConnect, ///< The room is not responding to a connection attempt
RoomIsFull ///< Room is already at the maximum number of players
RoomIsFull, ///< Room is already at the maximum number of players
HostBanned, ///< The user is banned by the host
// Reasons why moderation request failed
PermissionDenied, ///< The user does not have mod permissions
NoSuchUser, ///< The nickname the user attempts to kick/ban does not exist
};
struct MemberInformation {
@ -161,6 +171,19 @@ public:
*/
void SendGameInfo(const GameInfo& game_info);
/**
* Sends a moderation request to the room.
* @param type Moderation request type.
* @param nickname The subject of the request. (i.e. the user you want to kick/ban)
*/
void SendModerationRequest(RoomMessageTypes type, const std::string& nickname);
/**
* Attempts to retrieve ban list from the room.
* If success, the ban list callback would be called. Otherwise an error would be emitted.
*/
void RequestBanList();
/**
* Binds a function to an event that will be triggered every time the State of the member
* changed. The function wil be called every time the event is triggered. The callback function
@ -170,6 +193,15 @@ public:
*/
CallbackHandle<State> BindOnStateChanged(std::function<void(const State&)> callback);
/**
* Binds a function to an event that will be triggered every time an error happened. The
* function wil be called every time the event is triggered. The callback function must not bind
* or unbind a function. Doing so will cause a deadlock
* @param callback The function to call
* @return A handle used for removing the function from the registered list
*/
CallbackHandle<Error> BindOnError(std::function<void(const Error&)> callback);
/**
* Binds a function to an event that will be triggered every time a WifiPacket is received.
* The function wil be called everytime the event is triggered.
@ -210,6 +242,16 @@ public:
CallbackHandle<StatusMessageEntry> BindOnStatusMessageReceived(
std::function<void(const StatusMessageEntry&)> callback);
/**
* Binds a function to an event that will be triggered every time a requested ban list
* received. The function will be called every time the event is triggered. The callback
* function must not bind or unbind a function. Doing so will cause a deadlock
* @param callback The function to call
* @return A handle used for removing the function from the registered list
*/
CallbackHandle<Room::BanList> BindOnBanListReceived(
std::function<void(const Room::BanList&)> callback);
/**
* Leaves the current room.
*/
@ -224,24 +266,42 @@ static const char* GetStateStr(const RoomMember::State& s) {
switch (s) {
case RoomMember::State::Idle:
return "Idle";
case RoomMember::State::Error:
return "Error";
case RoomMember::State::Joining:
return "Joining";
case RoomMember::State::Joined:
return "Joined";
case RoomMember::State::LostConnection:
}
return "Unknown";
}
static const char* GetErrorStr(const RoomMember::Error& e) {
switch (e) {
case RoomMember::Error::LostConnection:
return "LostConnection";
case RoomMember::State::NameCollision:
case RoomMember::Error::HostKicked:
return "HostKicked";
case RoomMember::Error::UnknownError:
return "UnknownError";
case RoomMember::Error::NameCollision:
return "NameCollision";
case RoomMember::State::MacCollision:
return "MacCollision";
case RoomMember::State::WrongVersion:
case RoomMember::Error::MacCollision:
return "MaxCollision";
case RoomMember::Error::ConsoleIdCollision:
return "ConsoleIdCollision";
case RoomMember::Error::WrongVersion:
return "WrongVersion";
case RoomMember::State::WrongPassword:
case RoomMember::Error::WrongPassword:
return "WrongPassword";
case RoomMember::State::CouldNotConnect:
case RoomMember::Error::CouldNotConnect:
return "CouldNotConnect";
case RoomMember::Error::RoomIsFull:
return "RoomIsFull";
case RoomMember::Error::HostBanned:
return "HostBanned";
case RoomMember::Error::PermissionDenied:
return "PermissionDenied";
case RoomMember::Error::NoSuchUser:
return "NoSuchUser";
}
return "Unknown";
}