Big network rewrites

Hook up protocol, lots of cleaning 🧹
This commit is contained in:
Joshua Ashton 2020-07-31 02:53:40 +01:00
parent aaf6abaf07
commit 0e0a8d89ed
16 changed files with 266 additions and 177 deletions

View File

@ -1,6 +1,9 @@
#include "DedicatedServer.h" #include "DedicatedServer.h"
#include "config/ServerProperties.h" #include "config/ServerProperties.h"
#include "Protocol.h"
#include "PacketReader.h"
namespace Feather namespace Feather
{ {
DedicatedServer::DedicatedServer(ServerProperties* properties) : DedicatedServer::DedicatedServer(ServerProperties* properties) :
@ -13,7 +16,17 @@ namespace Feather
while (1) 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) void DedicatedServer::OnClientDisconnect(const Network::TCPClient& client)
{ {
auto [clients, lock] = m_clients.borrow(); auto [clients, lock] = m_clients.borrow();
clients.remove(client); clients.remove_if([&](MinecraftClient& other) { return &other.GetTCPClient() == &client; });
} }
} }

View File

@ -1,7 +1,8 @@
#pragma once #pragma once
#include "network/TCPListener.h" #include "MinecraftClient.h"
#include "network/ServerStatus.h" #include "ServerStatus.h"
#include "network/IListenerInterface.h" #include "network/IListenerInterface.h"
namespace Feather namespace Feather
@ -20,8 +21,8 @@ namespace Feather
private: private:
ServerProperties* m_properties; ServerProperties* m_properties;
Network::TCPListener m_listener; Network::TCPListener m_listener;
Network::ServerStatus m_status; ServerStatus m_status;
LockableList<Network::TCPClient> m_clients; LockableList<MinecraftClient> m_clients;
}; };
} }

18
src/MinecraftClient.cpp Normal file
View File

@ -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());
}
}

24
src/MinecraftClient.h Normal file
View File

@ -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;
};
}

View File

@ -7,7 +7,7 @@
#define VARINT_MAX_SIZE 5 #define VARINT_MAX_SIZE 5
namespace Feather::Network namespace Feather
{ {
class NetworkMessageCounter class NetworkMessageCounter
{ {

View File

@ -1,6 +1,6 @@
#pragma once #pragma once
#include "../Common.h" #include "Common.h"
#include <cstdio> #include <cstdio>
#include <cstdint> #include <cstdint>
@ -8,9 +8,8 @@
using std::string; using std::string;
namespace Feather::Network namespace Feather
{ {
// Class to read packet data, such as is produced by NetworkMessage // Class to read packet data, such as is produced by NetworkMessage
class PacketReader class PacketReader
{ {
@ -20,18 +19,25 @@ namespace Feather::Network
{ {
} }
template <typename T>
inline T Peek()
{
return *reinterpret_cast<const T* const>(&m_data[m_offset]);
}
template <typename T> template <typename T>
inline T Read() inline T Read()
{ {
T value = *reinterpret_cast<const T *const>(&m_data[m_offset]); T value = Peek<T>();
m_offset += sizeof(T); m_offset += sizeof(T);
return value; return value;
} }
inline int ReadVarInt() template <typename T = int32_t>
inline T ReadVarInt()
{ {
int numRead = 0; int32_t numRead = 0;
int result = 0; int32_t result = 0;
uint8_t read; uint8_t read;
do do
{ {
@ -47,7 +53,7 @@ namespace Feather::Network
} }
} while ((read & 0b10000000) != 0); } while ((read & 0b10000000) != 0);
return result; return static_cast<T>(result);
} }
string ReadString() string ReadString()

17
src/PacketTypes.h Normal file
View File

@ -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;
}

112
src/Protocol.cpp Normal file
View File

@ -0,0 +1,112 @@
#include "Protocol.h"
#include "PacketReader.h"
#include "PacketTypes.h"
#include "MinecraftClient.h"
#include <string>
// 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<ServerboundHandholdingPacketId>();
uint8_t legacyId = packet.Peek<uint8_t>();
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<uint16_t>();
// 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<ServerboundStatusPacketId>();
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<int32_t>(json_template.length()));
msg.Finalize();
client.SendMessage(msg);
break;
}
case ServerboundStatusPacketId::Ping:
{
int64_t timestamp = packet.Read<int64_t>();
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;
}
}
}
}

