43 Commits

Author SHA1 Message Date
0c82680af0 refactor DataBufferOperators
All checks were successful
Linux arm64 / Build (push) Successful in 17s
2025-08-03 19:46:11 +02:00
695d15588f don't use insert 2025-08-03 18:39:13 +02:00
7eb96163ab fix SerializableMessage
All checks were successful
Linux arm64 / Build (push) Successful in 16s
2025-08-01 12:52:46 +02:00
b8dafa4eb1 refactor DataBuffer
All checks were successful
Linux arm64 / Build (push) Successful in 16s
2025-08-01 12:44:05 +02:00
c5b3281be7 remove unused include 2025-08-01 12:41:22 +02:00
9374332cd2 refactor SerializableMessage
All checks were successful
Linux arm64 / Build (push) Successful in 16s
2025-07-31 17:56:01 +02:00
bce37f59df allow message serialization through DataBuffer
All checks were successful
Linux arm64 / Build (push) Successful in 15s
2025-07-31 15:01:37 +02:00
a1a4176801 add more DataBuffer serialization types
All checks were successful
Linux arm64 / Build (push) Successful in 17s
2025-07-31 14:12:33 +02:00
5e9a0a9bae remove unused files
All checks were successful
Linux arm64 / Build (push) Successful in 17s
2025-07-29 14:52:49 +02:00
01e406cd89 refactor bitfield io 2025-07-29 14:52:22 +02:00
366a40afee optional dispatch definition 2025-07-29 09:27:19 +02:00
45a3c427fb fix concretemessage
All checks were successful
Linux arm64 / Build (push) Successful in 16s
2025-07-18 17:27:58 +02:00
143b2f357c use generic handler
All checks were successful
Linux arm64 / Build (push) Successful in 15s
2025-07-18 16:45:27 +02:00
2149172b41 redundant template parameter
All checks were successful
Linux arm64 / Build (push) Successful in 1m3s
2025-07-18 16:20:17 +02:00
4c5c859221 Merge pull request 'v2.0' (#15) from v2.0 into main
All checks were successful
Linux arm64 / Build (push) Successful in 14s
Reviewed-on: #15
2025-07-10 13:15:42 +00:00
632650e73d refactor
All checks were successful
Linux arm64 / Build (push) Successful in 15s
2025-07-10 15:13:22 +02:00
4aa25c4189 add asserts
All checks were successful
Linux arm64 / Build (push) Successful in 15s
2025-07-10 11:57:39 +02:00
6f667deece add BitField
All checks were successful
Linux arm64 / Build (push) Successful in 15s
2025-06-30 12:08:16 +02:00
6c24280690 use std::decay 2025-06-30 12:07:59 +02:00
ed0b06f78d finish io
All checks were successful
Linux arm64 / Build (push) Successful in 19s
2025-06-27 18:53:03 +02:00
0d26879152 feat: add streams
All checks were successful
Linux arm64 / Build (push) Successful in 16s
2025-06-26 19:17:52 +02:00
59bedd6482 read/write for basic types
All checks were successful
Linux arm64 / Build (push) Successful in 14s
2025-06-26 16:02:18 +02:00
073872df94 byte swapping
All checks were successful
Linux arm64 / Build (push) Successful in 15s
2025-06-26 14:43:27 +02:00
10b49b34dd working dispatcher + factory
All checks were successful
Linux arm64 / Build (push) Successful in 1m17s
2025-06-25 19:33:11 +02:00
194205be41 Update README.md
All checks were successful
Linux arm64 / Build (push) Successful in 16s
2025-03-19 18:07:52 +00:00
392fcb3d17 add operator<< for packets (#14)
All checks were successful
Linux arm64 / Build (push) Successful in 16s
Reviewed-on: #14
Co-authored-by: Persson-dev <sim16.prib@gmail.com>
Co-committed-by: Persson-dev <sim16.prib@gmail.com>
2025-03-13 15:20:37 +00:00
205c09a338 move SocketHandle to TcpTag
All checks were successful
Linux arm64 / Build (push) Successful in 1m3s
2025-03-12 19:07:29 +01:00
77356ce749 better print override
All checks were successful
Linux arm64 / Build (push) Successful in 15s
2025-03-04 22:45:50 +01:00
7f8d9e3f96 add msg.ToString()
All checks were successful
Linux arm64 / Build (push) Successful in 15s
2025-03-04 20:26:42 +01:00
81c9dbadd6 fix printing
All checks were successful
Linux arm64 / Build (push) Successful in 15s
2025-03-04 12:23:59 +01:00
e8367cd91c MessagePrinter (Fixes #2)
All checks were successful
Linux arm64 / Build (push) Successful in 15s
2025-03-04 12:01:15 +01:00
fa6ba74068 operator<< for VarInt 2025-03-04 11:44:40 +01:00
d501c0181d fix encapsulate/decapsulated
All checks were successful
Linux arm64 / Build (push) Successful in 14s
2025-03-04 10:59:18 +01:00
680f72f881 emplace instead of push 2025-03-03 22:55:17 +01:00
4a7eb7a1df Add TCP support (#12)
All checks were successful
Linux arm64 / Build (push) Successful in 15s
Reviewed-on: #12
Co-authored-by: Persson-dev <sim16.prib@gmail.com>
Co-committed-by: Persson-dev <sim16.prib@gmail.com>
2025-03-03 10:04:41 +00:00
61d0ce3a47 Add basic CI (#10)
All checks were successful
Linux arm64 / Build (push) Successful in 15s
Reviewed-on: #10
Co-authored-by: Persson-dev <sim16.prib@gmail.com>
Co-committed-by: Persson-dev <sim16.prib@gmail.com>
2025-03-01 18:38:41 +00:00
5beb5e92a7 zlib support (#9)
Reviewed-on: #9
Co-authored-by: Persson-dev <sim16.prib@gmail.com>
Co-committed-by: Persson-dev <sim16.prib@gmail.com>
2025-03-01 18:20:51 +00:00
59aaf03421 serialize id with VarInt 2025-02-26 12:23:26 +01:00
03d799e064 Add generic IO (#3)
Reviewed-on: #3
Co-authored-by: Persson-dev <sim16.prib@gmail.com>
Co-committed-by: Persson-dev <sim16.prib@gmail.com>
2025-02-26 09:29:31 +00:00
8a5286d0ce move ArrayFiller in its own file 2025-02-25 20:30:26 +01:00
a194774925 add MessageDispatcher 2025-02-25 20:29:59 +01:00
8f32b09b17 add extensions include 2025-02-25 18:29:55 +01:00
60bb4ea06e begin compression module 2025-02-25 14:06:56 +01:00
59 changed files with 2430 additions and 1466 deletions

31
.github/workflows/ubuntu.yml vendored Normal file
View File

@@ -0,0 +1,31 @@
name: Linux arm64
run-name: Build And Test
on: [push]
env:
XMAKE_ROOT: y
jobs:
Build:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v3
- name: Prepare XMake
uses: xmake-io/github-action-setup-xmake@v1
with:
xmake-version: latest
actions-cache-folder: '.xmake-cache'
actions-cache-key: 'ubuntu'
- name: XMake config
run: xmake f -p linux -y
- name: Build
run: xmake
- name: Test
run: xmake test

2
.gitignore vendored
View File

@@ -7,3 +7,5 @@ build/
.vscode .vscode
*.bin

View File

@@ -1,6 +1,6 @@
# SimpleProtocolLib # SimpleProtocolLib
Network engine used to (mainly) communicate with packets C++ Protocol engine used to (mainly) communicate with network
# Integrate with xmake # Integrate with xmake

View File

@@ -4,17 +4,28 @@
#include <sp/protocol/Field.h> #include <sp/protocol/Field.h>
#include <sp/protocol/MessageBase.h> #include <sp/protocol/MessageBase.h>
enum class DisconnectFieldsE {
Reason = 0 struct DisconnectPacketData {
std::string m_Reason;
}; };
using DisconnectFields = std::tuple<std::string /*Reason*/>; class DisconnectPacket : public sp::MessageBase<sp::PacketMessage, sp::option::DispatchImpl<DisconnectPacket>> {
private:
DisconnectPacketData m_Data;
DeclarePacket(Disconnect){
public: public:
PacketConstructor(Disconnect) template<typename ... T>
DisconnectPacket(T... args) : m_Data{args...} {}
const std::string& GetReason() const { const std::string& GetReason() const {
return GetField<DisconnectFieldsE, DisconnectFieldsE::Reason>(); return m_Data.m_Reason;
}
virtual sp::PacketID GetId() const {
return Disconnect;
} }
}; };
void ff() {
sizeof(std::string);
}

View File

@@ -4,19 +4,40 @@
#include <sp/protocol/Field.h> #include <sp/protocol/Field.h>
#include <sp/protocol/MessageBase.h> #include <sp/protocol/MessageBase.h>
enum class KeepAliveFieldsE {
KeepAliveId = 0,
};
using KeepAliveFields = std::tuple< template <sp::PacketID ID, typename TData>
std::uint64_t //<- KeepAliveId class ConcreteMessage {
>;
DeclarePacket(KeepAlive){
public: public:
PacketConstructor(KeepAlive) using DataType = TData;
std::uint64_t GetKeepAliveId() const { template<typename... T>
return GetField<KeepAliveFieldsE, KeepAliveFieldsE::KeepAliveId>(); ConcreteMessage(const T&... args) : m_Data {args ...};
private:
DataType m_Data;
virtual sp::PacketID GetId() const {
return ID;
}
};
struct KeepAlivePacket {
std::uint64_t m_AliveId;
};
class KeepAliveMessage : public sp::MessageBase<sp::PacketMessage, sp::option::DispatchImpl<DisconnectPacket>> {
private:
KeepAlivePacket m_Data;
public:
template <typename... T>
KeepAliveMessage(T... args) : m_Data{args...} {}
virtual sp::PacketID GetId() const {
return KeepAlive;
} }
}; };

View File

@@ -1,17 +1,18 @@
#pragma once #pragma once
enum PacketId { enum PacketIds {
KeepAlive = 0, KeepAlive = 0,
Disconnect, Disconnect,
UpgradeTower, UpgradeTower,
}; };
#include <examples/KeepAlivePacket.h>
#include <examples/DisconnectPacket.h> #include <examples/DisconnectPacket.h>
#include <examples/KeepAlivePacket.h>
#include <examples/UpgradeTowerPacket.h> #include <examples/UpgradeTowerPacket.h>
// they must be in the same order as in the enum ! // they must be in the same order as in the enum !
using AllPackets = std::tuple<KeepAlivePacket, DisconnectPacket, UpgradeTowerPacket>; using AllPackets = std::tuple<KeepAlivePacket, DisconnectPacket, UpgradeTowerPacket>;
#include <sp/default/DefaultPacketHandler.h> #include <sp/default/DefaultPacketDispatcher.h>
#include <sp/default/DefaultPacketFactory.h> #include <sp/default/DefaultPacketFactory.h>
#include <sp/default/DefaultPacketHandler.h>

View File

@@ -4,28 +4,36 @@
#include <sp/protocol/Field.h> #include <sp/protocol/Field.h>
#include <sp/protocol/MessageBase.h> #include <sp/protocol/MessageBase.h>
struct UpgradeTowerPacketData {
enum class UpgradeTowerFieldsE { sp::BitField<std::uint16_t,
m_Tower = 0, sp::Field<std::uint16_t, 12>, // std::uint16_t m_Tower : 12;
m_Upgrade, sp::Field<std::uint8_t, 4> // std::uint8_t m_Upgrade : 4;
> m_TowerAndUpgrade;
sp::VarInt m_Test;
std::map<std::string, std::vector<int>> m_Test2;
}; };
using UpgradeTowerFields = std::tuple< class UpgradeTowerPacket : public sp::MessageBase<sp::PacketMessage, sp::option::DispatchImpl<UpgradeTowerPacket>> {
sp::BitField<std::uint16_t, private:
sp::Field<std::uint16_t, 12>, //<- m_Tower UpgradeTowerPacketData m_Data;
sp::Field<std::uint8_t, 4> //<- m_Upgrade
>
>;
DeclarePacket(UpgradeTower){
public: public:
PacketConstructor(UpgradeTower) template <typename... T>
UpgradeTowerPacket(T... args) : m_Data{args...} {}
std::uint16_t GetTowerId() const { std::uint16_t GetTowerId() const {
return GetField<0>().GetField<UpgradeTowerFieldsE, UpgradeTowerFieldsE::m_Tower>(); return m_Data.m_TowerAndUpgrade.GetField<0>();
} }
std::uint8_t GetTowerUpgrade() const { std::uint8_t GetTowerUpgrade() const {
return GetField<0>().GetField<UpgradeTowerFieldsE, UpgradeTowerFieldsE::m_Upgrade>(); return m_Data.m_TowerAndUpgrade.GetField<1>();
}
virtual sp::PacketID GetId() const {
return UpgradeTower;
}
UpgradeTowerPacketData& GetData() {
return m_Data;
} }
}; };

View File

@@ -4,51 +4,13 @@
namespace sp { namespace sp {
bool IsSystemBigEndian(); bool IsLittleEndian();
/** void SwapBytes(std::uint8_t* begin, std::uint8_t* end);
* \brief Serialize value to (network byte order) big endian
*/
template <typename T>
void ToNetwork(T& value) {}
template <> template<typename T>
void ToNetwork<std::uint16_t>(std::uint16_t& value); void SwapBytes(T& a_Data) {
SwapBytes(reinterpret_cast<std::uint8_t*>(&a_Data), reinterpret_cast<std::uint8_t*>(&a_Data) + sizeof(T));
template <> }
void ToNetwork<std::uint32_t>(std::uint32_t& value);
template <>
void ToNetwork<std::uint64_t>(std::uint64_t& value);
/**
* \brief Deserialize value from (network byte order) big endian
*/
template <typename T>
void FromNetwork(T& value) {}
template <>
void FromNetwork<std::uint16_t>(std::uint16_t& value);
template <>
void FromNetwork<std::uint32_t>(std::uint32_t& value);
template <>
void FromNetwork<std::uint64_t>(std::uint64_t& value);
/**
* \brief Swap bytes if the value is any kind of integer
*/
template <typename T>
void TrySwapBytes(T& value) {}
template <>
void TrySwapBytes<std::uint16_t>(std::uint16_t& value);
template <>
void TrySwapBytes<std::uint32_t>(std::uint32_t& value);
template <>
void TrySwapBytes<std::uint64_t>(std::uint64_t& value);
} // namespace sp } // namespace sp

View File

@@ -10,10 +10,13 @@
#include <cassert> #include <cassert>
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
#include <string> #include <list>
#include <sp/common/VarInt.h>
#include <vector>
#include <map> #include <map>
#include <memory>
#include <sp/common/ByteSwapping.h>
#include <sp/common/VarInt.h>
#include <string>
#include <vector>
namespace sp { namespace sp {
@@ -23,18 +26,21 @@ namespace sp {
*/ */
class DataBuffer { class DataBuffer {
private: private:
typedef std::vector<std::uint8_t> Data; using Data = std::vector<std::uint8_t>;
private:
Data m_Buffer; Data m_Buffer;
std::size_t m_ReadOffset; std::size_t m_ReadOffset;
public: public:
typedef Data::iterator iterator; using iterator = Data::iterator;
typedef Data::const_iterator const_iterator; using const_iterator = Data::const_iterator;
typedef Data::reference reference; using reference = Data::reference;
typedef Data::const_reference const_reference; using const_reference = Data::const_reference;
typedef Data::difference_type difference_type; using difference_type = Data::difference_type;
DataBuffer(); DataBuffer();
DataBuffer(std::size_t a_InitialSize);
DataBuffer(const DataBuffer& other); DataBuffer(const DataBuffer& other);
DataBuffer(const DataBuffer& other, difference_type offset); DataBuffer(const DataBuffer& other, difference_type offset);
DataBuffer(DataBuffer&& other); DataBuffer(DataBuffer&& other);
@@ -45,143 +51,25 @@ class DataBuffer {
/** /**
* \brief Append data to the buffer * \brief Append data to the buffer
* \warning No endian checks
*/ */
template <typename T> template <typename T>
void Append(const T& data) { void Append(const T& a_Data) {
std::size_t size = sizeof(data); std::size_t size = sizeof(a_Data);
std::size_t end_pos = m_Buffer.size(); std::size_t end_pos = m_Buffer.size();
m_Buffer.resize(m_Buffer.size() + size); m_Buffer.resize(m_Buffer.size() + size);
std::memcpy(&m_Buffer[end_pos], &data, size); std::memcpy(&m_Buffer[end_pos], &a_Data, size);
} }
/** /**
* \brief Append data to the buffer * \brief Read data into a_Data
* \warning No endian checks
*/ */
template <typename T> template <typename T>
DataBuffer& operator<<(const T& data) { void Read(T& a_Data) {
Append(data);
return *this;
}
/**
* \brief Append a string to the buffer
* \warning Don't use it for binary data !
* \param str The string to append
*/
DataBuffer& operator<<(const std::string& str);
/**
* \brief Append data to the buffer from another const buffer
* \param data The buffer to append
*/
DataBuffer& operator<<(const DataBuffer& data);
/**
* \brief Append a vector to the buffer by first writing the size
* \param data The vector to append
*/
template <typename T>
DataBuffer& operator<<(const std::vector<T>& data) {
*this << VarInt{data.size()};
for (const auto& element : data) {
*this << element;
}
return *this;
}
/**
* \brief Append a map to the buffer by first writing the size
* \param data The map to append
*/
template <typename K, typename V>
DataBuffer& operator<<(const std::map<K, V>& data) {
*this << VarInt{data.size()};
for (const auto& element : data) {
*this << element.first << element.second;
}
return *this;
}
/**
* \brief Append an array to the buffer by first writing the size
* \param data The buffer to append
*/
template <typename T, std::size_t Size>
DataBuffer& operator<<(const std::array<T, Size>& data) {
for (const auto& element : data) {
*this << element;
}
return *this;
}
/**
* \brief Read some data from the buffer and assign to desired variable
*/
template <typename T>
DataBuffer& operator>>(T& data) {
assert(m_ReadOffset + sizeof(T) <= GetSize()); assert(m_ReadOffset + sizeof(T) <= GetSize());
data = *(reinterpret_cast<T*>(&m_Buffer[m_ReadOffset])); std::memcpy(&a_Data, m_Buffer.data() + m_ReadOffset, sizeof(T));
m_ReadOffset += sizeof(T); m_ReadOffset += sizeof(T);
return *this;
}
/**
* \brief Read some data from the buffer and assign to the new buffer
* \param data The buffer to assign
*/
DataBuffer& operator>>(DataBuffer& data);
/**
* \brief Read a string from the buffer
* \param str The string to assign in the new buffer
* \warning Don't use it for binary data !
*/
DataBuffer& operator>>(std::string& str);
/**
* \brief Read a vector (size + data) from the buffer
* \pre The vector is assumed to be empty
*/
template <typename T>
DataBuffer& operator>>(std::vector<T>& data) {
VarInt arraySize;
*this >> arraySize;
for (std::size_t i = 0; i < arraySize.GetValue(); i++) {
T newElement;
*this >> newElement;
data.push_back(newElement);
}
return *this;
}
/**
* \brief Read a map (size + data) from the buffer
* \pre The map is assumed to be empty
*/
template <typename K, typename V>
DataBuffer& operator>>(std::map<K, V>& data) {
VarInt mapSize;
*this >> mapSize;
for (std::size_t i = 0; i < mapSize.GetValue(); i++) {
K newKey;
V newValue;
*this >> newKey >> newValue;
data.insert({newKey, newValue});
}
return *this;
}
/**
* \brief Read an array from the buffer
*/
template <std::size_t Size, typename T>
DataBuffer& operator>>(std::array<T, Size>& data) {
for (std::size_t i = 0; i < Size; i++) {
T newElement;
*this >> newElement;
data[i] = newElement;
}
return *this;
} }
/** /**
@@ -235,7 +123,6 @@ class DataBuffer {
m_Buffer.reserve(amount); m_Buffer.reserve(amount);
} }
/** /**
* \brief Clear the buffer * \brief Clear the buffer
*/ */
@@ -324,8 +211,9 @@ class DataBuffer {
}; };
/** /**
* \brief Operator << to write a DataBuffer to an ostream * \brief Append data to the buffer from another const buffer
* \param data The buffer to append
*/ */
std::ostream& operator<<(std::ostream& os, const DataBuffer& buffer); DataBuffer& operator<<(DataBuffer& a_Buffer, const DataBuffer& data);
} // namespace sp } // namespace sp

View File

@@ -0,0 +1,455 @@
#pragma once
#include <boost/pfr.hpp>
#include <sp/common/DataBuffer.h>
namespace sp {
template <typename T>
using is_default_serializable = std::bool_constant<(std::is_class_v<T> && std::is_aggregate_v<T>) || !std::is_class_v<T>>;
template <typename T>
static constexpr bool is_default_serializable_v = is_default_serializable<T>::value;
namespace details {
template <typename T>
void WriteRaw(DataBuffer& a_Buffer, T a_Data);
template <typename T>
void WriteFields(DataBuffer& a_Buffer, const T& a_Data);
template <typename T>
void WriteSharedPtr(DataBuffer& a_Buffer, const std::shared_ptr<T>& a_Data);
template <typename T>
void WriteUniquePtr(DataBuffer& a_Buffer, const std::unique_ptr<T>& a_Data);
template <typename T>
void WriteVector(DataBuffer& a_Buffer, const std::vector<T>& a_Data);
template <typename T>
void WriteList(DataBuffer& a_Buffer, const std::list<T>& a_Data);
template <typename K, typename V>
void WriteMap(DataBuffer& a_Buffer, const std::map<K, V>& a_Data);
template <typename K, typename V>
void WriteUnorderedMap(DataBuffer& a_Buffer, const std::unordered_map<K, V>& a_Data);
template <typename K, typename V>
void WritePair(DataBuffer& a_Buffer, const std::pair<K, V>& a_Data);
template <typename T, std::size_t S>
void WriteArray(DataBuffer& a_Buffer, const std::array<T, S>& a_Data);
template <typename T>
void ReadRaw(DataBuffer& a_Buffer, T& a_Data);
template <typename T>
void ReadFields(DataBuffer& a_Buffer, T& a_Data);
template <typename T>
void ReadSharedPtr(DataBuffer& a_Buffer, std::shared_ptr<T>& a_Data);
template <typename T>
void ReadUniquePtr(DataBuffer& a_Buffer, std::unique_ptr<T>& a_Data);
template <typename T>
void ReadVector(DataBuffer& a_Buffer, std::vector<T>& a_Data);
template <typename T>
void ReadList(DataBuffer& a_Buffer, std::list<T>& a_Data);
template <typename K, typename V>
void ReadMap(DataBuffer& a_Buffer, std::map<K, V>& a_Data);
template <typename K, typename V>
void ReadUnorderedMap(DataBuffer& a_Buffer, std::unordered_map<K, V>& a_Data);
template <typename K, typename V>
void ReadPair(DataBuffer& a_Buffer, std::pair<K, V>& a_Data);
template <typename T, std::size_t S>
void ReadArray(DataBuffer& a_Buffer, std::array<T, S>& a_Data);
} // namespace details
/**
* \brief Append data to the buffer (converted to big endian)
*/
template <typename T, typename = typename std::enable_if_t<is_default_serializable_v<T>>>
DataBuffer& operator<<(DataBuffer& a_Buffer, const T& a_Data) {
if constexpr (std::is_class_v<T>) {
details::WriteFields(a_Buffer, a_Data);
} else {
details::WriteRaw(a_Buffer, a_Data);
}
return a_Buffer;
}
/**
* \brief Append a string to the buffer
* \warning Don't use it for binary data !
* \param str The string to append
*/
DataBuffer& operator<<(DataBuffer& a_Buffer, const std::string& str);
/**
* \brief Operator << to write a DataBuffer to an ostream
*/
std::ostream& operator<<(std::ostream& os, const DataBuffer& buffer);
/**
* \brief Append a pointer to the buffer
* \param data The data to append
*/
template <typename T, typename = typename std::enable_if_t<!std::is_abstract_v<T>>>
DataBuffer& operator<<(DataBuffer& a_Buffer, const std::shared_ptr<T>& a_Data) {
details::WriteSharedPtr(a_Buffer, a_Data);
return a_Buffer;
}
/**
* \brief Append a pointer to the buffer
* \param data The data to append
*/
template <typename T, typename = typename std::enable_if_t<!std::is_abstract_v<T>>>
DataBuffer& operator<<(DataBuffer& a_Buffer, const std::unique_ptr<T>& a_Data) {
details::WriteUniquePtr(a_Buffer, a_Data);
return a_Buffer;
}
/**
* \brief Append a vector to the buffer by first writing the size
* \param data The vector to append
*/
template <typename T>
DataBuffer& operator<<(DataBuffer& a_Buffer, const std::vector<T>& a_Data) {
details::WriteVector(a_Buffer, a_Data);
return a_Buffer;
}
/**
* \brief Append a list to the buffer by first writing the size
* \param data The list to append
*/
template <typename T>
DataBuffer& operator<<(DataBuffer& a_Buffer, const std::list<T>& a_Data) {
details::WriteList(a_Buffer, a_Data);
return a_Buffer;
}
/**
* \brief Append a map to the buffer by first writing the size
* \param data The map to append
*/
template <typename K, typename V>
DataBuffer& operator<<(DataBuffer& a_Buffer, const std::map<K, V>& a_Data) {
details::WriteMap(a_Buffer, a_Data);
return a_Buffer;
}
/**
* \brief Append a map to the buffer by first writing the size
* \param data The map to append
*/
template <typename K, typename V>
DataBuffer& operator<<(DataBuffer& a_Buffer, const std::unordered_map<K, V>& a_Data) {
details::WriteUnorderedMap(a_Buffer, a_Data);
return a_Buffer;
}
/**
* \brief Append a pair to the buffer
* \param data The pair to append
*/
template <typename K, typename V>
DataBuffer& operator<<(DataBuffer& a_Buffer, const std::pair<K, V>& a_Data) {
details::WritePair(a_Buffer, a_Data);
return a_Buffer;
}
/**
* \brief Append an array to the buffer by first writing the size
* \param data The buffer to append
*/
template <typename T, std::size_t Size>
DataBuffer& operator<<(DataBuffer& a_Buffer, const std::array<T, Size>& a_Data) {
details::WriteArray(a_Buffer, a_Data);
return a_Buffer;
}
/**
* \brief Read some data from the buffer and assign to desired variable
*/
template <typename T, typename = typename std::enable_if_t<is_default_serializable_v<T>>>
DataBuffer& operator>>(DataBuffer& a_Buffer, T& a_Data) {
if constexpr (std::is_class_v<T>) {
details::ReadFields(a_Buffer, a_Data);
} else {
details::ReadRaw(a_Buffer, a_Data);
}
return a_Buffer;
}
/**
* \brief Read some data from the buffer and assign to the new buffer
* \param data The buffer to assign
*/
DataBuffer& operator>>(DataBuffer& a_Buffer, DataBuffer& a_Data);
/**
* \brief Read a string from the buffer
* \param str The string to assign in the new buffer
* \warning Don't use it for binary data !
*/
DataBuffer& operator>>(DataBuffer& a_Buffer, std::string& str);
/**
* \brief Read a pointer
*/
template <typename T, typename = typename std::enable_if_t<!std::is_abstract_v<T>>>
DataBuffer& operator>>(DataBuffer& a_Buffer, std::shared_ptr<T>& a_Data) {
details::ReadSharedPtr(a_Buffer, a_Data);
return a_Buffer;
}
/**
* \brief Read a pointer
*/
template <typename T, typename = typename std::enable_if_t<!std::is_abstract_v<T>>>
DataBuffer& operator>>(DataBuffer& a_Buffer, std::unique_ptr<T>& a_Data) {
details::ReadUniquePtr(a_Buffer, a_Data);
return a_Buffer;
}
/**
* \brief Read a vector (size + data) from the buffer
* \pre The vector is assumed to be empty
*/
template <typename T>
DataBuffer& operator>>(DataBuffer& a_Buffer, std::vector<T>& a_Data) {
details::ReadVector(a_Buffer, a_Data);
return a_Buffer;
}
/**
* \brief Read a list (size + data) from the buffer
* \pre The list is assumed to be empty
*/
template <typename T>
DataBuffer& operator>>(DataBuffer& a_Buffer, std::list<T>& a_Data) {
details::ReadList(a_Buffer, a_Data);
return a_Buffer;
}
/**
* \brief Read a map (size + data) from the buffer
* \pre The map is assumed to be empty
*/
template <typename K, typename V>
DataBuffer& operator>>(DataBuffer& a_Buffer, std::map<K, V>& a_Data) {
details::ReadMap(a_Buffer, a_Data);
return a_Buffer;
}
/**
* \brief Read a map (size + data) from the buffer
* \pre The map is assumed to be empty
*/
template <typename K, typename V>
DataBuffer& operator>>(DataBuffer& a_Buffer, std::unordered_map<K, V>& a_Data) {
details::ReadUnorderedMap(a_Buffer, a_Data);
return a_Buffer;
}
/**
* \brief Read a pair
*/
template <typename K, typename V>
DataBuffer& operator>>(DataBuffer& a_Buffer, std::pair<K, V>& a_Data) {
details::ReadPair(a_Buffer, a_Data);
return a_Buffer;
}
/**
* \brief Read an array from the buffer
*/
template <std::size_t Size, typename T>
DataBuffer& operator>>(DataBuffer& a_Buffer, std::array<T, Size>& a_Data) {
details::ReadArray(a_Buffer, a_Data);
return a_Buffer;
}
namespace details {
template <typename T>
void WriteRaw(DataBuffer& a_Buffer, T a_Data) {
a_Buffer.Append(a_Data);
SwapBytes(a_Buffer.data() + a_Buffer.GetReadOffset() - sizeof(T), a_Buffer.data() + a_Buffer.GetReadOffset());
}
template <typename T>
void WriteFields(DataBuffer& a_Buffer, const T& a_Data) {
boost::pfr::for_each_field(a_Data, [&a_Buffer](const auto& a_Field) { a_Buffer << a_Field; });
}
template <typename T>
void WriteSharedPtr(DataBuffer& a_Buffer, const std::shared_ptr<T>& a_Data) {
a_Buffer << *a_Data;
}
template <typename T>
void WriteUniquePtr(DataBuffer& a_Buffer, const std::unique_ptr<T>& a_Data) {
a_Buffer << *a_Data;
}
template <typename T>
void WriteVector(DataBuffer& a_Buffer, const std::vector<T>& a_Data) {
a_Buffer << VarInt{a_Data.size()};
for (const auto& element : a_Data) {
a_Buffer << element;
}
}
template <typename T>
void WriteList(DataBuffer& a_Buffer, const std::list<T>& a_Data) {
a_Buffer << VarInt{a_Data.size()};
for (const auto& element : a_Data) {
a_Buffer << element;
}
}
template <typename K, typename V>
void WriteMap(DataBuffer& a_Buffer, const std::map<K, V>& a_Data) {
a_Buffer << VarInt{a_Data.size()};
for (const auto& [key, value] : a_Data) {
a_Buffer << key << value;
}
}
template <typename K, typename V>
void WriteUnorderedMap(DataBuffer& a_Buffer, const std::unordered_map<K, V>& a_Data) {
a_Buffer << VarInt{a_Data.size()};
for (const auto& [key, value] : a_Data) {
a_Buffer << key << value;
}
}
template <typename K, typename V>
void WritePair(DataBuffer& a_Buffer, const std::pair<K, V>& a_Data) {
a_Buffer << a_Data.first << a_Data.second;
}
template <typename T, std::size_t S>
void WriteArray(DataBuffer& a_Buffer, const std::array<T, S>& a_Data) {
for (const auto& element : a_Data) {
a_Buffer << element;
}
}
template <typename T>
void ReadRaw(DataBuffer& a_Buffer, T& a_Data) {
a_Buffer.Read(a_Data);
SwapBytes(a_Data);
}
template <typename T>
void ReadFields(DataBuffer& a_Buffer, T& a_Data) {
boost::pfr::for_each_field(a_Data, [&a_Buffer](auto& a_Field) { a_Buffer >> a_Field; });
}
template <typename T>
void ReadSharedPtr(DataBuffer& a_Buffer, std::shared_ptr<T>& a_Data) {
a_Data = std::make_shared<T>();
a_Buffer >> *a_Data;
}
template <typename T>
void ReadUniquePtr(DataBuffer& a_Buffer, std::unique_ptr<T>& a_Data) {
a_Data = std::make_unique<T>();
a_Buffer >> *a_Data;
}
template <typename T>
void ReadVector(DataBuffer& a_Buffer, std::vector<T>& a_Data) {
VarInt arraySize;
a_Buffer >> arraySize;
for (std::size_t i = 0; i < arraySize.GetValue(); i++) {
T newElement;
a_Buffer >> newElement;
a_Data.push_back(newElement);
}
}
template <typename T>
void ReadList(DataBuffer& a_Buffer, std::list<T>& a_Data) {
VarInt arraySize;
a_Buffer >> arraySize;
for (std::size_t i = 0; i < arraySize.GetValue(); i++) {
T newElement;
a_Buffer >> newElement;
a_Data.push_back(newElement);
}
}
template <typename K, typename V>
void ReadMap(DataBuffer& a_Buffer, std::map<K, V>& a_Data) {
VarInt mapSize;
a_Buffer >> mapSize;
for (std::size_t i = 0; i < mapSize.GetValue(); i++) {
K newKey;
V newValue;
a_Buffer >> newKey >> newValue;
a_Data.emplace(newKey, newValue);
}
}
template <typename K, typename V>
void ReadUnorderedMap(DataBuffer& a_Buffer, std::unordered_map<K, V>& a_Data) {
VarInt mapSize;
a_Buffer >> mapSize;
for (std::size_t i = 0; i < mapSize.GetValue(); i++) {
K newKey;
V newValue;
a_Buffer >> newKey >> newValue;
a_Data.emplace(newKey, newValue);
}
}
template <typename K, typename V>
void ReadPair(DataBuffer& a_Buffer, std::pair<K, V>& a_Data) {
a_Buffer >> a_Data.first >> a_Data.a_Data;
}
template <typename T, std::size_t S>
void ReadArray(DataBuffer& a_Buffer, std::array<T, S>& a_Data) {
for (std::size_t i = 0; i < S; i++) {
T newElement;
a_Buffer >> newElement;
a_Data[i] = newElement;
}
}
} // namespace details
} // namespace sp

View File

@@ -0,0 +1,139 @@
#pragma once
#include <tuple>
namespace sp
{
// This class is inspired by https://arobenko.gitbooks.io/comms-protocols-cpp/content/
// TCommon is common interface class for all the messages
// TAll is all the message types, that need to be handled, bundled in std::tuple
template <typename TAll>
class GenericHandler;
// Big boy to process packets 20 by 20, preventing needlessly copying vtable many times at each inheritance stage
template <typename T1, typename T2, typename T3, typename T4, typename T5,
typename T6, typename T7, typename T8, typename T9, typename T10,
typename T11, typename T12, typename T13, typename T14, typename T15,
typename T16, typename T17, typename T18, typename T19, typename T20,
typename... TRest>
class GenericHandler<std::tuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T11, T13, T14, T15, T16, T17, T18, T19, T20, TRest...> > : public GenericHandler<std::tuple<TRest...> >
{
using Base = GenericHandler<std::tuple<TRest...> >;
public:
using Base::Handle; // Don't hide all Handle() functions from base classes
virtual void Handle(const T1& msg) {}
virtual void Handle(const T2& msg) {}
virtual void Handle(const T3& msg) {}
virtual void Handle(const T4& msg) {}
virtual void Handle(const T5& msg) {}
virtual void Handle(const T6& msg) {}
virtual void Handle(const T7& msg) {}
virtual void Handle(const T8& msg) {}
virtual void Handle(const T9& msg) {}
virtual void Handle(const T10& msg) {}
virtual void Handle(const T11& msg) {}
virtual void Handle(const T12& msg) {}
virtual void Handle(const T13& msg) {}
virtual void Handle(const T14& msg) {}
virtual void Handle(const T15& msg) {}
virtual void Handle(const T16& msg) {}
virtual void Handle(const T17& msg) {}
virtual void Handle(const T18& msg) {}
virtual void Handle(const T19& msg) {}
virtual void Handle(const T20& msg) {}
};
// 10 by 10
template <typename T1, typename T2, typename T3, typename T4, typename T5,
typename T6, typename T7, typename T8, typename T9, typename T10,
typename... TRest>
class GenericHandler<std::tuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, TRest...> > : public GenericHandler<std::tuple<TRest...> >
{
using Base = GenericHandler<std::tuple<TRest...> >;
public:
using Base::Handle; // Don't hide all Handle() functions from base classes
virtual void Handle(const T1& msg) {}
virtual void Handle(const T2& msg) {}
virtual void Handle(const T3& msg) {}
virtual void Handle(const T4& msg) {}
virtual void Handle(const T5& msg) {}
virtual void Handle(const T6& msg) {}
virtual void Handle(const T7& msg) {}
virtual void Handle(const T8& msg) {}
virtual void Handle(const T9& msg) {}
virtual void Handle(const T10& msg) {}
};
// 5 by 5
template <
typename T1, typename T2, typename T3, typename T4, typename T5,
typename... TRest>
class GenericHandler<std::tuple<T1, T2, T3, T4, T5, TRest...> > : public GenericHandler<std::tuple<TRest...> >
{
using Base = GenericHandler<std::tuple<TRest...> >;
public:
using Base::Handle; // Don't hide all Handle() functions from base classes
virtual void Handle(const T1& msg) {}
virtual void Handle(const T2& msg) {}
virtual void Handle(const T3& msg) {}
virtual void Handle(const T4& msg) {}
virtual void Handle(const T5& msg) {}
};
// Deal with rest with 4 types
template <typename T1, typename T2, typename T3, typename T4>
class GenericHandler<std::tuple<T1, T2, T3, T4> >
{
public:
virtual ~GenericHandler() {}
virtual void Handle(const T1& msg) {}
virtual void Handle(const T2& msg) {}
virtual void Handle(const T3& msg) {}
virtual void Handle(const T4& msg) {}
// virtual void Handle(const TCommon&) {} //Nothing to do
};
// Deal with rest with 3 types
template < typename T1, typename T2, typename T3>
class GenericHandler<std::tuple<T1, T2, T3> >
{
public:
virtual ~GenericHandler() {}
virtual void Handle(const T1& msg) {}
virtual void Handle(const T2& msg) {}
virtual void Handle(const T3& msg) {}
// virtual void Handle(const TCommon&) {} //Nothing to do
};
// Deal with rest with 2 types
template <typename T1, typename T2>
class GenericHandler<std::tuple<T1, T2> >
{
public:
virtual ~GenericHandler() {}
virtual void Handle(const T1& msg) {}
virtual void Handle(const T2& msg) {}
// virtual void Handle(const TCommon&) {} //Nothing to do
};
// Deal with rest with 1 type
template <typename T1>
class GenericHandler<std::tuple<T1> >
{
public:
virtual ~GenericHandler() {}
virtual void Handle(const T1& msg) {}
// virtual void Handle(const TCommon&) {} //Nothing to do
};
// Deal with rest with 0 type
template <>
class GenericHandler<std::tuple<> >
{
public:
virtual ~GenericHandler() {}
virtual void Handle() {} //Nothing to do
};
} // sp

View File

@@ -0,0 +1,25 @@
#pragma once
/**
* \file NonCopyable.h
* \brief File containing the sp::NonCopyable class
*/
namespace sp {
/**
* \class NonCopyable
* \brief Class used to make a class non copyable
* \note Inherit from this class privately to make a class non copyable
*/
class NonCopyable {
public:
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator=(const NonCopyable&) = delete;
protected:
NonCopyable() {}
~NonCopyable() {}
};
} // namespace sp

View File

@@ -1,6 +1,8 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <string>
#include <tuple>
namespace sp { namespace sp {
@@ -50,4 +52,48 @@ void TupleForEach(TFunc&& func, TTuple&& tuple) {
details::TupleForEachHelper<TupleSize>::Exec(std::forward<TTuple>(tuple), std::forward<TFunc>(func)); details::TupleForEachHelper<TupleSize>::Exec(std::forward<TTuple>(tuple), std::forward<TFunc>(func));
} }
namespace details {
template <typename T, typename U = void>
struct is_mappish_impl : std::false_type {};
template <typename T>
struct is_mappish_impl<T, std::void_t<typename T::key_type, typename T::mapped_type,
decltype(std::declval<T&>()[std::declval<const typename T::key_type&>()])>> : std::true_type {};
template <typename T, typename U = void>
struct is_vectish_impl : std::false_type {};
template <typename T>
struct is_vectish_impl<T,
std::void_t<typename T::value_type, decltype(std::declval<T&>()[std::declval<const typename T::value_type&>()])>>
: std::true_type {};
template <typename T, typename U = void>
struct is_pairish_impl : std::false_type {};
template <typename T>
struct is_pairish_impl<T,
std::void_t<typename T::first_type, decltype(std::declval<T&>()[std::declval<const typename T::first_type&>()])>>
: std::true_type {};
template <typename T>
using is_general_t = std::integral_constant<bool,
(std::is_same_v<T, std::string> || (!std::is_same_v<T, char> && !std::is_same_v<T, unsigned char> && !std::is_arithmetic_v<T> &&
!is_pairish_impl<T>::value && !is_mappish_impl<T>::value && !is_vectish_impl<T>::value))>;
template <typename T>
using is_primitive =
std::integral_constant<bool, std::is_same_v<T, char> || std::is_same_v<T, unsigned char> || std::is_arithmetic_v<T>>;
} // namespace details
} // namespace sp } // namespace sp

View File

@@ -7,11 +7,14 @@
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <functional>
namespace sp { namespace sp {
class DataBuffer; class DataBuffer;
using ReadFunc = std::function<void(std::uint8_t&)>;
/** /**
* \class VarInt * \class VarInt
* \brief Variable-length format such that smaller numbers use fewer bytes. * \brief Variable-length format such that smaller numbers use fewer bytes.
@@ -21,6 +24,8 @@ class VarInt {
std::uint64_t m_Value; std::uint64_t m_Value;
public: public:
static const std::uint64_t MAX_VALUE = static_cast<std::uint64_t>(-1) >> 8;
VarInt() : m_Value(0) {} VarInt() : m_Value(0) {}
/** /**
* \brief Construct a variable integer from a value * \brief Construct a variable integer from a value
@@ -53,6 +58,8 @@ class VarInt {
* \param var The variable integer to deserialize * \param var The variable integer to deserialize
*/ */
friend DataBuffer& operator>>(DataBuffer& in, VarInt& var); friend DataBuffer& operator>>(DataBuffer& in, VarInt& var);
void Read(const ReadFunc& read);
}; };
} // namespace sp } // namespace sp

View File

@@ -1,30 +0,0 @@
#pragma once
#include <sp/protocol/GenericHandler.h>
#include <sp/protocol/Message.h>
namespace sp {
class PacketHandler;
using PacketMessage = Message<
option::MsgIdType<std::uint8_t>, // add id() operation
option::ReadOperations, // add read() operation
option::WriteOperations, // add write() operation
option::WriteId, // write id before data
option::Handler<PacketHandler> // add dispatch() operation
>;
#define PacketConstructor(packetName) \
packetName##Packet() {} \
template <typename... Args> \
packetName##Packet(Args... args) { \
Construct(args...); \
}
#define DeclarePacket(packetName) \
class packetName##Packet : public sp::MessageBase<sp::PacketMessage, sp::option::StaticNumIdImpl<packetName>, \
sp::option::DispatchImpl<packetName##Packet>, sp::option::FieldsImpl<packetName##Fields>>
} // namespace sp

View File

@@ -1,7 +0,0 @@
#pragma once
#include <sp/protocol/MessageFactory.h>
namespace sp {
using PacketFactory = sp::MessageFactory<sp::PacketMessage, AllPackets>;
} // namespace sp

View File

@@ -1,9 +0,0 @@
#pragma once
#include <sp/protocol/GenericHandler.h>
// the tuple AllPackets must be defined !
namespace sp {
class PacketHandler : public sp::GenericHandler<PacketMessage, AllPackets> {};
} // namespace sp

View File

@@ -0,0 +1,47 @@
#pragma once
/**
* \file Compression.h
* \brief File containing compress utilities
*/
#include <cstdint>
#include <sp/io/MessageEncapsulator.h>
namespace sp {
namespace zlib {
/**
* \brief Compress some data
* \param buffer the data to compress
* \return the compressed data
*/
DataBuffer Compress(const DataBuffer& buffer, std::size_t a_CompressionThreshold = 64);
/**
* \brief Uncompress some data
* \param buffer the data to uncompress
* \param packetLength lenght of data
* \return the uncompressed data
*/
DataBuffer Decompress(DataBuffer& buffer, std::uint64_t packetLength);
} // namespace zlib
class ZlibCompress : public MessageEncapsulator {
private:
std::size_t m_CompressionThreshold;
public:
ZlibCompress() : m_CompressionThreshold(64) {}
ZlibCompress(const ZlibCompress&) = default;
virtual ~ZlibCompress() {}
protected:
virtual DataBuffer EncapsulateImpl(const DataBuffer& a_Data) override;
virtual DataBuffer DecapsulateImpl(DataBuffer& a_Data) override;
};
} // namespace sp

View File

@@ -0,0 +1,9 @@
#pragma once
#if __has_include(<sp/extensions/Compress.h>)
#include <sp/extensions/Compress.h>
#endif
#if __has_include(<sp/extensions/Tcp.h>)
#include <sp/extensions/Tcp.h>
#endif

View File

@@ -0,0 +1,4 @@
#pragma once
#include <sp/extensions/tcp/TcpListener.h>
#include <sp/extensions/tcp/TcpSocket.h>

View File

@@ -0,0 +1,68 @@
#pragma once
#include <sp/extensions/tcp/TcpSocket.h>
#include <memory>
namespace sp {
namespace io {
/**
* \class TcpListener
*/
class TcpListener : private NonCopyable {
public:
using SocketHandle = TcpSocket::SocketHandle;
/**
* \brief Starts listening for guests to connect
* \param port The port to listen to
* \param maxConnexions The maximum amount of connexion that can happen at the same time. \n
* Every other guests will be kicked if this amount is reached.
* \return Whether this action was succesfull
*/
TcpListener(std::uint16_t a_Port, int a_MaxConnexions);
/**
* \brief Default destructor
*/
~TcpListener();
/**
* \brief Tries to accept an incoming request to connect
* \return the new socket if a new connexion was accepted or nullptr
*/
std::unique_ptr<TcpSocket> Accept();
/**
* \brief Closes the socket
*/
void Close();
/**
* \brief Allows to set the socket in non blocking/blocking mode
* \param a_Blocking If set to true, every call to Read will wait until the socket receives something
* \return true if the operation was successful
*/
bool SetBlocking(bool a_Blocking);
/**
* \brief Getter of the m_Port member
* \return The port which the socket listen to
*/
std::uint16_t GetListeningPort() const;
/**
* \brief Getter of the m_MaxConnections member
* \return The maximum amount of connexions that can happen at the same time.
*/
int GetMaximumConnections() const;
private:
SocketHandle m_Handle;
std::uint16_t m_Port;
int m_MaxConnections;
};
} // namespace io
} // namespace sp

View File

@@ -0,0 +1,79 @@
#pragma once
#include <sp/io/IoInterface.h>
namespace sp {
namespace io {
class TcpListener;
class SocketError : public std::exception {
private:
std::string m_Error;
public:
SocketError(std::string&& a_Msg) : m_Error(std::move(a_Msg)) {}
virtual const char* what() const noexcept override {
return m_Error.c_str();
}
};
class TcpSocket : public sp::IoInterface {
public:
using SocketHandle = int;
/**
* \enum Status
* \brief Describes the state of a socket
*/
enum class Status {
/** The socket is connected */
Connected,
/** The socket is not connected */
Disconnected,
/** Something bad happened */
Error,
};
TcpSocket();
TcpSocket(const std::string& a_Host, std::uint16_t a_Port);
TcpSocket(TcpSocket&& a_Other);
TcpSocket& operator=(TcpSocket&& a_Other);
virtual ~TcpSocket();
virtual DataBuffer Read(std::size_t a_Amount) override;
virtual void Write(const sp::DataBuffer& a_Data) override;
/**
* \brief Allows to set the socket in non blocking/blocking mode
* \param a_Block If set to true, every call to Read will wait until the socket receives something
* \return true if the operation was successful
*/
bool SetBlocking(bool a_Block);
/**
* \brief Getter of the m_Status member
* \return The TcpSocket::Status of this socket
*/
Status GetStatus() const;
/**
* \brief Disconnects the socket from the remote
* \note Does nothing if the socket is not connected. \n
* This function is also called by the destructor.
*/
void Disconnect();
private:
SocketHandle m_Handle;
Status m_Status;
void Connect(const std::string& a_Host, std::uint16_t a_Port);
friend class TcpListener;
};
} // namespace io
} // namespace sp

90
include/sp/io/BitBuffer.h Normal file
View File

@@ -0,0 +1,90 @@
#pragma once
#include <sp/common/DataBuffer.h>
#include <sp/protocol/BitField.h>
namespace sp {
// TODO: flush if offset exceeds 64
class BitBuffer {
private:
using Data = std::uint64_t;
private:
DataBuffer& m_Buffer;
Data m_Data;
std::size_t m_Offset;
bool m_WasBitField;
public:
BitBuffer(DataBuffer& a_Buffer) : m_Buffer(a_Buffer), m_Data(0), m_Offset(0), m_WasBitField(false) {}
void UpdateWrite(bool a_IsBitField) {
if (m_Offset == 0)
return;
if ((m_Offset % 8 == 0) || (!a_IsBitField && m_WasBitField)) {
Flush();
}
m_WasBitField = a_IsBitField;
}
void UpdateRead(bool a_IsBitField) {
if (m_Offset == 0)
return;
if ((m_Offset % 8 == 0) || (!a_IsBitField && m_WasBitField)) {
MoveReadOffset();
}
m_WasBitField = a_IsBitField;
}
template <typename T, std::size_t BitSize>
void Append(T a_Data) {
Data bin = static_cast<Data>(a_Data);
bin &= ((1 << BitSize) - 1); // prevents overflow
std::size_t pushCount = sizeof(Data) * 8 - m_Offset - BitSize;
m_Data |= bin << pushCount;
m_Offset += BitSize;
}
template <typename T, std::size_t BitSize>
void Read(T& a_Data) {
std::size_t byteCount = GetByteCount(m_Offset + BitSize);
constexpr Data dataMask = (1 << BitSize) - 1;
m_Buffer.ReadSome(reinterpret_cast<std::uint8_t*>(&m_Data), byteCount);
SwapBytes(reinterpret_cast<std::uint8_t*>(&m_Data), reinterpret_cast<std::uint8_t*>(&m_Data) + byteCount);
m_Data >>= byteCount * 8 - m_Offset - BitSize;
m_Data &= dataMask;
a_Data = T(m_Data);
m_Buffer.SetReadOffset(m_Buffer.GetReadOffset() - byteCount);
m_Offset += BitSize;
}
private:
void Flush() {
std::size_t byteCount = GetByteCount();
m_Data >>= (sizeof(Data) - byteCount) * 8;
SwapBytes(reinterpret_cast<std::uint8_t*>(&m_Data), reinterpret_cast<std::uint8_t*>(&m_Data) + byteCount);
m_Buffer.WriteSome(reinterpret_cast<std::uint8_t*>(&m_Data), byteCount);
m_Offset = 0;
m_WasBitField = false;
m_Data = 0;
}
void MoveReadOffset() {
std::size_t byteCount = GetByteCount();
m_Buffer.SetReadOffset(m_Buffer.GetReadOffset() + byteCount);
m_Offset = 0;
m_WasBitField = false;
m_Data = 0;
}
std::size_t GetByteCount(std::size_t a_Offset = -1) const {
if (a_Offset == static_cast<std::size_t>(-1))
a_Offset = m_Offset;
if (a_Offset <= 8)
return 1;
return (a_Offset - 1) / 8 + 1;
}
};
} // namespace sp

View File

@@ -0,0 +1,14 @@
#pragma once
#include <sp/common/DataBuffer.h>
#include <sp/common/NonCopyable.h>
namespace sp {
class IoInterface : private NonCopyable {
public:
virtual DataBuffer Read(std::size_t a_Amount) = 0;
virtual void Write(const DataBuffer& a_Data) = 0;
};
} // namespace sp

View File

@@ -0,0 +1,31 @@
#pragma once
#include <sp/common/DataBuffer.h>
namespace sp {
class MessageEncapsulator {
protected:
bool m_Enabled = true;
public:
MessageEncapsulator() {}
virtual ~MessageEncapsulator() {}
DataBuffer Encapsulate(const DataBuffer& a_Data) {
if (!m_Enabled)
return a_Data;
return EncapsulateImpl(a_Data);
}
DataBuffer Decapsulate(DataBuffer& a_Data) {
if (!m_Enabled)
return a_Data;
return DecapsulateImpl(a_Data);
}
protected:
virtual DataBuffer EncapsulateImpl(const DataBuffer& a_Data) = 0;
virtual DataBuffer DecapsulateImpl(DataBuffer& a_Data) = 0;
};
} // namespace sp

53
include/sp/io/MessageIO.h Normal file
View File

@@ -0,0 +1,53 @@
#pragma once
#include <sp/io/BitBuffer.h>
#include <sp/common/DataBufferOperators.h>
namespace sp {
namespace details {
template <typename T, std::size_t BitSize>
void WriteField(DataBuffer& a_Buffer, const BitField<T, BitSize>& a_Data, BitBuffer& a_BitBuffer) {
a_BitBuffer.Append<T, BitSize>(*a_Data);
a_BitBuffer.UpdateWrite(true);
}
template <typename T>
void WriteField(DataBuffer& a_Buffer, const T& a_Data, BitBuffer& a_BitBuffer) {
a_Buffer << a_Data;
a_BitBuffer.UpdateWrite(false);
}
template <typename T, std::size_t BitSize>
void ReadField(DataBuffer& a_Buffer, BitField<T, BitSize>& a_Data, BitBuffer& a_BitBuffer) {
a_BitBuffer.Read<T, BitSize>(*a_Data);
a_BitBuffer.UpdateRead(true);
}
template <typename T>
void ReadField(DataBuffer& a_Buffer, T& a_Data, BitBuffer& a_BitBuffer) {
a_Buffer >> a_Data;
a_BitBuffer.UpdateRead(false);
}
} // namespace details
template <typename TData>
DataBuffer WriteMessage(const TData& a_MessageData) {
DataBuffer buffer;
BitBuffer bitBuffer(buffer);
boost::pfr::for_each_field(
a_MessageData, [&buffer, &bitBuffer](const auto& a_Field) { details::WriteField(buffer, a_Field, bitBuffer); });
bitBuffer.UpdateWrite(false);
return buffer;
}
template <typename TData>
void ReadMessage(DataBuffer& a_Buffer, TData& a_MessageData) {
BitBuffer bitBuffer(a_Buffer);
boost::pfr::for_each_field(
a_MessageData, [&a_Buffer, &bitBuffer](auto& a_Field) { details::ReadField(a_Buffer, a_Field, bitBuffer); });
bitBuffer.UpdateRead(false);
}
} // namespace sp

View File

@@ -0,0 +1,54 @@
#pragma once
#include <sp/common/DataBuffer.h>
#include <sp/io/IoInterface.h>
#include <sp/io/MessageEncapsulator.h>
#include <vector>
#include <memory>
namespace sp {
template <typename TMessageFactory>
class MessageStream {
protected:
std::vector<std::shared_ptr<MessageEncapsulator>> m_Encapsulators;
std::shared_ptr<IoInterface> m_Stream;
using MessageBaseType = typename TMessageFactory::MessageBaseType;
using MessageIdType = typename MessageBaseType::MessageIdType;
public:
MessageStream(std::shared_ptr<IoInterface>&& a_Stream) : m_Stream(std::move(a_Stream)) {}
template<typename... TEnc>
MessageStream(std::shared_ptr<IoInterface>&& a_Stream, TEnc&&... a_Encapsulators) :
m_Stream(std::move(a_Stream)){
m_Encapsulators.reserve(sizeof...(a_Encapsulators));
AddEncapsulators(std::move(a_Encapsulators ...));
}
std::unique_ptr<MessageBaseType> ReadMessage();
std::unique_ptr<MessageBaseType> ReadMessage(MessageIdType a_Id);
void WriteMessage(const MessageBaseType& a_Message, bool a_WriteId = true);
template<typename... Args>
void AddEncapsulators(Args&& ... a_Encapsulators) {
AddEncapsulators(std::move(std::make_tuple<>(a_Encapsulators ...)));
}
template<typename... Args>
void AddEncapsulators(std::tuple<Args...>&& a_Encapsulators) {
TupleForEach([this](auto&& a_Encapsulator){
m_Encapsulators.push_back(std::move(a_Encapsulator));
}, a_Encapsulators);
}
private:
DataBuffer ReadAndDecapsulate();
std::unique_ptr<MessageBaseType> MakeMessage(DataBuffer& buffer, MessageIdType a_Id);
};
} // namespace sp
#include <sp/io/MessageStream.inl>

View File

@@ -0,0 +1,66 @@
#pragma once
#include <sp/common/VarInt.h>
#include <sp/protocol/MessageFactory.h>
namespace sp {
template <typename TMessageFactory>
DataBuffer MessageStream<TMessageFactory>::ReadAndDecapsulate() {
VarInt messageLength;
messageLength.Read([this](std::uint8_t& data) {
DataBuffer buffer = m_Stream->Read(1);
data = *buffer.data();
});
std::size_t amount = messageLength.GetValue();
DataBuffer buffer = m_Stream->Read(amount);
for (auto& enc : m_Encapsulators) {
buffer = enc->Decapsulate(buffer);
}
return buffer;
}
template <typename TMessageFactory>
std::unique_ptr<typename TMessageFactory::MessageBaseType> MessageStream<TMessageFactory>::MakeMessage(DataBuffer& buffer, MessageIdType a_Id) {
static const TMessageFactory FACTORY;
auto message = FACTORY.CreateMessage(a_Id);
message->Read(buffer);
return message;
}
template <typename TMessageFactory>
std::unique_ptr<typename TMessageFactory::MessageBaseType> MessageStream<TMessageFactory>::ReadMessage(MessageIdType a_Id) {
DataBuffer buffer = ReadAndDecapsulate();
return MakeMessage(buffer, a_Id);
}
template <typename TMessageFactory>
std::unique_ptr<typename TMessageFactory::MessageBaseType> MessageStream<TMessageFactory>::ReadMessage() {
DataBuffer buffer = ReadAndDecapsulate();
VarInt messageId;
buffer >> messageId;
return MakeMessage(buffer, MessageIdType(messageId.GetValue()));
}
template <typename TMessageFactory>
void MessageStream<TMessageFactory>::WriteMessage(const MessageBaseType& a_Message, bool a_WriteId) {
DataBuffer buffer;
if (a_WriteId)
buffer << VarInt{static_cast<std::uint64_t>(a_Message.GetId())};
buffer << a_Message.Write();
for (auto& enc : m_Encapsulators) {
buffer = enc->Encapsulate(buffer);
}
DataBuffer header;
header << VarInt{buffer.GetSize()};
m_Stream->Write(header);
m_Stream->Write(buffer);
}
} // namespace sp

View File

@@ -0,0 +1,56 @@
#pragma once
#include <sp/common/DataBuffer.h>
namespace sp {
template <typename TMessageFactory>
class SerializableMessage {
using MessageBaseType = typename TMessageFactory::MessageBaseType;
using HandlerType = typename MessageBaseType::HandlerType;
using MessageIdType = typename MessageBaseType::MessageIdType;
public:
std::shared_ptr<MessageBaseType> m_Message;
SerializableMessage() {}
SerializableMessage(std::shared_ptr<MessageBaseType>&& a_Message) : m_Message(a_Message) {}
SerializableMessage(const std::shared_ptr<MessageBaseType>& a_Message) : m_Message(a_Message) {}
MessageBaseType* operator->() {
return m_Message.get();
}
operator bool() {
return m_Message.get();
}
const MessageBaseType* operator->() const {
return m_Message.get();
}
};
template <typename TMessageFactory>
DataBuffer& operator<<(DataBuffer& a_Buffer, const SerializableMessage<TMessageFactory>& a_Message) {
return a_Buffer << VarInt{static_cast<std::uint64_t>(a_Message->GetId())} << a_Message->Write();
}
template <typename TMessageFactory>
DataBuffer& operator>>(DataBuffer& a_Buffer, SerializableMessage<TMessageFactory>& a_Message) {
using MsgId = typename TMessageFactory::IdType;
using MsgBase = typename TMessageFactory::MessageBaseType;
static TMessageFactory factory;
VarInt msgId;
a_Buffer >> msgId;
auto msgPtr = std::shared_ptr<MsgBase>(factory.CreateMessage(MsgId(msgId.GetValue())).release());
a_Message = SerializableMessage<TMessageFactory>(msgPtr);
a_Message->Read(a_Buffer);
return a_Buffer;
}
} // namespace sp

43
include/sp/io/StdIo.h Normal file
View File

@@ -0,0 +1,43 @@
#pragma once
#include <sp/io/IoInterface.h>
#include <iostream>
namespace sp {
class StdInput : public IoInterface {
private:
std::istream& m_Io;
public:
StdInput(std::istream& a_Io) : m_Io(a_Io) {}
virtual DataBuffer Read(std::size_t a_Amount) override {
DataBuffer buffer(a_Amount);
m_Io.read(reinterpret_cast<char*>(buffer.data()), a_Amount);
return buffer;
}
virtual void Write(const DataBuffer& a_Data) override {
assert(!"Write not implemented !");
}
};
class StdOuput : public IoInterface {
private:
std::ostream& m_Io;
public:
StdOuput(std::ostream& a_Io) : m_Io(a_Io) {}
virtual DataBuffer Read(std::size_t a_Amount) override {
assert(!"Read not implemented !");
return {};
}
virtual void Write(const DataBuffer& a_Data) override {
m_Io.write(reinterpret_cast<const char*>(a_Data.data()), a_Data.GetSize());
}
};
} // namespace sp

View File

@@ -0,0 +1,37 @@
#pragma once
#include <cstdint>
namespace sp {
template <typename T, std::size_t BitSize>
class BitField {
private:
static constexpr int BITS_IN_CHAR = 8;
static_assert(sizeof(T) * BITS_IN_CHAR > BitSize, "The bit count must be lower than the actual type size !");
T m_Data;
public:
BitField() : m_Data{} {};
BitField(T a_Data) : m_Data(a_Data) {}
BitField& operator=(T a_Data) {
m_Data = a_Data;
return *this;
}
constexpr std::size_t GetBitSize() const {
return BitSize;
}
T& operator*() {
return m_Data;
}
const T& operator*() const {
return m_Data;
}
};
} // namespace sp

View File

@@ -0,0 +1,57 @@
#pragma once
#include <sp/protocol/MessageBase.h>
#include <sp/io/MessageIO.h>
namespace sp {
template <typename TData, typename MessageBase, typename MessageBase::MessageIdType ID, bool DefineDispatch = true>
class ConcreteMessage : public MessageBase {
public:
using DataType = TData;
using MessageIdType = typename MessageBase::MessageIdType;
using HandlerType = typename MessageBase::HandlerType;
template <typename... T>
ConcreteMessage(T&&... args) : m_Data{std::move(args)...} {}
virtual ~ConcreteMessage() {}
virtual MessageIdType GetId() const override {
return ID;
}
virtual void Dispatch(HandlerType& handler) const override {
if constexpr (DefineDispatch)
handler.Handle(*this);
}
virtual void Read(DataBuffer& a_Buffer) override {
ReadMessage(a_Buffer, m_Data);
}
virtual DataBuffer Write() const override {
return WriteMessage(m_Data);
}
DataType& operator*() {
return m_Data;
}
DataType* operator->() {
return &m_Data;
}
const DataType& operator*() const {
return m_Data;
}
const DataType* operator->() const {
return &m_Data;
}
private:
DataType m_Data;
};
} // namespace sp

View File

@@ -1,119 +0,0 @@
#pragma once
#include <sp/common/DataBuffer.h>
#include <sp/common/Templates.h>
namespace sp {
/**
* \brief Example usage :
* sp::BitField<std::uint16_t, sp::Field<std::uint16_t, 12>, sp::Field<std::uint8_t, 4>>;
*/
template <typename TContainer, typename... TFields>
class BitField {
using AllFields = std::tuple<TFields...>;
public:
template <typename... T>
BitField(const std::tuple<T...>& args) {
Apply<0, T...>(args);
}
BitField() {}
template <typename... T>
BitField& operator=(const std::tuple<T...>& args) {
Apply<0, T...>(args);
return *this;
}
AllFields& GetFields() {
return m_Fields;
}
const AllFields& GetFields() const {
return m_Fields;
}
template <std::size_t FIndex>
auto& GetField() {
return std::get<FIndex>(this->GetFields()).GetValue();
}
template <std::size_t FIndex>
const auto& GetField() const {
return std::get<FIndex>(this->GetFields()).GetValue();
}
// allow use of enums
template <typename E, E FIndex>
const auto& GetField() const {
return std::get<static_cast<std::size_t>(FIndex)>(this->GetFields()).GetValue();
}
private:
template <int IOffset, typename... T, std::enable_if_t<IOffset >= sizeof...(T), bool> = true>
void Apply(const std::tuple<T...>& args) {}
template <int IOffset, typename... T, std::enable_if_t<!(IOffset >= sizeof...(T)), bool> = true>
void Apply(const std::tuple<T...>& args) {
this->GetField<IOffset>() = std::get<IOffset>(args);
Apply<1 + IOffset, T...>(args);
}
TContainer m_Value;
AllFields m_Fields;
};
/**
*
* \tparam ValueType the type of the value to store
* \tparam IAlignment 0 means no alignment
*/
template <typename ValueType, int IAlignment>
class Field {
public:
// Provide an access to the stored value
ValueType& GetValue() {
return m_Value;
}
const ValueType& GetValue() const {
return m_Value;
}
Field& operator=(const ValueType& value) {
m_Value = value;
return *this;
}
constexpr int GetAlignment() const {
return IAlignment;
}
private:
ValueType m_Value;
};
namespace details {
template <typename... TFields>
struct FieldsBuilder {};
template <>
struct FieldsBuilder<> {
using Type = std::tuple<>;
};
template <typename... TFields>
struct FieldsBuilder<std::tuple<TFields...>> {
using Type = typename FieldsBuilder<TFields...>::Type;
};
template <typename TField, typename... TFields>
struct FieldsBuilder<TField, TFields...> {
using Type = sp::tuple_cat_t<std::tuple<Field<TField, 0>>, typename FieldsBuilder<TFields...>::Type>;
};
} // namespace details
} // namespace sp

View File

@@ -1,141 +0,0 @@
#pragma once
#include <tuple>
namespace sp
{
// This class is inspired by https://arobenko.gitbooks.io/comms-protocols-cpp/content/
// TCommon is common interface class for all the messages
// TAll is all the message types, that need to be handled, bundled in std::tuple
template <typename TCommon, typename TAll>
class GenericHandler;
// Big boy to process packets 20 by 20, preventing needlessly copying vtable many times at each inheritance stage
template <typename TCommon,
typename T1, typename T2, typename T3, typename T4, typename T5,
typename T6, typename T7, typename T8, typename T9, typename T10,
typename T11, typename T12, typename T13, typename T14, typename T15,
typename T16, typename T17, typename T18, typename T19, typename T20,
typename... TRest>
class GenericHandler<TCommon, std::tuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T11, T13, T14, T15, T16, T17, T18, T19, T20, TRest...> > : public GenericHandler<TCommon, std::tuple<TRest...> >
{
using Base = GenericHandler<TCommon, std::tuple<TRest...> >;
public:
using Base::Handle; // Don't hide all Handle() functions from base classes
virtual void Handle(const T1& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T2& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T3& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T4& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T5& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T6& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T7& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T8& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T9& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T10& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T11& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T12& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T13& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T14& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T15& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T16& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T17& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T18& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T19& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T20& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
};
// 10 by 10
template <typename TCommon,
typename T1, typename T2, typename T3, typename T4, typename T5,
typename T6, typename T7, typename T8, typename T9, typename T10,
typename... TRest>
class GenericHandler<TCommon, std::tuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, TRest...> > : public GenericHandler<TCommon, std::tuple<TRest...> >
{
using Base = GenericHandler<TCommon, std::tuple<TRest...> >;
public:
using Base::Handle; // Don't hide all Handle() functions from base classes
virtual void Handle(const T1& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T2& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T3& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T4& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T5& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T6& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T7& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T8& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T9& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T10& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
};
// 5 by 5
template <typename TCommon,
typename T1, typename T2, typename T3, typename T4, typename T5,
typename... TRest>
class GenericHandler<TCommon, std::tuple<T1, T2, T3, T4, T5, TRest...> > : public GenericHandler<TCommon, std::tuple<TRest...> >
{
using Base = GenericHandler<TCommon, std::tuple<TRest...> >;
public:
using Base::Handle; // Don't hide all Handle() functions from base classes
virtual void Handle(const T1& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T2& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T3& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T4& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T5& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
};
// Deal with rest with 4 types
template <typename TCommon, typename T1, typename T2, typename T3, typename T4>
class GenericHandler<TCommon, std::tuple<T1, T2, T3, T4> >
{
public:
virtual ~GenericHandler() {}
virtual void Handle(const T1& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T2& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T3& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T4& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const TCommon&) { } //Nothing to do
};
// Deal with rest with 3 types
template <typename TCommon, typename T1, typename T2, typename T3>
class GenericHandler<TCommon, std::tuple<T1, T2, T3> >
{
public:
virtual ~GenericHandler() {}
virtual void Handle(const T1& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T2& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T3& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const TCommon&) { } //Nothing to do
};
// Deal with rest with 2 types
template <typename TCommon, typename T1, typename T2>
class GenericHandler<TCommon, std::tuple<T1, T2> >
{
public:
virtual ~GenericHandler() {}
virtual void Handle(const T1& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const T2& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const TCommon&) { } //Nothing to do
};
// Deal with rest with 1 type
template <typename TCommon, typename T1>
class GenericHandler<TCommon, std::tuple<T1> >
{
public:
virtual ~GenericHandler() {}
virtual void Handle(const T1& msg) { this->Handle(static_cast<const TCommon&>(msg)); }
virtual void Handle(const TCommon&) { } //Nothing to do
};
// Deal with rest with 0 type
template <typename TCommon>
class GenericHandler<TCommon, std::tuple<> >
{
public:
virtual ~GenericHandler() {}
virtual void Handle(const TCommon&) { } //Nothing to do
};
} // sp

View File

@@ -1,16 +0,0 @@
#pragma once
// Inspired by
// https://alex-robenko.gitbook.io/comms-protocols-cpp
#include <sp/protocol/message/MessageInterfaceBuilder.h>
namespace sp {
template <typename... TOptions>
class Message : public details::MessageInterfaceBuilder<TOptions...>::Type {
public:
using ParsedOptions = typename details::MessageInterfaceBuilder<TOptions...>::ParsedOptions;
};
} // namespace sp

View File

@@ -1,16 +1,25 @@
#pragma once #pragma once
#include <sp/protocol/Message.h> #include <iosfwd>
#include <sp/common/DataBuffer.h>
#include <sp/protocol/message/MessageImplOptions.h>
#include <sp/protocol/message/MessagesImpl.h>
#include <sp/protocol/message/MessageImplProcess.h>
#include <sp/protocol/message/MessageImplBuilder.h>
namespace sp { namespace sp {
template <typename TBase, typename... TOptions> template <typename TMessageID, typename THandler>
class MessageBase : public details::MessageImplBuilder<TBase, TOptions...>::Type {}; class MessageBase {
public:
using HandlerType = THandler;
using MessageIdType = TMessageID;
MessageBase() {}
virtual ~MessageBase() {}
virtual MessageIdType GetId() const = 0;
virtual void Dispatch(HandlerType& handler) const = 0;
virtual void Read(DataBuffer& a_Buffer) = 0;
virtual DataBuffer Write() const = 0;
};
} // namespace sp } // namespace sp

View File

@@ -0,0 +1,62 @@
#pragma once
/**
* \file MessageDispatcher.h
* \brief File containing the sp::MessageDispatcher class
*/
#include <map>
#include <memory>
#include <vector>
namespace sp {
/**
* \class MessageDispatcher
* \brief Class used to dispatch messages
*/
template <typename MessageBase>
class MessageDispatcher {
public:
using MessageBaseType = MessageBase;
using MessageIdType = typename MessageBase::MessageIdType;
using MessageHandler = typename MessageBase::HandlerType;
/**
* \brief Constructor
*/
MessageDispatcher() {}
/**
* \brief Dispatch a packet
* \param packet The packet to dispatch
*/
void Dispatch(const MessageBase& a_Message);
/**
* \brief Register a packet handler
* \param type The packet type
* \param handler The packet handler
*/
void RegisterHandler(MessageIdType a_MessageType, MessageHandler* a_Handler);
/**
* \brief Unregister a packet handler
* \param type The packet type
* \param handler The packet handler
*/
void UnregisterHandler(MessageIdType a_MessageType, MessageHandler* a_Handler);
/**
* \brief Unregister a packet handler
* \param handler The packet handler
*/
void UnregisterHandler(MessageHandler* a_Handler);
private:
std::map<MessageIdType, std::vector<MessageHandler*>> m_Handlers;
};
} // namespace sp
#include <sp/protocol/MessageDispatcherImpl.inl>

View File

@@ -0,0 +1,43 @@
#pragma once
#include <algorithm>
#include <cassert>
namespace sp {
template <typename MessageBase>
void MessageDispatcher<MessageBase>::RegisterHandler(MessageIdType a_MessageType, MessageHandler* a_Handler) {
assert(a_Handler);
auto found = std::find(m_Handlers[a_MessageType].begin(), m_Handlers[a_MessageType].end(), a_Handler);
if (found == m_Handlers[a_MessageType].end())
m_Handlers[a_MessageType].push_back(a_Handler);
}
template <typename MessageBase>
void MessageDispatcher<MessageBase>::UnregisterHandler(MessageIdType a_MessageType, MessageHandler* a_Handler) {
auto found = std::find(m_Handlers[a_MessageType].begin(), m_Handlers[a_MessageType].end(), a_Handler);
if (found != m_Handlers[a_MessageType].end())
m_Handlers[a_MessageType].erase(found);
}
template <typename MessageBase>
void MessageDispatcher<MessageBase>::UnregisterHandler(MessageHandler* a_Handler) {
for (auto& pair : m_Handlers) {
if (pair.second.empty())
continue;
MessageIdType type = pair.first;
pair.second.erase(std::remove(pair.second.begin(), pair.second.end(), a_Handler), pair.second.end());
}
}
template <typename MessageBase>
void MessageDispatcher<MessageBase>::Dispatch(const MessageBase& a_Message) {
MessageIdType type = a_Message.GetId();
for (auto& handler : m_Handlers[type]) {
a_Message.Dispatch(*handler);
}
}
} // namespace sp

View File

@@ -2,63 +2,37 @@
#include <array> #include <array>
#include <functional> #include <functional>
#include <iostream>
#include <memory> #include <memory>
#include <sp/common/Tuples.h>
namespace sp { namespace sp {
namespace details {
template <typename TBase>
using ArrayType = std::vector<std::function<std::unique_ptr<TBase>(void)>>;
template <typename TBase, typename... TMessages>
struct ArrayFiller {};
template <typename TBase, typename... TMessages>
struct ArrayFiller<TBase, std::tuple<TMessages...>> {
static ArrayType<TBase> ArrayCreate() {
ArrayType<TBase> array;
array.reserve(sizeof...(TMessages));
ArrayFiller<TBase, TMessages...>::ArrayAppend(array);
return array;
}
};
template <typename TBase, typename TMessage, typename... TMessages>
struct ArrayFiller<TBase, TMessage, TMessages...> {
static void ArrayAppend(details::ArrayType<TBase>& array) {
ArrayFiller<TBase, TMessage>::ArrayAppend(array);
ArrayFiller<TBase, TMessages...>::ArrayAppend(array);
}
};
template <typename TBase, typename TMessage>
struct ArrayFiller<TBase, TMessage> {
static void ArrayAppend(details::ArrayType<TBase>& array) {
array.push_back([]() -> std::unique_ptr<TBase> { return std::make_unique<TMessage>(); });
}
};
} // namespace details
template <typename TBase, typename TTMessages> template <typename TBase, typename TTMessages>
class MessageFactory { class MessageFactory {
public: public:
using IdType = typename TBase::MsgIdType; using IdType = typename TBase::MessageIdType;
using MessageBaseType = TBase;
MessageFactory() : m_Factory(details::ArrayFiller<TBase, TTMessages>::ArrayCreate()) {} MessageFactory() {
constexpr std::size_t messageCount = std::tuple_size_v<TTMessages>;
m_Factory.resize(messageCount);
TupleForEach([this](const auto& message){
std::size_t messageID = static_cast<std::size_t>(message.GetId());
using MessageType = std::decay_t<decltype(message)>;
m_Factory.emplace(m_Factory.begin() + messageID, []() -> std::unique_ptr<TBase> { return std::make_unique<MessageType>(); });
}, TTMessages{});
}
std::unique_ptr<TBase> CreateMessage(IdType id) { std::unique_ptr<TBase> CreateMessage(IdType id) const {
if (id >= m_Factory.size()) std::size_t idSize = static_cast<std::size_t>(id);
if (idSize >= m_Factory.size())
return nullptr; return nullptr;
return m_Factory.at(id)(); return m_Factory.at(idSize)();
} }
private: private:
details::ArrayType<TBase> m_Factory; std::vector<std::function<std::unique_ptr<TBase>(void)>> m_Factory;
}; };

View File

@@ -1,45 +0,0 @@
#pragma once
namespace sp {
namespace details {
// TBase is interface class
// TOptions... are the implementation options
template <typename TBase, typename... TOptions>
struct MessageImplBuilder {
// ParsedOptions class is supposed to be defined in comms::Message class
using InterfaceOptions = typename TBase::ParsedOptions;
// Parse implementation options
using ImplOptions = MessageImplParsedOptions<TOptions...>;
// Provide GetIdImpl() if possible
static const bool HasStaticNumIdImpl = InterfaceOptions::HasMsgIdType && ImplOptions::HasStaticNumIdImpl;
using Base1 = typename MessageImplProcessStaticNumId<TBase, ImplOptions, HasStaticNumIdImpl>::Type;
// Provide DispatchImpl() if possible
static const bool HasDispatchImpl = InterfaceOptions::HasHandler && ImplOptions::HasDispatchImpl;
using Base2 = typename MessageImplProcessDispatch<Base1, ImplOptions, HasDispatchImpl>::Type;
// Provide access to fields if possible
using Base3 = typename MessageImplProcessFields<Base2, ImplOptions, ImplOptions::HasFieldsImpl>::Type;
// Provide ReadImpl() if possible
static const bool HasReadImpl = InterfaceOptions::HasReadOperations && ImplOptions::HasFieldsImpl;
using Base4 = typename MessageImplProcessReadFields<Base3, HasReadImpl>::Type;
// Provide WriteImpl() if possible
static const bool HasWriteImpl = InterfaceOptions::HasWriteOperations && ImplOptions::HasFieldsImpl;
using Base5 = typename MessageImplProcessWriteFields<Base4, HasWriteImpl>::Type;
// Provide ValidImpl() if possible
static const bool HasValidImpl = InterfaceOptions::HasValid && ImplOptions::HasFieldsImpl;
using Base6 = typename MessageImplProcessValidFields<Base5, HasValidImpl>::Type;
// The last BaseN must be taken as final type.
using Type = Base6;
};
} // namespace details
} // namespace sp

View File

@@ -1,61 +0,0 @@
#pragma once
namespace sp {
namespace option {
// Provide static numeric ID, to facilitate implementation of GetIdImpl()
template <std::intmax_t TId>
struct StaticNumIdImpl {};
// Facilitate implementation of DispatchImpl()
template <typename TActual>
struct DispatchImpl {};
// Provide fields of the message, facilitate implementation of
// ReadImpl(), WriteImpl(), ValidImpl(), etc...
template <typename TFields>
struct FieldsImpl {};
} // namespace option
namespace details {
template <typename... TOptions>
class MessageImplParsedOptions;
template <>
struct MessageImplParsedOptions<> {
static const bool HasStaticNumIdImpl = false;
static const bool HasDispatchImpl = false;
static const bool HasFieldsImpl = false;
};
template <std::intmax_t TId, typename... TOptions>
struct MessageImplParsedOptions<option::StaticNumIdImpl<TId>, TOptions...> : public MessageImplParsedOptions<TOptions...> {
static const bool HasStaticNumIdImpl = true;
static const std::intmax_t MsgId = TId;
};
template <typename TActual, typename... TOptions>
struct MessageImplParsedOptions<option::DispatchImpl<TActual>, TOptions...> : public MessageImplParsedOptions<TOptions...> {
static const bool HasDispatchImpl = true;
using ActualMessage = TActual;
};
template <typename TFields, typename... TOptions>
struct MessageImplParsedOptions<option::FieldsImpl<TFields>, TOptions...> : public MessageImplParsedOptions<TOptions...> {
static const bool HasFieldsImpl = true;
using Fields = TFields;
};
} // namespace details
} // namespace sp

View File

@@ -1,93 +0,0 @@
#pragma once
namespace sp {
namespace details {
// id impl
template <typename TBase, typename ParsedImplOptions, bool TImplement>
struct MessageImplProcessStaticNumId;
template <typename TBase, typename ParsedImplOptions>
struct MessageImplProcessStaticNumId<TBase, ParsedImplOptions, true> {
using Type = MessageImplStaticNumIdBase<TBase, ParsedImplOptions::MsgId>;
};
template <typename TBase, typename ParsedImplOptions>
struct MessageImplProcessStaticNumId<TBase, ParsedImplOptions, false> {
using Type = TBase;
};
// dispatch impl
template <typename TBase, typename ParsedImplOptions, bool TImplement>
struct MessageImplProcessDispatch;
template <typename TBase, typename ParsedImplOptions>
struct MessageImplProcessDispatch<TBase, ParsedImplOptions, true> {
using Type = MessageImplDispatchBase<TBase, typename ParsedImplOptions::ActualMessage>;
};
template <typename TBase, typename ParsedImplOptions>
struct MessageImplProcessDispatch<TBase, ParsedImplOptions, false> {
using Type = TBase;
};
// fields impl
template <typename TBase, typename ParsedImplOptions, bool TImplement>
struct MessageImplProcessFields;
template <typename TBase, typename ParsedImplOptions>
struct MessageImplProcessFields<TBase, ParsedImplOptions, true> {
using Type = MessageImplFieldsBase<TBase, typename ParsedImplOptions::Fields>;
};
template <typename TBase, typename ParsedImplOptions>
struct MessageImplProcessFields<TBase, ParsedImplOptions, false> {
using Type = TBase;
};
// read impl
template <typename TBase, bool TImplement>
struct MessageImplProcessReadFields;
template <typename TBase>
struct MessageImplProcessReadFields<TBase, true> {
using Type = MessageImplFieldsReadBase<TBase>;
};
template <typename TBase>
struct MessageImplProcessReadFields<TBase, false> {
using Type = TBase;
};
// write impl
template <typename TBase, bool TImplement>
struct MessageImplProcessWriteFields;
template <typename TBase>
struct MessageImplProcessWriteFields<TBase, true> {
using Type = MessageImplFieldsWriteBase<TBase>;
};
template <typename TBase>
struct MessageImplProcessWriteFields<TBase, false> {
using Type = TBase;
};
// valid impl
template <typename TBase, bool TImplement>
struct MessageImplProcessValidFields;
template <typename TBase>
struct MessageImplProcessValidFields<TBase, true> {
using Type = MessageImplFieldsValidBase<TBase>;
};
template <typename TBase>
struct MessageImplProcessValidFields<TBase, false> {
using Type = TBase;
};
} // namespace details
} // namespace sp

View File

@@ -1,41 +0,0 @@
#pragma once
#include <sp/protocol/message/MessageInterfaces.h>
namespace sp {
namespace details {
class EmptyBase {};
template <typename... TOptions>
struct MessageInterfaceBuilder {
// Parse the options
using ParsedOptions = MessageInterfaceParsedOptions<TOptions...>;
// Add ID retrieval functionality if ID type was provided
using Base1 = typename MessageInterfaceProcessMsgId<EmptyBase, ParsedOptions, ParsedOptions::HasMsgIdType>::Type;
// Add ReadData() and WriteData(), that use the right endian
using Base2 = typename MessageInterfaceProcessEndian<Base1, ParsedOptions::HasLittleEndian>::Type;
// Add read functionality if Read type was provided
using Base3 = typename MessageInterfaceProcessRead<Base2, ParsedOptions::HasReadOperations>::Type;
// Add write functionality if Write type was provided
using Base4 = typename MessageInterfaceProcessWrite<Base3, ParsedOptions::HasWriteOperations>::Type;
// add dispatch functionality if Handler type was provided
using Base5 = typename MessageInterfaceProcessHandler<Base4, ParsedOptions, ParsedOptions::HasHandler>::Type;
// add valid functionality if Valid tpe was provided
using Base6 = typename MessageInterfaceProcessValid<Base5, ParsedOptions::HasValid>::Type;
// add write id functionality if write id and write was provided
using Base7 = typename MessageInterfaceProcessWriteId<Base6, ParsedOptions::HasWriteId && ParsedOptions::HasWriteOperations>::Type;
// The last Base7 must be taken as final type.
using Type = Base7;
};
} // namespace details
} // namespace sp

View File

@@ -1,105 +0,0 @@
#pragma once
namespace sp {
namespace details {
// Build message Id
template <typename TBase, typename TParsedOptions, bool THasMsgIdType>
struct MessageInterfaceProcessMsgId;
template <typename TBase, typename TParsedOptions>
struct MessageInterfaceProcessMsgId<TBase, TParsedOptions, true> {
using Type = MessageInterfaceIdTypeBase<TBase, typename TParsedOptions::MsgIdType>;
};
template <typename TBase, typename TParsedOptions>
struct MessageInterfaceProcessMsgId<TBase, TParsedOptions, false> {
using Type = TBase;
};
// Build endianess
template <typename TBase, bool THasLittleEndian>
struct MessageInterfaceProcessEndian;
template <typename TBase>
struct MessageInterfaceProcessEndian<TBase, true> {
using Type = MessageInterfaceLittleEndian<TBase>;
};
template <typename TBase>
struct MessageInterfaceProcessEndian<TBase, false> {
using Type = MessageInterfaceBigEndian<TBase>;
};
// Build read
template <typename TBase, bool THasRead>
struct MessageInterfaceProcessRead;
template <typename TBase>
struct MessageInterfaceProcessRead<TBase, true> {
using Type = MessageInterfaceReadBase<TBase>;
};
template <typename TBase>
struct MessageInterfaceProcessRead<TBase, false> {
using Type = TBase;
};
// Build write
template <typename TBase, bool THasWrite>
struct MessageInterfaceProcessWrite;
template <typename TBase>
struct MessageInterfaceProcessWrite<TBase, true> {
using Type = MessageInterfaceWriteBase<TBase>;
};
template <typename TBase>
struct MessageInterfaceProcessWrite<TBase, false> {
using Type = TBase;
};
// Build handler
template <typename TBase, typename TParsedOptions, bool THasHandler>
struct MessageInterfaceProcessHandler;
template <typename TBase, typename TParsedOptions>
struct MessageInterfaceProcessHandler<TBase, TParsedOptions, true> {
using Type = MessageInterfaceHandlerBase<TBase, typename TParsedOptions::HandlerType>;
};
template <typename TBase, typename TParsedOptions>
struct MessageInterfaceProcessHandler<TBase, TParsedOptions, false> {
using Type = TBase;
};
// Build valid
template <typename TBase, bool THasValid>
struct MessageInterfaceProcessValid;
template <typename TBase>
struct MessageInterfaceProcessValid<TBase, true> {
using Type = MessageInterfaceValidityBase<TBase>;
};
template <typename TBase>
struct MessageInterfaceProcessValid<TBase, false> {
using Type = TBase;
};
// Build id writing
template <typename TBase, bool THasValid>
struct MessageInterfaceProcessWriteId;
template <typename TBase>
struct MessageInterfaceProcessWriteId<TBase, true> {
using Type = MessageInterfaceWriteIdBase<TBase>;
};
template <typename TBase>
struct MessageInterfaceProcessWriteId<TBase, false> {
using Type = TBase;
};
} // namespace details
} // namespace sp

View File

@@ -1,7 +0,0 @@
#pragma once
#include <sp/protocol/message/MessageOptions.h>
#include <sp/protocol/message/MessageInterfacesOptions.h>
#include <sp/protocol/message/MessageInterfacesImpl.h>
#include <sp/protocol/message/MessageInterfaceProcess.h>

View File

@@ -1,119 +0,0 @@
#pragma once
#include <sp/common/ByteSwapping.h>
namespace sp {
namespace details {
// ID retrieval chunk
template <typename TBase, typename TId>
class MessageInterfaceIdTypeBase : public TBase {
public:
using MsgIdType = TId;
MsgIdType GetId() const {
return GetIdImpl();
}
protected:
virtual MsgIdType GetIdImpl() const = 0;
};
// Big endian serialisation chunk
template <typename TBase>
class MessageInterfaceBigEndian : public TBase {
protected:
template <typename T>
void ReadData(T& value, DataBuffer& buffer) {
buffer >> value;
FromNetwork(value);
}
template <typename T>
void WriteData(T value, DataBuffer& buffer) const {
ToNetwork(value);
buffer << value;
}
};
// Little endian serialisation chunk
template <typename TBase>
class MessageInterfaceLittleEndian : public TBase {
protected:
template <typename T>
void ReadData(T& value, DataBuffer& buffer) {
buffer >> value;
TrySwapBytes(value);
FromNetwork(value);
}
template <typename T>
void WriteData(const T& value, DataBuffer& buffer) {
ToNetwork(value);
TrySwapBytes(value);
buffer << value;
}
};
// Read functionality chunk
template <typename TBase>
class MessageInterfaceReadBase : public TBase {
public:
void Read(DataBuffer& buffer) {
return ReadImpl(buffer);
}
protected:
virtual void ReadImpl(DataBuffer& buffer) = 0;
};
// Write functionality chunk
template <typename TBase>
class MessageInterfaceWriteBase : public TBase {
public:
void Write(DataBuffer& buffer) const {
WriteImpl(buffer);
}
protected:
virtual void WriteImpl(DataBuffer& buffer) const = 0;
};
// Handler functionality chunk
template <typename TBase, typename THandler>
class MessageInterfaceHandlerBase : public TBase {
public:
using HandlerType = typename THandler::HandlerT;
void Dispatch(HandlerType& handler) const {
DispatchImpl(handler);
}
protected:
virtual void DispatchImpl(HandlerType& handler) const = 0;
};
// Validity functionality chunk
template <typename TBase>
class MessageInterfaceValidityBase : public TBase {
public:
bool Valid() const {
return ValidImpl();
}
protected:
virtual bool ValidImpl() const = 0;
};
// Writing id functionality chunk
template <typename TBase>
class MessageInterfaceWriteIdBase : public TBase {
public:
void Write(DataBuffer& buffer) const {
this->WriteData(this->GetId(), buffer);
this->WriteImpl(buffer);
}
};
} // namespace details
} // namespace sp

View File

@@ -1,63 +0,0 @@
#pragma once
namespace sp {
namespace details {
template <typename... TOptions>
struct MessageInterfaceParsedOptions {};
template <>
struct MessageInterfaceParsedOptions<> {
static const bool HasMsgIdType = false;
static const bool HasLittleEndian = false;
static const bool HasReadOperations = false;
static const bool HasWriteOperations = false;
static const bool HasWriteId = false;
static const bool HasHandler = false;
static const bool HasValid = false;
};
template <typename T, typename... TOptions>
struct MessageInterfaceParsedOptions<option::MsgIdType<T>, TOptions...> : public MessageInterfaceParsedOptions<TOptions...> {
static const bool HasMsgIdType = true;
using MsgIdType = T;
};
template <typename... TOptions>
struct MessageInterfaceParsedOptions<option::LittleEndian, TOptions...> : public MessageInterfaceParsedOptions<TOptions...> {
static const bool HasLittleEndian = true;
};
template <typename... TOptions>
struct MessageInterfaceParsedOptions<option::ReadOperations, TOptions...> : public MessageInterfaceParsedOptions<TOptions...> {
static const bool HasReadOperations = true;
};
template <typename... TOptions>
struct MessageInterfaceParsedOptions<option::WriteOperations, TOptions...> : public MessageInterfaceParsedOptions<TOptions...> {
static const bool HasWriteOperations = true;
};
template <typename... TOptions>
struct MessageInterfaceParsedOptions<option::WriteId, TOptions...> : public MessageInterfaceParsedOptions<TOptions...> {
static const bool HasWriteId = true;
};
template <typename T, typename... TOptions>
struct MessageInterfaceParsedOptions<option::Handler<T>, TOptions...> : public MessageInterfaceParsedOptions<TOptions...> {
static const bool HasHandler = true;
using HandlerType = option::Handler<T>;
};
template <typename... TOptions>
struct MessageInterfaceParsedOptions<option::ValidCheckInterface, TOptions...> : public MessageInterfaceParsedOptions<TOptions...> {
static const bool HasValid = true;
};
} // namespace details
} // namespace sp

View File

@@ -1,34 +0,0 @@
#pragma once
#include <sp/common/DataBuffer.h>
namespace sp {
namespace option {
// Define type used to store message ID
template <typename T>
struct MsgIdType {};
// Enable reading
struct ReadOperations {};
// Enable writing
struct WriteOperations {};
// Enable id writing
struct WriteId {};
// Use little endian for serialisation (instead of default big)
struct LittleEndian {};
// Include validity check in public interface
struct ValidCheckInterface {};
// Define handler class
template <typename T>
struct Handler {
using HandlerT = T;
};
} // namespace option
} // namespace sp

View File

@@ -1,160 +0,0 @@
#pragma once
namespace sp {
namespace details {
// ID information chunk
template <typename TBase, std::intmax_t TId>
class MessageImplStaticNumIdBase : public TBase {
public:
// Reuse the message ID type defined in the interface
using MsgIdType = typename TBase::MsgIdType;
protected:
virtual MsgIdType GetIdImpl() const override {
return static_cast<MsgIdType>(TId);
}
};
// Dispatch implementation chunk
template <typename TBase, typename TActual>
class MessageImplDispatchBase : public TBase {
public:
// Reuse the Handler type defined in the interface class
using Handler = typename TBase::HandlerType;
protected:
virtual void DispatchImpl(Handler& handler) const override {
handler.Handle(static_cast<const TActual&>(*this));
}
};
template <typename TBase, typename TFields>
class MessageImplFieldsBase : public TBase {
public:
using AllFields = typename details::FieldsBuilder<TFields>::Type;
template <typename... Args>
void Construct(Args... args) {
m_Fields = std::make_tuple(args...);
}
AllFields& GetFields() {
return m_Fields;
}
const AllFields& GetFields() const {
return m_Fields;
}
template <std::size_t FIndex>
auto& GetField() {
return std::get<FIndex>(GetFields()).GetValue();
}
template <std::size_t FIndex>
const auto& GetField() const {
return std::get<FIndex>(GetFields()).GetValue();
}
// allow use of enums
template <typename E, E FIndex>
const auto& GetField() const {
return std::get<static_cast<std::size_t>(FIndex)>(this->GetFields()).GetValue();
}
private:
AllFields m_Fields;
};
template <typename TBase>
class MessageImplFieldsReadBase : public TBase {
private:
// normal reading
template <typename TField>
void ReadField(Field<TField, 0>& field, DataBuffer& buffer) {
this->ReadData(field.GetValue(), buffer);
}
// reading field in bitfield
template <typename TFieldType, typename TField, int IAlignment>
void ReadField(Field<TField, IAlignment>& field, TFieldType data, std::size_t offset) {
static constexpr std::size_t TotalBitCount = sizeof(TFieldType) * 8;
// we suppose that the first element is at the highest bits
field.GetValue() = (data >> TotalBitCount - IAlignment - offset) & ((1 << IAlignment) - 1);
}
// reading bitfield
template <typename TContainer, typename TFirst, typename... TFields>
void ReadField(Field<BitField<TContainer, TFirst, TFields...>, 0>& field, DataBuffer& buffer) {
TContainer data;
this->ReadData(data, buffer);
std::size_t offset = 0;
TupleForEach(
[data, this, &offset](auto& field) {
this->ReadField(field, data, offset);
offset += field.GetAlignment();
},
field.GetValue().GetFields());
}
void ReadImpl(DataBuffer& buffer) override {
auto& allFields = this->GetFields();
TupleForEach([&buffer, this](auto& field) { this->ReadField(field, buffer); }, allFields);
}
};
template <typename TBase>
class MessageImplFieldsWriteBase : public TBase {
private:
// normal writing
template <typename TField>
void WriteField(const Field<TField, 0>& field, DataBuffer& buffer) const {
this->WriteData(field.GetValue(), buffer);
}
// writing field in bitfield
template <typename TFieldType, typename TField, int IAlignment>
void WriteField(const Field<TField, IAlignment>& field, TFieldType& data, std::size_t offset) const {
static constexpr std::size_t TotalBitCount = sizeof(TFieldType) * 8;
// we suppose that the first element is at the highest bits
data |= (field.GetValue() & ((1 << IAlignment) - 1)) << TotalBitCount - IAlignment - offset;
}
// writing bitfield
template <typename TContainer, typename TFirst, typename... TFields>
void WriteField(const Field<BitField<TContainer, TFirst, TFields...>, 0>& field, DataBuffer& buffer) const {
TContainer data = 0;
std::size_t offset = 0;
TupleForEach(
[&data, this, &offset](auto& field) {
this->WriteField(field, data, offset);
offset += field.GetAlignment();
},
field.GetValue().GetFields());
this->WriteData(data, buffer);
}
void WriteImpl(DataBuffer& buffer) const override {
auto& allFields = this->GetFields();
TupleForEach([&buffer, this](const auto& field) { this->WriteField(field, buffer); }, allFields);
}
};
template <typename TBase>
class MessageImplFieldsValidBase : public TBase {
protected:
bool ValidImpl() const override {
// Access fields via interface provided in previous chunk
// auto& allFields = TBase::GetFields();
//... // validate all the fields
return true;
}
};
} // namespace details
} // namespace sp

View File

@@ -1,74 +1,21 @@
#include <sp/common/ByteSwapping.h> #include <sp/common/ByteSwapping.h>
#ifdef _WIN32
#include <winsock2.h>
#else
#include <arpa/inet.h>
#endif
#include <algorithm> #include <algorithm>
namespace sp { namespace sp {
template <typename T> bool IsLittleEndian() {
void SwapBytes(T& value) { #ifdef SP_BIG_ENDIAN
char* ptr = reinterpret_cast<char*>(&value); return false;
std::reverse(ptr, ptr + sizeof(T)); #else
return true;
#endif
} }
bool IsSystemBigEndian() { void SwapBytes(std::uint8_t* begin, std::uint8_t* end) {
static constexpr std::uint16_t test = 10; if (IsLittleEndian()) {
static const bool isBigEndian = reinterpret_cast<const std::uint8_t*>(&test)[1] == 10; std::reverse(begin, end);
return isBigEndian; }
}
template <>
void ToNetwork<std::uint16_t>(std::uint16_t& value) {
value = htons(value);
}
template <>
void ToNetwork<std::uint32_t>(std::uint32_t& value) {
value = htonl(value);
}
template <>
void ToNetwork<std::uint64_t>(std::uint64_t& value) {
if (IsSystemBigEndian())
return;
SwapBytes(value);
}
template <>
void FromNetwork<std::uint16_t>(std::uint16_t& value) {
value = ntohs(value);
}
template <>
void FromNetwork<std::uint32_t>(std::uint32_t& value) {
value = ntohl(value);
}
template <>
void FromNetwork<std::uint64_t>(std::uint64_t& value) {
if (IsSystemBigEndian())
return;
SwapBytes(value);
}
template <>
void TrySwapBytes<std::uint16_t>(std::uint16_t& value) {
SwapBytes(value);
}
template <>
void TrySwapBytes<std::uint32_t>(std::uint32_t& value) {
SwapBytes(value);
}
template <>
void TrySwapBytes<std::uint64_t>(std::uint64_t& value) {
SwapBytes(value);
} }
} // namespace sp } // namespace sp

View File

@@ -8,6 +8,8 @@ namespace sp {
DataBuffer::DataBuffer() : m_ReadOffset(0) {} DataBuffer::DataBuffer() : m_ReadOffset(0) {}
DataBuffer::DataBuffer(std::size_t a_InitialSize) : m_Buffer(a_InitialSize), m_ReadOffset(0) {}
DataBuffer::DataBuffer(const DataBuffer& other) : m_Buffer(other.m_Buffer), m_ReadOffset(other.m_ReadOffset) {} DataBuffer::DataBuffer(const DataBuffer& other) : m_Buffer(other.m_Buffer), m_ReadOffset(other.m_ReadOffset) {}
DataBuffer::DataBuffer(DataBuffer&& other) : m_Buffer(std::move(other.m_Buffer)), m_ReadOffset(std::move(other.m_ReadOffset)) {} DataBuffer::DataBuffer(DataBuffer&& other) : m_Buffer(std::move(other.m_Buffer)), m_ReadOffset(std::move(other.m_ReadOffset)) {}
@@ -19,33 +21,35 @@ DataBuffer::DataBuffer(const DataBuffer& other, Data::difference_type offset) :
std::copy(other.m_Buffer.begin() + offset, other.m_Buffer.end(), std::back_inserter(m_Buffer)); std::copy(other.m_Buffer.begin() + offset, other.m_Buffer.end(), std::back_inserter(m_Buffer));
} }
DataBuffer& DataBuffer::operator<<(const std::string& str) { DataBuffer& operator<<(DataBuffer& a_Buffer, const std::string& str) {
std::size_t strlen = str.length() + 1; // including null character std::size_t strlen = str.length() + 1; // including null character
Resize(GetSize() + strlen); a_Buffer.Resize(a_Buffer.GetSize() + strlen);
std::memcpy(m_Buffer.data() + GetSize() - strlen, str.data(), strlen); std::memcpy(a_Buffer.data() + a_Buffer.GetSize() - strlen, str.data(), strlen);
return *this; return a_Buffer;
} }
DataBuffer& DataBuffer::operator<<(const DataBuffer& data) { DataBuffer& operator<<(DataBuffer& a_Buffer, const DataBuffer& data) {
m_Buffer.insert(m_Buffer.end(), data.begin(), data.end()); std::size_t end = a_Buffer.GetSize();
return *this; a_Buffer.Resize(a_Buffer.GetSize() + data.GetSize());
std::copy(data.begin(), data.end(), a_Buffer.begin() + end);
return a_Buffer;
} }
DataBuffer& DataBuffer::operator>>(std::string& str) { DataBuffer& operator>>(DataBuffer& a_Buffer, std::string& str) {
std::size_t stringSize = strlen(reinterpret_cast<const char*>(m_Buffer.data()) + m_ReadOffset) + 1; // including null character std::size_t stringSize = strlen(reinterpret_cast<const char*>(a_Buffer.data()) + a_Buffer.GetReadOffset()) + 1; // including null character
str.resize(stringSize); str.resize(stringSize);
std::copy(m_Buffer.begin() + static_cast<difference_type>(m_ReadOffset), std::copy(a_Buffer.begin() + static_cast<DataBuffer::difference_type>(a_Buffer.GetReadOffset()),
m_Buffer.begin() + static_cast<difference_type>(m_ReadOffset + stringSize), str.begin()); a_Buffer.begin() + static_cast<DataBuffer::difference_type>(a_Buffer.GetReadOffset() + stringSize), str.begin());
m_ReadOffset += stringSize; a_Buffer.SetReadOffset(a_Buffer.GetReadOffset() + stringSize);
str.resize(stringSize - 1); str.resize(stringSize - 1);
return *this; return a_Buffer;
} }
DataBuffer& DataBuffer::operator>>(DataBuffer& data) { DataBuffer& operator>>(DataBuffer& a_Buffer, DataBuffer& data) {
data.Resize(GetSize() - m_ReadOffset); data.Resize(a_Buffer.GetSize() - a_Buffer.GetReadOffset());
std::copy(m_Buffer.begin() + static_cast<difference_type>(m_ReadOffset), m_Buffer.end(), data.begin()); std::copy(a_Buffer.begin() + static_cast<DataBuffer::difference_type>(a_Buffer.GetReadOffset()), a_Buffer.end(), data.begin());
m_ReadOffset = m_Buffer.size(); a_Buffer.SetReadOffset(a_Buffer.GetSize());
return *this; return a_Buffer;
} }
void DataBuffer::WriteSome(const char* buffer, std::size_t amount) { void DataBuffer::WriteSome(const char* buffer, std::size_t amount) {

View File

@@ -1,7 +1,8 @@
#include <sp/common/VarInt.h> #include <sp/common/VarInt.h>
#include <stdexcept>
#include <sp/common/DataBuffer.h> #include <sp/common/DataBuffer.h>
#include <sp/common/DataBufferOperators.h>
#include <stdexcept>
namespace sp { namespace sp {
@@ -29,24 +30,29 @@ DataBuffer& operator<<(DataBuffer& out, const VarInt& var) {
} }
DataBuffer& operator>>(DataBuffer& in, VarInt& var) { DataBuffer& operator>>(DataBuffer& in, VarInt& var) {
var.m_Value = 0; var.Read([&in](std::uint8_t& data){
in.ReadSome(&data, 1);
});
return in;
}
void VarInt::Read(const ReadFunc& read) {
m_Value = 0;
unsigned int position = 0; unsigned int position = 0;
std::uint8_t currentByte; std::uint8_t currentByte;
while (true) { while (true) {
in.ReadSome(&currentByte, 1); read(currentByte);
var.m_Value |= static_cast<std::uint64_t>(currentByte & SEGMENT_BITS) << position; m_Value |= static_cast<std::uint64_t>(currentByte & SEGMENT_BITS) << position;
if ((currentByte & CONTINUE_BIT) == 0) if ((currentByte & CONTINUE_BIT) == 0)
break; break;
position += 7; position += 7;
if (position >= 8 * sizeof(var.m_Value)) if (position >= 8 * sizeof(m_Value))
throw std::runtime_error("VarInt is too big"); throw std::runtime_error("VarInt is too big");
} }
return in;
} }
} // namespace sp } // namespace sp

View File

@@ -0,0 +1,85 @@
#include <sp/extensions/Compress.h>
#include <cassert>
#include <sp/common/VarInt.h>
#include <zlib.h>
namespace sp {
namespace zlib {
static DataBuffer Inflate(const std::uint8_t* source, std::size_t size, std::size_t uncompressedSize) {
DataBuffer result;
result.Resize(uncompressedSize);
uncompress(static_cast<Bytef*>(result.data()), reinterpret_cast<uLongf*>(&uncompressedSize), static_cast<const Bytef*>(source),
static_cast<uLong>(size));
assert(result.GetSize() == uncompressedSize);
return result;
}
static DataBuffer Deflate(const std::uint8_t* source, std::size_t size) {
DataBuffer result;
uLongf compressedSize = size;
result.Resize(size); // Resize for the compressed data to fit into
compress(static_cast<Bytef*>(result.data()), &compressedSize, static_cast<const Bytef*>(source), static_cast<uLong>(size));
result.Resize(compressedSize); // Resize to cut useless data
return result;
}
DataBuffer Compress(const DataBuffer& buffer, std::size_t a_CompressionThreshold) {
DataBuffer packet;
if (buffer.GetSize() < a_CompressionThreshold) {
// Don't compress since it's a small packet
packet << VarInt{0};
packet << buffer;
return packet;
}
DataBuffer compressedData = Deflate(buffer.data(), buffer.GetSize());
VarInt uncompressedDataLength = buffer.GetSize();
if (compressedData.GetSize() >= buffer.GetSize()) {
// the compression is overkill so we don't send the compressed buffer
packet << VarInt{0};
packet << buffer;
} else {
packet << uncompressedDataLength;
packet << compressedData;
}
return packet;
}
DataBuffer Decompress(DataBuffer& buffer, std::uint64_t packetLength) {
VarInt uncompressedLength;
buffer >> uncompressedLength;
std::uint64_t compressedLength = packetLength - uncompressedLength.GetSerializedLength();
if (uncompressedLength.GetValue() == 0) {
// Data already uncompressed. Nothing to do
DataBuffer ret;
buffer.ReadSome(ret, compressedLength);
return ret;
}
assert(buffer.GetReadOffset() + compressedLength <= buffer.GetSize());
return Inflate(buffer.data() + buffer.GetReadOffset(), compressedLength, uncompressedLength.GetValue());
}
} // namespace zlib
DataBuffer ZlibCompress::EncapsulateImpl(const DataBuffer& a_Data) {
return zlib::Compress(a_Data, m_CompressionThreshold);
}
DataBuffer ZlibCompress::DecapsulateImpl(DataBuffer& a_Data) {
return zlib::Decompress(a_Data, a_Data.GetSize());
}
} // namespace sp

View File

@@ -0,0 +1,114 @@
#include <sp/extensions/tcp/TcpListener.h>
#ifdef _WIN32
// Windows
#include <winsock2.h>
#include <ws2tcpip.h>
#define ioctl ioctlsocket
#define WOULDBLOCK WSAEWOULDBLOCK
#define MSG_DONTWAIT 0
#else
// Linux/Unix
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#define closesocket close
#define WOULDBLOCK EWOULDBLOCK
#define SD_BOTH SHUT_RDWR
#endif
#ifndef INVALID_SOCKET
#define INVALID_SOCKET -1
#endif
namespace sp {
namespace io {
TcpListener::TcpListener(std::uint16_t a_Port, int a_MaxConnexions) {
if ((m_Handle = static_cast<SocketHandle>(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))) < 0) {
throw SocketError("Failed to create server socket");
}
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(a_Port);
if (bind(m_Handle, reinterpret_cast<sockaddr*>(&address), sizeof(address)) < 0)
throw SocketError("Failed to create server socket");
if (listen(m_Handle, a_MaxConnexions) < 0)
throw SocketError("Failed to create server socket");
socklen_t len = sizeof(address);
if (getsockname(m_Handle, reinterpret_cast<sockaddr*>(&address), &len) < 0)
throw SocketError("Failed to create server socket");
m_Port = ntohs(address.sin_port);
m_MaxConnections = a_MaxConnexions;
}
TcpListener::~TcpListener() {
Close();
}
std::unique_ptr<TcpSocket> TcpListener::Accept() {
sockaddr remoteAddress;
int addrlen = sizeof(remoteAddress);
auto newSocket = std::make_unique<TcpSocket>();
newSocket->m_Handle = static_cast<SocketHandle>(
accept(m_Handle, reinterpret_cast<sockaddr*>(&remoteAddress), reinterpret_cast<socklen_t*>(&addrlen)));
if (newSocket->m_Handle < 0)
return nullptr;
newSocket->m_Status = TcpSocket::Status::Connected;
return newSocket;
}
void TcpListener::Close() {
if (m_Handle > 0) {
closesocket(m_Handle);
shutdown(m_Handle, SD_BOTH);
}
}
bool TcpListener::SetBlocking(bool a_Blocking) {
unsigned long mode = !a_Blocking;
if (ioctl(m_Handle, FIONBIO, &mode) < 0) {
return false;
}
return true;
}
std::uint16_t TcpListener::GetListeningPort() const {
return m_Port;
}
int TcpListener::GetMaximumConnections() const {
return m_MaxConnections;
}
} // namespace io
} // namespace sp

View File

@@ -0,0 +1,164 @@
#include <sp/extensions/tcp/TcpSocket.h>
#ifdef _WIN32
// Windows
#include <winsock2.h>
#include <ws2tcpip.h>
#define ioctl ioctlsocket
#define WOULDBLOCK WSAEWOULDBLOCK
#define MSG_DONTWAIT 0
#else
// Linux/Unix
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#define closesocket close
#define WOULDBLOCK EWOULDBLOCK
#endif
#ifndef INVALID_SOCKET
#define INVALID_SOCKET -1
#endif
namespace sp {
namespace io {
TcpSocket::TcpSocket() : m_Handle(static_cast<SocketHandle>(INVALID_SOCKET)), m_Status(Status::Disconnected) {}
TcpSocket::TcpSocket(const std::string& a_Host, std::uint16_t a_Port) : TcpSocket() {
Connect(a_Host, a_Port);
}
TcpSocket::TcpSocket(TcpSocket&& a_Other) {
std::swap(m_Handle, a_Other.m_Handle);
std::swap(m_Status, a_Other.m_Status);
}
TcpSocket::~TcpSocket() {}
void TcpSocket::Connect(const std::string& a_Host, std::uint16_t a_Port) {
struct addrinfo hints {};
struct addrinfo* result = nullptr;
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
m_Status = Status::Error;
if (getaddrinfo(a_Host.c_str(), std::to_string(static_cast<int>(a_Port)).c_str(), &hints, &result) != 0) {
throw SocketError("Failed to get address info");
}
m_Handle = static_cast<SocketHandle>(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
if (m_Handle < 0) {
throw SocketError("Failed to create socket");
}
struct addrinfo* ptr = nullptr;
for (ptr = result; ptr != nullptr; ptr = ptr->ai_next) {
struct sockaddr* sockaddr = ptr->ai_addr;
if (connect(m_Handle, sockaddr, sizeof(sockaddr_in)) == 0) {
break;
}
}
freeaddrinfo(result);
if (!ptr) {
throw SocketError("Could not find a suitable interface for connecting");
}
m_Status = Status::Connected;
}
DataBuffer TcpSocket::Read(std::size_t a_Amount) {
DataBuffer buffer(a_Amount);
std::size_t totalRecieved = 0;
while (totalRecieved < a_Amount) {
int recvAmount =
recv(m_Handle, reinterpret_cast<char*>(buffer.data() + totalRecieved), static_cast<int>(a_Amount - totalRecieved), 0);
if (recvAmount <= 0) {
#if defined(_WIN32) || defined(WIN32)
int err = WSAGetLastError();
#else
int err = errno;
#endif
if (err == WOULDBLOCK) {
// we are in non blocking mode and nothing is available
return {};
}
Disconnect();
m_Status = Status::Error;
throw SocketError("Error while reading");
}
totalRecieved += recvAmount;
}
return buffer;
}
void TcpSocket::Write(const sp::DataBuffer& a_Data) {
if (GetStatus() != Status::Connected)
return;
std::size_t sent = 0;
while (sent < a_Data.GetSize()) {
int cur = send(m_Handle, reinterpret_cast<const char*>(a_Data.data() + sent), static_cast<int>(a_Data.GetSize() - sent), 0);
if (cur <= 0) {
Disconnect();
m_Status = Status::Error;
return;
}
sent += static_cast<std::size_t>(cur);
}
}
bool TcpSocket::SetBlocking(bool a_Block) {
unsigned long mode = !a_Block;
if (ioctl(m_Handle, FIONBIO, &mode) < 0) {
return false;
}
return true;
}
TcpSocket::Status TcpSocket::GetStatus() const {
return m_Status;
}
void TcpSocket::Disconnect() {
if (m_Handle > 0)
closesocket(m_Handle);
m_Status = Status::Disconnected;
}
TcpSocket& TcpSocket::operator=(TcpSocket&& a_Other) {
std::swap(m_Handle, a_Other.m_Handle);
std::swap(m_Status, a_Other.m_Status);
return *this;
}
} // namespace io
} // namespace sp

103
test/test_message.cpp Normal file
View File

@@ -0,0 +1,103 @@
#include <sp/common/GenericHandler.h>
#include <sp/io/MessageStream.h>
#include <sp/io/StdIo.h>
#include <sp/protocol/BitField.h>
#include <sp/protocol/ConcreteMessage.h>
#include <sp/protocol/MessageDispatcher.h>
#include <sp/protocol/MessageFactory.h>
#include <sp/extensions/Compress.h>
#include <cstdint>
#include <fstream>
#include <iostream>
enum class PacketID : std::uint8_t { KeepAlive = 0, MDC = 1 };
class PacketHandler;
using PacketBase = sp::MessageBase<PacketID, PacketHandler>;
template <typename TData, PacketID ID>
using Message = sp::ConcreteMessage<TData, PacketBase, ID>;
struct KeepAlivePacket {
sp::BitField<std::uint16_t, 12> one;
sp::BitField<std::uint16_t, 4> two;
std::shared_ptr<std::string> test;
};
struct MDCPacket {
sp::BitField<std::uint16_t, 12> one;
sp::BitField<PacketID, 4> two;
std::unique_ptr<std::string> test;
};
using KeepAliveMessage = Message<KeepAlivePacket, PacketID::KeepAlive>;
using MDCMessage = Message<MDCPacket, PacketID::MDC>;
using AllMessages = std::tuple<KeepAliveMessage, MDCMessage>;
class PacketHandler : public sp::GenericHandler<AllMessages> {};
class MyHandler : public PacketHandler {
public:
virtual void Handle(const KeepAliveMessage& msg) override {
std::cout << "I recieved a keep alive : " << *msg->one << " : " << *msg->two << " : " << (msg->test ? *msg->test : "nullptr") << "\n";
}
virtual void Handle(const MDCMessage& msg) override {
std::cout << "I recieved a mdc : " << *msg->one << " : " << static_cast<unsigned>(*msg->two) << " : " << *msg->test << "\n";
}
};
using PacketDispatcher = sp::MessageDispatcher<PacketBase>;
using PacketFactory = sp::MessageFactory<PacketBase, AllMessages>;
using PacketStream = sp::MessageStream<PacketFactory>;
int main() {
KeepAliveMessage m{69, 5, std::make_shared<std::string>("I'm a keepalive")};
// dispatch tests
MyHandler h;
PacketDispatcher d;
d.RegisterHandler(PacketID::KeepAlive, &h);
d.RegisterHandler(PacketID::MDC, &h);
d.Dispatch(m);
PacketFactory f;
auto message = f.CreateMessage(PacketID::KeepAlive);
d.Dispatch(*message);
// write tests
auto compress = std::make_shared<sp::ZlibCompress>();
std::ofstream file{"test.bin"};
PacketStream p(std::make_shared<sp::StdOuput>(file));
p.WriteMessage(m);
p.WriteMessage(MDCMessage{42, PacketID::MDC, std::make_unique<std::string>("Coucou")});
file.flush();
std::ifstream file2{"test.bin"};
PacketStream p2(std::make_shared<sp::StdInput>(file2));
auto message2 = p2.ReadMessage();
auto message3 = p2.ReadMessage();
d.Dispatch(*message2);
d.Dispatch(*message3);
// message->Write(file);
// file << std::endl;
// m.Write(file);
// file << std::endl;
// message->Read(file);
return 0;
}

View File

@@ -1,49 +0,0 @@
#include <iostream>
#include <examples/PacketExample.h>
#include <memory>
class KeepAliveHandler : public sp::PacketHandler {
void Handle(const KeepAlivePacket& packet) {
std::cout << "KeepAlive handled !\n";
}
void Handle(const DisconnectPacket& packet) {
std::cout << "Disconnect handled !\n";
}
void Handle(const UpgradeTowerPacket& packet) {
std::cout << "UpgradeTower handled !\n";
}
};
int main() {
auto upgradeTower = std::make_unique<UpgradeTowerPacket>(std::make_tuple(666, 9));
sp::PacketMessage* msg = upgradeTower.get();
KeepAliveHandler handler;
msg->Dispatch(handler);
sp::DataBuffer buffer;
msg->Write(buffer);
std::uint8_t msgId;
buffer >> msgId;
auto upgradeTower2 = std::make_unique<UpgradeTowerPacket>();
upgradeTower2->Read(buffer);
std::cout << "Test : " << (unsigned) upgradeTower2->GetTowerId() << "\n";
sp::PacketFactory factory;
auto packet = factory.CreateMessage(msgId);
if (packet == nullptr) {
std::cout << "Bad ID !\n";
return 1;
}
std::cout << (unsigned)packet->GetId() << std::endl;
packet->Dispatch(handler);
return 0;
}

155
test/type_name.hpp Normal file
View File

@@ -0,0 +1,155 @@
// Copyright (c) 2018 Will Wray https://keybase.io/willwray
//
// Distributed under the Boost Software License, Version 1.0.
// (http://www.boost.org/LICENSE_1_0.txt)
#pragma once
#include <cstring>
#include <string>
#include <string_view>
#include <typeinfo>
#include <type_traits>
#if __has_include(<cxxabi.h>)
# include <cxxabi.h>
# include <cstdlib>
# include <memory>
#endif
constexpr bool CXXABI
{
#if __has_include(<cxxabi.h>)
true
#endif
};
/*
"type_name_rt.hpp": get type names at runtime (hence 'rt')
^^^^^^^^^^^^^^^^
This header defines, in global scope (i.e. not namespace'd):
(1) A function template type_name_str<T>() for extracting a type's name.
(2) A variable template type_name_rt<T> initialized to the type's name.
(also an incomplete class template IdT<T>, an implementation detail.)
The template type parameter T is mapped to a readable name for the type.
The work is done at runtime by what is the most standard current method;
runtime type information (RTTI) and, on CXXABI, a demangle call
(for compile-time alternatives see type_name_pt or type_name_ct).
(1) type_name_str<T>()
Returns a std::string copy of the demangled typeid name.
On each call it does all the work, and cleans it all up
(i.e. it frees any demangle allocation once copied from).
(2) type_name_rt<T>
A std::string_view global constant (a view into the
demangle buffer, on CXXABI, which is not ever free'd).
All work is done in static initialization, before main()
Failure is signalled by an empty return value; ""
(indicates a demangle failure as typeid is assumed fail-safe).
Requirements:
C++17 for string_view, constexpr-if, CTAD (unique_ptr) and __has_include
RTTI, the compiler's runtime type information, must be enabled
Dependencies: <string>,<string_view>,<type_traits> for std::conditional
<typeinfo> (RTTI)
for typeid(T).name(), an implementation-defined name.
<cxxabi.h> (on CXXABI platforms only - GCC, Clang, etc.)
for abi::__cxa_demangle(name,...)
to map typeid(T).name to a human readable name for T.
<cstdlib> for std::free, <memory> for std::unique_ptr
E.g.
int i;
std::cout << type_name_rt<decltype(i)> << "\n^^^ tada!";
--- stdout ---
int
^^^ tada!
*/
// IdT<T> wraps T as template param so typeid can't decay ref or cv quals.
// An implementation detail; must be a 3-character id, any 3 chars will do.
template <typename T> struct IdT {};
namespace impl
{
// demangle<bool Free=false>( const char* name)
//
// (1) On non-CXXABI returns name, regardless of the template parameter.
// i.e. the function does nothing but return its parameter, a char*.
//
// (2) On CXXABI the demangle ABI is called and the result is returned
// with return type depending on the boolean template argument:
// (a) char* by default (Free=false). Any demangle malloc is not free'd.
// (b) unique_ptr<char> (Free=true) to RAII-free any malloc'd chars.
//
// The input name should be a valid mangled name like typeid(T).name()
// Null return value implies demangle fail (no malloc, free is harmless).
//
template <bool Free = false>
auto
demangle(char const* name) noexcept(!CXXABI)
{
if constexpr (!CXXABI) {
return name; // NOP: assume already demangled if not on CXXABI
} else {
auto dmg = abi::__cxa_demangle(name, nullptr, nullptr, nullptr);
if constexpr (Free)
return std::unique_ptr<char, decltype(std::free)*>( dmg, std::free);
else
return dmg;
}
}
// prefix_len (constant): prefix length of demangled typeid(IdT<T>)
// for different compilers (remove 4 chars "int>" from the length)
size_t IdT_prefix_len()
{
static size_t const len = std::strlen(demangle<>(typeid(IdT<int>).name()))
- std::strlen("int>");
return len;
}
template <typename T>
using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<T>>;
template <typename T>
inline constexpr bool is_cvref_v = !std::is_same_v<T,remove_cvref_t<T>>;
// type_name_rt<T>() Returns string, frees any malloc from ABI demangle
// type_name_rt<T,false>() Returns string_view, does not free demangle malloc
//
template <typename T, bool Free = true, typename = void>
auto
type_name_rt() noexcept(!CXXABI) -> std::conditional_t<Free, std::string,
std::string_view>
{
if constexpr (!is_cvref_v<T>)
{
if (auto dmg = demangle<Free>(typeid(T).name()))
{
return { &*dmg };
}
}
else // wrap all cvref types for now - maybe only do functions and arrays
{
if (auto dmg = demangle<Free>(typeid(IdT<T>).name()))
{
size_t const p = IdT_prefix_len();
return { &*dmg + p, std::strlen(&*dmg) - p - 1 };
}
}
return "";
}
} // namespace impl
// type_name_str<T>() Returns a std::string copy of the demangled typeid name.
//
template <typename T>
std::string
const type_name_str() { return impl::type_name_rt<T>(); }
// type_name_rt<T> Global constant; "The Demangle that Never Dangles"
//
template <typename T>
inline
std::string_view
const type_name_rt = impl::type_name_rt<T, false>();

101
xmake.lua
View File

@@ -1,13 +1,104 @@
add_rules("mode.debug", "mode.release") add_rules("mode.debug", "mode.release")
includes("@builtin/check")
add_requires("boost_pfr")
set_warnings("all")
set_languages("c++17") set_languages("c++17")
target("SimpleProtocolLib") local modules = {
Compression = {
Option = "zlib",
Deps = {"zlib"},
Includes = {"include/(sp/extensions/Compress.h)"},
Sources = {"src/sp/extensions/Compress.cpp"}
},
TcpSocket = {
Option = "tcp",
Deps = {},
Includes = {"include/(sp/extensions/Tcp.h)", "include/(sp/extensions/tcp/*.h)"},
Sources = {"src/sp/extensions/Tcp*.cpp"}
}
}
-- Map modules to options
for name, module in table.orderpairs(modules) do
if module.Option then
option(module.Option, { description = "Enables the " .. name .. " module", default = true, category = "Modules" })
end
end
-- Add modules requirements
for name, module in table.orderpairs(modules) do
if module.Deps then
add_requires(module.Deps)
end
end
-- Add modules targets
for name, module in table.orderpairs(modules) do
if module.Deps and has_config(module.Option) then
target("SimpleProtocol-" .. name)
add_includedirs("include") add_includedirs("include")
add_headerfiles("include/(sp/**.h)") for _, include in table.orderpairs(module.Includes) do
add_headerfiles(include)
end
for _, source in table.orderpairs(module.Sources) do
add_files(source)
end
for _, package in table.orderpairs(module.Deps) do
add_packages(package)
end
set_group("Library") set_group("Library")
add_files("src/sp/**.cpp")
set_kind("$(kind)") set_kind("$(kind)")
end
end
target("SimpleProtocol")
add_includedirs("include")
add_files("src/sp/**.cpp")
set_group("Library")
set_kind("$(kind)")
add_packages("boost_pfr", {public = true})
check_bigendian("SP_BIG_ENDIAN")
add_headerfiles("include/(sp/**.h)", "include/(sp/**.inl)")
-- adding extensions
for name, module in table.orderpairs(modules) do
if module.Deps and has_config(module.Option) then
add_deps("SimpleProtocol-" .. name)
end
end
-- we don't want extensions
remove_files("src/sp/extensions/**.cpp")
remove_headerfiles("include/(sp/extension/**.h)")
-- we need this for endian functions
if is_os("windows") then
add_links("ws2_32")
end
-- Tests -- Tests
for _, file in ipairs(os.files("test/**.cpp")) do for _, file in ipairs(os.files("test/**.cpp")) do
@@ -18,7 +109,9 @@ for _, file in ipairs(os.files("test/**.cpp")) do
add_files(file) add_files(file)
add_includedirs("include") add_includedirs("include")
add_deps("SimpleProtocolLib") set_rundir(".")
add_deps("SimpleProtocol")
add_tests("compile_and_run") add_tests("compile_and_run")
end end