diff --git a/CMakeLists.txt b/CMakeLists.txt index 18f3ea6..5a11d4d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,8 @@ add_subdirectory(lib/base) add_subdirectory(lib/impl) add_subdirectory(lib/http) +add_subdirectory(lib/aries) + # projects add_subdirectory(src) diff --git a/lib/aries/CMakeLists.txt b/lib/aries/CMakeLists.txt new file mode 100644 index 0000000..d2a0717 --- /dev/null +++ b/lib/aries/CMakeLists.txt @@ -0,0 +1,11 @@ +add_library(ls_aries + Tags.cpp +) + +lobbyserver_target(ls_aries) + +target_link_libraries(ls_aries PUBLIC + base::base +) + +add_library(ls::aries ALIAS ls_aries) diff --git a/lib/aries/Message.hpp b/lib/aries/Message.hpp new file mode 100644 index 0000000..be42a77 --- /dev/null +++ b/lib/aries/Message.hpp @@ -0,0 +1,22 @@ +#pragma once +#include +#include + +namespace ls::aries { + + /// The Aries message header. + struct [[gnu::packed]] AriesMessageHeader { + /// Message FourCC. + base::FourCC32_t typeCode {}; + + /// Apparently a extra 4 bytes of FourCC? + base::FourCC32_t typeCodeHi {}; + + /// The size of the message payload. Is network order (big endian), and includes the size of this header + base::NetworkOrder messageSize {}; + }; + + // Sanity checking. + static_assert(sizeof(AriesMessageHeader) == 12, "Aries message header size is invalid"); + +} // namespace ls::proto \ No newline at end of file diff --git a/lib/aries/MessageIo.hpp b/lib/aries/MessageIo.hpp new file mode 100644 index 0000000..f366c42 --- /dev/null +++ b/lib/aries/MessageIo.hpp @@ -0,0 +1,63 @@ +#include +#include +#include +#include +#include +#include +#include + +namespace ls::aries { + + constexpr static auto MAX_PAYLOAD_SIZE_IN_MB = 1; + constexpr static auto MAX_PAYLOAD_SIZE_IN_BYTES = MAX_PAYLOAD_SIZE_IN_MB * (1024 * 1024); + + /// Raw read aries massage. + struct RawAriesMessage { + AriesMessageHeader header; + std::vector tagPayload; + }; + + namespace errors { + + struct TagPayloadTooLarge : std::exception { + TagPayloadTooLarge(u32 size) + : payloadSize(size) { + whatStr = std::format("Tag payload over {} MB (Max is {}MB).", (static_cast(payloadSize) / 1024 / 1024), MAX_PAYLOAD_SIZE_IN_MB); + } + + const char* what() const noexcept override { + return whatStr.c_str(); + } + + private: + u32 payloadSize; + std::string whatStr; + }; + + } // namespace errors + + /// Reads an Aries message from an Boost.Asio async read stream. + template + base::Awaitable AsyncReadAriesMessage(AsyncReadStream& stream) { + RawAriesMessage res; + + // Read the header first + co_await asio::async_read(stream, asio::buffer(&res.header, sizeof(res.header)), asio::deferred); + + auto realPayloadSize = res.header.messageSize - sizeof(res.header); + + // Read tag payload (if there is one) + if(res.header.messageSize != sizeof(res.header)) { + // Sanity check. I don't expect game payloads to ever reach this large, but who knows. + if(realPayloadSize > MAX_PAYLOAD_SIZE_IN_BYTES) + throw errors::TagPayloadTooLarge(realPayloadSize); + + res.tagPayload.resize(realPayloadSize); + + co_await asio::async_read(stream, asio::buffer(res.tagPayload), asio::deferred); + } + + co_return res; + } + +} // namespace ls::aries \ No newline at end of file diff --git a/lib/aries/README.md b/lib/aries/README.md new file mode 100644 index 0000000..5547579 --- /dev/null +++ b/lib/aries/README.md @@ -0,0 +1,3 @@ +# aries + +Library for working with DirtySDK Aries (old-lobby) protocol messages. \ No newline at end of file diff --git a/lib/aries/Tags.cpp b/lib/aries/Tags.cpp new file mode 100644 index 0000000..8ad2046 --- /dev/null +++ b/lib/aries/Tags.cpp @@ -0,0 +1,101 @@ +#include + +namespace ls::aries { + + bool ParseTagField(std::span tagFieldData, TagMap& outMap) { + // Nothing to parse, + // which isn't exclusively a failure condition. + if(tagFieldData.empty()) + return true; + + std::string key; + std::string val; + + usize inputIndex = 0; + + // TODO: Investigate rewriting this using ragel or something, so it's not something that has to be + // heavily maintained or unit tested to avoid bugs. + // + // Also: maybe we should strongly type or deserialize binary payloads. There are multiple variants of these.. + + enum class ReaderState : u32 { + InKey, ///< The state machine is currently parsing a key. + InValue ///< The state machine is currently parsing a value. + } state { ReaderState::InKey }; + + // Parse all properties, using a fairly simple state machine to do so. + // + // State transition mappings: + // = - from key to value state (if in key state) + // \n - from value to key state (if in value state, otherwise error) + + while(inputIndex != tagFieldData.size()) { + switch(tagFieldData[inputIndex]) { + case '=': + if(state == ReaderState::InKey) { + state = ReaderState::InValue; + break; + } else { + // If we're in the value state, we're allowed to nest = signs, I think. + // if not, then this is PROBABLY an error state. + val += static_cast(tagFieldData[inputIndex]); + } + break; + + case '\n': + if(state == ReaderState::InValue) { + outMap[key] = val; + + // Reset the state machine, to read another property. + key.clear(); + val.clear(); + state = ReaderState::InKey; + break; + } else { + // If we get here in the key state, this is DEFINITELY an error. + return false; + } + + // Any other characters are not important to the state machine, + // and are instead written to the given staging string for the current + // state machine state. + default: + switch(state) { + case ReaderState::InKey: + key += static_cast(tagFieldData[inputIndex]); + break; + case ReaderState::InValue: + // Skip past quotation marks. + if(static_cast(tagFieldData[inputIndex]) == '\"' || static_cast(tagFieldData[inputIndex]) == '\'') + break; + + val += static_cast(tagFieldData[inputIndex]); + break; + } + break; + } + + inputIndex++; + } + + // Parse succeeded + return true; + } + + void SerializeTagFields(const TagMap& map, std::string& outStr) { + std::string tagFieldBuffer{}; + + // Reserve a sane amount, to avoid allocations when serializing + // (in most cases; larger tag count MIGHT still cause some allocation pressure). + tagFieldBuffer.reserve(512); + + // Serialize the tag fields + for(auto [key, value] : map) + tagFieldBuffer += std::format("{}={}\n", key, value); + + // Null terminate it. (TODO: We shouldn't have to do this anymore, std::string does this on its own) + tagFieldBuffer.push_back('\0'); + + outStr = std::move(tagFieldBuffer); + } +} // namespace ls::aries \ No newline at end of file diff --git a/lib/aries/Tags.hpp b/lib/aries/Tags.hpp new file mode 100644 index 0000000..ba71039 --- /dev/null +++ b/lib/aries/Tags.hpp @@ -0,0 +1,17 @@ + +#include +#include + +namespace ls::aries { + + using TagMap = std::unordered_map; + + /// Parses tag field data to a TagMap. + /// # Returns + /// True on success; false otherwise (TODO: Move to exceptions or error_category) + bool ParseTagField(std::span tagFieldData, TagMap& outMap); + + /// Serializes a TagMap to a string. + void SerializeTagFields(const TagMap& map, std::string& outStr); + +} \ No newline at end of file diff --git a/lib/base/fourcc.hpp b/lib/base/fourcc.hpp index 48cb320..424e696 100644 --- a/lib/base/fourcc.hpp +++ b/lib/base/fourcc.hpp @@ -18,15 +18,20 @@ namespace base { case std::endian::big: return static_cast((fccString[0] << 24) | (fccString[1] << 16) | (fccString[2] << 8) | fccString[3]); - // endian::native is practically implemented in most standard libraries - // by aliasing the native endian enumerator, so that it will match - // one of the two cases here. therefore this code is literally useless - // and i have no idea why i even wrote it 4 years ago :') - //default: - // throw "Invalid endian provided? How'd you do that?"; // NOLINT + // endian::native is practically implemented in most standard libraries + // by aliasing the native endian enumerator, so that it will match + // one of the two cases here. therefore this code is literally useless + // and i have no idea why i even wrote it 4 years ago :') + // default: + // throw "Invalid endian provided? How'd you do that?"; // NOLINT } } + inline std::string FourCC32ToString(FourCC32_t fcc) { + auto* fccAsBytes = std::bit_cast(&fcc); + return std::format("{:c}{:c}{:c}{:c}", fccAsBytes[0], fccAsBytes[1], fccAsBytes[2], fccAsBytes[3]); + } + // TODO: 64-bit version which returns a u64 (if required?) } // namespace base \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cd53aaf..b216733 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -19,4 +19,5 @@ target_link_libraries(lobbyserver PRIVATE base::base base::http Boost::json + ls::aries ) diff --git a/src/DirtySockClient.cpp b/src/DirtySockClient.cpp index 9ae827e..7f9243e 100644 --- a/src/DirtySockClient.cpp +++ b/src/DirtySockClient.cpp @@ -1,17 +1,18 @@ #include "DirtySockClient.hpp" -#include -#include -#include +#include #include "DirtySockServer.hpp" -constexpr static auto MAX_PAYLOAD_SIZE_IN_MB = 4; -constexpr static auto MAX_PAYLOAD_SIZE_IN_BYTES = MAX_PAYLOAD_SIZE_IN_MB * (1024 * 1024); - // All our Asio/network related ops set this expiry time before they call Asio ops // so that Beast's stream timer stuff can work its magic and automatically timeout. -constexpr static auto EXPIRY_TIME = std::chrono::seconds(10); +// +// The read timeout is selected as high as it is mostly because Dirtysock's LobbyAPI seems to prefer pings +// (That is, ICMP Echo Requests) to determine if the server is responding, and does not seem to send +// periodic data as a result.. A bit of a iffy decision, but it was one made over 20 years ago and it worked +// for at least 5, so I can't really complain that much. +constexpr static auto READ_EXPIRY_TIME = std::chrono::seconds(960); +constexpr static auto WRITE_EXPIRY_TIME = std::chrono::seconds(10); namespace ls { @@ -48,39 +49,28 @@ namespace ls { } base::Awaitable DirtySockClient::Network_ReadMessage() { - proto::WireMessageHeader header; - std::vector propertyBuffer; - try { - // Read the header first - stream.expires_after(EXPIRY_TIME); - co_await asio::async_read(stream, asio::buffer(&header, sizeof(header)), asio::deferred); + // Set the read expiry time. We only do this once every read because + // we want the timer to account throughout the whole AsyncReadAriesMessage + // operation, which issues multiple I/O operations to complete its work. + stream.expires_after(READ_EXPIRY_TIME); - auto realPayloadSize = header.payloadSize - sizeof(header); - - // Sanity check. I don't expect game payloads to ever reach this large, but who knows. - if(realPayloadSize > MAX_PAYLOAD_SIZE_IN_BYTES) { - logger->error("{}: WOAH! Client sent a message with a payload size of {} MB (Max is {}MB).", GetAddress().to_string(), (static_cast(header.payloadSize) / 1024 / 1024), MAX_PAYLOAD_SIZE_IN_MB); - co_return nullptr; - } + auto res = co_await aries::AsyncReadAriesMessage(stream); // If the message type isn't in the server's allowed message list, give up. // (we probably should throw instead...) if(!server->allowedMessages.empty()) { - if(!server->allowedMessages.contains(static_cast(header.typeCode))) + if(!server->allowedMessages.contains(res.header.typeCode)) co_return nullptr; } - propertyBuffer.resize(realPayloadSize); - - stream.expires_after(EXPIRY_TIME); - co_await asio::async_read(stream, asio::buffer(propertyBuffer), asio::deferred); - - logger->info("read properties"); - // this function may fail and also return nullptr. Maybe we should instead throw an exception there // (that we leave to callers to catch) - co_return MessageFactory::CreateAndParseMessage(header, propertyBuffer); + co_return AriesMessageFactory::CreateAndParseMessage(res.header, res.tagPayload); + + } catch(aries::errors::TagPayloadTooLarge& large) { + logger->error("{}: {}", GetAddress().to_string(), large.what()); + co_return nullptr; } catch(bsys::system_error& ec) { // Instead of bubbling up errors we DO care about, rethrow them to the higher level // calling us. @@ -100,7 +90,7 @@ namespace ls { message->SerializeTo(buf); try { - stream.expires_after(std::chrono::seconds(EXPIRY_TIME)); + stream.expires_after(std::chrono::seconds(WRITE_EXPIRY_TIME)); co_await asio::async_write(stream, asio::buffer(buf), asio::deferred); } catch(bsys::system_error& ec) { if(ec.code() != asio::error::operation_aborted || ec.code() != beast::error::timeout) @@ -164,7 +154,7 @@ namespace ls { } } catch(bsys::system_error& ec) { if(ec.code() == asio::error::eof) { - logger->info("{}: Connection closed", GetAddress().to_string()); + logger->info("Server \"{}\": {} closed connection", GetServer()->name, GetAddress().to_string()); } else if(ec.code() != asio::error::operation_aborted) logger->error("{}: Error in DirtySockClient::Coro_ReaderEnd(): {}", GetAddress().to_string(), ec.what()); } @@ -174,7 +164,7 @@ namespace ls { } base::Awaitable DirtySockClient::Run() { - logger->info("{}: Got connection", GetAddress().to_string()); + logger->info("Server \"{}\": Got connection from {}", GetServer()->name, GetAddress().to_string()); asio::co_spawn( stream.get_executor(), [self = shared_from_this()] { return self->Coro_WriterEnd(); diff --git a/src/DirtySockClient.hpp b/src/DirtySockClient.hpp index 71e1793..2e3f218 100644 --- a/src/DirtySockClient.hpp +++ b/src/DirtySockClient.hpp @@ -12,8 +12,8 @@ namespace ls { struct DirtySockServer; struct DirtySockClient : public std::enable_shared_from_this { - using MessagePtr = base::Ref; - using ConstMessagePtr = base::Ref; + using MessagePtr = base::Ref; + using ConstMessagePtr = base::Ref; using Protocol = asio::ip::tcp; using Stream = base::BeastStream; diff --git a/src/DirtySockServer.cpp b/src/DirtySockServer.cpp index 5b48afc..40e1fe5 100644 --- a/src/DirtySockServer.cpp +++ b/src/DirtySockServer.cpp @@ -6,8 +6,8 @@ namespace ls { - DirtySockServer::DirtySockServer(asio::any_io_executor exec) - : exec(exec), acceptor(exec) { + DirtySockServer::DirtySockServer(asio::any_io_executor exec, std::string_view name) + : exec(exec), acceptor(exec), name(name.data(), name.length()) { } void DirtySockServer::Start(const Protocol::endpoint& ep) { @@ -34,7 +34,7 @@ namespace ls { acceptor.bind(endpoint); acceptor.listen(asio::socket_base::max_listen_connections); - logger->info("DirtySockServer listening on {}:{}", endpoint.address().to_string(), endpoint.port()); + logger->info("DirtySockServer \"{}\" listening on {}:{}", name, endpoint.address().to_string(), endpoint.port()); while(true) { auto socket = co_await acceptor.async_accept(asio::deferred); diff --git a/src/DirtySockServer.hpp b/src/DirtySockServer.hpp index 1858e88..9bcebdb 100644 --- a/src/DirtySockServer.hpp +++ b/src/DirtySockServer.hpp @@ -16,7 +16,7 @@ namespace ls { /// alias for thing using AllowedMessagesSet = std::set; - DirtySockServer(asio::any_io_executor exec); + DirtySockServer(asio::any_io_executor exec, std::string_view name); void Start(const Protocol::endpoint& endpoint); @@ -39,6 +39,7 @@ namespace ls { std::set> clientSet; + std::string name; base::Ref logger = spdlog::get("ls_dsock_server"); }; diff --git a/src/IMessage.cpp b/src/IMessage.cpp index 0de4ba9..7ee176e 100644 --- a/src/IMessage.cpp +++ b/src/IMessage.cpp @@ -6,166 +6,77 @@ // So debug message can just reply #include "DirtySockClient.hpp" -#include "WireMessage.hpp" namespace ls { - IMessage::IMessage(const proto::WireMessageHeader& header) + IAriesMessage::IAriesMessage(const aries::AriesMessageHeader& header) : header(header) { } - bool IMessage::ParseFromInputBuffer(std::span inputBuffer) { - // Nothing to parse, - // which isn't exclusively a failure condition. - if(inputBuffer.empty()) - return true; - - std::string key; - std::string val; - - usize inputIndex = 0; - - // TODO: Investigate rewriting this using ragel or something, so it's not something that has to be - // heavily maintained or unit tested to avoid bugs. - - enum class ReaderState : u32 { - InKey, ///< The state machine is currently parsing a key. - InValue ///< The state machine is currently parsing a value. - } state { ReaderState::InKey }; - - // Parse all properties, using a fairly simple state machine to do so. - // - // State transition mappings: - // = - from key to value state (if in key state) - // \n - from value to key state (if in value state, otherwise error) - - while(inputIndex != inputBuffer.size()) { - switch(inputBuffer[inputIndex]) { - case '=': - if(state == ReaderState::InKey) { - state = ReaderState::InValue; - break; - } else { - // If we're in the value state, we're allowed to nest = signs, I think. - // if not, then this is PROBABLY an error state. - val += static_cast(inputBuffer[inputIndex]); - } - break; - - case '\n': - if(state == ReaderState::InValue) { - properties[key] = val; - - // Reset the state machine, to read another property. - key.clear(); - val.clear(); - state = ReaderState::InKey; - break; - } else { - // If we get here in the key state, this is DEFINITELY an error. - return false; - } - - // Any other characters are not important to the state machine, - // and are instead written to the given staging string for the current - // state machine state. - default: - switch(state) { - case ReaderState::InKey: - key += static_cast(inputBuffer[inputIndex]); - break; - case ReaderState::InValue: - // Skip past quotation marks. - if(static_cast(inputBuffer[inputIndex]) == '\"' || static_cast(inputBuffer[inputIndex]) == '\'') - break; - - val += static_cast(inputBuffer[inputIndex]); - break; - } - break; - } - - inputIndex++; - } - - // Parse succeeded - return true; + bool IAriesMessage::ParseFromInputBuffer(std::span inputBuffer) { + return aries::ParseTagField(inputBuffer, tagFields); } - void IMessage::SerializeTo(std::vector& dataBuffer) const { + void IAriesMessage::SerializeTo(std::vector& dataBuffer) const { std::string serializedProperties; - // Reserve a sane amount, to avoid allocations when serializing properties - // (in most cases; larger messages MIGHT still cause some allocation pressure.) - serializedProperties.reserve(512); - - // Serialize properties - { - auto i = properties.size(); - for(auto [key, value] : properties) - if(--i != 0) - serializedProperties += std::format("{}={}\n", key, value); - else - serializedProperties += std::format("{}={}", key, value); - } - - // Null terminate the property data. - serializedProperties.push_back('\0'); + aries::SerializeTagFields(tagFields, serializedProperties); // Create an appropriate header for the data. - proto::WireMessageHeader header { - .typeCode = static_cast(TypeCode()), - .typeCodeHi = 0, - .payloadSize = sizeof(proto::WireMessageHeader) + serializedProperties.length() - 1 + aries::AriesMessageHeader newHeader { + .typeCode = header.typeCode, + .typeCodeHi = header.typeCodeHi, + .messageSize = sizeof(aries::AriesMessageHeader) + serializedProperties.length() }; - auto fullLength = sizeof(proto::WireMessageHeader) + serializedProperties.length(); + auto fullLength = sizeof(aries::AriesMessageHeader) + serializedProperties.length(); // Resize the output buffer to the right size dataBuffer.resize(fullLength); // Write to the output buffer now. - memcpy(&dataBuffer[0], &header, sizeof(proto::WireMessageHeader)); - memcpy(&dataBuffer[sizeof(proto::WireMessageHeader)], serializedProperties.data(), serializedProperties.length()); + memcpy(&dataBuffer[0], &newHeader, sizeof(aries::AriesMessageHeader)); + memcpy(&dataBuffer[sizeof(aries::AriesMessageHeader)], serializedProperties.data(), serializedProperties.length()); } - const std::optional IMessage::MaybeGetKey(const std::string& key) const { - if(properties.find(key) == properties.end()) + const std::optional IAriesMessage::MaybeGetKey(const std::string& key) const { + if(tagFields.find(key) == tagFields.end()) return std::nullopt; else - return properties.at(key); + return tagFields.at(key); } - void IMessage::SetOrAddProperty(const std::string& key, const std::string& value) { - properties[key] = value; + void IAriesMessage::SetOrAddProperty(const std::string& key, const std::string& value) { + tagFields[key] = value; } // message factory /// Debug message, used to.. well, debug, obviously. - struct DebugMessage : IMessage { - explicit DebugMessage(const proto::WireMessageHeader& header) - : IMessage(header) { + struct DebugMessage : IAriesMessage { + explicit DebugMessage(const aries::AriesMessageHeader& header) + : IAriesMessage(header) { } base::Awaitable Process(base::Ref client) override { - auto* fccbytes = std::bit_cast(&header.typeCode); + spdlog::info("Unhandled Aries message \"{}\" \"{}\"", FourCC32ToString(header.typeCode), base::FourCC32ToString(header.typeCodeHi)); + if(!tagFields.empty()) { + spdlog::info("With tags:"); - spdlog::info("Debug Message: FourCC lo: \"{:c}{:c}{:c}{:c}\"", fccbytes[0], fccbytes[1], fccbytes[2], fccbytes[3]); - spdlog::info("Debug Message Properties:"); - - for(auto [key, value] : properties) - spdlog::info("{}: {}", key, value); - - // a bit :( however it works to just replay the message. + for(auto [key, value] : tagFields) + spdlog::info("{}={}", key, value); + } + // This snippet is a fair bit :( however it works to just replay the message. + // And it's like 1kb at most it's not going to hurt much for code that will be snipped off + // at some point anyways lol client->Send(std::make_shared(*this)); co_return; } }; - struct MessageWithFourCC : IMessage { - explicit MessageWithFourCC(const proto::WireMessageHeader& header) - : IMessage(header) { + struct AriesSendOnlyMessage : IAriesMessage { + explicit AriesSendOnlyMessage(const aries::AriesMessageHeader& header) + : IAriesMessage(header) { } base::Awaitable Process(base::Ref client) override { @@ -174,16 +85,16 @@ namespace ls { } }; - MessageFactory::FactoryMap& MessageFactory::GetFactoryMap() { - static MessageFactory::FactoryMap factoryMap; + AriesMessageFactory::FactoryMap& AriesMessageFactory::GetFactoryMap() { + static AriesMessageFactory::FactoryMap factoryMap; return factoryMap; } - base::Ref MessageFactory::CreateAndParseMessage(const proto::WireMessageHeader& header, std::span propertyDataBuffer) { + base::Ref AriesMessageFactory::CreateAndParseMessage(const aries::AriesMessageHeader& header, std::span propertyDataBuffer) { const auto& factories = GetFactoryMap(); - base::Ref ret = nullptr; + base::Ref ret = nullptr; - if(const auto it = factories.find(static_cast(header.typeCode)); it != factories.end()) + if(const auto it = factories.find(header.typeCode); it != factories.end()) ret = (it->second)(header); else ret = std::make_shared(header); @@ -194,14 +105,15 @@ namespace ls { return ret; } - base::Ref MessageFactory::CreateMessageWithFourCC(base::FourCC32_t fourCC) { - auto fakeHeader = proto::WireMessageHeader { - static_cast(fourCC), - 0, - 0 + base::Ref AriesMessageFactory::CreateSendMessage(base::FourCC32_t fourCC, base::FourCC32_t fourccHi) { + // A fake header so that we can just use the constructor of IAriesMessage + auto fakeHeader = aries::AriesMessageHeader { + fourCC, + fourccHi, + sizeof(aries::AriesMessageHeader) }; - return std::make_shared(fakeHeader); + return std::make_shared(fakeHeader); } } // namespace ls \ No newline at end of file diff --git a/src/IMessage.hpp b/src/IMessage.hpp index 65ced5b..2f6c792 100644 --- a/src/IMessage.hpp +++ b/src/IMessage.hpp @@ -3,16 +3,17 @@ #include #include -#include "WireMessage.hpp" +#include +#include namespace ls { - struct Server; struct DirtySockClient; - struct IMessage { - explicit IMessage(const proto::WireMessageHeader& header); + /// A higher level repressentation of an Aries message. Contains a method to process the data. + struct IAriesMessage { + explicit IAriesMessage(const aries::AriesMessageHeader& header); - virtual ~IMessage() = default; + virtual ~IAriesMessage() = default; /// Parses from input buffer. The data must live until /// this function returns. @@ -20,11 +21,9 @@ namespace ls { /// error code enumeration..) if the parsing fails. bool ParseFromInputBuffer(std::span data); - /// Serializes to a output data buffer. + /// Serializes this Aries message to a output data buffer. void SerializeTo(std::vector& dataBuffer) const; - base::FourCC32_t TypeCode() const { return static_cast(header.typeCode); } - /// Process a single message. virtual base::Awaitable Process(base::Ref client) = 0; @@ -32,54 +31,54 @@ namespace ls { void SetOrAddProperty(const std::string& key, const std::string& value); - const proto::WireMessageHeader& GetHeader() const { return header; } + const aries::AriesMessageHeader& GetHeader() const { return header; } protected: - proto::WireMessageHeader header; + aries::AriesMessageHeader header; /// all properties. - std::unordered_map properties {}; + aries::TagMap tagFields {}; }; - struct MessageFactory { + struct AriesMessageFactory { /// Creates and parses the given implementation of IMessage. - static base::Ref CreateAndParseMessage(const proto::WireMessageHeader& header, std::span propertyDataBuffer); + static base::Ref CreateAndParseMessage(const aries::AriesMessageHeader& header, std::span propertyDataBuffer); /// Creates a message intended for sending to a client. - static base::Ref CreateMessageWithFourCC(base::FourCC32_t fourCC); + static base::Ref CreateSendMessage(base::FourCC32_t fourCC, base::FourCC32_t fourccHi = {}); private: template - friend struct MessageMixin; + friend struct AriesMessageMixIn; - using FactoryMap = std::unordered_map (*)(const proto::WireMessageHeader&)>; + using FactoryMap = std::unordered_map (*)(const aries::AriesMessageHeader&)>; static FactoryMap& GetFactoryMap(); }; template - struct MessageMixin : IMessage { + struct AriesMessageMixIn : IAriesMessage { constexpr static auto TYPE_CODE = base::FourCC32(); - explicit MessageMixin(const proto::WireMessageHeader& header) - : IMessage(header) { + explicit AriesMessageMixIn(const aries::AriesMessageHeader& header) + : IAriesMessage(header) { static_cast(registered); } private: static bool Register() { - MessageFactory::GetFactoryMap().insert({ TYPE_CODE, [](const proto::WireMessageHeader& header) -> base::Ref { - return std::make_shared(header); - } }); + AriesMessageFactory::GetFactoryMap().insert({ TYPE_CODE, [](const aries::AriesMessageHeader& header) -> base::Ref { + return std::make_shared(header); + } }); return true; } static inline bool registered = Register(); }; // :( Makes the boilerplate shorter and sweeter (and easier to change) though. -#define LS_MESSAGE(T, fourCC) struct T : public ls::MessageMixin +#define LS_MESSAGE(T, fourCC) struct T : public ls::AriesMessageMixIn #define LS_MESSAGE_CTOR(T, fourCC) \ - explicit T(const ls::proto::WireMessageHeader& header) \ - : ls::MessageMixin(header) { \ + explicit T(const ls::aries::AriesMessageHeader& header) \ + : ls::AriesMessageMixIn(header) { \ } } // namespace ls \ No newline at end of file diff --git a/src/Server.cpp b/src/Server.cpp index 48a8f1a..a23c9fc 100644 --- a/src/Server.cpp +++ b/src/Server.cpp @@ -16,19 +16,25 @@ namespace ls { base::Awaitable Server::Start() { // TODO: make mariadb connection first, if this fails blow up - lobbyServer = std::make_shared(exec); + lobbyRdirServer = std::make_shared(exec, "Redirector server"); + lobbyRdirServer->SetAllowedMessages({ base::FourCC32<"@dir">() }); - lobbyServer->Start(config.lobbyListenEndpoint); + lobbyRdirServer->Start(config.lobbyListenEndpoint); - if(!lobbyServer->Listening()) { + if(!lobbyRdirServer->Listening()) { // uh oh worm.. - logger->error("for some reason lobby server isnt listening.."); + logger->error("for some reason the redirector server isnt listening.."); co_return; } - buddyServer = std::make_shared(exec); + buddyServer = std::make_shared(exec, "Lobby server"); buddyServer->Start(config.buddyListenEndpoint); + if(!lobbyRdirServer->Listening()) { + // uh oh worm.. + logger->error("for some reason the lobby server isnt listening.."); + co_return; + } // TODO: http server? there's apparently some stuff we can have that uses it diff --git a/src/Server.hpp b/src/Server.hpp index 25a50f6..f59d9eb 100644 --- a/src/Server.hpp +++ b/src/Server.hpp @@ -30,7 +30,7 @@ namespace ls { base::AsyncConditionVariable stopCv; bool stopping { false }; - base::Ref lobbyServer; + base::Ref lobbyRdirServer; base::Ref buddyServer; Config config; diff --git a/src/WireMessage.hpp b/src/WireMessage.hpp deleted file mode 100644 index bd853c6..0000000 --- a/src/WireMessage.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once -#include - -namespace ls::proto { - - /// The on-wire message header. - struct [[gnu::packed]] WireMessageHeader { - /// Message FourCC. - u32 typeCode {}; - - /// Apparently a extra 4 bytes of FourCC? - u32 typeCodeHi {}; - - /// The size of the data payload. - base::NetworkOrder payloadSize {}; - }; - - // Sanity checking. - static_assert(sizeof(WireMessageHeader) == 12, "Wire message header size is invalid"); - -} // namespace ls::proto \ No newline at end of file diff --git a/src/messages/RdirMessage.cpp b/src/messages/RdirMessage.cpp index daa4f56..e2b11d0 100644 --- a/src/messages/RdirMessage.cpp +++ b/src/messages/RdirMessage.cpp @@ -6,21 +6,21 @@ // clang-format off -LS_MESSAGE(AtDirMessage, "@dir") { - LS_MESSAGE_CTOR(AtDirMessage, "@dir") +LS_MESSAGE(AriesRedirMessage, "@dir") { + LS_MESSAGE_CTOR(AriesRedirMessage, "@dir") base::Awaitable Process(base::Ref client) override { spdlog::info("Got redir message!"); - spdlog::info("@dir Properties:"); + spdlog::info("@dir tags:"); - for(auto [key, value] : properties) + for(auto [key, value] : tagFields) spdlog::info("{}: {}", key, value); // create our @dir message we send BACK to the client. - auto rdirOut = ls::MessageFactory::CreateMessageWithFourCC(base::FourCC32<"@dir">()); + auto rdirOut = ls::AriesMessageFactory::CreateSendMessage(base::FourCC32<"@dir">()); - // TODO: Use the server class to get at this.. + // TODO: Please use the server class to get at this.. rdirOut->SetOrAddProperty("ADDR", "192.168.1.149"); rdirOut->SetOrAddProperty("PORT", "10998"); // sample