From 7acd2664dd046c231fbf5f6b94bbacb169061450 Mon Sep 17 00:00:00 2001 From: zhupengfei Date: Sat, 24 Nov 2018 16:13:46 +0800 Subject: [PATCH] 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. --- src/citra_qt/multiplayer/message.cpp | 5 ++ src/citra_qt/multiplayer/message.h | 3 + src/citra_qt/multiplayer/state.cpp | 90 ++++++++++++-------- src/citra_qt/multiplayer/state.h | 3 + src/network/room_member.cpp | 122 ++++++++++++++++++++++++--- src/network/room_member.h | 84 +++++++++++++++--- 6 files changed, 251 insertions(+), 56 deletions(-) diff --git a/src/citra_qt/multiplayer/message.cpp b/src/citra_qt/multiplayer/message.cpp index e13463a21..36e465acb 100644 --- a/src/citra_qt/multiplayer/message.cpp +++ b/src/citra_qt/multiplayer/message.cpp @@ -36,11 +36,16 @@ const ConnectionError WRONG_PASSWORD(QT_TR_NOOP("Incorrect password.")); const ConnectionError GENERIC_ERROR( QT_TR_NOOP("An unknown error occured. If this error continues to occur, please open an issue")); const ConnectionError LOST_CONNECTION(QT_TR_NOOP("Connection to room lost. Try to reconnect.")); +const ConnectionError HOST_KICKED(QT_TR_NOOP("You have been kicked by the room host.")); const ConnectionError MAC_COLLISION( QT_TR_NOOP("MAC address is already in use. Please choose another.")); const ConnectionError CONSOLE_ID_COLLISION(QT_TR_NOOP( "Your Console ID conflicted with someone else's in the room.\n\nPlease go to Emulation " "> Configure > System to regenerate your Console ID.")); +const ConnectionError PERMISSION_DENIED( + QT_TR_NOOP("You do not have enough permission to perform this action.")); +const ConnectionError NO_SUCH_USER(QT_TR_NOOP( + "The user you are trying to kick/ban could not be found.\nThey may have left the room.")); static bool WarnMessage(const std::string& title, const std::string& text) { return QMessageBox::Ok == QMessageBox::warning(nullptr, QObject::tr(title.c_str()), diff --git a/src/citra_qt/multiplayer/message.h b/src/citra_qt/multiplayer/message.h index cc8e0f4a4..955b90847 100644 --- a/src/citra_qt/multiplayer/message.h +++ b/src/citra_qt/multiplayer/message.h @@ -36,8 +36,11 @@ extern const ConnectionError WRONG_VERSION; extern const ConnectionError WRONG_PASSWORD; extern const ConnectionError GENERIC_ERROR; extern const ConnectionError LOST_CONNECTION; +extern const ConnectionError HOST_KICKED; extern const ConnectionError MAC_COLLISION; extern const ConnectionError CONSOLE_ID_COLLISION; +extern const ConnectionError PERMISSION_DENIED; +extern const ConnectionError NO_SUCH_USER; /** * Shows a standard QMessageBox with a error message diff --git a/src/citra_qt/multiplayer/state.cpp b/src/citra_qt/multiplayer/state.cpp index d819a3d0f..14b2bd7ec 100644 --- a/src/citra_qt/multiplayer/state.cpp +++ b/src/citra_qt/multiplayer/state.cpp @@ -27,9 +27,13 @@ MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_lis [this](const Network::RoomMember::State& state) { emit NetworkStateChanged(state); }); connect(this, &MultiplayerState::NetworkStateChanged, this, &MultiplayerState::OnNetworkStateChanged); + error_callback_handle = member->BindOnError( + [this](const Network::RoomMember::Error& error) { emit NetworkError(error); }); + connect(this, &MultiplayerState::NetworkError, this, &MultiplayerState::OnNetworkError); } qRegisterMetaType(); + qRegisterMetaType(); qRegisterMetaType(); announce_multiplayer_session = std::make_shared(); announce_multiplayer_session->BindErrorCallback( @@ -52,6 +56,12 @@ MultiplayerState::~MultiplayerState() { member->Unbind(state_callback_handle); } } + + if (error_callback_handle) { + if (auto member = Network::GetRoomMember().lock()) { + member->Unbind(error_callback_handle); + } + } } void MultiplayerState::Close() { @@ -88,41 +98,8 @@ void MultiplayerState::retranslateUi() { void MultiplayerState::OnNetworkStateChanged(const Network::RoomMember::State& state) { LOG_DEBUG(Frontend, "Network State: {}", Network::GetStateStr(state)); - bool is_connected = false; - switch (state) { - case Network::RoomMember::State::LostConnection: - NetworkMessage::ShowError(NetworkMessage::LOST_CONNECTION); - break; - case Network::RoomMember::State::CouldNotConnect: - NetworkMessage::ShowError(NetworkMessage::UNABLE_TO_CONNECT); - break; - case Network::RoomMember::State::NameCollision: - NetworkMessage::ShowError(NetworkMessage::USERNAME_NOT_VALID_SERVER); - break; - case Network::RoomMember::State::MacCollision: - NetworkMessage::ShowError(NetworkMessage::MAC_COLLISION); - break; - case Network::RoomMember::State::ConsoleIdCollision: - NetworkMessage::ShowError(NetworkMessage::CONSOLE_ID_COLLISION); - break; - case Network::RoomMember::State::RoomIsFull: - NetworkMessage::ShowError(NetworkMessage::ROOM_IS_FULL); - break; - case Network::RoomMember::State::WrongPassword: - NetworkMessage::ShowError(NetworkMessage::WRONG_PASSWORD); - break; - case Network::RoomMember::State::WrongVersion: - NetworkMessage::ShowError(NetworkMessage::WRONG_VERSION); - break; - case Network::RoomMember::State::Error: - NetworkMessage::ShowError(NetworkMessage::UNABLE_TO_CONNECT); - break; - case Network::RoomMember::State::Joined: - is_connected = true; + if (state == Network::RoomMember::State::Joined) { OnOpenNetworkRoom(); - break; - } - if (is_connected) { status_icon->setPixmap(QIcon::fromTheme("connected").pixmap(16)); status_text->setText(tr("Connected")); leave_room->setEnabled(true); @@ -137,6 +114,51 @@ void MultiplayerState::OnNetworkStateChanged(const Network::RoomMember::State& s current_state = state; } +void MultiplayerState::OnNetworkError(const Network::RoomMember::Error& error) { + LOG_DEBUG(Frontend, "Network Error: {}", Network::GetErrorStr(error)); + switch (error) { + case Network::RoomMember::Error::LostConnection: + NetworkMessage::ShowError(NetworkMessage::LOST_CONNECTION); + break; + case Network::RoomMember::Error::HostKicked: + NetworkMessage::ShowError(NetworkMessage::HOST_KICKED); + break; + case Network::RoomMember::Error::CouldNotConnect: + NetworkMessage::ShowError(NetworkMessage::UNABLE_TO_CONNECT); + break; + case Network::RoomMember::Error::NameCollision: + NetworkMessage::ShowError(NetworkMessage::USERNAME_NOT_VALID_SERVER); + break; + case Network::RoomMember::Error::MacCollision: + NetworkMessage::ShowError(NetworkMessage::MAC_COLLISION); + break; + case Network::RoomMember::Error::ConsoleIdCollision: + NetworkMessage::ShowError(NetworkMessage::CONSOLE_ID_COLLISION); + break; + case Network::RoomMember::Error::RoomIsFull: + NetworkMessage::ShowError(NetworkMessage::ROOM_IS_FULL); + break; + case Network::RoomMember::Error::WrongPassword: + NetworkMessage::ShowError(NetworkMessage::WRONG_PASSWORD); + break; + case Network::RoomMember::Error::WrongVersion: + NetworkMessage::ShowError(NetworkMessage::WRONG_VERSION); + break; + case Network::RoomMember::Error::HostBanned: + NetworkMessage::ShowError(NetworkMessage::HOST_BANNED); + break; + case Network::RoomMember::Error::UnknownError: + NetworkMessage::ShowError(NetworkMessage::UNABLE_TO_CONNECT); + break; + case Network::RoomMember::Error::PermissionDenied: + NetworkMessage::ShowError(NetworkMessage::PERMISSION_DENIED); + break; + case Network::RoomMember::Error::NoSuchUser: + NetworkMessage::ShowError(NetworkMessage::NO_SUCH_USER); + break; + } +} + void MultiplayerState::OnAnnounceFailed(const Common::WebResult& result) { announce_multiplayer_session->Stop(); QMessageBox::warning( diff --git a/src/citra_qt/multiplayer/state.h b/src/citra_qt/multiplayer/state.h index b375a81a6..78a36865b 100644 --- a/src/citra_qt/multiplayer/state.h +++ b/src/citra_qt/multiplayer/state.h @@ -40,6 +40,7 @@ public: public slots: void OnNetworkStateChanged(const Network::RoomMember::State& state); + void OnNetworkError(const Network::RoomMember::Error& error); void OnViewLobby(); void OnCreateRoom(); bool OnCloseRoom(); @@ -50,6 +51,7 @@ public slots: signals: void NetworkStateChanged(const Network::RoomMember::State&); + void NetworkError(const Network::RoomMember::Error&); void AnnounceFailed(const Common::WebResult&); private: @@ -65,6 +67,7 @@ private: std::shared_ptr announce_multiplayer_session; Network::RoomMember::State current_state = Network::RoomMember::State::Uninitialized; Network::RoomMember::CallbackHandle state_callback_handle; + Network::RoomMember::CallbackHandle error_callback_handle; }; Q_DECLARE_METATYPE(Common::WebResult); diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp index 79ba71da8..40d7e7068 100644 --- a/src/network/room_member.cpp +++ b/src/network/room_member.cpp @@ -31,6 +31,7 @@ public: std::atomic 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 callback_set_status_messages; CallbackSet callback_set_room_information; CallbackSet callback_set_state; + CallbackSet callback_set_error; + CallbackSet 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(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(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(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::RoomMemberImpl::Callbacks::Get() { + return callback_set_error; +} + template <> RoomMember::RoomMemberImpl::CallbackSet& RoomMember::RoomMemberImpl::Callbacks::Get() { @@ -400,6 +461,12 @@ RoomMember::RoomMemberImpl::Callbacks::Get() { return callback_set_status_messages; } +template <> +RoomMember::RoomMemberImpl::CallbackSet& +RoomMember::RoomMemberImpl::Callbacks::Get() { + return callback_set_ban_list; +} + template void RoomMember::RoomMemberImpl::Invoke(const T& data) { std::lock_guard 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(type); + packet << nickname; + room_member_impl->Send(std::move(packet)); +} + +void RoomMember::RequestBanList() { + if (!IsConnected()) + return; + + Packet packet; + packet << static_cast(IdModGetBanList); + room_member_impl->Send(std::move(packet)); +} + RoomMember::CallbackHandle RoomMember::BindOnStateChanged( std::function callback) { return room_member_impl->Bind(callback); } +RoomMember::CallbackHandle RoomMember::BindOnError( + std::function callback) { + return room_member_impl->Bind(callback); +} + RoomMember::CallbackHandle RoomMember::BindOnWifiPacketReceived( std::function callback) { return room_member_impl->Bind(callback); @@ -557,6 +652,11 @@ RoomMember::CallbackHandle RoomMember::BindOnStatusMessageRe return room_member_impl->Bind(callback); } +RoomMember::CallbackHandle RoomMember::BindOnBanListReceived( + std::function callback) { + return room_member_impl->Bind(callback); +} + template void RoomMember::Unbind(CallbackHandle handle) { std::lock_guard lock(room_member_impl->callback_mutex); @@ -574,8 +674,10 @@ void RoomMember::Leave() { template void RoomMember::Unbind(CallbackHandle); template void RoomMember::Unbind(CallbackHandle); +template void RoomMember::Unbind(CallbackHandle); template void RoomMember::Unbind(CallbackHandle); template void RoomMember::Unbind(CallbackHandle); template void RoomMember::Unbind(CallbackHandle); +template void RoomMember::Unbind(CallbackHandle); } // namespace Network diff --git a/src/network/room_member.h b/src/network/room_member.h index 5062b225e..65d1c64eb 100644 --- a/src/network/room_member.h +++ b/src/network/room_member.h @@ -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 BindOnStateChanged(std::function 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 BindOnError(std::function 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 BindOnStatusMessageReceived( std::function 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 BindOnBanListReceived( + std::function 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"; }