Browse Source

Make SourceX/dvlnet C++11 compatible

std::make_unique is only available in C++17 and above
pull/286/head
Gleb Mazovetskiy 7 years ago committed by Anders Jenbo
parent
commit
94f62177f1
  1. 5
      CMakeLists.txt
  2. 58
      SourceX/dvlnet/abstract_net.cpp
  3. 520
      SourceX/dvlnet/base.cpp
  4. 548
      SourceX/dvlnet/packet.h
  5. 218
      SourceX/dvlnet/tcp_client.cpp
  6. 430
      SourceX/dvlnet/tcp_server.cpp

5
CMakeLists.txt

@ -291,7 +291,12 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
target_compile_options(devilution PRIVATE "/W0")
endif()
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
endif()
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
# Style issues
target_compile_options(devilutionx PRIVATE -Wno-parentheses -Wno-logical-op-parentheses -Wno-bitwise-op-parentheses)
# Silence warnings about __int64 alignment hack not always being applicable

58
SourceX/dvlnet/abstract_net.cpp

@ -1,29 +1,29 @@
#include "dvlnet/abstract_net.h"
#include "stubs.h"
#include "dvlnet/tcp_client.h"
#include "dvlnet/udp_p2p.h"
#include "dvlnet/loopback.h"
namespace dvl {
namespace net {
abstract_net::~abstract_net()
{
}
std::unique_ptr<abstract_net> abstract_net::make_net(provider_t provider)
{
if (provider == 'TCPN') {
return std::make_unique<tcp_client>();
} else if (provider == 'UDPN') {
return std::make_unique<udp_p2p>();
} else if (provider == 'SCBL' || provider == 0) {
return std::make_unique<loopback>();
} else {
ABORT();
}
}
} // namespace net
} // namespace dvl
#include "dvlnet/abstract_net.h"
#include "stubs.h"
#include "dvlnet/tcp_client.h"
#include "dvlnet/udp_p2p.h"
#include "dvlnet/loopback.h"
namespace dvl {
namespace net {
abstract_net::~abstract_net()
{
}
std::unique_ptr<abstract_net> abstract_net::make_net(provider_t provider)
{
if (provider == 'TCPN') {
return std::unique_ptr<abstract_net>(new tcp_client);
} else if (provider == 'UDPN') {
return std::unique_ptr<abstract_net>(new udp_p2p);
} else if (provider == 'SCBL' || provider == 0) {
return std::unique_ptr<abstract_net>(new loopback);
} else {
ABORT();
}
}
} // namespace net
} // namespace dvl

520
SourceX/dvlnet/base.cpp

