feat: add streams
All checks were successful
Linux arm64 / Build (push) Successful in 16s

This commit is contained in:
2025-06-26 19:17:52 +02:00
parent 59bedd6482
commit 0d26879152
25 changed files with 385 additions and 778 deletions

View File

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

@@ -7,12 +7,14 @@
#include <cstddef>
#include <cstdint>
#include <ostream>
#include <functional>
namespace sp {
class DataBuffer;
using ReadFunc = std::function<void(std::uint8_t&)>;
/**
* \class VarInt
* \brief Variable-length format such that smaller numbers use fewer bytes.
@@ -57,6 +59,7 @@ class VarInt {
*/
friend DataBuffer& operator>>(DataBuffer& in, VarInt& var);
void Read(const ReadFunc& read);
};
} // namespace sp

View File

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

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

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

View File

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

View File

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

@@ -0,0 +1,16 @@
#pragma once
#include <sp/common/DataBuffer.h>
namespace sp {
class MessageEncapsulator {
public:
MessageEncapsulator() {}
virtual ~MessageEncapsulator() {}
virtual DataBuffer Encapsulate(const DataBuffer& a_Data) = 0;
virtual DataBuffer Decapsulate(DataBuffer& a_Data) = 0;
};
} // namespace sp

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

@@ -0,0 +1,36 @@
#pragma once
#include <boost/pfr.hpp>
#include <sp/common/ByteSwapping.h>
#include <sp/common/DataBuffer.h>
namespace sp {
namespace details {
template <typename T>
void WriteField(DataBuffer& a_Buffer, const T& a_Data) {
T swapped = a_Data;
ToNetwork(swapped);
a_Buffer << swapped;
}
template <typename T>
void ReadField(DataBuffer& a_Buffer, T& a_Data) {
a_Buffer >> a_Data;
FromNetwork(a_Data);
}
template <typename TData>
DataBuffer WriteMessage(const TData& a_MessageData) {
DataBuffer buffer;
boost::pfr::for_each_field(a_MessageData, [&buffer](const auto& a_Field) { WriteField(buffer, a_Field); });
return buffer;
}
template <typename TData>
void ReadMessage(DataBuffer& a_Buffer, TData& a_MessageData) {
boost::pfr::for_each_field(a_MessageData, [&a_Buffer](auto& a_Field) { ReadField(a_Buffer, a_Field); });
}
} // namespace details
} // namespace sp

View File

@@ -0,0 +1,34 @@
#pragma once
#include <sp/common/DataBuffer.h>
#include <sp/io/IoInterface.h>
#include <sp/io/MessageEncapsulator.h>
#include <vector>
namespace sp {
template <typename TMessageFactory>
class MessageStream {
private:
std::vector<MessageEncapsulator> m_Encapsulators;
std::shared_ptr<IoInterface> m_Stream;
using MessageBaseType = typename TMessageFactory::MessageBaseType;
using MessageIdType = typename MessageBaseType::MessageIdType;
public:
MessageStream(std::shared_ptr<IoInterface>&& a_Stream) : m_Stream(std::move(a_Stream)) {}
std::unique_ptr<MessageBaseType> ReadMessage();
std::unique_ptr<MessageBaseType> ReadMessage(MessageIdType a_Id);
void WriteMessage(const MessageBaseType& a_Message, bool a_WriteId = true);
private:
DataBuffer ReadAndDecapsulate();
std::unique_ptr<MessageBaseType> MakeMessage(DataBuffer& buffer, MessageIdType a_Id);
};
} // namespace sp
#include <sp/io/MessageStream.inl>

View File

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

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

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

View File

