41 Commits

Author SHA1 Message Date
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
2acbd76c5a byteswapping: move declaration into source file 2025-02-23 21:57:10 +01:00
468f5ce8a0 add const 2025-02-23 13:20:45 +01:00
f145716cf6 serialize maps 2025-02-23 11:27:57 +01:00
6ee7524e17 update README 2025-02-23 10:50:39 +01:00
db0c5f3245 fix alignment + refactor
Reviewed-on: #1
Co-authored-by: Persson-dev <sim16.prib@gmail.com>
Co-committed-by: Persson-dev <sim16.prib@gmail.com>
2025-02-23 09:40:46 +00:00
ee865021c2 update varint constants 2025-02-08 20:08:19 +01:00
462086b13c feat: add alignment 2025-02-08 19:45:45 +01:00
530232ba16 add back tupleforeach 2025-02-08 19:39:37 +01:00
dd808b4d57 remove unused functions 2025-02-08 14:52:28 +01:00
f5a3a443af optimise endian 2025-02-08 14:38:18 +01:00
06d69fb976 feat: add support for all endianesses 2025-02-08 13:44:04 +01:00
5e89531508 cpp version back to c++17 2025-02-07 22:03:14 +01:00
51d7c8f66d update example 2025-02-07 22:01:09 +01:00
534757f884 better endianess handling 2025-02-07 22:01:04 +01:00
6725a63c07 add write id 2025-02-07 21:10:06 +01:00
032800b220 credits 2025-02-06 22:35:00 +01:00
e13c73fe59 clean MessageFactory 2025-02-06 22:32:59 +01:00
097dab01fd clean up namespaces 2025-02-06 22:23:40 +01:00
bbafae2588 working message factory 2025-02-06 22:16:07 +01:00
f5430f3d0f add todos 2025-02-06 19:22:35 +01:00
7108ed4f28 constructor for packets 2025-02-06 19:01:32 +01:00
a63395f219 better fields 2025-02-06 18:30:35 +01:00
8ecfb95167 fields builder 2025-02-06 18:01:27 +01:00
c3a54d8e5b cleaner example 2025-02-06 16:14:11 +01:00
e6a58eb999 better example 2025-02-06 16:04:51 +01:00
70 changed files with 2990 additions and 1264 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

10
README.md Normal file
View File

@@ -0,0 +1,10 @@
# SimpleProtocolLib
Network engine used to (mainly) communicate with packets
# Integrate with xmake
```lua
add_repositories("persson-repo https://git.ale-pri.com/Persson-dev/xmake-repo.git")
add_requires("splib", { debug = is_mode("debug") })
```

View File

@@ -0,0 +1,20 @@
#pragma once
#include <sp/default/DefaultPacket.h>
#include <sp/protocol/Field.h>
#include <sp/protocol/MessageBase.h>
enum class DisconnectFieldsE {
Reason = 0
};
using DisconnectFields = std::tuple<std::string /*Reason*/>;
DeclarePacket(Disconnect){
public:
PacketConstructor(Disconnect)
const std::string& GetReason() const {
return GetField<DisconnectFieldsE, DisconnectFieldsE::Reason>();
}
};

View File

@@ -0,0 +1,22 @@
#pragma once
#include <sp/default/DefaultPacket.h>
#include <sp/protocol/Field.h>
#include <sp/protocol/MessageBase.h>
enum class KeepAliveFieldsE {
KeepAliveId = 0,
};
using KeepAliveFields = std::tuple<
std::uint64_t //<- KeepAliveId
>;
DeclarePacket(KeepAlive){
public:
PacketConstructor(KeepAlive)
std::uint64_t GetKeepAliveId() const {
return GetField<KeepAliveFieldsE, KeepAliveFieldsE::KeepAliveId>();
}
};

View File

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

View File

@@ -0,0 +1,32 @@
#pragma once
#include <sp/default/DefaultPacket.h>
#include <sp/protocol/Field.h>
#include <sp/protocol/MessageBase.h>
enum class UpgradeTowerFieldsE {
m_Tower = 0,
m_Upgrade,
};
using UpgradeTowerFields = std::tuple<
sp::BitField<std::uint16_t,
sp::Field<std::uint16_t, 12>, //<- m_Tower
sp::Field<std::uint8_t, 4> //<- m_Upgrade
>,
sp::VarInt //<- just for testing
>;
DeclarePacket(UpgradeTower){
public:
PacketConstructor(UpgradeTower)
std::uint16_t GetTowerId() const {
return GetField<0>().GetField<UpgradeTowerFieldsE, UpgradeTowerFieldsE::m_Tower>();
}
std::uint8_t GetTowerUpgrade() const {
return GetField<0>().GetField<UpgradeTowerFieldsE, UpgradeTowerFieldsE::m_Upgrade>();
}
};

View File

@@ -1,65 +0,0 @@
#pragma once
#include <sp/common/DataBuffer.h>
namespace sp {
template <typename ValueType>
class Field {
public:
// Provide an access to the stored value
ValueType& GetValue() {
return m_Value;
}
const ValueType& GetValue() const {
return m_Value;
}
// Read (deserialise) and update internal value
void Read(DataBuffer& buffer) {
buffer >> m_Value;
}
// Write (serialise) internal value
void Write(DataBuffer& buffer) const {
buffer << m_Value;
}
Field& operator=(const ValueType& value) {
m_Value = value;
return *this;
}
private:
ValueType m_Value;
};
// Functor used to read all tuple values
class FieldReader {
public:
FieldReader(DataBuffer& buffer) : m_Buffer(buffer) {}
template <typename TField>
void operator()(TField& field) {
field.Read(m_Buffer);
}
private:
DataBuffer& m_Buffer;
};
// Functor used to write all tuple values
class FieldWriter {
public:
FieldWriter(DataBuffer& buffer) : m_Buffer(buffer) {}
template <typename TField>
void operator()(TField& field) {
field.Write(m_Buffer);
}
private:
DataBuffer& m_Buffer;
};
} // 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(T1& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T2& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T3& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T4& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T5& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T6& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T7& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T8& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T9& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T10& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T11& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T12& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T13& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T14& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T15& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T16& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T17& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T18& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T19& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T20& msg) { this->Handle(static_cast<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(T1& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T2& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T3& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T4& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T5& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T6& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T7& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T8& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T9& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T10& msg) { this->Handle(static_cast<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(T1& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T2& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T3& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T4& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T5& msg) { this->Handle(static_cast<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(T1& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T2& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T3& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T4& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(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(T1& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T2& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T3& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(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(T1& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(T2& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(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(T1& msg) { this->Handle(static_cast<TCommon&>(msg)); }
virtual void Handle(TCommon&) { } //Nothing to do
};
// Deal with rest with 0 type
template <typename TCommon>
class GenericHandler<TCommon, std::tuple<> >
{
public:
virtual ~GenericHandler() {}
virtual void Handle(TCommon&) { } //Nothing to do
};
} // sp

View File

@@ -1,13 +0,0 @@
#pragma once
#include <sp/MessageInterfaceBuilder.h>
namespace sp {
template <typename... TOptions>
class Message : public option::MessageInterfaceBuilder<TOptions...>::Type {
public:
using ParsedOptions = typename option::MessageInterfaceBuilder<TOptions...>::ParsedOptions;
};
} // namespace sp

View File

@@ -1,278 +0,0 @@
#pragma once
#include <sp/Message.h>
#include <sp/Templates.h>
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
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;
};
// 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) override {
handler.Handle(static_cast<TActual&>(*this));
}
};
template <typename TBase, typename TFields>
class MessageImplFieldsBase : public TBase {
public:
using AllFields = TFields;
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();
}
private:
TFields m_Fields;
};
template <typename TBase>
class MessageImplFieldsReadBase : public TBase {
protected:
void ReadImpl(DataBuffer& buffer) override {
//TODO: add endianess
auto& allFields = TBase::GetFields();
tupleForEach(allFields, FieldReader{buffer});
}
};
template <typename TBase>
class MessageImplFieldsWriteBase : public TBase {
protected:
void WriteImpl(DataBuffer& buffer) override {
//TODO: add endianess
auto& allFields = TBase::GetFields();
tupleForEach(allFields, FieldWriter{buffer});
}
};
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
}
};
// 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;
};
// 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::HasWriteOperations && ImplOptions::HasFieldsImpl;
using Base6 = typename MessageImplProcessValidFields<Base5, HasValidImpl>::Type;
// The last BaseN must be taken as final type.
using Type = Base6;
};
template <typename TBase, typename... TOptions>
class MessageBase : public MessageImplBuilder<TBase, TOptions...>::Type {};
} // namespace sp