@ -1,260 +1,260 @@
#include "dvlnet/base.h"
#include <algorithm>
#include <cstring>
namespace dvl {
namespace net {
void base::setup_gameinfo(buffer_t info)
{
game_init_info = std::move(info);
pktfty = std::make_unique<packet_factory>();
}
void base::setup_password(std::string pw)
{
pktfty = std::make_unique<packet_factory>(pw);
}
void base::run_event_handler(_SNETEVENT& ev)
{
auto f = registered_handlers[static_cast<event_type>(ev.eventid)];
if (f) {
f(&ev);
}
}
void base::handle_accept(packet& pkt)
{
if (plr_self != PLR_BROADCAST) {
return; // already have player id
}
if (pkt.cookie() == cookie_self) {
plr_self = pkt.newplr();
connected_table[plr_self] = true;
}
if (game_init_info != pkt.info()) {
// we joined and did not create
_SNETEVENT ev;
ev.eventid = EVENT_TYPE_PLAYER_CREATE_GAME;
ev.playerid = plr_self;
ev.data = const_cast<unsigned char*>(pkt.info().data());
ev.databytes = pkt.info().size();
run_event_handler(ev);
}
}
void base::clear_msg(plr_t plr)
{
message_queue.erase(std::remove_if(message_queue.begin(),
message_queue.end(),
[&](message_t& msg)
{
return msg.sender == plr;
}),
message_queue.end());
}
void base::recv_local(packet& pkt)
{
if (pkt.src() < MAX_PLRS) {
connected_table[pkt.src()] = true;
}
switch (pkt.type()) {
case PT_MESSAGE:
message_queue.push_back(message_t(pkt.src(), pkt.message()));
break;
case PT_TURN:
turn_queue[pkt.src()].push_back(pkt.turn());
break;
case PT_JOIN_ACCEPT:
handle_accept(pkt);
break;
case PT_CONNECT:
connected_table[pkt.newplr()] = true; // this can probably be removed
break;
case PT_DISCONNECT:
if (pkt.newplr() != plr_self) {
if (connected_table[pkt.newplr()]) {
auto leaveinfo = pkt.leaveinfo();
_SNETEVENT ev;
ev.eventid = EVENT_TYPE_PLAYER_LEAVE_GAME;
ev.playerid = pkt.newplr();
ev.data = reinterpret_cast<unsigned char*>(&leaveinfo);
ev.databytes = sizeof(leaveinfo_t);
run_event_handler(ev);
connected_table[pkt.newplr()] = false;
clear_msg(pkt.newplr());
turn_queue[pkt.newplr()].clear();
}
} else {
ABORT(); // we were dropped by the owner?!?
}
break;
default:
break;
// otherwise drop
}
}
bool base::SNetReceiveMessage(int* sender, char** data, int* size)
{
poll();
if (message_queue.empty())
return false;
message_last = message_queue.front();
message_queue.pop_front();
*sender = message_last.sender;
*size = message_last.payload.size();
*data = reinterpret_cast<char*>(message_last.payload.data());
return true;
}
bool base::SNetSendMessage(int playerID, void* data, unsigned int size)
{
if (playerID != SNPLAYER_ALL && playerID != SNPLAYER_OTHERS
&& (playerID < 0 || playerID >= MAX_PLRS))
abort();
auto raw_message = reinterpret_cast<unsigned char*>(data);
buffer_t message(raw_message, raw_message + size);
if (playerID == plr_self || playerID == SNPLAYER_ALL)
message_queue.push_back(message_t(plr_self, message));
plr_t dest;
if (playerID == SNPLAYER_ALL || playerID == SNPLAYER_OTHERS)
dest = PLR_BROADCAST;
else
dest = playerID;
if (dest != plr_self) {
auto pkt = pktfty->make_packet<PT_MESSAGE>(plr_self, dest, message);
send(*pkt);
}
return true;
}
bool base::SNetReceiveTurns(char** data, unsigned int* size, DWORD* status)
{
poll();
bool all_turns_arrived = true;
for (auto i = 0; i < MAX_PLRS; ++i) {
status[i] = 0;
if (connected_table[i]) {
status[i] |= PS_CONNECTED;
if (turn_queue[i].empty())
all_turns_arrived = false;
}
}
if (all_turns_arrived) {
for (auto i = 0; i < MAX_PLRS; ++i) {
if (connected_table[i]) {
size[i] = sizeof(turn_t);
status[i] |= PS_ACTIVE;
status[i] |= PS_TURN_ARRIVED;
turn_last[i] = turn_queue[i].front();
turn_queue[i].pop_front();
data[i] = reinterpret_cast<char*>(&turn_last[i]);
}
}
return true;
} else {
for (auto i = 0; i < MAX_PLRS; ++i) {
if (connected_table[i]) {
if (!turn_queue[i].empty()) {
status[i] |= PS_ACTIVE;
}
}
}
return false;
}
}
bool base::SNetSendTurn(char* data, unsigned int size)
{
if (size != sizeof(turn_t))
ABORT();
turn_t turn;
std::memcpy(&turn, data, sizeof(turn));
auto pkt = pktfty->make_packet<PT_TURN>(plr_self, PLR_BROADCAST, turn);
send(*pkt);
turn_queue[plr_self].push_back(pkt->turn());
return true;
}
int base::SNetGetProviderCaps(struct _SNETCAPS* caps)
{
caps->size = 0; // engine writes only ?!?
caps->flags = 0; // unused
caps->maxmessagesize = 512; // capped to 512; underflow if < 24
caps->maxqueuesize = 0; // unused
caps->maxplayers = MAX_PLRS; // capped to 4
caps->bytessec = 1000000; // ?
caps->latencyms = 0; // unused
caps->defaultturnssec = 10; // ?
caps->defaultturnsintransit = 1; // maximum acceptable number
// of turns in queue?
return 1;
}
bool base::SNetUnregisterEventHandler(event_type evtype, SEVTHANDLER func)
{
registered_handlers.erase(evtype);
return true;
}
bool base::SNetRegisterEventHandler(event_type evtype, SEVTHANDLER func)
{
/*
engine registers handler for:
EVENT_TYPE_PLAYER_LEAVE_GAME
EVENT_TYPE_PLAYER_CREATE_GAME (should be raised during SNetCreateGame
for non-creating player)
EVENT_TYPE_PLAYER_MESSAGE (for bnet? not implemented)
(engine uses same function for all three)
*/
registered_handlers[evtype] = func;
return true;
}
bool base::SNetLeaveGame(int type)
{
auto pkt = pktfty->make_packet<PT_DISCONNECT>(plr_self, PLR_BROADCAST,
plr_self, type);
send(*pkt);
return true;
}
bool base::SNetDropPlayer(int playerid, DWORD flags)
{
auto pkt = pktfty->make_packet<PT_DISCONNECT>(plr_self,
PLR_BROADCAST,
(plr_t)playerid,
(leaveinfo_t)flags);
send(*pkt);
recv_local(*pkt);
return true;
}
plr_t base::get_owner()
{
for (auto i = 0; i < MAX_PLRS; ++i) {
if (connected_table[i]) {
return i;
}
}
return PLR_BROADCAST; // should be unreachable
}
bool base::SNetGetOwnerTurnsWaiting(DWORD *turns)
{
*turns = turn_queue[get_owner()].size();
return true;
}
bool base::SNetGetTurnsInTransit(int *turns)
{
*turns = turn_queue[plr_self].size();
return true;
}
} // namespace net
} // namespace dvl
#include "dvlnet/base.h"
#include <algorithm>
#include <cstring>
namespace dvl {
namespace net {
void base::setup_gameinfo(buffer_t info)
{
game_init_info = std::move(info);
pktfty.reset(new packet_factory());
}
void base::setup_password(std::string pw)
{
pktfty.reset(new packet_factory(pw));
}
void base::run_event_handler(_SNETEVENT& ev)
{
auto f = registered_handlers[static_cast<event_type>(ev.eventid)];
if (f) {
f(&ev);
}
}
void base::handle_accept(packet& pkt)
{
if (plr_self != PLR_BROADCAST) {
return; // already have player id
}
if (pkt.cookie() == cookie_self) {
plr_self = pkt.newplr();
connected_table[plr_self] = true;
}
if (game_init_info != pkt.info()) {
// we joined and did not create
_SNETEVENT ev;
ev.eventid = EVENT_TYPE_PLAYER_CREATE_GAME;
ev.playerid = plr_self;
ev.data = const_cast<unsigned char*>(pkt.info().data());
ev.databytes = pkt.info().size();
run_event_handler(ev);
}
}
void base::clear_msg(plr_t plr)
{
message_queue.erase(std::remove_if(message_queue.begin(),
message_queue.end(),
[&](message_t& msg)
{
return msg.sender == plr;
}),
message_queue.end());
}
void base::recv_local(packet& pkt)
{
if (pkt.src() < MAX_PLRS) {
connected_table[pkt.src()] = true;
}
switch (pkt.type()) {
case PT_MESSAGE:
message_queue.push_back(message_t(pkt.src(), pkt.message()));
break;
case PT_TURN:
turn_queue[pkt.src()].push_back(pkt.turn());
break;
case PT_JOIN_ACCEPT:
handle_accept(pkt);
break;
case PT_CONNECT:
connected_table[pkt.newplr()] = true; // this can probably be removed
break;
case PT_DISCONNECT:
if (pkt.newplr() != plr_self) {
if (connected_table[pkt.newplr()]) {
auto leaveinfo = pkt.leaveinfo();
_SNETEVENT ev;
ev.eventid = EVENT_TYPE_PLAYER_LEAVE_GAME;
ev.playerid = pkt.newplr();
ev.data = reinterpret_cast<unsigned char*>(&leaveinfo);
ev.databytes = sizeof(leaveinfo_t);
run_event_handler(ev);
connected_table[pkt.newplr()] = false;
clear_msg(pkt.newplr());
turn_queue[pkt.newplr()].clear();
}
} else {
ABORT(); // we were dropped by the owner?!?
}
break;
default:
break;
// otherwise drop
}
}
bool base::SNetReceiveMessage(int* sender, char** data, int* size)
{
poll();
if (message_queue.empty())
return false;
message_last = message_queue.front();
message_queue.pop_front();
*sender = message_last.sender;
*size = message_last.payload.size();
*data = reinterpret_cast<char*>(message_last.payload.data());
return true;
}
bool base::SNetSendMessage(int playerID, void* data, unsigned int size)
{
if (playerID != SNPLAYER_ALL && playerID != SNPLAYER_OTHERS
&& (playerID < 0 || playerID >= MAX_PLRS))
abort();
auto raw_message = reinterpret_cast<unsigned char*>(data);
buffer_t message(raw_message, raw_message + size);
if (playerID == plr_self || playerID == SNPLAYER_ALL)
message_queue.push_back(message_t(plr_self, message));
plr_t dest;
if (playerID == SNPLAYER_ALL || playerID == SNPLAYER_OTHERS)
dest = PLR_BROADCAST;
else
dest = playerID;
if (dest != plr_self) {
auto pkt = pktfty->make_packet<PT_MESSAGE>(plr_self, dest, message);
send(*pkt);
}
return true;
}
bool base::SNetReceiveTurns(char** data, unsigned int* size, DWORD* status)
{
poll();
bool all_turns_arrived = true;
for (auto i = 0; i < MAX_PLRS; ++i) {
status[i] = 0;
if (connected_table[i]) {
status[i] |= PS_CONNECTED;
if (turn_queue[i].empty())
all_turns_arrived = false;
}
}
if (all_turns_arrived) {
for (auto i = 0; i < MAX_PLRS; ++i) {
if (connected_table[i]) {
size[i] = sizeof(turn_t);
status[i] |= PS_ACTIVE;
status[i] |= PS_TURN_ARRIVED;
turn_last[i] = turn_queue[i].front();
turn_queue[i].pop_front();
data[i] = reinterpret_cast<char*>(&turn_last[i]);
}
}
return true;
} else {
for (auto i = 0; i < MAX_PLRS; ++i) {
if (connected_table[i]) {
if (!turn_queue[i].empty()) {
status[i] |= PS_ACTIVE;
}
}
}
return false;
}
}
bool base::SNetSendTurn(char* data, unsigned int size)
{
if (size != sizeof(turn_t))
ABORT();
turn_t turn;
std::memcpy(&turn, data, sizeof(turn));
auto pkt = pktfty->make_packet<PT_TURN>(plr_self, PLR_BROADCAST, turn);
send(*pkt);
turn_queue[plr_self].push_back(pkt->turn());
return true;
}
int base::SNetGetProviderCaps(struct _SNETCAPS* caps)
{
caps->size = 0; // engine writes only ?!?
caps->flags = 0; // unused
caps->maxmessagesize = 512; // capped to 512; underflow if < 24
caps->maxqueuesize = 0; // unused
caps->maxplayers = MAX_PLRS; // capped to 4
caps->bytessec = 1000000; // ?
caps->latencyms = 0; // unused
caps->defaultturnssec = 10; // ?
caps->defaultturnsintransit = 1; // maximum acceptable number
// of turns in queue?
return 1;
}
bool base::SNetUnregisterEventHandler(event_type evtype, SEVTHANDLER func)
{
registered_handlers.erase(evtype);
return true;
}
bool base::SNetRegisterEventHandler(event_type evtype, SEVTHANDLER func)
{
/*
engine registers handler for:
EVENT_TYPE_PLAYER_LEAVE_GAME
EVENT_TYPE_PLAYER_CREATE_GAME (should be raised during SNetCreateGame
for non-creating player)
EVENT_TYPE_PLAYER_MESSAGE (for bnet? not implemented)
(engine uses same function for all three)
*/
registered_handlers[evtype] = func;
return true;
}
bool base::SNetLeaveGame(int type)
{
auto pkt = pktfty->make_packet<PT_DISCONNECT>(plr_self, PLR_BROADCAST,
plr_self, type);
send(*pkt);
return true;
}
bool base::SNetDropPlayer(int playerid, DWORD flags)
{
auto pkt = pktfty->make_packet<PT_DISCONNECT>(plr_self,
PLR_BROADCAST,
(plr_t)playerid,
(leaveinfo_t)flags);
send(*pkt);
recv_local(*pkt);
return true;
}
plr_t base::get_owner()
{
for (auto i = 0; i < MAX_PLRS; ++i) {
if (connected_table[i]) {
return i;
}
}
return PLR_BROADCAST; // should be unreachable
}
bool base::SNetGetOwnerTurnsWaiting(DWORD *turns)
{
*turns = turn_queue[get_owner()].size();
return true;
}
bool base::SNetGetTurnsInTransit(int *turns)
{
*turns = turn_queue[plr_self].size();
return true;
}
} // namespace net
} // namespace dvl

