From 03d799e0648749e0f883ecab7ceb00f0bc817e0f Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Wed, 26 Feb 2025 09:29:31 +0000 Subject: [PATCH] Add generic IO (#3) Reviewed-on: https://git.ale-pri.com/Persson-dev/Simple-Protocol-Lib/pulls/3 Co-authored-by: Persson-dev Co-committed-by: Persson-dev --- include/examples/PacketExample.h | 7 +- include/sp/io/File.h | 33 ++++++++ include/sp/io/IOInterface.h | 41 ++++++++++ include/sp/io/IOInterfaceImpl.inl | 78 +++++++++++++++++++ include/sp/io/Memory.h | 23 ++++++ include/sp/protocol/MessageDispatcher.h | 5 +- include/sp/protocol/MessageFactory.h | 6 +- ...{ArrayFillerImpl.inl => ArrayFillerImpl.h} | 5 +- .../protocol/message/MessageInterfacesImpl.h | 14 ++++ src/sp/io/File.cpp | 31 ++++++++ src/sp/io/Memory.cpp | 16 ++++ test/test_file.cpp | 36 +++++++++ test/test_io.cpp | 40 ++++++++++ test/test_packets.cpp | 7 +- xmake.lua | 11 ++- 15 files changed, 338 insertions(+), 15 deletions(-) create mode 100644 include/sp/io/File.h create mode 100644 include/sp/io/IOInterface.h create mode 100644 include/sp/io/IOInterfaceImpl.inl create mode 100644 include/sp/io/Memory.h rename include/sp/protocol/message/{ArrayFillerImpl.inl => ArrayFillerImpl.h} (94%) create mode 100644 src/sp/io/File.cpp create mode 100644 src/sp/io/Memory.cpp create mode 100644 test/test_file.cpp create mode 100644 test/test_io.cpp diff --git a/include/examples/PacketExample.h b/include/examples/PacketExample.h index c185c8f..bf51532 100644 --- a/include/examples/PacketExample.h +++ b/include/examples/PacketExample.h @@ -6,12 +6,13 @@ enum PacketId { UpgradeTower, }; -#include #include +#include #include // they must be in the same order as in the enum ! using AllPackets = std::tuple; -#include -#include \ No newline at end of file +#include +#include +#include \ No newline at end of file diff --git a/include/sp/io/File.h b/include/sp/io/File.h new file mode 100644 index 0000000..2b329e6 --- /dev/null +++ b/include/sp/io/File.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +namespace sp { +namespace io { + +struct FileTag { + enum OpenMode { + In = 1, + Out = 1 << 1, + }; +}; + +template <> +class IOInterface { + private: + std::unique_ptr m_FileInput; + std::unique_ptr 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; + +} // namespace io +} // namespace sp diff --git a/include/sp/io/IOInterface.h b/include/sp/io/IOInterface.h new file mode 100644 index 0000000..65c19f7 --- /dev/null +++ b/include/sp/io/IOInterface.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include + +namespace sp { +namespace io { + +template +class IOInterface { + public: + DataBuffer Read(std::size_t a_Amount); + void Write(const DataBuffer& a_Data); +}; + +template +class Stream { + protected: + MessageDispatcher m_Dispatcher; + IOInterface m_Interface; + + using MessageBase = typename MessageDispatcher::MessageBaseType; + using MsgIdType = typename MessageBase::MsgIdType; + + public: + Stream() {} + Stream(IOInterface&& a_Interface); + Stream(Stream&& a_Stream); + + void RecieveMessages(); + void SendMessage(const MessageBase& a_Message); + + MessageDispatcher& GetDispatcher() { + return m_Dispatcher; + } +}; + +} // namespace io +} // namespace sp + +#include \ No newline at end of file diff --git a/include/sp/io/IOInterfaceImpl.inl b/include/sp/io/IOInterfaceImpl.inl new file mode 100644 index 0000000..8c19734 --- /dev/null +++ b/include/sp/io/IOInterfaceImpl.inl @@ -0,0 +1,78 @@ +#pragma once + +#include + +namespace sp { +namespace io { +template +Stream::Stream(IOInterface&& a_Interface) : m_Interface(std::move(a_Interface)) {} + +template +Stream::Stream(Stream&& a_Stream) : m_Dispatcher(std::move(a_Stream.m_Dispatcher)), m_Interface(std::move(a_Stream.m_Interface)) {} + +template +void Stream::SendMessage(const MessageBase& a_Message) { + // TODO: process compress + encryption + DataBuffer data = a_Message.Write(); + DataBuffer dataSize; + m_Interface.Write(dataSize << sp::VarInt{data.GetSize()} << data); +} + +template +void Stream::RecieveMessages() { + // TODO: process compress + encryption + 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(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); + + // TODO: process compress + encryption + + MsgIdType packetType; + buffer >> packetType; + + static const MessageFactory messageFactory; + + std::unique_ptr message = messageFactory.CreateMessage(packetType); + + assert(message != nullptr); + + message->Read(buffer); + + GetDispatcher().Dispatch(*message); + } +} + +} // namespace io +} // namespace sp \ No newline at end of file diff --git a/include/sp/io/Memory.h b/include/sp/io/Memory.h new file mode 100644 index 0000000..83021bc --- /dev/null +++ b/include/sp/io/Memory.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +namespace sp { +namespace io { + +struct MemoryTag {}; + +template <> +class IOInterface { + private: + sp::DataBuffer m_VirtualIO; + + public: + sp::DataBuffer Read(std::size_t a_Amount); + void Write(const sp::DataBuffer& a_Data); +}; + +using Memory = IOInterface; + +} // namespace io +} // namespace sp diff --git a/include/sp/protocol/MessageDispatcher.h b/include/sp/protocol/MessageDispatcher.h index eb346e6..cd6b5cd 100644 --- a/include/sp/protocol/MessageDispatcher.h +++ b/include/sp/protocol/MessageDispatcher.h @@ -6,6 +6,7 @@ */ #include +#include namespace sp { @@ -19,6 +20,8 @@ class MessageDispatcher { std::map>> m_Handlers; public: + using MessageBaseType = MessageBase; + /** * \brief Constructor */ @@ -51,4 +54,4 @@ class MessageDispatcher { #include -} // namespace blitz +} // namespace sp diff --git a/include/sp/protocol/MessageFactory.h b/include/sp/protocol/MessageFactory.h index 3212dd9..abc5a99 100644 --- a/include/sp/protocol/MessageFactory.h +++ b/include/sp/protocol/MessageFactory.h @@ -5,9 +5,9 @@ #include #include -namespace sp { +#include -#include +namespace sp { template class MessageFactory { @@ -16,7 +16,7 @@ class MessageFactory { MessageFactory() : m_Factory(details::ArrayFiller::ArrayCreate()) {} - std::unique_ptr CreateMessage(IdType id) { + std::unique_ptr CreateMessage(IdType id) const { if (id >= m_Factory.size()) return nullptr; return m_Factory.at(id)(); diff --git a/include/sp/protocol/message/ArrayFillerImpl.inl b/include/sp/protocol/message/ArrayFillerImpl.h similarity index 94% rename from include/sp/protocol/message/ArrayFillerImpl.inl rename to include/sp/protocol/message/ArrayFillerImpl.h index 410afba..f0bf3dd 100644 --- a/include/sp/protocol/message/ArrayFillerImpl.inl +++ b/include/sp/protocol/message/ArrayFillerImpl.h @@ -1,12 +1,12 @@ #pragma once +namespace sp { namespace details { template using ArrayType = std::vector(void)>>; - template struct ArrayFiller {}; @@ -35,4 +35,5 @@ struct ArrayFiller { } }; -} // namespace details \ No newline at end of file +} // namespace details +} // namespace sp \ No newline at end of file diff --git a/include/sp/protocol/message/MessageInterfacesImpl.h b/include/sp/protocol/message/MessageInterfacesImpl.h index 5f464a8..e04f054 100644 --- a/include/sp/protocol/message/MessageInterfacesImpl.h +++ b/include/sp/protocol/message/MessageInterfacesImpl.h @@ -75,6 +75,13 @@ class MessageInterfaceWriteBase : public TBase { WriteImpl(buffer); } + // helper + DataBuffer Write() const { + DataBuffer buffer; + this->Write(buffer); + return buffer; + } + protected: virtual void WriteImpl(DataBuffer& buffer) const = 0; }; @@ -113,6 +120,13 @@ class MessageInterfaceWriteIdBase : public TBase { this->WriteData(this->GetId(), buffer); this->WriteImpl(buffer); } + + // helper + DataBuffer Write() const { + DataBuffer buffer; + this->Write(buffer); + return buffer; + } }; } // namespace details diff --git a/src/sp/io/File.cpp b/src/sp/io/File.cpp new file mode 100644 index 0000000..2bccc19 --- /dev/null +++ b/src/sp/io/File.cpp @@ -0,0 +1,31 @@ +#include + +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(a_FilePath, std::ios::binary); + if (a_OpenMode & FileTag::OpenMode::Out) + m_FileOutput = std::make_unique(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(buffer.data()), a_Amount); + return buffer; +} + +void File::Write(const sp::DataBuffer& a_Data) { + assert(m_FileOutput != nullptr); + m_FileOutput->write(reinterpret_cast(a_Data.data()), a_Data.GetSize()); + m_FileOutput->flush(); +} + +} // namespace io +} // namespace sp diff --git a/src/sp/io/Memory.cpp b/src/sp/io/Memory.cpp new file mode 100644 index 0000000..57ffd4f --- /dev/null +++ b/src/sp/io/Memory.cpp @@ -0,0 +1,16 @@ +#include + +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 diff --git a/test/test_file.cpp b/test/test_file.cpp new file mode 100644 index 0000000..f05d696 --- /dev/null +++ b/test/test_file.cpp @@ -0,0 +1,36 @@ +#include + +#include +#include + +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; + +int main() { + auto handler = std::make_shared(); + + FileStream stream(sp::io::File{"test.txt", sp::io::FileTag::In | sp::io::FileTag::Out}); + stream.GetDispatcher().RegisterHandler(PacketId::Disconnect, handler); + stream.GetDispatcher().RegisterHandler(PacketId::KeepAlive, handler); + + stream.SendMessage(KeepAlivePacket{96}); + stream.SendMessage(KeepAlivePacket{69}); + stream.SendMessage(DisconnectPacket{"This is in the file !"}); + + stream.RecieveMessages(); + + return 0; +} \ No newline at end of file diff --git a/test/test_io.cpp b/test/test_io.cpp new file mode 100644 index 0000000..8bb78ec --- /dev/null +++ b/test/test_io.cpp @@ -0,0 +1,40 @@ +#include + +#include +#include + +using DataBufferStream = sp::io::Stream; + +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(); + + DataBufferStream stream; + stream.GetDispatcher().RegisterHandler(PacketId::Disconnect, handler); + + // this should not be dispatched + stream.SendMessage(KeepAlivePacket{96}); + stream.RecieveMessages(); + + stream.GetDispatcher().RegisterHandler(PacketId::KeepAlive, handler); + + stream.SendMessage(KeepAlivePacket{69}); + stream.RecieveMessages(); + stream.SendMessage(DisconnectPacket{"I don't know"}); + stream.RecieveMessages(); + + return 0; +} \ No newline at end of file diff --git a/test/test_packets.cpp b/test/test_packets.cpp index 3af7410..e89e171 100644 --- a/test/test_packets.cpp +++ b/test/test_packets.cpp @@ -3,8 +3,8 @@ #include #include -#include #include +#include class KeepAliveHandler : public sp::PacketHandler { void Handle(const KeepAlivePacket& packet) { @@ -28,8 +28,7 @@ int main() { auto handler = std::make_shared(); msg->Dispatch(*handler); - sp::DataBuffer buffer; - msg->Write(buffer); + sp::DataBuffer buffer = msg->Write(); std::uint8_t msgId; buffer >> msgId; @@ -37,7 +36,7 @@ int main() { auto upgradeTower2 = std::make_unique(); upgradeTower2->Read(buffer); - std::cout << "Test : " << (unsigned) upgradeTower2->GetTowerId() << "\n"; + std::cout << "Test : " << (unsigned)upgradeTower2->GetTowerId() << "\n"; sp::PacketFactory factory; auto packet = factory.CreateMessage(msgId); diff --git a/xmake.lua b/xmake.lua index 6bd68e8..79ca7d5 100644 --- a/xmake.lua +++ b/xmake.lua @@ -47,9 +47,16 @@ end target("SimpleProtocolLib") add_includedirs("include") - add_headerfiles("include/(sp/common/**.h)", "include/(sp/common/**.h)", "include/(sp/common/**.h)") + add_files("src/sp/**.cpp") + + local includeFolders = {"common", "default", "io", "protocol"} + for _, folder in ipairs(includeFolders) do + add_headerfiles("include/(sp/" .. folder .. "/**.h)") + end + + -- we don't want extensions + remove_files("src/sp/extensions/**.cpp") set_group("Library") - add_files("src/sp/common/*.cpp") set_kind("$(kind)") -- Tests