protocol: Implement module loading and I/O for DML messages

'das alota cod
This commit is contained in:
Joshua Scott 2018-04-05 17:36:26 +01:00
parent c618e7d536
commit 3e34c0b3bc
9 changed files with 591 additions and 24 deletions

View File

@ -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<char> m_raw_data;
ki::dml::Record *m_record;
};
}

View File

@ -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;

View File

@ -3,6 +3,7 @@
#include <cstdint>
#include <string>
#include <vector>
#include <array>
#include <map>
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<MessageTemplate *> m_templates;
std::array<MessageTemplate *, 255> m_templates;
std::map<std::string, MessageTemplate *> m_message_name_map;
std::map<uint8_t, MessageTemplate *> m_message_type_map;
};
typedef std::vector<MessageModule *> MessageModuleList;

View File

@ -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;

View File

@ -0,0 +1,26 @@
#pragma once
#include <stdexcept>
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) { }
};
}
}

View File

@ -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<ki::dml::UBYT>("m_service_id")->set_value(m_service_id);
record.add_field<ki::dml::UBYT>("m_type")->set_value(m_type);
auto *size_field = record.add_field<ki::dml::USHRT>("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<ki::dml::UBYT>("ServiceID");
auto *message_type_field = record.add_field<ki::dml::UBYT>("MsgType");
auto *size_field = record.add_field<ki::dml::USHRT>("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();
}
}
}
}

View File

@ -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 <fstream>
#include <sstream>
#include <rapidxml.hpp>
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<ki::dml::UBYT>("ServiceID");
auto *type_field = record->get_field<ki::dml::STR>("ProtocolType");
auto *description_field = record->get_field<ki::dml::STR>("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;
}
}
}
}

View File

@ -1,4 +1,6 @@
#include "ki/protocol/dml/MessageModule.h"
#include "ki/protocol/exception.h"
#include <sstream>
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<MessageTemplate *, 255> { 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<ki::dml::STR>("_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<ki::dml::UBYT>("_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);
}
}
}
}

View File

@ -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);
}
}
}
}