@@ -1,7 +1,7 @@
#pragma once
#include <sp/protocol/MessageBase.h>
#include <sp/protocol/MessageIO.h>
#include <sp/io/MessageIO.h>
namespace sp {
@@ -9,7 +9,6 @@ template <typename TData, typename TMessageID, TMessageID ID, typename THandler>
class ConcreteMessage : public MessageBase<TMessageID, THandler> {
public:
using DataType = TData;
using ThisType = ConcreteMessage<TData, TMessageID, ID, THandler>;
template <typename... T>
ConcreteMessage(const T&... args) : m_Data{args...} {}
@@ -21,15 +20,15 @@ class ConcreteMessage : public MessageBase<TMessageID, THandler> {
}
virtual void Dispatch(THandler& handler) const override {
handler.Handle(static_cast<const ThisType&>(*this));
handler.Handle(static_cast<const DataType&>(m_Data));
}
virtual void Read(std::istream& a_Is) override {
details::ReadMessage(a_Is, m_Data);
virtual void Read(DataBuffer& a_Buffer) override {
details::ReadMessage(a_Buffer, m_Data);
}
virtual void Write(std::ostream& a_Os) const override {
details::WriteMessage(a_Os, m_Data);
virtual DataBuffer Write() const override {
return details::WriteMessage(m_Data);
}
private:

View File

@@ -1,6 +1,7 @@
#pragma once
#include <iosfwd>
#include <sp/common/DataBuffer.h>
namespace sp {
@@ -17,8 +18,8 @@ class MessageBase {
virtual void Dispatch(HandlerType& handler) const = 0;
virtual void Read(std::istream& a_Is) = 0;
virtual void Write(std::ostream& a_Os) const = 0;
virtual void Read(DataBuffer& a_Buffer) = 0;
virtual DataBuffer Write() const = 0;
};
} // namespace sp

View File

@@ -12,6 +12,7 @@ template <typename TBase, typename TTMessages>
class MessageFactory {
public:
using IdType = typename TBase::MessageIdType;
using MessageBaseType = TBase;
MessageFactory() {
constexpr std::size_t messageCount = std::tuple_size_v<TTMessages>;

View File

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

View File

@@ -1,38 +0,0 @@
#pragma once
#include <boost/pfr.hpp>
#include <iostream>
#include <sp/common/ByteSwapping.h>
namespace sp {
namespace details {
template<typename T>
void WriteField(std::ostream& a_Os, const T& a_Data) {
T swapped = a_Data;
ToNetwork(swapped);
a_Os.write(reinterpret_cast<const char*>(&swapped), sizeof(a_Data));
}
template<typename T>
void ReadField(std::istream& a_Is, T& a_Data) {
a_Is.read(reinterpret_cast<char*>(&a_Data), sizeof(a_Data));
FromNetwork(a_Data);
}
template <typename TData>
void WriteMessage(std::ostream& a_Os, const TData& a_MessageData) {
boost::pfr::for_each_field(a_MessageData, [&a_Os](const auto& a_Field) {
WriteField(a_Os, a_Field);
});
}
template <typename TData>
void ReadMessage(std::istream& a_Is, TData& a_MessageData) {
boost::pfr::for_each_field(a_MessageData, [&a_Is](auto& a_Field) {
ReadField(a_Is, a_Field);
});
}
} // namespace details
} // namespace sp

View File

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

View File

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

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

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

View File

@@ -1,31 +0,0 @@
#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_FileInput(std::move(other.m_FileInput)), m_FileOutput(std::move(other.m_FileOutput)) {}
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

View File

@@ -1,16 +0,0 @@
#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,7 +1,9 @@
#include <sp/protocol/ConcreteMessage.h>
#include <sp/protocol/GenericHandler.h>
#include <sp/protocol/MessageDispatcher.h>
#include <sp/protocol/MessageFactory.h>
#include <sp/protocol/MessageHandler.h>
#include <sp/io/MessageStream.h>
#include <sp/io/StdIo.h>
#include <cstdint>
#include <iostream>
@@ -24,12 +26,12 @@ using KeepAliveMessage = Message<KeepAlivePacket, PacketID::KeepAlive>;
using AllMessages = std::tuple<KeepAliveMessage>;
class PacketHandler : public sp::GenericHandler<AllMessages> {};
class PacketHandler : public sp::MessageHandler<AllMessages> {};
class MyHandler : public PacketHandler {
public:
virtual void Handle(const KeepAliveMessage& msg) {
std::cout << "yo !" << "\n";
virtual void Handle(const KeepAlivePacket& msg) {
std::cout << "I recieved a keep alive : " << msg.m_KeepAlive << "\n";
}
};
@@ -37,8 +39,10 @@ using PacketDispatcher = sp::MessageDispatcher<PacketBase>;
using PacketFactory = sp::MessageFactory<PacketBase, AllMessages>;
using PacketStream = sp::MessageStream<PacketFactory>;
int main() {
KeepAliveMessage m{5UL};
KeepAliveMessage m{69UL};
// dispatch tests
@@ -56,9 +60,23 @@ int main() {
std::ofstream file {"test.bin"};
message->Write(file);
PacketStream p(std::make_shared<sp::StdOuput>(file));
p.WriteMessage(m);
file.flush();
std::ifstream file2 {"test.bin"};
PacketStream p2(std::make_shared<sp::StdInput>(file2));
auto message2 = p2.ReadMessage();
d.Dispatch(*message2);
// message->Write(file);
// file << std::endl;
m.Write(file);
// m.Write(file);
// file << std::endl;
// message->Read(file);
return 0;