From 589dc083a58425cadd8390ddd81854dcf054dd27 Mon Sep 17 00:00:00 2001 From: B3n30 Date: Sat, 8 Jul 2017 15:24:47 +0200 Subject: [PATCH 01/12] Network: Threads for Room and RoomMember --- src/network/room.cpp | 38 ++++++++++++++++-- src/network/room.h | 14 ++++++- src/network/room_member.cpp | 79 +++++++++++++++++++++++++++++++++---- src/network/room_member.h | 1 - 4 files changed, 119 insertions(+), 13 deletions(-) diff --git a/src/network/room.cpp b/src/network/room.cpp index 48de2f5cb8..274cb41590 100644 --- a/src/network/room.cpp +++ b/src/network/room.cpp @@ -2,6 +2,8 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include +#include #include "enet/enet.h" #include "network/room.h" @@ -16,8 +18,38 @@ public: std::atomic state{State::Closed}; ///< Current state of the room. RoomInformation room_information; ///< Information about this room. + + /// Thread that receives and dispatches network packets + std::unique_ptr room_thread; + + /// Thread function that will receive and dispatch messages until the room is destroyed. + void ServerLoop(); + void StartLoop(); }; +// RoomImpl +void Room::RoomImpl::ServerLoop() { + while (state != State::Closed) { + ENetEvent event; + if (enet_host_service(server, &event, 1000) > 0) { + switch (event.type) { + case ENET_EVENT_TYPE_RECEIVE: + // TODO(B3N30): Select the type of message and handle it + enet_packet_destroy(event.packet); + break; + case ENET_EVENT_TYPE_DISCONNECT: + // TODO(B3N30): Handle the disconnect from a client + break; + } + } + } +} + +void Room::RoomImpl::StartLoop() { + room_thread = std::make_unique(&Room::RoomImpl::ServerLoop, this); +} + +// Room Room::Room() : room_impl{std::make_unique()} {} Room::~Room() = default; @@ -34,8 +66,7 @@ void Room::Create(const std::string& name, const std::string& server_address, u1 room_impl->room_information.name = name; room_impl->room_information.member_slots = MaxConcurrentConnections; - - // TODO(B3N30): Start the receiving thread + room_impl->StartLoop(); } Room::State Room::GetState() const { @@ -48,7 +79,8 @@ const RoomInformation& Room::GetRoomInformation() const { void Room::Destroy() { room_impl->state = State::Closed; - // TODO(B3n30): Join the receiving thread + room_impl->room_thread->join(); + room_impl->room_thread.reset(); if (room_impl->server) { enet_host_destroy(room_impl->server); diff --git a/src/network/room.h b/src/network/room.h index 70c64d5f17..0a6217c11e 100644 --- a/src/network/room.h +++ b/src/network/room.h @@ -4,7 +4,6 @@ #pragma once -#include #include #include #include "common/common_types.h" @@ -19,6 +18,19 @@ struct RoomInformation { u32 member_slots; ///< Maximum number of members in this room }; +// The different types of messages that can be sent. The first byte of each packet defines the type +typedef uint8_t MessageID; +enum RoomMessageTypes { + IdJoinRequest = 1, + IdJoinSuccess, + IdRoomInformation, + IdSetGameName, + IdWifiPacket, + IdChatMessage, + IdNameCollision, + IdMacCollision +}; + /// This is what a server [person creating a server] would use. class Room final { public: diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp index c87f009f43..e1a0dfdabf 100644 --- a/src/network/room_member.cpp +++ b/src/network/room_member.cpp @@ -2,6 +2,9 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include +#include +#include #include "common/assert.h" #include "enet/enet.h" #include "network/room_member.h" @@ -16,10 +19,65 @@ public: ENetPeer* server = nullptr; ///< The server peer the client is connected to std::atomic state{State::Idle}; ///< Current state of the RoomMember. + void SetState(const State new_state); + bool IsConnected() const; std::string nickname; ///< The nickname of this member. + + std::mutex network_mutex; ///< Mutex that controls access to the `client` variable. + /// Thread that receives and dispatches network packets + std::unique_ptr receive_thread; + void ReceiveLoop(); + void StartLoop(); }; +// RoomMemberImpl +void RoomMember::RoomMemberImpl::SetState(const State new_state) { + state = new_state; + // TODO(B3N30): Invoke the callback functions +} + +bool RoomMember::RoomMemberImpl::IsConnected() const { + return state == State::Joining || state == State::Joined; +} + +void RoomMember::RoomMemberImpl::ReceiveLoop() { + // Receive packets while the connection is open + while (IsConnected()) { + 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.packet->data[0]) { + // TODO(B3N30): Handle the other message types + 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); + } + } + } +}; + +void RoomMember::RoomMemberImpl::StartLoop() { + receive_thread = std::make_unique(&RoomMember::RoomMemberImpl::ReceiveLoop, this); +} + +// 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"); @@ -44,7 +102,7 @@ void RoomMember::Join(const std::string& nick, const char* server_addr, u16 serv enet_host_connect(room_member_impl->client, &address, NumChannels, 0); if (!room_member_impl->server) { - room_member_impl->state = State::Error; + room_member_impl->SetState(State::Error); return; } @@ -52,22 +110,27 @@ void RoomMember::Join(const std::string& nick, const char* server_addr, u16 serv int net = enet_host_service(room_member_impl->client, &event, ConnectionTimeoutMs); if (net > 0 && event.type == ENET_EVENT_TYPE_CONNECT) { room_member_impl->nickname = nick; - room_member_impl->state = State::Joining; + room_member_impl->SetState(State::Joining); + room_member_impl->StartLoop(); // TODO(B3N30): Send a join request with the nickname to the server - // TODO(B3N30): Start the receive thread } else { - room_member_impl->state = State::CouldNotConnect; + room_member_impl->SetState(State::CouldNotConnect); } } bool RoomMember::IsConnected() const { - return room_member_impl->state == State::Joining || room_member_impl->state == State::Joined; + return room_member_impl->IsConnected(); } void RoomMember::Leave() { - enet_peer_disconnect(room_member_impl->server, 0); - room_member_impl->state = State::Idle; - // TODO(B3N30): Close the receive thread + 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->receive_thread->join(); + room_member_impl->receive_thread.reset(); enet_peer_reset(room_member_impl->server); } diff --git a/src/network/room_member.h b/src/network/room_member.h index 177622b690..89ec6ae5ad 100644 --- a/src/network/room_member.h +++ b/src/network/room_member.h @@ -4,7 +4,6 @@ #pragma once -#include #include #include #include "common/common_types.h" From 7d9b7394dde4e5e20b72bc0453c6026f43ed8495 Mon Sep 17 00:00:00 2001 From: B3n30 Date: Sat, 8 Jul 2017 15:50:59 +0200 Subject: [PATCH 02/12] Network: Added Packet class for serialization --- src/network/CMakeLists.txt | 2 + src/network/packet.cpp | 229 +++++++++++++++++++++++++++++++++++++ src/network/packet.h | 192 +++++++++++++++++++++++++++++++ 3 files changed, 423 insertions(+) create mode 100644 src/network/packet.cpp create mode 100644 src/network/packet.h diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index aeabe430ea..ac9d028dac 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -1,11 +1,13 @@ set(SRCS network.cpp + packet.cpp room.cpp room_member.cpp ) set(HEADERS network.h + packet.h room.h room_member.h ) diff --git a/src/network/packet.cpp b/src/network/packet.cpp new file mode 100644 index 0000000000..b3a61d824b --- /dev/null +++ b/src/network/packet.cpp @@ -0,0 +1,229 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#ifdef _WIN32 +#include +#else +#include +#endif +#include +#include +#include "network/packet.h" + +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(); + data.resize(start + size_in_bytes); + std::memcpy(&data[start], in_data, size_in_bytes); + } +} + +void Packet::Read(void* out_data, std::size_t size_in_bytes) { + if (out_data && CheckSize(size_in_bytes)) { + std::memcpy(out_data, &data[read_pos], size_in_bytes); + read_pos += size_in_bytes; + } +} + +void Packet::Clear() { + data.clear(); + read_pos = 0; + is_valid = true; +} + +const void* Packet::GetData() const { + return !data.empty() ? &data[0] : NULL; +} + +void Packet::IgnoreBytes(u32 length) { + read_pos += length; +} + +std::size_t Packet::GetDataSize() const { + return data.size(); +} + +bool Packet::EndOfPacket() const { + return read_pos >= data.size(); +} + +Packet::operator BoolType() const { + return is_valid ? &Packet::CheckSize : NULL; +} + +Packet& Packet::operator>>(bool& out_data) { + u8 value; + if (*this >> value) { + out_data = (value != 0); + } + return *this; +} + +Packet& Packet::operator>>(s8& out_data) { + Read(&out_data, sizeof(out_data)); + return *this; +} + +Packet& Packet::operator>>(u8& out_data) { + Read(&out_data, sizeof(out_data)); + return *this; +} + +Packet& Packet::operator>>(s16& out_data) { + s16 value; + Read(&value, sizeof(value)); + out_data = ntohs(value); + return *this; +} + +Packet& Packet::operator>>(u16& out_data) { + u16 value; + Read(&value, sizeof(value)); + out_data = ntohs(value); + return *this; +} + +Packet& Packet::operator>>(s32& out_data) { + s32 value; + Read(&value, sizeof(value)); + out_data = ntohl(value); + return *this; +} + +Packet& Packet::operator>>(u32& out_data) { + u32 value; + Read(&value, sizeof(value)); + out_data = ntohl(value); + return *this; +} + +Packet& Packet::operator>>(float& out_data) { + Read(&out_data, sizeof(out_data)); + return *this; +} + +Packet& Packet::operator>>(double& out_data) { + Read(&out_data, sizeof(out_data)); + return *this; +} + +Packet& Packet::operator>>(char* out_data) { + // First extract string length + u32 length = 0; + *this >> length; + + if ((length > 0) && CheckSize(length)) { + // Then extract characters + std::memcpy(out_data, &data[read_pos], length); + out_data[length] = '\0'; + + // Update reading position + read_pos += length; + } + + return *this; +} + +Packet& Packet::operator>>(std::string& out_data) { + // First extract string length + u32 length = 0; + *this >> length; + + out_data.clear(); + if ((length > 0) && CheckSize(length)) { + // Then extract characters + out_data.assign(&data[read_pos], length); + + // Update reading position + read_pos += length; + } + + return *this; +} + +Packet& Packet::operator<<(bool in_data) { + *this << static_cast(in_data); + return *this; +} + +Packet& Packet::operator<<(s8 in_data) { + Append(&in_data, sizeof(in_data)); + return *this; +} + +Packet& Packet::operator<<(u8 in_data) { + Append(&in_data, sizeof(in_data)); + return *this; +} + +Packet& Packet::operator<<(s16 in_data) { + s16 toWrite = htons(in_data); + Append(&toWrite, sizeof(toWrite)); + return *this; +} + +Packet& Packet::operator<<(u16 in_data) { + u16 toWrite = htons(in_data); + Append(&toWrite, sizeof(toWrite)); + return *this; +} + +Packet& Packet::operator<<(s32 in_data) { + s32 toWrite = htonl(in_data); + Append(&toWrite, sizeof(toWrite)); + return *this; +} + +Packet& Packet::operator<<(u32 in_data) { + u32 toWrite = htonl(in_data); + Append(&toWrite, sizeof(toWrite)); + return *this; +} + +Packet& Packet::operator<<(float in_data) { + Append(&in_data, sizeof(in_data)); + return *this; +} + +Packet& Packet::operator<<(double in_data) { + Append(&in_data, sizeof(in_data)); + return *this; +} + +Packet& Packet::operator<<(const char* in_data) { + // First insert string length + u32 length = std::strlen(in_data); + *this << length; + + // Then insert characters + Append(in_data, length * sizeof(char)); + + return *this; +} + +Packet& Packet::operator<<(const std::string& in_data) { + // First insert string length + u32 length = static_cast(in_data.size()); + *this << length; + + // Then insert characters + if (length > 0) + Append(in_data.c_str(), length * sizeof(std::string::value_type)); + + return *this; +} + +bool Packet::CheckSize(std::size_t size) { + is_valid = is_valid && (read_pos + size <= data.size()); + + return is_valid; +} + +} // namespace Network diff --git a/src/network/packet.h b/src/network/packet.h new file mode 100644 index 0000000000..6d84cfbac0 --- /dev/null +++ b/src/network/packet.h @@ -0,0 +1,192 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include "common/common_types.h" + +namespace Network { + +/// A class for serialize 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(); + + /** + * Append data to the end of the packet + * @param data Pointer to the sequence of bytes to append + * @param size_in_bytes Number of bytes to append + */ + void Append(const void* data, std::size_t size_in_bytes); + + /** + * Reads data from the current read position of the packet + * @param out_data Pointer where the data should get written to + * @param size_in_bytes Number of bytes to read + */ + void Read(void* out_data, std::size_t size_in_bytes); + + /** + * Clear the packet + * After calling Clear, the packet is empty. + */ + void Clear(); + + /** + * Ignores bytes while reading + * @param length THe number of bytes to ignore + */ + void IgnoreBytes(u32 length); + + /** + * Get a pointer to the data contained in the packet + * @return Pointer to the data + */ + const void* GetData() const; + + /** + * This function returns the number of bytes pointed to by + * what getData returns. + * @return Data size, in bytes + */ + std::size_t GetDataSize() const; + + /** + * This function is useful to know if there is some data + * left to be read, without actually reading it. + * @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; + + /// Overloads of operator >> to read data from the packet + Packet& operator>>(bool& out_data); + Packet& operator>>(s8& out_data); + Packet& operator>>(u8& out_data); + Packet& operator>>(s16& out_data); + Packet& operator>>(u16& out_data); + Packet& operator>>(s32& out_data); + Packet& operator>>(u32& out_data); + Packet& operator>>(float& out_data); + Packet& operator>>(double& out_data); + Packet& operator>>(char* out_data); + Packet& operator>>(std::string& out_data); + template + Packet& operator>>(std::vector& out_data); + template + Packet& operator>>(std::array& out_data); + + /// Overloads of operator << to write data into the packet + Packet& operator<<(bool in_data); + Packet& operator<<(s8 in_data); + Packet& operator<<(u8 in_data); + Packet& operator<<(s16 in_data); + Packet& operator<<(u16 in_data); + Packet& operator<<(s32 in_data); + Packet& operator<<(u32 in_data); + Packet& operator<<(float in_data); + Packet& operator<<(double in_data); + Packet& operator<<(const char* in_data); + Packet& operator<<(const std::string& in_data); + template + Packet& operator<<(const std::vector& in_data); + template + 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. + * @param size Size to check + * @return True if size bytes can be read from the packet + */ + 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 +}; + +template +Packet& Packet::operator>>(std::vector& out_data) { + for (u32 i = 0; i < out_data.size(); ++i) { + T character = 0; + *this >> character; + out_data[i] = character; + } + return *this; +} + +template +Packet& Packet::operator>>(std::array& out_data) { + for (u32 i = 0; i < out_data.size(); ++i) { + T character = 0; + *this >> character; + out_data[i] = character; + } + return *this; +} + +template +Packet& Packet::operator<<(const std::vector& in_data) { + for (u32 i = 0; i < in_data.size(); ++i) { + *this << in_data[i]; + } + return *this; +} + +template +Packet& Packet::operator<<(const std::array& in_data) { + for (u32 i = 0; i < in_data.size(); ++i) { + *this << in_data[i]; + } + return *this; +} + +} // namespace Network From 2af9a7146d17e89840c2c9c4f9134c992d27361c Mon Sep 17 00:00:00 2001 From: B3n30 Date: Sat, 8 Jul 2017 16:47:24 +0200 Subject: [PATCH 03/12] Network: Handle join request in Room --- src/network/room.cpp | 200 ++++++++++++++++++++++++++++++++++++++++++- src/network/room.h | 6 ++ 2 files changed, 205 insertions(+), 1 deletion(-) diff --git a/src/network/room.cpp b/src/network/room.cpp index 274cb41590..3502264e1f 100644 --- a/src/network/room.cpp +++ b/src/network/room.cpp @@ -3,8 +3,11 @@ // Refer to the license.txt file included. #include +#include #include +#include #include "enet/enet.h" +#include "network/packet.h" #include "network/room.h" namespace Network { @@ -14,17 +17,87 @@ static constexpr u32 MaxConcurrentConnections = 10; class Room::RoomImpl { public: + // This MAC address is used to generate a 'Nintendo' like Mac address. + const MacAddress NintendoOUI = {0x00, 0x1F, 0x32, 0x00, 0x00, 0x00}; + std::mt19937 random_gen; ///< Random number generator. Used for GenerateMacAddress + ENetHost* server = nullptr; ///< Network interface. std::atomic state{State::Closed}; ///< Current state of the room. RoomInformation room_information; ///< Information about this room. + struct Member { + std::string nickname; ///< The nickname of the member. + std::string game_name; ///< The current game of the member + MacAddress mac_address; ///< The assigned mac address of the member. + ENetPeer* peer; ///< The remote peer. + }; + using MemberList = std::vector; + MemberList members; ///< Information about the members of this room. + + RoomImpl() : random_gen(std::random_device()()) {} + /// Thread that receives and dispatches network packets std::unique_ptr room_thread; /// Thread function that will receive and dispatch messages until the room is destroyed. void ServerLoop(); void StartLoop(); + + /** + * Parses and answers a room join request from a client. + * Validates the uniqueness of the username and assigns the MAC address + * that the client will use for the remainder of the connection. + */ + void HandleJoinRequest(const ENetEvent* event); + + /** + * Returns whether the nickname is valid, ie. isn't already taken by someone else in the room. + */ + bool IsValidNickname(const std::string& nickname) const; + + /** + * Returns whether the MAC address is valid, ie. isn't already taken by someone else in the + * room. + */ + bool IsValidMacAddress(const MacAddress& address) const; + + /** + * Sends a ID_ROOM_NAME_COLLISION message telling the client that the name is invalid. + */ + void SendNameCollision(ENetPeer* client); + + /** + * Sends a ID_ROOM_MAC_COLLISION message telling the client that the MAC is invalid. + */ + void SendMacCollision(ENetPeer* client); + + /** + * Notifies the member that its connection attempt was successful, + * and it is now part of the room. + */ + void SendJoinSuccess(ENetPeer* client, MacAddress mac_address); + + /** + * Sends the information about the room, along with the list of members + * to every connected client in the room. + * 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 + * This is followed by the following three values for each member: + * nickname of that member + * mac_address of that member + * game_name of that member + */ + void BroadcastRoomInformation(); + + /** + * Generates a free MAC address to assign to a new client. + * The first 3 bytes are the NintendoOUI 0x00, 0x1F, 0x32 + */ + MacAddress GenerateMacAddress(); }; // RoomImpl @@ -34,7 +107,12 @@ void Room::RoomImpl::ServerLoop() { if (enet_host_service(server, &event, 1000) > 0) { switch (event.type) { case ENET_EVENT_TYPE_RECEIVE: - // TODO(B3N30): Select the type of message and handle it + switch (event.packet->data[0]) { + case IdJoinRequest: + HandleJoinRequest(&event); + break; + // TODO(B3N30): Handle the other message types + } enet_packet_destroy(event.packet); break; case ENET_EVENT_TYPE_DISCONNECT: @@ -49,6 +127,126 @@ void Room::RoomImpl::StartLoop() { room_thread = std::make_unique(&Room::RoomImpl::ServerLoop, this); } +void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) { + Packet packet; + packet.Append(event->packet->data, event->packet->dataLength); + packet.IgnoreBytes(sizeof(MessageID)); + std::string nickname; + packet >> nickname; + + MacAddress preferred_mac; + packet >> preferred_mac; + + if (!IsValidNickname(nickname)) { + SendNameCollision(event->peer); + return; + } + + if (preferred_mac != NoPreferredMac) { + // Verify if the preferred mac is available + if (!IsValidMacAddress(preferred_mac)) { + SendMacCollision(event->peer); + return; + } + } else { + // Assign a MAC address of this client automatically + preferred_mac = GenerateMacAddress(); + } + + // At this point the client is ready to be added to the room. + Member member{}; + member.mac_address = preferred_mac; + member.nickname = nickname; + member.peer = event->peer; + + members.push_back(member); + + // Notify everyone that the room information has changed. + BroadcastRoomInformation(); + SendJoinSuccess(event->peer, preferred_mac); +} + +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; +} + +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; +} + +void Room::RoomImpl::SendNameCollision(ENetPeer* client) { + Packet packet; + packet << static_cast(IdNameCollision); + + 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::SendMacCollision(ENetPeer* client) { + Packet packet; + packet << static_cast(IdMacCollision); + + 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::SendJoinSuccess(ENetPeer* client, MacAddress mac_address) { + Packet packet; + packet << static_cast(IdJoinSuccess); + packet << mac_address; + 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::BroadcastRoomInformation() { + Packet packet; + packet << static_cast(IdRoomInformation); + packet << room_information.name; + packet << room_information.member_slots; + + packet << static_cast(members.size()); + for (const auto& member : members) { + packet << member.nickname; + packet << member.mac_address; + packet << member.game_name; + } + + ENetPacket* enet_packet = + enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE); + enet_host_broadcast(server, 0, enet_packet); + enet_host_flush(server); +} + +MacAddress Room::RoomImpl::GenerateMacAddress() { + MacAddress result_mac = NintendoOUI; + std::uniform_int_distribution<> dis(0x00, 0xFF); // Random byte between 0 and 0xFF + do { + for (int i = 3; i < result_mac.size(); ++i) { + result_mac[i] = dis(random_gen); + } + } while (!IsValidMacAddress(result_mac)); + return result_mac; +} + // Room Room::Room() : room_impl{std::make_unique()} {} diff --git a/src/network/room.h b/src/network/room.h index 0a6217c11e..ca663058f3 100644 --- a/src/network/room.h +++ b/src/network/room.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include #include "common/common_types.h" @@ -18,6 +19,11 @@ struct RoomInformation { u32 member_slots; ///< Maximum number of members in this room }; +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}; + // The different types of messages that can be sent. The first byte of each packet defines the type typedef uint8_t MessageID; enum RoomMessageTypes { From 77677e205ebf1a6c47114ac1d449fc78be250c6d Mon Sep 17 00:00:00 2001 From: B3n30 Date: Sat, 8 Jul 2017 18:31:35 +0200 Subject: [PATCH 04/12] Network: Send JoinRequest and handle the answer in RoomMember --- src/network/room_member.cpp | 109 +++++++++++++++++++++++++++++++++++- src/network/room_member.h | 18 ++++++ 2 files changed, 125 insertions(+), 2 deletions(-) diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp index e1a0dfdabf..09573ee43a 100644 --- a/src/network/room_member.cpp +++ b/src/network/room_member.cpp @@ -7,6 +7,7 @@ #include #include "common/assert.h" #include "enet/enet.h" +#include "network/packet.h" #include "network/room_member.h" namespace Network { @@ -18,17 +19,49 @@ public: ENetHost* client = nullptr; ///< ENet network interface. ENetPeer* server = nullptr; ///< The server peer the client is connected to + /// Information about the clients connected to the same room as us. + MemberList member_information; + /// Information about the room we're connected to. + RoomInformation room_information; + std::atomic state{State::Idle}; ///< Current state of the RoomMember. void SetState(const State new_state); bool IsConnected() const; - std::string nickname; ///< The nickname of this member. + std::string nickname; ///< The nickname of this member. + MacAddress mac_address; ///< The mac_address of this member. std::mutex network_mutex; ///< Mutex that controls access to the `client` variable. /// Thread that receives and dispatches network packets std::unique_ptr receive_thread; void ReceiveLoop(); void StartLoop(); + + /** + * Sends data to the room. It will be send on channel 0 with flag RELIABLE + * @param packet The data to send + */ + void Send(Packet& packet); + /** + * Sends a request to the server, asking for permission to join a room with the specified + * nickname and preferred mac. + * @params nickname The desired nickname. + * @params preferred_mac The preferred MAC address to use in the room, the NoPreferredMac tells + * the server to assign one for us. + */ + void SendJoinRequest(const std::string& nickname, + const MacAddress& preferred_mac = NoPreferredMac); + + /** + * Extracts a MAC Address from a received ENet packet. + * @param event The ENet event that was received. + */ + void HandleJoinPacket(const ENetEvent* event); + /** + * Extracts RoomInformation and MemberInformation from a received RakNet packet. + * @param event The ENet event that was received. + */ + void HandleRoomInformationPacket(const ENetEvent* event); }; // RoomMemberImpl @@ -50,6 +83,17 @@ void RoomMember::RoomMemberImpl::ReceiveLoop() { if (event.type == ENET_EVENT_TYPE_RECEIVE) { switch (event.packet->data[0]) { // TODO(B3N30): Handle the other message types + case IdRoomInformation: + HandleRoomInformationPacket(&event); + break; + case IdJoinSuccess: + // The join request was successful, we are now in the room. + // If we joined successfully, there must be at least one client in the room: us. + ASSERT_MSG(member_information.size() > 0, + "We have not yet received member information."); + HandleJoinPacket(&event); // Get the MAC Address for the client + SetState(State::Joined); + break; case IdNameCollision: SetState(State::NameCollision); enet_packet_destroy(event.packet); @@ -77,6 +121,59 @@ void RoomMember::RoomMemberImpl::StartLoop() { receive_thread = std::make_unique(&RoomMember::RoomMemberImpl::ReceiveLoop, this); } +void RoomMember::RoomMemberImpl::Send(Packet& packet) { + ENetPacket* enetPacket = + enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE); + enet_peer_send(server, 0, enetPacket); + enet_host_flush(client); +} + +void RoomMember::RoomMemberImpl::SendJoinRequest(const std::string& nickname, + const MacAddress& preferred_mac) { + Packet packet; + packet << static_cast(IdJoinRequest); + packet << nickname; + packet << preferred_mac; + Send(packet); +} + +void RoomMember::RoomMemberImpl::HandleRoomInformationPacket(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(MessageID)); + + RoomInformation info{}; + packet >> info.name; + packet >> info.member_slots; + room_information.name = info.name; + room_information.member_slots = info.member_slots; + + uint32_t num_members; + packet >> num_members; + member_information.resize(num_members); + + for (auto& member : member_information) { + packet >> member.nickname; + packet >> member.mac_address; + packet >> member.game_name; + } + // TODO(B3N30): Invoke callbacks +} + +void RoomMember::RoomMemberImpl::HandleJoinPacket(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(MessageID)); + + // Parse the MAC Address from the BitStream + packet >> mac_address; + // TODO(B3N30): Invoke callbacks +} + // RoomMember RoomMember::RoomMember() : room_member_impl{std::make_unique()} { room_member_impl->client = enet_host_create(nullptr, 1, NumChannels, 0, 0); @@ -92,6 +189,14 @@ RoomMember::State RoomMember::GetState() const { return room_member_impl->state; } +const RoomMember::MemberList& RoomMember::GetMemberInformation() const { + return room_member_impl->member_information; +} + +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) { ENetAddress address{}; @@ -112,7 +217,7 @@ 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(); - // TODO(B3N30): Send a join request with the nickname to the server + room_member_impl->SendJoinRequest(nick); } else { room_member_impl->SetState(State::CouldNotConnect); } diff --git a/src/network/room_member.h b/src/network/room_member.h index 89ec6ae5ad..f8bdbaea83 100644 --- a/src/network/room_member.h +++ b/src/network/room_member.h @@ -6,6 +6,7 @@ #include #include +#include #include "common/common_types.h" #include "network/room.h" @@ -31,6 +32,14 @@ public: CouldNotConnect ///< The room is not responding to a connection attempt }; + struct MemberInformation { + std::string nickname; ///< Nickname of the member. + std::string game_name; ///< Name of the game they're currently playing, or empty if they're + /// not playing anything. + MacAddress mac_address; ///< MAC address associated with this member. + }; + using MemberList = std::vector; + RoomMember(); ~RoomMember(); @@ -39,6 +48,15 @@ public: */ State GetState() const; + /** + * Returns information about the members in the room we're currently connected to. + */ + const MemberList& GetMemberInformation() const; + /** + * Returns information about the room we're currently connected to. + */ + RoomInformation GetRoomInformation() const; + /** * Returns whether we're connected to a server or not. */ From ebff5ba5140f0b093facf95fd5a05e6c71384736 Mon Sep 17 00:00:00 2001 From: B3n30 Date: Sat, 8 Jul 2017 18:42:14 +0200 Subject: [PATCH 05/12] Network: Init Network in SDL and QT --- src/citra/CMakeLists.txt | 2 +- src/citra/emu_window/emu_window_sdl2.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/citra/CMakeLists.txt b/src/citra/CMakeLists.txt index d72d2b5f40..a885f22f8a 100644 --- a/src/citra/CMakeLists.txt +++ b/src/citra/CMakeLists.txt @@ -16,7 +16,7 @@ set(HEADERS create_directory_groups(${SRCS} ${HEADERS}) add_executable(citra ${SRCS} ${HEADERS}) -target_link_libraries(citra PRIVATE common core input_common) +target_link_libraries(citra PRIVATE common core input_common network) target_link_libraries(citra PRIVATE inih glad) if (MSVC) target_link_libraries(citra PRIVATE getopt) diff --git a/src/citra/emu_window/emu_window_sdl2.cpp b/src/citra/emu_window/emu_window_sdl2.cpp index 47aadd60c3..b0f8083997 100644 --- a/src/citra/emu_window/emu_window_sdl2.cpp +++ b/src/citra/emu_window/emu_window_sdl2.cpp @@ -16,6 +16,7 @@ #include "core/settings.h" #include "input_common/keyboard.h" #include "input_common/main.h" +#include "network/network.h" void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) { TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0)); @@ -58,6 +59,7 @@ void EmuWindow_SDL2::OnResize() { EmuWindow_SDL2::EmuWindow_SDL2() { InputCommon::Init(); + Network::Init(); motion_emu = std::make_unique(*this); @@ -116,6 +118,8 @@ EmuWindow_SDL2::~EmuWindow_SDL2() { SDL_GL_DeleteContext(gl_context); SDL_Quit(); motion_emu = nullptr; + + Network::Shutdown(); InputCommon::Shutdown(); } From 641346c15c0091d59259f6acc5f8789efe16c937 Mon Sep 17 00:00:00 2001 From: B3n30 Date: Sun, 9 Jul 2017 10:40:11 +0200 Subject: [PATCH 06/12] Network: Enable to send WifiPackets --- src/network/room.cpp | 19 ++++++++++++++- src/network/room_member.cpp | 46 +++++++++++++++++++++++++++++++++++++ src/network/room_member.h | 18 +++++++++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) diff --git a/src/network/room.cpp b/src/network/room.cpp index 3502264e1f..3caa3aeae7 100644 --- a/src/network/room.cpp +++ b/src/network/room.cpp @@ -98,6 +98,12 @@ public: * The first 3 bytes are the NintendoOUI 0x00, 0x1F, 0x32 */ MacAddress GenerateMacAddress(); + + /** + * Broadcasts this packet to all members except the sender. + * @param event The ENet event containing the data + */ + void HandleWifiPacket(const ENetEvent* event); }; // RoomImpl @@ -111,7 +117,10 @@ void Room::RoomImpl::ServerLoop() { case IdJoinRequest: HandleJoinRequest(&event); break; - // TODO(B3N30): Handle the other message types + // TODO(B3N30): Handle the other message types + case IdWifiPacket: + HandleWifiPacket(&event); + break; } enet_packet_destroy(event.packet); break; @@ -247,6 +256,14 @@ MacAddress Room::RoomImpl::GenerateMacAddress() { return result_mac; } +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); + } + enet_host_flush(server); +} + // Room Room::Room() : room_impl{std::make_unique()} {} diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp index 09573ee43a..f919e4de0b 100644 --- a/src/network/room_member.cpp +++ b/src/network/room_member.cpp @@ -62,6 +62,12 @@ public: * @param event The ENet event that was received. */ void HandleRoomInformationPacket(const ENetEvent* event); + + /** + * Extracts a WifiPacket from a received ENet packet. + * @param event The ENet event that was received. + */ + void HandleWifiPackets(const ENetEvent* event); }; // RoomMemberImpl @@ -174,6 +180,34 @@ void RoomMember::RoomMemberImpl::HandleJoinPacket(const ENetEvent* event) { // TODO(B3N30): Invoke callbacks } +void RoomMember::RoomMemberImpl::HandleWifiPackets(const ENetEvent* event) { + WifiPacket wifi_packet{}; + Packet packet; + packet.Append(event->packet->data, event->packet->dataLength); + + // Ignore the first byte, which is the message id. + packet.IgnoreBytes(sizeof(MessageID)); + + // Parse the WifiPacket from the BitStream + uint8_t frame_type; + packet >> frame_type; + WifiPacket::PacketType type = static_cast(frame_type); + + wifi_packet.type = type; + packet >> wifi_packet.channel; + packet >> wifi_packet.transmitter_address; + packet >> wifi_packet.destination_address; + + uint32_t data_length; + packet >> data_length; + + std::vector data(data_length); + packet >> data; + + wifi_packet.data = std::move(data); + // TODO(B3N30): Invoke callbacks +} + // RoomMember RoomMember::RoomMember() : room_member_impl{std::make_unique()} { room_member_impl->client = enet_host_create(nullptr, 1, NumChannels, 0, 0); @@ -227,6 +261,18 @@ bool RoomMember::IsConnected() const { return room_member_impl->IsConnected(); } +void RoomMember::SendWifiPacket(const WifiPacket& wifi_packet) { + Packet packet; + packet << static_cast(IdWifiPacket); + 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 << wifi_packet.data; + room_member_impl->Send(packet); +} + void RoomMember::Leave() { ASSERT_MSG(room_member_impl->receive_thread != nullptr, "Must be in a room to leave it."); { diff --git a/src/network/room_member.h b/src/network/room_member.h index f8bdbaea83..d23f5d4b62 100644 --- a/src/network/room_member.h +++ b/src/network/room_member.h @@ -12,6 +12,18 @@ 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. + 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. +}; + /** * This is what a client [person joining a server] would use. * It also has to be used if you host a game yourself (You'd create both, a Room and a @@ -69,6 +81,12 @@ public: void Join(const std::string& nickname, const char* server_addr = "127.0.0.1", const u16 serverPort = DefaultRoomPort, const u16 clientPort = 0); + /** + * Sends a WiFi packet to the room. + * @param packet The WiFi packet to send. + */ + void SendWifiPacket(const WifiPacket& packet); + /** * Leaves the current room. */ From 35a0b32553a3bb3a7d66694511350fdc2ef698d9 Mon Sep 17 00:00:00 2001 From: B3n30 Date: Sun, 9 Jul 2017 10:43:53 +0200 Subject: [PATCH 07/12] Network: Handle the disconnect of a client --- src/network/room.cpp | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/network/room.cpp b/src/network/room.cpp index 3caa3aeae7..6dc7db341a 100644 --- a/src/network/room.cpp +++ b/src/network/room.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include #include #include @@ -104,6 +105,12 @@ public: * @param event The ENet event containing the data */ void HandleWifiPacket(const ENetEvent* event); + + /** + * Removes the client from the members list if it was in it and announces the change + * to all other clients. + */ + void HandleClientDisconnection(ENetPeer* client); }; // RoomImpl @@ -125,7 +132,7 @@ void Room::RoomImpl::ServerLoop() { enet_packet_destroy(event.packet); break; case ENET_EVENT_TYPE_DISCONNECT: - // TODO(B3N30): Handle the disconnect from a client + HandleClientDisconnection(event.peer); break; } } @@ -264,6 +271,16 @@ void Room::RoomImpl::HandleWifiPacket(const ENetEvent* event) { enet_host_flush(server); } +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; }), + members.end()); + + // Announce the change to all clients. + BroadcastRoomInformation(); +} + // Room Room::Room() : room_impl{std::make_unique()} {} From 42e57c121896818f9fbde5ddd9b7dbb5b2a267b1 Mon Sep 17 00:00:00 2001 From: B3n30 Date: Sun, 9 Jul 2017 12:26:03 +0200 Subject: [PATCH 08/12] Network: Enable sending and receiving chat messages --- src/network/room.cpp | 38 +++++++++++++++++++++++++++++++++++++ src/network/room_member.cpp | 29 ++++++++++++++++++++++++++++ src/network/room_member.h | 12 ++++++++++++ 3 files changed, 79 insertions(+) diff --git a/src/network/room.cpp b/src/network/room.cpp index 6dc7db341a..0fdb5e68f8 100644 --- a/src/network/room.cpp +++ b/src/network/room.cpp @@ -106,6 +106,12 @@ public: */ void HandleWifiPacket(const ENetEvent* event); + /** + * Extracts a chat entry from a received ENet packet and adds it to the chat queue. + * @param event The ENet event that was received. + */ + void HandleChatPacket(const ENetEvent* event); + /** * Removes the client from the members list if it was in it and announces the change * to all other clients. @@ -128,6 +134,9 @@ void Room::RoomImpl::ServerLoop() { case IdWifiPacket: HandleWifiPacket(&event); break; + case IdChatMessage: + HandleChatPacket(&event); + break; } enet_packet_destroy(event.packet); break; @@ -271,6 +280,35 @@ void Room::RoomImpl::HandleWifiPacket(const ENetEvent* event) { enet_host_flush(server); } +void Room::RoomImpl::HandleChatPacket(const ENetEvent* event) { + Packet in_packet; + in_packet.Append(event->packet->data, event->packet->dataLength); + + in_packet.IgnoreBytes(sizeof(MessageID)); + std::string message; + in_packet >> message; + auto CompareNetworkAddress = [&](const Member member) -> bool { + return member.peer == event->peer; + }; + const auto sending_member = std::find_if(members.begin(), members.end(), CompareNetworkAddress); + if (sending_member == members.end()) { + return; // Received a chat message from a unknown sender + } + + Packet out_packet; + out_packet << static_cast(IdChatMessage); + out_packet << sending_member->nickname; + out_packet << message; + + ENetPacket* enet_packet = enet_packet_create(out_packet.GetData(), out_packet.GetDataSize(), + ENET_PACKET_FLAG_RELIABLE); + for (auto it = members.begin(); it != members.end(); ++it) { + if (it->peer != event->peer) + enet_peer_send(it->peer, 0, enet_packet); + } + enet_host_flush(server); +} + void Room::RoomImpl::HandleClientDisconnection(ENetPeer* client) { // Remove the client from the members list. members.erase(std::remove_if(members.begin(), members.end(), diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp index f919e4de0b..d68bb551d4 100644 --- a/src/network/room_member.cpp +++ b/src/network/room_member.cpp @@ -68,6 +68,12 @@ public: * @param event The ENet event that was received. */ void HandleWifiPackets(const ENetEvent* event); + + /** + * Extracts a chat entry from a received ENet packet and adds it to the chat queue. + * @param event The ENet event that was received. + */ + void HandleChatPacket(const ENetEvent* event); }; // RoomMemberImpl @@ -89,6 +95,9 @@ void RoomMember::RoomMemberImpl::ReceiveLoop() { if (event.type == ENET_EVENT_TYPE_RECEIVE) { switch (event.packet->data[0]) { // TODO(B3N30): Handle the other message types + case IdChatMessage: + HandleChatPacket(&event); + break; case IdRoomInformation: HandleRoomInformationPacket(&event); break; @@ -208,6 +217,19 @@ void RoomMember::RoomMemberImpl::HandleWifiPackets(const ENetEvent* event) { // TODO(B3N30): Invoke callbacks } +void RoomMember::RoomMemberImpl::HandleChatPacket(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(MessageID)); + + ChatEntry chat_entry{}; + packet >> chat_entry.nickname; + packet >> chat_entry.message; + // TODO(B3N30): Invoke callbacks +} + // RoomMember RoomMember::RoomMember() : room_member_impl{std::make_unique()} { room_member_impl->client = enet_host_create(nullptr, 1, NumChannels, 0, 0); @@ -273,6 +295,13 @@ void RoomMember::SendWifiPacket(const WifiPacket& wifi_packet) { room_member_impl->Send(packet); } +void RoomMember::SendChatMessage(const std::string& message) { + Packet packet; + packet << static_cast(IdChatMessage); + packet << message; + room_member_impl->Send(packet); +} + void RoomMember::Leave() { ASSERT_MSG(room_member_impl->receive_thread != nullptr, "Must be in a room to leave it."); { diff --git a/src/network/room_member.h b/src/network/room_member.h index d23f5d4b62..693aa4e7f5 100644 --- a/src/network/room_member.h +++ b/src/network/room_member.h @@ -24,6 +24,12 @@ struct WifiPacket { uint8_t channel; ///< WiFi channel where this frame was transmitted. }; +/// Represents a chat message. +struct ChatEntry { + std::string nickname; ///< Nickname of the client who sent this message. + std::string message; ///< Body of the message. +}; + /** * This is what a client [person joining a server] would use. * It also has to be used if you host a game yourself (You'd create both, a Room and a @@ -87,6 +93,12 @@ public: */ void SendWifiPacket(const WifiPacket& packet); + /** + * Sends a chat message to the room. + * @param message The contents of the message. + */ + void SendChatMessage(const std::string& message); + /** * Leaves the current room. */ From 859be35d54fda177a237e0c24bc1eaca76f1936d Mon Sep 17 00:00:00 2001 From: B3n30 Date: Sun, 9 Jul 2017 15:06:02 +0200 Subject: [PATCH 09/12] 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 b3a61d824b..660e92c0dc 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 6d84cfbac0..026271701b 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 0fdb5e68f8..9af5b4ea07 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 ca663058f3..82e3dc62ce 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 d68bb551d4..ec67aa5be7 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 693aa4e7f5..d874cc5e4d 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. */ From a0626221a52056669c0b6d19b37d4189c1671fb7 Mon Sep 17 00:00:00 2001 From: B3n30 Date: Fri, 14 Jul 2017 09:20:39 +0200 Subject: [PATCH 10/12] Network: Made send async in RoomMember --- src/network/room.cpp | 35 +++++++++++++++++++++--- src/network/room.h | 6 ++++- src/network/room_member.cpp | 53 +++++++++++++++++++++++-------------- src/network/room_member.h | 1 + 4 files changed, 70 insertions(+), 25 deletions(-) diff --git a/src/network/room.cpp b/src/network/room.cpp index 9af5b4ea07..da16793120 100644 --- a/src/network/room.cpp +++ b/src/network/room.cpp @@ -73,6 +73,11 @@ public: */ void SendMacCollision(ENetPeer* client); + /** + * Sends a ID_ROOM_VERSION_MISMATCH message telling the client that the MAC is invalid. + */ + void SendVersionMismatch(ENetPeer* client); + /** * Notifies the member that its connection attempt was successful, * and it is now part of the room. @@ -170,6 +175,9 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) { MacAddress preferred_mac; packet >> preferred_mac; + u32 client_version; + packet >> client_version; + if (!IsValidNickname(nickname)) { SendNameCollision(event->peer); return; @@ -186,6 +194,11 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) { preferred_mac = GenerateMacAddress(); } + if (client_version != network_version) { + SendVersionMismatch(event->peer); + return; + } + // At this point the client is ready to be added to the room. Member member{}; member.mac_address = preferred_mac; @@ -232,6 +245,17 @@ void Room::RoomImpl::SendMacCollision(ENetPeer* client) { enet_host_flush(server); } +void Room::RoomImpl::SendVersionMismatch(ENetPeer* client) { + Packet packet; + packet << static_cast(IdVersionMismatch); + packet << network_version; + + 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::SendJoinSuccess(ENetPeer* client, MacAddress mac_address) { Packet packet; packet << static_cast(IdJoinSuccess); @@ -291,7 +315,7 @@ void Room::RoomImpl::HandleWifiPacket(const ENetEvent* event) { 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); + enet_peer_send(member.peer, 0, enet_packet); } } else { // Send the data only to the destination client auto member = std::find_if(members.begin(), members.end(), @@ -327,9 +351,9 @@ void Room::RoomImpl::HandleChatPacket(const ENetEvent* event) { ENetPacket* enet_packet = enet_packet_create(out_packet.GetData(), out_packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE); - for (auto it = members.begin(); it != members.end(); ++it) { - if (it->peer != event->peer) - enet_peer_send(it->peer, 0, enet_packet); + for (const auto& member : members) { + if (member.peer != event->peer) + enet_peer_send(member.peer, 0, enet_packet); } enet_host_flush(server); } @@ -369,6 +393,9 @@ 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; + if (!server_address.empty()) { + enet_address_set_host(&address, server_address.c_str()); + } address.port = server_port; room_impl->server = enet_host_create(&address, MaxConcurrentConnections, NumChannels, 0, 0); diff --git a/src/network/room.h b/src/network/room.h index 82e3dc62ce..ffa17599d4 100644 --- a/src/network/room.h +++ b/src/network/room.h @@ -11,6 +11,8 @@ namespace Network { +constexpr u32 network_version = 1; ///< The version of this Room and RoomMember + constexpr u16 DefaultRoomPort = 1234; constexpr size_t NumChannels = 1; // Number of channels used for the connection @@ -37,7 +39,9 @@ enum RoomMessageTypes { IdWifiPacket, IdChatMessage, IdNameCollision, - IdMacCollision + IdMacCollision, + IdVersionMismatch, + IdCloseRoom }; /// This is what a server [person creating a server] would use. diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp index ec67aa5be7..f6f8b0475c 100644 --- a/src/network/room_member.cpp +++ b/src/network/room_member.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include +#include #include #include #include "common/assert.h" @@ -33,8 +34,10 @@ public: std::mutex network_mutex; ///< Mutex that controls access to the `client` variable. /// Thread that receives and dispatches network packets - std::unique_ptr receive_thread; - void ReceiveLoop(); + std::unique_ptr loop_thread; + std::mutex send_list_mutex; ///< Mutex that controls access to the `send_list` variable. + std::list send_list; ///< A list that stores all packets to send the async + void MemberLoop(); void StartLoop(); /** @@ -91,7 +94,7 @@ bool RoomMember::RoomMemberImpl::IsConnected() const { return state == State::Joining || state == State::Joined; } -void RoomMember::RoomMemberImpl::ReceiveLoop() { +void RoomMember::RoomMemberImpl::MemberLoop() { // Receive packets while the connection is open while (IsConnected()) { std::lock_guard lock(network_mutex); @@ -121,6 +124,9 @@ void RoomMember::RoomMemberImpl::ReceiveLoop() { case IdMacCollision: SetState(State::MacCollision); break; + case IdVersionMismatch: + SetState(State::WrongVersion); + break; default: break; } @@ -128,22 +134,30 @@ void RoomMember::RoomMemberImpl::ReceiveLoop() { break; case ENET_EVENT_TYPE_DISCONNECT: SetState(State::LostConnection); + break; } } + { + std::lock_guard lock(send_list_mutex); + for (const auto& packet : send_list) { + ENetPacket* enetPacket = enet_packet_create(packet.GetData(), packet.GetDataSize(), + ENET_PACKET_FLAG_RELIABLE); + enet_peer_send(server, 0, enetPacket); + } + enet_host_flush(client); + send_list.clear(); + } } Disconnect(); }; void RoomMember::RoomMemberImpl::StartLoop() { - receive_thread = std::make_unique(&RoomMember::RoomMemberImpl::ReceiveLoop, this); + loop_thread = std::make_unique(&RoomMember::RoomMemberImpl::MemberLoop, this); } 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); - enet_host_flush(client); + std::lock_guard lock(send_list_mutex); + send_list.push_back(std::move(packet)); } void RoomMember::RoomMemberImpl::SendJoinRequest(const std::string& nickname, @@ -152,6 +166,7 @@ void RoomMember::RoomMemberImpl::SendJoinRequest(const std::string& nickname, packet << static_cast(IdJoinRequest); packet << nickname; packet << preferred_mac; + packet << network_version; Send(packet); } @@ -238,11 +253,9 @@ void RoomMember::RoomMemberImpl::Disconnect() { room_information.member_slots = 0; room_information.name.clear(); - if (server) { - enet_peer_disconnect(server, 0); - } else { + if (!server) return; - } + enet_peer_disconnect(server, 0); ENetEvent event; while (enet_host_service(client, &event, ConnectionTimeoutMs) > 0) { @@ -296,14 +309,14 @@ RoomInformation RoomMember::GetRoomInformation() const { 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()) { + if (room_member_impl->loop_thread && room_member_impl->loop_thread->joinable()) { room_member_impl->SetState(State::Error); - room_member_impl->receive_thread->join(); - room_member_impl->receive_thread.reset(); + room_member_impl->loop_thread->join(); + room_member_impl->loop_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(); + else if (room_member_impl->loop_thread) { + room_member_impl->loop_thread.reset(); } ENetAddress address{}; @@ -361,8 +374,8 @@ void RoomMember::SendGameName(const std::string& game_name) { void RoomMember::Leave() { room_member_impl->SetState(State::Idle); - room_member_impl->receive_thread->join(); - room_member_impl->receive_thread.reset(); + room_member_impl->loop_thread->join(); + room_member_impl->loop_thread.reset(); } } // namespace Network diff --git a/src/network/room_member.h b/src/network/room_member.h index d874cc5e4d..6522f053cf 100644 --- a/src/network/room_member.h +++ b/src/network/room_member.h @@ -47,6 +47,7 @@ public: // Reasons why connection was rejected 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 CouldNotConnect ///< The room is not responding to a connection attempt }; From 253d3dd3d889eb61131810b04137ee3f9445db64 Mon Sep 17 00:00:00 2001 From: B3n30 Date: Sat, 15 Jul 2017 11:39:27 +0200 Subject: [PATCH 11/12] Network: Propagate Room closing to connected members --- src/network/room.cpp | 21 +++++++++++++++++++++ src/network/room_member.cpp | 7 +++++-- src/network/room_member.h | 3 ++- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/network/room.cpp b/src/network/room.cpp index da16793120..3f72d7cbe6 100644 --- a/src/network/room.cpp +++ b/src/network/room.cpp @@ -84,6 +84,11 @@ public: */ void SendJoinSuccess(ENetPeer* client, MacAddress mac_address); + /** + * Notifies the members that the room is closed, + */ + void SendCloseMessage(); + /** * Sends the information about the room, along with the list of members * to every connected client in the room. @@ -159,6 +164,8 @@ void Room::RoomImpl::ServerLoop() { } } } + // Close the connection to all members: + SendCloseMessage(); } void Room::RoomImpl::StartLoop() { @@ -266,6 +273,20 @@ void Room::RoomImpl::SendJoinSuccess(ENetPeer* client, MacAddress mac_address) { enet_host_flush(server); } +void Room::RoomImpl::SendCloseMessage() { + Packet packet; + packet << static_cast(IdCloseRoom); + ENetPacket* enet_packet = + enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE); + for (auto& member : members) { + enet_peer_send(member.peer, 0, enet_packet); + } + enet_host_flush(server); + for (auto& member : members) { + enet_peer_disconnect(member.peer, 0); + } +} + void Room::RoomImpl::BroadcastRoomInformation() { Packet packet; packet << static_cast(IdRoomInformation); diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp index f6f8b0475c..8fd226ba57 100644 --- a/src/network/room_member.cpp +++ b/src/network/room_member.cpp @@ -127,6 +127,9 @@ void RoomMember::RoomMemberImpl::MemberLoop() { case IdVersionMismatch: SetState(State::WrongVersion); break; + case IdCloseRoom: + SetState(State::LostConnection); + break; default: break; } @@ -307,7 +310,7 @@ RoomInformation RoomMember::GetRoomInformation() const { } void RoomMember::Join(const std::string& nick, const char* server_addr, u16 server_port, - u16 client_port) { + u16 client_port, const MacAddress& preferred_mac) { // 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); @@ -336,7 +339,7 @@ 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); + room_member_impl->SendJoinRequest(nick, preferred_mac); } else { room_member_impl->SetState(State::CouldNotConnect); } diff --git a/src/network/room_member.h b/src/network/room_member.h index 6522f053cf..fce608c828 100644 --- a/src/network/room_member.h +++ b/src/network/room_member.h @@ -97,7 +97,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 u16 serverPort = DefaultRoomPort, const u16 clientPort = 0, + const MacAddress& preferred_mac = NoPreferredMac); /** * Sends a WiFi packet to the room. From 77df82f5d66683f4928c4ad37f1deb77b79bb7df Mon Sep 17 00:00:00 2001 From: B3n30 Date: Sat, 15 Jul 2017 21:24:11 +0200 Subject: [PATCH 12/12] Network: Changed timeout for receiving packets to 100ms --- src/network/packet.h | 10 +++++++ src/network/room.cpp | 26 +++++++++---------- src/network/room.h | 3 +-- src/network/room_member.cpp | 52 ++++++++++++++++++------------------- src/network/room_member.h | 2 +- 5 files changed, 50 insertions(+), 43 deletions(-) diff --git a/src/network/packet.h b/src/network/packet.h index 026271701b..94b351ab12 100644 --- a/src/network/packet.h +++ b/src/network/packet.h @@ -115,6 +115,12 @@ private: template Packet& Packet::operator>>(std::vector& out_data) { + // First extract the size + u32 size = 0; + *this >> size; + out_data.resize(size); + + // Then extract the data for (std::size_t i = 0; i < out_data.size(); ++i) { T character = 0; *this >> character; @@ -135,6 +141,10 @@ Packet& Packet::operator>>(std::array& out_data) { template Packet& Packet::operator<<(const std::vector& in_data) { + // First insert the size + *this << static_cast(in_data.size()); + + // Then insert the data for (std::size_t i = 0; i < in_data.size(); ++i) { *this << in_data[i]; } diff --git a/src/network/room.cpp b/src/network/room.cpp index 3f72d7cbe6..8b7915bb70 100644 --- a/src/network/room.cpp +++ b/src/network/room.cpp @@ -74,7 +74,7 @@ public: void SendMacCollision(ENetPeer* client); /** - * Sends a ID_ROOM_VERSION_MISMATCH message telling the client that the MAC is invalid. + * Sends a ID_ROOM_VERSION_MISMATCH message telling the client that the version is invalid. */ void SendVersionMismatch(ENetPeer* client); @@ -139,7 +139,7 @@ public: void Room::RoomImpl::ServerLoop() { while (state != State::Closed) { ENetEvent event; - if (enet_host_service(server, &event, 1000) > 0) { + if (enet_host_service(server, &event, 100) > 0) { switch (event.type) { case ENET_EVENT_TYPE_RECEIVE: switch (event.packet->data[0]) { @@ -175,7 +175,7 @@ void Room::RoomImpl::StartLoop() { void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) { Packet packet; packet.Append(event->packet->data, event->packet->dataLength); - packet.IgnoreBytes(sizeof(MessageID)); + packet.IgnoreBytes(sizeof(u8)); // Igonore the message type std::string nickname; packet >> nickname; @@ -234,7 +234,7 @@ bool Room::RoomImpl::IsValidMacAddress(const MacAddress& address) const { void Room::RoomImpl::SendNameCollision(ENetPeer* client) { Packet packet; - packet << static_cast(IdNameCollision); + packet << static_cast(IdNameCollision); ENetPacket* enet_packet = enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE); @@ -244,7 +244,7 @@ void Room::RoomImpl::SendNameCollision(ENetPeer* client) { void Room::RoomImpl::SendMacCollision(ENetPeer* client) { Packet packet; - packet << static_cast(IdMacCollision); + packet << static_cast(IdMacCollision); ENetPacket* enet_packet = enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE); @@ -254,7 +254,7 @@ void Room::RoomImpl::SendMacCollision(ENetPeer* client) { void Room::RoomImpl::SendVersionMismatch(ENetPeer* client) { Packet packet; - packet << static_cast(IdVersionMismatch); + packet << static_cast(IdVersionMismatch); packet << network_version; ENetPacket* enet_packet = @@ -265,7 +265,7 @@ void Room::RoomImpl::SendVersionMismatch(ENetPeer* client) { void Room::RoomImpl::SendJoinSuccess(ENetPeer* client, MacAddress mac_address) { Packet packet; - packet << static_cast(IdJoinSuccess); + packet << static_cast(IdJoinSuccess); packet << mac_address; ENetPacket* enet_packet = enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE); @@ -275,7 +275,7 @@ void Room::RoomImpl::SendJoinSuccess(ENetPeer* client, MacAddress mac_address) { void Room::RoomImpl::SendCloseMessage() { Packet packet; - packet << static_cast(IdCloseRoom); + packet << static_cast(IdCloseRoom); ENetPacket* enet_packet = enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE); for (auto& member : members) { @@ -289,7 +289,7 @@ void Room::RoomImpl::SendCloseMessage() { void Room::RoomImpl::BroadcastRoomInformation() { Packet packet; - packet << static_cast(IdRoomInformation); + packet << static_cast(IdRoomInformation); packet << room_information.name; packet << room_information.member_slots; @@ -321,7 +321,7 @@ MacAddress Room::RoomImpl::GenerateMacAddress() { void Room::RoomImpl::HandleWifiPacket(const ENetEvent* event) { Packet in_packet; in_packet.Append(event->packet->data, event->packet->dataLength); - in_packet.IgnoreBytes(sizeof(MessageID)); + in_packet.IgnoreBytes(sizeof(u8)); // Message type in_packet.IgnoreBytes(sizeof(u8)); // WifiPacket Type in_packet.IgnoreBytes(sizeof(u8)); // WifiPacket Channel in_packet.IgnoreBytes(sizeof(MacAddress)); // WifiPacket Transmitter Address @@ -354,7 +354,7 @@ void Room::RoomImpl::HandleChatPacket(const ENetEvent* event) { Packet in_packet; in_packet.Append(event->packet->data, event->packet->dataLength); - in_packet.IgnoreBytes(sizeof(MessageID)); + in_packet.IgnoreBytes(sizeof(u8)); // Igonore the message type std::string message; in_packet >> message; auto CompareNetworkAddress = [event](const Member member) -> bool { @@ -366,7 +366,7 @@ void Room::RoomImpl::HandleChatPacket(const ENetEvent* event) { } Packet out_packet; - out_packet << static_cast(IdChatMessage); + out_packet << static_cast(IdChatMessage); out_packet << sending_member->nickname; out_packet << message; @@ -383,7 +383,7 @@ void Room::RoomImpl::HandleGameNamePacket(const ENetEvent* event) { Packet in_packet; in_packet.Append(event->packet->data, event->packet->dataLength); - in_packet.IgnoreBytes(sizeof(MessageID)); + in_packet.IgnoreBytes(sizeof(u8)); // Igonore the message type std::string game_name; in_packet >> game_name; auto member = diff --git a/src/network/room.h b/src/network/room.h index ffa17599d4..54cccf0ae3 100644 --- a/src/network/room.h +++ b/src/network/room.h @@ -30,8 +30,7 @@ const MacAddress NoPreferredMac = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; 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 -using MessageID = u8; -enum RoomMessageTypes { +enum RoomMessageTypes : u8 { IdJoinRequest = 1, IdJoinSuccess, IdRoomInformation, diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp index 8fd226ba57..dac9bacaeb 100644 --- a/src/network/room_member.cpp +++ b/src/network/room_member.cpp @@ -38,13 +38,15 @@ public: std::mutex send_list_mutex; ///< Mutex that controls access to the `send_list` variable. std::list send_list; ///< A list that stores all packets to send the async void MemberLoop(); + void StartLoop(); /** * Sends data to the room. It will be send on channel 0 with flag RELIABLE * @param packet The data to send */ - void Send(Packet& packet); + void Send(Packet&& packet); + /** * Sends a request to the server, asking for permission to join a room with the specified * nickname and preferred mac. @@ -99,11 +101,13 @@ void RoomMember::RoomMemberImpl::MemberLoop() { while (IsConnected()) { std::lock_guard lock(network_mutex); ENetEvent event; - if (enet_host_service(client, &event, 1000) > 0) { + if (enet_host_service(client, &event, 100) > 0) { switch (event.type) { case ENET_EVENT_TYPE_RECEIVE: switch (event.packet->data[0]) { - // TODO(B3N30): Handle the other message types + case IdWifiPacket: + HandleWifiPackets(&event); + break; case IdChatMessage: HandleChatPacket(&event); break; @@ -130,8 +134,6 @@ void RoomMember::RoomMemberImpl::MemberLoop() { case IdCloseRoom: SetState(State::LostConnection); break; - default: - break; } enet_packet_destroy(event.packet); break; @@ -158,7 +160,7 @@ void RoomMember::RoomMemberImpl::StartLoop() { loop_thread = std::make_unique(&RoomMember::RoomMemberImpl::MemberLoop, this); } -void RoomMember::RoomMemberImpl::Send(Packet& packet) { +void RoomMember::RoomMemberImpl::Send(Packet&& packet) { std::lock_guard lock(send_list_mutex); send_list.push_back(std::move(packet)); } @@ -166,11 +168,11 @@ void RoomMember::RoomMemberImpl::Send(Packet& packet) { void RoomMember::RoomMemberImpl::SendJoinRequest(const std::string& nickname, const MacAddress& preferred_mac) { Packet packet; - packet << static_cast(IdJoinRequest); + packet << static_cast(IdJoinRequest); packet << nickname; packet << preferred_mac; packet << network_version; - Send(packet); + Send(std::move(packet)); } void RoomMember::RoomMemberImpl::HandleRoomInformationPacket(const ENetEvent* event) { @@ -178,7 +180,7 @@ void RoomMember::RoomMemberImpl::HandleRoomInformationPacket(const ENetEvent* ev packet.Append(event->packet->data, event->packet->dataLength); // Ignore the first byte, which is the message id. - packet.IgnoreBytes(sizeof(MessageID)); + packet.IgnoreBytes(sizeof(u8)); // Igonore the message type RoomInformation info{}; packet >> info.name; @@ -203,9 +205,9 @@ void RoomMember::RoomMemberImpl::HandleJoinPacket(const ENetEvent* event) { packet.Append(event->packet->data, event->packet->dataLength); // Ignore the first byte, which is the message id. - packet.IgnoreBytes(sizeof(MessageID)); + packet.IgnoreBytes(sizeof(u8)); // Igonore the message type - // Parse the MAC Address from the BitStream + // Parse the MAC Address from the packet packet >> mac_address; // TODO(B3N30): Invoke callbacks } @@ -216,9 +218,9 @@ void RoomMember::RoomMemberImpl::HandleWifiPackets(const ENetEvent* event) { packet.Append(event->packet->data, event->packet->dataLength); // Ignore the first byte, which is the message id. - packet.IgnoreBytes(sizeof(MessageID)); + packet.IgnoreBytes(sizeof(u8)); // Igonore the message type - // Parse the WifiPacket from the BitStream + // Parse the WifiPacket from the packet u8 frame_type; packet >> frame_type; WifiPacket::PacketType type = static_cast(frame_type); @@ -231,10 +233,8 @@ void RoomMember::RoomMemberImpl::HandleWifiPackets(const ENetEvent* event) { u32 data_length; packet >> data_length; - std::vector data(data_length); - packet >> data; + packet >> wifi_packet.data; - wifi_packet.data = std::move(data); // TODO(B3N30): Invoke callbacks } @@ -243,7 +243,7 @@ void RoomMember::RoomMemberImpl::HandleChatPacket(const ENetEvent* event) { packet.Append(event->packet->data, event->packet->dataLength); // Ignore the first byte, which is the message id. - packet.IgnoreBytes(sizeof(MessageID)); + packet.IgnoreBytes(sizeof(u8)); ChatEntry chat_entry{}; packet >> chat_entry.nickname; @@ -300,9 +300,8 @@ const std::string& RoomMember::GetNickname() const { } const MacAddress& RoomMember::GetMacAddress() const { - if (GetState() == State::Joined) - return room_member_impl->mac_address; - return MacAddress{}; + ASSERT_MSG(IsConnected(), "Tried to get MAC address while not connected"); + return room_member_impl->mac_address; } RoomInformation RoomMember::GetRoomInformation() const { @@ -351,28 +350,27 @@ bool RoomMember::IsConnected() const { void RoomMember::SendWifiPacket(const WifiPacket& wifi_packet) { Packet packet; - packet << static_cast(IdWifiPacket); + packet << static_cast(IdWifiPacket); 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 << wifi_packet.data; - room_member_impl->Send(packet); + room_member_impl->Send(std::move(packet)); } void RoomMember::SendChatMessage(const std::string& message) { Packet packet; - packet << static_cast(IdChatMessage); + packet << static_cast(IdChatMessage); packet << message; - room_member_impl->Send(packet); + room_member_impl->Send(std::move(packet)); } void RoomMember::SendGameName(const std::string& game_name) { Packet packet; - packet << static_cast(IdSetGameName); + packet << static_cast(IdSetGameName); packet << game_name; - room_member_impl->Send(packet); + room_member_impl->Send(std::move(packet)); } void RoomMember::Leave() { diff --git a/src/network/room_member.h b/src/network/room_member.h index fce608c828..bc1af3a7e8 100644 --- a/src/network/room_member.h +++ b/src/network/room_member.h @@ -15,7 +15,7 @@ namespace Network { /// Information about the received WiFi packets. /// Acts as our own 802.11 header. struct WifiPacket { - enum class PacketType { Beacon, Data, Authentication, AssociationResponse }; + enum class PacketType : u8 { 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.