Add packet generation + handling automagically

This commit is contained in:
Joshua Ashton 2020-08-03 06:07:32 +01:00
parent e11f7ee3c5
commit e8244a3359
13 changed files with 413 additions and 294 deletions

View File

@ -2,15 +2,13 @@
#include "config/ServerProperties.h"
#include "PacketReader.h"
#include <Windows.h>
namespace Feather
{
DedicatedServer::DedicatedServer(ServerProperties* properties) :
m_properties(properties),
m_listener(properties->serverPort.GetValue(), this),
m_status(),
m_protocol(*this)
m_status()
{
m_status.descriptionText = properties->motd.GetValue();
m_status.maxPlayers = properties->maxPlayers.GetValue();
@ -27,7 +25,7 @@ namespace Feather
while (offset != data.size())
{
auto reader = PacketReader(&data[offset]);
m_protocol.HandlePacket(client, reader);
Protocol::ProcessPacket(*this, client, reader, client.GetContext().GetState());
offset += reader.Size();
}
data.clear();
@ -56,4 +54,62 @@ namespace Feather
auto [clients, lock] = m_clients.borrow();
clients.remove_if([&](MinecraftClient& other) { return other.GetTCPClient().get() == client; });
}
using namespace Protocol;
void DedicatedServer::HandleLegacyPing(MinecraftClient& client)
{
Log_Info("Got legacy server list ping.");
}
template <>
void DedicatedServer::HandlePacket(MinecraftClient& client, const Handholding::ServerboundHandshake& handshake)
{
Log_Info("Client Intention Packet: version=%d, serverIp=%s, port=%u, intention=%d\n",
handshake.protocolVersion,
handshake.serverIP.c_str(),
handshake.port,
handshake.intention
);
client.GetContext().SetState(handshake.intention);
}
template <>
void DedicatedServer::HandlePacket(MinecraftClient& client, const Status::ServerboundRequest& request)
{
Log_Info("Client sent STATUS_PING_REQUEST");
Status::ClientboundResponse message =
{
.jsonResponse = m_status.GetServerStatusJSON()
};
client.SendMessage(message);
}
template <>
void DedicatedServer::HandlePacket(MinecraftClient& client, const Status::ServerboundPing& ping)
{
Log_Info("Client sent STATUS_PING: %llu", ping.timestamp);
Status::ClientboundPong message =
{
.timestamp = ping.timestamp
};
client.SendMessage(message);
}
template <>
void DedicatedServer::HandlePacket(MinecraftClient& client, const Login::ServerboundStart& start)
{
Login::ClientboundSuccess message =
{
.uuid = { 4658857991808325907ull, 7518717155607718277ull },
.username = start.username
};
client.SendMessage(message);
}
}

View File

@ -2,7 +2,7 @@
#include "MinecraftClient.h"
#include "ServerStatus.h"
#include "Protocol.h"
#include "protocol/Protocol.h"
#include "network/IListenerInterface.h"
@ -19,13 +19,15 @@ namespace Feather
void OnClientConnect(Network::TCPClientHandle&& client) override;
void OnClientDisconnect(const Network::TCPClient* client) override;
ServerStatus& GetStatus() { return m_status; }
void HandleLegacyPing(MinecraftClient& client);
template <typename T>
void HandlePacket(MinecraftClient& client, const T& message);
private:
ServerProperties* m_properties;
Network::TCPListener m_listener;
ServerStatus m_status;
Protocol m_protocol;
LockableList<MinecraftClient> m_clients;
};

View File

@ -2,7 +2,7 @@
#include "network/TCPListener.h"
#include "network/TCPClient.h"
#include "Protocol.h"
#include "protocol/Protocol.h"
#include "NetworkMessage.h"
namespace Feather
@ -14,12 +14,12 @@ namespace Feather
~MinecraftClient();
inline Network::TCPClientHandle& GetTCPClient() { return m_client; }
inline ProtocolContext& GetContext() { return m_context; }
inline Protocol::ProtocolContext& GetContext() { return m_context; }
void SendMessage(const NetworkMessage& message);
private:
Network::TCPClientHandle m_client;
ProtocolContext m_context;
Network::TCPClientHandle m_client;
Protocol::ProtocolContext m_context;
};
}

View File

