18 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
194205be41 Update README.md
All checks were successful
Linux arm64 / Build (push) Successful in 16s
2025-03-19 18:07:52 +00:00
21 changed files with 759 additions and 429 deletions

View File

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

View File

@@ -4,34 +4,13 @@
namespace sp {
/**
* \brief Serialize value to (network byte order) big endian
*/
template <typename T>
void ToNetwork(T& value) {}
bool IsLittleEndian();
template <>
void ToNetwork<std::uint16_t>(std::uint16_t& value);
void SwapBytes(std::uint8_t* begin, std::uint8_t* end);
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);
template<typename T>
void SwapBytes(T& a_Data) {
SwapBytes(reinterpret_cast<std::uint8_t*>(&a_Data), reinterpret_cast<std::uint8_t*>(&a_Data) + sizeof(T));
}
} // namespace sp

View File

@@ -10,10 +10,13 @@
#include <cassert>
#include <cstdint>
#include <cstring>
#include <string>
#include <sp/common/VarInt.h>
#include <vector>
#include <list>
#include <map>
#include <memory>
#include <sp/common/ByteSwapping.h>
#include <sp/common/VarInt.h>
#include <string>
#include <vector>
namespace sp {
@@ -23,16 +26,18 @@ namespace sp {
*/
class DataBuffer {
private:
typedef std::vector<std::uint8_t> Data;
using Data = std::vector<std::uint8_t>;
private:
Data m_Buffer;
std::size_t m_ReadOffset;
public:
typedef Data::iterator iterator;
typedef Data::const_iterator const_iterator;
typedef Data::reference reference;
typedef Data::const_reference const_reference;
typedef Data::difference_type difference_type;
using iterator = Data::iterator;
using const_iterator = Data::const_iterator;
using reference = Data::reference;
using const_reference = Data::const_reference;
using difference_type = Data::difference_type;
DataBuffer();
DataBuffer(std::size_t a_InitialSize);
@@ -46,143 +51,25 @@ class DataBuffer {
/**
* \brief Append data to the buffer
* \warning No endian checks
*/
template <typename T>
void Append(const T& data) {
std::size_t size = sizeof(data);
void Append(const T& a_Data) {
std::size_t size = sizeof(a_Data);
std::size_t end_pos = m_Buffer.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>
DataBuffer& operator<<(const T& 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) {
void Read(T& a_Data) {
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);
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.emplace(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;
}
/**
@@ -236,7 +123,6 @@ class DataBuffer {
m_Buffer.reserve(amount);
}
/**
* \brief Clear the buffer
*/
@@ -325,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

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

@@ -133,7 +133,7 @@ namespace sp
{
public:
virtual ~GenericHandler() {}
// virtual void Handle(const TCommon&) {} //Nothing to do
virtual void Handle() {} //Nothing to do
};
} // sp

View File

@@ -1,6 +1,5 @@
#pragma once
#include <sp/common/NonCopyable.h>
#include <sp/io/IoInterface.h>
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

@@ -1,72 +1,53 @@
#pragma once
#include <boost/pfr.hpp>
#include <sp/common/ByteSwapping.h>
#include <sp/common/DataBuffer.h>
#include <sp/protocol/BitField.h>
#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, std::uint64_t& a_DataRaw, std::size_t& a_Offset) {
T cut = *a_Data & ((1 << a_Data.GetBitSize()) - 1);
std::size_t pushCount = sizeof(T) * 8 - a_Offset - a_Data.GetBitSize();
a_DataRaw |= cut << pushCount;
a_Offset += a_Data.GetBitSize();
if (a_Offset == sizeof(T) * 8) {
T filled = static_cast<T>(a_DataRaw);
ToNetwork(filled);
a_Buffer << filled;
a_Offset = 0;
a_DataRaw = 0;
}
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, std::uint64_t& a_DataRaw, std::size_t& a_Offset) {
T swapped = a_Data;
ToNetwork(swapped);
a_Buffer << swapped;
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, std::size_t& a_Offset) {
a_Buffer >> *a_Data;
FromNetwork(*a_Data);
*a_Data >>= sizeof(T) * 8 - a_Offset - a_Data.GetBitSize();
*a_Data &= (1 << a_Data.GetBitSize()) - 1;
if (a_Offset != sizeof(T) * 8) {
a_Buffer.SetReadOffset(a_Buffer.GetReadOffset() - sizeof(T));
a_Offset += a_Data.GetBitSize();
} else {
a_Offset = 0;
}
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, std::size_t& a_Offset) {
void ReadField(DataBuffer& a_Buffer, T& a_Data, BitBuffer& a_BitBuffer) {
a_Buffer >> a_Data;
FromNetwork(a_Data);
a_BitBuffer.UpdateRead(false);
}
} // namespace details
template <typename TData>
DataBuffer WriteMessage(const TData& a_MessageData) {
DataBuffer buffer;
std::size_t currentOffset = 0;
std::uint64_t dataRaw = 0;
boost::pfr::for_each_field(a_MessageData,
[&buffer, &dataRaw, &currentOffset](const auto& a_Field) { WriteField(buffer, a_Field, dataRaw, currentOffset); });
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) {
std::size_t currentOffset = 0;
boost::pfr::for_each_field(a_MessageData, [&a_Buffer, &currentOffset](auto& a_Field) { ReadField(a_Buffer, a_Field, currentOffset); });
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 details
} // namespace sp

View File

@@ -4,6 +4,7 @@
#include <sp/io/IoInterface.h>
#include <sp/io/MessageEncapsulator.h>
#include <vector>
#include <memory>
namespace sp {

View File

@@ -8,7 +8,10 @@ namespace sp {
template <typename TMessageFactory>
DataBuffer MessageStream<TMessageFactory>::ReadAndDecapsulate() {
VarInt messageLength;
messageLength.Read([this](std::uint8_t& data) { m_Stream->Read(1) >> data; });
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);

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

View File

@@ -18,7 +18,9 @@ class StdInput : public IoInterface {
return buffer;
}
virtual void Write(const DataBuffer& a_Data) override {}
virtual void Write(const DataBuffer& a_Data) override {
assert(!"Write not implemented !");
}
};
class StdOuput : public IoInterface {
@@ -29,6 +31,7 @@ class StdOuput : public IoInterface {
StdOuput(std::ostream& a_Io) : m_Io(a_Io) {}
virtual DataBuffer Read(std::size_t a_Amount) override {
assert(!"Read not implemented !");
return {};
}

View File

@@ -5,30 +5,49 @@
namespace sp {
template <typename TData, typename TMessageID, TMessageID ID, typename THandler>
class ConcreteMessage : public MessageBase<TMessageID, THandler> {
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(const T&... args) : m_Data{args...} {}
ConcreteMessage(T&&... args) : m_Data{std::move(args)...} {}
virtual ~ConcreteMessage() {}
virtual TMessageID GetId() const override {
virtual MessageIdType GetId() const override {
return ID;
}
virtual void Dispatch(THandler& handler) const override {
handler.Handle(static_cast<const DataType&>(m_Data));
virtual void Dispatch(HandlerType& handler) const override {
if constexpr (DefineDispatch)
handler.Handle(*this);
}
virtual void Read(DataBuffer& a_Buffer) override {
details::ReadMessage(a_Buffer, m_Data);
ReadMessage(a_Buffer, m_Data);
}
virtual DataBuffer Write() const override {
return details::WriteMessage(m_Data);
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:

View File

@@ -1,133 +0,0 @@
#pragma once
#include <tuple>
namespace sp
{
// This class is inspired by https://arobenko.gitbooks.io/comms-protocols-cpp/content/
// TAll is all the message types, that need to be handled, bundled in std::tuple
template <typename TAll>
class MessageHandler;
// 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 MessageHandler<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 MessageHandler<std::tuple<TRest...> >
{
using Base = MessageHandler<std::tuple<TRest...> >;
public:
using Base::Handle; // Don't hide all Handle() functions from base classes
virtual void Handle(const typename T1::DataType& msg) {}
virtual void Handle(const typename T2::DataType& msg) {}
virtual void Handle(const typename T3::DataType& msg) {}
virtual void Handle(const typename T4::DataType& msg) {}
virtual void Handle(const typename T5::DataType& msg) {}
virtual void Handle(const typename T6::DataType& msg) {}
virtual void Handle(const typename T7::DataType& msg) {}
virtual void Handle(const typename T8::DataType& msg) {}
virtual void Handle(const typename T9::DataType& msg) {}
virtual void Handle(const typename T10::DataType& msg) {}
virtual void Handle(const typename T11::DataType& msg) {}
virtual void Handle(const typename T12::DataType& msg) {}
virtual void Handle(const typename T13::DataType& msg) {}
virtual void Handle(const typename T14::DataType& msg) {}
virtual void Handle(const typename T15::DataType& msg) {}
virtual void Handle(const typename T16::DataType& msg) {}
virtual void Handle(const typename T17::DataType& msg) {}
virtual void Handle(const typename T18::DataType& msg) {}
virtual void Handle(const typename T19::DataType& msg) {}
virtual void Handle(const typename T20::DataType& 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 MessageHandler<std::tuple<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, TRest...> > : public MessageHandler<std::tuple<TRest...> >
{
using Base = MessageHandler<std::tuple<TRest...> >;
public:
using Base::Handle; // Don't hide all Handle() functions from base classes
virtual void Handle(const typename T1::DataType& msg) {}
virtual void Handle(const typename T2::DataType& msg) {}
virtual void Handle(const typename T3::DataType& msg) {}
virtual void Handle(const typename T4::DataType& msg) {}
virtual void Handle(const typename T5::DataType& msg) {}
virtual void Handle(const typename T6::DataType& msg) {}
virtual void Handle(const typename T7::DataType& msg) {}
virtual void Handle(const typename T8::DataType& msg) {}
virtual void Handle(const typename T9::DataType& msg) {}
virtual void Handle(const typename T10::DataType& msg) {}
};
// 5 by 5
template <
typename T1, typename T2, typename T3, typename T4, typename T5,
typename... TRest>
class MessageHandler<std::tuple<T1, T2, T3, T4, T5, TRest...> > : public MessageHandler<std::tuple<TRest...> >
{
using Base = MessageHandler<std::tuple<TRest...> >;
public:
using Base::Handle; // Don't hide all Handle() functions from base classes
virtual void Handle(const typename T1::DataType& msg) {}
virtual void Handle(const typename T2::DataType& msg) {}
virtual void Handle(const typename T3::DataType& msg) {}
virtual void Handle(const typename T4::DataType& msg) {}
virtual void Handle(const typename T5::DataType& msg) {}
};
// Deal with rest with 4 types
template <typename T1, typename T2, typename T3, typename T4>
class MessageHandler<std::tuple<T1, T2, T3, T4> >
{
public:
virtual ~MessageHandler() {}
virtual void Handle(const typename T1::DataType& msg) {}
virtual void Handle(const typename T2::DataType& msg) {}
virtual void Handle(const typename T3::DataType& msg) {}
virtual void Handle(const typename T4::DataType& msg) {}
};
// Deal with rest with 3 types
template < typename T1, typename T2, typename T3>
class MessageHandler<std::tuple<T1, T2, T3> >
{
public:
virtual ~MessageHandler() {}
virtual void Handle(const typename T1::DataType& msg) {}
virtual void Handle(const typename T2::DataType& msg) {}
virtual void Handle(const typename T3::DataType& msg) {}
};
// Deal with rest with 2 types
template <typename T1, typename T2>
class MessageHandler<std::tuple<T1, T2> >
{
public:
virtual ~MessageHandler() {}
virtual void Handle(const typename T1::DataType& msg) {}
virtual void Handle(const typename T2::DataType& msg) {}
};
// Deal with rest with 1 type
template <typename T1>
class MessageHandler<std::tuple<T1> >
{
public:
virtual ~MessageHandler() {}
virtual void Handle(const typename T1::DataType& msg) {}
};
// Deal with rest with 0 type
template <>
class MessageHandler<std::tuple<> >
{
public:
virtual ~MessageHandler() {}
};
} // sp

View File

@@ -1,49 +1,21 @@
#include <sp/common/ByteSwapping.h>
#ifdef _WIN32
#include <winsock2.h>
#else
#include <arpa/inet.h>
#include <endian.h>
#define htonll htobe64
#define ntohll be64toh
#endif
#include <algorithm>
namespace sp {
template <>
void ToNetwork<std::uint16_t>(std::uint16_t& value) {
value = htons(value);
bool IsLittleEndian() {
#ifdef SP_BIG_ENDIAN
return false;
#else
return true;
#endif
}
template <>
void ToNetwork<std::uint32_t>(std::uint32_t& value) {
value = htonl(value);
}
template <>
void ToNetwork<std::uint64_t>(std::uint64_t& value) {
value = htonll(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) {
value = ntohll(value);
void SwapBytes(std::uint8_t* begin, std::uint8_t* end) {
if (IsLittleEndian()) {
std::reverse(begin, end);
}
}
} // namespace sp

View File

@@ -21,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));
}
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
Resize(GetSize() + strlen);
std::memcpy(m_Buffer.data() + GetSize() - strlen, str.data(), strlen);
return *this;
a_Buffer.Resize(a_Buffer.GetSize() + strlen);
std::memcpy(a_Buffer.data() + a_Buffer.GetSize() - strlen, str.data(), strlen);
return a_Buffer;
}
DataBuffer& DataBuffer::operator<<(const DataBuffer& data) {
m_Buffer.insert(m_Buffer.end(), data.begin(), data.end());
return *this;
DataBuffer& operator<<(DataBuffer& a_Buffer, const DataBuffer& data) {
std::size_t end = a_Buffer.GetSize();
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) {
std::size_t stringSize = strlen(reinterpret_cast<const char*>(m_Buffer.data()) + m_ReadOffset) + 1; // including null character
DataBuffer& operator>>(DataBuffer& a_Buffer, std::string& str) {
std::size_t stringSize = strlen(reinterpret_cast<const char*>(a_Buffer.data()) + a_Buffer.GetReadOffset()) + 1; // including null character
str.resize(stringSize);
std::copy(m_Buffer.begin() + static_cast<difference_type>(m_ReadOffset),
m_Buffer.begin() + static_cast<difference_type>(m_ReadOffset + stringSize), str.begin());
m_ReadOffset += stringSize;
std::copy(a_Buffer.begin() + static_cast<DataBuffer::difference_type>(a_Buffer.GetReadOffset()),
a_Buffer.begin() + static_cast<DataBuffer::difference_type>(a_Buffer.GetReadOffset() + stringSize), str.begin());
a_Buffer.SetReadOffset(a_Buffer.GetReadOffset() + stringSize);
str.resize(stringSize - 1);
return *this;
return a_Buffer;
}
DataBuffer& DataBuffer::operator>>(DataBuffer& data) {
data.Resize(GetSize() - m_ReadOffset);
std::copy(m_Buffer.begin() + static_cast<difference_type>(m_ReadOffset), m_Buffer.end(), data.begin());
m_ReadOffset = m_Buffer.size();
return *this;
DataBuffer& operator>>(DataBuffer& a_Buffer, DataBuffer& data) {
data.Resize(a_Buffer.GetSize() - a_Buffer.GetReadOffset());
std::copy(a_Buffer.begin() + static_cast<DataBuffer::difference_type>(a_Buffer.GetReadOffset()), a_Buffer.end(), data.begin());
a_Buffer.SetReadOffset(a_Buffer.GetSize());
return a_Buffer;
}
void DataBuffer::WriteSome(const char* buffer, std::size_t amount) {

View File

@@ -1,6 +1,7 @@
#include <sp/common/VarInt.h>
#include <sp/common/DataBuffer.h>
#include <sp/common/DataBufferOperators.h>
#include <stdexcept>
namespace sp {

View File

View File

View File

@@ -1,41 +1,52 @@
#include <sp/protocol/ConcreteMessage.h>
#include <sp/protocol/MessageDispatcher.h>
#include <sp/protocol/MessageFactory.h>
#include <sp/protocol/MessageHandler.h>
#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 <iostream>
#include <fstream>
#include <iostream>
enum class PacketID { KeepAlive = 0 };
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, PacketID, ID, PacketHandler>;
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>;
using AllMessages = std::tuple<KeepAliveMessage, MDCMessage>;
class PacketHandler : public sp::MessageHandler<AllMessages> {};
class PacketHandler : public sp::GenericHandler<AllMessages> {};
class MyHandler : public PacketHandler {
public:
virtual void Handle(const KeepAlivePacket& msg) {
std::cout << "I recieved a keep alive : " << *msg.one << " : " << *msg.two << "\n";
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";
}
};
@@ -46,41 +57,42 @@ using PacketFactory = sp::MessageFactory<PacketBase, AllMessages>;
using PacketStream = sp::MessageStream<PacketFactory>;
int main() {
KeepAliveMessage m{69, 5};
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"};
std::ofstream file{"test.bin"};
PacketStream p(std::make_shared<sp::StdOuput>(file), compress);
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"};
std::ifstream file2{"test.bin"};
PacketStream p2(std::make_shared<sp::StdInput>(file2), compress);
PacketStream p2(std::make_shared<sp::StdInput>(file2));
auto message2 = p2.ReadMessage();
auto message3 = p2.ReadMessage();
d.Dispatch(*message2);
// Todo : verify bitfields
d.Dispatch(*message3);
// message->Write(file);
// file << std::endl;

View File

@@ -1,5 +1,7 @@
add_rules("mode.debug", "mode.release")
includes("@builtin/check")
add_requires("boost_pfr")
set_warnings("all")
@@ -76,6 +78,7 @@ target("SimpleProtocol")
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)")