From 859be35d54fda177a237e0c24bc1eaca76f1936d Mon Sep 17 00:00:00 2001 From: B3n30 Date: Sun, 9 Jul 2017 15:06:02 +0200 Subject: [PATCH] Network: Send the game title --- src/network/packet.cpp | 10 ++-- src/network/packet.h | 64 +++++------------------- src/network/room.cpp | 94 +++++++++++++++++++++++++---------- src/network/room.h | 7 ++- src/network/room_member.cpp | 97 ++++++++++++++++++++++++++++--------- src/network/room_member.h | 27 +++++++++-- 6 files changed, 185 insertions(+), 114 deletions(-) diff --git a/src/network/packet.cpp b/src/network/packet.cpp index b3a61d824..660e92c0d 100644 --- a/src/network/packet.cpp +++ b/src/network/packet.cpp @@ -13,10 +13,6 @@ namespace Network { -Packet::Packet() : read_pos(0), is_valid(true) {} - -Packet::~Packet() {} - void Packet::Append(const void* in_data, std::size_t size_in_bytes) { if (in_data && (size_in_bytes > 0)) { std::size_t start = data.size(); @@ -39,7 +35,7 @@ void Packet::Clear() { } const void* Packet::GetData() const { - return !data.empty() ? &data[0] : NULL; + return !data.empty() ? &data[0] : nullptr; } void Packet::IgnoreBytes(u32 length) { @@ -54,8 +50,8 @@ bool Packet::EndOfPacket() const { return read_pos >= data.size(); } -Packet::operator BoolType() const { - return is_valid ? &Packet::CheckSize : NULL; +Packet::operator bool() const { + return is_valid ? &Packet::CheckSize : nullptr; } Packet& Packet::operator>>(bool& out_data) { diff --git a/src/network/packet.h b/src/network/packet.h index 6d84cfbac..026271701 100644 --- a/src/network/packet.h +++ b/src/network/packet.h @@ -10,14 +10,11 @@ namespace Network { -/// A class for serialize data for network transfer. It also handles endianess +/// A class that serializes data for network transfer. It also handles endianess class Packet { - /// A bool-like type that cannot be converted to integer or pointer types - typedef bool (Packet::*BoolType)(std::size_t); - public: - Packet(); - ~Packet(); + Packet() = default; + ~Packet() = default; /** * Append data to the end of the packet @@ -64,41 +61,8 @@ public: * @return True if all data was read, false otherwise */ bool EndOfPacket() const; - /** - * Test the validity of the packet, for reading - * This operator allows to test the packet as a boolean - * variable, to check if a reading operation was successful. - * - * A packet will be in an invalid state if it has no more - * data to read. - * - * This behaviour is the same as standard C++ streams. - * - * Usage example: - * @code - * float x; - * packet >> x; - * if (packet) - * { - * // ok, x was extracted successfully - * } - * - * // -- or -- - * - * float x; - * if (packet >> x) - * { - * // ok, x was extracted successfully - * } - * @endcode - * - * Don't focus on the return type, it's equivalent to bool but - * it disallows unwanted implicit conversions to integer or - * pointer types. - * - * @return True if last data extraction from packet was successful - */ - operator BoolType() const; + + explicit operator bool() const; /// Overloads of operator >> to read data from the packet Packet& operator>>(bool& out_data); @@ -135,10 +99,6 @@ public: Packet& operator<<(const std::array& data); private: - /// Disallow comparisons between packets - bool operator==(const Packet& right) const; - bool operator!=(const Packet& right) const; - /** * Check if the packet can extract a given number of bytes * This function updates accordingly the state of the packet. @@ -148,14 +108,14 @@ private: bool CheckSize(std::size_t size); // Member data - std::vector data; ///< Data stored in the packet - std::size_t read_pos; ///< Current reading position in the packet - bool is_valid; ///< Reading state of the packet + std::vector data; ///< Data stored in the packet + std::size_t read_pos = 0; ///< Current reading position in the packet + bool is_valid = true; ///< Reading state of the packet }; template Packet& Packet::operator>>(std::vector& out_data) { - for (u32 i = 0; i < out_data.size(); ++i) { + for (std::size_t i = 0; i < out_data.size(); ++i) { T character = 0; *this >> character; out_data[i] = character; @@ -165,7 +125,7 @@ Packet& Packet::operator>>(std::vector& out_data) { template Packet& Packet::operator>>(std::array& out_data) { - for (u32 i = 0; i < out_data.size(); ++i) { + for (std::size_t i = 0; i < out_data.size(); ++i) { T character = 0; *this >> character; out_data[i] = character; @@ -175,7 +135,7 @@ Packet& Packet::operator>>(std::array& out_data) { template Packet& Packet::operator<<(const std::vector& in_data) { - for (u32 i = 0; i < in_data.size(); ++i) { + for (std::size_t i = 0; i < in_data.size(); ++i) { *this << in_data[i]; } return *this; @@ -183,7 +143,7 @@ Packet& Packet::operator<<(const std::vector& in_data) { template Packet& Packet::operator<<(const std::array& in_data) { - for (u32 i = 0; i < in_data.size(); ++i) { + for (std::size_t i = 0; i < in_data.size(); ++i) { *this << in_data[i]; } return *this; diff --git a/src/network/room.cpp b/src/network/room.cpp index 0fdb5e68f..9af5b4ea0 100644 --- a/src/network/room.cpp +++ b/src/network/room.cpp @@ -85,8 +85,8 @@ public: * The packet has the structure: * ID_ROOM_INFORMATION * room_name - * member_slots: The max number of clients allowed in this room - * num_members: the number of currently joined clients + * member_slots: The max number of clients allowed in this room + * num_members: the number of currently joined clients * This is followed by the following three values for each member: * nickname of that member * mac_address of that member @@ -112,6 +112,12 @@ public: */ void HandleChatPacket(const ENetEvent* event); + /** + * Extracts the game name from a received ENet packet and broadcasts it. + * @param event The ENet event that was received. + */ + void HandleGameNamePacket(const ENetEvent* event); + /** * Removes the client from the members list if it was in it and announces the change * to all other clients. @@ -130,7 +136,9 @@ void Room::RoomImpl::ServerLoop() { case IdJoinRequest: HandleJoinRequest(&event); break; - // TODO(B3N30): Handle the other message types + case IdSetGameName: + HandleGameNamePacket(&event); + break; case IdWifiPacket: HandleWifiPacket(&event); break; @@ -184,7 +192,7 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) { member.nickname = nickname; member.peer = event->peer; - members.push_back(member); + members.push_back(std::move(member)); // Notify everyone that the room information has changed. BroadcastRoomInformation(); @@ -194,22 +202,14 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) { bool Room::RoomImpl::IsValidNickname(const std::string& nickname) const { // A nickname is valid if it is not already taken by anybody else in the room. // TODO(B3N30): Check for empty names, spaces, etc. - for (const Member& member : members) { - if (member.nickname == nickname) { - return false; - } - } - return true; + return std::all_of(members.begin(), members.end(), + [&nickname](const auto& member) { return member.nickname != nickname; }); } bool Room::RoomImpl::IsValidMacAddress(const MacAddress& address) const { // A MAC address is valid if it is not already taken by anybody else in the room. - for (const Member& member : members) { - if (member.mac_address == address) { - return false; - } - } - return true; + return std::all_of(members.begin(), members.end(), + [&address](const auto& member) { return member.mac_address != address; }); } void Room::RoomImpl::SendNameCollision(ENetPeer* client) { @@ -248,7 +248,7 @@ void Room::RoomImpl::BroadcastRoomInformation() { packet << room_information.name; packet << room_information.member_slots; - packet << static_cast(members.size()); + packet << static_cast(members.size()); for (const auto& member : members) { packet << member.nickname; packet << member.mac_address; @@ -262,10 +262,11 @@ void Room::RoomImpl::BroadcastRoomInformation() { } MacAddress Room::RoomImpl::GenerateMacAddress() { - MacAddress result_mac = NintendoOUI; + MacAddress result_mac = + NintendoOUI; // The first three bytes of each MAC address will be the NintendoOUI std::uniform_int_distribution<> dis(0x00, 0xFF); // Random byte between 0 and 0xFF do { - for (int i = 3; i < result_mac.size(); ++i) { + for (size_t i = 3; i < result_mac.size(); ++i) { result_mac[i] = dis(random_gen); } } while (!IsValidMacAddress(result_mac)); @@ -273,9 +274,33 @@ MacAddress Room::RoomImpl::GenerateMacAddress() { } void Room::RoomImpl::HandleWifiPacket(const ENetEvent* event) { - for (auto it = members.begin(); it != members.end(); ++it) { - if (it->peer != event->peer) - enet_peer_send(it->peer, 0, event->packet); + Packet in_packet; + in_packet.Append(event->packet->data, event->packet->dataLength); + in_packet.IgnoreBytes(sizeof(MessageID)); + in_packet.IgnoreBytes(sizeof(u8)); // WifiPacket Type + in_packet.IgnoreBytes(sizeof(u8)); // WifiPacket Channel + in_packet.IgnoreBytes(sizeof(MacAddress)); // WifiPacket Transmitter Address + MacAddress destination_address; + in_packet >> destination_address; + + Packet out_packet; + out_packet.Append(event->packet->data, event->packet->dataLength); + ENetPacket* enet_packet = enet_packet_create(out_packet.GetData(), out_packet.GetDataSize(), + ENET_PACKET_FLAG_RELIABLE); + + if (destination_address == BroadcastMac) { // Send the data to everyone except the sender + for (const auto& member : members) { + if (member.peer != event->peer) + enet_peer_send(member.peer, 0, event->packet); + } + } else { // Send the data only to the destination client + auto member = std::find_if(members.begin(), members.end(), + [destination_address](const Member& member) -> bool { + return member.mac_address == destination_address; + }); + if (member != members.end()) { + enet_peer_send(member->peer, 0, enet_packet); + } } enet_host_flush(server); } @@ -287,7 +312,7 @@ void Room::RoomImpl::HandleChatPacket(const ENetEvent* event) { in_packet.IgnoreBytes(sizeof(MessageID)); std::string message; in_packet >> message; - auto CompareNetworkAddress = [&](const Member member) -> bool { + auto CompareNetworkAddress = [event](const Member member) -> bool { return member.peer == event->peer; }; const auto sending_member = std::find_if(members.begin(), members.end(), CompareNetworkAddress); @@ -309,13 +334,30 @@ void Room::RoomImpl::HandleChatPacket(const ENetEvent* event) { enet_host_flush(server); } +void Room::RoomImpl::HandleGameNamePacket(const ENetEvent* event) { + Packet in_packet; + in_packet.Append(event->packet->data, event->packet->dataLength); + + in_packet.IgnoreBytes(sizeof(MessageID)); + std::string game_name; + in_packet >> game_name; + auto member = + std::find_if(members.begin(), members.end(), + [event](const Member& member) -> bool { return member.peer == event->peer; }); + if (member != members.end()) { + member->game_name = game_name; + BroadcastRoomInformation(); + } +} + void Room::RoomImpl::HandleClientDisconnection(ENetPeer* client) { // Remove the client from the members list. members.erase(std::remove_if(members.begin(), members.end(), - [&](const Member& member) { return member.peer == client; }), + [client](const Member& member) { return member.peer == client; }), members.end()); // Announce the change to all clients. + enet_peer_disconnect(client, 0); BroadcastRoomInformation(); } @@ -327,7 +369,6 @@ Room::~Room() = default; void Room::Create(const std::string& name, const std::string& server_address, u16 server_port) { ENetAddress address; address.host = ENET_HOST_ANY; - enet_address_set_host(&address, server_address.c_str()); address.port = server_port; room_impl->server = enet_host_create(&address, MaxConcurrentConnections, NumChannels, 0, 0); @@ -357,6 +398,9 @@ void Room::Destroy() { } room_impl->room_information = {}; room_impl->server = nullptr; + room_impl->members.clear(); + room_impl->room_information.member_slots = 0; + room_impl->room_information.name.clear(); } } // namespace Network diff --git a/src/network/room.h b/src/network/room.h index ca663058f..82e3dc62c 100644 --- a/src/network/room.h +++ b/src/network/room.h @@ -19,13 +19,16 @@ struct RoomInformation { u32 member_slots; ///< Maximum number of members in this room }; -using MacAddress = std::array; +using MacAddress = std::array; /// A special MAC address that tells the room we're joining to assign us a MAC address /// automatically. const MacAddress NoPreferredMac = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; +// 802.11 broadcast MAC address +constexpr MacAddress BroadcastMac = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + // The different types of messages that can be sent. The first byte of each packet defines the type -typedef uint8_t MessageID; +using MessageID = u8; enum RoomMessageTypes { IdJoinRequest = 1, IdJoinSuccess, diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp index d68bb551d..ec67aa5be 100644 --- a/src/network/room_member.cpp +++ b/src/network/room_member.cpp @@ -74,6 +74,11 @@ public: * @param event The ENet event that was received. */ void HandleChatPacket(const ENetEvent* event); + + /** + * Disconnects the RoomMember from the Room + */ + void Disconnect(); }; // RoomMemberImpl @@ -92,7 +97,8 @@ void RoomMember::RoomMemberImpl::ReceiveLoop() { std::lock_guard lock(network_mutex); ENetEvent event; if (enet_host_service(client, &event, 1000) > 0) { - if (event.type == ENET_EVENT_TYPE_RECEIVE) { + switch (event.type) { + case ENET_EVENT_TYPE_RECEIVE: switch (event.packet->data[0]) { // TODO(B3N30): Handle the other message types case IdChatMessage: @@ -111,25 +117,21 @@ void RoomMember::RoomMemberImpl::ReceiveLoop() { break; case IdNameCollision: SetState(State::NameCollision); - enet_packet_destroy(event.packet); - enet_peer_disconnect(server, 0); - enet_peer_reset(server); - return; break; case IdMacCollision: SetState(State::MacCollision); - enet_packet_destroy(event.packet); - enet_peer_disconnect(server, 0); - enet_peer_reset(server); - return; break; default: break; } enet_packet_destroy(event.packet); + break; + case ENET_EVENT_TYPE_DISCONNECT: + SetState(State::LostConnection); } } } + Disconnect(); }; void RoomMember::RoomMemberImpl::StartLoop() { @@ -137,6 +139,7 @@ void RoomMember::RoomMemberImpl::StartLoop() { } void RoomMember::RoomMemberImpl::Send(Packet& packet) { + std::lock_guard lock(network_mutex); ENetPacket* enetPacket = enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE); enet_peer_send(server, 0, enetPacket); @@ -165,7 +168,7 @@ void RoomMember::RoomMemberImpl::HandleRoomInformationPacket(const ENetEvent* ev room_information.name = info.name; room_information.member_slots = info.member_slots; - uint32_t num_members; + u32 num_members; packet >> num_members; member_information.resize(num_members); @@ -198,7 +201,7 @@ void RoomMember::RoomMemberImpl::HandleWifiPackets(const ENetEvent* event) { packet.IgnoreBytes(sizeof(MessageID)); // Parse the WifiPacket from the BitStream - uint8_t frame_type; + u8 frame_type; packet >> frame_type; WifiPacket::PacketType type = static_cast(frame_type); @@ -207,10 +210,10 @@ void RoomMember::RoomMemberImpl::HandleWifiPackets(const ENetEvent* event) { packet >> wifi_packet.transmitter_address; packet >> wifi_packet.destination_address; - uint32_t data_length; + u32 data_length; packet >> data_length; - std::vector data(data_length); + std::vector data(data_length); packet >> data; wifi_packet.data = std::move(data); @@ -230,6 +233,33 @@ void RoomMember::RoomMemberImpl::HandleChatPacket(const ENetEvent* event) { // TODO(B3N30): Invoke callbacks } +void RoomMember::RoomMemberImpl::Disconnect() { + member_information.clear(); + room_information.member_slots = 0; + room_information.name.clear(); + + if (server) { + enet_peer_disconnect(server, 0); + } else { + return; + } + + ENetEvent event; + while (enet_host_service(client, &event, ConnectionTimeoutMs) > 0) { + switch (event.type) { + case ENET_EVENT_TYPE_RECEIVE: + enet_packet_destroy(event.packet); // Ignore all incoming data + break; + case ENET_EVENT_TYPE_DISCONNECT: + server = nullptr; + return; + } + } + // didn't disconnect gracefully force disconnect + enet_peer_reset(server); + server = nullptr; +} + // RoomMember RoomMember::RoomMember() : room_member_impl{std::make_unique()} { room_member_impl->client = enet_host_create(nullptr, 1, NumChannels, 0, 0); @@ -249,16 +279,36 @@ const RoomMember::MemberList& RoomMember::GetMemberInformation() const { return room_member_impl->member_information; } +const std::string& RoomMember::GetNickname() const { + return room_member_impl->nickname; +} + +const MacAddress& RoomMember::GetMacAddress() const { + if (GetState() == State::Joined) + return room_member_impl->mac_address; + return MacAddress{}; +} + RoomInformation RoomMember::GetRoomInformation() const { return room_member_impl->room_information; } void RoomMember::Join(const std::string& nick, const char* server_addr, u16 server_port, u16 client_port) { + // If the member is connected, kill the connection first + if (room_member_impl->receive_thread && room_member_impl->receive_thread->joinable()) { + room_member_impl->SetState(State::Error); + room_member_impl->receive_thread->join(); + room_member_impl->receive_thread.reset(); + } + // If the thread isn't running but the ptr still exists, reset it + else if (room_member_impl->receive_thread) { + room_member_impl->receive_thread.reset(); + } + ENetAddress address{}; enet_address_set_host(&address, server_addr); address.port = server_port; - room_member_impl->server = enet_host_connect(room_member_impl->client, &address, NumChannels, 0); @@ -286,11 +336,11 @@ bool RoomMember::IsConnected() const { void RoomMember::SendWifiPacket(const WifiPacket& wifi_packet) { Packet packet; packet << static_cast(IdWifiPacket); - packet << static_cast(wifi_packet.type); + packet << static_cast(wifi_packet.type); packet << wifi_packet.channel; packet << wifi_packet.transmitter_address; packet << wifi_packet.destination_address; - packet << static_cast(wifi_packet.data.size()); + packet << static_cast(wifi_packet.data.size()); packet << wifi_packet.data; room_member_impl->Send(packet); } @@ -302,16 +352,17 @@ void RoomMember::SendChatMessage(const std::string& message) { room_member_impl->Send(packet); } +void RoomMember::SendGameName(const std::string& game_name) { + Packet packet; + packet << static_cast(IdSetGameName); + packet << game_name; + room_member_impl->Send(packet); +} + void RoomMember::Leave() { - ASSERT_MSG(room_member_impl->receive_thread != nullptr, "Must be in a room to leave it."); - { - std::lock_guard lock(room_member_impl->network_mutex); - enet_peer_disconnect(room_member_impl->server, 0); - room_member_impl->SetState(State::Idle); - } + room_member_impl->SetState(State::Idle); room_member_impl->receive_thread->join(); room_member_impl->receive_thread.reset(); - enet_peer_reset(room_member_impl->server); } } // namespace Network diff --git a/src/network/room_member.h b/src/network/room_member.h index 693aa4e7f..d874cc5e4 100644 --- a/src/network/room_member.h +++ b/src/network/room_member.h @@ -15,13 +15,13 @@ namespace Network { /// Information about the received WiFi packets. /// Acts as our own 802.11 header. struct WifiPacket { - enum class PacketType { Beacon, Data, Management }; - PacketType type; ///< The type of 802.11 frame, Beacon / Data. - std::vector data; ///< Raw 802.11 frame data, starting at the management frame header - /// for management frames. + enum class PacketType { Beacon, Data, Authentication, AssociationResponse }; + 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. MacAddress transmitter_address; ///< Mac address of the transmitter. MacAddress destination_address; ///< Mac address of the receiver. - uint8_t channel; ///< WiFi channel where this frame was transmitted. + u8 channel; ///< WiFi channel where this frame was transmitted. }; /// Represents a chat message. @@ -70,6 +70,17 @@ public: * Returns information about the members in the room we're currently connected to. */ const MemberList& GetMemberInformation() const; + + /** + * Returns the nickname of the RoomMember. + */ + const std::string& GetNickname() const; + + /** + * Returns the MAC address of the RoomMember. + */ + const MacAddress& GetMacAddress() const; + /** * Returns information about the room we're currently connected to. */ @@ -99,6 +110,12 @@ public: */ void SendChatMessage(const std::string& message); + /** + * Sends the current game name to the room. + * @param game_name The game name. + */ + void SendGameName(const std::string& game_name); + /** * Leaves the current room. */