40
src/Protocol.h Normal file
View File

@ -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);
};
}

View File

@ -6,7 +6,7 @@
using string = std::string; using string = std::string;
using stringstream = std::stringstream; using stringstream = std::stringstream;
namespace Feather::Network namespace Feather
{ {
class ServerStatus class ServerStatus
{ {

View File

@ -1,12 +1,13 @@
feather_src = [ feather_src = [
'Main.cpp', 'Main.cpp',
'DedicatedServer.cpp', 'DedicatedServer.cpp',
'MinecraftClient.cpp',
'Protocol.cpp',
'logging/Logger.cpp', 'logging/Logger.cpp',
'network/NetworkManager.cpp', 'network/NetworkManager.cpp',
'network/TCPListener.cpp', 'network/TCPListener.cpp',
'network/Protocol.cpp',
'util/StringUtil.cpp', 'util/StringUtil.cpp',

View File

@ -1,8 +0,0 @@
#pragma once
// Packets from Client -> Server
enum class ServerboundPacketType
{
STATUS_PING_REQUEST = 0,
STATUS_PING = 1
};

View File

@ -1,111 +0,0 @@
#if 0
#include "Protocol.h"
#include "PacketReader.h"
#include "PacketTypes.h"
#include <string>
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<uint16_t>();
// 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<int32_t>(json_template.length()));
m_client->SendPacket(&msg);*/
break;
}
case ServerboundPacketType::STATUS_PING:
int64_t timestamp = packet.Read<int64_t>();
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

View File

@ -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);
};
}

View File

@ -3,6 +3,7 @@
#include "../Lockable.h" #include "../Lockable.h"
#include "TCPListener.h" #include "TCPListener.h"
#include "IListenerInterface.h"
#include "NetworkManager.h" #include "NetworkManager.h"
#include "NetworkMessage.h" #include "NetworkMessage.h"
#include "PacketReader.h" #include "PacketReader.h"
@ -154,7 +155,13 @@ namespace Feather::Network
{ {
} }
const LockableVector<uint8_t>& GetData() const void Write(const uint8_t* data, size_t size)
{
if (bufferevent_write(m_bufferEvent, data, size) != 0)
printf("Fuck!");
}
LockableVector<uint8_t>& GetData()
{ {
return m_data; return m_data;
} }
@ -182,11 +189,16 @@ namespace Feather::Network
{ {
} }
const LockableVector<uint8_t>& TCPClient::GetData() const LockableVector<uint8_t>& TCPClient::GetData()
{ {
return m_client->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) TCPListener::TCPListener(uint16_t port, IListenerInterface* callbacks)
: m_callbacks(callbacks) : m_callbacks(callbacks)
{ {

View File

@ -1,7 +1,6 @@
#pragma once #pragma once
#include "Lockable.h" #include "Lockable.h"
#include "IListenerInterface.h"
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
@ -11,6 +10,7 @@ namespace Feather::Network
{ {
class TCPSocket; class TCPSocket;
class TCPListenerClient; class TCPListenerClient;
class IListenerInterface;
class TCPClient class TCPClient
{ {
@ -19,15 +19,12 @@ namespace Feather::Network
TCPClient(TCPClient&& client); TCPClient(TCPClient&& client);
~TCPClient(); ~TCPClient();
const LockableVector<uint8_t>& GetData() const; LockableVector<uint8_t>& GetData();
void Write(const uint8_t* data, size_t size);
private: private:
std::unique_ptr<TCPListenerClient> m_client; std::unique_ptr<TCPListenerClient> 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 class TCPListener
{ {