@ -5,6 +5,7 @@
#include <cstdint>
#include <cstring>
#include <vector>
#include <string>
#include <cassert>
#define VARINT_MAX_SIZE 5
@ -16,9 +17,7 @@ namespace Feather
public:
NetworkMessage(int32_t expectedSize)
{
size_t initialSize = VARINT_MAX_SIZE + expectedSize;
m_data.reserve(initialSize);
m_data.reserve(expectedSize);
m_data.resize(VARINT_MAX_SIZE);
}
@ -37,7 +36,7 @@ namespace Feather
{
if constexpr (asEndian == Endian::Big && sizeof(T) > 1)
{
T valueReversed = ReverseBytes<T>(value);
T valueReversed = ReverseBytes(value);
WriteData(&valueReversed, sizeof(T));
}
else
@ -67,6 +66,11 @@ namespace Feather
WriteData(string, stringSize);
}
inline void WriteString(const std::string& string)
{
WriteString(string.c_str(), string.length());
}
inline void Finalize()
{
PrependVarIntSize();

View File

@ -27,7 +27,7 @@ namespace Feather
{
T value = *reinterpret_cast<const T *const>(&m_data[m_offset]);
return (endianMode == Endian::Big) ? ReverseBytes<T>(value) : value;
return (endianMode == Endian::Big) ? ReverseBytes(value) : value;
}
// Read the next value
@ -51,11 +51,9 @@ namespace Feather
int32_t length = ReadVarInt(&m_lengthSize);
// HACK: handle Legacy Server List Ping
if (length == 0xFE) {
if (PeekByte() == 0x01) {
return 1;
}
}
m_legacy = length == 0xFE && PeekByte() == 0x01;
if (m_legacy)
return 1;
return length;
}
@ -100,11 +98,15 @@ namespace Feather
uint32_t Length() const { return m_length; }
uint32_t Size() const { return m_length + m_lengthSize; }
bool IsLegacyPing() const { return m_legacy; }
private:
const uint8_t *const m_data;
uint32_t m_offset;
const uint32_t m_length;
uint32_t m_lengthSize;
bool m_legacy;
};
// Use fast Read and Peek for uint8_t

View File

@ -1,39 +0,0 @@
#pragma once
namespace Feather
{
enum class ServerboundHandholdingPacketId : int32_t
{
Handshake = 0,
};
enum class ServerboundStatusPacketId : int32_t
{
Request = 0,
Ping = 1
};
enum class ClientBoundStatusPacketId : int32_t
{
Response = 0,
Pong = 1
};
enum class ServerboundLoginPacketId : int32_t
{
LoginStart = 0,
EncryptionResponse = 1,
LoginPluginResponse = 2
};
enum class ClientboundLoginPacketId : int32_t
{
Disconnect = 0,
EncryptionRequest = 1,
LoginSuccess = 2,
SetCompression = 3,
LoginPluginRequest = 4
};
constexpr uint8_t LegacyServerListPing = 0xFE;
}

View File

@ -1,125 +0,0 @@
#include "Protocol.h"
#include "PacketReader.h"
#include "PacketTypes.h"
#include "MinecraftClient.h"
#include "DedicatedServer.h"
#include <string>
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;
}
HandshakeMessage handshake(packet);
printf("[Protocol] Client Intention Packet: version=%d, serverIp=%s, port=%u, intention=%d\n",
handshake.protocolVersion,
handshake.serverIP.c_str(),
handshake.port,
handshake.intention
);
context.SetState(handshake.intention);
break;
}
case ProtocolState::Status:
{
auto id = packet.ReadVarInt<ServerboundStatusPacketId>();
switch (id)
{
case ServerboundStatusPacketId::Request:
{
printf("[Protocol] Client sent STATUS_PING_REQUEST\n");
std::string status = m_server.GetStatus().GetServerStatusJSON();
NetworkMessage msg(VARINT_MAX_SIZE + status.length());
// Packet ID
msg.WriteVarInt(ClientBoundStatusPacketId::Response);
// JSON Contents
msg.WriteString(status.c_str(), static_cast<int32_t>(status.length()));
msg.Finalize();
client.SendMessage(msg);
break;
}
case ServerboundStatusPacketId::Ping:
{
PingMessage ping(packet);
printf("[Protocol] Client sent STATUS_PING: %lld\n", ping.timestamp);
NetworkMessage msg(VARINT_MAX_SIZE + sizeof(int64_t));
msg.WriteVarInt(ClientBoundStatusPacketId::Pong);
msg.Write<uint64_t>(ping.timestamp);
msg.Finalize();
client.SendMessage(msg);
break;
}
}
break;
}
case ProtocolState::Login:
{
auto id = packet.ReadVarInt<ServerboundLoginPacketId>();
switch (id)
{
case ServerboundLoginPacketId::LoginStart:
{
LoginStartMessage loginStart(packet);
// 1.15.2...
//std::string uuid = "ecb99913-96a8-40a7-8529-a2ca6ad95768";
//uuid.resize(36);
//NetworkMessage msg(VARINT_MAX_SIZE * 3 + uuid.length() + loginStart.username.length());
NetworkMessage msg(VARINT_MAX_SIZE * 3 + 2 * sizeof(uint64_t) + loginStart.username.length());
msg.WriteVarInt(ClientboundLoginPacketId::LoginSuccess);
// UUID 1.16.1
msg.Write<uint64_t>(4658857991808325907LL);
msg.Write<uint64_t>(7518717155607718277LL);
// UUID 1.15.2
//msg.WriteString(uuid.c_str(), uuid.length());
msg.WriteString(loginStart.username.c_str(), loginStart.username.length());
msg.Finalize();
client.SendMessage(msg);
break;
}
}
break;
}
}
}
}