View File

@@ -1,243 +0,0 @@
#pragma once
#include <sp/Options.h>
namespace sp {
namespace option {
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 HasHandler = false;
static const bool HasValid = false;
};
template <typename T, typename... TOptions>
struct MessageInterfaceParsedOptions<MsgIdType<T>, TOptions...> : public MessageInterfaceParsedOptions<TOptions...> {
static const bool HasMsgIdType = true;
using MsgIdType = T;
};
template <typename... TOptions>
struct MessageInterfaceParsedOptions<LittleEndian, TOptions...> : public MessageInterfaceParsedOptions<TOptions...> {
static const bool HasLittleEndian = true;
};
template <typename... TOptions>
struct MessageInterfaceParsedOptions<ReadOperations, TOptions...> : public MessageInterfaceParsedOptions<TOptions...> {
static const bool HasReadOperations = true;
};
template <typename... TOptions>
struct MessageInterfaceParsedOptions<WriteOperations, TOptions...> : public MessageInterfaceParsedOptions<TOptions...> {
static const bool HasWriteOperations = true;
};
template <typename T, typename... TOptions>
struct MessageInterfaceParsedOptions<Handler<T>, TOptions...> : public MessageInterfaceParsedOptions<TOptions...> {
static const bool HasHandler = true;
using HandlerType = Handler<T>;
};
template <typename... TOptions>
struct MessageInterfaceParsedOptions<ValidCheckInterface, TOptions...> : public MessageInterfaceParsedOptions<TOptions...> {
static const bool HasValid = true;
};
// 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>
static T ReadData(DataBuffer& buffer) {
// use big endian
}
template <typename T>
static void WriteData(T value, DataBuffer& buffer) {
// use big endian
}
};
// Little endian serialisation chunk
template <typename TBase>
class MessageInterfaceLittleEndian : public TBase {
protected:
template <typename T>
static T ReadData(DataBuffer& buffer) {
// use little endian
}
template <typename T>
static void WriteData(const T& value, DataBuffer& buffer) {
// use little endian
}
};
// 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) {
return WriteImpl(buffer);
}
protected:
virtual void WriteImpl(DataBuffer& buffer) = 0;
};
// Handler functionality chunk
template <typename TBase, typename THandler>
class MessageInterfaceHandlerBase : public TBase {
public:
using HandlerType = typename THandler::HandlerT;
void Dispatch(HandlerType& handler) {
DispatchImpl(handler);
}
protected:
virtual void DispatchImpl(HandlerType& handler) = 0;
};
// Validity functionality chunk
template <typename TBase>
class MessageInterfaceValidityBase : public TBase {
public:
bool Valid() const {
return ValidImpl();
}
protected:
virtual bool ValidImpl() const = 0;
};
// 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;
};
} // namespace option
} // namespace sp

View File

@@ -1,86 +0,0 @@
#pragma once
#include <cstdint>
namespace sp {
/// @brief Default case, see field_index specialization for implementation details
template <size_t N, typename T, template <size_t, typename> typename U, typename = void>
static constexpr size_t field_index = 0;
/// @brief A templated size_t that counts the number of existing classes U with a "field_name" member
/// @tparam N Current counter for recursion (user should always call it with 0)
/// @tparam T Can be any type, must be different for each field we want to be counted later (used because we can't have a empty
/// template<> specialization nested in a class)
/// @tparam U The templated class that will be searched for match
template <size_t N, typename T, template <size_t, typename> typename U>
static constexpr size_t field_index<N, T, U, std::void_t<decltype(U<N, T>::field_name)>> = 1 + field_index<N + 1, T, U>;
/// @brief Concat multiple tuples in one big tuple
/// @tparam ...input_t Multiple std::tuple types to concat
template <typename... input_t>
using tuple_cat_t = decltype(std::tuple_cat(std::declval<input_t>()...));
template <typename T, typename Tuple>
constexpr bool tuple_contains_type = false;
template <typename T, typename... Ts>
constexpr bool tuple_contains_type<T, std::tuple<Ts...>> = std::disjunction_v<std::is_same<T, Ts>...>;
template <typename T, typename Tuple>
constexpr int get_tuple_index = 0;
template <typename T, typename... Rest>
constexpr int get_tuple_index<T, std::tuple<T, Rest...>> = 0;
template <typename T, typename First, typename... Rest>
constexpr int get_tuple_index<T, std::tuple<First, Rest...>> = 1 + get_tuple_index<T, std::tuple<Rest...>>;
// Template black magic to loop at compile time
template <std::size_t... indices, class LoopBody>
void loop_impl(std::index_sequence<indices...>, LoopBody&& loop_body) {
(loop_body(std::integral_constant<std::size_t, indices>{}), ...);
}
template <std::size_t N, class LoopBody>
void loop(LoopBody&& loop_body) {
loop_impl(std::make_index_sequence<N>{}, std::forward<LoopBody>(loop_body));
}
namespace details {
template <std::size_t TRem>
class TupleForEachHelper {
public:
template <typename TTuple, typename TFunc>
static void exec(TTuple&& tuple, TFunc&& func) {
using Tuple = typename std::decay<TTuple>::type;
static const std::size_t TupleSize = std::tuple_size<Tuple>::value;
static_assert(TRem <= TupleSize, "Incorrect parameters");
// Invoke function with current element
static const std::size_t Idx = TupleSize - TRem;
func(std::get<Idx>(std::forward<TTuple>(tuple)));
// Compile time recursion - invoke function with the remaining elements
TupleForEachHelper<TRem - 1>::exec(std::forward<TTuple>(tuple), std::forward<TFunc>(func));
}
};
template <>
class TupleForEachHelper<0> {
public:
// Stop compile time recursion
template <typename TTuple, typename TFunc>
static void exec(TTuple&& tuple, TFunc&& func) {
static_cast<void>(tuple);
static_cast<void>(func);
}
};
} // namespace details
template <typename TTuple, typename TFunc>
void tupleForEach(TTuple&& tuple, TFunc&& func) {
using Tuple = typename std::decay<TTuple>::type;
static const std::size_t TupleSize = std::tuple_size<Tuple>::value;
details::TupleForEachHelper<TupleSize>::exec(std::forward<TTuple>(tuple), std::forward<TFunc>(func));
}
} // namespace sp

View File

@@ -1,9 +0,0 @@
#pragma once
#include <cstdint>
namespace sp {
using PeerID = std::uint16_t;
} // namespace sp

View File