548
SourceX/dvlnet/packet.h

@ -1,274 +1,274 @@
#pragma once
#include <string>
#include <memory>
#include <array>
#include <cstring>
#include <sodium.h>
#include "dvlnet/abstract_net.h"
#include "stubs.h"
namespace dvl {
namespace net {
enum packet_type : uint8_t {
PT_MESSAGE = 0x01,
PT_TURN = 0x02,
PT_JOIN_REQUEST = 0x11,
PT_JOIN_ACCEPT = 0x12,
PT_CONNECT = 0x13,
PT_DISCONNECT = 0x14,
};
typedef uint8_t plr_t;
typedef uint32_t cookie_t;
typedef int turn_t; // change int to something else in devilution code later
typedef int leaveinfo_t; // also change later
typedef std::array<unsigned char, crypto_secretbox_KEYBYTES> key_t;
static constexpr plr_t PLR_MASTER = 0xFE;
static constexpr plr_t PLR_BROADCAST = 0xFF;
class packet_exception : public dvlnet_exception {};
class packet {
protected:
packet_type m_type;
plr_t m_src;
plr_t m_dest;
buffer_t m_message;
turn_t m_turn;
cookie_t m_cookie;
plr_t m_newplr;
buffer_t m_info;
leaveinfo_t m_leaveinfo;
const key_t& key;
bool have_encrypted = false;
bool have_decrypted = false;
buffer_t encrypted_buffer;
buffer_t decrypted_buffer;
public:
packet(const key_t& k) : key(k) {};
const buffer_t& data();
packet_type type();
plr_t src();
plr_t dest();
const buffer_t& message();
turn_t turn();
cookie_t cookie();
plr_t newplr();
const buffer_t& info();
leaveinfo_t leaveinfo();
};
template<class P> class packet_proc : public packet {
public:
using packet::packet;
void process_data();
};
class packet_in : public packet_proc<packet_in> {
public:
using packet_proc<packet_in>::packet_proc;
void create(buffer_t buf);
void process_element(buffer_t& x);
template <class T> void process_element(T& x);
void decrypt();
};
class packet_out : public packet_proc<packet_out> {
public:
using packet_proc<packet_out>::packet_proc;
template<packet_type t, typename... Args>
void create(Args... args);
void process_element(buffer_t& x);
template<class T> void process_element(T& x);
template<class T> static const unsigned char* begin(const T& x);
template<class T> static const unsigned char* end(const T& x);
void encrypt();
};
template<class P> void packet_proc<P>::process_data()
{
P& self = static_cast<P&>(*this);
self.process_element(m_type);
self.process_element(m_src);
self.process_element(m_dest);
switch (m_type) {
case PT_MESSAGE:
self.process_element(m_message);
break;
case PT_TURN:
self.process_element(m_turn);
break;
case PT_JOIN_REQUEST:
self.process_element(m_cookie);
self.process_element(m_info);
break;
case PT_JOIN_ACCEPT:
self.process_element(m_cookie);
self.process_element(m_newplr);
self.process_element(m_info);
break;
case PT_CONNECT:
self.process_element(m_newplr);
break;
case PT_DISCONNECT:
self.process_element(m_newplr);
self.process_element(m_leaveinfo);
break;
}
}
inline void packet_in::process_element(buffer_t& x)
{
x.insert(x.begin(), decrypted_buffer.begin(), decrypted_buffer.end());
decrypted_buffer.resize(0);
}
template <class T> void packet_in::process_element(T& x)
{
if (decrypted_buffer.size() < sizeof(T))
throw packet_exception();
std::memcpy(&x, decrypted_buffer.data(), sizeof(T));
decrypted_buffer.erase(decrypted_buffer.begin(),
decrypted_buffer.begin() + sizeof(T));
}
template<>
inline void packet_out::create<PT_MESSAGE>(plr_t s, plr_t d, buffer_t m)
{
if (have_encrypted || have_decrypted)
ABORT();
have_decrypted = true;
m_type = PT_MESSAGE;
m_src = s;
m_dest = d;
m_message = std::move(m);
}
template<>
inline void packet_out::create<PT_TURN>(plr_t s, plr_t d, turn_t u)
{
if (have_encrypted || have_decrypted)
ABORT();
have_decrypted = true;
m_type = PT_TURN;
m_src = s;
m_dest = d;
m_turn = u;
}
template<>
inline void packet_out::create<PT_JOIN_REQUEST>(plr_t s, plr_t d,
cookie_t c, buffer_t i)
{
if (have_encrypted || have_decrypted)
ABORT();
have_decrypted = true;
m_type = PT_JOIN_REQUEST;
m_src = s;
m_dest = d;
m_cookie = c;
m_info = i;
}
template<>
inline void packet_out::create<PT_JOIN_ACCEPT>(plr_t s, plr_t d, cookie_t c,
plr_t n, buffer_t i)
{
if (have_encrypted || have_decrypted)
ABORT();
have_decrypted = true;
m_type = PT_JOIN_ACCEPT;
m_src = s;
m_dest = d;
m_cookie = c;
m_newplr = n;
m_info = i;
}
template<>
inline void packet_out::create<PT_CONNECT>(plr_t s, plr_t d, plr_t n)
{
if (have_encrypted || have_decrypted)
ABORT();
have_decrypted = true;
m_type = PT_CONNECT;
m_src = s;
m_dest = d;
m_newplr = n;
}
template<>
inline void packet_out::create<PT_DISCONNECT>(plr_t s, plr_t d, plr_t n,
leaveinfo_t l)
{
if (have_encrypted || have_decrypted)
ABORT();
have_decrypted = true;
m_type = PT_DISCONNECT;
m_src = s;
m_dest = d;
m_newplr = n;
m_leaveinfo = l;
}
inline void packet_out::process_element(buffer_t& x)
{
encrypted_buffer.insert(encrypted_buffer.end(), x.begin(), x.end());
}
template <class T> void packet_out::process_element(T& x)
{
encrypted_buffer.insert(encrypted_buffer.end(), begin(x), end(x));
}
template <class T> const unsigned char* packet_out::begin(const T& x)
{
return reinterpret_cast<const unsigned char *>(&x);
}
template <class T> const unsigned char* packet_out::end(const T& x)
{
return reinterpret_cast<const unsigned char *>(&x) + sizeof(T);
}
class packet_factory {
key_t key = {};
public:
static constexpr unsigned short max_packet_size = 0xFFFF;
packet_factory(std::string pw = "");
std::unique_ptr<packet> make_packet(buffer_t buf);
template<packet_type t, typename... Args>
std::unique_ptr<packet> make_packet(Args... args);
};
inline std::unique_ptr<packet> packet_factory::make_packet(buffer_t buf)
{
auto ret = std::make_unique<packet_in>(key);
ret->create(std::move(buf));
ret->decrypt();
return ret;
}
template<packet_type t, typename... Args>
std::unique_ptr<packet> packet_factory::make_packet(Args... args)
{
auto ret = std::make_unique<packet_out>(key);
ret->create<t>(args...);
ret->encrypt();
return ret;
}
} // namespace net
} // namespace dvl
#pragma once
#include <string>
#include <memory>
#include <array>
#include <cstring>
#include <sodium.h>
#include "dvlnet/abstract_net.h"
#include "stubs.h"
namespace dvl {
namespace net {
enum packet_type : uint8_t {
PT_MESSAGE = 0x01,
PT_TURN = 0x02,
PT_JOIN_REQUEST = 0x11,
PT_JOIN_ACCEPT = 0x12,
PT_CONNECT = 0x13,
PT_DISCONNECT = 0x14,
};
typedef uint8_t plr_t;
typedef uint32_t cookie_t;
typedef int turn_t; // change int to something else in devilution code later
typedef int leaveinfo_t; // also change later
typedef std::array<unsigned char, crypto_secretbox_KEYBYTES> key_t;
static constexpr plr_t PLR_MASTER = 0xFE;
static constexpr plr_t PLR_BROADCAST = 0xFF;
class packet_exception : public dvlnet_exception {};
class packet {
protected:
packet_type m_type;
plr_t m_src;
plr_t m_dest;
buffer_t m_message;
turn_t m_turn;
cookie_t m_cookie;
plr_t m_newplr;
buffer_t m_info;
leaveinfo_t m_leaveinfo;
const key_t& key;
bool have_encrypted = false;
bool have_decrypted = false;
buffer_t encrypted_buffer;
buffer_t decrypted_buffer;
public:
packet(const key_t& k) : key(k) {};
const buffer_t& data();
packet_type type();
plr_t src();
plr_t dest();
const buffer_t& message();
turn_t turn();
cookie_t cookie();
plr_t newplr();
const buffer_t& info();
leaveinfo_t leaveinfo();
};
template<class P> class packet_proc : public packet {
public:
using packet::packet;
void process_data();
};
class packet_in : public packet_proc<packet_in> {
public:
using packet_proc<packet_in>::packet_proc;
void create(buffer_t buf);
void process_element(buffer_t& x);
template <class T> void process_element(T& x);
void decrypt();
};
class packet_out : public packet_proc<packet_out> {
public:
using packet_proc<packet_out>::packet_proc;
template<packet_type t, typename... Args>
void create(Args... args);
void process_element(buffer_t& x);
template<class T> void process_element(T& x);
template<class T> static const unsigned char* begin(const T& x);
template<class T> static const unsigned char* end(const T& x);
void encrypt();
};
template<class P> void packet_proc<P>::process_data()
{
P& self = static_cast<P&>(*this);
self.process_element(m_type);
self.process_element(m_src);
self.process_element(m_dest);
switch (m_type) {
case PT_MESSAGE:
self.process_element(m_message);
break;
case PT_TURN:
self.process_element(m_turn);
break;
case PT_JOIN_REQUEST:
self.process_element(m_cookie);
self.process_element(m_info);
break;
case PT_JOIN_ACCEPT:
self.process_element(m_cookie);
self.process_element(m_newplr);
self.process_element(m_info);
break;
case PT_CONNECT:
self.process_element(m_newplr);
break;
case PT_DISCONNECT:
self.process_element(m_newplr);
self.process_element(m_leaveinfo);
break;
}
}
inline void packet_in::process_element(buffer_t& x)
{
x.insert(x.begin(), decrypted_buffer.begin(), decrypted_buffer.end());
decrypted_buffer.resize(0);
}
template <class T> void packet_in::process_element(T& x)
{
if (decrypted_buffer.size() < sizeof(T))
throw packet_exception();
std::memcpy(&x, decrypted_buffer.data(), sizeof(T));
decrypted_buffer.erase(decrypted_buffer.begin(),
decrypted_buffer.begin() + sizeof(T));
}
template<>
inline void packet_out::create<PT_MESSAGE>(plr_t s, plr_t d, buffer_t m)
{
if (have_encrypted || have_decrypted)
ABORT();
have_decrypted = true;
m_type = PT_MESSAGE;
m_src = s;
m_dest = d;
m_message = std::move(m);
}
template<>
inline void packet_out::create<PT_TURN>(plr_t s, plr_t d, turn_t u)
{
if (have_encrypted || have_decrypted)
ABORT();
have_decrypted = true;
m_type = PT_TURN;
m_src = s;
m_dest = d;
m_turn = u;
}
template<>
inline void packet_out::create<PT_JOIN_REQUEST>(plr_t s, plr_t d,
cookie_t c, buffer_t i)
{
if (have_encrypted || have_decrypted)
ABORT();
have_decrypted = true;
m_type = PT_JOIN_REQUEST;
m_src = s;
m_dest = d;
m_cookie = c;
m_info = i;
}
template<>
inline void packet_out::create<PT_JOIN_ACCEPT>(plr_t s, plr_t d, cookie_t c,
plr_t n, buffer_t i)
{
if (have_encrypted || have_decrypted)
ABORT();
have_decrypted = true;
m_type = PT_JOIN_ACCEPT;
m_src = s;
m_dest = d;
m_cookie = c;
m_newplr = n;
m_info = i;
}
template<>
inline void packet_out::create<PT_CONNECT>(plr_t s, plr_t d, plr_t n)
{
if (have_encrypted || have_decrypted)
ABORT();
have_decrypted = true;
m_type = PT_CONNECT;
m_src = s;
m_dest = d;
m_newplr = n;
}
template<>
inline void packet_out::create<PT_DISCONNECT>(plr_t s, plr_t d, plr_t n,
leaveinfo_t l)
{
if (have_encrypted || have_decrypted)
ABORT();
have_decrypted = true;
m_type = PT_DISCONNECT;
m_src = s;
m_dest = d;
m_newplr = n;
m_leaveinfo = l;
}
inline void packet_out::process_element(buffer_t& x)
{
encrypted_buffer.insert(encrypted_buffer.end(), x.begin(), x.end());
}
template <class T> void packet_out::process_element(T& x)
{
encrypted_buffer.insert(encrypted_buffer.end(), begin(x), end(x));
}
template <class T> const unsigned char* packet_out::begin(const T& x)
{
return reinterpret_cast<const unsigned char *>(&x);
}
template <class T> const unsigned char* packet_out::end(const T& x)
{
return reinterpret_cast<const unsigned char *>(&x) + sizeof(T);
}
class packet_factory {
key_t key = {};
public:
static constexpr unsigned short max_packet_size = 0xFFFF;
packet_factory(std::string pw = "");
std::unique_ptr<packet> make_packet(buffer_t buf);
template<packet_type t, typename... Args>
std::unique_ptr<packet> make_packet(Args... args);
};
inline std::unique_ptr<packet> packet_factory::make_packet(buffer_t buf)
{
std::unique_ptr<packet_in> ret(new packet_in(key));
ret->create(std::move(buf));
ret->decrypt();
return std::unique_ptr<packet>(std::move(ret));
}
template<packet_type t, typename... Args>
std::unique_ptr<packet> packet_factory::make_packet(Args... args)
{
std::unique_ptr<packet_out> ret(new packet_out(key));
ret->create<t>(args...);
ret->encrypt();
return std::unique_ptr<packet>(std::move(ret));
}
} // namespace net
} // namespace dvl

