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

View File

@ -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<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
namespace Feather::Network
namespace Feather
{
class NetworkMessageCounter
{

View File

@ -1,6 +1,6 @@
#pragma once
#include "../Common.h"
#include "Common.h"
#include <cstdio>
#include <cstdint>
@ -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 <typename T>
inline T Peek()
{
return *reinterpret_cast<const T* const>(&m_data[m_offset]);
}
template <typename T>
inline T Read()
{
T value = *reinterpret_cast<const T *const>(&m_data[m_offset]);
T value = Peek<T>();
m_offset += sizeof(T);
return value;
}
inline int ReadVarInt()
template <typename T = int32_t>
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<T>(result);
}
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 stringstream = std::stringstream;
namespace Feather::Network
namespace Feather
{
class ServerStatus
{

View File

@ -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',

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 "TCPListener.h"
#include "IListenerInterface.h"
#include "NetworkManager.h"
#include "NetworkMessage.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;
}
@ -182,11 +189,16 @@ namespace Feather::Network
{
}
const LockableVector<uint8_t>& TCPClient::GetData() const
LockableVector<uint8_t>& 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)
{

View File

@ -1,7 +1,6 @@
#pragma once
#include "Lockable.h"
#include "IListenerInterface.h"
#include <cstdint>
#include <memory>
@ -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<uint8_t>& GetData() const;
LockableVector<uint8_t>& GetData();
void Write(const uint8_t* data, size_t size);
private:
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
{