diff --git a/src/network/room.cpp b/src/network/room.cpp index 261049ab0..21ed32d91 100644 --- a/src/network/room.cpp +++ b/src/network/room.cpp @@ -4,8 +4,10 @@ #include #include +#include #include #include +#include #include #include "enet/enet.h" #include "network/packet.h" @@ -13,9 +15,6 @@ namespace Network { -/// Maximum number of concurrent connections allowed to this room. -static constexpr u32 MaxConcurrentConnections = 10; - class Room::RoomImpl { public: // This MAC address is used to generate a 'Nintendo' like Mac address. @@ -27,6 +26,8 @@ public: std::atomic state{State::Closed}; ///< Current state of the room. RoomInformation room_information; ///< Information about this room. + std::string password; ///< The password required to connect to this room. + struct Member { std::string nickname; ///< The nickname of the member. GameInfo game_info; ///< The current game of the member @@ -81,6 +82,11 @@ public: */ void SendVersionMismatch(ENetPeer* client); + /** + * Sends a ID_ROOM_WRONG_PASSWORD message telling the client that the password is wrong. + */ + void SendWrongPassword(ENetPeer* client); + /** * Notifies the member that its connection attempt was successful, * and it is now part of the room. @@ -99,6 +105,8 @@ public: * ID_ROOM_INFORMATION * room_name * member_slots: The max number of clients allowed in this room + * uid + * port * num_members: the number of currently joined clients * This is followed by the following three values for each member: * nickname of that member @@ -136,13 +144,18 @@ public: * to all other clients. */ void HandleClientDisconnection(ENetPeer* client); + + /** + * Creates a random ID in the form 12345678-1234-1234-1234-123456789012 + */ + void CreateUniqueID(); }; // RoomImpl void Room::RoomImpl::ServerLoop() { while (state != State::Closed) { ENetEvent event; - if (enet_host_service(server, &event, 100) > 0) { + if (enet_host_service(server, &event, 50) > 0) { switch (event.type) { case ENET_EVENT_TYPE_RECEIVE: switch (event.packet->data[0]) { @@ -188,6 +201,14 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) { u32 client_version; packet >> client_version; + std::string pass; + packet >> pass; + + if (pass != password) { + SendWrongPassword(event->peer); + return; + } + if (!IsValidNickname(nickname)) { SendNameCollision(event->peer); return; @@ -260,6 +281,16 @@ void Room::RoomImpl::SendMacCollision(ENetPeer* client) { enet_host_flush(server); } +void Room::RoomImpl::SendWrongPassword(ENetPeer* client) { + Packet packet; + packet << static_cast(IdWrongPassword); + + ENetPacket* enet_packet = + enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE); + enet_peer_send(client, 0, enet_packet); + enet_host_flush(server); +} + void Room::RoomImpl::SendVersionMismatch(ENetPeer* client) { Packet packet; packet << static_cast(IdVersionMismatch); @@ -301,6 +332,9 @@ void Room::RoomImpl::BroadcastRoomInformation() { packet << static_cast(IdRoomInformation); packet << room_information.name; packet << room_information.member_slots; + packet << room_information.uid; + packet << room_information.port; + packet << room_information.preferred_game; packet << static_cast(members.size()); { @@ -382,6 +416,9 @@ void Room::RoomImpl::HandleChatPacket(const ENetEvent* event) { return; // Received a chat message from a unknown sender } + // Limit the size of chat messages to MaxMessageSize + message.resize(MaxMessageSize); + Packet out_packet; out_packet << static_cast(IdChatMessage); out_packet << sending_member->nickname; @@ -433,12 +470,28 @@ void Room::RoomImpl::HandleClientDisconnection(ENetPeer* client) { BroadcastRoomInformation(); } +void Room::RoomImpl::CreateUniqueID() { + std::uniform_int_distribution<> dis(0, 9999); + std::ostringstream stream; + stream << std::setfill('0') << std::setw(4) << dis(random_gen); + stream << std::setfill('0') << std::setw(4) << dis(random_gen) << "-"; + stream << std::setfill('0') << std::setw(4) << dis(random_gen) << "-"; + stream << std::setfill('0') << std::setw(4) << dis(random_gen) << "-"; + stream << std::setfill('0') << std::setw(4) << dis(random_gen) << "-"; + stream << std::setfill('0') << std::setw(4) << dis(random_gen); + stream << std::setfill('0') << std::setw(4) << dis(random_gen); + stream << std::setfill('0') << std::setw(4) << dis(random_gen); + room_information.uid = stream.str(); +} + // Room Room::Room() : room_impl{std::make_unique()} {} Room::~Room() = default; -void Room::Create(const std::string& name, const std::string& server_address, u16 server_port) { +bool Room::Create(const std::string& name, const std::string& server_address, u16 server_port, + const std::string& password, const u32 max_connections, + const std::string& preferred_game, u64 preferred_game_id) { ENetAddress address; address.host = ENET_HOST_ANY; if (!server_address.empty()) { @@ -446,13 +499,22 @@ void Room::Create(const std::string& name, const std::string& server_address, u1 } address.port = server_port; - room_impl->server = enet_host_create(&address, MaxConcurrentConnections, NumChannels, 0, 0); - // TODO(B3N30): Allow specifying the maximum number of concurrent connections. + room_impl->server = enet_host_create(&address, max_connections, NumChannels, 0, 0); + if (!room_impl->server) { + return false; + } room_impl->state = State::Open; room_impl->room_information.name = name; - room_impl->room_information.member_slots = MaxConcurrentConnections; + room_impl->room_information.member_slots = max_connections; + room_impl->room_information.port = server_port; + room_impl->room_information.preferred_game = preferred_game; + room_impl->room_information.preferred_game_id = preferred_game_id; + room_impl->password = password; + room_impl->CreateUniqueID(); + room_impl->StartLoop(); + return true; } Room::State Room::GetState() const { @@ -474,7 +536,11 @@ std::vector Room::GetRoomMemberList() const { member_list.push_back(member); } return member_list; -}; +} + +bool Room::HasPassword() const { + return !room_impl->password.empty(); +} void Room::Destroy() { room_impl->state = State::Closed; diff --git a/src/network/room.h b/src/network/room.h index 8285a4d0c..cf7364e24 100644 --- a/src/network/room.h +++ b/src/network/room.h @@ -14,12 +14,22 @@ namespace Network { constexpr u32 network_version = 1; ///< The version of this Room and RoomMember -constexpr u16 DefaultRoomPort = 1234; +constexpr u16 DefaultRoomPort = 24872; + +constexpr u32 MaxMessageSize = 500; + +/// Maximum number of concurrent connections allowed to this room. +static constexpr u32 MaxConcurrentConnections = 254; + constexpr size_t NumChannels = 1; // Number of channels used for the connection struct RoomInformation { - std::string name; ///< Name of the server - u32 member_slots; ///< Maximum number of members in this room + std::string name; ///< Name of the server + u32 member_slots; ///< Maximum number of members in this room + std::string uid; ///< The unique ID of the room + u16 port; ///< The port of this room + std::string preferred_game; ///< Game to advertise that you want to play + u64 preferred_game_id; ///< Title ID for the advertised game }; struct GameInfo { @@ -46,6 +56,7 @@ enum RoomMessageTypes : u8 { IdNameCollision, IdMacCollision, IdVersionMismatch, + IdWrongPassword, IdCloseRoom }; @@ -81,12 +92,19 @@ public: */ std::vector GetRoomMemberList() const; + /** + * Checks if the room is password protected + */ + bool HasPassword() const; + /** * Creates the socket for this room. Will bind to default address if * server is empty string. */ - void Create(const std::string& name, const std::string& server = "", - u16 server_port = DefaultRoomPort); + bool Create(const std::string& name, const std::string& server = "", + u16 server_port = DefaultRoomPort, const std::string& password = "", + const u32 max_connections = MaxConcurrentConnections, + const std::string& preferred_game = "", u64 preferred_game_id = 0); /** * Destroys the socket diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp index f229ec6fd..6ad4dbfa8 100644 --- a/src/network/room_member.cpp +++ b/src/network/room_member.cpp @@ -74,10 +74,12 @@ public: * nickname and preferred mac. * @params nickname The desired nickname. * @params preferred_mac The preferred MAC address to use in the room, the NoPreferredMac tells + * @params password The password for the room * the server to assign one for us. */ void SendJoinRequest(const std::string& nickname, - const MacAddress& preferred_mac = NoPreferredMac); + const MacAddress& preferred_mac = NoPreferredMac, + const std::string& password = ""); /** * Extracts a MAC Address from a received ENet packet. @@ -161,6 +163,9 @@ void RoomMember::RoomMemberImpl::MemberLoop() { case IdVersionMismatch: SetState(State::WrongVersion); break; + case IdWrongPassword: + SetState(State::WrongPassword); + break; case IdCloseRoom: SetState(State::LostConnection); break; @@ -196,12 +201,14 @@ void RoomMember::RoomMemberImpl::Send(Packet&& packet) { } void RoomMember::RoomMemberImpl::SendJoinRequest(const std::string& nickname, - const MacAddress& preferred_mac) { + const MacAddress& preferred_mac, + const std::string& password) { Packet packet; packet << static_cast(IdJoinRequest); packet << nickname; packet << preferred_mac; packet << network_version; + packet << password; Send(std::move(packet)); } @@ -215,8 +222,13 @@ void RoomMember::RoomMemberImpl::HandleRoomInformationPacket(const ENetEvent* ev RoomInformation info{}; packet >> info.name; packet >> info.member_slots; + packet >> info.uid; + packet >> info.port; + packet >> info.preferred_game; room_information.name = info.name; room_information.member_slots = info.member_slots; + room_information.port = info.port; + room_information.preferred_game = info.preferred_game; u32 num_members; packet >> num_members; @@ -260,10 +272,6 @@ void RoomMember::RoomMemberImpl::HandleWifiPackets(const ENetEvent* event) { packet >> wifi_packet.channel; packet >> wifi_packet.transmitter_address; packet >> wifi_packet.destination_address; - - u32 data_length; - packet >> data_length; - packet >> wifi_packet.data; Invoke(wifi_packet); @@ -348,14 +356,13 @@ RoomMember::CallbackHandle RoomMember::RoomMemberImpl::Bind( } // RoomMember -RoomMember::RoomMember() : room_member_impl{std::make_unique()} { - room_member_impl->client = enet_host_create(nullptr, 1, NumChannels, 0, 0); - ASSERT_MSG(room_member_impl->client != nullptr, "Could not create client"); -} +RoomMember::RoomMember() : room_member_impl{std::make_unique()} {} RoomMember::~RoomMember() { ASSERT_MSG(!IsConnected(), "RoomMember is being destroyed while connected"); - enet_host_destroy(room_member_impl->client); + if (room_member_impl->loop_thread) { + Leave(); + } } RoomMember::State RoomMember::GetState() const { @@ -380,18 +387,22 @@ RoomInformation RoomMember::GetRoomInformation() const { } void RoomMember::Join(const std::string& nick, const char* server_addr, u16 server_port, - u16 client_port, const MacAddress& preferred_mac) { + u16 client_port, const MacAddress& preferred_mac, + const std::string& password) { // If the member is connected, kill the connection first if (room_member_impl->loop_thread && room_member_impl->loop_thread->joinable()) { - room_member_impl->SetState(State::Error); - room_member_impl->loop_thread->join(); - room_member_impl->loop_thread.reset(); + Leave(); } // If the thread isn't running but the ptr still exists, reset it else if (room_member_impl->loop_thread) { room_member_impl->loop_thread.reset(); } + if (!room_member_impl->client) { + room_member_impl->client = enet_host_create(nullptr, 1, NumChannels, 0, 0); + ASSERT_MSG(room_member_impl->client != nullptr, "Could not create client"); + } + ENetAddress address{}; enet_address_set_host(&address, server_addr); address.port = server_port; @@ -409,9 +420,10 @@ void RoomMember::Join(const std::string& nick, const char* server_addr, u16 serv room_member_impl->nickname = nick; room_member_impl->SetState(State::Joining); room_member_impl->StartLoop(); - room_member_impl->SendJoinRequest(nick, preferred_mac); + room_member_impl->SendJoinRequest(nick, preferred_mac, password); SendGameInfo(room_member_impl->current_game_info); } else { + enet_peer_disconnect(room_member_impl->server, 0); room_member_impl->SetState(State::CouldNotConnect); } } @@ -480,6 +492,9 @@ void RoomMember::Leave() { room_member_impl->SetState(State::Idle); room_member_impl->loop_thread->join(); room_member_impl->loop_thread.reset(); + + enet_host_destroy(room_member_impl->client); + room_member_impl->client = nullptr; } template void RoomMember::Unbind(CallbackHandle); diff --git a/src/network/room_member.h b/src/network/room_member.h index 98770a234..3ff1535a5 100644 --- a/src/network/room_member.h +++ b/src/network/room_member.h @@ -16,7 +16,13 @@ namespace Network { /// Information about the received WiFi packets. /// Acts as our own 802.11 header. struct WifiPacket { - enum class PacketType : u8 { Beacon, Data, Authentication, AssociationResponse }; + enum class PacketType : u8 { + Beacon, + Data, + Authentication, + AssociationResponse, + Deauthentication + }; PacketType type; ///< The type of 802.11 frame. std::vector data; ///< Raw 802.11 frame data, starting at the management frame header /// for management frames. @@ -49,6 +55,7 @@ public: NameCollision, ///< Somebody is already using this name MacCollision, ///< Somebody is already using that mac-address 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 }; @@ -109,8 +116,8 @@ public: * This may fail if the username is already taken. */ void Join(const std::string& nickname, const char* server_addr = "127.0.0.1", - const u16 serverPort = DefaultRoomPort, const u16 clientPort = 0, - const MacAddress& preferred_mac = NoPreferredMac); + const u16 server_port = DefaultRoomPort, const u16 client_port = 0, + const MacAddress& preferred_mac = NoPreferredMac, const std::string& password = ""); /** * Sends a WiFi packet to the room.