218
SourceX/dvlnet/tcp_client.cpp

@ -1,109 +1,109 @@
#include "dvlnet/tcp_client.h"
#include <functional>
#include <exception>
#include <system_error>
#include <stdexcept>
#include <sodium.h>
#include <SDL.h>
namespace dvl {
namespace net {
int tcp_client::create(std::string addrstr, std::string passwd)
{
try {
auto port = default_port;
local_server = std::make_unique<tcp_server>(ioc, addrstr, port, passwd);
return join(local_server->localhost_self(), passwd);
} catch(std::system_error &e) {
return -1;
}
}
int tcp_client::join(std::string addrstr, std::string passwd)
{
constexpr int ms_sleep = 10;
constexpr int no_sleep = 250;
setup_password(passwd);
try {
auto ipaddr = asio::ip::make_address(addrstr);
sock.connect(asio::ip::tcp::endpoint(ipaddr, default_port));
asio::ip::tcp::no_delay option(true);
sock.set_option(option);
} catch(std::exception &e) {
return -1;
}
start_recv();
{
randombytes_buf(reinterpret_cast<unsigned char*>(&cookie_self),
sizeof(cookie_t));
auto pkt = pktfty->make_packet<PT_JOIN_REQUEST>(PLR_BROADCAST,
PLR_MASTER, cookie_self,
game_init_info);
send(*pkt);
for (auto i = 0; i < no_sleep; ++i) {
try {
poll();
} catch (const std::runtime_error &e) {
return -1;
}
if (plr_self != PLR_BROADCAST)
break; // join successful
SDL_Delay(ms_sleep);
}
}
return (plr_self == PLR_BROADCAST ? -1 : plr_self);
}
void tcp_client::poll()
{
ioc.poll();
}
void tcp_client::handle_recv(const asio::error_code& error, size_t bytes_read)
{
if (error) {
// error in recv from server
// returning and doing nothing should be the same
// as if all connections to other clients were lost
return;
}
if (bytes_read == 0) {
throw std::runtime_error("error: read 0 bytes from server");
}
recv_buffer.resize(bytes_read);
recv_queue.write(std::move(recv_buffer));
recv_buffer.resize(frame_queue::max_frame_size);
while (recv_queue.packet_ready()) {
auto pkt = pktfty->make_packet(recv_queue.read_packet());
recv_local(*pkt);
}
start_recv();
}
void tcp_client::start_recv()
{
sock.async_receive(asio::buffer(recv_buffer),
std::bind(&tcp_client::handle_recv, this,
std::placeholders::_1, std::placeholders::_2));
}
void tcp_client::handle_send(const asio::error_code& error, size_t bytes_sent)
{
// empty for now
}
void tcp_client::send(packet& pkt)
{
auto frame = std::make_shared<buffer_t>(frame_queue::make_frame(pkt.data()));
auto buf = asio::buffer(*frame);
asio::async_write(sock, buf, [this, frame = std::move(frame)]
(const asio::error_code &error, size_t bytes_sent) {
handle_send(error, bytes_sent);
});
}
} // namespace net
} // namespace dvl
#include "dvlnet/tcp_client.h"
#include <functional>
#include <exception>
#include <system_error>
#include <stdexcept>
#include <sodium.h>
#include <SDL.h>
namespace dvl {
namespace net {
int tcp_client::create(std::string addrstr, std::string passwd)
{
try {
auto port = default_port;
local_server.reset(new tcp_server(ioc, addrstr, port, passwd));
return join(local_server->localhost_self(), passwd);
} catch(std::system_error &e) {
return -1;
}
}
int tcp_client::join(std::string addrstr, std::string passwd)
{
constexpr int ms_sleep = 10;
constexpr int no_sleep = 250;
setup_password(passwd);
try {
auto ipaddr = asio::ip::make_address(addrstr);
sock.connect(asio::ip::tcp::endpoint(ipaddr, default_port));
asio::ip::tcp::no_delay option(true);
sock.set_option(option);
} catch(std::exception &e) {
return -1;
}
start_recv();
{
randombytes_buf(reinterpret_cast<unsigned char*>(&cookie_self),
sizeof(cookie_t));
auto pkt = pktfty->make_packet<PT_JOIN_REQUEST>(PLR_BROADCAST,
PLR_MASTER, cookie_self,
game_init_info);
send(*pkt);
for (auto i = 0; i < no_sleep; ++i) {
try {
poll();
} catch (const std::runtime_error &e) {
return -1;
}
if (plr_self != PLR_BROADCAST)
break; // join successful
SDL_Delay(ms_sleep);
}
}
return (plr_self == PLR_BROADCAST ? -1 : plr_self);
}
void tcp_client::poll()
{
ioc.poll();
}
void tcp_client::handle_recv(const asio::error_code& error, size_t bytes_read)
{
if (error) {
// error in recv from server
// returning and doing nothing should be the same
// as if all connections to other clients were lost
return;
}
if (bytes_read == 0) {
throw std::runtime_error("error: read 0 bytes from server");
}
recv_buffer.resize(bytes_read);
recv_queue.write(std::move(recv_buffer));
recv_buffer.resize(frame_queue::max_frame_size);
while (recv_queue.packet_ready()) {
auto pkt = pktfty->make_packet(recv_queue.read_packet());
recv_local(*pkt);
}
start_recv();
}
void tcp_client::start_recv()
{
sock.async_receive(asio::buffer(recv_buffer),
std::bind(&tcp_client::handle_recv, this,
std::placeholders::_1, std::placeholders::_2));
}
void tcp_client::handle_send(const asio::error_code& error, size_t bytes_sent)
{
// empty for now
}
void tcp_client::send(packet& pkt)
{
auto frame = std::make_shared<buffer_t>(frame_queue::make_frame(pkt.data()));
auto buf = asio::buffer(*frame);
asio::async_write(sock, buf, [this, frame = std::move(frame)]
(const asio::error_code &error, size_t bytes_sent) {
handle_send(error, bytes_sent);
});
}
} // namespace net
} // namespace dvl