22
src/Types.h Normal file
View File

@ -0,0 +1,22 @@
#pragma once
#include "util/ByteUtil.h"
#include <cstdint>
#include <array>
namespace Feather
{
struct MinecraftUUID
{
std::array<uint64_t, 2> data;
};
inline MinecraftUUID ReverseBytes(MinecraftUUID uuid)
{
return MinecraftUUID
{
.data = { ReverseBytes(uuid.data[0]), ReverseBytes(uuid.data[1]) }
};
}
}

View File

@ -2,7 +2,6 @@ feather_src = [
'Main.cpp',
'DedicatedServer.cpp',
'MinecraftClient.cpp',
'Protocol.cpp',
'logging/Logger.cpp',

View File

@ -8,10 +8,13 @@
namespace Feather
{
class DedicatedServer;
class PacketReader;
class MinecraftClient;
class DedicatedServer;
}
namespace Feather::Protocol
{
class ProtocolContext
{
public:
@ -28,13 +31,4 @@ namespace Feather
private:
ProtocolState m_state = ProtocolState::Handholding;
};
class Protocol
{
DedicatedServer& m_server;
public:
Protocol(DedicatedServer& server) : m_server(server) {}
void HandlePacket(MinecraftClient& client, PacketReader& packet);
};
}

View File

@ -2,22 +2,75 @@ import hjson
import sys
text = ''
varint_max_size = 5
indent_count = 0
def indent():
global indent_count
indent_count += 1
def unindent():
global indent_count
indent_count -= 1
def add_text(fmt, *args):
global text
global indent_count
for i in range(indent_count):
text += ' '
text += (fmt + '\n').format(*args)
def resolve_type(aliases, type):
if type in aliases:
return aliases[type]
def newline():
global text
text += '\n'
def extract_array_type(type):
if '[' in type and ']' in type:
return type[:type.rfind('[')]
return type
def extract_array_count(type):
if '[' in type and ']' in type:
return int(type[type.find('[')+1:type.rfind(']')])
return 1
def resolve_type(aliases, type):
type_no_array = type.rstrip('_')
if type_no_array in aliases:
return aliases[type_no_array]
return type
def get_type_size(type):
global varint_max_size
count = extract_array_count(type)
type = extract_array_type(type)
if type == 'varint':
return varint_max_size
elif type == 'int64' or type == 'uint64' or type == 'double':
return 8
elif type == 'int32' or type == 'uint32' or type == 'float':
return 4
elif type == 'int16' or type == 'uint16':
return 2
elif type == 'int8' or type == 'uint8':
return 1
elif type == 'string':
return count
elif type == 'uuid':
return 16
else:
print(type)
assert False
def print_states(states):
add_text(' enum class ProtocolState : int32_t')
add_text(' {{')
add_text('enum class ProtocolState : int32_t')
add_text('{{')
indent()
for state, value in states.items():
add_text(' {} = {},', state, value)
add_text(' }};')
add_text('{} = {},', state, value)
unindent()
add_text('}};')
def get_rw_func(primitiveType, aliasedType, read):
prefix = 'Read' if read else 'Write'
@ -29,30 +82,113 @@ def get_rw_func(primitiveType, aliasedType, read):
elif aliasedType == 'string':
return '{}String'.format(prefix)
else:
return 'Read<{}>'.format(aliasedType)
return '{}<{}>'.format(prefix, primitiveType)
def print_messages(list, aliases, primitives, serverbound):
def print_messages(list, aliases, primitives):
global text
for message_name, message in list.items():
add_text(' struct {}Message', message_name)
add_text(' {{')
add_text(' static constexpr int32_t PacketId = {};', message['id'])
add_text(' static constexpr bool ServerBound = {};', 'true' if serverbound else 'false')
add_text(' static constexpr ProtocolState PacketState = ProtocolState::{};', message['state'])
add_text('')
if serverbound:
add_text(' {}Message(PacketReader& reader)', message_name)
add_text(' {{')
for name, type in message['vars'].items():
add_text(' {} = reader.{}();', name, get_rw_func(resolve_type(primitives, type), resolve_type(aliases, type), True))
add_text(' }}')
add_text('')
for state, direction_list in list.items():
add_text('namespace {}', state.capitalize())
add_text('{{')
indent()
for direction, messages in direction_list.items():
serverbound = direction == 'serverbound'
for message_name, message in messages.items():
global varint_max_size
size = varint_max_size # Packet Length
size += varint_max_size # Packet Id
for name, type in message['vars'].items():
size += get_type_size(resolve_type(aliases, type))
struct_name = '{}{}'.format(direction.capitalize(), message_name)
add_text('struct {}', struct_name)
add_text('{{')
indent()
add_text('static constexpr int32_t PacketId = {};', message['id'])
add_text('static constexpr bool Serverbound = {};', 'true' if serverbound else 'false')
add_text('static constexpr ProtocolState PacketState = ProtocolState::{};', state.capitalize())
add_text('static constexpr size_t MaxSize = {};', size)
newline()
if serverbound:
add_text('{}(PacketReader& reader)', struct_name)
add_text('{{')
indent()
for name, type in message['vars'].items():
add_text('{} = reader.{}();', name, get_rw_func(resolve_type(primitives, extract_array_type(type)), resolve_type(aliases, extract_array_type(type)), True))
unindent()
add_text('}}')
newline()
else:
add_text('operator NetworkMessage() const')
add_text('{{')
indent()
add_text('NetworkMessage msg(MaxSize);')
add_text('msg.WriteVarInt(PacketId);')
for name, type in message['vars'].items():
add_text('msg.{}({});', get_rw_func(resolve_type(primitives, extract_array_type(type)), resolve_type(aliases, extract_array_type(type)), False), name)
add_text('msg.Finalize();')
add_text('return msg;')
unindent()
add_text('}}')
for name, type in message['vars'].items():
resolved_type = resolve_type(primitives, extract_array_type(type))
if not serverbound and resolved_type == 'std::string':
add_text('const {}& {};', resolved_type, name)
else:
add_text('{} {};', resolved_type, name)
unindent()
add_text('}};')
newline()
unindent()
add_text('}}')
newline()
def print_handler(list):
add_text('template <typename HandlerType, typename ClientType>')
add_text('void ProcessPacket(HandlerType& handler, ClientType& client, PacketReader& packet, ProtocolState state)')
add_text('{{')
indent()
add_text('if (packet.IsLegacyPing())')
add_text('{{')
indent()
add_text('handler.HandleLegacyPing(client);')
add_text('return;')
unindent()
add_text('}}')
newline()
add_text('const int32_t packetId = packet.ReadVarInt();')
add_text('switch (state)')
add_text('{{')
indent()
for state, direction_list in list.items():
add_text('case ProtocolState::{}:', state.capitalize())
add_text('{{')
indent()
add_text('switch (packetId)')
add_text('{{')
indent()
for direction, messages in direction_list.items():
serverbound = direction == 'serverbound'
if not serverbound:
continue
for message_name, message in messages.items():
name = '{}::Serverbound{}'.format(state.capitalize(), message_name)
add_text('case {}::PacketId:', name)
indent()
add_text('handler.HandlePacket<{}>(client, {}(packet));', name, name)
add_text('break;')
unindent()
unindent()
add_text('}}')
add_text('break;')
unindent()
add_text('}}')
unindent()
add_text('}}')
unindent()
add_text('}}')
for name, type in message['vars'].items():
add_text(' {} {};', resolve_type(primitives, type), name)
add_text(' }};')
add_text('')
def print_protocol():
with open(sys.argv[1]) as message_file:
@ -60,29 +196,37 @@ def print_protocol():
print_states(message_scheme['states'])
add_text('')
newline()
print_messages(
message_scheme['messages']['serverbound'],
message_scheme['messages'],
message_scheme['types']['aliases'],
message_scheme['types']['primitives'],
True)
message_scheme['types']['primitives'])
newline()
print_handler(message_scheme['messages'])
def main():
global text
if len(sys.argv) != 3:
print('Usage: generate_protocol.py <input: protocol.hjson> <output: ProtocolDefinitions.h>')
return
add_text('#pragma once')
add_text('')
newline()
add_text('#include <cstdint>')
add_text('#include "Types.h"')
add_text('#include "PacketReader.h"')
add_text('')
add_text('namespace Feather')
newline()
add_text('namespace Feather::Protocol')
add_text('{{')
indent()
print_protocol()
unindent()
add_text('}}')
add_text('')
newline()
with open(sys.argv[2], 'w') as out_file:
out_file.write(text)