@@ -0,0 +1,54 @@
#pragma once
#include <cstdint>
namespace sp {
bool IsSystemBigEndian();
/**
* \brief Serialize value to (network byte order) big endian
*/
template <typename T>
void ToNetwork(T& value) {}
template <>
void ToNetwork<std::uint16_t>(std::uint16_t& value);
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

View File

@@ -13,6 +13,7 @@
#include <string>
#include <sp/common/VarInt.h>
#include <vector>
#include <map>
namespace sp {
@@ -34,6 +35,7 @@ class DataBuffer {
typedef Data::difference_type difference_type;
DataBuffer();
DataBuffer(std::size_t a_InitialSize);
DataBuffer(const DataBuffer& other);
DataBuffer(const DataBuffer& other, difference_type offset);
DataBuffer(DataBuffer&& other);
@@ -88,6 +90,19 @@ class DataBuffer {
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
@@ -140,6 +155,23 @@ class DataBuffer {
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
*/
@@ -261,13 +293,13 @@ class DataBuffer {
* \brief Read a file into the buffer
* \param fileName The name of the file to read
*/
bool ReadFile(const std::string& fileName);
void ReadFile(const std::string& fileName);
/**
* \brief Write a file into the buffer
* \param fileName The name of the file to write to
*/
bool WriteFile(const std::string& fileName) const;
void WriteFile(const std::string& fileName) const;
/**
* \brief Allocate the buffer on the heap

View File

@@ -1,54 +0,0 @@
/*
* Created by William Swanson in 2012.
*
* I, William Swanson, dedicate this work to the public domain.
* I waive all rights to the work worldwide under copyright law,
* including all related and neighboring rights,
* to the extent allowed by law.
*
* You can copy, modify, distribute and perform the work,
* even for commercial purposes, all without asking permission.
*/
#ifndef MAP_H_INCLUDED
#define MAP_H_INCLUDED
#define EVAL0(...) __VA_ARGS__
#define EVAL1(...) EVAL0(EVAL0(EVAL0(__VA_ARGS__)))
#define EVAL2(...) EVAL1(EVAL1(EVAL1(__VA_ARGS__)))
#define EVAL3(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__)))
#define EVAL4(...) EVAL3(EVAL3(EVAL3(__VA_ARGS__)))
#define EVAL(...) EVAL4(EVAL4(EVAL4(__VA_ARGS__)))
#define MAP_END(...)
#define MAP_OUT
#define MAP_COMMA ,
#define MAP_GET_END2() 0, MAP_END
#define MAP_GET_END1(...) MAP_GET_END2
#define MAP_GET_END(...) MAP_GET_END1
#define MAP_NEXT0(test, next, ...) next MAP_OUT
#define MAP_NEXT1(test, next) MAP_NEXT0(test, next, 0)
#define MAP_NEXT(test, next) MAP_NEXT1(MAP_GET_END test, next)
#define MAP0(f, x, peek, ...) f(x) MAP_NEXT(peek, MAP1)(f, peek, __VA_ARGS__)
#define MAP1(f, x, peek, ...) f(x) MAP_NEXT(peek, MAP0)(f, peek, __VA_ARGS__)
#define MAP_LIST_NEXT1(test, next) MAP_NEXT0(test, MAP_COMMA next, 0)
#define MAP_LIST_NEXT(test, next) MAP_LIST_NEXT1(MAP_GET_END test, next)
#define MAP_LIST0(f, x, peek, ...) f(x) MAP_LIST_NEXT(peek, MAP_LIST1)(f, peek, __VA_ARGS__)
#define MAP_LIST1(f, x, peek, ...) f(x) MAP_LIST_NEXT(peek, MAP_LIST0)(f, peek, __VA_ARGS__)
/**
* Applies the function macro `f` to each of the remaining parameters.
*/
#define MAP(f, ...) EVAL(MAP1(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0))
/**
* Applies the function macro `f` to each of the remaining parameters and
* inserts commas between the results.
*/
#define MAP_LIST(f, ...) EVAL(MAP_LIST1(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0))
#endif

View File

@@ -0,0 +1,60 @@
#pragma once
#include <cxxabi.h>
#include <string>
namespace sp {
template <typename T>
std::string GetBasicClassName(const T& a_Value) {
int status;
char* demangled = abi::__cxa_demangle(typeid(a_Value).name(), 0, 0, &status);
if (status != 0)
return "";
return std::string(demangled);
}
template <typename T>
std::string GetBasicClassName() {
int status;
char* demangled = abi::__cxa_demangle(typeid(T).name(), 0, 0, &status);
if (status != 0)
return "";
return std::string(demangled);
}
template <typename T>
struct Reflector {
static std::string GetClassName() {
return GetBasicClassName<T>();
}
};
template <>
struct Reflector<std::string> {
static std::string GetClassName() {
return "std::string";
}
};
template <typename K, typename V>
struct Reflector<std::map<K, V>> {
static std::string GetClassName();
};
template <typename T>
struct Reflector<std::vector<T>> {
static std::string GetClassName();
};
template <typename T>
std::string Reflector<std::vector<T>>::GetClassName() {
return "std::vector<" + Reflector<T>::GetClassName() + ">";
}
template <typename K, typename V>
std::string Reflector<std::map<K, V>>::GetClassName() {
return "std::map<" + Reflector<K>::GetClassName() + ", " + Reflector<V>::GetClassName() + ">";
}
} // namespace sp

View File

@@ -0,0 +1,98 @@
#pragma once
#include <cstdint>
#include <string>
namespace sp {
/// @brief Concat multiple tuples in one big tuple
/// @tparam ...input_t Multiple std::tuple types to concat
template <typename... input_t>
using tuple_cat_t = decltype(std::tuple_cat(std::declval<input_t>()...));
namespace details {
template <std::size_t TRem>
class TupleForEachHelper {
public:
template <typename TTuple, typename TFunc>
static void Exec(TTuple&& tuple, TFunc&& func) {
using Tuple = typename std::decay<TTuple>::type;
static const std::size_t TupleSize = std::tuple_size<Tuple>::value;
static_assert(TRem <= TupleSize, "Incorrect parameters");
// Invoke function with current element
static const std::size_t Idx = TupleSize - TRem;
func(std::get<Idx>(std::forward<TTuple>(tuple)));
// Compile time recursion - invoke function with the remaining elements
TupleForEachHelper<TRem - 1>::Exec(std::forward<TTuple>(tuple), std::forward<TFunc>(func));
}
};
template <>
class TupleForEachHelper<0> {
public:
// Stop compile time recursion
template <typename TTuple, typename TFunc>
static void Exec(TTuple&& tuple, TFunc&& func) {
static_cast<void>(tuple);
static_cast<void>(func);
}
};
} // namespace details
template <typename TFunc, typename TTuple>
void TupleForEach(TFunc&& func, TTuple&& tuple) {
using Tuple = typename std::decay<TTuple>::type;
static const std::size_t TupleSize = std::tuple_size<Tuple>::value;
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

View File

@@ -7,6 +7,7 @@
#include <cstddef>
#include <cstdint>
#include <sp/protocol/MessagePrinter.h>
namespace sp {
@@ -21,6 +22,8 @@ class VarInt {
std::uint64_t m_Value;
public:
static const std::uint64_t MAX_VALUE = static_cast<std::uint64_t>(-1) >> 8;
VarInt() : m_Value(0) {}
/**
* \brief Construct a variable integer from a value
@@ -55,4 +58,9 @@ class VarInt {
friend DataBuffer& operator>>(DataBuffer& in, VarInt& var);
};
template<>
inline std::string PrintData(const VarInt& a_VarInt) {
return PrintData(a_VarInt.GetValue());
}
} // namespace sp

View File

@@ -0,0 +1,32 @@
#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
option::DebugPrint // add ToString() operator
>;
#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>>, \
sp::option::ToStringImpl<packetName##Packet>
} // namespace sp

View File

@@ -0,0 +1,15 @@
#pragma once
#include <sp/default/DefaultPacket.h>
#include <sp/default/DefaultPacketHandler.h>
#include <sp/protocol/MessageDispatcher.h>
namespace sp {
using PacketDispatcher = MessageDispatcher<
PacketMessage::ParsedOptions::MsgIdType,
PacketMessage,
PacketMessage::ParsedOptions::HandlerType::HandlerT
>;
} // namespace sp

View File

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

View File

@@ -0,0 +1,9 @@
#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,54 @@
#pragma once
/**
* \file Compression.h
* \brief File containing compress utilities
*/
#include <cstdint>
#include <sp/common/DataBuffer.h>
namespace sp {
namespace option {
struct ZlibCompress {
bool m_Enabled = true;
std::size_t m_CompressionThreshold = 64;
};
} // namespace option
} // namespace sp
#include <sp/io/IOInterface.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
namespace io {
template <>
class MessageEncapsulator<option::ZlibCompress> {
public:
static DataBuffer Encapsulate(const DataBuffer& a_Data, const option::ZlibCompress& a_Option);
static DataBuffer Decapsulate(DataBuffer& a_Data, const option::ZlibCompress& a_Option);
};
} // namespace io
} // 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,65 @@
#pragma once
#include <sp/extensions/tcp/TcpSocket.h>
namespace sp {
namespace io {
/**
* \class TcpListener
*/
class TcpListener : private NonCopyable {
public:
/**
* \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,86 @@
#pragma once
#include <sp/common/NonCopyable.h>
#include <sp/io/IOInterface.h>
namespace sp {
namespace io {
using SocketHandle = int;
struct TcpTag {};
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();
}
};
template <>
class IOInterface<TcpTag> : private NonCopyable {
public:
/**
* \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,
};
IOInterface();
IOInterface(const std::string& a_Host, std::uint16_t a_Port);
IOInterface(IOInterface&& a_Other);
IOInterface& operator=(IOInterface&& a_Other);
virtual ~IOInterface();
DataBuffer Read(std::size_t a_Amount);
void Write(const sp::DataBuffer& a_Data);
/**
* \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;
};
/**
* \typedef TcpSocket
*/
using TcpSocket = IOInterface<TcpTag>;
} // namespace io
} // namespace sp

33
include/sp/io/File.h Normal file
View File

@@ -0,0 +1,33 @@
#pragma once
#include <fstream>
#include <sp/io/IOInterface.h>
namespace sp {
namespace io {
struct FileTag {
enum OpenMode {
In = 1,
Out = 1 << 1,
};
};
template <>
class IOInterface<FileTag> {
private:
std::unique_ptr<std::ifstream> m_FileInput;
std::unique_ptr<std::ofstream> m_FileOutput;
public:
IOInterface(const std::string& a_FilePath, unsigned int a_OpenMode);
IOInterface(IOInterface&& other);
DataBuffer Read(std::size_t a_Amount);
void Write(const sp::DataBuffer& a_Data);
};
using File = IOInterface<FileTag>;
} // namespace io
} // namespace sp

View File

@@ -0,0 +1,58 @@
#pragma once
#include <memory>
#include <sp/common/DataBuffer.h>
namespace sp {
namespace io {
template <typename IOTag>
class IOInterface {
public:
DataBuffer Read(std::size_t a_Amount);
void Write(const DataBuffer& a_Data);
};
template <typename TOption>
class MessageEncapsulator {
public:
static DataBuffer Encapsulate(const DataBuffer& a_Data, const TOption& a_Option);
static DataBuffer Decapsulate(DataBuffer& a_Data, const TOption& a_Option);
};
template <typename IOTag, typename MessageDispatcher, typename MessageFactory, typename... TOptions>
class Stream {
protected:
MessageDispatcher m_Dispatcher;
IOInterface<IOTag> m_Interface;
std::tuple<TOptions...> m_Options;
using MessageBase = typename MessageDispatcher::MessageBaseType;
public:
Stream() {}
Stream(IOInterface<IOTag>&& a_Interface, TOptions&&... a_Options);
Stream(Stream&& a_Stream);
void RecieveMessages();
void SendMessage(const MessageBase& a_Message);
template <typename TOption>
TOption& GetOption() {
return std::get<TOption>(m_Options);
}
MessageDispatcher& GetDispatcher() {
return m_Dispatcher;
}
private:
static DataBuffer Encapsulate(const DataBuffer& a_Data, const TOptions&... a_Options);
static DataBuffer Decapsulate(DataBuffer& a_Data, const TOptions&... a_Options);
};
} // namespace io
} // namespace sp
#include <sp/io/IOInterfaceImpl.inl>

View File

@@ -0,0 +1,129 @@
#pragma once
#include <sp/extensions/Compress.h>
#include <stdexcept>
namespace sp {
namespace details {
template <typename... TOptions>
struct MessageEncapsulatorPack {};
template <>
struct MessageEncapsulatorPack<> {
static DataBuffer Encapsulate(const DataBuffer& a_Data) {
return a_Data;
}
static DataBuffer Decapsulate(DataBuffer& a_Data) {
return a_Data;
}
};
template <typename TOption, typename... TOptions>
struct MessageEncapsulatorPack<TOption, TOptions...> {
static DataBuffer Encapsulate(const DataBuffer& a_Data, const TOption& a_Option, const TOptions&... a_Options) {
DataBuffer data = io::MessageEncapsulator<TOption>::Encapsulate(a_Data, a_Option);
return MessageEncapsulatorPack<TOptions...>::Encapsulate(data, a_Options...);
}
static DataBuffer Decapsulate(DataBuffer& a_Data, const TOption& a_Option, const TOptions&... a_Options) {
DataBuffer data = io::MessageEncapsulator<TOption>::Decapsulate(a_Data, a_Option);
return MessageEncapsulatorPack<TOptions...>::Decapsulate(data, a_Options...);
}
};
} // namespace details
namespace io {
template <typename IOTag, typename MessageDispatcher, typename MessageFactory, typename... TOptions>
Stream<IOTag, MessageDispatcher, MessageFactory, TOptions...>::Stream(IOInterface<IOTag>&& a_Interface, TOptions&&... a_Options) :
m_Interface(std::move(a_Interface)), m_Options(std::make_tuple<TOptions...>(std::move(a_Options)...)) {}
template <typename IOTag, typename MessageDispatcher, typename MessageFactory, typename... TOptions>
Stream<IOTag, MessageDispatcher, MessageFactory, TOptions...>::Stream(
Stream<IOTag, MessageDispatcher, MessageFactory, TOptions...>&& a_Stream) :
m_Dispatcher(std::move(a_Stream.m_Dispatcher)), m_Interface(std::move(a_Stream.m_Interface)) {}
template <typename IOTag, typename MessageDispatcher, typename MessageFactory, typename... TOptions>
void Stream<IOTag, MessageDispatcher, MessageFactory, TOptions...>::SendMessage(const MessageBase& a_Message) {
DataBuffer data = a_Message.Write();
TupleForEach([&data](const auto&... a_Options) {
data = Encapsulate(data, a_Options...);
}, m_Options);
DataBuffer finalData;
finalData.Reserve(data.GetSize() + sizeof(VarInt::MAX_VALUE));
finalData << VarInt{data.GetSize()} << data;
m_Interface.Write(finalData);
}
template <typename IOTag, typename MessageDispatcher, typename MessageFactory, typename... TOptions>
void Stream<IOTag, MessageDispatcher, MessageFactory, TOptions...>::RecieveMessages() {
while (true) {
// reading the first VarInt part byte by byte
std::uint64_t lenghtValue = 0;
unsigned int readPos = 0;
while (true) {
static constexpr int SEGMENT_BITS = (1 << 7) - 1;
static constexpr int CONTINUE_BIT = 1 << 7;
DataBuffer buffer = m_Interface.Read(sizeof(std::uint8_t));
// eof
if (buffer.GetSize() == 0)
return;
std::uint8_t part;
buffer >> part;
lenghtValue |= static_cast<std::uint64_t>(part & SEGMENT_BITS) << readPos;
if ((part & CONTINUE_BIT) == 0)
break;
readPos += 7;
if (readPos >= 8 * sizeof(lenghtValue))
throw std::runtime_error("VarInt is too big");
}
// nothing to read
if (lenghtValue == 0)
return;
DataBuffer buffer;
buffer = m_Interface.Read(lenghtValue);
TupleForEach([&buffer, lenghtValue](const auto&... a_Options) {
buffer = Decapsulate(buffer, a_Options...);
}, m_Options);
VarInt packetType;
buffer >> packetType;
static const MessageFactory messageFactory;
std::unique_ptr<MessageBase> message = messageFactory.CreateMessage(packetType.GetValue());
assert(message != nullptr);
message->Read(buffer);
GetDispatcher().Dispatch(*message);
}
}
template <typename IOTag, typename MessageDispatcher, typename MessageFactory, typename... TOptions>
DataBuffer Stream<IOTag, MessageDispatcher, MessageFactory, TOptions...>::Encapsulate(
const DataBuffer& a_Data, const TOptions&... a_Options) {
return details::MessageEncapsulatorPack<TOptions...>::Encapsulate(a_Data, a_Options...);
}
template <typename IOTag, typename MessageDispatcher, typename MessageFactory, typename... TOptions>
DataBuffer Stream<IOTag, MessageDispatcher, MessageFactory, TOptions...>::Decapsulate(
DataBuffer& a_Data, const TOptions&... a_Options) {
return details::MessageEncapsulatorPack<TOptions...>::Decapsulate(a_Data, a_Options...);
}
} // namespace io
} // namespace sp

23
include/sp/io/Memory.h Normal file
View File

@@ -0,0 +1,23 @@
#pragma once
#include <sp/io/IOInterface.h>
namespace sp {
namespace io {
struct MemoryTag {};
template <>
class IOInterface<MemoryTag> {
private:
sp::DataBuffer m_VirtualIO;
public:
sp::DataBuffer Read(std::size_t a_Amount);
void Write(const sp::DataBuffer& a_Data);
};
using Memory = IOInterface<MemoryTag>;
} // namespace io
} // namespace sp

View File

@@ -1,38 +0,0 @@
#pragma once
/**
* \file Format.h
* \brief This file contains the definition of the `Format` function.
*/
#include <memory>
#include <stdexcept>
#include <string>
namespace sp {
namespace utils {
/**
* \brief Formats a string using a format string and variadic arguments.
*
* This function takes a format string and a variable number of arguments and returns a formatted string.
* The format string can contain placeholders that will be replaced by the corresponding arguments.
*
* \param format The format string.
* \param args The variadic arguments to be formatted.
* \return The formatted string.
* \throws std::runtime_error if an error occurs during formatting.
*/
template <typename... Args>
std::string Format(const std::string& format, Args... args) {
int size = snprintf(nullptr, 0, format.c_str(), args...) + 1; // Extra space for '\0'
if (size <= 0) {
throw std::runtime_error("Error during formatting.");
}
std::unique_ptr<char[]> buf(new char[size]);
snprintf(buf.get(), static_cast<std::size_t>(size), format.c_str(), args...);
return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside
}
} // namespace utils
} // namespace sp

View File

@@ -1,32 +0,0 @@
#pragma once
/**
* \file Log.h
* \brief File defining log functions
*/
#include <string>
namespace sp {
namespace utils {
/**
* \brief Logs a normal message.
* \param msg The message to be logged.
*/
void LOG(const std::string& msg);
/**
* \brief Logs a normal message in debug mode.
* \param msg The message to be logged.
*/
void LOGD(const std::string& msg);
/**
* \brief Logs an error message.
* \param err The error message to be logged.
*/
void LOGE(const std::string& err);
} // namespace utils
} // namespace sp

View File

@@ -1,70 +0,0 @@
#pragma once
/**
* \file Test.h
* \brief File containing unit testing utilities
*/
#include <cstdlib>
#include <sp/misc/Log.h>
namespace sp {
namespace test {
/**
* \def SP_TEST_SUCCESSFUL
* \brief Used in tests to indicate that a test was successful
*/
#define SP_TEST_SUCCESSFUL 0
/**
* \def SP_TEST_FAILED
* \brief Used in tests to indicate that a test failed
*/
#define SP_TEST_FAILED 1
#ifndef __FUNCTION_NAME__
#ifdef _WIN32
#define __FUNCTION_NAME__ __FUNCTION__
#else
#define __FUNCTION_NAME__ __PRETTY_FUNCTION__
#endif
#endif
/**
* \def blitz_test_assert
* \param ... The expression to evaluate
* \brief Evaluates the expression and exits the program if not valid.
* \note It works like a basic assert() but also in release mode
*/
#define sp_test_assert(...) \
if (!static_cast<bool>(__VA_ARGS__)) { \
td::test::LogAssert(#__VA_ARGS__, __FILE__, __LINE__, __FUNCTION_NAME__); \
std::exit(SP_TEST_FAILED); \
}
/**
* \def blitz_debug_assert
* \param ... The expression to execute
* \brief Assertion without checks in release mode
* \note The expression is always executed. However, in release, no checks are made !
*/
#ifdef NDEBUG
#define sp_debug_assert(...) __VA_ARGS__
#else
#define sp_debug_assert sp_test_assert
#endif
/**
* \brief Prints an error message associated with a failed assertion
* \param expression The expression that was tested
* \param file The file in which the assertion failed
* \param line The line in the file in which the assertion failed
* \param function The function in which the assertion failed
*/
void LogAssert(const char* expression, const char* file, int line, const char* function);
} // namespace test
} // namespace sp

122
include/sp/protocol/Field.h Normal file
View File

@@ -0,0 +1,122 @@
#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, std::size_t IAlignment>
class Field {
public:
using StorageType = ValueType;
static constexpr std::size_t AlignmentValue = IAlignment;
// Provide an access to the stored value
StorageType& GetValue() {
return m_Value;
}
const StorageType& GetValue() const {
return m_Value;
}
Field& operator=(const StorageType& value) {
m_Value = value;
return *this;
}
constexpr std::size_t GetAlignment() const {
return IAlignment;
}
private:
StorageType 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

@@ -0,0 +1,141 @@
#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

@@ -0,0 +1,16 @@
#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

@@ -0,0 +1,18 @@
#pragma once
#include <sp/protocol/Message.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 {
template <typename TBase, typename... TOptions>
class MessageBase : public details::MessageImplBuilder<TBase, TOptions...>::Type {};
} // namespace sp
#include <sp/protocol/message/MessagePrinterImpl.h>

View File

@@ -0,0 +1,59 @@
#pragma once
/**
* \file MessageDispatcher.h
* \brief File containing the sp::MessageDispatcher class
*/
#include <map>
#include <memory>
namespace sp {
/**
* \class MessageDispatcher
* \brief Class used to dispatch messages
*/
template <typename MessageIdType, typename MessageBase, typename MessageHandler>
class MessageDispatcher {
private:
std::map<MessageIdType, std::vector<MessageHandler*>> m_Handlers;
public:
using MessageBaseType = MessageBase;
/**
* \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);
};
#include <sp/protocol/message/MessageDispatcherImpl.inl>
} // namespace sp

View File

@@ -0,0 +1,31 @@
#pragma once
#include <array>
#include <functional>
#include <iostream>
#include <memory>
#include <sp/protocol/message/ArrayFillerImpl.h>
namespace sp {
template <typename TBase, typename TTMessages>
class MessageFactory {
public:
using IdType = typename TBase::MsgIdType;
MessageFactory() : m_Factory(details::ArrayFiller<TBase, TTMessages>::ArrayCreate()) {}
std::unique_ptr<TBase> CreateMessage(IdType id) const {
if (id >= m_Factory.size())
return nullptr;
return m_Factory.at(id)();
}
private:
details::ArrayType<TBase> m_Factory;
};
} // namespace sp

View File

@@ -0,0 +1,55 @@
#pragma once
#include <map>
#include <sp/common/Templates.h>
#include <string>
#include <vector>
namespace sp {
template <typename T, std::enable_if_t<details::is_general_t<T>::value, bool> = true>
inline std::string PrintData(const T& a_Data);
template <typename T, std::enable_if_t<details::is_primitive<T>::value, bool> = true>
inline std::string PrintData(T a_Data) {
return std::to_string(a_Data);
}
template <>
inline std::string PrintData(const std::string& a_Data) {
return "\"" + a_Data + "\"";
}
template <typename K, typename V>
std::string PrintData(const std::pair<K, V>& a_Data);
template <typename K, typename V>
std::string PrintData(const std::map<K, V>& a_Data);
template <typename T>
std::string PrintData(const std::vector<T>& a_Data);
template <typename K, typename V>
std::string PrintData(const std::pair<K, V>& a_Data) {
return "{" + PrintData(a_Data.first) + " => " + PrintData(a_Data.second) + "}";
}
template <typename K, typename V>
std::string PrintData(const std::map<K, V>& a_Data) {
std::string result = "{";
for (const auto& pair : a_Data) {
result += PrintData(pair) + ", ";
}
return result.substr(0, result.size() - 2) + "}";
}
template <typename T>
std::string PrintData(const std::vector<T>& a_Data) {
std::string result = "{";
for (const T& value : a_Data) {
result += PrintData(value) + ", ";
}
return result.substr(0, result.size() - 2) + "}";
}
} // namespace sp

View File

@@ -0,0 +1,39 @@
#pragma once
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.emplace_back([]() -> std::unique_ptr<TBase> { return std::make_unique<TMessage>(); });
}
};
} // namespace details
} // namespace sp

View File

@@ -0,0 +1,36 @@
#pragma once
template <typename MessageIdType, typename MessageBase, typename MessageHandler>
void MessageDispatcher<MessageIdType, MessageBase, MessageHandler>::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 MessageIdType, typename MessageBase, typename MessageHandler>
void MessageDispatcher<MessageIdType, MessageBase, MessageHandler>::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 MessageIdType, typename MessageBase, typename MessageHandler>
void MessageDispatcher<MessageIdType, MessageBase, MessageHandler>::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 MessageIdType, typename MessageBase, typename MessageHandler>
void MessageDispatcher<MessageIdType, MessageBase, MessageHandler>::Dispatch(const MessageBase& a_Message) {
MessageIdType type = a_Message.GetId();
for (auto& handler : m_Handlers[type]) {
a_Message.Dispatch(*handler);
}
}

View File

@@ -0,0 +1,49 @@
#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;
// Provide ToStringImpl() if possible
static const bool HasToStringImpl = InterfaceOptions::HasToString;
using Base7 = typename MessageImplProcessToString<Base6, ImplOptions, HasToStringImpl>::Type;
// The last BaseN must be taken as final type.
using Type = Base7;
};
} // namespace details
} // namespace sp

View File

@@ -0,0 +1,66 @@
#pragma once
namespace sp {
namespace option {
// Provide static numeric ID, to facilitate implementation of GetIdImpl()
template <std::uintmax_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 {};
// Print fields of the message, facilitate implementation of
// ToStringImpl()
template <typename TActual>
struct ToStringImpl {};
} // 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::uintmax_t TId, typename... TOptions>
struct MessageImplParsedOptions<option::StaticNumIdImpl<TId>, TOptions...> : public MessageImplParsedOptions<TOptions...> {
static const bool HasStaticNumIdImpl = true;
static const std::uintmax_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

@@ -0,0 +1,107 @@
#pragma once
namespace sp {
namespace details {
// ToString impl
template <typename TBase, typename ParsedImplOptions, bool TImplement>
struct MessageImplProcessToString;
template <typename TBase, typename ParsedImplOptions>
struct MessageImplProcessToString<TBase, ParsedImplOptions, true> {
using Type = MessageImplToStringBase<TBase, typename ParsedImplOptions::ActualMessage>;
};
template <typename TBase, typename ParsedImplOptions>
struct MessageImplProcessToString<TBase, ParsedImplOptions, false> {
using Type = TBase;
};
// 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,9 +1,9 @@
#pragma once
#include <sp/MessageInterfaces.h>
#include <sp/protocol/message/MessageInterfaces.h>
namespace sp {
namespace option {
namespace details {
class EmptyBase {};
@@ -30,9 +30,15 @@ struct MessageInterfaceBuilder {
// add valid functionality if Valid tpe was provided
using Base6 = typename MessageInterfaceProcessValid<Base5, ParsedOptions::HasValid>::Type;
// The last Base6 must be taken as final type.
using Type = Base6;
// add write id functionality if write id and write was provided
using Base7 = typename MessageInterfaceProcessWriteId<Base6, ParsedOptions::HasWriteId && ParsedOptions::HasWriteOperations>::Type;
// add ToString() if HasToString was provided
using Base8 = typename MessageInterfaceProcessToString<Base7, ParsedOptions::HasToString>::Type;
// The last Base8 must be taken as final type.
using Type = Base8;
};
} // namespace option
} // namespace details
} // namespace sp

View File

@@ -0,0 +1,119 @@
#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;
};
// Build to string
template <typename TBase, bool THasToString>
struct MessageInterfaceProcessToString;
template <typename TBase>
struct MessageInterfaceProcessToString<TBase, true> {
using Type = MessageInterfaceToStringBase<TBase>;
};
template <typename TBase>
struct MessageInterfaceProcessToString<TBase, false> {
using Type = TBase;
};
} // namespace details
} // namespace sp

View File

@@ -0,0 +1,7 @@
#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

@@ -0,0 +1,145 @@
#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);
}
// helper
DataBuffer Write() const {
DataBuffer buffer;
this->Write(buffer);
return 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 {
buffer << VarInt{this->GetId()};
this->WriteImpl(buffer);
}
// helper
DataBuffer Write() const {
DataBuffer buffer;
this->Write(buffer);
return buffer;
}
};
// Debug print functionality chunk
template <typename TBase>
class MessageInterfaceToStringBase : public TBase {
public:
std::string ToString() const {
return ToStringImpl();
}
protected:
virtual std::string ToStringImpl() const = 0;
};
} // namespace details
} // namespace sp

View File

@@ -0,0 +1,69 @@
#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;
static const bool HasToString = 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;
};
template <typename... TOptions>
struct MessageInterfaceParsedOptions<option::DebugPrint, TOptions...> : public MessageInterfaceParsedOptions<TOptions...> {
static const bool HasToString = true;
};
} // namespace details
} // namespace sp

View File

@@ -15,12 +15,18 @@ 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 {};
// Add a ToString() method containing fields
struct DebugPrint {};
// Define handler class
template <typename T>
struct Handler {

View File

@@ -0,0 +1,71 @@
#pragma once
#include <sp/common/Reflection.h>
#include <sp/common/Templates.h>
#include <sp/protocol/MessagePrinter.h>
namespace sp {
namespace details {
template <typename... TOptions>
struct IdPrinter {};
template <>
struct IdPrinter<> {
static std::string PrintMessageId() {
return "";
}
};
template <typename TOption, typename... TOptions>
struct IdPrinter<TOption, TOptions...> {
static std::string PrintMessageId() {
return IdPrinter<TOptions...>::PrintMessageId();
}
};
template <typename... TOptions, std::uintmax_t TId>
struct IdPrinter<option::StaticNumIdImpl<TId>, TOptions...> {
static std::string PrintMessageId() {
return "(Id: " + std::to_string(TId) + ")";
}
};
template <typename... TFields>
std::string PrintFields(const std::tuple<TFields...>& a_Fields);
template <typename TField, unsigned int IAlignment>
struct FieldPrinter {
static std::string PrintField(const sp::Field<TField, IAlignment>& a_Field) {
return Reflector<TField>::GetClassName() + "=" + PrintData(a_Field.GetValue());
}
};
template <unsigned int IAlignment, typename TContainer, typename... TFields>
struct FieldPrinter<BitField<TContainer, TFields...>, IAlignment> {
static std::string PrintField(const Field<BitField<TContainer, TFields...>, IAlignment>& a_Field) {
return "BitField<" + Reflector<TContainer>::GetClassName() + ">[" + PrintFields(a_Field.GetValue().GetFields()) + "]";
}
};
template <typename... TFields>
std::string PrintFields(const std::tuple<TFields...>& a_Fields) {
std::string concat;
TupleForEach(
[&concat](const auto& a_Field) {
using TField = typename std::decay<decltype(a_Field)>::type;
constexpr std::size_t alignment = TField::AlignmentValue;
concat += FieldPrinter<typename TField::StorageType, alignment>::PrintField(a_Field) + ", ";
},
a_Fields);
return concat.substr(0, concat.size() - 2);
}
template <typename TBase, typename... TOptions>
std::string PrintMessage(const MessageBase<TBase, TOptions...>& a_Message) {
return sp::GetBasicClassName(a_Message) + sp::details::IdPrinter<TOptions...>::PrintMessageId() + "[" +
sp::details::PrintFields(a_Message.GetFields()) + "]";
}
} // namespace details
} // namespace sp

View File

@@ -0,0 +1,178 @@
#pragma once
namespace sp {
template <typename TBase, typename... TOptions>
class MessageBase;
namespace details {
template <typename TBase, typename... TOptions>
std::string PrintMessage(const MessageBase<TBase, TOptions...>& a_Message);
// ID information chunk
template <typename TBase, typename TActual>
class MessageImplToStringBase : public TBase {
protected:
virtual std::string ToStringImpl() const override {
return PrintMessage(static_cast<const TActual&>(*this));
}
};
// 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, std::size_t 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, std::size_t 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,70 +0,0 @@
#include <iostream>
#include <sp/Field.h>
#include <sp/GenericHandler.h>
#include <sp/MessageBase.h>
#include <memory>
enum MyMsgId {
MyMsgId_Msg1,
MyMsgId_Msg2,
};
class MyHandler; // forward declaration of the handler class.
using MyMessage = sp::Message<sp::option::MsgIdType<MyMsgId>, // add id() operation
sp::option::ReadOperations, // add read() operation
sp::option::WriteOperations, // add write() operation
sp::option::Handler<MyHandler>, // add dispatch() operation
sp::option::ValidCheckInterface, // add valid() operation
sp::option::LittleEndian // use little endian for serialisation
>;
using ActualMessage1Fields = std::tuple<sp::Field<std::uint16_t>, sp::Field<std::int8_t>>;
template <typename TMessageInterface>
class ActualMessage1 : public sp::MessageBase<TMessageInterface,
sp::option::StaticNumIdImpl<MyMsgId_Msg1>, // provide idImpl() if needed
sp::option::DispatchImpl<ActualMessage1<TMessageInterface>>, // provide dispatchImpl() if needed
sp::option::FieldsImpl<ActualMessage1Fields> // provide access to fields and
// readImpl(), writeImpl(),
// lengthImpl(), validImpl()
// functions if needed
> {};
using MyActualMessage1 = ActualMessage1<MyMessage>;
using AllMessages = std::tuple<MyActualMessage1>;
class MyHandler : public sp::GenericHandler<MyMessage, AllMessages> {
public:
void Handle(MyActualMessage1& msg) {
std::cout << "Got message 1 !\n";
}
};
int main() {
MyMessage::MsgIdType test;
auto yes = std::make_unique<ActualMessage1<MyMessage>>();
auto& fields = yes->GetFields();
std::get<0>(fields) = 69;
std::get<1>(fields) = 42;
MyHandler handlerTest;
yes->Dispatch(handlerTest);
sp::DataBuffer buffer;
yes->Write(buffer);
auto yesyes = std::make_unique<ActualMessage1<MyMessage>>();
yesyes->Read(buffer);
std::cout << "Field 1 : " << yesyes->GetField<0>() << std::endl;
std::cout << "Field 2 : " << (unsigned) yesyes->GetField<1>() << std::endl;
return 0;
}

View File

@@ -0,0 +1,74 @@
#include <sp/common/ByteSwapping.h>
#ifdef _WIN32
#include <winsock2.h>
#else
#include <arpa/inet.h>
#endif
#include <algorithm>
namespace sp {
template <typename T>
void SwapBytes(T& value) {
char* ptr = reinterpret_cast<char*>(&value);
std::reverse(ptr, ptr + sizeof(T));
}
bool IsSystemBigEndian() {
static constexpr std::uint16_t test = 10;
static const bool isBigEndian = reinterpret_cast<const std::uint8_t*>(&test)[1] == 10;
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

View File

@@ -4,13 +4,12 @@
#include <iomanip>
#include <sstream>
#include <sp/misc/Format.h>
#include <sp/misc/Log.h>
namespace sp {
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(DataBuffer&& other) : m_Buffer(std::move(other.m_Buffer)), m_ReadOffset(std::move(other.m_ReadOffset)) {}
@@ -133,31 +132,19 @@ std::ostream& operator<<(std::ostream& os, const DataBuffer& buffer) {
return os;
}
bool DataBuffer::ReadFile(const std::string& fileName) {
try {
void DataBuffer::ReadFile(const std::string& fileName) {
std::ifstream file(fileName, std::istream::binary);
std::ostringstream ss;
ss << file.rdbuf();
const std::string& s = ss.str();
m_Buffer = DataBuffer::Data(s.begin(), s.end());
m_ReadOffset = 0;
} catch (std::exception& e) {
utils::LOGE(utils::Format("[IO] Failed to read file %s ! reason : %s", fileName.c_str(), e.what()));
return false;
}
return m_Buffer.size() > 0;
}
bool DataBuffer::WriteFile(const std::string& fileName) const {
try {
void DataBuffer::WriteFile(const std::string& fileName) const {
std::ofstream file(fileName, std::ostream::binary);
file.write(reinterpret_cast<const char*>(m_Buffer.data()), static_cast<std::streamsize>(m_Buffer.size()));
file.flush();
} catch (std::exception& e) {
utils::LOGE(utils::Format("[IO] Failed to read file %s ! reason : %s", fileName.c_str(), e.what()));
return false;
}
return true;
}
} // namespace sp

View File

@@ -1,12 +1,12 @@
#include <sp/common/VarInt.h>
#include <stdexcept>
#include <sp/common/DataBuffer.h>
#include <stdexcept>
namespace sp {
static constexpr int SEGMENT_BITS = 0x7F;
static constexpr int CONTINUE_BIT = 0x80;
static constexpr int SEGMENT_BITS = (1 << 7) - 1;
static constexpr int CONTINUE_BIT = 1 << 7;
std::size_t VarInt::GetSerializedLength() const {
DataBuffer buffer;

View File

@@ -0,0 +1,89 @@
#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
namespace io {
DataBuffer MessageEncapsulator<option::ZlibCompress>::Encapsulate(const DataBuffer& a_Data, const option::ZlibCompress& a_Option) {
static constexpr std::size_t MAX_COMPRESS_THRESHOLD = VarInt::MAX_VALUE;
return zlib::Compress(a_Data, a_Option.m_Enabled ? a_Option.m_CompressionThreshold : MAX_COMPRESS_THRESHOLD);
}
DataBuffer MessageEncapsulator<option::ZlibCompress>::Decapsulate(DataBuffer& a_Data, const option::ZlibCompress& a_Option) {
return zlib::Decompress(a_Data, a_Data.GetSize());
}
} // namespace io
} // 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::IOInterface() : m_Handle(static_cast<SocketHandle>(INVALID_SOCKET)), m_Status(Status::Disconnected) {}
TcpSocket::IOInterface(const std::string& a_Host, std::uint16_t a_Port) : IOInterface() {
Connect(a_Host, a_Port);
}
TcpSocket::IOInterface(IOInterface&& a_Other) {
std::swap(m_Handle, a_Other.m_Handle);
std::swap(m_Status, a_Other.m_Status);
}
TcpSocket::~IOInterface() {}
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=(IOInterface&& a_Other) {
std::swap(m_Handle, a_Other.m_Handle);
std::swap(m_Status, a_Other.m_Status);
return *this;
}
} // namespace io
} // namespace sp

31
src/sp/io/File.cpp Normal file
View File

@@ -0,0 +1,31 @@
#include <sp/io/File.h>
namespace sp {
namespace io {
File::IOInterface(const std::string& a_FilePath, unsigned int a_OpenMode) {
if (a_OpenMode & FileTag::OpenMode::In)
m_FileInput = std::make_unique<std::ifstream>(a_FilePath, std::ios::binary);
if (a_OpenMode & FileTag::OpenMode::Out)
m_FileOutput = std::make_unique<std::ofstream>(a_FilePath, std::ios::binary);
}
File::IOInterface(File&& other) :
m_FileOutput(std::move(other.m_FileOutput)), m_FileInput(std::move(other.m_FileInput)) {}
DataBuffer File::Read(std::size_t a_Amount) {
DataBuffer buffer;
buffer.Resize(a_Amount);
assert(m_FileInput != nullptr);
m_FileInput->read(reinterpret_cast<char*>(buffer.data()), a_Amount);
return buffer;
}
void File::Write(const sp::DataBuffer& a_Data) {
assert(m_FileOutput != nullptr);
m_FileOutput->write(reinterpret_cast<const char*>(a_Data.data()), a_Data.GetSize());
m_FileOutput->flush();
}
} // namespace io
} // namespace sp

16
src/sp/io/Memory.cpp Normal file
View File

@@ -0,0 +1,16 @@
#include <sp/io/Memory.h>
namespace sp {
namespace io {
sp::DataBuffer Memory::Read(std::size_t a_Amount) {
DataBuffer data;
m_VirtualIO.ReadSome(data, a_Amount > m_VirtualIO.GetRemaining() ? m_VirtualIO.GetRemaining() : a_Amount);
return data;
}
void Memory::Write(const sp::DataBuffer& a_Data) {
m_VirtualIO << a_Data;
}
} // namespace io
} // namespace sp

View File

@@ -1,37 +0,0 @@
#include <sp/misc/Log.h>
#ifdef SP_ANDROID_LOGGING
#include <android/log.h>
#else
#include <iostream>
#endif
namespace sp {
namespace utils {
void LOG(const std::string& msg) {
#ifdef SP_ANDROID_LOGGING
__android_log_print(ANDROID_LOG_INFO, "TRACKERS", "%s", msg.c_str());
#else
std::cout << msg << "\n";
#endif
}
void LOGD(const std::string& msg) {
#if !defined(NDEBUG)
LOG(msg);
#endif
}
void LOGE(const std::string& err) {
#ifdef SP_ANDROID_LOGGING
__android_log_print(ANDROID_LOG_ERROR, "TRACKERS", "%s", err.c_str());
#else
std::cerr << err << "\n";
#endif
}
} // namespace utils
} // namespace sp

View File

@@ -1,14 +0,0 @@
#include <sp/misc/Format.h>
#include <sp/misc/Log.h>
#include <sp/misc/Test.h>
namespace sp {
namespace test {
void LogAssert(const char* expression, const char* file, int line, const char* function) {
utils::LOGE(utils::Format("%s:%i: %s: Assertion failed !", file, line, function));
utils::LOGE(utils::Format(" %i |\t%s;", line, expression));
}
} // namespace test
} // namespace sp

42
test/test_file.cpp Normal file
View File

@@ -0,0 +1,42 @@
#include <iostream>
#include <examples/PacketExample.h>
#include <sp/io/File.h>
class CustomPacketHandler : public sp::PacketHandler {
void Handle(const KeepAlivePacket& packet) {
std::cout << "KeepAlive handled ! " << packet.GetKeepAliveId() << "\n";
}
void Handle(const DisconnectPacket& packet) {
std::cout << "Disconnect handled ! " << packet.GetReason() << "\n";
}
void Handle(const UpgradeTowerPacket& packet) {
std::cout << "UpgradeTower handled !\n";
}
};
using FileStream = sp::io::Stream<sp::io::FileTag, sp::PacketDispatcher, sp::PacketFactory, sp::option::ZlibCompress>;
int main() {
auto handler = std::make_shared<CustomPacketHandler>();
FileStream stream(sp::io::File{"test.txt", sp::io::FileTag::In | sp::io::FileTag::Out}, {});
stream.GetDispatcher().RegisterHandler(PacketId::Disconnect, handler.get());
stream.GetDispatcher().RegisterHandler(PacketId::KeepAlive, handler.get());
stream.SendMessage(KeepAlivePacket{96});
stream.SendMessage(KeepAlivePacket{69});
stream.SendMessage(DisconnectPacket{
"This is in the "
"fiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiile !"});
stream.GetOption<sp::option::ZlibCompress>().m_Enabled = false;
stream.SendMessage(DisconnectPacket{
"This is in the "
"fiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiile !"});
stream.RecieveMessages();
return 0;
}

40
test/test_io.cpp Normal file
View File

@@ -0,0 +1,40 @@
#include <iostream>
#include <examples/PacketExample.h>
#include <sp/io/Memory.h>
using DataBufferStream = sp::io::Stream<sp::io::MemoryTag, sp::PacketDispatcher, sp::PacketFactory>;
class CustomPacketHandler : public sp::PacketHandler {
void Handle(const KeepAlivePacket& packet) {
std::cout << "KeepAlive handled ! " << packet.GetKeepAliveId() << "\n";
}
void Handle(const DisconnectPacket& packet) {
std::cout << "Disconnect handled ! " << packet.GetReason() << "\n";
}
void Handle(const UpgradeTowerPacket& packet) {
std::cout << "UpgradeTower handled !\n";
}
};
int main() {
auto handler = std::make_shared<CustomPacketHandler>();
DataBufferStream stream;
stream.GetDispatcher().RegisterHandler(PacketId::Disconnect, handler.get());
// this should not be dispatched
stream.SendMessage(KeepAlivePacket{96});
stream.RecieveMessages();
stream.GetDispatcher().RegisterHandler(PacketId::KeepAlive, handler.get());
stream.SendMessage(KeepAlivePacket{69});
stream.RecieveMessages();
stream.SendMessage(DisconnectPacket{"A valid reason"});
stream.RecieveMessages();
return 0;
}

60
test/test_packets.cpp Normal file
View File

@@ -0,0 +1,60 @@
#include <iostream>
#include <examples/PacketExample.h>
#include <memory>
#include <sp/default/DefaultPacketDispatcher.h>
#include <sp/extensions/Extensions.h>
class KeepAliveHandler : public sp::PacketHandler {
void Handle(const KeepAlivePacket& packet) {
std::cout << "KeepAlive handled !!\n";
std::cout << packet.ToString() << std::endl;
}
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), 789);
auto keepAlive = std::make_unique<KeepAlivePacket>(6969);
sp::PacketMessage* msg = upgradeTower.get();
auto handler = std::make_shared<KeepAliveHandler>();
msg->Dispatch(*handler);
keepAlive->Dispatch(*handler);
sp::DataBuffer buffer = msg->Write();
std::uint8_t msgId;
buffer >> msgId;
auto upgradeTower2 = std::make_unique<UpgradeTowerPacket>();
upgradeTower2->Read(buffer);
std::cout << "Test : " << msg->ToString() << "\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);
sp::PacketDispatcher dispatcher;
dispatcher.RegisterHandler(PacketId::KeepAlive, handler.get());
dispatcher.Dispatch(*packet);
dispatcher.UnregisterHandler(PacketId::KeepAlive, handler.get());
dispatcher.UnregisterHandler(handler.get());
return 0;
}

166
xmake.lua
View File

@@ -2,13 +2,93 @@ add_rules("mode.debug", "mode.release")
set_languages("c++17")
add_requires("enet6")
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"}
}
}
target("SimpleProtocolLib")
add_includedirs("include", {public = true})
set_kind("binary")
add_files("src/**.cpp")
add_packages("enet6", {public = true})
-- 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")
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_kind("$(kind)")
end
end
target("SimpleProtocol")
add_includedirs("include")
add_files("src/sp/**.cpp")
set_group("Library")
set_kind("$(kind)")
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
@@ -19,79 +99,9 @@ for _, file in ipairs(os.files("test/**.cpp")) do
set_kind("binary")
add_files(file)
add_includedirs("include")
set_default(false)
add_deps("SimpleProtocolLib")
add_deps("SimpleProtocol")
add_tests("compile_and_run")
end
--
-- If you want to known more usage about xmake, please see https://xmake.io
--
-- ## FAQ
--
-- You can enter the project directory firstly before building project.
--
-- $ cd projectdir
--
-- 1. How to build project?
--
-- $ xmake
--
-- 2. How to configure project?
--
-- $ xmake f -p [macosx|linux|iphoneos ..] -a [x86_64|i386|arm64 ..] -m [debug|release]
--
-- 3. Where is the build output directory?
--
-- The default output directory is `./build` and you can configure the output directory.
--
-- $ xmake f -o outputdir
-- $ xmake
--
-- 4. How to run and debug target after building project?
--
-- $ xmake run [targetname]
-- $ xmake run -d [targetname]
--
-- 5. How to install target to the system directory or other output directory?
--
-- $ xmake install
-- $ xmake install -o installdir
--
-- 6. Add some frequently-used compilation flags in xmake.lua
--
-- @code
-- -- add debug and release modes
-- add_rules("mode.debug", "mode.release")
--
-- -- add macro definition
-- add_defines("NDEBUG", "_GNU_SOURCE=1")
--
-- -- set warning all as error
-- set_warnings("all", "error")
--
-- -- set language: c99, c++11
-- set_languages("c99", "c++11")
--
-- -- set optimization: none, faster, fastest, smallest
-- set_optimize("fastest")
--
-- -- add include search directories
-- add_includedirs("/usr/include", "/usr/local/include")
--
-- -- add link libraries and search directories
-- add_links("tbox")
-- add_linkdirs("/usr/local/lib", "/usr/lib")
--
-- -- add system link libraries
-- add_syslinks("z", "pthread")
--
-- -- add compilation and link flags
-- add_cxflags("-stdnolib", "-fno-strict-aliasing")
-- add_ldflags("-L/usr/local/lib", "-lpthread", {force = true})
--
-- @endcode
--