430
SourceX/dvlnet/tcp_server.cpp

@ -1,215 +1,215 @@
#include "dvlnet/tcp_server.h"
#include <functional>
#include <chrono>
#include "dvlnet/base.h"
namespace dvl {
namespace net {
tcp_server::tcp_server(asio::io_context& ioc, std::string bindaddr,
unsigned short port, std::string pw) :
ioc(ioc), pktfty(pw)
{
auto addr = asio::ip::address::from_string(bindaddr);
auto ep = asio::ip::tcp::endpoint(addr, port);
acceptor = std::make_unique<asio::ip::tcp::acceptor>(ioc, ep);
start_accept();
}
std::string tcp_server::localhost_self()
{
auto addr = acceptor->local_endpoint().address();
if (addr.is_unspecified()) {
if (addr.is_v4()) {
return asio::ip::address_v4::loopback().to_string();
} else if (addr.is_v6()) {
return asio::ip::address_v6::loopback().to_string();
} else {
ABORT();
}
} else {
return addr.to_string();
}
}
tcp_server::scc tcp_server::make_connection()
{
return std::make_shared<client_connection>(ioc);
}
plr_t tcp_server::next_free()
{
for (plr_t i = 0; i < MAX_PLRS; ++i)
if (!connections[i])
return i;
return PLR_BROADCAST;
}
bool tcp_server::empty()
{
for (plr_t i = 0; i < MAX_PLRS; ++i)
if (connections[i])
return false;
return true;
}
void tcp_server::start_recv(scc con)
{
con->socket.async_receive(asio::buffer(con->recv_buffer),
std::bind(&tcp_server::handle_recv, this, con,
std::placeholders::_1,
std::placeholders::_2));
}
void tcp_server::handle_recv(scc con, const asio::error_code& ec,
size_t bytes_read)
{
if (ec || bytes_read == 0) {
drop_connection(con);
return;
}
con->recv_buffer.resize(bytes_read);
con->recv_queue.write(std::move(con->recv_buffer));
con->recv_buffer.resize(frame_queue::max_frame_size);
while (con->recv_queue.packet_ready()) {
try {
auto pkt = pktfty.make_packet(con->recv_queue.read_packet());
if (con->plr == PLR_BROADCAST) {
handle_recv_newplr(con, *pkt);
} else {
con->timeout = timeout_active;
handle_recv_packet(*pkt);
}
} catch (dvlnet_exception &e) {
drop_connection(con);
return;
}
}
start_recv(con);
}
void tcp_server::send_connect(scc con)
{
auto pkt = pktfty.make_packet<PT_CONNECT>(PLR_MASTER, PLR_BROADCAST,
con->plr);
send_packet(*pkt);
}
void tcp_server::handle_recv_newplr(scc con, packet& pkt)
{
auto newplr = next_free();
if (newplr == PLR_BROADCAST)
throw server_exception();
if (empty())
game_init_info = pkt.info();
auto reply = pktfty.make_packet<PT_JOIN_ACCEPT>(PLR_MASTER, PLR_BROADCAST,
pkt.cookie(), newplr,
game_init_info);
start_send(con, *reply);
con->plr = newplr;
connections[newplr] = con;
con->timeout = timeout_active;
send_connect(con);
}
void tcp_server::handle_recv_packet(packet& pkt)
{
send_packet(pkt);
}
void tcp_server::send_packet(packet& pkt)
{
if (pkt.dest() == PLR_BROADCAST) {
for (auto i = 0; i < MAX_PLRS; ++i)
if (i != pkt.src() && connections[i])
start_send(connections[i], pkt);
} else {
if (pkt.dest() >= MAX_PLRS)
throw server_exception();
if ((pkt.dest() != pkt.src()) && connections[pkt.dest()])
start_send(connections[pkt.dest()], pkt);
}
}
void tcp_server::start_send(scc con, packet& pkt)
{
auto frame = std::make_shared<buffer_t>(frame_queue::make_frame(pkt.data()));
auto buf = asio::buffer(*frame);
asio::async_write(con->socket, buf,
[this, con, frame = std::move(frame)]
(const asio::error_code &ec, size_t bytes_sent) {
handle_send(con, ec, bytes_sent);
});
}
void tcp_server::handle_send(scc con, const asio::error_code& ec,
size_t bytes_sent)
{
// empty for now
}
void tcp_server::start_accept()
{
auto nextcon = make_connection();
acceptor->async_accept(nextcon->socket,
std::bind(&tcp_server::handle_accept,
this, nextcon,
std::placeholders::_1));
}
void tcp_server::handle_accept(scc con, const asio::error_code& ec)
{
if (next_free() == PLR_BROADCAST) {
drop_connection(con);
} else {
asio::ip::tcp::no_delay option(true);
con->socket.set_option(option);
con->timeout = timeout_connect;
start_recv(con);
start_timeout(con);
}
start_accept();
}
void tcp_server::start_timeout(scc con)
{
con->timer.expires_after(std::chrono::seconds(1));
con->timer.async_wait(std::bind(&tcp_server::handle_timeout, this, con,
std::placeholders::_1));
}
void tcp_server::handle_timeout(scc con, const asio::error_code& ec)
{
if (ec) {
drop_connection(con);
return;
}
if (con->timeout > 0)
con->timeout -= 1;
if (con->timeout < 0)
con->timeout = 0;
if (!con->timeout) {
drop_connection(con);
return;
}
start_timeout(con);
}
void tcp_server::drop_connection(scc con)
{
if (con->plr != PLR_BROADCAST) {
auto pkt = pktfty.make_packet<PT_DISCONNECT>(PLR_MASTER, PLR_BROADCAST,
con->plr, LEAVE_DROP);
connections[con->plr] = nullptr;
send_packet(*pkt);
// TODO: investigate if it is really ok for the server to
// drop a client directly.
}
con->timer.cancel();
con->socket.close();
}
} // namespace net
} // namespace dvl
#include "dvlnet/tcp_server.h"
#include <functional>
#include <chrono>
#include "dvlnet/base.h"
namespace dvl {
namespace net {
tcp_server::tcp_server(asio::io_context& ioc, std::string bindaddr,
unsigned short port, std::string pw) :
ioc(ioc), pktfty(pw)
{
auto addr = asio::ip::address::from_string(bindaddr);
auto ep = asio::ip::tcp::endpoint(addr, port);
acceptor.reset(new asio::ip::tcp::acceptor(ioc, ep));
start_accept();
}
std::string tcp_server::localhost_self()
{
auto addr = acceptor->local_endpoint().address();
if (addr.is_unspecified()) {
if (addr.is_v4()) {
return asio::ip::address_v4::loopback().to_string();
} else if (addr.is_v6()) {
return asio::ip::address_v6::loopback().to_string();
} else {
ABORT();
}
} else {
return addr.to_string();
}
}
tcp_server::scc tcp_server::make_connection()
{
return std::make_shared<client_connection>(ioc);
}
plr_t tcp_server::next_free()
{
for (plr_t i = 0; i < MAX_PLRS; ++i)
if (!connections[i])
return i;
return PLR_BROADCAST;
}
bool tcp_server::empty()
{
for (plr_t i = 0; i < MAX_PLRS; ++i)
if (connections[i])
return false;
return true;
}
void tcp_server::start_recv(scc con)
{
con->socket.async_receive(asio::buffer(con->recv_buffer),
std::bind(&tcp_server::handle_recv, this, con,
std::placeholders::_1,
std::placeholders::_2));
}
void tcp_server::handle_recv(scc con, const asio::error_code& ec,
size_t bytes_read)
{
if (ec || bytes_read == 0) {
drop_connection(con);
return;
}
con->recv_buffer.resize(bytes_read);
con->recv_queue.write(std::move(con->recv_buffer));
con->recv_buffer.resize(frame_queue::max_frame_size);
while (con->recv_queue.packet_ready()) {
try {
auto pkt = pktfty.make_packet(con->recv_queue.read_packet());
if (con->plr == PLR_BROADCAST) {
handle_recv_newplr(con, *pkt);
} else {
con->timeout = timeout_active;
handle_recv_packet(*pkt);
}
} catch (dvlnet_exception &e) {
drop_connection(con);
return;
}
}
start_recv(con);
}
void tcp_server::send_connect(scc con)
{
auto pkt = pktfty.make_packet<PT_CONNECT>(PLR_MASTER, PLR_BROADCAST,
con->plr);
send_packet(*pkt);
}
void tcp_server::handle_recv_newplr(scc con, packet& pkt)
{
auto newplr = next_free();
if (newplr == PLR_BROADCAST)
throw server_exception();
if (empty())
game_init_info = pkt.info();
auto reply = pktfty.make_packet<PT_JOIN_ACCEPT>(PLR_MASTER, PLR_BROADCAST,
pkt.cookie(), newplr,
game_init_info);
start_send(con, *reply);
con->plr = newplr;
connections[newplr] = con;
con->timeout = timeout_active;
send_connect(con);
}
void tcp_server::handle_recv_packet(packet& pkt)
{
send_packet(pkt);
}
void tcp_server::send_packet(packet& pkt)
{
if (pkt.dest() == PLR_BROADCAST) {
for (auto i = 0; i < MAX_PLRS; ++i)
if (i != pkt.src() && connections[i])
start_send(connections[i], pkt);
} else {
if (pkt.dest() >= MAX_PLRS)
throw server_exception();
if ((pkt.dest() != pkt.src()) && connections[pkt.dest()])
start_send(connections[pkt.dest()], pkt);
}
}
void tcp_server::start_send(scc con, packet& pkt)
{
auto frame = std::make_shared<buffer_t>(frame_queue::make_frame(pkt.data()));
auto buf = asio::buffer(*frame);
asio::async_write(con->socket, buf,
[this, con, frame = std::move(frame)]
(const asio::error_code &ec, size_t bytes_sent) {
handle_send(con, ec, bytes_sent);
});
}
void tcp_server::handle_send(scc con, const asio::error_code& ec,
size_t bytes_sent)
{
// empty for now
}
void tcp_server::start_accept()
{
auto nextcon = make_connection();
acceptor->async_accept(nextcon->socket,
std::bind(&tcp_server::handle_accept,
this, nextcon,
std::placeholders::_1));
}
void tcp_server::handle_accept(scc con, const asio::error_code& ec)
{
if (next_free() == PLR_BROADCAST) {
drop_connection(con);
} else {
asio::ip::tcp::no_delay option(true);
con->socket.set_option(option);
con->timeout = timeout_connect;
start_recv(con);
start_timeout(con);
}
start_accept();
}
void tcp_server::start_timeout(scc con)
{
con->timer.expires_after(std::chrono::seconds(1));
con->timer.async_wait(std::bind(&tcp_server::handle_timeout, this, con,
std::placeholders::_1));
}
void tcp_server::handle_timeout(scc con, const asio::error_code& ec)
{
if (ec) {
drop_connection(con);
return;
}
if (con->timeout > 0)
con->timeout -= 1;
if (con->timeout < 0)
con->timeout = 0;
if (!con->timeout) {
drop_connection(con);
return;
}
start_timeout(con);
}
void tcp_server::drop_connection(scc con)
{
if (con->plr != PLR_BROADCAST) {
auto pkt = pktfty.make_packet<PT_DISCONNECT>(PLR_MASTER, PLR_BROADCAST,
con->plr, LEAVE_DROP);
connections[con->plr] = nullptr;
send_packet(*pkt);
// TODO: investigate if it is really ok for the server to
// drop a client directly.
}
con->timer.cancel();
con->socket.close();
}
} // namespace net
} // namespace dvl

Loading…
Cancel
Save