View File

@ -10,6 +10,16 @@
{
varint : int32_t
string : std::string
uuid : MinecraftUUID
uint64 : uint64_t
int64 : int64_t
uint32 : uint32_t
int32 : int32_t
uint16 : uint16_t
int16 : int16_t
uint8 : uint8_t
int8 : int8_t
}
}
@ -23,45 +33,103 @@
messages :
{
serverbound :
handholding :
{
Handshake :
serverbound :
{
id : 0
state : Handholding
vars :
Handshake :
{
protocolVersion : varint
serverIP : string
port : uint16_t
intention : ProtocolState
}
}
Ping :
{
id : 1
state : Login
vars :
{
timestamp : uint64_t
}
}
LoginStart :
{
id : 0
state : Login
vars :
{
username : string
id : 0
vars :
{
protocolVersion : varint
serverIP : string[255]
port : uint16
intention : ProtocolState
}
}
}
}
clientbound :
status :
{
serverbound :
{
Request :
{
id : 0
vars :
{
}
}
Ping :
{
id : 1
vars :
{
timestamp : uint64
}
}
}
clientbound :
{
Response :
{
id : 0
vars :
{
jsonResponse : string[32767]
}
}
Pong :
{
id : 1
vars :
{
timestamp : uint64
}
}
}
}
login :
{
clientbound :
{
Disconnect :
{
id : 0
vars :
{
username : string[32767]
}
}
Success :
{
id : 2
vars :
{
uuid : uuid
username : string[16]
}
}
}
serverbound :
{
Start :
{
id : 0
vars :
{
username : string[16]
}
}
}
}
}
}

