From 3e34c0b3bc26336a0cee31f7d4babaee5aa28041 Mon Sep 17 00:00:00 2001 From: Joshua Scott Date: Thu, 5 Apr 2018 17:36:26 +0100 Subject: [PATCH] protocol: Implement module loading and I/O for DML messages 'das alota cod --- include/ki/protocol/dml/Message.h | 25 ++- include/ki/protocol/dml/MessageManager.h | 28 +-- include/ki/protocol/dml/MessageModule.h | 14 +- include/ki/protocol/dml/MessageTemplate.h | 2 +- include/ki/protocol/exception.h | 26 +++ src/protocol/dml/Message.cpp | 121 +++++++++++- src/protocol/dml/MessageManager.cpp | 224 ++++++++++++++++++++++ src/protocol/dml/MessageModule.cpp | 128 +++++++++++++ src/protocol/dml/MessageTemplate.cpp | 47 +++++ 9 files changed, 591 insertions(+), 24 deletions(-) create mode 100644 include/ki/protocol/exception.h diff --git a/include/ki/protocol/dml/Message.h b/include/ki/protocol/dml/Message.h index 4709198..096c96c 100644 --- a/include/ki/protocol/dml/Message.h +++ b/include/ki/protocol/dml/Message.h @@ -13,7 +13,7 @@ namespace dml { public: Message(uint8_t service_id = 0, uint8_t type = 0); - virtual ~Message() = default; + virtual ~Message(); uint8_t get_service_id() const; void set_service_id(uint8_t service_id); @@ -21,16 +21,35 @@ namespace dml uint8_t get_type() const; void set_type(uint8_t type); - ki::dml::Record &get_record(); - const ki::dml::Record &get_record() const; + ki::dml::Record *get_record(); + const ki::dml::Record *get_record() const; + + /** + * Sets the record to a copy of the specified record. + */ void set_record(const ki::dml::Record &record); + /** + * If raw data is present, then this uses the specified record + * to parse the raw DML message payload into a new Record. + * If raw data is not present, this is equivalent to set_record. + * + * If the raw data is parsed successfully, the internal raw + * data is cleared, and calls to get_record will return a valid + * Record pointer. + * + * However, if the raw data is not parsed successfully, then + * calls to get_record will still return nullptr. + */ + void use_template_record(const ki::dml::Record &record); + void write_to(std::ostream &ostream) const override final; void read_from(std::istream &istream) override final; size_t get_size() const override final; private: uint8_t m_service_id; uint8_t m_type; + std::vector m_raw_data; ki::dml::Record *m_record; }; } diff --git a/include/ki/protocol/dml/MessageManager.h b/include/ki/protocol/dml/MessageManager.h index be3281e..75e9e16 100644 --- a/include/ki/protocol/dml/MessageManager.h +++ b/include/ki/protocol/dml/MessageManager.h @@ -13,24 +13,28 @@ namespace dml class MessageManager { public: - MessageManager(); + MessageManager() = default; ~MessageManager(); - static MessageManager &get_singleton(); - - const MessageModule &load_module(std::string filepath); - const MessageModule &get_module(uint8_t service_id) const; - const MessageModule &get_module(std::string protocol_type) const; + const MessageModule *load_module(std::string filepath); + const MessageModule *get_module(uint8_t service_id) const; + const MessageModule *get_module(const std::string &protocol_type) const; MessageBuilder &build_message(uint8_t service_id, uint8_t message_type) const; - MessageBuilder &build_message(uint8_t service_id, std::string message_name) const; - MessageBuilder &build_message(std::string service_type, uint8_t message_type) const; - MessageBuilder &build_message(std::string service_type, std::string message_name) const; + MessageBuilder &build_message(uint8_t service_id, const std::string &message_name) const; + MessageBuilder &build_message(const std::string &protocol_type, uint8_t message_type) const; + MessageBuilder &build_message(const std::string &protocol_type, const std::string &message_name) const; - const Message *from_binary(std::istream &istream); + /** + * If the DML message header cannot be read, then a nullptr + * is returned; otherwise, a valid Message pointer is always returned. + * However, that does not mean that the message itself is valid. + * + * To verify if the record was completely parsed, get_record + * should return a valid Record pointer, rather than nullptr. + */ + const Message *message_from_binary(std::istream &istream) const; private: - static MessageManager *g_instance; - MessageModuleList m_modules; MessageModuleServiceIdMap m_service_id_map; MessageModuleProtocolTypeMap m_protocol_type_map; diff --git a/include/ki/protocol/dml/MessageModule.h b/include/ki/protocol/dml/MessageModule.h index 137cc42..94d4e38 100644 --- a/include/ki/protocol/dml/MessageModule.h +++ b/include/ki/protocol/dml/MessageModule.h @@ -3,6 +3,7 @@ #include #include #include +#include #include namespace ki @@ -14,7 +15,7 @@ namespace dml class MessageModule { public: - MessageModule(uint8_t service_id, std::string protocol_type); + MessageModule(uint8_t service_id = 0, std::string protocol_type = ""); ~MessageModule(); uint8_t get_service_id() const; @@ -26,10 +27,9 @@ namespace dml std::string get_protocol_desription() const; void set_protocol_description(std::string protocol_description); - const MessageTemplate &add_message_template(std::string name, uint8_t type, - ki::dml::Record *record); - const MessageTemplate &get_message_template(uint8_t type); - const MessageTemplate &get_message_template(std::string name); + const MessageTemplate *add_message_template(std::string name, ki::dml::Record *record); + const MessageTemplate *get_message_template(uint8_t type) const; + const MessageTemplate *get_message_template(std::string name) const; MessageBuilder &build_message(uint8_t message_type) const; MessageBuilder &build_message(std::string message_name) const; @@ -37,10 +37,10 @@ namespace dml uint8_t m_service_id; std::string m_protocol_type; std::string m_protocol_description; + uint8_t m_last_message_type; - std::vector m_templates; + std::array m_templates; std::map m_message_name_map; - std::map m_message_type_map; }; typedef std::vector MessageModuleList; diff --git a/include/ki/protocol/dml/MessageTemplate.h b/include/ki/protocol/dml/MessageTemplate.h index ba8b744..178c152 100644 --- a/include/ki/protocol/dml/MessageTemplate.h +++ b/include/ki/protocol/dml/MessageTemplate.h @@ -24,7 +24,7 @@ namespace dml const ki::dml::Record &get_record() const; void set_record(ki::dml::Record *record); - MessageBuilder &build_message(); + MessageBuilder &build_message() const; private: std::string m_name; uint8_t m_type; diff --git a/include/ki/protocol/exception.h b/include/ki/protocol/exception.h new file mode 100644 index 0000000..998c011 --- /dev/null +++ b/include/ki/protocol/exception.h @@ -0,0 +1,26 @@ +#pragma once +#include + +namespace ki +{ +namespace protocol +{ + class runtime_error : public std::runtime_error + { + public: + runtime_error(std::string message) : std::runtime_error(message) { } + }; + + class parse_error : public runtime_error + { + public: + parse_error(std::string message) : runtime_error(message) { } + }; + + class value_error : public runtime_error + { + public: + value_error(std::string message) : runtime_error(message) { } + }; +} +} \ No newline at end of file diff --git a/src/protocol/dml/Message.cpp b/src/protocol/dml/Message.cpp index d9a4dab..8fdef6f 100644 --- a/src/protocol/dml/Message.cpp +++ b/src/protocol/dml/Message.cpp @@ -1,4 +1,5 @@ #include "ki/protocol/dml/Message.h" +#include "ki/protocol/exception.h" namespace ki { @@ -6,7 +7,125 @@ namespace protocol { namespace dml { - + Message::Message(uint8_t service_id, uint8_t type) + { + m_service_id = service_id; + m_type = type; + } + + Message::~Message() + { + delete m_record; + } + + uint8_t Message::get_service_id() const + { + return m_service_id; + } + + void Message::set_service_id(uint8_t service_id) + { + m_service_id = service_id; + } + + uint8_t Message::get_type() const + { + return m_type; + } + + void Message::set_type(uint8_t type) + { + m_type = type; + } + + ki::dml::Record *Message::get_record() + { + return m_record; + } + + const ki::dml::Record *Message::get_record() const + { + return m_record; + } + + void Message::set_record(const ki::dml::Record &record) + { + m_record = new ki::dml::Record(record); + } + + void Message::use_template_record(const ki::dml::Record &record) + { + set_record(record); + if (!m_raw_data.empty()) + { + std::istringstream iss(std::string(m_raw_data.data(), m_raw_data.size())); + try + { + m_record->read_from(iss); + m_raw_data.clear(); + } + catch (ki::dml::parse_error &e) + { + delete m_record; + m_record = nullptr; + + std::ostringstream oss; + oss << "Error reading DML message payload: " << e.what(); + throw parse_error(oss.str()); + } + } + } + + void Message::write_to(std::ostream &ostream) const + { + ki::dml::Record record; + record.add_field("m_service_id")->set_value(m_service_id); + record.add_field("m_type")->set_value(m_type); + auto *size_field = record.add_field("size"); + if (m_record) + size_field->set_value(m_record->get_size() + 4); + else + size_field->set_value(m_raw_data.size() + 4); + record.write_to(ostream); + + if (m_record) + record.write_to(ostream); + else + ostream.write(m_raw_data.data(), m_raw_data.size()); + } + + void Message::read_from(std::istream &istream) + { + ki::dml::Record record; + auto *service_id_field = record.add_field("ServiceID"); + auto *message_type_field = record.add_field("MsgType"); + auto *size_field = record.add_field("Length"); + try + { + record.read_from(istream); + } + catch (ki::dml::parse_error &e) + { + std::ostringstream oss; + oss << "Error reading DML message header: " << e.what(); + throw parse_error(oss.str()); + } + + m_service_id = service_id_field->get_value(); + m_type = message_type_field->get_value(); + const ki::dml::USHRT size = size_field->get_value() - 4; + m_raw_data.resize(size); + istream.read(m_raw_data.data(), size); + if (istream.fail()) + throw parse_error("Not enough data was available to read DML message payload."); + } + + size_t Message::get_size() const + { + if (m_record) + return 4 + m_record->get_size(); + return 4 + m_raw_data.size(); + } } } } \ No newline at end of file diff --git a/src/protocol/dml/MessageManager.cpp b/src/protocol/dml/MessageManager.cpp index ae3703d..73eee25 100644 --- a/src/protocol/dml/MessageManager.cpp +++ b/src/protocol/dml/MessageManager.cpp @@ -1,4 +1,10 @@ #include "ki/protocol/dml/MessageManager.h" +#include "ki/protocol/exception.h" +#include "ki/dml/Record.h" +#include "ki/util/ValueBytes.h" +#include +#include +#include namespace ki { @@ -6,7 +12,225 @@ namespace protocol { namespace dml { + MessageManager::~MessageManager() + { + for (auto it = m_modules.begin(); + it != m_modules.end(); ++it) + delete *it; + m_modules.clear(); + m_service_id_map.clear(); + m_protocol_type_map.clear(); + } + const MessageModule *MessageManager::load_module(std::string filepath) + { + // Open the file + std::ifstream ifs(filepath, std::ios::ate); + if (!ifs.is_open()) + { + std::ostringstream oss; + oss << "Could not open file: " << filepath; + throw value_error(oss.str()); + } + + // Load contents into memory + size_t size = ifs.tellg(); + ifs.seekg(0, std::ios::beg); + char *data = new char[size + 1] { 0 }; + ifs.read(data, size); + + // Parse the contents + rapidxml::xml_document<> doc; + try + { + doc.parse<0>(data); + } + catch (rapidxml::parse_error &e) + { + delete[] data; + + std::ostringstream oss; + oss << "Failed to parse: " << filepath; + throw parse_error(oss.str()); + } + + // It's safe to allocate the module we're working on now + auto *message_module = new MessageModule(); + + // Get the root node and iterate through children + // Each child is a MessageTemplate + auto *root = doc.first_node(); + for (auto *node = root->first_node(); + node; node = node->next_sibling()) + { + // Parse the record node inside this node + auto *record_node = node->first_node(); + if (!record_node) + continue; + auto *record = new ki::dml::Record(); + record->from_xml(record_node); + + // The message name is initially based on the element name + const std::string message_name = node->name(); + if (message_name == "_ProtocolInfo") + { + auto *service_id_field = record->get_field("ServiceID"); + auto *type_field = record->get_field("ProtocolType"); + auto *description_field = record->get_field("ProtocolDescription"); + + // Set the module metadata from this template + if (service_id_field) + message_module->set_service_id(service_id_field->get_value()); + if (type_field) + message_module->set_protocol_type(type_field->get_value()); + if (description_field) + message_module->set_protocol_description(description_field->get_value()); + } + else + { + // The template will use the record itself to figure out name and type; + // we only give the XML data incase the record doesn't have it defined. + auto *message_template = message_module->add_message_template(message_name, record); + if (!message_template) + { + delete[] data; + delete message_module; + delete record; + + std::ostringstream oss; + oss << "Failed to create message template for "; + oss << message_name; + throw value_error(oss.str()); + } + } + } + + // Make sure we aren't overwriting another module + if (m_service_id_map.count(message_module->get_service_id()) == 1) + { + delete[] data; + delete message_module; + + std::ostringstream oss; + oss << "Message Module has already been loaded with Service ID "; + oss << message_module->get_service_id(); + throw value_error(oss.str()); + } + + if (m_protocol_type_map.count(message_module->get_protocol_type()) == 1) + { + delete[] data; + delete message_module; + + std::ostringstream oss; + oss << "Message Module has already been loaded with Protocol Type "; + oss << message_module->get_protocol_type(); + throw value_error(oss.str()); + } + + // Add it to our maps + m_modules.push_back(message_module); + m_service_id_map.insert({ message_module->get_service_id(), message_module }); + m_protocol_type_map.insert({ message_module->get_protocol_type(), message_module }); + + delete[] data; + return message_module; + } + + const MessageModule *MessageManager::get_module(uint8_t service_id) const + { + if (m_service_id_map.count(service_id) == 1) + return m_service_id_map.at(service_id); + return nullptr; + } + + const MessageModule *MessageManager::get_module(const std::string &protocol_type) const + { + if (m_protocol_type_map.count(protocol_type) == 1) + return m_protocol_type_map.at(protocol_type); + return nullptr; + } + + MessageBuilder &MessageManager::build_message(uint8_t service_id, uint8_t message_type) const + { + auto *message_module = get_module(service_id); + if (!message_module) + { + std::ostringstream oss; + oss << "No service exists with id: " << service_id; + throw value_error(oss.str()); + } + + return message_module->build_message(message_type); + } + + MessageBuilder& MessageManager::build_message(uint8_t service_id, const std::string& message_name) const + { + auto *message_module = get_module(service_id); + if (!message_module) + { + std::ostringstream oss; + oss << "No service exists with id: " << service_id; + throw value_error(oss.str()); + } + + return message_module->build_message(message_name); + } + + MessageBuilder& MessageManager::build_message(const std::string& protocol_type, uint8_t message_type) const + { + auto *message_module = get_module(protocol_type); + if (!message_module) + { + std::ostringstream oss; + oss << "No service exists with protocol type: " << protocol_type; + throw value_error(oss.str()); + } + + return message_module->build_message(message_type); + } + + MessageBuilder& MessageManager::build_message(const std::string& protocol_type, const std::string& message_name) const + { + auto *message_module = get_module(protocol_type); + if (!message_module) + { + std::ostringstream oss; + oss << "No service exists with protocol type: " << protocol_type; + throw value_error(oss.str()); + } + + return message_module->build_message(message_name); + } + + const Message *MessageManager::message_from_binary(std::istream& istream) const + { + // Read the message header and raw payload + Message *message = new Message(); + try + { + message->read_from(istream); + } + catch (parse_error &e) + { + delete message; + return nullptr; + } + + // Get the message module that uses the specified service id + auto *message_module = get_module(message->get_service_id()); + if (!message_module) + return message; + + // Get the message template for this message type + auto *message_template = message_module->get_message_template(message->get_type()); + if (!message_template) + return message; + + // Parse the raw payload with the template + message->use_template_record(message_template->get_record()); + return message; + } } } } \ No newline at end of file diff --git a/src/protocol/dml/MessageModule.cpp b/src/protocol/dml/MessageModule.cpp index 9b239fc..4d2c626 100644 --- a/src/protocol/dml/MessageModule.cpp +++ b/src/protocol/dml/MessageModule.cpp @@ -1,4 +1,6 @@ #include "ki/protocol/dml/MessageModule.h" +#include "ki/protocol/exception.h" +#include namespace ki { @@ -6,7 +8,133 @@ namespace protocol { namespace dml { + MessageModule::MessageModule(uint8_t service_id, std::string protocol_type) + { + m_service_id = service_id; + m_protocol_type = protocol_type; + m_protocol_description = ""; + m_last_message_type = 0; + m_templates = std::array { nullptr }; + } + MessageModule::~MessageModule() + { + for (auto it = m_templates.begin(); + it != m_templates.end(); ++it) + delete *it; + m_message_name_map.clear(); + } + + uint8_t MessageModule::get_service_id() const + { + return m_service_id; + } + + void MessageModule::set_service_id(uint8_t service_id) + { + m_service_id = service_id; + } + + std::string MessageModule::get_protocol_type() const + { + return m_protocol_type; + } + + void MessageModule::set_protocol_type(std::string protocol_type) + { + m_protocol_type = protocol_type; + } + + std::string MessageModule::get_protocol_desription() const + { + return m_protocol_description; + } + + void MessageModule::set_protocol_description(std::string protocol_description) + { + m_protocol_description = protocol_description; + } + + const MessageTemplate *MessageModule::add_message_template(std::string name, ki::dml::Record *record) + { + if (!record) + return nullptr; + + // If the field exists, get the name from the record rather than the XML + auto *name_field = record->get_field("_MsgName"); + if (name_field) + name = name_field->get_value(); + + // Do we already have a message template with this name? + if (m_message_name_map.count(name) == 1) + return nullptr; + + // Message type is based on the _MsgOrder field if it's present + // Otherwise it just goes in order of added templates + uint8_t message_type; + auto *order_field = record->get_field("_MsgOrder"); + if (order_field) + message_type = order_field->get_value(); + else + message_type = m_last_message_type + 1; + + // Don't allow message type to be zero + if (message_type == 0) + return nullptr; + + // Do we already have a message template with this type? + if (m_templates[message_type] != nullptr) + return nullptr; + + // Create the template and add it to our maps + auto *message_template = new MessageTemplate(name, message_type, record); + m_templates[message_type] = message_template; + m_message_name_map.insert({ name, message_template }); + m_last_message_type = message_type; + return message_template; + } + + const MessageTemplate *MessageModule::get_message_template(uint8_t type) const + { + return m_templates[type]; + } + + const MessageTemplate *MessageModule::get_message_template(std::string name) const + { + if (m_message_name_map.count(name) == 1) + return m_message_name_map.at(name); + return nullptr; + } + + MessageBuilder& MessageModule::build_message(uint8_t message_type) const + { + auto *message_template = get_message_template(message_type); + if (!message_template) + { + std::ostringstream oss; + oss << "No message exists with type: " << message_type; + oss << "(service=" << m_protocol_type << ")"; + throw value_error(oss.str()); + } + + return message_template->build_message() + .set_service_id(m_service_id); + } + + MessageBuilder &MessageModule::build_message(std::string message_name) const + { + auto *message_template = get_message_template(message_name); + if (!message_template) + { + std::ostringstream oss; + oss << "No message exists with name: " << message_name; + oss << "(service=" << m_protocol_type << ")"; + throw value_error(oss.str()); + } + + return message_template->build_message() + .set_service_id(m_service_id); + } } } } \ No newline at end of file diff --git a/src/protocol/dml/MessageTemplate.cpp b/src/protocol/dml/MessageTemplate.cpp index 405de3f..fdb3b26 100644 --- a/src/protocol/dml/MessageTemplate.cpp +++ b/src/protocol/dml/MessageTemplate.cpp @@ -6,7 +6,54 @@ namespace protocol { namespace dml { + MessageTemplate::MessageTemplate(std::string name, uint8_t type, ki::dml::Record* record) + { + m_name = name; + m_type = type; + m_record = record; + } + MessageTemplate::~MessageTemplate() + { + delete m_record; + } + + std::string MessageTemplate::get_name() const + { + return m_name; + } + + void MessageTemplate::set_name(std::string name) + { + m_name = name; + } + + uint8_t MessageTemplate::get_type() const + { + return m_type; + } + + void MessageTemplate::set_type(uint8_t type) + { + m_type = type; + } + + const ki::dml::Record& MessageTemplate::get_record() const + { + return *m_record; + } + + void MessageTemplate::set_record(ki::dml::Record* record) + { + m_record = record; + } + + MessageBuilder &MessageTemplate::build_message() const + { + return MessageBuilder() + .set_message_type(m_type) + .use_template_record(*m_record); + } } } } \ No newline at end of file