diff --git a/src/DedicatedServer.cpp b/src/DedicatedServer.cpp index 22fbc27..70a25b0 100644 --- a/src/DedicatedServer.cpp +++ b/src/DedicatedServer.cpp @@ -1,6 +1,9 @@ #include "DedicatedServer.h" #include "config/ServerProperties.h" +#include "Protocol.h" +#include "PacketReader.h" + namespace Feather { DedicatedServer::DedicatedServer(ServerProperties* properties) : @@ -13,7 +16,17 @@ namespace Feather while (1) { - + auto [clients, clientsLock] = m_clients.borrow(); + for (auto& client : clients) + { + Protocol protocol; + auto [data, clientLock] = client.GetTCPClient().GetData().borrow(); + if (data.empty()) + continue; + auto reader = PacketReader(data.data()); + protocol.HandlePacket(client, reader); + data.clear(); + } } } @@ -30,6 +43,6 @@ namespace Feather void DedicatedServer::OnClientDisconnect(const Network::TCPClient& client) { auto [clients, lock] = m_clients.borrow(); - clients.remove(client); + clients.remove_if([&](MinecraftClient& other) { return &other.GetTCPClient() == &client; }); } } diff --git a/src/DedicatedServer.h b/src/DedicatedServer.h index cde7c1b..5a8f2aa 100644 --- a/src/DedicatedServer.h +++ b/src/DedicatedServer.h @@ -1,7 +1,8 @@ #pragma once -#include "network/TCPListener.h" -#include "network/ServerStatus.h" +#include "MinecraftClient.h" +#include "ServerStatus.h" + #include "network/IListenerInterface.h" namespace Feather @@ -20,8 +21,8 @@ namespace Feather private: ServerProperties* m_properties; Network::TCPListener m_listener; - Network::ServerStatus m_status; + ServerStatus m_status; - LockableList m_clients; + LockableList m_clients; }; } diff --git a/src/MinecraftClient.cpp b/src/MinecraftClient.cpp new file mode 100644 index 0000000..ebce558 --- /dev/null +++ b/src/MinecraftClient.cpp @@ -0,0 +1,18 @@ +#include "MinecraftClient.h" + +namespace Feather +{ + MinecraftClient::MinecraftClient(Network::TCPClient&& client) + : m_client (std::move(client)) + { + } + + MinecraftClient::~MinecraftClient() + { + } + + void MinecraftClient::SendMessage(const NetworkMessage& message) + { + m_client.Write(message.GetData(), message.GetDataSize()); + } +} \ No newline at end of file diff --git a/src/MinecraftClient.h b/src/MinecraftClient.h new file mode 100644 index 0000000..8f11ca7 --- /dev/null +++ b/src/MinecraftClient.h @@ -0,0 +1,24 @@ +#pragma once + +#include "network/TCPListener.h" +#include "Protocol.h" +#include "NetworkMessage.h" + +namespace Feather +{ + class MinecraftClient + { + public: + MinecraftClient(Network::TCPClient&& client); + ~MinecraftClient(); + + inline Network::TCPClient& GetTCPClient() { return m_client; } + inline ProtocolContext& GetContext() { return m_context; } + + void SendMessage(const NetworkMessage& message); + + private: + Network::TCPClient m_client; + ProtocolContext m_context; + }; +} \ No newline at end of file diff --git a/src/network/NetworkMessage.h b/src/NetworkMessage.h similarity index 99% rename from src/network/NetworkMessage.h rename to src/NetworkMessage.h index 0df8c82..cf87844 100644 --- a/src/network/NetworkMessage.h +++ b/src/NetworkMessage.h @@ -7,7 +7,7 @@ #define VARINT_MAX_SIZE 5 -namespace Feather::Network +namespace Feather { class NetworkMessageCounter { diff --git a/src/network/PacketReader.h b/src/PacketReader.h similarity index 78% rename from src/network/PacketReader.h rename to src/PacketReader.h index 80c7447..0eb1524 100644 --- a/src/network/PacketReader.h +++ b/src/PacketReader.h @@ -1,6 +1,6 @@ #pragma once -#include "../Common.h" +#include "Common.h" #include #include @@ -8,9 +8,8 @@ using std::string; -namespace Feather::Network +namespace Feather { - // Class to read packet data, such as is produced by NetworkMessage class PacketReader { @@ -20,18 +19,25 @@ namespace Feather::Network { } + template + inline T Peek() + { + return *reinterpret_cast(&m_data[m_offset]); + } + template inline T Read() { - T value = *reinterpret_cast(&m_data[m_offset]); + T value = Peek(); m_offset += sizeof(T); return value; } - inline int ReadVarInt() + template + inline T ReadVarInt() { - int numRead = 0; - int result = 0; + int32_t numRead = 0; + int32_t result = 0; uint8_t read; do { @@ -47,7 +53,7 @@ namespace Feather::Network } } while ((read & 0b10000000) != 0); - return result; + return static_cast(result); } string ReadString() diff --git a/src/PacketTypes.h b/src/PacketTypes.h new file mode 100644 index 0000000..ffb9f82 --- /dev/null +++ b/src/PacketTypes.h @@ -0,0 +1,17 @@ +#pragma once + +namespace Feather +{ + enum class ServerboundHandholdingPacketId : int32_t + { + Handshake = 0, + }; + + enum class ServerboundStatusPacketId : int32_t + { + Request = 0, + Ping = 1 + }; + + constexpr uint8_t LegacyServerListPing = 0xFE; +} \ No newline at end of file diff --git a/src/Protocol.cpp b/src/Protocol.cpp new file mode 100644 index 0000000..4e1b957 --- /dev/null +++ b/src/Protocol.cpp @@ -0,0 +1,112 @@ +#include "Protocol.h" +#include "PacketReader.h" +#include "PacketTypes.h" +#include "MinecraftClient.h" + +#include + +// TODO Remove. +const std::string json_template = +R"({ + "version": { + "name": "1.16.1", + "protocol": 736 + }, + "players": { + "max": 10, + "online": 10, + "sample": [ + { + "name": "thinkofdeath", + "id": "4566e69f-c907-48ee-8d71-d7ba5aa00d20" + } + ] + }, + "description": { + "text": "Hello Nukem!" + } +})"; + +namespace Feather +{ + void Protocol::HandlePacket(MinecraftClient& client, PacketReader& packet) + { + auto& context = client.GetContext(); + switch (context.GetState()) + { + case ProtocolState::Handholding: + { + auto id = packet.ReadVarInt(); + uint8_t legacyId = packet.Peek(); + if (id != ServerboundHandholdingPacketId::Handshake && legacyId != LegacyServerListPing) + { + printf("[Protocol] Client sent packet with non-zero ID 0x%x while state was HANDSHAKING\n", id); + break; + } + + if (id != ServerboundHandholdingPacketId::Handshake) + { + // Legacy server ping. + // TODO: Do server list ping here. + break; + } + + int clientProtocolVersion = packet.ReadVarInt(); + string serverIp = packet.ReadString(); + uint16_t port = packet.Read(); + + // next desired state + ProtocolState intention = (ProtocolState)(packet.ReadVarInt()); + + printf("[Protocol] Client Intention Packet: version=%d, serverIp=%s, port=%u, intention=%d\n", + clientProtocolVersion, + serverIp.c_str(), + port, + intention + ); + + context.SetState(intention); + + break; + } + + case ProtocolState::Status: + { + auto id = packet.ReadVarInt(); + switch (id) + { + case ServerboundStatusPacketId::Request: + { + printf("[Protocol] Client sent STATUS_PING_REQUEST\n"); + + DynamicNetworkMessage msg(VARINT_MAX_SIZE + json_template.length()); + // Packet ID + msg.WriteVarInt(0); + // JSON Contents + msg.WriteString(json_template.c_str(), static_cast(json_template.length())); + msg.Finalize(); + client.SendMessage(msg); + + break; + } + case ServerboundStatusPacketId::Ping: + { + int64_t timestamp = packet.Read(); + printf("[Protocol] Client sent STATUS_PING: %lld\n", timestamp); + + DynamicNetworkMessage msg(VARINT_MAX_SIZE + sizeof(int64_t)); + + msg.WriteVarInt(1); + msg.WriteLong(timestamp); + msg.Finalize(); + + client.SendMessage(msg); + + break; + } + } + break; + } + } + } +} \ No newline at end of file diff --git a/src/Protocol.h b/src/Protocol.h new file mode 100644 index 0000000..ff89185 --- /dev/null +++ b/src/Protocol.h @@ -0,0 +1,40 @@ +#pragma once + +#include "NetworkMessage.h" + +namespace Feather +{ + class PacketReader; + class MinecraftClient; + + enum class ProtocolState + { + Handholding = -1, + Play = 0, + Status = 1, + Login = 2, + }; + + class ProtocolContext + { + public: + inline ProtocolState GetState() const + { + return m_state; + } + + inline void SetState(ProtocolState state) + { + printf("Setting state"); + m_state = state; + } + private: + ProtocolState m_state = ProtocolState::Handholding; + }; + + class Protocol + { + public: + void HandlePacket(MinecraftClient& client, PacketReader& packet); + }; +} \ No newline at end of file diff --git a/src/network/ServerStatus.h b/src/ServerStatus.h similarity index 96% rename from src/network/ServerStatus.h rename to src/ServerStatus.h index b98ba50..8dc3226 100644 --- a/src/network/ServerStatus.h +++ b/src/ServerStatus.h @@ -6,7 +6,7 @@ using string = std::string; using stringstream = std::stringstream; -namespace Feather::Network +namespace Feather { class ServerStatus { diff --git a/src/meson.build b/src/meson.build index f7676ce..10c21d8 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,12 +1,13 @@ feather_src = [ 'Main.cpp', 'DedicatedServer.cpp', + 'MinecraftClient.cpp', + 'Protocol.cpp', 'logging/Logger.cpp', 'network/NetworkManager.cpp', 'network/TCPListener.cpp', - 'network/Protocol.cpp', 'util/StringUtil.cpp', diff --git a/src/network/PacketTypes.h b/src/network/PacketTypes.h deleted file mode 100644 index cb234cf..0000000 --- a/src/network/PacketTypes.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -// Packets from Client -> Server -enum class ServerboundPacketType -{ - STATUS_PING_REQUEST = 0, - STATUS_PING = 1 -}; \ No newline at end of file diff --git a/src/network/Protocol.cpp b/src/network/Protocol.cpp deleted file mode 100644 index 82bf0a0..0000000 --- a/src/network/Protocol.cpp +++ /dev/null @@ -1,111 +0,0 @@ -#if 0 -#include "Protocol.h" -#include "PacketReader.h" -#include "PacketTypes.h" - -#include - -using std::string; - -namespace Feather::Network -{ - void Protocol::HandlePacket(PacketReader& packet) - { - int id = packet.ReadVarInt(); - printf("PacketReader[%u]: ID = %u\n", packet.Length(), id); - - switch (m_state) - { - case ProtocolState::HANDSHAKING: - { - if (id != 0) { - // this could be, for example, Legacy Server List Ping 0xFE - printf("[Protocol] Client sent packet with non-zero ID 0x%x while state was HANDSHAKING\n", id); - break; - } - - int clientProtocolVersion = packet.ReadVarInt(); - string serverIp = packet.ReadString(); - uint16_t port = packet.Read(); - - // next desired state - ProtocolState intention = (ProtocolState)(packet.ReadVarInt()); - - printf("[Protocol] Client Intention Packet: version=%d, serverIp=%s, port=%u, intention=%d\n", - clientProtocolVersion, - serverIp.c_str(), - port, - intention - ); - - SetState(intention); - - break; - } - - case ProtocolState::STATUS: - { - ServerboundPacketType type = (ServerboundPacketType)id; - printf("[Protocol] [STATUS mode] Client sent packet ID %d (%d)\n", id, type); - switch (type) - { - case ServerboundPacketType::STATUS_PING_REQUEST: - { - printf("[Protocol] Client sent STATUS_PING_REQUEST\n"); - - const std::string json_template = -R"({ - "version": { - "name": "1.16.1", - "protocol": 736 - }, - "players": { - "max": 10, - "online": 10, - "sample": [ - { - "name": "thinkofdeath", - "id": "4566e69f-c907-48ee-8d71-d7ba5aa00d20" - } - ] - }, - "description": { - "text": "Hello Nukem!" - } -})"; - - /*DynamicNetworkMessage msg(VARINT_MAX_SIZE + json_template.length()); - // Packet ID - msg.WriteVarInt(0); - // JSON Contents - msg.WriteString(json_template.c_str(), static_cast(json_template.length())); - m_client->SendPacket(&msg);*/ - - break; - } - case ServerboundPacketType::STATUS_PING: - int64_t timestamp = packet.Read(); - printf("[Protocol] Client sent STATUS_PING: %lld\n", timestamp); - - /*DynamicNetworkMessage msg(VARINT_MAX_SIZE + sizeof(int64_t)); - - msg.WriteVarInt(1); - msg.WriteLong(timestamp); - - m_client->SendPacket(&msg);*/ - - break; - } - break; - } - } - } - - void Protocol::SetState(ProtocolState state) - { - printf("[Protocol] Switching state from %d to %d\n", m_state, state); - // TODO: validate state here - m_state = state; - } -} -#endif \ No newline at end of file diff --git a/src/network/Protocol.h b/src/network/Protocol.h deleted file mode 100644 index cd83a7f..0000000 --- a/src/network/Protocol.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include "NetworkMessage.h" -#include "ClientHandle.h" - -namespace Feather::Network -{ - class PacketReader; - - enum class ProtocolState - { - HANDSHAKING = -1, - PLAY = 0, - STATUS = 1, - LOGIN = 2, - }; - - // TODO: This should become abstract so we can support multiple versions - class Protocol - { - private: - ClientHandle* m_client; - ProtocolState m_state = ProtocolState::HANDSHAKING; - - public: - Protocol(ClientHandle* client) : m_client(client) {} - - void HandlePacket(PacketReader& packet); - - protected: - void SetState(ProtocolState state); - }; -} \ No newline at end of file diff --git a/src/network/TCPListener.cpp b/src/network/TCPListener.cpp index fb98e12..7a92417 100644 --- a/src/network/TCPListener.cpp +++ b/src/network/TCPListener.cpp @@ -3,6 +3,7 @@ #include "../Lockable.h" #include "TCPListener.h" +#include "IListenerInterface.h" #include "NetworkManager.h" #include "NetworkMessage.h" #include "PacketReader.h" @@ -154,7 +155,13 @@ namespace Feather::Network { } - const LockableVector& GetData() const + void Write(const uint8_t* data, size_t size) + { + if (bufferevent_write(m_bufferEvent, data, size) != 0) + printf("Fuck!"); + } + + LockableVector& GetData() { return m_data; } @@ -182,11 +189,16 @@ namespace Feather::Network { } - const LockableVector& TCPClient::GetData() const + LockableVector& TCPClient::GetData() { return m_client->GetData(); } + void TCPClient::Write(const uint8_t* data, size_t size) + { + m_client->Write(data, size); + } + TCPListener::TCPListener(uint16_t port, IListenerInterface* callbacks) : m_callbacks(callbacks) { diff --git a/src/network/TCPListener.h b/src/network/TCPListener.h index 7c1085d..81e325a 100644 --- a/src/network/TCPListener.h +++ b/src/network/TCPListener.h @@ -1,7 +1,6 @@ #pragma once #include "Lockable.h" -#include "IListenerInterface.h" #include #include @@ -11,6 +10,7 @@ namespace Feather::Network { class TCPSocket; class TCPListenerClient; + class IListenerInterface; class TCPClient { @@ -19,15 +19,12 @@ namespace Feather::Network TCPClient(TCPClient&& client); ~TCPClient(); - const LockableVector& GetData() const; + LockableVector& GetData(); + void Write(const uint8_t* data, size_t size); private: std::unique_ptr m_client; }; - // TODO: Can we eliminate the move constructor + overloads? - // Kinda unclean. - inline bool operator==(const TCPClient& a, const TCPClient& b) { return &a == &b; } - inline bool operator!=(const TCPClient& a, const TCPClient& b) { return &a != &b; } class TCPListener {