View File

@ -1,38 +1,30 @@
#pragma once
#ifdef _MSC_VER
#include <stdlib.h>
#endif
#include <cstdlib>
#include <cstdint>
enum class Endian
namespace Feather
{
Little,
Big
};
enum class Endian
{
Little,
Big
};
// Reverses the byte order of a primitive value of any typical size
template <typename T>
inline constexpr T ReverseBytes(T n)
{
// 1 byte, cannot reverse
if constexpr (sizeof(T) == 1) return n;
#ifdef __GNUC__
// GCC intrinsic byte swaps
// https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html#index-_005f_005fbuiltin_005fbswap16
if constexpr (sizeof(T) == 2) return __builtin_bswap16(n); // 2 bytes
else if constexpr (sizeof(T) == 4) return __builtin_bswap32(n); // 4 bytes
else if constexpr (sizeof(T) == 8) return __builtin_bswap64(n); // 8 bytes
#elif defined(_MSC_VER)
// MSVC intrinsic byteswaps
// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/byteswap-uint64-byteswap-ulong-byteswap-ushort
if constexpr (sizeof(T) == 2) return _byteswap_ushort(n); // 2 bytes
else if constexpr (sizeof(T) == 4) return _byteswap_ulong(n); // 4 bytes
else if constexpr (sizeof(T) == 8) return _byteswap_uint64(n); // 8 bytes
#endif
//else static_assert(false, "Attempted to call ReverseBytes() on type with unusual size.");
}
// TODO: Make this constexpr
inline uint8_t ReverseBytes(uint8_t n) { return n; }
#ifdef __GNUC__
inline uint16_t ReverseBytes(uint16_t n) { return __builtin_bswap16(n); }
inline uint32_t ReverseBytes(uint32_t n) { return __builtin_bswap32(n); }
inline uint32_t ReverseBytes(uint64_t n) { return __builtin_bswap64(n); }
#else
inline uint16_t ReverseBytes(uint16_t n) { return _byteswap_ushort(n); }
inline uint32_t ReverseBytes(uint32_t n) { return _byteswap_ulong (n); }
inline uint64_t ReverseBytes(uint64_t n) { return _byteswap_uint64(n); }
#endif
inline int8_t ReverseBytes(int8_t n) { return n; }
inline int16_t ReverseBytes(int16_t n) { return int16_t(ReverseBytes(uint16_t(n))); }
inline int32_t ReverseBytes(int32_t n) { return int32_t(ReverseBytes(uint32_t(n))); }
inline int64_t ReverseBytes(int64_t n) { return int64_t(ReverseBytes(uint64_t(n))); }
}