FeatherMC/src/network/TCPListener.cpp

269 lines
7.3 KiB
C++

#include "../Common.h"
#include "Protocol.h"
#include "TCPListener.h"
#include "NetworkManager.h"
#include "NetworkMessage.h"
#include "PacketReader.h"
#include "ClientHandle.h"
#include <event2/event.h>
#include <event2/listener.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <mutex>
#include <cstring>
namespace Feather::Network
{
class TCPSocket
{
public:
TCPSocket()
{
m_socket = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
// Can't create IPv6 socket? Create an IPv4 one.
if (!(m_ipv6 = IsValid()))
m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
}
~TCPSocket()
{
Close();
}
bool IsIPV6() const { return m_ipv6; }
bool MarkReusable()
{
return evutil_make_listen_socket_reuseable(m_socket) == 0;
}
bool MarkDualBind()
{
constexpr uint32_t zero = 0;
return setsockopt(m_socket, IPPROTO_IPV6, IPV6_V6ONLY,
reinterpret_cast<const char*>(&zero),
sizeof(zero)) == 0;
}
bool Bind(uint16_t port)
{
sockaddr_in6 name_ipv6 = {};
name_ipv6.sin6_family = AF_INET6;
name_ipv6.sin6_port = ntohs(port);
sockaddr_in name_ipv4 = {};
name_ipv4.sin_family = AF_INET;
name_ipv4.sin_port = ntohs(port);
return m_ipv6
? BindGeneric(name_ipv6)
: BindGeneric(name_ipv4);
}
bool MarkNonBlocking()
{
return evutil_make_socket_nonblocking(m_socket) == 0;
}
bool Close()
{
if (!IsValid())
return true;
return evutil_closesocket(m_socket) == 0;
}
template <typename T>
evconnlistener* Listen(evconnlistener_cb callback, T* self)
{
if (listen(m_socket, SOMAXCONN) != 0)
return nullptr;
return evconnlistener_new(
NetworkManager::Instance().GetEventBase(),
callback, reinterpret_cast<void*>(self),
LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE,
0,
m_socket);
}
bool IsValid() const { return m_socket != EVUTIL_INVALID_SOCKET; }
private:
template <typename T>
bool BindGeneric(const T& name)
{
return bind(m_socket, reinterpret_cast<const sockaddr*>(&name), sizeof(name)) == 0;
}
evutil_socket_t m_socket;
bool m_ipv6;
};
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:
TCPListenerClient(TCPListener* parent, evutil_socket_t socket)
: 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))
{
printf("Created TCPListenerClient\n");
bufferevent_setcb(m_bufferEvent,
[](bufferevent* be, void* self) { static_cast<TCPListenerClient*>(self)->ReadCallback(); },
[](bufferevent* be, void* self) { static_cast<TCPListenerClient*>(self)->WriteCallback(); },
[](bufferevent* be, short event, void* self) { static_cast<TCPListenerClient*>(self)->EventCallback(event); },
this);
bufferevent_enable(m_bufferEvent, EV_READ | EV_WRITE);
}
~TCPListenerClient()
{
bufferevent_free(m_bufferEvent);
}
void ReadCallback()
{
printf("Read callback!\n");
auto lock = std::unique_lock(m_mutex);
uint8_t data[1KB];
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)
{
size_t oldSize = m_incomingData.size();
m_incomingData.resize(oldSize + 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>
void WritePingMessage(T& message, std::string json)
{
// Packet ID
message.WriteVarInt(0);
// JSON Contents
message.WriteString(json.c_str(), static_cast<int32_t>(json.length()));
}
DEFINE_MESSAGE_WRAPPER(WritePingMessage);
bool SendShitTest()
{
auto lock = std::unique_lock(m_mutex);
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!"
}
})";
auto message = WritePingMessage(json_template);
bufferevent_write(m_bufferEvent, message.GetData(), message.GetDataSize());
return true;
}
void WriteCallback()
{
printf("Write callback!\n");
//SendShitTest();
}
void EventCallback(short event)
{
printf("Event! %d\n", event);
}
};
TCPListener::TCPListener(uint16_t port)
{
// Setup the listen socket.
auto socket = std::make_unique<TCPSocket>();
if (!socket->IsValid())
return;
if (!socket->MarkReusable())
return;
if (socket->IsIPV6() && !socket->MarkDualBind())
return;
if (!socket->Bind(port))
return;
if (!socket->MarkNonBlocking())
return;
auto ListenerCallback = [](evconnlistener* evListener, evutil_socket_t socket, sockaddr* addr, int len, void* self)
{
TCPListener* listener = static_cast<TCPListener*>(self);
listener->m_clients.emplace_back(std::make_unique<TCPListenerClient>(listener, socket));
};
if (!socket->Listen(ListenerCallback, this))
return;
m_socket = std::move(socket);
}
TCPListener::~TCPListener()
{
}
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();
}
}
}
}