Add Protocol, ClientHandle, PacketTypes
This commit is contained in:
parent
fb6d99409e
commit
8dabf013ef
|
@ -4,6 +4,7 @@ feather_src = [
|
||||||
|
|
||||||
'network/NetworkManager.cpp',
|
'network/NetworkManager.cpp',
|
||||||
'network/TCPListener.cpp',
|
'network/TCPListener.cpp',
|
||||||
|
'network/Protocol.cpp',
|
||||||
|
|
||||||
'util/StringUtil.cpp',
|
'util/StringUtil.cpp',
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "NetworkMessage.h"
|
||||||
|
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
|
using std::queue;
|
||||||
|
|
||||||
|
namespace Feather::Network
|
||||||
|
{
|
||||||
|
class ClientHandle
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
friend class TCPListener;
|
||||||
|
|
||||||
|
queue<NetworkMessage*> m_queue;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void SendPacket(NetworkMessage* msg)
|
||||||
|
{
|
||||||
|
m_queue.push(msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -33,6 +33,11 @@ namespace Feather::Network
|
||||||
m_size += stringSize;
|
m_size += stringSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void WriteLong(int64_t value)
|
||||||
|
{
|
||||||
|
m_size += sizeof(int64_t);
|
||||||
|
}
|
||||||
|
|
||||||
inline int32_t GetSize() const { return m_size; }
|
inline int32_t GetSize() const { return m_size; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -77,6 +82,14 @@ namespace Feather::Network
|
||||||
std::memcpy(&m_data[oldSize], string, size_t(stringSize));
|
std::memcpy(&m_data[oldSize], string, size_t(stringSize));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void WriteLong(int64_t value)
|
||||||
|
{
|
||||||
|
size_t oldSize = m_data.size();
|
||||||
|
m_data.resize(oldSize + sizeof(int64_t));
|
||||||
|
// TODO: do this with registers if faster (see libcore.io.Memory.pokeLong)
|
||||||
|
std::memcpy(&m_data[oldSize], &value, sizeof(int64_t));
|
||||||
|
}
|
||||||
|
|
||||||
inline virtual const uint8_t* GetData() const { return m_data.data(); }
|
inline virtual const uint8_t* GetData() const { return m_data.data(); }
|
||||||
inline virtual size_t GetDataSize() const { return m_data.size(); }
|
inline virtual size_t GetDataSize() const { return m_data.size(); }
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "../Common.h"
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
using std::string;
|
||||||
|
|
||||||
namespace Feather::Network
|
namespace Feather::Network
|
||||||
{
|
{
|
||||||
|
@ -42,7 +47,41 @@ namespace Feather::Network
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline uint32_t Length() const { return m_length; }
|
inline uint16_t ReadUnsignedShort()
|
||||||
|
{
|
||||||
|
return (ReadByte() << 8) | ReadByte();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int64_t ReadLong()
|
||||||
|
{
|
||||||
|
int64_t value = 0;
|
||||||
|
// FIXME
|
||||||
|
value |= (ReadByte() << 56);
|
||||||
|
value |= (ReadByte() << 48);
|
||||||
|
value |= (ReadByte() << 40);
|
||||||
|
value |= (ReadByte() << 32);
|
||||||
|
value |= (ReadByte() << 24);
|
||||||
|
value |= (ReadByte() << 16);
|
||||||
|
value |= (ReadByte() << 8);
|
||||||
|
value |= (ReadByte() << 0);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
string ReadString()
|
||||||
|
{
|
||||||
|
int size = ReadVarInt();
|
||||||
|
string str;
|
||||||
|
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
str += ReadByte();
|
||||||
|
}
|
||||||
|
|
||||||
|
//printf("Read string of length %d: %s\n", size, str);
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t Length() const { return m_length; }
|
||||||
private:
|
private:
|
||||||
const uint8_t *const m_data;
|
const uint8_t *const m_data;
|
||||||
const uint32_t m_length;
|
const uint32_t m_length;
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Packets from Client -> Server
|
||||||
|
enum class ServerboundPacketType
|
||||||
|
{
|
||||||
|
STATUS_PING_REQUEST = 0,
|
||||||
|
STATUS_PING = 1
|
||||||
|
};
|
|
@ -0,0 +1,109 @@
|
||||||
|
#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();
|
||||||
|
unsigned short port = packet.ReadUnsignedShort();
|
||||||
|
|
||||||
|
// 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.ReadLong();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
#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);
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,6 +1,12 @@
|
||||||
#include "TCPListener.h"
|
|
||||||
|
#include "../Common.h"
|
||||||
|
|
||||||
|
#include "Protocol.h"
|
||||||
|
#include "TCPListener.h"
|
||||||
#include "NetworkManager.h"
|
#include "NetworkManager.h"
|
||||||
#include "NetworkMessage.h"
|
#include "NetworkMessage.h"
|
||||||
|
#include "PacketReader.h"
|
||||||
|
#include "ClientHandle.h"
|
||||||
|
|
||||||
#include <event2/event.h>
|
#include <event2/event.h>
|
||||||
#include <event2/listener.h>
|
#include <event2/listener.h>
|
||||||
|
@ -100,13 +106,26 @@ namespace Feather::Network
|
||||||
bool m_ipv6;
|
bool m_ipv6;
|
||||||
};
|
};
|
||||||
|
|
||||||
class TCPListenerClient
|
class TCPListenerClient final : public ClientHandle
|
||||||
{
|
{
|
||||||
|
private:
|
||||||
|
friend class TCPListener;
|
||||||
|
|
||||||
|
// Protocol for this specific client
|
||||||
|
Protocol m_protocol;
|
||||||
|
|
||||||
|
TCPListener *m_parent;
|
||||||
|
bufferevent *m_bufferEvent;
|
||||||
|
|
||||||
|
std::vector<uint8_t> m_incomingData;
|
||||||
|
std::mutex m_mutex;
|
||||||
public:
|
public:
|
||||||
TCPListenerClient(TCPListener* parent, evutil_socket_t socket)
|
TCPListenerClient(TCPListener* parent, evutil_socket_t socket)
|
||||||
: m_parent(parent)
|
: m_parent(parent)
|
||||||
|
, m_protocol(this)
|
||||||
, m_bufferEvent(bufferevent_socket_new(NetworkManager::Instance().GetEventBase(), socket, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE | BEV_OPT_DEFER_CALLBACKS | BEV_OPT_UNLOCK_CALLBACKS))
|
, m_bufferEvent(bufferevent_socket_new(NetworkManager::Instance().GetEventBase(), socket, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE | BEV_OPT_DEFER_CALLBACKS | BEV_OPT_UNLOCK_CALLBACKS))
|
||||||
{
|
{
|
||||||
|
printf("Created TCPListenerClient\n");
|
||||||
bufferevent_setcb(m_bufferEvent,
|
bufferevent_setcb(m_bufferEvent,
|
||||||
[](bufferevent* be, void* self) { static_cast<TCPListenerClient*>(self)->ReadCallback(); },
|
[](bufferevent* be, void* self) { static_cast<TCPListenerClient*>(self)->ReadCallback(); },
|
||||||
[](bufferevent* be, void* self) { static_cast<TCPListenerClient*>(self)->WriteCallback(); },
|
[](bufferevent* be, void* self) { static_cast<TCPListenerClient*>(self)->WriteCallback(); },
|
||||||
|
@ -122,16 +141,26 @@ namespace Feather::Network
|
||||||
|
|
||||||
void ReadCallback()
|
void ReadCallback()
|
||||||
{
|
{
|
||||||
|
printf("Read callback!\n");
|
||||||
auto lock = std::unique_lock(m_mutex);
|
auto lock = std::unique_lock(m_mutex);
|
||||||
|
|
||||||
char data[1024];
|
uint8_t data[1KB];
|
||||||
size_t length;
|
size_t length;
|
||||||
|
|
||||||
|
size_t originalSize = m_incomingData.size();
|
||||||
|
|
||||||
|
// TODO: could switch to bufferevent_read_buffer and use evbuffer to avoid copies
|
||||||
while ((length = bufferevent_read(m_bufferEvent, data, sizeof(data))) > 0)
|
while ((length = bufferevent_read(m_bufferEvent, data, sizeof(data))) > 0)
|
||||||
{
|
{
|
||||||
size_t oldSize = m_incomingData.size();
|
size_t oldSize = m_incomingData.size();
|
||||||
m_incomingData.resize(oldSize + length);
|
m_incomingData.resize(oldSize + length);
|
||||||
std::memcpy(&m_incomingData[oldSize], data, length);
|
std::memcpy(&m_incomingData[oldSize], data, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint8_t* dataPtr = &(m_incomingData.data()[originalSize]);
|
||||||
|
PacketReader reader(dataPtr);
|
||||||
|
|
||||||
|
m_protocol.HandlePacket(reader);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
@ -177,19 +206,14 @@ R"({
|
||||||
void WriteCallback()
|
void WriteCallback()
|
||||||
{
|
{
|
||||||
printf("Write callback!\n");
|
printf("Write callback!\n");
|
||||||
SendShitTest();
|
|
||||||
|
//SendShitTest();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EventCallback(short event)
|
void EventCallback(short event)
|
||||||
{
|
{
|
||||||
printf("Event!\n");
|
printf("Event! %d\n", event);
|
||||||
}
|
}
|
||||||
private:
|
|
||||||
TCPListener* m_parent;
|
|
||||||
bufferevent* m_bufferEvent;
|
|
||||||
|
|
||||||
std::vector<char> m_incomingData;
|
|
||||||
std::mutex m_mutex;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
TCPListener::TCPListener(uint16_t port)
|
TCPListener::TCPListener(uint16_t port)
|
||||||
|
@ -228,4 +252,18 @@ R"({
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TCPListener::DispatchQueuedPackets()
|
||||||
|
{
|
||||||
|
for (const auto& client : m_clients)
|
||||||
|
{
|
||||||
|
while (!client->m_queue.empty())
|
||||||
|
{
|
||||||
|
NetworkMessage *msg = client->m_queue.front();
|
||||||
|
printf("Writing packet of size %u\n", msg->GetDataSize());
|
||||||
|
bufferevent_write(client->m_bufferEvent, msg->GetData(), msg->GetDataSize());
|
||||||
|
client->m_queue.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -5,7 +5,9 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace Feather::Network
|
namespace Feather::Network
|
||||||
{
|
{
|
||||||
|
class Protocol;
|
||||||
|
|
||||||
class TCPSocket;
|
class TCPSocket;
|
||||||
class TCPListenerClient;
|
class TCPListenerClient;
|
||||||
|
|
||||||
|
@ -15,6 +17,8 @@ namespace Feather::Network
|
||||||
TCPListener(uint16_t port);
|
TCPListener(uint16_t port);
|
||||||
~TCPListener();
|
~TCPListener();
|
||||||
|
|
||||||
|
void DispatchQueuedPackets();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<TCPSocket> m_socket;
|
std::unique_ptr<TCPSocket> m_socket;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue