From b6745137b5466df7a74e7b950a840b3973bc2438 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Fri, 31 Oct 2025 13:17:51 +0100 Subject: [PATCH 1/3] Added fatal excetion handling and graceful shutdown of the agent. --- src/mtconnect/agent.cpp | 591 +++++++++--------- src/mtconnect/configuration/agent_config.cpp | 12 +- src/mtconnect/configuration/agent_config.hpp | 123 ++-- src/mtconnect/configuration/async_context.hpp | 170 +++-- src/mtconnect/configuration/service.cpp | 13 +- src/mtconnect/configuration/service.hpp | 2 +- src/mtconnect/device_model/agent_device.cpp | 1 + .../device_model/data_item/data_item.cpp | 2 +- src/mtconnect/device_model/device.cpp | 12 +- src/mtconnect/entity/factory.cpp | 2 +- src/mtconnect/observation/observation.cpp | 20 +- src/mtconnect/parser/xml_parser.cpp | 8 +- src/mtconnect/ruby/embedded.cpp | 6 +- src/mtconnect/sink/rest_sink/file_cache.cpp | 4 +- src/mtconnect/sink/rest_sink/rest_service.cpp | 4 +- src/mtconnect/sink/rest_sink/server.cpp | 136 ++-- .../adapter/agent_adapter/agent_adapter.cpp | 5 +- .../adapter/agent_adapter/session_impl.hpp | 213 ++++--- .../source/adapter/mqtt/mqtt_adapter.cpp | 6 +- src/mtconnect/utilities.hpp | 317 +++++----- test_package/agent_test_helper.cpp | 4 +- test_package/file_cache_test.cpp | 4 +- 22 files changed, 875 insertions(+), 780 deletions(-) diff --git a/src/mtconnect/agent.cpp b/src/mtconnect/agent.cpp index 8efbb565c..fee9145e2 100644 --- a/src/mtconnect/agent.cpp +++ b/src/mtconnect/agent.cpp @@ -73,26 +73,26 @@ namespace mtconnect { namespace net = boost::asio; namespace fs = boost::filesystem; namespace config = mtconnect::configuration; - + static const string g_unavailable("UNAVAILABLE"); static const string g_available("AVAILABLE"); - + // Agent public methods Agent::Agent(config::AsyncContext &context, const string &deviceXmlPath, const ConfigOptions &options) - : m_options(options), - m_context(context), - m_strand(m_context), - m_xmlParser(make_unique()), - m_schemaVersion(GetOption(options, config::SchemaVersion)), - m_deviceXmlPath(deviceXmlPath), - m_circularBuffer(GetOption(options, config::BufferSize).value_or(17), - GetOption(options, config::CheckpointFrequency).value_or(1000)), - m_pretty(IsOptionSet(options, mtconnect::configuration::Pretty)), - m_validation(IsOptionSet(options, mtconnect::configuration::Validation)) + : m_options(options), + m_context(context), + m_strand(m_context), + m_xmlParser(make_unique()), + m_schemaVersion(GetOption(options, config::SchemaVersion)), + m_deviceXmlPath(deviceXmlPath), + m_circularBuffer(GetOption(options, config::BufferSize).value_or(17), + GetOption(options, config::CheckpointFrequency).value_or(1000)), + m_pretty(IsOptionSet(options, mtconnect::configuration::Pretty)), + m_validation(IsOptionSet(options, mtconnect::configuration::Validation)) { using namespace asset; - + CuttingToolArchetype::registerAsset(); CuttingTool::registerAsset(); FileArchetypeAsset::registerAsset(); @@ -102,26 +102,26 @@ namespace mtconnect { ComponentConfigurationParameters::registerAsset(); Pallet::registerAsset(); Fixture::registerAsset(); - + m_assetStorage = make_unique( - GetOption(options, mtconnect::configuration::MaxAssets).value_or(1024)); + GetOption(options, mtconnect::configuration::MaxAssets).value_or(1024)); m_versionDeviceXml = IsOptionSet(options, mtconnect::configuration::VersionDeviceXml); m_createUniqueIds = IsOptionSet(options, config::CreateUniqueIds); - + auto jsonVersion = - uint32_t(GetOption(options, mtconnect::configuration::JsonVersion).value_or(2)); - + uint32_t(GetOption(options, mtconnect::configuration::JsonVersion).value_or(2)); + // Create the Printers m_printers["xml"] = make_unique(m_pretty, m_validation); m_printers["json"] = make_unique(jsonVersion, m_pretty, m_validation); - + if (m_schemaVersion) { m_intSchemaVersion = IntSchemaVersion(*m_schemaVersion); for (auto &[k, pr] : m_printers) pr->setSchemaVersion(*m_schemaVersion); } - + auto sender = GetOption(options, config::Sender); if (sender) { @@ -129,51 +129,51 @@ namespace mtconnect { pr->setSenderName(*sender); } } - + void Agent::initialize(pipeline::PipelineContextPtr context) { NAMED_SCOPE("Agent::initialize"); - + m_beforeInitializeHooks.exec(*this); - + m_pipelineContext = context; m_loopback = - std::make_shared("AgentSource", m_strand, context, m_options); - + std::make_shared("AgentSource", m_strand, context, m_options); + auto devices = loadXMLDeviceFile(m_deviceXmlPath); if (!m_schemaVersion) { m_schemaVersion.emplace(StrDefaultSchemaVersion()); } - + m_intSchemaVersion = IntSchemaVersion(*m_schemaVersion); for (auto &[k, pr] : m_printers) pr->setSchemaVersion(*m_schemaVersion); - + auto disableAgentDevice = GetOption(m_options, config::DisableAgentDevice); if (!(disableAgentDevice && *disableAgentDevice) && m_intSchemaVersion >= SCHEMA_VERSION(1, 7)) { createAgentDevice(); } - + // For the DeviceAdded event for each device for (auto device : devices) addDevice(device); - + if (m_versionDeviceXml && m_createUniqueIds) versionDeviceXml(); - + loadCachedProbe(); - + m_initialized = true; - + m_afterInitializeHooks.exec(*this); } - + void Agent::initialDataItemObservations() { NAMED_SCOPE("Agent::initialDataItemObservations"); - + if (!m_observationsInitialized) { if (m_intSchemaVersion < SCHEMA_VERSION(2, 5) && @@ -183,17 +183,17 @@ namespace mtconnect { for (auto &printer : m_printers) printer.second->setValidation(false); } - + for (auto device : m_deviceIndex) initializeDataItems(device); - + if (m_agentDevice) { for (auto device : m_deviceIndex) { auto d = m_agentDevice->getDeviceDataItem("device_added"); string uuid = *device->getUuid(); - + entity::Properties props {{"VALUE", uuid}}; if (m_intSchemaVersion >= SCHEMA_VERSION(2, 2)) { @@ -201,15 +201,15 @@ namespace mtconnect { if (ValueType(hash.index()) != ValueType::EMPTY) props.insert_or_assign("hash", hash); } - + m_loopback->receive(d, props); } } - + m_observationsInitialized = true; } } - + Agent::~Agent() { m_xmlParser.reset(); @@ -217,64 +217,64 @@ namespace mtconnect { m_sources.clear(); m_agentDevice = nullptr; } - + void Agent::start() { NAMED_SCOPE("Agent::start"); - + if (m_started) { LOG(warning) << "Agent already started."; return; } - + try { m_beforeStartHooks.exec(*this); - + for (auto sink : m_sinks) sink->start(); - + initialDataItemObservations(); - + if (m_agentDevice) { auto d = m_agentDevice->getDeviceDataItem("agent_avail"); m_loopback->receive(d, "AVAILABLE"s); } - + // Start all the sources for (auto source : m_sources) source->start(); - + m_afterStartHooks.exec(*this); } catch (std::runtime_error &e) { LOG(fatal) << "Cannot start server: " << e.what(); - std::exit(1); + throw FatalException(e.what()); } - + m_started = true; } - + void Agent::stop() { NAMED_SCOPE("Agent::stop"); - + if (!m_started) { LOG(warning) << "Agent already stopped."; return; } - + m_beforeStopHooks.exec(*this); - + // Stop all adapter threads... LOG(info) << "Shutting down sources"; for (auto source : m_sources) source->stop(); - + // Signal all observers LOG(info) << "Signaling observers to close sessions"; for (auto di : m_dataItemMap) @@ -283,16 +283,16 @@ namespace mtconnect { if (ldi) ldi->signalObservers(0); } - + LOG(info) << "Shutting down sinks"; for (auto sink : m_sinks) sink->stop(); - + LOG(info) << "Shutting down completed"; - + m_started = false; } - + // --------------------------------------- // Pipeline methods // --------------------------------------- @@ -307,7 +307,7 @@ namespace mtconnect { { if (item.expired()) continue; - + auto di = item.lock(); if (di->hasInitialValue()) { @@ -315,7 +315,7 @@ namespace mtconnect { } } } - + std::lock_guard lock(m_circularBuffer); if (m_circularBuffer.addToBuffer(observation) != 0) { @@ -323,7 +323,7 @@ namespace mtconnect { sink->publish(observation); } } - + void Agent::receiveAsset(asset::AssetPtr asset) { DevicePtr device; @@ -332,14 +332,14 @@ namespace mtconnect { device = findDeviceByUUIDorName(*uuid); else device = getDefaultDevice(); - + if (device && device->getAssetChanged() && device->getAssetRemoved()) { if (asset->getDeviceUuid() && *asset->getDeviceUuid() != *device->getUuid()) { asset->setProperty("deviceUuid", *device->getUuid()); } - + string aid = asset->getAssetId(); if (aid[0] == '@') { @@ -353,16 +353,16 @@ namespace mtconnect { asset->setAssetId(aid); } } - + // Add hash to asset if (m_intSchemaVersion >= SCHEMA_VERSION(2, 2)) asset->addHash(); - + auto old = m_assetStorage->addAsset(asset); - + for (auto &sink : m_sinks) sink->publish(asset); - + if (device) { DataItemPtr di; @@ -388,7 +388,7 @@ namespace mtconnect { if (di) { entity::Properties props {{"assetType", asset->getName()}, {"VALUE", asset->getAssetId()}}; - + if (m_intSchemaVersion >= SCHEMA_VERSION(2, 2)) { const auto &hash = asset->getProperty("hash"); @@ -397,22 +397,22 @@ namespace mtconnect { props.insert_or_assign("hash", hash); } } - + m_loopback->receive(di, props); } - + updateAssetCounts(device, asset->getType()); } } - + bool Agent::reloadDevices(const std::string &deviceFile) { try { // Load the configuration for the Agent auto devices = m_xmlParser->parseFile( - deviceFile, dynamic_cast(m_printers["xml"].get())); - + deviceFile, dynamic_cast(m_printers["xml"].get())); + if (m_xmlParser->getSchemaVersion() && IntSchemaVersion(*m_xmlParser->getSchemaVersion()) != m_intSchemaVersion) { @@ -420,7 +420,7 @@ namespace mtconnect { LOG(warning) << "Schema version does not match agent schema version, restarting the agent"; return false; } - + bool changed = false; for (auto device : devices) { @@ -428,7 +428,7 @@ namespace mtconnect { } if (changed) loadCachedProbe(); - + return true; } catch (runtime_error &e) @@ -436,17 +436,17 @@ namespace mtconnect { LOG(fatal) << "Error loading xml configuration: " + deviceFile; LOG(fatal) << "Error detail: " << e.what(); cerr << e.what() << endl; - throw e; + throw FatalException(e.what()); } catch (exception &f) { LOG(fatal) << "Error loading xml configuration: " + deviceFile; LOG(fatal) << "Error detail: " << f.what(); cerr << f.what() << endl; - throw f; + throw FatalException(f.what()); } } - + void Agent::loadDeviceXml(const string &deviceXml, const optional source) { try @@ -468,7 +468,7 @@ namespace mtconnect { cerr << f.what() << endl; } } - + void Agent::loadDevices(list devices, const optional source, bool force) { if (!force && !IsOptionSet(m_options, config::EnableSourceDeviceModels)) @@ -476,7 +476,7 @@ namespace mtconnect { LOG(warning) << "Device updates are disabled, skipping update"; return; } - + auto callback = [=, this](config::AsyncContext &context) { try { @@ -490,10 +490,10 @@ namespace mtconnect { { oldUuid = *oldDev->getUuid(); } - + auto uuid = *device->getUuid(); auto name = *device->getComponentName(); - + changed = receiveDevice(device, true) || changed; if (changed) { @@ -505,7 +505,7 @@ namespace mtconnect { s->setOptions({{config::Device, uuid}}); } } - + for (auto src : m_sources) { auto adapter = std::dynamic_pointer_cast(src); @@ -521,10 +521,14 @@ namespace mtconnect { } } } - + if (changed) loadCachedProbe(); } + catch (FatalException &e) + { + throw e; + } catch (runtime_error &e) { for (auto device : devices) @@ -544,7 +548,7 @@ namespace mtconnect { cerr << f.what() << endl; } }; - + // Gets around a race condition in the loading of adapaters and setting of // UUID. if (m_context.isRunning() && !m_context.isPauased()) @@ -552,11 +556,11 @@ namespace mtconnect { else callback(m_context); } - + bool Agent::receiveDevice(device_model::DevicePtr device, bool version) { NAMED_SCOPE("Agent::receiveDevice"); - + // diff the device against the current device with the same uuid auto uuid = device->getUuid(); if (!uuid) @@ -564,7 +568,7 @@ namespace mtconnect { LOG(error) << "Device does not have a uuid: " << device->getName(); return false; } - + DevicePtr oldDev = findDeviceByUUIDorName(*uuid); if (!oldDev) { @@ -574,10 +578,10 @@ namespace mtconnect { LOG(error) << "Device does not have a name" << *uuid; return false; } - + oldDev = findDeviceByUUIDorName(*name); } - + // If this is a new device if (!oldDev) { @@ -585,10 +589,10 @@ namespace mtconnect { addDevice(device); if (version) versionDeviceXml(); - + for (auto &sink : m_sinks) sink->publish(device); - + return true; } else @@ -599,15 +603,15 @@ namespace mtconnect { LOG(error) << "Device does not have a name: " << *device->getUuid(); return false; } - + verifyDevice(device); createUniqueIds(device); - + LOG(info) << "Checking if device " << *uuid << " has changed"; if (device->different(*oldDev)) { LOG(info) << "Device " << *uuid << " changed, updating model"; - + // Remove the old data items set skip; for (auto &di : oldDev->getDeviceDataItems()) @@ -618,7 +622,7 @@ namespace mtconnect { skip.insert(di.lock()->getId()); } } - + // Replace device in device maps auto it = find(m_deviceIndex.begin(), m_deviceIndex.end(), oldDev); if (it != m_deviceIndex.end()) @@ -628,18 +632,18 @@ namespace mtconnect { LOG(error) << "Cannot find Device " << *uuid << " in devices"; return false; } - + initializeDataItems(device, skip); - + LOG(info) << "Device " << *uuid << " updating circular buffer"; m_circularBuffer.updateDataItems(m_dataItemMap); - + if (m_intSchemaVersion > SCHEMA_VERSION(2, 2)) device->addHash(); - + if (version) versionDeviceXml(); - + if (m_agentDevice) { entity::Properties props {{"VALUE", *uuid}}; @@ -649,14 +653,14 @@ namespace mtconnect { if (ValueType(hash.index()) != ValueType::EMPTY) props.insert_or_assign("hash", hash); } - + auto d = m_agentDevice->getDeviceDataItem("device_changed"); m_loopback->receive(d, props); } - + for (auto &sink : m_sinks) sink->publish(device); - + return true; } else @@ -664,29 +668,29 @@ namespace mtconnect { LOG(info) << "Device " << *uuid << " did not change, ignoring new device"; } } - + return false; } - + void Agent::versionDeviceXml() { NAMED_SCOPE("Agent::versionDeviceXml"); - + using namespace std::chrono; - + if (m_versionDeviceXml) { m_beforeDeviceXmlUpdateHooks.exec(*this); - + // update with a new version of the device.xml, saving the old one // with a date time stamp auto ext = - date::format(".%Y-%m-%dT%H+%M+%SZ", date::floor(system_clock::now())); + date::format(".%Y-%m-%dT%H+%M+%SZ", date::floor(system_clock::now())); fs::path file(m_deviceXmlPath); fs::path backup(m_deviceXmlPath + ext); if (!fs::exists(backup)) fs::rename(file, backup); - + auto printer = getPrinter("xml"); if (printer != nullptr) { @@ -694,11 +698,11 @@ namespace mtconnect { copy_if(m_deviceIndex.begin(), m_deviceIndex.end(), back_inserter(list), [](DevicePtr d) { return dynamic_cast(d.get()) == nullptr; }); auto probe = printer->printProbe(0, 0, 0, 0, 0, list, nullptr, true, true); - + ofstream devices(file.string()); devices << probe; devices.close(); - + m_afterDeviceXmlUpdateHooks.exec(*this); } else @@ -707,7 +711,7 @@ namespace mtconnect { } } } - + bool Agent::removeAsset(DevicePtr device, const std::string &id, const std::optional time) { @@ -716,10 +720,10 @@ namespace mtconnect { { for (auto &sink : m_sinks) sink->publish(asset); - + notifyAssetRemoved(device, asset); updateAssetCounts(device, asset->getType()); - + return true; } else @@ -727,7 +731,7 @@ namespace mtconnect { return false; } } - + bool Agent::removeAllAssets(const std::optional device, const std::optional type, const std::optional time, asset::AssetList &list) @@ -742,13 +746,13 @@ namespace mtconnect { else uuid = device; } - + auto count = m_assetStorage->removeAll(list, uuid, type, time); for (auto &asset : list) { notifyAssetRemoved(nullptr, asset); } - + if (dev) { updateAssetCounts(dev, type); @@ -758,10 +762,10 @@ namespace mtconnect { for (auto d : m_deviceIndex) updateAssetCounts(d, type); } - + return count > 0; } - + void Agent::notifyAssetRemoved(DevicePtr device, const asset::AssetPtr &asset) { if (device || asset->getDeviceUuid()) @@ -787,7 +791,7 @@ namespace mtconnect { {{"assetType", asset->getName()}, {"VALUE", g_unavailable}}); } } - + auto added = dev->getAssetAdded(); if (added) { @@ -800,57 +804,57 @@ namespace mtconnect { } } } - + // --------------------------------------- // Agent Device // --------------------------------------- - + void Agent::createAgentDevice() { NAMED_SCOPE("Agent::createAgentDevice"); - + using namespace boost; - + auto address = GetBestHostAddress(m_context); - + auto port = GetOption(m_options, mtconnect::configuration::Port).value_or(5000); auto service = boost::lexical_cast(port); address.append(":").append(service); - + uuids::name_generator_latest gen(uuids::ns::dns()); auto uuid = GetOption(m_options, mtconnect::configuration::AgentDeviceUUID) - .value_or(uuids::to_string(gen(address))); + .value_or(uuids::to_string(gen(address))); auto id = "agent_"s + uuid.substr(0, uuid.find_first_of('-')); - + // Create the Agent Device ErrorList errors; Properties ps { - {"uuid", uuid}, {"id", id}, {"name", "Agent"s}, {"mtconnectVersion", *m_schemaVersion}}; + {"uuid", uuid}, {"id", id}, {"name", "Agent"s}, {"mtconnectVersion", *m_schemaVersion}}; m_agentDevice = - dynamic_pointer_cast(AgentDevice::getFactory()->make("Agent", ps, errors)); + dynamic_pointer_cast(AgentDevice::getFactory()->make("Agent", ps, errors)); if (!errors.empty()) { for (auto &e : errors) LOG(fatal) << "Error creating the agent device: " << e->what(); - throw EntityError("Cannot create AgentDevice"); + throw FatalException("Cannot create AgentDevice"); } addDevice(m_agentDevice); } - + // ---------------------------------------------- // Device management and Initialization // ---------------------------------------------- - + std::list Agent::loadXMLDeviceFile(const std::string &configXmlPath) { NAMED_SCOPE("Agent::loadXMLDeviceFile"); - + try { // Load the configuration for the Agent auto devices = m_xmlParser->parseFile( - configXmlPath, dynamic_cast(m_printers["xml"].get())); - + configXmlPath, dynamic_cast(m_printers["xml"].get())); + if (!m_schemaVersion && m_xmlParser->getSchemaVersion() && !m_xmlParser->getSchemaVersion()->empty()) { @@ -862,7 +866,7 @@ namespace mtconnect { m_schemaVersion = StrDefaultSchemaVersion(); m_intSchemaVersion = IntSchemaVersion(*m_schemaVersion); } - + return devices; } catch (runtime_error &e) @@ -870,23 +874,23 @@ namespace mtconnect { LOG(fatal) << "Error loading xml configuration: " + configXmlPath; LOG(fatal) << "Error detail: " << e.what(); cerr << e.what() << endl; - throw e; + throw FatalException(e.what()); } catch (exception &f) { LOG(fatal) << "Error loading xml configuration: " + configXmlPath; LOG(fatal) << "Error detail: " << f.what(); cerr << f.what() << endl; - throw f; + throw FatalException(f.what()); } - + return {}; } - + void Agent::verifyDevice(DevicePtr device) { NAMED_SCOPE("Agent::verifyDevice"); - + // Add the devices to the device map and create availability and // asset changed events if they don't exist // Make sure we have two device level data items: @@ -897,88 +901,92 @@ namespace mtconnect { // Create availability data item and add it to the device. entity::ErrorList errors; auto di = DataItem::make( - {{"type", "AVAILABILITY"s}, {"id", device->getId() + "_avail"}, {"category", "EVENT"s}}, - errors); + {{"type", "AVAILABILITY"s}, {"id", device->getId() + "_avail"}, {"category", "EVENT"s}}, + errors); device->addDataItem(di, errors); } - + if (!device->getAssetChanged() && m_intSchemaVersion >= SCHEMA_VERSION(1, 2)) { entity::ErrorList errors; // Create asset change data item and add it to the device. auto di = DataItem::make({{"type", "ASSET_CHANGED"s}, - {"id", device->getId() + "_asset_chg"}, - {"category", "EVENT"s}}, + {"id", device->getId() + "_asset_chg"}, + {"category", "EVENT"s}}, errors); device->addDataItem(di, errors); } - + if (device->getAssetChanged() && m_intSchemaVersion >= SCHEMA_VERSION(1, 5)) { auto di = device->getAssetChanged(); if (!di->isDiscrete()) di->makeDiscrete(); } - + if (!device->getAssetRemoved() && m_intSchemaVersion >= SCHEMA_VERSION(1, 3)) { // Create asset removed data item and add it to the device. entity::ErrorList errors; auto di = DataItem::make({{"type", "ASSET_REMOVED"s}, - {"id", device->getId() + "_asset_rem"}, - {"category", "EVENT"s}}, + {"id", device->getId() + "_asset_rem"}, + {"category", "EVENT"s}}, errors); device->addDataItem(di, errors); } - + if (!device->getAssetAdded() && m_intSchemaVersion >= SCHEMA_VERSION(2, 6)) { // Create asset removed data item and add it to the device. entity::ErrorList errors; auto di = DataItem::make({{"type", "ASSET_ADDED"s}, - {"id", device->getId() + "_asset_add"}, - {"discrete", true}, - {"category", "EVENT"s}}, + {"id", device->getId() + "_asset_add"}, + {"discrete", true}, + {"category", "EVENT"s}}, errors); device->addDataItem(di, errors); } - + if (!device->getAssetCount() && m_intSchemaVersion >= SCHEMA_VERSION(2, 0)) { entity::ErrorList errors; auto di = DataItem::make({{"type", "ASSET_COUNT"s}, - {"id", device->getId() + "_asset_count"}, - {"category", "EVENT"s}, - {"representation", "DATA_SET"s}}, + {"id", device->getId() + "_asset_count"}, + {"category", "EVENT"s}, + {"representation", "DATA_SET"s}}, errors); device->addDataItem(di, errors); } - + if (IsOptionSet(m_options, mtconnect::configuration::PreserveUUID)) { device->setPreserveUuid(*GetOption(m_options, mtconnect::configuration::PreserveUUID)); } } - + void Agent::initializeDataItems(DevicePtr device, std::optional> skip) { NAMED_SCOPE("Agent::initializeDataItems"); - + // Initialize the id mapping for the devices and set all data items to UNAVAILABLE for (auto item : device->getDeviceDataItems()) { if (item.expired()) continue; - + auto d = item.lock(); if ((!skip || skip->count(d->getId()) > 0) && m_dataItemMap.count(d->getId()) > 0) { auto di = m_dataItemMap[d->getId()].lock(); if (di && di != d) { - LOG(fatal) << "Duplicate DataItem id " << d->getId() - << " for device: " << *device->getComponentName(); - std::exit(1); + stringstream msg; + + msg << "Duplicate DataItem id " << d->getId() + << " for device: " << *device->getComponentName() << + ". Try using configuration option CreateUniqueIds to resolve."; + LOG(fatal) << msg.str(); + throw FatalException(msg.str()); } } else @@ -989,18 +997,18 @@ namespace mtconnect { value = &g_unavailable; else if (d->getConstantValue()) value = &d->getConstantValue().value(); - + m_loopback->receive(d, *value); m_dataItemMap[d->getId()] = d; } } } - + // Add the a device from a configuration file void Agent::addDevice(DevicePtr device) { NAMED_SCOPE("Agent::addDevice"); - + // Check if device already exists string uuid = *device->getUuid(); auto &idx = m_deviceIndex.get(); @@ -1008,23 +1016,24 @@ namespace mtconnect { if (old != idx.end()) { // Update existing device - LOG(fatal) << "Device " << *device->getUuid() << " already exists. " - << " Update not supported yet"; - std::exit(1); + stringstream msg; + msg << "Device " << *device->getUuid() << " already exists. " + << " Update not supported yet"; + throw msg; } else { m_deviceIndex.push_back(device); - + // TODO: Redo Resolve Reference with entity // device->resolveReferences(); verifyDevice(device); createUniqueIds(device); - + if (m_observationsInitialized) { initializeDataItems(device); - + // Check for single valued constrained data items. if (m_agentDevice && device != m_agentDevice) { @@ -1035,24 +1044,24 @@ namespace mtconnect { if (ValueType(hash.index()) != ValueType::EMPTY) props.insert_or_assign("hash", hash); } - + auto d = m_agentDevice->getDeviceDataItem("device_added"); m_loopback->receive(d, props); } } } - + if (m_intSchemaVersion >= SCHEMA_VERSION(2, 2)) device->addHash(); - + for (auto &printer : m_printers) printer.second->setModelChangeTime(getCurrentTime(GMT_UV_SEC)); } - + void Agent::deviceChanged(DevicePtr device, const std::string &uuid) { NAMED_SCOPE("Agent::deviceChanged"); - + bool changed = false; string oldUuid = *device->getUuid(); if (uuid != oldUuid) @@ -1065,28 +1074,28 @@ namespace mtconnect { m_loopback->receive(d, oldUuid); } } - + if (changed) { // Create a new device auto xmlPrinter = dynamic_cast(m_printers["xml"].get()); auto newDevice = m_xmlParser->parseDevice(xmlPrinter->printDevice(device), xmlPrinter); - + newDevice->setUuid(uuid); - + m_loopback->receive(newDevice); } } - + void Agent::createUniqueIds(DevicePtr device) { if (m_createUniqueIds && !dynamic_pointer_cast(device)) { std::unordered_map idMap; - + device->createUniqueIds(idMap); device->updateReferences(idMap); - + // Update the data item map. for (auto &id : idMap) { @@ -1099,83 +1108,83 @@ namespace mtconnect { } } } - + void Agent::loadCachedProbe() { NAMED_SCOPE("Agent::loadCachedProbe"); - + // Reload the document for path resolution auto xmlPrinter = dynamic_cast(m_printers["xml"].get()); m_xmlParser->loadDocument(xmlPrinter->printProbe(0, 0, 0, 0, 0, getDevices())); - + for (auto &printer : m_printers) printer.second->setModelChangeTime(getCurrentTime(GMT_UV_SEC)); } - + // ---------------------------------------------------- // Helper Methods // ---------------------------------------------------- - + DevicePtr Agent::getDeviceByName(const std::string &name) const { if (name.empty()) return getDefaultDevice(); - + auto &idx = m_deviceIndex.get(); auto devPos = idx.find(name); if (devPos != idx.end()) return *devPos; - + return nullptr; } - + DevicePtr Agent::getDeviceByName(const std::string &name) { if (name.empty()) return getDefaultDevice(); - + auto &idx = m_deviceIndex.get(); auto devPos = idx.find(name); if (devPos != idx.end()) return *devPos; - + return nullptr; } - + DevicePtr Agent::findDeviceByUUIDorName(const std::string &idOrName) const { if (idOrName.empty()) return getDefaultDevice(); - + DevicePtr res; if (auto d = m_deviceIndex.get().find(idOrName); d != m_deviceIndex.get().end()) res = *d; else if (auto d = m_deviceIndex.get().find(idOrName); d != m_deviceIndex.get().end()) res = *d; - + return res; } - + // ---------------------------------------------------- // Adapter Methods // ---------------------------------------------------- - + void Agent::addSource(source::SourcePtr source, bool start) { m_sources.emplace_back(source); - + if (start) source->start(); - + auto adapter = dynamic_pointer_cast(source); if (m_agentDevice && adapter) { m_agentDevice->addAdapter(adapter); - + if (m_observationsInitialized) initializeDataItems(m_agentDevice); - + // Reload the document for path resolution if (m_initialized) { @@ -1183,15 +1192,15 @@ namespace mtconnect { } } } - + void Agent::addSink(sink::SinkPtr sink, bool start) { m_sinks.emplace_back(sink); - + if (start) sink->start(); } - + void AgentPipelineContract::deliverConnectStatus(entity::EntityPtr entity, const StringList &devices, bool autoAvailable) { @@ -1213,15 +1222,15 @@ namespace mtconnect { LOG(error) << "Unexpected connection status received: " << value; } } - + void AgentPipelineContract::deliverCommand(entity::EntityPtr entity) { auto command = entity->get("command"); auto value = entity->getValue(); - + auto device = entity->maybeGet("device"); auto source = entity->maybeGet("source"); - + if ((command != "devicemodel" && !device) || !source) { LOG(error) << "Invalid command: " << command << ", device or source not specified"; @@ -1234,7 +1243,7 @@ namespace mtconnect { m_agent->receiveCommand(*device, command, value, *source); } } - + void Agent::connecting(const std::string &adapter) { if (m_agentDevice) @@ -1244,30 +1253,30 @@ namespace mtconnect { m_loopback->receive(di, "LISTEN"); } } - + // Add values for related data items UNAVAILABLE void Agent::disconnected(const std::string &adapter, const StringList &devices, bool autoAvailable) { LOG(debug) << "Disconnected from adapter, setting all values to UNAVAILABLE"; - + if (m_agentDevice) { auto di = m_agentDevice->getConnectionStatus(adapter); if (di) m_loopback->receive(di, "CLOSED"); } - + for (auto &name : devices) { DevicePtr device = findDeviceByUUIDorName(name); if (device == nullptr) { LOG(warning) << "Cannot find device " << name << " when adapter " << adapter - << "disconnected"; + << "disconnected"; continue; } - + for (auto di : device->getDeviceDataItems()) { auto dataItem = di.lock(); @@ -1276,7 +1285,7 @@ namespace mtconnect { dataItem->getType() == "AVAILABILITY"))) { auto ptr = getLatest(dataItem->getId()); - + if (ptr) { const string *value = nullptr; @@ -1284,7 +1293,7 @@ namespace mtconnect { value = &dataItem->getConstantValue().value(); else if (!ptr->isUnavailable()) value = &g_unavailable; - + if (value) m_loopback->receive(dataItem, *value); } @@ -1294,7 +1303,7 @@ namespace mtconnect { } } } - + void Agent::connected(const std::string &adapter, const StringList &devices, bool autoAvailable) { if (m_agentDevice) @@ -1303,10 +1312,10 @@ namespace mtconnect { if (di) m_loopback->receive(di, "ESTABLISHED"); } - + if (!autoAvailable) return; - + for (auto &name : devices) { DevicePtr device = findDeviceByUUIDorName(name); @@ -1316,7 +1325,7 @@ namespace mtconnect { continue; } LOG(debug) << "Connected to adapter, setting all Availability data items to AVAILABLE"; - + if (auto avail = device->getAvailability()) { LOG(debug) << "Adding availabilty event for " << device->getAvailability()->getId(); @@ -1326,7 +1335,7 @@ namespace mtconnect { LOG(debug) << "Cannot find availability for " << *device->getComponentName(); } } - + void Agent::sourceFailed(const std::string &identity) { auto source = findSource(identity); @@ -1334,7 +1343,7 @@ namespace mtconnect { { source->stop(); m_sources.remove(source); - + bool ext = false; for (auto &s : m_sources) { @@ -1344,7 +1353,7 @@ namespace mtconnect { break; } } - + if (!ext) { LOG(fatal) << "Source " << source->getName() << " failed"; @@ -1362,16 +1371,16 @@ namespace mtconnect { LOG(error) << "Cannot find failed source: " << source->getName(); } } - + // ----------------------------------------------- // Validation methods // ----------------------------------------------- - + string Agent::devicesAndPath(const std::optional &path, const DevicePtr device, const std::optional &deviceType) const { string dataPath; - + if (device || deviceType) { string prefix; @@ -1381,16 +1390,16 @@ namespace mtconnect { prefix = "//Devices/Device[@uuid=\"" + *device->getUuid() + "\"]"; else if (deviceType) prefix = "//Devices/Device"; - + if (path) { stringstream parse(*path); string token; - + // Prefix path (i.e. "path1|path2" => "{prefix}path1|{prefix}path2") while (getline(parse, token, '|')) dataPath += prefix + token + "|"; - + dataPath.erase(dataPath.length() - 1); } else @@ -1405,10 +1414,10 @@ namespace mtconnect { else dataPath = "//Devices/Device|//Devices/Agent"; } - + return dataPath; } - + void AgentPipelineContract::deliverAssetCommand(entity::EntityPtr command) { const std::string &cmd = command->getValue(); @@ -1433,33 +1442,33 @@ namespace mtconnect { LOG(error) << "Invalid assent command: " << cmd; } } - + void Agent::updateAssetCounts(const DevicePtr &device, const std::optional type) { if (!device) return; - + auto dc = device->getAssetCount(); if (dc) { if (type) { auto count = m_assetStorage->getCountForDeviceAndType(*device->getUuid(), *type); - + DataSet set; if (count > 0) set.emplace(*type, int64_t(count)); else set.emplace(*type, DataSetValue(), true); - + m_loopback->receive(dc, {{"VALUE", set}}); } else { auto counts = m_assetStorage->getCountsByTypeForDevice(*device->getUuid()); - + DataSet set; - + for (auto &[t, count] : counts) { if (count > 0) @@ -1467,67 +1476,67 @@ namespace mtconnect { else set.emplace(t, DataSetValue(), true); } - + m_loopback->receive(dc, {{"resetTriggered", "RESET_COUNTS"s}, {"VALUE", set}}); } } } - + void Agent::receiveCommand(const std::string &deviceName, const std::string &command, const std::string &value, const std::string &source) { DevicePtr device {nullptr}; device = findDeviceByUUIDorName(deviceName); - + if (!device) { LOG(warning) << source << ": Cannot find device for name " << deviceName; } - + static std::unordered_map> - deviceCommands { - {"manufacturer", mem_fn(&Device::setManufacturer)}, - {"station", mem_fn(&Device::setStation)}, - {"serialnumber", mem_fn(&Device::setSerialNumber)}, - {"description", mem_fn(&Device::setDescriptionValue)}, - {"nativename", - [](DevicePtr device, const string &name) { device->setProperty("nativeName", name); }}, - {"calibration", [](DevicePtr device, const string &value) { - istringstream line(value); - - // Look for name|factor|offset triples - string name, factor, offset; - while (getline(line, name, '|') && getline(line, factor, '|') && - getline(line, offset, '|')) - { - // Convert to a floating point number - auto di = device->getDeviceDataItem(name); - if (!di) - LOG(warning) << "Cannot find data item to calibrate for " << name; - else - { - try - { - double fact_value = stod(factor); - double off_value = stod(offset); - - device_model::data_item::UnitConversion conv(fact_value, off_value); - di->setConverter(conv); - } - catch (std::exception e) - { - LOG(error) << "Cannot convert factor " << factor << " or " << offset - << " to double: " << e.what(); - } - } - } - }}}; - + deviceCommands { + {"manufacturer", mem_fn(&Device::setManufacturer)}, + {"station", mem_fn(&Device::setStation)}, + {"serialnumber", mem_fn(&Device::setSerialNumber)}, + {"description", mem_fn(&Device::setDescriptionValue)}, + {"nativename", + [](DevicePtr device, const string &name) { device->setProperty("nativeName", name); }}, + {"calibration", [](DevicePtr device, const string &value) { + istringstream line(value); + + // Look for name|factor|offset triples + string name, factor, offset; + while (getline(line, name, '|') && getline(line, factor, '|') && + getline(line, offset, '|')) + { + // Convert to a floating point number + auto di = device->getDeviceDataItem(name); + if (!di) + LOG(warning) << "Cannot find data item to calibrate for " << name; + else + { + try + { + double fact_value = stod(factor); + double off_value = stod(offset); + + device_model::data_item::UnitConversion conv(fact_value, off_value); + di->setConverter(conv); + } + catch (std::exception e) + { + LOG(error) << "Cannot convert factor " << factor << " or " << offset + << " to double: " << e.what(); + } + } + } + }}}; + static std::unordered_map adapterDataItems { - {"adapterversion", "_adapter_software_version"}, - {"mtconnectversion", "_mtconnect_version"}, + {"adapterversion", "_adapter_software_version"}, + {"mtconnectversion", "_mtconnect_version"}, }; - + if (command == "devicemodel") { loadDeviceXml(value, source); @@ -1569,7 +1578,7 @@ namespace mtconnect { else { LOG(warning) << "Cannot find data item for the Agent device when processing command " - << command << " with value " << value << " for adapter " << source; + << command << " with value " << value << " for adapter " << source; } } } @@ -1583,10 +1592,10 @@ namespace mtconnect { else { LOG(error) << source << ":Received protocol command '" << command << "' for device '" - << deviceName << "', but the device could not be found"; + << deviceName << "', but the device could not be found"; } } - + // ------------------------------------------- // End // ------------------------------------------- diff --git a/src/mtconnect/configuration/agent_config.cpp b/src/mtconnect/configuration/agent_config.cpp index 48ee6f8b2..a42f93f9d 100644 --- a/src/mtconnect/configuration/agent_config.cpp +++ b/src/mtconnect/configuration/agent_config.cpp @@ -182,12 +182,12 @@ namespace mtconnect::configuration { buffer << file.rdbuf(); FileFormat fmt = MTCONNECT; - if (ends_with(m_configFile.string(), "json")) + if (m_configFile.string().ends_with("json")) { LOG(debug) << "Parsing json configuration"; fmt = JSON; } - else if (ends_with(m_configFile.string(), "xml")) + else if (m_configFile.string().ends_with("xml")) { LOG(debug) << "Parsing xml configuration"; fmt = XML; @@ -392,7 +392,7 @@ namespace mtconnect::configuration { m_monitorTimer.async_wait(boost::bind(&AgentConfiguration::monitorFiles, this, _1)); } - void AgentConfiguration::start() + int AgentConfiguration::start() { if (m_monitorFiles) { @@ -411,7 +411,7 @@ namespace mtconnect::configuration { m_context->setThreadCount(m_workerThreadCount); m_beforeStartHooks.exec(*this); m_agent->start(); - m_context->start(); + return m_context->start(); } void AgentConfiguration::stop() @@ -879,7 +879,7 @@ namespace mtconnect::configuration { LOG(fatal) << "Cannot find device configuration file"; logPaths(LOG_LEVEL(fatal), m_configPaths); - throw runtime_error(((string) "Please make sure the configuration " + throw FatalException(((string) "Please make sure the configuration " "file probe.xml or Devices.xml is in the current " "directory or specify the correct file " "in the configuration file " + @@ -958,7 +958,7 @@ namespace mtconnect::configuration { if (host.empty()) { LOG(fatal) << "Malformed URL in configuration file: '" << url << "', exiting"; - exit(1); + throw FatalException(); } options[configuration::Host] = host; diff --git a/src/mtconnect/configuration/agent_config.hpp b/src/mtconnect/configuration/agent_config.hpp index 72191af28..347123fa1 100644 --- a/src/mtconnect/configuration/agent_config.hpp +++ b/src/mtconnect/configuration/agent_config.hpp @@ -47,7 +47,7 @@ namespace mtconnect { namespace device_model { class Device; } - + #ifdef WITH_PYTHON namespace python { class Embedded; @@ -58,13 +58,13 @@ namespace mtconnect { class Embedded; } #endif - + class XmlPrinter; - + /// @brief Configuration namespace namespace configuration { using DevicePtr = std::shared_ptr; - + /// @brief Parses the configuration file and creates the `Agent`. Manages config /// file tracking and restarting of the agent. class AGENT_LIB_API AgentConfiguration : public MTConnectService @@ -77,19 +77,19 @@ namespace mtconnect { XML, UNKNOWN }; - + using InitializationFn = void(const boost::property_tree::ptree &, AgentConfiguration &); using InitializationFunction = boost::function; - + using ptree = boost::property_tree::ptree; - + /// @brief Construct the agent configuration AgentConfiguration(); virtual ~AgentConfiguration(); - + /// @name Callbacks for initialization phases ///@{ - + /// @brief Get the callback manager after the agent is created /// @return the callback manager auto &afterAgentHooks() { return m_afterAgentHooks; } @@ -100,32 +100,33 @@ namespace mtconnect { /// @return the callback manager auto &beforeStopHooks() { return m_beforeStopHooks; } ///@} - + /// @brief stops the agent. Used in daemons. void stop() override; /// @brief starts the agent. Used in daemons. - void start() override; + /// @return 0 on success + int start() override; /// @brief initializes the configuration of the agent from the command line parameters /// @param[in] options command line parameters void initialize(const boost::program_options::variables_map &options) override; - + /// @brief Configure the logger with the config node from the config file /// @param channelName the log channel name /// @param config the configuration node /// @param formatter optional custom message format void configureLoggerChannel( - const std::string &channelName, const ptree &config, - std::optional> formatter = std::nullopt); - + const std::string &channelName, const ptree &config, + std::optional> formatter = std::nullopt); + /// @brief Configure the agent logger with the config node from the config file /// @param config the configuration node void configureLogger(const ptree &config); - + /// @brief load a configuration text /// @param[in] text the configuration text loaded from a file /// @param[in] fmt the file format, can be MTCONNECT, JSON, or XML void loadConfig(const std::string &text, FileFormat fmt = MTCONNECT); - + /// @brief assign the agent associated with this configuration /// @param[in] agent the agent the configuration will take ownership of void setAgent(std::unique_ptr &agent) { m_agent = std::move(agent); } @@ -135,10 +136,10 @@ namespace mtconnect { auto &getContext() { return m_context->get(); } /// @brief get a pointer to the async io manager auto &getAsyncContext() { return *m_context.get(); } - + /// @brief sets the path for the working directory to the current path void updateWorkingDirectory() { m_working = std::filesystem::current_path(); } - + /// @name Configuration factories ///@{ /// @brief get the factory for creating sinks @@ -148,11 +149,11 @@ namespace mtconnect { /// @return the factory auto &getSourceFactory() { return m_sourceFactory; } ///@} - + /// @brief get the pipeline context for this configuration /// @note set after the agent is created auto getPipelineContext() { return m_pipelineContext; } - + /// @name Logging methods ///@{ /// @brief gets the boost log sink @@ -209,7 +210,7 @@ namespace mtconnect { { return m_logChannels[channelName].m_logLevel; } - + /// @brief set the logging level /// @param[in] level the new logging level void setLoggingLevel(const boost::log::trivial::severity_level level); @@ -217,62 +218,62 @@ namespace mtconnect { /// @param level the new logging level /// @return the logging level boost::log::trivial::severity_level setLoggingLevel(const std::string &level); - + std::optional findConfigFile(const std::string &file) { return findFile(m_configPaths, file); } - + std::optional findDataFile(const std::string &file) { return findFile(m_dataPaths, file); } - + /// @brief Create a sink contract with functions to find config and data files. /// @return shared pointer to a sink contract sink::SinkContractPtr makeSinkContract() { auto contract = m_agent->makeSinkContract(); contract->m_findConfigFile = - [this](const std::string &n) -> std::optional { + [this](const std::string &n) -> std::optional { return findConfigFile(n); }; contract->m_findDataFile = - [this](const std::string &n) -> std::optional { + [this](const std::string &n) -> std::optional { return findDataFile(n); }; return contract; } - + /// @brief add a path to the config paths /// @param path the path to add void addConfigPath(const std::filesystem::path &path) { addPathBack(m_configPaths, path); } - + /// @brief add a path to the data paths /// @param path the path to add void addDataPath(const std::filesystem::path &path) { addPathBack(m_dataPaths, path); } - + /// @brief add a path to the plugin paths /// @param path the path to add void addPluginPath(const std::filesystem::path &path) { addPathBack(m_pluginPaths, path); } - + protected: DevicePtr getDefaultDevice(); void loadAdapters(const ptree &tree, const ConfigOptions &options); void loadSinks(const ptree &sinks, ConfigOptions &options); - + #ifdef WITH_PYTHON void configurePython(const ptree &tree, ConfigOptions &options); #endif #ifdef WITH_RUBY void configureRuby(const ptree &tree, ConfigOptions &options); #endif - + void loadPlugins(const ptree &tree); bool loadPlugin(const std::string &name, const ptree &tree); void monitorFiles(boost::system::error_code ec); void scheduleMonitorTimer(); - + protected: std::optional findFile(const std::list &paths, const std::string file) @@ -284,33 +285,33 @@ namespace mtconnect { if (std::filesystem::exists(tst, ec) && !ec) { LOG(debug) << "Found file '" << file << "' " - << " in path " << path; + << " in path " << path; auto con {std::filesystem::canonical(tst)}; return con; } else { LOG(debug) << "Cannot find file '" << file << "' " - << " in path " << path; + << " in path " << path; } } - + return std::nullopt; } - + void addPathBack(std::list &paths, std::filesystem::path path) { std::error_code ec; auto con {std::filesystem::canonical(path, ec)}; if (std::find(paths.begin(), paths.end(), con) != paths.end()) return; - + if (!ec) paths.emplace_back(con); else LOG(debug) << "Cannot file path: " << path << ", " << ec.message(); } - + void addPathFront(std::list &paths, std::filesystem::path path) { std::error_code ec; @@ -321,7 +322,7 @@ namespace mtconnect { else LOG(debug) << "Cannot file path: " << path << ", " << ec.message(); } - + template void logPaths(T lvl, const std::list &paths) { @@ -329,52 +330,52 @@ namespace mtconnect { { BOOST_LOG_STREAM_WITH_PARAMS(::boost::log::trivial::logger::get(), (::boost::log::keywords::severity = lvl)) - << " " << p; + << " " << p; } } - + void expandConfigVariables(boost::property_tree::ptree &); - + protected: using text_sink = boost::log::sinks::synchronous_sink; using console_sink = - boost::log::sinks::synchronous_sink; - + boost::log::sinks::synchronous_sink; + struct LogChannel { std::string m_channelName; std::filesystem::path m_logDirectory; std::filesystem::path m_logArchivePattern; std::filesystem::path m_logFileName; - + int64_t m_maxLogFileSize {0}; int64_t m_logRotationSize {0}; int64_t m_rotationLogInterval {0}; - + boost::log::trivial::severity_level m_logLevel {boost::log::trivial::severity_level::info}; - + boost::shared_ptr m_logSink; }; - + std::map m_logChannels; - + std::map m_initializers; - + std::unique_ptr m_context; std::unique_ptr m_agent; - + pipeline::PipelineContextPtr m_pipelineContext; std::unique_ptr m_adapterHandler; - + std::string m_version; std::string m_devicesFile; std::filesystem::path m_exePath; std::filesystem::path m_working; - + std::list m_configPaths; std::list m_dataPaths; std::list m_pluginPaths; - + // File monitoring boost::asio::steady_timer m_monitorTimer; bool m_monitorFiles = false; @@ -383,23 +384,23 @@ namespace mtconnect { bool m_restart = false; std::optional m_configTime; std::optional m_deviceTime; - + // Factories sink::SinkFactory m_sinkFactory; source::SourceFactory m_sourceFactory; - + int m_workerThreadCount {1}; - + // Reference to the global logger boost::log::trivial::logger_type *m_logger {nullptr}; - + #ifdef WITH_RUBY std::unique_ptr m_ruby; #endif #ifdef WITH_PYTHON std::unique_ptr m_python; #endif - + HookManager m_afterAgentHooks; HookManager m_beforeStartHooks; HookManager m_beforeStopHooks; diff --git a/src/mtconnect/configuration/async_context.hpp b/src/mtconnect/configuration/async_context.hpp index aa92c3e5b..c13b037cf 100644 --- a/src/mtconnect/configuration/async_context.hpp +++ b/src/mtconnect/configuration/async_context.hpp @@ -20,11 +20,12 @@ #include #include +#include "mtconnect/utilities.hpp" #include "mtconnect/config.hpp" #include "mtconnect/logging.hpp" namespace mtconnect::configuration { - + /// @brief Manages the boost asio context and allows for a syncronous /// callback to execute when all the worker threads have stopped. class AGENT_LIB_API AsyncContext @@ -32,79 +33,131 @@ namespace mtconnect::configuration { public: using SyncCallback = std::function; using WorkGuard = boost::asio::executor_work_guard; - + /// @brief creates an asio context and a guard to prevent it from /// stopping AsyncContext() { m_guard.emplace(m_context.get_executor()); } /// @brief removes the copy constructor AsyncContext(const AsyncContext &) = delete; ~AsyncContext() {} - + /// @brief is the context running /// @returns running status auto isRunning() { return m_running; } - + /// @brief return the paused state /// @returns the paused state auto isPauased() { return m_paused; } - + /// @brief Testing only: method to remove the run guard from the context void removeGuard() { m_guard.reset(); } - + /// @brief get the boost asio context reference auto &get() { return m_context; } - + /// @brief operator() returns a reference to the io context /// @return the io context operator boost::asio::io_context &() { return m_context; } - + /// @brief sets the number of theads for asio thread pool /// @param[in] threads number of threads void setThreadCount(int threads) { m_threadCount = threads; } - + /// @brief start `m_threadCount` worker threads - void start() + /// @returns the exit code + int start() { - m_running = true; - m_paused = false; - do + m_exitCode = 0; + try { - for (int i = 0; i < m_threadCount; i++) + m_running = true; + m_paused = false; + do { - m_workers.emplace_back(boost::thread([this]() { m_context.run(); })); - } - auto &first = m_workers.front(); - while (m_running && !m_paused) - { - if (!first.try_join_for(boost::chrono::seconds(5)) && !m_running) + for (int i = 0; i < m_threadCount; i++) { - if (!first.try_join_for(boost::chrono::seconds(5))) - m_context.stop(); + m_workers.emplace_back(boost::thread([this]() + { + try + { + m_context.run(); + } + + catch (FatalException &e) + { + LOG(fatal) << "Fatal exception occurred: " << e.what(); + stop(); + m_exitCode = 1; + } + catch (std::exception &e) + { + LOG(fatal) << "Uncaught exception occurred: " << e.what(); + stop(); + m_exitCode = 1; + } + catch (...) + { + LOG(fatal) << "Unknown fatal exception occurred"; + stop(); + m_exitCode = 1; + } + + })); } - } - - for (auto &w : m_workers) - { - w.join(); - } - m_workers.clear(); - - if (m_delayedStop.joinable()) - m_delayedStop.join(); - - if (m_syncCallback) - { - m_syncCallback(*this); - if (m_running) + auto &first = m_workers.front(); + while (m_running && !m_paused) { - m_syncCallback = nullptr; - restart(); + if (!first.try_join_for(boost::chrono::seconds(5)) && !m_running) + { + if (!first.try_join_for(boost::chrono::seconds(5))) + m_context.stop(); + } } - } - - } while (m_running); + + for (auto &w : m_workers) + { + w.join(); + } + m_workers.clear(); + + if (m_delayedStop.joinable()) + m_delayedStop.join(); + + if (m_syncCallback && m_exitCode == 0) + { + m_syncCallback(*this); + if (m_running) + { + m_syncCallback = nullptr; + restart(); + } + } + + } while (m_running); + } + + catch (FatalException &e) + { + LOG(fatal) << "Fatal exception occurred: " << e.what(); + stop(); + m_exitCode = 1; + } + catch (std::exception &e) + { + LOG(fatal) << "Uncaught exception occurred: " << e.what(); + stop(); + m_exitCode = 1; + } + catch (...) + { + LOG(fatal) << "Unknown fatal exception occurred"; + stop(); + m_exitCode = 1; + } + + return m_exitCode; } - + /// @brief pause the worker threads. Sets a callback when the threads are paused. /// @param[in] callback the callback to call /// @param[in] safeStop stops by resetting the the guard, otherwise stop the @@ -118,7 +171,7 @@ namespace mtconnect::configuration { else m_context.stop(); } - + /// @brief stop the worker threads /// @param safeStop if `true` resets the guard or stops the context void stop(bool safeStop = true) @@ -129,7 +182,7 @@ namespace mtconnect::configuration { else m_context.stop(); } - + /// @brief restarts the worker threads when paused void restart() { @@ -138,58 +191,59 @@ namespace mtconnect::configuration { m_guard.emplace(m_context.get_executor()); m_context.restart(); } - + /// @name Cover methods for asio io_context /// @{ - + /// @brief io_context::run_for template auto run_for(const std::chrono::duration &rel_time) { return m_context.run_for(rel_time); } - + /// @brief io_context::run auto run() { return m_context.run(); } - + /// @brief io_context::run_one auto run_one() { return m_context.run_one(); } - + /// @brief io_context::run_one_for template auto run_one_for(const std::chrono::duration &rel_time) { return m_context.run_one_for(rel_time); } - + /// @brief io_context::run_one_until template auto run_one_until(const std::chrono::time_point &abs_time) { return m_context.run_one_for(abs_time); } - + /// @brief io_context::poll auto poll() { return m_context.poll(); } - + /// @brief io_context::poll auto get_executor() BOOST_ASIO_NOEXCEPT { return m_context.get_executor(); } - + /// @} - + private: void operator=(const AsyncContext &) {} - + protected: boost::asio::io_context m_context; std::list m_workers; SyncCallback m_syncCallback; std::optional m_guard; std::thread m_delayedStop; - + int m_threadCount = 1; bool m_running = false; bool m_paused = false; + int m_exitCode = 0; }; - + } // namespace mtconnect::configuration diff --git a/src/mtconnect/configuration/service.cpp b/src/mtconnect/configuration/service.cpp index a98185912..4c47c95ff 100644 --- a/src/mtconnect/configuration/service.cpp +++ b/src/mtconnect/configuration/service.cpp @@ -176,6 +176,8 @@ namespace mtconnect { using namespace std; + int res = 0; + try { // If command-line parameter is "install", install the service. If debug or run @@ -208,9 +210,9 @@ namespace mtconnect { m_isDebug = true; initialize(options); - start(); + res = start(); std::thread cmd(commandLine); - return 0; + return res; } else { @@ -236,14 +238,16 @@ namespace mtconnect { { LOG(fatal) << "Agent top level exception: " << e.what(); std::cerr << "Agent top level exception: " << e.what() << std::endl; + res = 1; } catch (std::string &s) { LOG(fatal) << "Agent top level exception: " << s; std::cerr << "Agent top level exception: " << s << std::endl; + res = 1; } - return 0; + return res; } bool MTConnectService::isElevated() @@ -841,8 +845,7 @@ namespace mtconnect { usage(1); } - start(); - return 0; + return start(); } void MTConnectService::install() {} diff --git a/src/mtconnect/configuration/service.hpp b/src/mtconnect/configuration/service.hpp index 7795965be..60e6f736d 100644 --- a/src/mtconnect/configuration/service.hpp +++ b/src/mtconnect/configuration/service.hpp @@ -44,7 +44,7 @@ namespace mtconnect { /// @brief stop the srvice virtual void stop() = 0; /// @brief start the service - virtual void start() = 0; + virtual int start() = 0; /// @brief set the name of the service /// @param[in] name name of the service diff --git a/src/mtconnect/device_model/agent_device.cpp b/src/mtconnect/device_model/agent_device.cpp index 287e4065a..d88257488 100644 --- a/src/mtconnect/device_model/agent_device.cpp +++ b/src/mtconnect/device_model/agent_device.cpp @@ -66,6 +66,7 @@ namespace mtconnect { { LOG(fatal) << "Cannot create AgentDevice: " << e->what(); } + throw FatalException(); } } diff --git a/src/mtconnect/device_model/data_item/data_item.cpp b/src/mtconnect/device_model/data_item/data_item.cpp index a6828900c..d6dfd230a 100644 --- a/src/mtconnect/device_model/data_item/data_item.cpp +++ b/src/mtconnect/device_model/data_item/data_item.cpp @@ -130,7 +130,7 @@ namespace mtconnect { auto &category = get("category"); auto units = maybeGet("units"); - if (units && ends_with(*units, "3D")) + if (units && units->ends_with("3D")) m_specialClass = THREE_SPACE_CLS; if (category == "SAMPLE") diff --git a/src/mtconnect/device_model/device.cpp b/src/mtconnect/device_model/device.cpp index fea72bc30..b5c2bbd1c 100644 --- a/src/mtconnect/device_model/device.cpp +++ b/src/mtconnect/device_model/device.cpp @@ -60,9 +60,11 @@ namespace mtconnect { { if (auto it = m_dataItems.get().find(di->getId()); it != m_dataItems.get().end()) { - LOG(fatal) << "Device " << getName() << ": Duplicatie data item id '" << di->getId() + stringstream msg; + msg << "Device " << getName() << ": Duplicatie data item id '" << di->getId() << "', Exiting"; - exit(1); + LOG(fatal) << msg.str(); + throw FatalException(msg.str()); } if (di->hasProperty("Source") && di->getSource()->hasValue()) @@ -93,9 +95,11 @@ namespace mtconnect { auto [id, added] = m_dataItems.emplace(di); if (!added) { - LOG(fatal) << "Device " << getName() << ": DataItem '" << di->getId() + stringstream msg; + msg << "Device " << getName() << ": DataItem '" << di->getId() << " could not be added, exiting"; - exit(1); + LOG(fatal) << msg.str(); + throw FatalException(msg.str()); } } diff --git a/src/mtconnect/entity/factory.cpp b/src/mtconnect/entity/factory.cpp index eb93aebd5..3e1591760 100644 --- a/src/mtconnect/entity/factory.cpp +++ b/src/mtconnect/entity/factory.cpp @@ -188,7 +188,7 @@ namespace mtconnect { // If the property is a namespace declaration, then // remove it if it is related to mtconnect, otherwise // allow it to pass through. - if (ba::starts_with(p.first.str(), "xmlns"s)) + if (p.first.str().starts_with("xmlns"sv)) { if (holds_alternative(p.second) && ba::contains(get(p.second), "mtconnect"s)) diff --git a/src/mtconnect/observation/observation.cpp b/src/mtconnect/observation/observation.cpp index 58f276b16..e50f9d46c 100644 --- a/src/mtconnect/observation/observation.cpp +++ b/src/mtconnect/observation/observation.cpp @@ -66,40 +66,40 @@ namespace mtconnect { // regex(".+TimeSeries$") factory->registerFactory( - [](const std::string &name) { return ends_with(name, "TimeSeries"); }, + [](const std::string &name) { return name.ends_with("TimeSeries"); }, Timeseries::getFactory()); - factory->registerFactory([](const std::string &name) { return ends_with(name, "DataSet"); }, + factory->registerFactory([](const std::string &name) { return name.ends_with("DataSet"); }, DataSetEvent::getFactory()); - factory->registerFactory([](const std::string &name) { return ends_with(name, "Table"); }, + factory->registerFactory([](const std::string &name) { return name.ends_with("Table"); }, TableEvent::getFactory()); factory->registerFactory( - [](const std::string &name) { return starts_with(name, "Condition:"); }, + [](const std::string &name) { return name.starts_with("Condition:"); }, Condition::getFactory()); factory->registerFactory( [](const std::string &name) { - return starts_with(name, "Samples:") && ends_with(name, ":3D"); + return name.starts_with("Samples:") && name.ends_with(":3D"); }, ThreeSpaceSample::getFactory()); factory->registerFactory( [](const std::string &name) { - return starts_with(name, "Events:") && ends_with(name, ":3D"); + return name.starts_with("Events:") && name.ends_with(":3D"); }, ThreeSpaceSample::getFactory()); factory->registerFactory( - [](const std::string &name) { return starts_with(name, "Samples:"); }, + [](const std::string &name) { return name.starts_with("Samples:"); }, Sample::getFactory()); factory->registerFactory( [](const std::string &name) { - return starts_with(name, "Events:") && ends_with(name, ":DOUBLE"); + return name.starts_with("Events:") && name.ends_with(":DOUBLE"); }, DoubleEvent::getFactory()); factory->registerFactory( [](const std::string &name) { - return starts_with(name, "Events:") && ends_with(name, ":INT"); + return name.starts_with("Events:") && name.ends_with(":INT"); }, IntEvent::getFactory()); factory->registerFactory( - [](const std::string &name) { return starts_with(name, "Events:"); }, + [](const std::string &name) { return name.starts_with("Events:"); }, Event::getFactory()); } return factory; diff --git a/src/mtconnect/parser/xml_parser.cpp b/src/mtconnect/parser/xml_parser.cpp index 9f5363e75..3a03709a4 100644 --- a/src/mtconnect/parser/xml_parser.cpp +++ b/src/mtconnect/parser/xml_parser.cpp @@ -227,7 +227,7 @@ namespace mtconnect::parser { xmlXPathFreeContext(xpathCtx); LOG(fatal) << "Cannot parse XML file: " << e; - throw; + throw FatalException(e); } catch (...) { @@ -237,7 +237,7 @@ namespace mtconnect::parser { if (xpathCtx) xmlXPathFreeContext(xpathCtx); - throw; + throw FatalException(); } return deviceList; @@ -272,7 +272,7 @@ namespace mtconnect::parser { catch (string e) { LOG(fatal) << "Cannot parse XML document: " << e; - throw; + throw FatalException(); } return device; @@ -312,7 +312,7 @@ namespace mtconnect::parser { catch (string e) { LOG(fatal) << "Cannot parse XML document: " << e; - throw; + throw FatalException(); } } diff --git a/src/mtconnect/ruby/embedded.cpp b/src/mtconnect/ruby/embedded.cpp index f461d086b..57365e03e 100644 --- a/src/mtconnect/ruby/embedded.cpp +++ b/src/mtconnect/ruby/embedded.cpp @@ -186,18 +186,18 @@ namespace mtconnect::ruby { if (mrb_false_p(res)) { LOG(fatal) << "Error loading file " << *modulePath << ": exiting agent"; - exit(1); + throw FatalException("Fatal error loading module"); } } catch (std::exception ex) { LOG(fatal) << "Failed to load module: " << *modulePath << ": " << ex.what(); - exit(1); + throw FatalException("Fatal error loading module"); } catch (...) { LOG(fatal) << "Failed to load module: " << *modulePath; - exit(1); + throw FatalException("Fatal error loading module"); } if (fp != nullptr) { diff --git a/src/mtconnect/sink/rest_sink/file_cache.cpp b/src/mtconnect/sink/rest_sink/file_cache.cpp index 4c1ae8f5c..ceec74ffc 100644 --- a/src/mtconnect/sink/rest_sink/file_cache.cpp +++ b/src/mtconnect/sink/rest_sink/file_cache.cpp @@ -263,7 +263,7 @@ namespace mtconnect::sink::rest_sink { for (const auto &dir : m_directories) { - if (boost::starts_with(name, dir.first)) + if (name.starts_with(dir.first)) { auto fileName = boost::erase_first_copy(name, dir.first); if (fileName.empty()) @@ -382,7 +382,7 @@ namespace mtconnect::sink::rest_sink { if (fs::exists(path)) { string root(uri); - if (boost::ends_with(root, "/")) + if (root.ends_with("/")) { boost::erase_last(root, "/"); } diff --git a/src/mtconnect/sink/rest_sink/rest_service.cpp b/src/mtconnect/sink/rest_sink/rest_service.cpp index 46775087c..41108936f 100644 --- a/src/mtconnect/sink/rest_sink/rest_service.cpp +++ b/src/mtconnect/sink/rest_sink/rest_service.cpp @@ -473,7 +473,7 @@ namespace mtconnect { auto format = request->parameter("format"); auto printer = getPrinter(request->m_accepts, format); - if (device && !ends_with(request->m_path, string("probe")) && + if (device && !request->m_path.ends_with("probe"sv) && m_sinkContract->findDeviceByUUIDorName(*device) == nullptr) return false; @@ -1473,7 +1473,7 @@ namespace mtconnect { { for (const auto &p : m_sinkContract->getPrinters()) { - if (ends_with(accept, p.first)) + if (accept.ends_with(p.first)) return p.first; } } diff --git a/src/mtconnect/sink/rest_sink/server.cpp b/src/mtconnect/sink/rest_sink/server.cpp index 550715817..b6b23707d 100644 --- a/src/mtconnect/sink/rest_sink/server.cpp +++ b/src/mtconnect/sink/rest_sink/server.cpp @@ -43,12 +43,12 @@ namespace mtconnect::sink::rest_sink { using tcp = boost::asio::ip::tcp; namespace algo = boost::algorithm; namespace ssl = boost::asio::ssl; - + using namespace std; using namespace rapidjson; using std::placeholders::_1; using std::placeholders::_2; - + void Server::loadTlsCertificate() { if (HasOption(m_options, configuration::TlsCertificateChain) && @@ -59,27 +59,27 @@ namespace mtconnect::sink::rest_sink { if (HasOption(m_options, configuration::TlsCertificatePassword)) { m_sslContext.set_password_callback( - [this](size_t, boost::asio::ssl::context_base::password_purpose) -> string { - return *GetOption(m_options, configuration::TlsCertificatePassword); - }); + [this](size_t, boost::asio::ssl::context_base::password_purpose) -> string { + return *GetOption(m_options, configuration::TlsCertificatePassword); + }); } - + m_sslContext.set_options(ssl::context::default_workarounds | asio::ssl::context::no_sslv2 | asio::ssl::context::single_dh_use); m_sslContext.use_certificate_chain_file( - *GetOption(m_options, configuration::TlsCertificateChain)); + *GetOption(m_options, configuration::TlsCertificateChain)); m_sslContext.use_private_key_file(*GetOption(m_options, configuration::TlsPrivateKey), asio::ssl::context::file_format::pem); m_sslContext.use_tmp_dh_file(*GetOption(m_options, configuration::TlsDHKey)); - + m_tlsEnabled = true; - + m_tlsOnly = IsOptionSet(m_options, configuration::TlsOnly); - + if (IsOptionSet(m_options, configuration::TlsVerifyClientCertificate)) { LOG(info) << "Will only accept client connections with valid certificates"; - + m_sslContext.set_verify_mode(ssl::verify_peer | ssl::verify_fail_if_no_peer_cert); if (HasOption(m_options, configuration::TlsClientCAs)) { @@ -89,7 +89,7 @@ namespace mtconnect::sink::rest_sink { } } } - + void Server::start() { try @@ -100,17 +100,17 @@ namespace mtconnect::sink::rest_sink { catch (exception &e) { LOG(fatal) << "Cannot start server: " << e.what(); - std::exit(1); + throw FatalException(e.what()); } } - + // Listen for an HTTP server connection void Server::listen() { NAMED_SCOPE("Server::listen"); - + beast::error_code ec; - + // Blocking call to listen for a connection tcp::endpoint ep(m_address, m_port); m_acceptor.open(ep.protocol(), ec); @@ -135,23 +135,23 @@ namespace mtconnect::sink::rest_sink { { m_port = m_acceptor.local_endpoint().port(); } - + m_acceptor.listen(net::socket_base::max_listen_connections, ec); if (ec) { fail(ec, "Cannot set listen queue length"); return; } - + m_listening = true; m_acceptor.async_accept(net::make_strand(m_context), beast::bind_front_handler(&Server::accept, this)); } - + bool Server::allowPutFrom(const std::string &host) { NAMED_SCOPE("Server::allowPutFrom"); - + // Resolve the host to an ip address to verify remote addr beast::error_code ec; ip::tcp::resolver resolve(m_context); @@ -162,25 +162,25 @@ namespace mtconnect::sink::rest_sink { LOG(error) << ec.category().message(ec.value()) << ": " << ec.message(); return false; } - + // Add the results to the set of allowed hosts for (auto &addr : results) { m_allowPutsFrom.insert(addr.endpoint().address()); } m_allowPuts = true; - + return true; } - + void Server::accept(beast::error_code ec, tcp::socket socket) { NAMED_SCOPE("Server::accept"); - + if (ec) { LOG(error) << ec.category().message(ec.value()) << ": " << ec.message(); - + fail(ec, "Accept failed"); } else if (m_run) @@ -188,7 +188,7 @@ namespace mtconnect::sink::rest_sink { auto dispatcher = [this](SessionPtr session, RequestPtr request) { if (!m_run) return false; - + if (m_lastSession) m_lastSession(session); dispatch(session, request); @@ -197,9 +197,9 @@ namespace mtconnect::sink::rest_sink { if (m_tlsEnabled) { auto dectector = - make_shared(std::move(socket), m_sslContext, m_tlsOnly, m_allowPuts, - m_allowPutsFrom, m_fields, dispatcher, m_errorFunction); - + make_shared(std::move(socket), m_sslContext, m_tlsOnly, m_allowPuts, + m_allowPutsFrom, m_fields, dispatcher, m_errorFunction); + dectector->run(); } else @@ -208,34 +208,34 @@ namespace mtconnect::sink::rest_sink { boost::beast::tcp_stream stream(std::move(socket)); auto session = make_shared(std::move(stream), std::move(buffer), m_fields, dispatcher, m_errorFunction); - + if (!m_allowPutsFrom.empty()) session->allowPutsFrom(m_allowPutsFrom); else if (m_allowPuts) session->allowPuts(); - + session->run(); } m_acceptor.async_accept(net::make_strand(m_context), beast::bind_front_handler(&Server::accept, this)); } } - + //------------------------------------------------------------------------------ - + // Report a failure void Server::fail(beast::error_code ec, char const *what) { LOG(error) << " error: " << ec.message(); } - + using namespace mtconnect::printer; - + template void AddParameter(T &writer, const Parameter ¶m) { AutoJsonObject obj(writer); - + obj.AddPairs("name", param.m_name, "in", param.m_part == PATH ? "path" : "query", "required", param.m_part == PATH); { @@ -245,23 +245,23 @@ namespace mtconnect::sink::rest_sink { case ParameterType::STRING: obj.AddPairs("type", "string", "format", "string"); break; - + case ParameterType::INTEGER: obj.AddPairs("type", "integer", "format", "int64"); break; - + case ParameterType::UNSIGNED_INTEGER: obj.AddPairs("type", "integer", "format", "uint64"); break; - + case ParameterType::DOUBLE: obj.AddPairs("type", "double", "format", "double"); break; - + case ParameterType::BOOL: obj.AddPairs("type", "boolean", "format", "bool"); break; - + case ParameterType::NONE: obj.AddPairs("type", "unknown", "format", "unknown"); break; @@ -270,30 +270,30 @@ namespace mtconnect::sink::rest_sink { { obj.Key("default"); visit( - overloaded {[](const std::monostate &) {}, [&obj](const std::string &s) { obj.Add(s); }, - [&obj](int32_t i) { obj.Add(i); }, [&obj](uint64_t i) { obj.Add(i); }, - [&obj](double d) { obj.Add(d); }, [&obj](bool b) { obj.Add(b); }}, - param.m_default); + overloaded {[](const std::monostate &) {}, [&obj](const std::string &s) { obj.Add(s); }, + [&obj](int32_t i) { obj.Add(i); }, [&obj](uint64_t i) { obj.Add(i); }, + [&obj](double d) { obj.Add(d); }, [&obj](bool b) { obj.Add(b); }}, + param.m_default); } } if (param.m_description) obj.AddPairs("description", *param.m_description); } - + template void AddRouting(T &writer, const Routing &routing) { string verb {to_string(routing.getVerb())}; boost::to_lower(verb); - + { AutoJsonObject obj(writer, verb.data()); - + if (routing.getSummary()) obj.AddPairs("summary", *routing.getSummary()); if (routing.getDescription()) obj.AddPairs("description", *routing.getDescription()); - + if (!routing.getPathParameters().empty() || !routing.getQueryParameters().empty()) { AutoJsonArray ary(writer, "parameters"); @@ -306,7 +306,7 @@ namespace mtconnect::sink::rest_sink { AddParameter(writer, param); } } - + { AutoJsonObject obj(writer, "responses"); { @@ -326,36 +326,36 @@ namespace mtconnect::sink::rest_sink { } } } - + // Swagger stuff template const void Server::renderSwaggerResponse(T &writer) { { AutoJsonObject obj(writer); - + obj.AddPairs("openapi", "3.0.0"); - + { AutoJsonObject obj(writer, "info"); obj.AddPairs("title", "MTConnect – REST API", "description", "MTConnect REST API "); - + { AutoJsonObject obj(writer, "contact"); obj.AddPairs("email", "will@metalogi.io"); } { AutoJsonObject obj(writer, "license"); - + obj.AddPairs("name", "Apache 2.0", "url", "http://www.apache.org/licenses/LICENSE-2.0.html"); } - + obj.AddPairs("version", GetAgentVersion()); } { AutoJsonObject obj(writer, "externalDocs"); - + obj.AddPairs("description", "For information related to MTConnect", "url", "http://mtconnect.org"); } @@ -363,27 +363,27 @@ namespace mtconnect::sink::rest_sink { AutoJsonArray ary(writer, "servers"); { AutoJsonObject obj(writer); - + stringstream str; if (m_tlsEnabled) str << "https://"; else str << "http://"; - + str << GetBestHostAddress(m_context, true) << ':' << m_port << '/'; obj.AddPairs("url", str.str()); } } { AutoJsonObject obj(writer, "paths"); - + multimap routings; for (const auto &routing : m_routings) { if (!routing.isSwagger() && routing.getPath()) routings.emplace(make_pair(*routing.getPath(), &routing)); } - + AutoJsonObject robj(writer, false); for (const auto &[path, routing] : routings) { @@ -394,23 +394,23 @@ namespace mtconnect::sink::rest_sink { } } } - + void Server::addSwaggerRoutings() { auto handler = [&](SessionPtr session, const RequestPtr request) -> bool { auto pretty = *request->parameter("pretty"); - + StringBuffer output; RenderJson(output, pretty, [this](auto &writer) { renderSwaggerResponse(writer); }); - + session->writeResponse( - make_unique(status::ok, string(output.GetString()), "application/json")); - + make_unique(status::ok, string(output.GetString()), "application/json")); + return true; }; - + addRouting({boost::beast::http::verb::get, "/swagger?pretty={bool:false}", handler, true}); // addRouting({boost::beast::http::verb::get, "/swagger.yaml", handler, true}); } - + } // namespace mtconnect::sink::rest_sink diff --git a/src/mtconnect/source/adapter/agent_adapter/agent_adapter.cpp b/src/mtconnect/source/adapter/agent_adapter/agent_adapter.cpp index b99bff088..4beda3b69 100644 --- a/src/mtconnect/source/adapter/agent_adapter/agent_adapter.cpp +++ b/src/mtconnect/source/adapter/agent_adapter/agent_adapter.cpp @@ -125,7 +125,10 @@ namespace mtconnect::source::adapter::agent_adapter { } else if (device || HasOption(m_options, configuration::SourceDevice)) { - m_sourceDevice = GetOption(m_options, configuration::SourceDevice).value_or(*device); + + m_sourceDevice = GetOption(m_options, configuration::SourceDevice); + if (!m_sourceDevice) + m_sourceDevice = device; } else { diff --git a/src/mtconnect/source/adapter/agent_adapter/session_impl.hpp b/src/mtconnect/source/adapter/agent_adapter/session_impl.hpp index 1557d46d3..02765498a 100644 --- a/src/mtconnect/source/adapter/agent_adapter/session_impl.hpp +++ b/src/mtconnect/source/adapter/agent_adapter/session_impl.hpp @@ -25,6 +25,7 @@ #include #include +#include "mtconnect/utilities.hpp" #include "mtconnect/config.hpp" #include "mtconnect/pipeline/mtconnect_xml_transform.hpp" #include "mtconnect/pipeline/response_document.hpp" @@ -37,7 +38,7 @@ namespace mtconnect::source::adapter::agent_adapter { namespace http = boost::beast::http; namespace ssl = boost::asio::ssl; using tcp = boost::asio::ip::tcp; - + /// @brief A session implementation that where the derived classes can support HTTP or HTTPS /// @tparam Derived template @@ -45,7 +46,7 @@ namespace mtconnect::source::adapter::agent_adapter { { /// @brief A list of HTTP requests using RequestQueue = std::list; - + public: /// @brief Cast this class as the derived class /// @return reference to the derived class @@ -53,22 +54,22 @@ namespace mtconnect::source::adapter::agent_adapter { /// @brief Immutably cast this class as its derived subclass /// @return const reference to the derived class const Derived &derived() const { return static_cast(*this); } - + // Objects are constructed with a strand to // ensure that handlers do not execute concurrently. SessionImpl(boost::asio::io_context::strand &strand, const url::Url &url) - : m_resolver(strand.context()), m_strand(strand), m_url(url), m_chunk(1 * 1024 * 1024) + : m_resolver(strand.context()), m_strand(strand), m_url(url), m_chunk(1 * 1024 * 1024) {} - + virtual ~SessionImpl() { stop(); } - + /// @brief see if the socket is connected /// @return `true` if the socket is open bool isOpen() const override { return derived().lowestLayer().socket().is_open(); } - + /// @brief close the connection void close() override { derived().lowestLayer().socket().close(); } - + /// @brief Method called when a request fails /// /// Closes the socket and resets the request @@ -77,7 +78,7 @@ namespace mtconnect::source::adapter::agent_adapter { void failed(std::error_code ec, const char *what) override { derived().lowestLayer().socket().close(); - + LOG(error) << "Agent Adapter Connection Failed: " << m_url.getUrlText(nullopt); if (m_request) LOG(error) << "Agent Adapter Target: " << m_request->getTarget(m_url); @@ -86,15 +87,15 @@ namespace mtconnect::source::adapter::agent_adapter { if (m_failed) m_failed(ec); } - + void stop() override { m_request.reset(); } - + bool makeRequest(const Request &req) override { if (!m_request) { m_request.emplace(req); - + // Clean out any previous data m_buffer.clear(); m_contentType.clear(); @@ -106,7 +107,7 @@ namespace mtconnect::source::adapter::agent_adapter { m_hasHeader = false; if (m_chunk.size() > 0) m_chunk.consume(m_chunk.size()); - + // Check if we are discussected. if (!derived().lowestLayer().socket().is_open()) { @@ -126,7 +127,7 @@ namespace mtconnect::source::adapter::agent_adapter { return false; } } - + /// @brief Process data from the remote agent /// @param data the payload from the agent void processData(const std::string &data) @@ -136,6 +137,10 @@ namespace mtconnect::source::adapter::agent_adapter { if (m_handler && m_handler->m_processData) m_handler->m_processData(data, m_identity); } + catch (FatalException &e) + { + throw e; + } catch (std::system_error &e) { LOG(warning) << "AgentAdapter - Error occurred processing data: " << e.what(); @@ -157,7 +162,7 @@ namespace mtconnect::source::adapter::agent_adapter { "Unknown exception in AgentAdapter::processData"); } } - + /// @brief Connect to the remote agent virtual void connect() { @@ -170,26 +175,26 @@ namespace mtconnect::source::adapter::agent_adapter { else if (holds_alternative(m_url.m_host)) { asio::ip::tcp::endpoint ep(get(m_url.m_host), m_url.getPort()); - + // Create the results type and call on resolve directly. using results_type = tcp::resolver::results_type; auto results = results_type::create(ep, m_url.getHost(), m_url.getService()); - + beast::error_code ec; onResolve(ec, results); } else { derived().lowestLayer().expires_after(m_timeout); - + // Do an async resolution of the address. m_resolver.async_resolve( - get(m_url.m_host), m_url.getService(), - asio::bind_executor( - m_strand, beast::bind_front_handler(&SessionImpl::onResolve, derived().getptr()))); + get(m_url.m_host), m_url.getService(), + asio::bind_executor( + m_strand, beast::bind_front_handler(&SessionImpl::onResolve, derived().getptr()))); } } - + /// @brief Callback when the host name needs to be resolved /// @param ec error code if resultion fails /// @param results the resolution results @@ -202,34 +207,34 @@ namespace mtconnect::source::adapter::agent_adapter { LOG(error) << " Reason: " << ec.category().name() << " " << ec.message(); return failed(source::make_error_code(source::ErrorCode::ADAPTER_FAILED), "resolve"); } - + if (!m_request) { LOG(error) << "Resolved but no request"; return; } - + if (!m_resolution) m_resolution.emplace(results); - + if (m_handler && m_handler->m_connecting) m_handler->m_connecting(m_identity); - + // Set a timeout on the operation derived().lowestLayer().expires_after(m_timeout); - + // Make the connection on the IP address we get from a lookup derived().lowestLayer().async_connect( - results, asio::bind_executor(m_strand, beast::bind_front_handler(&Derived::onConnect, - derived().getptr()))); + results, asio::bind_executor(m_strand, beast::bind_front_handler(&Derived::onConnect, + derived().getptr()))); } - + /// @brief Write a request to the remote agent void request() { if (!m_request) return; - + // Set up an HTTP GET request message m_req.emplace(); m_req->version(11); @@ -238,50 +243,50 @@ namespace mtconnect::source::adapter::agent_adapter { m_req->set(http::field::host, m_url.getHost()); m_req->set(http::field::user_agent, "MTConnect Agent/2.0"); m_req->set(http::field::connection, "keep-alive"); - + if (m_closeConnectionAfterResponse) { m_req->set(http::field::connection, "close"); } - + derived().lowestLayer().expires_after(m_timeout); - + LOG(debug) << "Agent adapter making request: " << m_url.getUrlText(nullopt) << " target " - << m_request->getTarget(m_url); - + << m_request->getTarget(m_url); + http::async_write(derived().stream(), *m_req, beast::bind_front_handler(&SessionImpl::onWrite, derived().getptr())); } - + /// @brief Callback on successful write to the agent /// @param ec error code if something failed /// @param bytes_transferred number of bytes transferred (unused) void onWrite(beast::error_code ec, std::size_t bytes_transferred) { boost::ignore_unused(bytes_transferred); - + if (ec) { LOG(error) << "Cannot send request: " << ec.category().name() << " " << ec.message(); return failed(source::make_error_code(ErrorCode::RETRY_REQUEST), "write"); } - + if (!m_request) { LOG(error) << "Wrote but no request"; return failed(source::make_error_code(ErrorCode::RETRY_REQUEST), "write"); } - + derived().lowestLayer().expires_after(m_timeout); - + // Receive the HTTP response m_headerParser.emplace(); http::async_read_header( - derived().stream(), m_buffer, *m_headerParser, - asio::bind_executor(m_strand, - beast::bind_front_handler(&Derived::onHeader, derived().getptr()))); + derived().stream(), m_buffer, *m_headerParser, + asio::bind_executor(m_strand, + beast::bind_front_handler(&Derived::onHeader, derived().getptr()))); } - + /// @brief Callback after write to process the message header /// @param ec error code if something failed /// @param bytes_transferred number of bytes transferred @@ -290,7 +295,7 @@ namespace mtconnect::source::adapter::agent_adapter { if (ec) { LOG(error) << "Agent Adapter Error getting request header: " << ec.category().name() << " " - << ec.message(); + << ec.message(); derived().lowestLayer().close(); if (m_request->m_stream && ec == beast::error::timeout) { @@ -301,16 +306,16 @@ namespace mtconnect::source::adapter::agent_adapter { { return failed(source::make_error_code(ErrorCode::RETRY_REQUEST), "header"); } - + return; } - + if (!m_request) { LOG(error) << "Received a header but no request"; return failed(source::make_error_code(ErrorCode::RETRY_REQUEST), "header"); } - + auto &msg = m_headerParser->get(); if (msg.version() < 11) { @@ -321,7 +326,7 @@ namespace mtconnect::source::adapter::agent_adapter { { m_closeOnRead = a->value() == "close"; } - + if (m_request->m_stream && m_headerParser->chunked()) { onChunkedContent(); @@ -329,49 +334,49 @@ namespace mtconnect::source::adapter::agent_adapter { else { derived().lowestLayer().expires_after(m_timeout); - + m_textParser.emplace(std::move(*m_headerParser)); http::async_read(derived().stream(), m_buffer, *m_textParser, asio::bind_executor(m_strand, beast::bind_front_handler( - &Derived::onRead, derived().getptr()))); + &Derived::onRead, derived().getptr()))); } } - + /// @brief Callback after header processing to read the body of the response /// @param ec error code if something failed /// @param bytes_transferred number of bytes transferred (unused) void onRead(beast::error_code ec, std::size_t bytes_transferred) { boost::ignore_unused(bytes_transferred); - + if (ec) { LOG(error) << "Error getting response: " << ec.category().name() << " " << ec.message(); return failed(source::make_error_code(ErrorCode::RETRY_REQUEST), "read"); } - + if (!m_request) { LOG(error) << "read data but no request"; return failed(source::make_error_code(ErrorCode::RETRY_REQUEST), "header"); } - + derived().lowestLayer().expires_after(m_timeout); - + if (!derived().lowestLayer().socket().is_open()) derived().disconnect(); - + processData(m_textParser->get().body()); - + m_textParser.reset(); m_req.reset(); - + auto next = m_request->m_next; m_request.reset(); - + if (m_closeOnRead) close(); - + if (next) { next(); @@ -383,10 +388,10 @@ namespace mtconnect::source::adapter::agent_adapter { makeRequest(req); } } - + /// @name Streaming related methods ///@{ - + /// @brief Find the x-multipart-replace MIME boundary /// @return the boundary string inline string findBoundary() @@ -410,10 +415,10 @@ namespace mtconnect::source::adapter::agent_adapter { } } } - + return ""; } - + /// @brief Create a function to handle the chunk header /// /// Sets the chunk parser on chunk header @@ -428,32 +433,32 @@ namespace mtconnect::source::adapter::agent_adapter { cout << "Ext: " << c.first << ": " << c.second << endl; #endif derived().lowestLayer().expires_after(m_timeout); - + if (ec) { return failed(ec, "Failed in chunked extension parse"); } }; - + m_chunkParser->on_chunk_header(m_chunkHeaderHandler); } - + /// @brief Parse the header and get the size and type /// @return `true` if successful bool parseMimeHeader() { using namespace boost; namespace algo = boost::algorithm; - + if (m_chunk.data().size() < 128) { LOG(trace) << "Not enough data for mime header: " << m_chunk.data().size(); return false; } - + auto start = static_cast(m_chunk.data().data()); boost::string_view view(start, m_chunk.data().size()); - + auto bp = view.find(m_boundary.c_str()); if (bp == boost::string_view::npos) { @@ -463,7 +468,7 @@ namespace mtconnect::source::adapter::agent_adapter { "Framing error in streaming data: no content length"); return false; } - + auto ep = view.find("\r\n\r\n", bp); if (bp == boost::string_view::npos) { @@ -474,11 +479,11 @@ namespace mtconnect::source::adapter::agent_adapter { return false; } ep += 4; - + using string_view_range = boost::iterator_range; auto svi = string_view_range(view.begin() + bp, view.end()); auto lp = boost::ifind_first(svi, boost::string_view("content-length:")); - + if (lp.empty()) { LOG(warning) << "Cannot find the content-length"; @@ -487,7 +492,7 @@ namespace mtconnect::source::adapter::agent_adapter { "Framing error in streaming data: no content length"); return false; } - + boost::string_view length(lp.end()); auto digits = length.substr(0, length.find("\n")); auto finder = boost::token_finder(algo::is_digit(), algo::token_compress_on); @@ -500,14 +505,14 @@ namespace mtconnect::source::adapter::agent_adapter { "Framing error in streaming data: no content length"); return false; } - + m_chunkLength = boost::lexical_cast(rng); m_hasHeader = true; m_chunk.consume(ep); - + return true; } - + /// @brief Creates the function to handle chunk body /// /// Sets the chunk parse on chunk body. @@ -522,15 +527,15 @@ namespace mtconnect::source::adapter::agent_adapter { "Stream body but no request"); return body.size(); } - + { std::ostream cstr(&m_chunk); cstr << body; } - + LOG(trace) << "Received: -------- " << m_chunk.size() << " " << remain << "\n" - << body << "\n-------------"; - + << body << "\n-------------"; + if (!m_hasHeader) { if (!parseMimeHeader()) @@ -539,27 +544,27 @@ namespace mtconnect::source::adapter::agent_adapter { return body.size(); } } - + auto len = m_chunk.size(); if (len >= m_chunkLength) { auto start = static_cast(m_chunk.data().data()); string_view sbuf(start, m_chunkLength); - + LOG(trace) << "Received Chunk: --------\n" << sbuf << "\n-------------"; - + processData(string(sbuf)); - + m_chunk.consume(m_chunkLength); m_hasHeader = false; } - + return body.size(); }; - + m_chunkParser->on_chunk_body(m_chunkHandler); } - + /// @brief Begins the async chunk reading if the boundry is found void onChunkedContent() { @@ -570,48 +575,48 @@ namespace mtconnect::source::adapter::agent_adapter { failed(ec, "Cannot find boundary"); return; } - + LOG(trace) << "Found boundary: " << m_boundary; - + m_chunkParser.emplace(std::move(*m_headerParser)); createChunkHeaderHandler(); createChunkBodyHandler(); - + derived().lowestLayer().expires_after(m_timeout); - + http::async_read(derived().stream(), m_buffer, *m_chunkParser, asio::bind_executor(m_strand, beast::bind_front_handler( - &Derived::onRead, derived().getptr()))); + &Derived::onRead, derived().getptr()))); } - + protected: tcp::resolver m_resolver; std::optional m_resolution; beast::flat_buffer m_buffer; // (Must persist between reads) std::optional> m_req; - + // Chunked content handling. std::optional> m_headerParser; std::optional> m_chunkParser; std::optional> m_textParser; asio::io_context::strand m_strand; url::Url m_url; - + std::function - m_chunkHandler; + m_chunkHandler; std::function - m_chunkHeaderHandler; - + m_chunkHeaderHandler; + std::string m_boundary; std::string m_contentType; - + size_t m_chunkLength; bool m_hasHeader = false; boost::asio::streambuf m_chunk; - + // For request queuing std::optional m_request; RequestQueue m_queue; }; - + } // namespace mtconnect::source::adapter::agent_adapter diff --git a/src/mtconnect/source/adapter/mqtt/mqtt_adapter.cpp b/src/mtconnect/source/adapter/mqtt/mqtt_adapter.cpp index 82533ae81..964b0de52 100644 --- a/src/mtconnect/source/adapter/mqtt/mqtt_adapter.cpp +++ b/src/mtconnect/source/adapter/mqtt/mqtt_adapter.cpp @@ -195,9 +195,11 @@ namespace mtconnect { } else if (!HasOption(options, configuration::Topics)) { - LOG(error) << "MQTT Adapter requires at least one topic to subscribe to. Provide 'Topics = " + stringstream msg; + msg << "MQTT Adapter requires at least one topic to subscribe to. Provide 'Topics = " "' or Topics block"; - exit(1); + LOG(fatal) << msg.str(); + throw FatalException(msg.str()); } } /// diff --git a/src/mtconnect/utilities.hpp b/src/mtconnect/utilities.hpp index 70bddd58f..6187923b7 100644 --- a/src/mtconnect/utilities.hpp +++ b/src/mtconnect/utilities.hpp @@ -66,7 +66,42 @@ namespace boost::asio { namespace mtconnect { // Message for when enumerations do not exist in an array/enumeration const int ENUM_MISS = -1; - + + /// @brtief Fatal Error Exception + /// An exception that get thrown to shut down the application. Only caught by the top level worker thread. + class FatalException : public std::exception + { + public: + /// @brief Create a fatal exception with a message + /// @param str The message + FatalException(const std::string &str) : m_what(str) {} + /// @brief Create a fatal exception with a message + /// @param str The message + FatalException(const std::string_view &str) : m_what(str) {} + /// @brief Create a fatal exception with a message + /// @param str The message + FatalException(const char *str) : m_what(str) {} + /// @brief Create a default fatal exception + /// Has the message `Fatal Exception Occurred` + FatalException() : m_what("Fatal Exception Occurred") {} + /// @brief Copy construction from an exception + /// @param ex the exception + FatalException(const std::exception &ex) : m_what(ex.what()) {} + /// @brief Defaut construction + FatalException(const FatalException &) = default; + /// @brief Default destructor + ~FatalException() = default; + + /// @brief gets the message + /// @returns the message as a string + const char* what() const noexcept override { return m_what.c_str(); } + + protected: + std::string m_what; + + }; + + /// @brief Time formats enum TimeFormat { @@ -75,7 +110,7 @@ namespace mtconnect { GMT_UV_SEC, ///< GMT with microsecond resolution LOCAL ///< Time using local time zone }; - + /// @brief Converts string to floating point numberss /// @param[in] text the number /// @return the converted value or 0.0 if incorrect. @@ -96,7 +131,7 @@ namespace mtconnect { } return value; } - + /// @brief Converts string to integer /// @param[in] text the number /// @return the converted value or 0 if incorrect. @@ -117,7 +152,7 @@ namespace mtconnect { } return value; } - + /// @brief converts a double to a string /// @param[in] value the double /// @return the string representation of the double (10 places max) @@ -128,18 +163,18 @@ namespace mtconnect { s << std::setprecision(precision) << value; return s.str(); } - + /// @brief inline formattor support for doubles class format_double_stream { protected: double val; - + public: /// @brief create a formatter /// @param[in] v the value format_double_stream(double v) { val = v; } - + /// @brief writes a double to an output stream with up to 10 digits of precision /// @tparam _CharT from std::basic_ostream /// @tparam _Traits from std::basic_ostream @@ -148,19 +183,19 @@ namespace mtconnect { /// @return reference to the output stream template inline friend std::basic_ostream<_CharT, _Traits> &operator<<( - std::basic_ostream<_CharT, _Traits> &os, const format_double_stream &fmter) + std::basic_ostream<_CharT, _Traits> &os, const format_double_stream &fmter) { constexpr int precision = std::numeric_limits::digits10; os << std::setprecision(precision) << fmter.val; return os; } }; - + /// @brief create a `format_doulble_stream` /// @param[in] v the value /// @return the format_double_stream inline format_double_stream formatted(double v) { return format_double_stream(v); } - + /// @brief Convert text to upper case /// @param[in,out] text text /// @return upper-case of text as string @@ -168,10 +203,10 @@ namespace mtconnect { { std::transform(text.begin(), text.end(), text.begin(), [](unsigned char c) { return std::toupper(c); }); - + return text; } - + /// @brief Simple check if a number as a string is negative /// @param s the numbeer /// @return `true` if positive @@ -182,10 +217,10 @@ namespace mtconnect { if (!isdigit(c)) return false; } - + return true; } - + /// @brief Checks if a string is a valid integer /// @param s the string /// @return `true` if is `[+-]\d+` @@ -194,21 +229,21 @@ namespace mtconnect { auto iter = s.cbegin(); if (*iter == '-' || *iter == '+') ++iter; - + for (; iter != s.end(); iter++) { if (!isdigit(*iter)) return false; } - + return true; } - + /// @brief Gets the local time /// @param[in] time the time /// @param[out] buf struct tm AGENT_LIB_API void mt_localtime(const time_t *time, struct tm *buf); - + /// @brief Formats the timePoint as string given the format /// @param[in] timePoint the time /// @param[in] format the format @@ -240,10 +275,10 @@ namespace mtconnect { return date::format("%Y-%m-%dT%H:%M:%S%z", zt); } } - + return ""; } - + /// @brief get the current time in the given format /// /// cover method for `getCurrentTime()` with `system_clock::now()` @@ -254,7 +289,7 @@ namespace mtconnect { { return getCurrentTime(std::chrono::system_clock::now(), format); } - + /// @brief Get the current time as a unsigned uns64 since epoch /// @tparam timePeriod the resolution type of time /// @return the time as an uns64 @@ -262,18 +297,18 @@ namespace mtconnect { inline uint64_t getCurrentTimeIn() { return std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); + std::chrono::system_clock::now().time_since_epoch()) + .count(); } - + /// @brief Current time in microseconds since epoch /// @return the time as uns64 in microsecnods inline uint64_t getCurrentTimeInMicros() { return getCurrentTimeIn(); } - + /// @brief Current time in seconds since epoch /// @return the time as uns64 in seconds inline uint64_t getCurrentTimeInSec() { return getCurrentTimeIn(); } - + /// @brief Parse the given time /// @param aTime the time in text /// @return uns64 in microseconds since epoch @@ -293,12 +328,12 @@ namespace mtconnect { date::from_stream(str, "%FT%T%Z", fields, &abbrev, &offset); if (!fields.ymd.ok() || !fields.tod.in_conventional_range()) return 0; - + micros microdays {date::sys_days(fields.ymd)}; auto us = fields.tod.to_duration().count() + microdays.time_since_epoch().count(); return us; } - + /// @brief escaped reserved XML characters from text /// @param data text with reserved characters escaped inline void replaceIllegalCharacters(std::string &data) @@ -306,41 +341,30 @@ namespace mtconnect { for (auto i = 0u; i < data.length(); i++) { char c = data[i]; - + switch (c) { case '&': data.replace(i, 1, "&"); break; - + case '<': data.replace(i, 1, "<"); break; - + case '>': data.replace(i, 1, ">"); break; } } } - + /// @brief add namespace prefixes to each element of the XPath /// @param[in] aPath the path to modify /// @param[in] aPrefix the prefix to add /// @return the modified path prefixed AGENT_LIB_API std::string addNamespace(const std::string aPath, const std::string aPrefix); - - /// @brief determines of a string ends with an ending - /// @param[in] value the string to check - /// @param[in] ending the ending to verify - /// @return `true` if the string ends with ending - inline bool ends_with(const std::string &value, const std::string_view &ending) - { - if (ending.size() > value.size()) - return false; - return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); - } - + /// @brief removes white space at the beginning of a string /// @param[in,out] s the string /// @return string with spaces removed @@ -349,7 +373,7 @@ namespace mtconnect { boost::algorithm::trim_left(s); return s; } - + /// @brief removes whitespace from the end of the string /// @param[in,out] s the string /// @return string with spaces removed @@ -358,7 +382,7 @@ namespace mtconnect { boost::algorithm::trim_right(s); return s; } - + /// @brief removes spaces from the beginning and end of a string /// @param[in] s the string /// @return string with spaces removed @@ -367,7 +391,7 @@ namespace mtconnect { boost::algorithm::trim(s); return s; } - + /// @brief split a string into two parts using a ':' separator /// @param key the key to split /// @return a pair of the key and an optional prefix. @@ -379,18 +403,7 @@ namespace mtconnect { else return {key, std::nullopt}; } - - /// @brief determines of a string starts with a beginning - /// @param[in] value the string to check - /// @param[in] beginning the beginning to verify - /// @return `true` if the string begins with beginning - inline bool starts_with(const std::string &value, const std::string_view &beginning) - { - if (beginning.size() > value.size()) - return false; - return std::equal(beginning.begin(), beginning.end(), value.begin()); - } - + /// @brief Case insensitive equals /// @param a first string /// @param b second string @@ -399,14 +412,14 @@ namespace mtconnect { { if (a.size() != b.size()) return false; - + return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin(), [](char a, char b) { - return tolower(a) == tolower(b); - }); + return tolower(a) == tolower(b); + }); } - + using Attributes = std::map; - + /// @brief overloaded pattern for variant visitors using list of lambdas /// @tparam ...Ts list of lambda classes template @@ -416,7 +429,7 @@ namespace mtconnect { }; template overloaded(Ts...) -> overloaded; - + /// @brief Reverse an iterable /// @tparam T The iterable type template @@ -424,13 +437,13 @@ namespace mtconnect { { private: T &m_iterable; - + public: explicit reverse(T &iterable) : m_iterable(iterable) {} auto begin() const { return std::rbegin(m_iterable); } auto end() const { return std::rend(m_iterable); } }; - + /// @brief observation sequence type using SequenceNumber_t = uint64_t; /// @brief set of data item ids for filtering @@ -442,16 +455,16 @@ namespace mtconnect { using Timestamp = std::chrono::time_point; using Timestamp = std::chrono::time_point; using StringList = std::list; - + /// @name Configuration related methods ///@{ - + /// @brief Variant for configuration options using ConfigOption = std::variant; + Milliseconds, StringList>; /// @brief A map of name to option value using ConfigOptions = std::map; - + /// @brief Get an option if available /// @tparam T the option type /// @param options the set of options @@ -466,7 +479,7 @@ namespace mtconnect { else return std::nullopt; } - + /// @brief checks if a boolean option is set /// @param options the set of options /// @param name the name of the option @@ -479,7 +492,7 @@ namespace mtconnect { else return false; } - + /// @brief checks if there is an option /// @param[in] options the set of options /// @param[in] name the name of the option @@ -489,7 +502,7 @@ namespace mtconnect { auto v = options.find(name); return v != options.end(); } - + /// @brief convert an option from a string to a typed option /// @param[in] s the /// @param[in] def template for the option @@ -502,29 +515,29 @@ namespace mtconnect { { std::string sv = std::get(option); visit(overloaded {[&option, &sv](const std::string &) { - if (sv.empty()) - option = std::monostate(); - else - option = sv; - }, - [&option, &sv](const int &) { option = stoi(sv); }, - [&option, &sv](const Milliseconds &) { option = Milliseconds {stoi(sv)}; }, - [&option, &sv](const Seconds &) { option = Seconds {stoi(sv)}; }, - [&option, &sv](const double &) { option = stod(sv); }, - [&option, &sv](const bool &) { option = sv == "yes" || sv == "true"; }, - [&option, &sv](const StringList &) { - StringList list; - boost::split(list, sv, boost::is_any_of(",")); - for (auto &s : list) - boost::trim(s); - option = list; - }, - [](const auto &) {}}, + if (sv.empty()) + option = std::monostate(); + else + option = sv; + }, + [&option, &sv](const int &) { option = stoi(sv); }, + [&option, &sv](const Milliseconds &) { option = Milliseconds {stoi(sv)}; }, + [&option, &sv](const Seconds &) { option = Seconds {stoi(sv)}; }, + [&option, &sv](const double &) { option = stod(sv); }, + [&option, &sv](const bool &) { option = sv == "yes" || sv == "true"; }, + [&option, &sv](const StringList &) { + StringList list; + boost::split(list, sv, boost::is_any_of(",")); + for (auto &s : list) + boost::trim(s); + option = list; + }, + [](const auto &) {}}, def); } return option; } - + /// @brief convert from a string option to a size /// /// Recognizes the following suffixes: @@ -542,7 +555,7 @@ namespace mtconnect { using namespace std; using boost::regex; using boost::smatch; - + auto value = GetOption(options, name); if (value) { @@ -559,11 +572,11 @@ namespace mtconnect { case 'G': case 'g': size *= 1024; - + case 'M': case 'm': size *= 1024; - + case 'K': case 'k': size *= 1024; @@ -577,10 +590,10 @@ namespace mtconnect { throw std::runtime_error(msg.str()); } } - + return size; } - + /// @brief adds a property tree node to an option set /// @param[in] tree the property tree coming from configuration parser /// @param[in,out] options the options set @@ -606,7 +619,7 @@ namespace mtconnect { } } } - + /// @brief adds a property tree node to an option set with defaults /// @param[in] tree the property tree coming from configuration parser /// @param[in,out] options the option set @@ -634,7 +647,7 @@ namespace mtconnect { options.insert_or_assign(e.first, e.second); } } - + /// @brief combine two option sets /// @param[in,out] options existing set of options /// @param[in] entries options to add or update @@ -645,7 +658,7 @@ namespace mtconnect { options.insert_or_assign(e.first, e.second); } } - + /// @brief get options from a property tree and create typed options /// @param[in] tree the property tree coming from configuration parser /// @param[in,out] options option set to modify @@ -663,9 +676,9 @@ namespace mtconnect { } AddOptions(tree, options, entries); } - + /// @} - + /// @brief Format a timestamp as a string in microseconds /// @param[in] ts the timestamp /// @return the time with microsecond resolution @@ -683,7 +696,7 @@ namespace mtconnect { time.append("Z"); return time; } - + /// @brief Capitalize a word /// /// Has special treatment of acronyms like AC, DC, PH, etc. @@ -694,12 +707,12 @@ namespace mtconnect { std::string::const_iterator end) { using namespace std; - + // Exceptions to the rule const static std::unordered_map exceptions = { - {"AC", "AC"}, {"DC", "DC"}, {"PH", "PH"}, - {"IP", "IP"}, {"URI", "URI"}, {"MTCONNECT", "MTConnect"}}; - + {"AC", "AC"}, {"DC", "DC"}, {"PH", "PH"}, + {"IP", "IP"}, {"URI", "URI"}, {"MTCONNECT", "MTConnect"}}; + std::string_view s(&*start, distance(start, end)); const auto &w = exceptions.find(s); ostream_iterator out(camel); @@ -713,7 +726,7 @@ namespace mtconnect { transform(start + 1, end, out, ::tolower); } } - + /// @brief creates an upper-camel-case string from words separated by an underscore (`_`) with /// optional prefix /// @@ -727,20 +740,20 @@ namespace mtconnect { using namespace std; if (type.empty()) return ""; - + ostringstream camel; - + auto start = type.begin(); decltype(start) end; - + auto colon = type.find(':'); - + if (colon != string::npos) { prefix = type.substr(0ul, colon); start += colon + 1; } - + bool done; do { @@ -753,10 +766,10 @@ namespace mtconnect { start = end + 1; } } while (!done); - + return camel.str(); } - + /// @brief parse a string timestamp to a `Timestamp` /// @param timestamp[in] the timestamp as a string /// @return converted `Timestamp` @@ -771,22 +784,22 @@ namespace mtconnect { } return ts; } - -/// @brief Creates a comparable schema version from a major and minor number + + /// @brief Creates a comparable schema version from a major and minor number #define SCHEMA_VERSION(major, minor) (major * 100 + minor) - + /// @brief Get the default schema version of the agent as a string /// @return the version inline std::string StrDefaultSchemaVersion() { return std::to_string(AGENT_VERSION_MAJOR) + "." + std::to_string(AGENT_VERSION_MINOR); } - + inline constexpr int32_t IntDefaultSchemaVersion() { return SCHEMA_VERSION(AGENT_VERSION_MAJOR, AGENT_VERSION_MINOR); } - + /// @brief convert a string version to a major and minor as two integers separated by a char. /// @param s the version inline int32_t IntSchemaVersion(const std::string &s) @@ -804,12 +817,12 @@ namespace mtconnect { return SCHEMA_VERSION(major, minor); } } - + /// @brief Retrieve the best Host IP address from the network interfaces. /// @param[in] context the boost asio io_context for resolving the address /// @param[in] onlyV4 only consider IPV4 addresses if `true` std::string GetBestHostAddress(boost::asio::io_context &context, bool onlyV4 = false); - + /// @brief Function to create a unique id given a sha1 namespace and an id. /// /// Creates a base 64 encoded version of the string and removes any illegal characters @@ -823,27 +836,27 @@ namespace mtconnect { { using namespace std; using namespace boost::uuids::detail; - + sha1 sha(contextSha); - + constexpr string_view startc("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_"); constexpr auto isIDStartChar = [](unsigned char c) -> bool { return isalpha(c) || c == '_'; }; constexpr auto isIDChar = [isIDStartChar](unsigned char c) -> bool { return isIDStartChar(c) || isdigit(c) || c == '.' || c == '-'; }; - + sha.process_bytes(id.data(), id.length()); sha1::digest_type digest; sha.get_digest(digest); - - auto data = (unsigned int *) digest; + + auto data = (unsigned int *) digest; string s(32, ' '); auto len = boost::beast::detail::base64::encode(s.data(), data, sizeof(digest)); - + s.erase(len - 1); s.erase(std::remove_if(++(s.begin()), s.end(), not_fn(isIDChar)), s.end()); - + // Check if the character is legal. if (!isIDStartChar(s[0])) { @@ -852,39 +865,39 @@ namespace mtconnect { s.erase(0, 1); s[0] = startc[c % startc.size()]; } - + s.erase(16); - + return s; } - + namespace url { using UrlQueryPair = std::pair; - + /// @brief A map of URL query parameters that can format as a string struct AGENT_LIB_API UrlQuery : public std::map { using std::map::map; - + /// @brief join the parameters as `=&=&...` /// @return std::string join() const { std::stringstream ss; bool has_pre = false; - + for (const auto &kv : *this) { if (has_pre) ss << '&'; - + ss << kv.first << '=' << kv.second; has_pre = true; } - + return ss.str(); } - + /// @brief Merge twos sets over-writing existing pairs set with `query` and adding new pairs /// @param query query to merge void merge(UrlQuery query) @@ -895,15 +908,15 @@ namespace mtconnect { } } }; - + /// @brief URL struct to parse and format URLs struct AGENT_LIB_API Url { /// @brief Variant for the Host that is either a host name or an ip address using Host = std::variant; - + std::string m_protocol; ///< either `http` or `https` - + Host m_host; ///< the host component std::optional m_username; ///< optional username std::optional m_password; ///< optional password @@ -911,22 +924,22 @@ namespace mtconnect { std::string m_path = "/"; ///< The path component UrlQuery m_query; ///< Query parameters std::string m_fragment; ///< The component after a `#` - + /// @brief Visitor to format the Host as a string struct HostVisitor { std::string operator()(std::string v) const { return v; } - + std::string operator()(boost::asio::ip::address v) const { return v.to_string(); } }; - + /// @brief Get the host as a string /// @return the host std::string getHost() const { return std::visit(HostVisitor(), m_host); } /// @brief Get the port as a string /// @return the port std::string getService() const { return boost::lexical_cast(getPort()); } - + /// @brief Get the path and the query portion of the URL /// @return the path and query std::string getTarget() const @@ -936,7 +949,7 @@ namespace mtconnect { else return m_path; } - + /// @brief Format a target using the existing host and port to make a request /// @param device an optional device /// @param operation the operation (probe,sample,current, or asset) @@ -948,7 +961,7 @@ namespace mtconnect { UrlQuery uq {m_query}; if (!query.empty()) uq.merge(query); - + std::stringstream path; path << m_path; if (m_path[m_path.size() - 1] != '/') @@ -959,10 +972,10 @@ namespace mtconnect { path << operation; if (uq.size() > 0) path << '?' << uq.join(); - + return path.str(); } - + int getPort() const { if (m_port) @@ -974,7 +987,7 @@ namespace mtconnect { else return 0; } - + /// @brief Format the URL as text /// @param device optional device to add to the URL /// @return formatted URL @@ -986,7 +999,7 @@ namespace mtconnect { url << *device; return url.str(); } - + /// @brief parse a string to a Url /// @return parsed URL static Url parse(const std::string_view &url); diff --git a/test_package/agent_test_helper.cpp b/test_package/agent_test_helper.cpp index b2a466a4a..ba25378aa 100644 --- a/test_package/agent_test_helper.cpp +++ b/test_package/agent_test_helper.cpp @@ -75,7 +75,7 @@ void AgentTestHelper::putResponseHelper(const char *file, int line, const string const char *accepts) { makeRequest(file, line, http::verb::put, body, aQueries, path, accepts); - if (ends_with(m_session->m_mimeType, "xml")) + if (m_session->m_mimeType.ends_with("xml"sv)) *doc = xmlParseMemory(m_session->m_body.c_str(), int32_t(m_session->m_body.size())); } @@ -83,7 +83,7 @@ void AgentTestHelper::deleteResponseHelper(const char *file, int line, const Que xmlDocPtr *doc, const char *path, const char *accepts) { makeRequest(file, line, http::verb::delete_, "", aQueries, path, accepts); - if (ends_with(m_session->m_mimeType, "xml")) + if (m_session->m_mimeType.ends_with("xml"sv)) *doc = xmlParseMemory(m_session->m_body.c_str(), int32_t(m_session->m_body.size())); } diff --git a/test_package/file_cache_test.cpp b/test_package/file_cache_test.cpp index a750463fd..48c5db5af 100644 --- a/test_package/file_cache_test.cpp +++ b/test_package/file_cache_test.cpp @@ -107,13 +107,13 @@ TEST_F(FileCacheTest, base_directory_should_redirect) ASSERT_TRUE(file); ASSERT_EQ("/schemas/none.xsd", file->m_redirect); ASSERT_TRUE(m_cache->hasFile("/schemas")); - ASSERT_TRUE(boost::starts_with(std::string(file->m_buffer), "")); + ASSERT_TRUE(std::string_view(file->m_buffer).starts_with("")); auto file2 = m_cache->getFile("/schemas"); ASSERT_TRUE(file); ASSERT_EQ("/schemas/none.xsd", file2->m_redirect); ASSERT_TRUE(m_cache->hasFile("/schemas")); - ASSERT_TRUE(boost::starts_with(std::string(file->m_buffer), "")); + ASSERT_TRUE(std::string_view(file->m_buffer).starts_with("")); } TEST_F(FileCacheTest, file_cache_should_compress_file) From 70f980f1a2079f598e1eeb1cf7416fe9f386d703 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Fri, 31 Oct 2025 13:39:03 +0100 Subject: [PATCH 2/3] Fixed tests to use FatalException --- src/mtconnect/agent.cpp | 566 ++++++------ src/mtconnect/configuration/agent_config.cpp | 11 +- src/mtconnect/configuration/agent_config.hpp | 120 +-- src/mtconnect/configuration/async_context.hpp | 70 +- src/mtconnect/configuration/service.cpp | 4 +- src/mtconnect/device_model/device.cpp | 4 +- src/mtconnect/entity/xml_printer.cpp | 2 +- src/mtconnect/pipeline/pipeline.hpp | 3 +- src/mtconnect/printer/xml_printer.cpp | 2 +- src/mtconnect/sink/rest_sink/rest_service.cpp | 1 - src/mtconnect/sink/rest_sink/server.cpp | 134 +-- .../adapter/agent_adapter/agent_adapter.cpp | 3 +- .../adapter/agent_adapter/session_impl.hpp | 210 ++--- .../source/adapter/mqtt/mqtt_adapter.cpp | 2 +- .../source/adapter/shdr/connector.hpp | 2 +- src/mtconnect/source/source.cpp | 12 +- src/mtconnect/source/source.hpp | 2 +- src/mtconnect/utilities.hpp | 282 +++--- test_package/agent_test.cpp | 860 +++++++++--------- test_package/http_server_test.cpp | 8 +- test_package/json_printer_probe_test.cpp | 6 +- test_package/tls_http_server_test.cpp | 6 +- test_package/websockets_test.cpp | 15 +- test_package/xml_parser_test.cpp | 2 +- 24 files changed, 1164 insertions(+), 1163 deletions(-) diff --git a/src/mtconnect/agent.cpp b/src/mtconnect/agent.cpp index fee9145e2..52544662e 100644 --- a/src/mtconnect/agent.cpp +++ b/src/mtconnect/agent.cpp @@ -73,26 +73,26 @@ namespace mtconnect { namespace net = boost::asio; namespace fs = boost::filesystem; namespace config = mtconnect::configuration; - + static const string g_unavailable("UNAVAILABLE"); static const string g_available("AVAILABLE"); - + // Agent public methods Agent::Agent(config::AsyncContext &context, const string &deviceXmlPath, const ConfigOptions &options) - : m_options(options), - m_context(context), - m_strand(m_context), - m_xmlParser(make_unique()), - m_schemaVersion(GetOption(options, config::SchemaVersion)), - m_deviceXmlPath(deviceXmlPath), - m_circularBuffer(GetOption(options, config::BufferSize).value_or(17), - GetOption(options, config::CheckpointFrequency).value_or(1000)), - m_pretty(IsOptionSet(options, mtconnect::configuration::Pretty)), - m_validation(IsOptionSet(options, mtconnect::configuration::Validation)) + : m_options(options), + m_context(context), + m_strand(m_context), + m_xmlParser(make_unique()), + m_schemaVersion(GetOption(options, config::SchemaVersion)), + m_deviceXmlPath(deviceXmlPath), + m_circularBuffer(GetOption(options, config::BufferSize).value_or(17), + GetOption(options, config::CheckpointFrequency).value_or(1000)), + m_pretty(IsOptionSet(options, mtconnect::configuration::Pretty)), + m_validation(IsOptionSet(options, mtconnect::configuration::Validation)) { using namespace asset; - + CuttingToolArchetype::registerAsset(); CuttingTool::registerAsset(); FileArchetypeAsset::registerAsset(); @@ -102,26 +102,26 @@ namespace mtconnect { ComponentConfigurationParameters::registerAsset(); Pallet::registerAsset(); Fixture::registerAsset(); - + m_assetStorage = make_unique( - GetOption(options, mtconnect::configuration::MaxAssets).value_or(1024)); + GetOption(options, mtconnect::configuration::MaxAssets).value_or(1024)); m_versionDeviceXml = IsOptionSet(options, mtconnect::configuration::VersionDeviceXml); m_createUniqueIds = IsOptionSet(options, config::CreateUniqueIds); - + auto jsonVersion = - uint32_t(GetOption(options, mtconnect::configuration::JsonVersion).value_or(2)); - + uint32_t(GetOption(options, mtconnect::configuration::JsonVersion).value_or(2)); + // Create the Printers m_printers["xml"] = make_unique(m_pretty, m_validation); m_printers["json"] = make_unique(jsonVersion, m_pretty, m_validation); - + if (m_schemaVersion) { m_intSchemaVersion = IntSchemaVersion(*m_schemaVersion); for (auto &[k, pr] : m_printers) pr->setSchemaVersion(*m_schemaVersion); } - + auto sender = GetOption(options, config::Sender); if (sender) { @@ -129,51 +129,51 @@ namespace mtconnect { pr->setSenderName(*sender); } } - + void Agent::initialize(pipeline::PipelineContextPtr context) { NAMED_SCOPE("Agent::initialize"); - + m_beforeInitializeHooks.exec(*this); - + m_pipelineContext = context; m_loopback = - std::make_shared("AgentSource", m_strand, context, m_options); - + std::make_shared("AgentSource", m_strand, context, m_options); + auto devices = loadXMLDeviceFile(m_deviceXmlPath); if (!m_schemaVersion) { m_schemaVersion.emplace(StrDefaultSchemaVersion()); } - + m_intSchemaVersion = IntSchemaVersion(*m_schemaVersion); for (auto &[k, pr] : m_printers) pr->setSchemaVersion(*m_schemaVersion); - + auto disableAgentDevice = GetOption(m_options, config::DisableAgentDevice); if (!(disableAgentDevice && *disableAgentDevice) && m_intSchemaVersion >= SCHEMA_VERSION(1, 7)) { createAgentDevice(); } - + // For the DeviceAdded event for each device for (auto device : devices) addDevice(device); - + if (m_versionDeviceXml && m_createUniqueIds) versionDeviceXml(); - + loadCachedProbe(); - + m_initialized = true; - + m_afterInitializeHooks.exec(*this); } - + void Agent::initialDataItemObservations() { NAMED_SCOPE("Agent::initialDataItemObservations"); - + if (!m_observationsInitialized) { if (m_intSchemaVersion < SCHEMA_VERSION(2, 5) && @@ -183,17 +183,17 @@ namespace mtconnect { for (auto &printer : m_printers) printer.second->setValidation(false); } - + for (auto device : m_deviceIndex) initializeDataItems(device); - + if (m_agentDevice) { for (auto device : m_deviceIndex) { auto d = m_agentDevice->getDeviceDataItem("device_added"); string uuid = *device->getUuid(); - + entity::Properties props {{"VALUE", uuid}}; if (m_intSchemaVersion >= SCHEMA_VERSION(2, 2)) { @@ -201,15 +201,15 @@ namespace mtconnect { if (ValueType(hash.index()) != ValueType::EMPTY) props.insert_or_assign("hash", hash); } - + m_loopback->receive(d, props); } } - + m_observationsInitialized = true; } } - + Agent::~Agent() { m_xmlParser.reset(); @@ -217,36 +217,36 @@ namespace mtconnect { m_sources.clear(); m_agentDevice = nullptr; } - + void Agent::start() { NAMED_SCOPE("Agent::start"); - + if (m_started) { LOG(warning) << "Agent already started."; return; } - + try { m_beforeStartHooks.exec(*this); - + for (auto sink : m_sinks) sink->start(); - + initialDataItemObservations(); - + if (m_agentDevice) { auto d = m_agentDevice->getDeviceDataItem("agent_avail"); m_loopback->receive(d, "AVAILABLE"s); } - + // Start all the sources for (auto source : m_sources) source->start(); - + m_afterStartHooks.exec(*this); } catch (std::runtime_error &e) @@ -254,27 +254,27 @@ namespace mtconnect { LOG(fatal) << "Cannot start server: " << e.what(); throw FatalException(e.what()); } - + m_started = true; } - + void Agent::stop() { NAMED_SCOPE("Agent::stop"); - + if (!m_started) { LOG(warning) << "Agent already stopped."; return; } - + m_beforeStopHooks.exec(*this); - + // Stop all adapter threads... LOG(info) << "Shutting down sources"; for (auto source : m_sources) source->stop(); - + // Signal all observers LOG(info) << "Signaling observers to close sessions"; for (auto di : m_dataItemMap) @@ -283,16 +283,16 @@ namespace mtconnect { if (ldi) ldi->signalObservers(0); } - + LOG(info) << "Shutting down sinks"; for (auto sink : m_sinks) sink->stop(); - + LOG(info) << "Shutting down completed"; - + m_started = false; } - + // --------------------------------------- // Pipeline methods // --------------------------------------- @@ -307,7 +307,7 @@ namespace mtconnect { { if (item.expired()) continue; - + auto di = item.lock(); if (di->hasInitialValue()) { @@ -315,7 +315,7 @@ namespace mtconnect { } } } - + std::lock_guard lock(m_circularBuffer); if (m_circularBuffer.addToBuffer(observation) != 0) { @@ -323,7 +323,7 @@ namespace mtconnect { sink->publish(observation); } } - + void Agent::receiveAsset(asset::AssetPtr asset) { DevicePtr device; @@ -332,14 +332,14 @@ namespace mtconnect { device = findDeviceByUUIDorName(*uuid); else device = getDefaultDevice(); - + if (device && device->getAssetChanged() && device->getAssetRemoved()) { if (asset->getDeviceUuid() && *asset->getDeviceUuid() != *device->getUuid()) { asset->setProperty("deviceUuid", *device->getUuid()); } - + string aid = asset->getAssetId(); if (aid[0] == '@') { @@ -353,16 +353,16 @@ namespace mtconnect { asset->setAssetId(aid); } } - + // Add hash to asset if (m_intSchemaVersion >= SCHEMA_VERSION(2, 2)) asset->addHash(); - + auto old = m_assetStorage->addAsset(asset); - + for (auto &sink : m_sinks) sink->publish(asset); - + if (device) { DataItemPtr di; @@ -388,7 +388,7 @@ namespace mtconnect { if (di) { entity::Properties props {{"assetType", asset->getName()}, {"VALUE", asset->getAssetId()}}; - + if (m_intSchemaVersion >= SCHEMA_VERSION(2, 2)) { const auto &hash = asset->getProperty("hash"); @@ -397,22 +397,22 @@ namespace mtconnect { props.insert_or_assign("hash", hash); } } - + m_loopback->receive(di, props); } - + updateAssetCounts(device, asset->getType()); } } - + bool Agent::reloadDevices(const std::string &deviceFile) { try { // Load the configuration for the Agent auto devices = m_xmlParser->parseFile( - deviceFile, dynamic_cast(m_printers["xml"].get())); - + deviceFile, dynamic_cast(m_printers["xml"].get())); + if (m_xmlParser->getSchemaVersion() && IntSchemaVersion(*m_xmlParser->getSchemaVersion()) != m_intSchemaVersion) { @@ -420,7 +420,7 @@ namespace mtconnect { LOG(warning) << "Schema version does not match agent schema version, restarting the agent"; return false; } - + bool changed = false; for (auto device : devices) { @@ -428,7 +428,7 @@ namespace mtconnect { } if (changed) loadCachedProbe(); - + return true; } catch (runtime_error &e) @@ -446,7 +446,7 @@ namespace mtconnect { throw FatalException(f.what()); } } - + void Agent::loadDeviceXml(const string &deviceXml, const optional source) { try @@ -468,7 +468,7 @@ namespace mtconnect { cerr << f.what() << endl; } } - + void Agent::loadDevices(list devices, const optional source, bool force) { if (!force && !IsOptionSet(m_options, config::EnableSourceDeviceModels)) @@ -476,7 +476,7 @@ namespace mtconnect { LOG(warning) << "Device updates are disabled, skipping update"; return; } - + auto callback = [=, this](config::AsyncContext &context) { try { @@ -490,10 +490,10 @@ namespace mtconnect { { oldUuid = *oldDev->getUuid(); } - + auto uuid = *device->getUuid(); auto name = *device->getComponentName(); - + changed = receiveDevice(device, true) || changed; if (changed) { @@ -505,7 +505,7 @@ namespace mtconnect { s->setOptions({{config::Device, uuid}}); } } - + for (auto src : m_sources) { auto adapter = std::dynamic_pointer_cast(src); @@ -521,7 +521,7 @@ namespace mtconnect { } } } - + if (changed) loadCachedProbe(); } @@ -548,7 +548,7 @@ namespace mtconnect { cerr << f.what() << endl; } }; - + // Gets around a race condition in the loading of adapaters and setting of // UUID. if (m_context.isRunning() && !m_context.isPauased()) @@ -556,11 +556,11 @@ namespace mtconnect { else callback(m_context); } - + bool Agent::receiveDevice(device_model::DevicePtr device, bool version) { NAMED_SCOPE("Agent::receiveDevice"); - + // diff the device against the current device with the same uuid auto uuid = device->getUuid(); if (!uuid) @@ -568,7 +568,7 @@ namespace mtconnect { LOG(error) << "Device does not have a uuid: " << device->getName(); return false; } - + DevicePtr oldDev = findDeviceByUUIDorName(*uuid); if (!oldDev) { @@ -578,10 +578,10 @@ namespace mtconnect { LOG(error) << "Device does not have a name" << *uuid; return false; } - + oldDev = findDeviceByUUIDorName(*name); } - + // If this is a new device if (!oldDev) { @@ -589,10 +589,10 @@ namespace mtconnect { addDevice(device); if (version) versionDeviceXml(); - + for (auto &sink : m_sinks) sink->publish(device); - + return true; } else @@ -603,15 +603,15 @@ namespace mtconnect { LOG(error) << "Device does not have a name: " << *device->getUuid(); return false; } - + verifyDevice(device); createUniqueIds(device); - + LOG(info) << "Checking if device " << *uuid << " has changed"; if (device->different(*oldDev)) { LOG(info) << "Device " << *uuid << " changed, updating model"; - + // Remove the old data items set skip; for (auto &di : oldDev->getDeviceDataItems()) @@ -622,7 +622,7 @@ namespace mtconnect { skip.insert(di.lock()->getId()); } } - + // Replace device in device maps auto it = find(m_deviceIndex.begin(), m_deviceIndex.end(), oldDev); if (it != m_deviceIndex.end()) @@ -632,18 +632,18 @@ namespace mtconnect { LOG(error) << "Cannot find Device " << *uuid << " in devices"; return false; } - + initializeDataItems(device, skip); - + LOG(info) << "Device " << *uuid << " updating circular buffer"; m_circularBuffer.updateDataItems(m_dataItemMap); - + if (m_intSchemaVersion > SCHEMA_VERSION(2, 2)) device->addHash(); - + if (version) versionDeviceXml(); - + if (m_agentDevice) { entity::Properties props {{"VALUE", *uuid}}; @@ -653,14 +653,14 @@ namespace mtconnect { if (ValueType(hash.index()) != ValueType::EMPTY) props.insert_or_assign("hash", hash); } - + auto d = m_agentDevice->getDeviceDataItem("device_changed"); m_loopback->receive(d, props); } - + for (auto &sink : m_sinks) sink->publish(device); - + return true; } else @@ -668,29 +668,29 @@ namespace mtconnect { LOG(info) << "Device " << *uuid << " did not change, ignoring new device"; } } - + return false; } - + void Agent::versionDeviceXml() { NAMED_SCOPE("Agent::versionDeviceXml"); - + using namespace std::chrono; - + if (m_versionDeviceXml) { m_beforeDeviceXmlUpdateHooks.exec(*this); - + // update with a new version of the device.xml, saving the old one // with a date time stamp auto ext = - date::format(".%Y-%m-%dT%H+%M+%SZ", date::floor(system_clock::now())); + date::format(".%Y-%m-%dT%H+%M+%SZ", date::floor(system_clock::now())); fs::path file(m_deviceXmlPath); fs::path backup(m_deviceXmlPath + ext); if (!fs::exists(backup)) fs::rename(file, backup); - + auto printer = getPrinter("xml"); if (printer != nullptr) { @@ -698,11 +698,11 @@ namespace mtconnect { copy_if(m_deviceIndex.begin(), m_deviceIndex.end(), back_inserter(list), [](DevicePtr d) { return dynamic_cast(d.get()) == nullptr; }); auto probe = printer->printProbe(0, 0, 0, 0, 0, list, nullptr, true, true); - + ofstream devices(file.string()); devices << probe; devices.close(); - + m_afterDeviceXmlUpdateHooks.exec(*this); } else @@ -711,7 +711,7 @@ namespace mtconnect { } } } - + bool Agent::removeAsset(DevicePtr device, const std::string &id, const std::optional time) { @@ -720,10 +720,10 @@ namespace mtconnect { { for (auto &sink : m_sinks) sink->publish(asset); - + notifyAssetRemoved(device, asset); updateAssetCounts(device, asset->getType()); - + return true; } else @@ -731,7 +731,7 @@ namespace mtconnect { return false; } } - + bool Agent::removeAllAssets(const std::optional device, const std::optional type, const std::optional time, asset::AssetList &list) @@ -746,13 +746,13 @@ namespace mtconnect { else uuid = device; } - + auto count = m_assetStorage->removeAll(list, uuid, type, time); for (auto &asset : list) { notifyAssetRemoved(nullptr, asset); } - + if (dev) { updateAssetCounts(dev, type); @@ -762,10 +762,10 @@ namespace mtconnect { for (auto d : m_deviceIndex) updateAssetCounts(d, type); } - + return count > 0; } - + void Agent::notifyAssetRemoved(DevicePtr device, const asset::AssetPtr &asset) { if (device || asset->getDeviceUuid()) @@ -791,7 +791,7 @@ namespace mtconnect { {{"assetType", asset->getName()}, {"VALUE", g_unavailable}}); } } - + auto added = dev->getAssetAdded(); if (added) { @@ -804,34 +804,34 @@ namespace mtconnect { } } } - + // --------------------------------------- // Agent Device // --------------------------------------- - + void Agent::createAgentDevice() { NAMED_SCOPE("Agent::createAgentDevice"); - + using namespace boost; - + auto address = GetBestHostAddress(m_context); - + auto port = GetOption(m_options, mtconnect::configuration::Port).value_or(5000); auto service = boost::lexical_cast(port); address.append(":").append(service); - + uuids::name_generator_latest gen(uuids::ns::dns()); auto uuid = GetOption(m_options, mtconnect::configuration::AgentDeviceUUID) - .value_or(uuids::to_string(gen(address))); + .value_or(uuids::to_string(gen(address))); auto id = "agent_"s + uuid.substr(0, uuid.find_first_of('-')); - + // Create the Agent Device ErrorList errors; Properties ps { - {"uuid", uuid}, {"id", id}, {"name", "Agent"s}, {"mtconnectVersion", *m_schemaVersion}}; + {"uuid", uuid}, {"id", id}, {"name", "Agent"s}, {"mtconnectVersion", *m_schemaVersion}}; m_agentDevice = - dynamic_pointer_cast(AgentDevice::getFactory()->make("Agent", ps, errors)); + dynamic_pointer_cast(AgentDevice::getFactory()->make("Agent", ps, errors)); if (!errors.empty()) { for (auto &e : errors) @@ -840,21 +840,21 @@ namespace mtconnect { } addDevice(m_agentDevice); } - + // ---------------------------------------------- // Device management and Initialization // ---------------------------------------------- - + std::list Agent::loadXMLDeviceFile(const std::string &configXmlPath) { NAMED_SCOPE("Agent::loadXMLDeviceFile"); - + try { // Load the configuration for the Agent auto devices = m_xmlParser->parseFile( - configXmlPath, dynamic_cast(m_printers["xml"].get())); - + configXmlPath, dynamic_cast(m_printers["xml"].get())); + if (!m_schemaVersion && m_xmlParser->getSchemaVersion() && !m_xmlParser->getSchemaVersion()->empty()) { @@ -866,7 +866,7 @@ namespace mtconnect { m_schemaVersion = StrDefaultSchemaVersion(); m_intSchemaVersion = IntSchemaVersion(*m_schemaVersion); } - + return devices; } catch (runtime_error &e) @@ -883,14 +883,14 @@ namespace mtconnect { cerr << f.what() << endl; throw FatalException(f.what()); } - + return {}; } - + void Agent::verifyDevice(DevicePtr device) { NAMED_SCOPE("Agent::verifyDevice"); - + // Add the devices to the device map and create availability and // asset changed events if they don't exist // Make sure we have two device level data items: @@ -901,79 +901,79 @@ namespace mtconnect { // Create availability data item and add it to the device. entity::ErrorList errors; auto di = DataItem::make( - {{"type", "AVAILABILITY"s}, {"id", device->getId() + "_avail"}, {"category", "EVENT"s}}, - errors); + {{"type", "AVAILABILITY"s}, {"id", device->getId() + "_avail"}, {"category", "EVENT"s}}, + errors); device->addDataItem(di, errors); } - + if (!device->getAssetChanged() && m_intSchemaVersion >= SCHEMA_VERSION(1, 2)) { entity::ErrorList errors; // Create asset change data item and add it to the device. auto di = DataItem::make({{"type", "ASSET_CHANGED"s}, - {"id", device->getId() + "_asset_chg"}, - {"category", "EVENT"s}}, + {"id", device->getId() + "_asset_chg"}, + {"category", "EVENT"s}}, errors); device->addDataItem(di, errors); } - + if (device->getAssetChanged() && m_intSchemaVersion >= SCHEMA_VERSION(1, 5)) { auto di = device->getAssetChanged(); if (!di->isDiscrete()) di->makeDiscrete(); } - + if (!device->getAssetRemoved() && m_intSchemaVersion >= SCHEMA_VERSION(1, 3)) { // Create asset removed data item and add it to the device. entity::ErrorList errors; auto di = DataItem::make({{"type", "ASSET_REMOVED"s}, - {"id", device->getId() + "_asset_rem"}, - {"category", "EVENT"s}}, + {"id", device->getId() + "_asset_rem"}, + {"category", "EVENT"s}}, errors); device->addDataItem(di, errors); } - + if (!device->getAssetAdded() && m_intSchemaVersion >= SCHEMA_VERSION(2, 6)) { // Create asset removed data item and add it to the device. entity::ErrorList errors; auto di = DataItem::make({{"type", "ASSET_ADDED"s}, - {"id", device->getId() + "_asset_add"}, - {"discrete", true}, - {"category", "EVENT"s}}, + {"id", device->getId() + "_asset_add"}, + {"discrete", true}, + {"category", "EVENT"s}}, errors); device->addDataItem(di, errors); } - + if (!device->getAssetCount() && m_intSchemaVersion >= SCHEMA_VERSION(2, 0)) { entity::ErrorList errors; auto di = DataItem::make({{"type", "ASSET_COUNT"s}, - {"id", device->getId() + "_asset_count"}, - {"category", "EVENT"s}, - {"representation", "DATA_SET"s}}, + {"id", device->getId() + "_asset_count"}, + {"category", "EVENT"s}, + {"representation", "DATA_SET"s}}, errors); device->addDataItem(di, errors); } - + if (IsOptionSet(m_options, mtconnect::configuration::PreserveUUID)) { device->setPreserveUuid(*GetOption(m_options, mtconnect::configuration::PreserveUUID)); } } - + void Agent::initializeDataItems(DevicePtr device, std::optional> skip) { NAMED_SCOPE("Agent::initializeDataItems"); - + // Initialize the id mapping for the devices and set all data items to UNAVAILABLE for (auto item : device->getDeviceDataItems()) { if (item.expired()) continue; - + auto d = item.lock(); if ((!skip || skip->count(d->getId()) > 0) && m_dataItemMap.count(d->getId()) > 0) { @@ -981,10 +981,10 @@ namespace mtconnect { if (di && di != d) { stringstream msg; - + msg << "Duplicate DataItem id " << d->getId() - << " for device: " << *device->getComponentName() << - ". Try using configuration option CreateUniqueIds to resolve."; + << " for device: " << *device->getComponentName() + << ". Try using configuration option CreateUniqueIds to resolve."; LOG(fatal) << msg.str(); throw FatalException(msg.str()); } @@ -997,18 +997,18 @@ namespace mtconnect { value = &g_unavailable; else if (d->getConstantValue()) value = &d->getConstantValue().value(); - + m_loopback->receive(d, *value); m_dataItemMap[d->getId()] = d; } } } - + // Add the a device from a configuration file void Agent::addDevice(DevicePtr device) { NAMED_SCOPE("Agent::addDevice"); - + // Check if device already exists string uuid = *device->getUuid(); auto &idx = m_deviceIndex.get(); @@ -1018,22 +1018,22 @@ namespace mtconnect { // Update existing device stringstream msg; msg << "Device " << *device->getUuid() << " already exists. " - << " Update not supported yet"; + << " Update not supported yet"; throw msg; } else { m_deviceIndex.push_back(device); - + // TODO: Redo Resolve Reference with entity // device->resolveReferences(); verifyDevice(device); createUniqueIds(device); - + if (m_observationsInitialized) { initializeDataItems(device); - + // Check for single valued constrained data items. if (m_agentDevice && device != m_agentDevice) { @@ -1044,24 +1044,24 @@ namespace mtconnect { if (ValueType(hash.index()) != ValueType::EMPTY) props.insert_or_assign("hash", hash); } - + auto d = m_agentDevice->getDeviceDataItem("device_added"); m_loopback->receive(d, props); } } } - + if (m_intSchemaVersion >= SCHEMA_VERSION(2, 2)) device->addHash(); - + for (auto &printer : m_printers) printer.second->setModelChangeTime(getCurrentTime(GMT_UV_SEC)); } - + void Agent::deviceChanged(DevicePtr device, const std::string &uuid) { NAMED_SCOPE("Agent::deviceChanged"); - + bool changed = false; string oldUuid = *device->getUuid(); if (uuid != oldUuid) @@ -1074,28 +1074,28 @@ namespace mtconnect { m_loopback->receive(d, oldUuid); } } - + if (changed) { // Create a new device auto xmlPrinter = dynamic_cast(m_printers["xml"].get()); auto newDevice = m_xmlParser->parseDevice(xmlPrinter->printDevice(device), xmlPrinter); - + newDevice->setUuid(uuid); - + m_loopback->receive(newDevice); } } - + void Agent::createUniqueIds(DevicePtr device) { if (m_createUniqueIds && !dynamic_pointer_cast(device)) { std::unordered_map idMap; - + device->createUniqueIds(idMap); device->updateReferences(idMap); - + // Update the data item map. for (auto &id : idMap) { @@ -1108,83 +1108,83 @@ namespace mtconnect { } } } - + void Agent::loadCachedProbe() { NAMED_SCOPE("Agent::loadCachedProbe"); - + // Reload the document for path resolution auto xmlPrinter = dynamic_cast(m_printers["xml"].get()); m_xmlParser->loadDocument(xmlPrinter->printProbe(0, 0, 0, 0, 0, getDevices())); - + for (auto &printer : m_printers) printer.second->setModelChangeTime(getCurrentTime(GMT_UV_SEC)); } - + // ---------------------------------------------------- // Helper Methods // ---------------------------------------------------- - + DevicePtr Agent::getDeviceByName(const std::string &name) const { if (name.empty()) return getDefaultDevice(); - + auto &idx = m_deviceIndex.get(); auto devPos = idx.find(name); if (devPos != idx.end()) return *devPos; - + return nullptr; } - + DevicePtr Agent::getDeviceByName(const std::string &name) { if (name.empty()) return getDefaultDevice(); - + auto &idx = m_deviceIndex.get(); auto devPos = idx.find(name); if (devPos != idx.end()) return *devPos; - + return nullptr; } - + DevicePtr Agent::findDeviceByUUIDorName(const std::string &idOrName) const { if (idOrName.empty()) return getDefaultDevice(); - + DevicePtr res; if (auto d = m_deviceIndex.get().find(idOrName); d != m_deviceIndex.get().end()) res = *d; else if (auto d = m_deviceIndex.get().find(idOrName); d != m_deviceIndex.get().end()) res = *d; - + return res; } - + // ---------------------------------------------------- // Adapter Methods // ---------------------------------------------------- - + void Agent::addSource(source::SourcePtr source, bool start) { m_sources.emplace_back(source); - + if (start) source->start(); - + auto adapter = dynamic_pointer_cast(source); if (m_agentDevice && adapter) { m_agentDevice->addAdapter(adapter); - + if (m_observationsInitialized) initializeDataItems(m_agentDevice); - + // Reload the document for path resolution if (m_initialized) { @@ -1192,15 +1192,15 @@ namespace mtconnect { } } } - + void Agent::addSink(sink::SinkPtr sink, bool start) { m_sinks.emplace_back(sink); - + if (start) sink->start(); } - + void AgentPipelineContract::deliverConnectStatus(entity::EntityPtr entity, const StringList &devices, bool autoAvailable) { @@ -1222,15 +1222,15 @@ namespace mtconnect { LOG(error) << "Unexpected connection status received: " << value; } } - + void AgentPipelineContract::deliverCommand(entity::EntityPtr entity) { auto command = entity->get("command"); auto value = entity->getValue(); - + auto device = entity->maybeGet("device"); auto source = entity->maybeGet("source"); - + if ((command != "devicemodel" && !device) || !source) { LOG(error) << "Invalid command: " << command << ", device or source not specified"; @@ -1243,7 +1243,7 @@ namespace mtconnect { m_agent->receiveCommand(*device, command, value, *source); } } - + void Agent::connecting(const std::string &adapter) { if (m_agentDevice) @@ -1253,30 +1253,30 @@ namespace mtconnect { m_loopback->receive(di, "LISTEN"); } } - + // Add values for related data items UNAVAILABLE void Agent::disconnected(const std::string &adapter, const StringList &devices, bool autoAvailable) { LOG(debug) << "Disconnected from adapter, setting all values to UNAVAILABLE"; - + if (m_agentDevice) { auto di = m_agentDevice->getConnectionStatus(adapter); if (di) m_loopback->receive(di, "CLOSED"); } - + for (auto &name : devices) { DevicePtr device = findDeviceByUUIDorName(name); if (device == nullptr) { LOG(warning) << "Cannot find device " << name << " when adapter " << adapter - << "disconnected"; + << "disconnected"; continue; } - + for (auto di : device->getDeviceDataItems()) { auto dataItem = di.lock(); @@ -1285,7 +1285,7 @@ namespace mtconnect { dataItem->getType() == "AVAILABILITY"))) { auto ptr = getLatest(dataItem->getId()); - + if (ptr) { const string *value = nullptr; @@ -1293,7 +1293,7 @@ namespace mtconnect { value = &dataItem->getConstantValue().value(); else if (!ptr->isUnavailable()) value = &g_unavailable; - + if (value) m_loopback->receive(dataItem, *value); } @@ -1303,7 +1303,7 @@ namespace mtconnect { } } } - + void Agent::connected(const std::string &adapter, const StringList &devices, bool autoAvailable) { if (m_agentDevice) @@ -1312,10 +1312,10 @@ namespace mtconnect { if (di) m_loopback->receive(di, "ESTABLISHED"); } - + if (!autoAvailable) return; - + for (auto &name : devices) { DevicePtr device = findDeviceByUUIDorName(name); @@ -1325,7 +1325,7 @@ namespace mtconnect { continue; } LOG(debug) << "Connected to adapter, setting all Availability data items to AVAILABLE"; - + if (auto avail = device->getAvailability()) { LOG(debug) << "Adding availabilty event for " << device->getAvailability()->getId(); @@ -1335,7 +1335,7 @@ namespace mtconnect { LOG(debug) << "Cannot find availability for " << *device->getComponentName(); } } - + void Agent::sourceFailed(const std::string &identity) { auto source = findSource(identity); @@ -1343,7 +1343,7 @@ namespace mtconnect { { source->stop(); m_sources.remove(source); - + bool ext = false; for (auto &s : m_sources) { @@ -1353,7 +1353,7 @@ namespace mtconnect { break; } } - + if (!ext) { LOG(fatal) << "Source " << source->getName() << " failed"; @@ -1371,16 +1371,16 @@ namespace mtconnect { LOG(error) << "Cannot find failed source: " << source->getName(); } } - + // ----------------------------------------------- // Validation methods // ----------------------------------------------- - + string Agent::devicesAndPath(const std::optional &path, const DevicePtr device, const std::optional &deviceType) const { string dataPath; - + if (device || deviceType) { string prefix; @@ -1390,16 +1390,16 @@ namespace mtconnect { prefix = "//Devices/Device[@uuid=\"" + *device->getUuid() + "\"]"; else if (deviceType) prefix = "//Devices/Device"; - + if (path) { stringstream parse(*path); string token; - + // Prefix path (i.e. "path1|path2" => "{prefix}path1|{prefix}path2") while (getline(parse, token, '|')) dataPath += prefix + token + "|"; - + dataPath.erase(dataPath.length() - 1); } else @@ -1414,10 +1414,10 @@ namespace mtconnect { else dataPath = "//Devices/Device|//Devices/Agent"; } - + return dataPath; } - + void AgentPipelineContract::deliverAssetCommand(entity::EntityPtr command) { const std::string &cmd = command->getValue(); @@ -1442,33 +1442,33 @@ namespace mtconnect { LOG(error) << "Invalid assent command: " << cmd; } } - + void Agent::updateAssetCounts(const DevicePtr &device, const std::optional type) { if (!device) return; - + auto dc = device->getAssetCount(); if (dc) { if (type) { auto count = m_assetStorage->getCountForDeviceAndType(*device->getUuid(), *type); - + DataSet set; if (count > 0) set.emplace(*type, int64_t(count)); else set.emplace(*type, DataSetValue(), true); - + m_loopback->receive(dc, {{"VALUE", set}}); } else { auto counts = m_assetStorage->getCountsByTypeForDevice(*device->getUuid()); - + DataSet set; - + for (auto &[t, count] : counts) { if (count > 0) @@ -1476,67 +1476,67 @@ namespace mtconnect { else set.emplace(t, DataSetValue(), true); } - + m_loopback->receive(dc, {{"resetTriggered", "RESET_COUNTS"s}, {"VALUE", set}}); } } } - + void Agent::receiveCommand(const std::string &deviceName, const std::string &command, const std::string &value, const std::string &source) { DevicePtr device {nullptr}; device = findDeviceByUUIDorName(deviceName); - + if (!device) { LOG(warning) << source << ": Cannot find device for name " << deviceName; } - + static std::unordered_map> - deviceCommands { - {"manufacturer", mem_fn(&Device::setManufacturer)}, - {"station", mem_fn(&Device::setStation)}, - {"serialnumber", mem_fn(&Device::setSerialNumber)}, - {"description", mem_fn(&Device::setDescriptionValue)}, - {"nativename", - [](DevicePtr device, const string &name) { device->setProperty("nativeName", name); }}, - {"calibration", [](DevicePtr device, const string &value) { - istringstream line(value); - - // Look for name|factor|offset triples - string name, factor, offset; - while (getline(line, name, '|') && getline(line, factor, '|') && - getline(line, offset, '|')) - { - // Convert to a floating point number - auto di = device->getDeviceDataItem(name); - if (!di) - LOG(warning) << "Cannot find data item to calibrate for " << name; - else - { - try - { - double fact_value = stod(factor); - double off_value = stod(offset); - - device_model::data_item::UnitConversion conv(fact_value, off_value); - di->setConverter(conv); - } - catch (std::exception e) - { - LOG(error) << "Cannot convert factor " << factor << " or " << offset - << " to double: " << e.what(); - } - } - } - }}}; - + deviceCommands { + {"manufacturer", mem_fn(&Device::setManufacturer)}, + {"station", mem_fn(&Device::setStation)}, + {"serialnumber", mem_fn(&Device::setSerialNumber)}, + {"description", mem_fn(&Device::setDescriptionValue)}, + {"nativename", + [](DevicePtr device, const string &name) { device->setProperty("nativeName", name); }}, + {"calibration", [](DevicePtr device, const string &value) { + istringstream line(value); + + // Look for name|factor|offset triples + string name, factor, offset; + while (getline(line, name, '|') && getline(line, factor, '|') && + getline(line, offset, '|')) + { + // Convert to a floating point number + auto di = device->getDeviceDataItem(name); + if (!di) + LOG(warning) << "Cannot find data item to calibrate for " << name; + else + { + try + { + double fact_value = stod(factor); + double off_value = stod(offset); + + device_model::data_item::UnitConversion conv(fact_value, off_value); + di->setConverter(conv); + } + catch (std::exception e) + { + LOG(error) << "Cannot convert factor " << factor << " or " << offset + << " to double: " << e.what(); + } + } + } + }}}; + static std::unordered_map adapterDataItems { - {"adapterversion", "_adapter_software_version"}, - {"mtconnectversion", "_mtconnect_version"}, + {"adapterversion", "_adapter_software_version"}, + {"mtconnectversion", "_mtconnect_version"}, }; - + if (command == "devicemodel") { loadDeviceXml(value, source); @@ -1578,7 +1578,7 @@ namespace mtconnect { else { LOG(warning) << "Cannot find data item for the Agent device when processing command " - << command << " with value " << value << " for adapter " << source; + << command << " with value " << value << " for adapter " << source; } } } @@ -1592,10 +1592,10 @@ namespace mtconnect { else { LOG(error) << source << ":Received protocol command '" << command << "' for device '" - << deviceName << "', but the device could not be found"; + << deviceName << "', but the device could not be found"; } } - + // ------------------------------------------- // End // ------------------------------------------- diff --git a/src/mtconnect/configuration/agent_config.cpp b/src/mtconnect/configuration/agent_config.cpp index a42f93f9d..2b6b9a67c 100644 --- a/src/mtconnect/configuration/agent_config.cpp +++ b/src/mtconnect/configuration/agent_config.cpp @@ -880,11 +880,11 @@ namespace mtconnect::configuration { logPaths(LOG_LEVEL(fatal), m_configPaths); throw FatalException(((string) "Please make sure the configuration " - "file probe.xml or Devices.xml is in the current " - "directory or specify the correct file " - "in the configuration file " + - m_configFile.string() + " using Devices = ") - .c_str()); + "file probe.xml or Devices.xml is in the current " + "directory or specify the correct file " + "in the configuration file " + + m_configFile.string() + " using Devices = ") + .c_str()); } m_name = get(options[configuration::ServiceName]); @@ -1252,4 +1252,3 @@ namespace mtconnect::configuration { return false; } } // namespace mtconnect::configuration - diff --git a/src/mtconnect/configuration/agent_config.hpp b/src/mtconnect/configuration/agent_config.hpp index 347123fa1..593ae6dca 100644 --- a/src/mtconnect/configuration/agent_config.hpp +++ b/src/mtconnect/configuration/agent_config.hpp @@ -47,7 +47,7 @@ namespace mtconnect { namespace device_model { class Device; } - + #ifdef WITH_PYTHON namespace python { class Embedded; @@ -58,13 +58,13 @@ namespace mtconnect { class Embedded; } #endif - + class XmlPrinter; - + /// @brief Configuration namespace namespace configuration { using DevicePtr = std::shared_ptr; - + /// @brief Parses the configuration file and creates the `Agent`. Manages config /// file tracking and restarting of the agent. class AGENT_LIB_API AgentConfiguration : public MTConnectService @@ -77,19 +77,19 @@ namespace mtconnect { XML, UNKNOWN }; - + using InitializationFn = void(const boost::property_tree::ptree &, AgentConfiguration &); using InitializationFunction = boost::function; - + using ptree = boost::property_tree::ptree; - + /// @brief Construct the agent configuration AgentConfiguration(); virtual ~AgentConfiguration(); - + /// @name Callbacks for initialization phases ///@{ - + /// @brief Get the callback manager after the agent is created /// @return the callback manager auto &afterAgentHooks() { return m_afterAgentHooks; } @@ -100,7 +100,7 @@ namespace mtconnect { /// @return the callback manager auto &beforeStopHooks() { return m_beforeStopHooks; } ///@} - + /// @brief stops the agent. Used in daemons. void stop() override; /// @brief starts the agent. Used in daemons. @@ -109,24 +109,24 @@ namespace mtconnect { /// @brief initializes the configuration of the agent from the command line parameters /// @param[in] options command line parameters void initialize(const boost::program_options::variables_map &options) override; - + /// @brief Configure the logger with the config node from the config file /// @param channelName the log channel name /// @param config the configuration node /// @param formatter optional custom message format void configureLoggerChannel( - const std::string &channelName, const ptree &config, - std::optional> formatter = std::nullopt); - + const std::string &channelName, const ptree &config, + std::optional> formatter = std::nullopt); + /// @brief Configure the agent logger with the config node from the config file /// @param config the configuration node void configureLogger(const ptree &config); - + /// @brief load a configuration text /// @param[in] text the configuration text loaded from a file /// @param[in] fmt the file format, can be MTCONNECT, JSON, or XML void loadConfig(const std::string &text, FileFormat fmt = MTCONNECT); - + /// @brief assign the agent associated with this configuration /// @param[in] agent the agent the configuration will take ownership of void setAgent(std::unique_ptr &agent) { m_agent = std::move(agent); } @@ -136,10 +136,10 @@ namespace mtconnect { auto &getContext() { return m_context->get(); } /// @brief get a pointer to the async io manager auto &getAsyncContext() { return *m_context.get(); } - + /// @brief sets the path for the working directory to the current path void updateWorkingDirectory() { m_working = std::filesystem::current_path(); } - + /// @name Configuration factories ///@{ /// @brief get the factory for creating sinks @@ -149,11 +149,11 @@ namespace mtconnect { /// @return the factory auto &getSourceFactory() { return m_sourceFactory; } ///@} - + /// @brief get the pipeline context for this configuration /// @note set after the agent is created auto getPipelineContext() { return m_pipelineContext; } - + /// @name Logging methods ///@{ /// @brief gets the boost log sink @@ -210,7 +210,7 @@ namespace mtconnect { { return m_logChannels[channelName].m_logLevel; } - + /// @brief set the logging level /// @param[in] level the new logging level void setLoggingLevel(const boost::log::trivial::severity_level level); @@ -218,62 +218,62 @@ namespace mtconnect { /// @param level the new logging level /// @return the logging level boost::log::trivial::severity_level setLoggingLevel(const std::string &level); - + std::optional findConfigFile(const std::string &file) { return findFile(m_configPaths, file); } - + std::optional findDataFile(const std::string &file) { return findFile(m_dataPaths, file); } - + /// @brief Create a sink contract with functions to find config and data files. /// @return shared pointer to a sink contract sink::SinkContractPtr makeSinkContract() { auto contract = m_agent->makeSinkContract(); contract->m_findConfigFile = - [this](const std::string &n) -> std::optional { + [this](const std::string &n) -> std::optional { return findConfigFile(n); }; contract->m_findDataFile = - [this](const std::string &n) -> std::optional { + [this](const std::string &n) -> std::optional { return findDataFile(n); }; return contract; } - + /// @brief add a path to the config paths /// @param path the path to add void addConfigPath(const std::filesystem::path &path) { addPathBack(m_configPaths, path); } - + /// @brief add a path to the data paths /// @param path the path to add void addDataPath(const std::filesystem::path &path) { addPathBack(m_dataPaths, path); } - + /// @brief add a path to the plugin paths /// @param path the path to add void addPluginPath(const std::filesystem::path &path) { addPathBack(m_pluginPaths, path); } - + protected: DevicePtr getDefaultDevice(); void loadAdapters(const ptree &tree, const ConfigOptions &options); void loadSinks(const ptree &sinks, ConfigOptions &options); - + #ifdef WITH_PYTHON void configurePython(const ptree &tree, ConfigOptions &options); #endif #ifdef WITH_RUBY void configureRuby(const ptree &tree, ConfigOptions &options); #endif - + void loadPlugins(const ptree &tree); bool loadPlugin(const std::string &name, const ptree &tree); void monitorFiles(boost::system::error_code ec); void scheduleMonitorTimer(); - + protected: std::optional findFile(const std::list &paths, const std::string file) @@ -285,33 +285,33 @@ namespace mtconnect { if (std::filesystem::exists(tst, ec) && !ec) { LOG(debug) << "Found file '" << file << "' " - << " in path " << path; + << " in path " << path; auto con {std::filesystem::canonical(tst)}; return con; } else { LOG(debug) << "Cannot find file '" << file << "' " - << " in path " << path; + << " in path " << path; } } - + return std::nullopt; } - + void addPathBack(std::list &paths, std::filesystem::path path) { std::error_code ec; auto con {std::filesystem::canonical(path, ec)}; if (std::find(paths.begin(), paths.end(), con) != paths.end()) return; - + if (!ec) paths.emplace_back(con); else LOG(debug) << "Cannot file path: " << path << ", " << ec.message(); } - + void addPathFront(std::list &paths, std::filesystem::path path) { std::error_code ec; @@ -322,7 +322,7 @@ namespace mtconnect { else LOG(debug) << "Cannot file path: " << path << ", " << ec.message(); } - + template void logPaths(T lvl, const std::list &paths) { @@ -330,52 +330,52 @@ namespace mtconnect { { BOOST_LOG_STREAM_WITH_PARAMS(::boost::log::trivial::logger::get(), (::boost::log::keywords::severity = lvl)) - << " " << p; + << " " << p; } } - + void expandConfigVariables(boost::property_tree::ptree &); - + protected: using text_sink = boost::log::sinks::synchronous_sink; using console_sink = - boost::log::sinks::synchronous_sink; - + boost::log::sinks::synchronous_sink; + struct LogChannel { std::string m_channelName; std::filesystem::path m_logDirectory; std::filesystem::path m_logArchivePattern; std::filesystem::path m_logFileName; - + int64_t m_maxLogFileSize {0}; int64_t m_logRotationSize {0}; int64_t m_rotationLogInterval {0}; - + boost::log::trivial::severity_level m_logLevel {boost::log::trivial::severity_level::info}; - + boost::shared_ptr m_logSink; }; - + std::map m_logChannels; - + std::map m_initializers; - + std::unique_ptr m_context; std::unique_ptr m_agent; - + pipeline::PipelineContextPtr m_pipelineContext; std::unique_ptr m_adapterHandler; - + std::string m_version; std::string m_devicesFile; std::filesystem::path m_exePath; std::filesystem::path m_working; - + std::list m_configPaths; std::list m_dataPaths; std::list m_pluginPaths; - + // File monitoring boost::asio::steady_timer m_monitorTimer; bool m_monitorFiles = false; @@ -384,23 +384,23 @@ namespace mtconnect { bool m_restart = false; std::optional m_configTime; std::optional m_deviceTime; - + // Factories sink::SinkFactory m_sinkFactory; source::SourceFactory m_sourceFactory; - + int m_workerThreadCount {1}; - + // Reference to the global logger boost::log::trivial::logger_type *m_logger {nullptr}; - + #ifdef WITH_RUBY std::unique_ptr m_ruby; #endif #ifdef WITH_PYTHON std::unique_ptr m_python; #endif - + HookManager m_afterAgentHooks; HookManager m_beforeStartHooks; HookManager m_beforeStopHooks; diff --git a/src/mtconnect/configuration/async_context.hpp b/src/mtconnect/configuration/async_context.hpp index c13b037cf..6c4694913 100644 --- a/src/mtconnect/configuration/async_context.hpp +++ b/src/mtconnect/configuration/async_context.hpp @@ -20,12 +20,12 @@ #include #include -#include "mtconnect/utilities.hpp" #include "mtconnect/config.hpp" #include "mtconnect/logging.hpp" +#include "mtconnect/utilities.hpp" namespace mtconnect::configuration { - + /// @brief Manages the boost asio context and allows for a syncronous /// callback to execute when all the worker threads have stopped. class AGENT_LIB_API AsyncContext @@ -33,36 +33,36 @@ namespace mtconnect::configuration { public: using SyncCallback = std::function; using WorkGuard = boost::asio::executor_work_guard; - + /// @brief creates an asio context and a guard to prevent it from /// stopping AsyncContext() { m_guard.emplace(m_context.get_executor()); } /// @brief removes the copy constructor AsyncContext(const AsyncContext &) = delete; ~AsyncContext() {} - + /// @brief is the context running /// @returns running status auto isRunning() { return m_running; } - + /// @brief return the paused state /// @returns the paused state auto isPauased() { return m_paused; } - + /// @brief Testing only: method to remove the run guard from the context void removeGuard() { m_guard.reset(); } - + /// @brief get the boost asio context reference auto &get() { return m_context; } - + /// @brief operator() returns a reference to the io context /// @return the io context operator boost::asio::io_context &() { return m_context; } - + /// @brief sets the number of theads for asio thread pool /// @param[in] threads number of threads void setThreadCount(int threads) { m_threadCount = threads; } - + /// @brief start `m_threadCount` worker threads /// @returns the exit code int start() @@ -76,13 +76,12 @@ namespace mtconnect::configuration { { for (int i = 0; i < m_threadCount; i++) { - m_workers.emplace_back(boost::thread([this]() - { + m_workers.emplace_back(boost::thread([this]() { try { m_context.run(); } - + catch (FatalException &e) { LOG(fatal) << "Fatal exception occurred: " << e.what(); @@ -101,7 +100,6 @@ namespace mtconnect::configuration { stop(); m_exitCode = 1; } - })); } auto &first = m_workers.front(); @@ -113,16 +111,16 @@ namespace mtconnect::configuration { m_context.stop(); } } - + for (auto &w : m_workers) { w.join(); } m_workers.clear(); - + if (m_delayedStop.joinable()) m_delayedStop.join(); - + if (m_syncCallback && m_exitCode == 0) { m_syncCallback(*this); @@ -132,10 +130,10 @@ namespace mtconnect::configuration { restart(); } } - + } while (m_running); } - + catch (FatalException &e) { LOG(fatal) << "Fatal exception occurred: " << e.what(); @@ -154,10 +152,10 @@ namespace mtconnect::configuration { stop(); m_exitCode = 1; } - + return m_exitCode; } - + /// @brief pause the worker threads. Sets a callback when the threads are paused. /// @param[in] callback the callback to call /// @param[in] safeStop stops by resetting the the guard, otherwise stop the @@ -171,7 +169,7 @@ namespace mtconnect::configuration { else m_context.stop(); } - + /// @brief stop the worker threads /// @param safeStop if `true` resets the guard or stops the context void stop(bool safeStop = true) @@ -182,7 +180,7 @@ namespace mtconnect::configuration { else m_context.stop(); } - + /// @brief restarts the worker threads when paused void restart() { @@ -191,59 +189,59 @@ namespace mtconnect::configuration { m_guard.emplace(m_context.get_executor()); m_context.restart(); } - + /// @name Cover methods for asio io_context /// @{ - + /// @brief io_context::run_for template auto run_for(const std::chrono::duration &rel_time) { return m_context.run_for(rel_time); } - + /// @brief io_context::run auto run() { return m_context.run(); } - + /// @brief io_context::run_one auto run_one() { return m_context.run_one(); } - + /// @brief io_context::run_one_for template auto run_one_for(const std::chrono::duration &rel_time) { return m_context.run_one_for(rel_time); } - + /// @brief io_context::run_one_until template auto run_one_until(const std::chrono::time_point &abs_time) { return m_context.run_one_for(abs_time); } - + /// @brief io_context::poll auto poll() { return m_context.poll(); } - + /// @brief io_context::poll auto get_executor() BOOST_ASIO_NOEXCEPT { return m_context.get_executor(); } - + /// @} - + private: void operator=(const AsyncContext &) {} - + protected: boost::asio::io_context m_context; std::list m_workers; SyncCallback m_syncCallback; std::optional m_guard; std::thread m_delayedStop; - + int m_threadCount = 1; bool m_running = false; bool m_paused = false; int m_exitCode = 0; }; - + } // namespace mtconnect::configuration diff --git a/src/mtconnect/configuration/service.cpp b/src/mtconnect/configuration/service.cpp index 4c47c95ff..1709937e7 100644 --- a/src/mtconnect/configuration/service.cpp +++ b/src/mtconnect/configuration/service.cpp @@ -177,7 +177,7 @@ namespace mtconnect { using namespace std; int res = 0; - + try { // If command-line parameter is "install", install the service. If debug or run @@ -238,7 +238,7 @@ namespace mtconnect { { LOG(fatal) << "Agent top level exception: " << e.what(); std::cerr << "Agent top level exception: " << e.what() << std::endl; - res = 1; + res = 1; } catch (std::string &s) { diff --git a/src/mtconnect/device_model/device.cpp b/src/mtconnect/device_model/device.cpp index b5c2bbd1c..a6e7a9250 100644 --- a/src/mtconnect/device_model/device.cpp +++ b/src/mtconnect/device_model/device.cpp @@ -62,7 +62,7 @@ namespace mtconnect { { stringstream msg; msg << "Device " << getName() << ": Duplicatie data item id '" << di->getId() - << "', Exiting"; + << "', Exiting"; LOG(fatal) << msg.str(); throw FatalException(msg.str()); } @@ -97,7 +97,7 @@ namespace mtconnect { { stringstream msg; msg << "Device " << getName() << ": DataItem '" << di->getId() - << " could not be added, exiting"; + << " could not be added, exiting"; LOG(fatal) << msg.str(); throw FatalException(msg.str()); } diff --git a/src/mtconnect/entity/xml_printer.cpp b/src/mtconnect/entity/xml_printer.cpp index 3cfb0ccb4..66d461886 100644 --- a/src/mtconnect/entity/xml_printer.cpp +++ b/src/mtconnect/entity/xml_printer.cpp @@ -19,8 +19,8 @@ #include -#include #include +#include #include "mtconnect/logging.hpp" #include "mtconnect/printer/xml_printer_helper.hpp" diff --git a/src/mtconnect/pipeline/pipeline.hpp b/src/mtconnect/pipeline/pipeline.hpp index 8fa7b8594..a6ce14c67 100644 --- a/src/mtconnect/pipeline/pipeline.hpp +++ b/src/mtconnect/pipeline/pipeline.hpp @@ -17,9 +17,10 @@ #pragma once -#include #include +#include + #include "mtconnect/config.hpp" #include "pipeline_context.hpp" #include "pipeline_contract.hpp" diff --git a/src/mtconnect/printer/xml_printer.cpp b/src/mtconnect/printer/xml_printer.cpp index 3ea09f277..06c0da048 100644 --- a/src/mtconnect/printer/xml_printer.cpp +++ b/src/mtconnect/printer/xml_printer.cpp @@ -24,8 +24,8 @@ #include #include -#include #include +#include #include "mtconnect/asset/asset.hpp" #include "mtconnect/asset/cutting_tool.hpp" diff --git a/src/mtconnect/sink/rest_sink/rest_service.cpp b/src/mtconnect/sink/rest_sink/rest_service.cpp index 41108936f..4bdfbaad2 100644 --- a/src/mtconnect/sink/rest_sink/rest_service.cpp +++ b/src/mtconnect/sink/rest_sink/rest_service.cpp @@ -1616,4 +1616,3 @@ namespace mtconnect { } // namespace sink::rest_sink } // namespace mtconnect - diff --git a/src/mtconnect/sink/rest_sink/server.cpp b/src/mtconnect/sink/rest_sink/server.cpp index b6b23707d..b05804866 100644 --- a/src/mtconnect/sink/rest_sink/server.cpp +++ b/src/mtconnect/sink/rest_sink/server.cpp @@ -43,12 +43,12 @@ namespace mtconnect::sink::rest_sink { using tcp = boost::asio::ip::tcp; namespace algo = boost::algorithm; namespace ssl = boost::asio::ssl; - + using namespace std; using namespace rapidjson; using std::placeholders::_1; using std::placeholders::_2; - + void Server::loadTlsCertificate() { if (HasOption(m_options, configuration::TlsCertificateChain) && @@ -59,27 +59,27 @@ namespace mtconnect::sink::rest_sink { if (HasOption(m_options, configuration::TlsCertificatePassword)) { m_sslContext.set_password_callback( - [this](size_t, boost::asio::ssl::context_base::password_purpose) -> string { - return *GetOption(m_options, configuration::TlsCertificatePassword); - }); + [this](size_t, boost::asio::ssl::context_base::password_purpose) -> string { + return *GetOption(m_options, configuration::TlsCertificatePassword); + }); } - + m_sslContext.set_options(ssl::context::default_workarounds | asio::ssl::context::no_sslv2 | asio::ssl::context::single_dh_use); m_sslContext.use_certificate_chain_file( - *GetOption(m_options, configuration::TlsCertificateChain)); + *GetOption(m_options, configuration::TlsCertificateChain)); m_sslContext.use_private_key_file(*GetOption(m_options, configuration::TlsPrivateKey), asio::ssl::context::file_format::pem); m_sslContext.use_tmp_dh_file(*GetOption(m_options, configuration::TlsDHKey)); - + m_tlsEnabled = true; - + m_tlsOnly = IsOptionSet(m_options, configuration::TlsOnly); - + if (IsOptionSet(m_options, configuration::TlsVerifyClientCertificate)) { LOG(info) << "Will only accept client connections with valid certificates"; - + m_sslContext.set_verify_mode(ssl::verify_peer | ssl::verify_fail_if_no_peer_cert); if (HasOption(m_options, configuration::TlsClientCAs)) { @@ -89,7 +89,7 @@ namespace mtconnect::sink::rest_sink { } } } - + void Server::start() { try @@ -103,14 +103,14 @@ namespace mtconnect::sink::rest_sink { throw FatalException(e.what()); } } - + // Listen for an HTTP server connection void Server::listen() { NAMED_SCOPE("Server::listen"); - + beast::error_code ec; - + // Blocking call to listen for a connection tcp::endpoint ep(m_address, m_port); m_acceptor.open(ep.protocol(), ec); @@ -135,23 +135,23 @@ namespace mtconnect::sink::rest_sink { { m_port = m_acceptor.local_endpoint().port(); } - + m_acceptor.listen(net::socket_base::max_listen_connections, ec); if (ec) { fail(ec, "Cannot set listen queue length"); return; } - + m_listening = true; m_acceptor.async_accept(net::make_strand(m_context), beast::bind_front_handler(&Server::accept, this)); } - + bool Server::allowPutFrom(const std::string &host) { NAMED_SCOPE("Server::allowPutFrom"); - + // Resolve the host to an ip address to verify remote addr beast::error_code ec; ip::tcp::resolver resolve(m_context); @@ -162,25 +162,25 @@ namespace mtconnect::sink::rest_sink { LOG(error) << ec.category().message(ec.value()) << ": " << ec.message(); return false; } - + // Add the results to the set of allowed hosts for (auto &addr : results) { m_allowPutsFrom.insert(addr.endpoint().address()); } m_allowPuts = true; - + return true; } - + void Server::accept(beast::error_code ec, tcp::socket socket) { NAMED_SCOPE("Server::accept"); - + if (ec) { LOG(error) << ec.category().message(ec.value()) << ": " << ec.message(); - + fail(ec, "Accept failed"); } else if (m_run) @@ -188,7 +188,7 @@ namespace mtconnect::sink::rest_sink { auto dispatcher = [this](SessionPtr session, RequestPtr request) { if (!m_run) return false; - + if (m_lastSession) m_lastSession(session); dispatch(session, request); @@ -197,9 +197,9 @@ namespace mtconnect::sink::rest_sink { if (m_tlsEnabled) { auto dectector = - make_shared(std::move(socket), m_sslContext, m_tlsOnly, m_allowPuts, - m_allowPutsFrom, m_fields, dispatcher, m_errorFunction); - + make_shared(std::move(socket), m_sslContext, m_tlsOnly, m_allowPuts, + m_allowPutsFrom, m_fields, dispatcher, m_errorFunction); + dectector->run(); } else @@ -208,34 +208,34 @@ namespace mtconnect::sink::rest_sink { boost::beast::tcp_stream stream(std::move(socket)); auto session = make_shared(std::move(stream), std::move(buffer), m_fields, dispatcher, m_errorFunction); - + if (!m_allowPutsFrom.empty()) session->allowPutsFrom(m_allowPutsFrom); else if (m_allowPuts) session->allowPuts(); - + session->run(); } m_acceptor.async_accept(net::make_strand(m_context), beast::bind_front_handler(&Server::accept, this)); } } - + //------------------------------------------------------------------------------ - + // Report a failure void Server::fail(beast::error_code ec, char const *what) { LOG(error) << " error: " << ec.message(); } - + using namespace mtconnect::printer; - + template void AddParameter(T &writer, const Parameter ¶m) { AutoJsonObject obj(writer); - + obj.AddPairs("name", param.m_name, "in", param.m_part == PATH ? "path" : "query", "required", param.m_part == PATH); { @@ -245,23 +245,23 @@ namespace mtconnect::sink::rest_sink { case ParameterType::STRING: obj.AddPairs("type", "string", "format", "string"); break; - + case ParameterType::INTEGER: obj.AddPairs("type", "integer", "format", "int64"); break; - + case ParameterType::UNSIGNED_INTEGER: obj.AddPairs("type", "integer", "format", "uint64"); break; - + case ParameterType::DOUBLE: obj.AddPairs("type", "double", "format", "double"); break; - + case ParameterType::BOOL: obj.AddPairs("type", "boolean", "format", "bool"); break; - + case ParameterType::NONE: obj.AddPairs("type", "unknown", "format", "unknown"); break; @@ -270,30 +270,30 @@ namespace mtconnect::sink::rest_sink { { obj.Key("default"); visit( - overloaded {[](const std::monostate &) {}, [&obj](const std::string &s) { obj.Add(s); }, - [&obj](int32_t i) { obj.Add(i); }, [&obj](uint64_t i) { obj.Add(i); }, - [&obj](double d) { obj.Add(d); }, [&obj](bool b) { obj.Add(b); }}, - param.m_default); + overloaded {[](const std::monostate &) {}, [&obj](const std::string &s) { obj.Add(s); }, + [&obj](int32_t i) { obj.Add(i); }, [&obj](uint64_t i) { obj.Add(i); }, + [&obj](double d) { obj.Add(d); }, [&obj](bool b) { obj.Add(b); }}, + param.m_default); } } if (param.m_description) obj.AddPairs("description", *param.m_description); } - + template void AddRouting(T &writer, const Routing &routing) { string verb {to_string(routing.getVerb())}; boost::to_lower(verb); - + { AutoJsonObject obj(writer, verb.data()); - + if (routing.getSummary()) obj.AddPairs("summary", *routing.getSummary()); if (routing.getDescription()) obj.AddPairs("description", *routing.getDescription()); - + if (!routing.getPathParameters().empty() || !routing.getQueryParameters().empty()) { AutoJsonArray ary(writer, "parameters"); @@ -306,7 +306,7 @@ namespace mtconnect::sink::rest_sink { AddParameter(writer, param); } } - + { AutoJsonObject obj(writer, "responses"); { @@ -326,36 +326,36 @@ namespace mtconnect::sink::rest_sink { } } } - + // Swagger stuff template const void Server::renderSwaggerResponse(T &writer) { { AutoJsonObject obj(writer); - + obj.AddPairs("openapi", "3.0.0"); - + { AutoJsonObject obj(writer, "info"); obj.AddPairs("title", "MTConnect – REST API", "description", "MTConnect REST API "); - + { AutoJsonObject obj(writer, "contact"); obj.AddPairs("email", "will@metalogi.io"); } { AutoJsonObject obj(writer, "license"); - + obj.AddPairs("name", "Apache 2.0", "url", "http://www.apache.org/licenses/LICENSE-2.0.html"); } - + obj.AddPairs("version", GetAgentVersion()); } { AutoJsonObject obj(writer, "externalDocs"); - + obj.AddPairs("description", "For information related to MTConnect", "url", "http://mtconnect.org"); } @@ -363,27 +363,27 @@ namespace mtconnect::sink::rest_sink { AutoJsonArray ary(writer, "servers"); { AutoJsonObject obj(writer); - + stringstream str; if (m_tlsEnabled) str << "https://"; else str << "http://"; - + str << GetBestHostAddress(m_context, true) << ':' << m_port << '/'; obj.AddPairs("url", str.str()); } } { AutoJsonObject obj(writer, "paths"); - + multimap routings; for (const auto &routing : m_routings) { if (!routing.isSwagger() && routing.getPath()) routings.emplace(make_pair(*routing.getPath(), &routing)); } - + AutoJsonObject robj(writer, false); for (const auto &[path, routing] : routings) { @@ -394,23 +394,23 @@ namespace mtconnect::sink::rest_sink { } } } - + void Server::addSwaggerRoutings() { auto handler = [&](SessionPtr session, const RequestPtr request) -> bool { auto pretty = *request->parameter("pretty"); - + StringBuffer output; RenderJson(output, pretty, [this](auto &writer) { renderSwaggerResponse(writer); }); - + session->writeResponse( - make_unique(status::ok, string(output.GetString()), "application/json")); - + make_unique(status::ok, string(output.GetString()), "application/json")); + return true; }; - + addRouting({boost::beast::http::verb::get, "/swagger?pretty={bool:false}", handler, true}); // addRouting({boost::beast::http::verb::get, "/swagger.yaml", handler, true}); } - + } // namespace mtconnect::sink::rest_sink diff --git a/src/mtconnect/source/adapter/agent_adapter/agent_adapter.cpp b/src/mtconnect/source/adapter/agent_adapter/agent_adapter.cpp index 4beda3b69..14fa7124e 100644 --- a/src/mtconnect/source/adapter/agent_adapter/agent_adapter.cpp +++ b/src/mtconnect/source/adapter/agent_adapter/agent_adapter.cpp @@ -125,7 +125,6 @@ namespace mtconnect::source::adapter::agent_adapter { } else if (device || HasOption(m_options, configuration::SourceDevice)) { - m_sourceDevice = GetOption(m_options, configuration::SourceDevice); if (!m_sourceDevice) m_sourceDevice = device; @@ -138,7 +137,7 @@ namespace mtconnect::source::adapter::agent_adapter { m_name = m_url.getUrlText(m_sourceDevice); m_identity = CreateIdentityHash(m_name); - + m_options.insert_or_assign(configuration::AdapterIdentity, m_identity); m_feedbackId = "XmlTransformFeedback:" + m_identity; diff --git a/src/mtconnect/source/adapter/agent_adapter/session_impl.hpp b/src/mtconnect/source/adapter/agent_adapter/session_impl.hpp index 02765498a..227ab16fb 100644 --- a/src/mtconnect/source/adapter/agent_adapter/session_impl.hpp +++ b/src/mtconnect/source/adapter/agent_adapter/session_impl.hpp @@ -25,10 +25,10 @@ #include #include -#include "mtconnect/utilities.hpp" #include "mtconnect/config.hpp" #include "mtconnect/pipeline/mtconnect_xml_transform.hpp" #include "mtconnect/pipeline/response_document.hpp" +#include "mtconnect/utilities.hpp" #include "session.hpp" namespace mtconnect::source::adapter::agent_adapter { @@ -38,7 +38,7 @@ namespace mtconnect::source::adapter::agent_adapter { namespace http = boost::beast::http; namespace ssl = boost::asio::ssl; using tcp = boost::asio::ip::tcp; - + /// @brief A session implementation that where the derived classes can support HTTP or HTTPS /// @tparam Derived template @@ -46,7 +46,7 @@ namespace mtconnect::source::adapter::agent_adapter { { /// @brief A list of HTTP requests using RequestQueue = std::list; - + public: /// @brief Cast this class as the derived class /// @return reference to the derived class @@ -54,22 +54,22 @@ namespace mtconnect::source::adapter::agent_adapter { /// @brief Immutably cast this class as its derived subclass /// @return const reference to the derived class const Derived &derived() const { return static_cast(*this); } - + // Objects are constructed with a strand to // ensure that handlers do not execute concurrently. SessionImpl(boost::asio::io_context::strand &strand, const url::Url &url) - : m_resolver(strand.context()), m_strand(strand), m_url(url), m_chunk(1 * 1024 * 1024) + : m_resolver(strand.context()), m_strand(strand), m_url(url), m_chunk(1 * 1024 * 1024) {} - + virtual ~SessionImpl() { stop(); } - + /// @brief see if the socket is connected /// @return `true` if the socket is open bool isOpen() const override { return derived().lowestLayer().socket().is_open(); } - + /// @brief close the connection void close() override { derived().lowestLayer().socket().close(); } - + /// @brief Method called when a request fails /// /// Closes the socket and resets the request @@ -78,7 +78,7 @@ namespace mtconnect::source::adapter::agent_adapter { void failed(std::error_code ec, const char *what) override { derived().lowestLayer().socket().close(); - + LOG(error) << "Agent Adapter Connection Failed: " << m_url.getUrlText(nullopt); if (m_request) LOG(error) << "Agent Adapter Target: " << m_request->getTarget(m_url); @@ -87,15 +87,15 @@ namespace mtconnect::source::adapter::agent_adapter { if (m_failed) m_failed(ec); } - + void stop() override { m_request.reset(); } - + bool makeRequest(const Request &req) override { if (!m_request) { m_request.emplace(req); - + // Clean out any previous data m_buffer.clear(); m_contentType.clear(); @@ -107,7 +107,7 @@ namespace mtconnect::source::adapter::agent_adapter { m_hasHeader = false; if (m_chunk.size() > 0) m_chunk.consume(m_chunk.size()); - + // Check if we are discussected. if (!derived().lowestLayer().socket().is_open()) { @@ -127,7 +127,7 @@ namespace mtconnect::source::adapter::agent_adapter { return false; } } - + /// @brief Process data from the remote agent /// @param data the payload from the agent void processData(const std::string &data) @@ -162,7 +162,7 @@ namespace mtconnect::source::adapter::agent_adapter { "Unknown exception in AgentAdapter::processData"); } } - + /// @brief Connect to the remote agent virtual void connect() { @@ -175,26 +175,26 @@ namespace mtconnect::source::adapter::agent_adapter { else if (holds_alternative(m_url.m_host)) { asio::ip::tcp::endpoint ep(get(m_url.m_host), m_url.getPort()); - + // Create the results type and call on resolve directly. using results_type = tcp::resolver::results_type; auto results = results_type::create(ep, m_url.getHost(), m_url.getService()); - + beast::error_code ec; onResolve(ec, results); } else { derived().lowestLayer().expires_after(m_timeout); - + // Do an async resolution of the address. m_resolver.async_resolve( - get(m_url.m_host), m_url.getService(), - asio::bind_executor( - m_strand, beast::bind_front_handler(&SessionImpl::onResolve, derived().getptr()))); + get(m_url.m_host), m_url.getService(), + asio::bind_executor( + m_strand, beast::bind_front_handler(&SessionImpl::onResolve, derived().getptr()))); } } - + /// @brief Callback when the host name needs to be resolved /// @param ec error code if resultion fails /// @param results the resolution results @@ -207,34 +207,34 @@ namespace mtconnect::source::adapter::agent_adapter { LOG(error) << " Reason: " << ec.category().name() << " " << ec.message(); return failed(source::make_error_code(source::ErrorCode::ADAPTER_FAILED), "resolve"); } - + if (!m_request) { LOG(error) << "Resolved but no request"; return; } - + if (!m_resolution) m_resolution.emplace(results); - + if (m_handler && m_handler->m_connecting) m_handler->m_connecting(m_identity); - + // Set a timeout on the operation derived().lowestLayer().expires_after(m_timeout); - + // Make the connection on the IP address we get from a lookup derived().lowestLayer().async_connect( - results, asio::bind_executor(m_strand, beast::bind_front_handler(&Derived::onConnect, - derived().getptr()))); + results, asio::bind_executor(m_strand, beast::bind_front_handler(&Derived::onConnect, + derived().getptr()))); } - + /// @brief Write a request to the remote agent void request() { if (!m_request) return; - + // Set up an HTTP GET request message m_req.emplace(); m_req->version(11); @@ -243,50 +243,50 @@ namespace mtconnect::source::adapter::agent_adapter { m_req->set(http::field::host, m_url.getHost()); m_req->set(http::field::user_agent, "MTConnect Agent/2.0"); m_req->set(http::field::connection, "keep-alive"); - + if (m_closeConnectionAfterResponse) { m_req->set(http::field::connection, "close"); } - + derived().lowestLayer().expires_after(m_timeout); - + LOG(debug) << "Agent adapter making request: " << m_url.getUrlText(nullopt) << " target " - << m_request->getTarget(m_url); - + << m_request->getTarget(m_url); + http::async_write(derived().stream(), *m_req, beast::bind_front_handler(&SessionImpl::onWrite, derived().getptr())); } - + /// @brief Callback on successful write to the agent /// @param ec error code if something failed /// @param bytes_transferred number of bytes transferred (unused) void onWrite(beast::error_code ec, std::size_t bytes_transferred) { boost::ignore_unused(bytes_transferred); - + if (ec) { LOG(error) << "Cannot send request: " << ec.category().name() << " " << ec.message(); return failed(source::make_error_code(ErrorCode::RETRY_REQUEST), "write"); } - + if (!m_request) { LOG(error) << "Wrote but no request"; return failed(source::make_error_code(ErrorCode::RETRY_REQUEST), "write"); } - + derived().lowestLayer().expires_after(m_timeout); - + // Receive the HTTP response m_headerParser.emplace(); http::async_read_header( - derived().stream(), m_buffer, *m_headerParser, - asio::bind_executor(m_strand, - beast::bind_front_handler(&Derived::onHeader, derived().getptr()))); + derived().stream(), m_buffer, *m_headerParser, + asio::bind_executor(m_strand, + beast::bind_front_handler(&Derived::onHeader, derived().getptr()))); } - + /// @brief Callback after write to process the message header /// @param ec error code if something failed /// @param bytes_transferred number of bytes transferred @@ -295,7 +295,7 @@ namespace mtconnect::source::adapter::agent_adapter { if (ec) { LOG(error) << "Agent Adapter Error getting request header: " << ec.category().name() << " " - << ec.message(); + << ec.message(); derived().lowestLayer().close(); if (m_request->m_stream && ec == beast::error::timeout) { @@ -306,16 +306,16 @@ namespace mtconnect::source::adapter::agent_adapter { { return failed(source::make_error_code(ErrorCode::RETRY_REQUEST), "header"); } - + return; } - + if (!m_request) { LOG(error) << "Received a header but no request"; return failed(source::make_error_code(ErrorCode::RETRY_REQUEST), "header"); } - + auto &msg = m_headerParser->get(); if (msg.version() < 11) { @@ -326,7 +326,7 @@ namespace mtconnect::source::adapter::agent_adapter { { m_closeOnRead = a->value() == "close"; } - + if (m_request->m_stream && m_headerParser->chunked()) { onChunkedContent(); @@ -334,49 +334,49 @@ namespace mtconnect::source::adapter::agent_adapter { else { derived().lowestLayer().expires_after(m_timeout); - + m_textParser.emplace(std::move(*m_headerParser)); http::async_read(derived().stream(), m_buffer, *m_textParser, asio::bind_executor(m_strand, beast::bind_front_handler( - &Derived::onRead, derived().getptr()))); + &Derived::onRead, derived().getptr()))); } } - + /// @brief Callback after header processing to read the body of the response /// @param ec error code if something failed /// @param bytes_transferred number of bytes transferred (unused) void onRead(beast::error_code ec, std::size_t bytes_transferred) { boost::ignore_unused(bytes_transferred); - + if (ec) { LOG(error) << "Error getting response: " << ec.category().name() << " " << ec.message(); return failed(source::make_error_code(ErrorCode::RETRY_REQUEST), "read"); } - + if (!m_request) { LOG(error) << "read data but no request"; return failed(source::make_error_code(ErrorCode::RETRY_REQUEST), "header"); } - + derived().lowestLayer().expires_after(m_timeout); - + if (!derived().lowestLayer().socket().is_open()) derived().disconnect(); - + processData(m_textParser->get().body()); - + m_textParser.reset(); m_req.reset(); - + auto next = m_request->m_next; m_request.reset(); - + if (m_closeOnRead) close(); - + if (next) { next(); @@ -388,10 +388,10 @@ namespace mtconnect::source::adapter::agent_adapter { makeRequest(req); } } - + /// @name Streaming related methods ///@{ - + /// @brief Find the x-multipart-replace MIME boundary /// @return the boundary string inline string findBoundary() @@ -415,10 +415,10 @@ namespace mtconnect::source::adapter::agent_adapter { } } } - + return ""; } - + /// @brief Create a function to handle the chunk header /// /// Sets the chunk parser on chunk header @@ -433,32 +433,32 @@ namespace mtconnect::source::adapter::agent_adapter { cout << "Ext: " << c.first << ": " << c.second << endl; #endif derived().lowestLayer().expires_after(m_timeout); - + if (ec) { return failed(ec, "Failed in chunked extension parse"); } }; - + m_chunkParser->on_chunk_header(m_chunkHeaderHandler); } - + /// @brief Parse the header and get the size and type /// @return `true` if successful bool parseMimeHeader() { using namespace boost; namespace algo = boost::algorithm; - + if (m_chunk.data().size() < 128) { LOG(trace) << "Not enough data for mime header: " << m_chunk.data().size(); return false; } - + auto start = static_cast(m_chunk.data().data()); boost::string_view view(start, m_chunk.data().size()); - + auto bp = view.find(m_boundary.c_str()); if (bp == boost::string_view::npos) { @@ -468,7 +468,7 @@ namespace mtconnect::source::adapter::agent_adapter { "Framing error in streaming data: no content length"); return false; } - + auto ep = view.find("\r\n\r\n", bp); if (bp == boost::string_view::npos) { @@ -479,11 +479,11 @@ namespace mtconnect::source::adapter::agent_adapter { return false; } ep += 4; - + using string_view_range = boost::iterator_range; auto svi = string_view_range(view.begin() + bp, view.end()); auto lp = boost::ifind_first(svi, boost::string_view("content-length:")); - + if (lp.empty()) { LOG(warning) << "Cannot find the content-length"; @@ -492,7 +492,7 @@ namespace mtconnect::source::adapter::agent_adapter { "Framing error in streaming data: no content length"); return false; } - + boost::string_view length(lp.end()); auto digits = length.substr(0, length.find("\n")); auto finder = boost::token_finder(algo::is_digit(), algo::token_compress_on); @@ -505,14 +505,14 @@ namespace mtconnect::source::adapter::agent_adapter { "Framing error in streaming data: no content length"); return false; } - + m_chunkLength = boost::lexical_cast(rng); m_hasHeader = true; m_chunk.consume(ep); - + return true; } - + /// @brief Creates the function to handle chunk body /// /// Sets the chunk parse on chunk body. @@ -527,15 +527,15 @@ namespace mtconnect::source::adapter::agent_adapter { "Stream body but no request"); return body.size(); } - + { std::ostream cstr(&m_chunk); cstr << body; } - + LOG(trace) << "Received: -------- " << m_chunk.size() << " " << remain << "\n" - << body << "\n-------------"; - + << body << "\n-------------"; + if (!m_hasHeader) { if (!parseMimeHeader()) @@ -544,27 +544,27 @@ namespace mtconnect::source::adapter::agent_adapter { return body.size(); } } - + auto len = m_chunk.size(); if (len >= m_chunkLength) { auto start = static_cast(m_chunk.data().data()); string_view sbuf(start, m_chunkLength); - + LOG(trace) << "Received Chunk: --------\n" << sbuf << "\n-------------"; - + processData(string(sbuf)); - + m_chunk.consume(m_chunkLength); m_hasHeader = false; } - + return body.size(); }; - + m_chunkParser->on_chunk_body(m_chunkHandler); } - + /// @brief Begins the async chunk reading if the boundry is found void onChunkedContent() { @@ -575,48 +575,48 @@ namespace mtconnect::source::adapter::agent_adapter { failed(ec, "Cannot find boundary"); return; } - + LOG(trace) << "Found boundary: " << m_boundary; - + m_chunkParser.emplace(std::move(*m_headerParser)); createChunkHeaderHandler(); createChunkBodyHandler(); - + derived().lowestLayer().expires_after(m_timeout); - + http::async_read(derived().stream(), m_buffer, *m_chunkParser, asio::bind_executor(m_strand, beast::bind_front_handler( - &Derived::onRead, derived().getptr()))); + &Derived::onRead, derived().getptr()))); } - + protected: tcp::resolver m_resolver; std::optional m_resolution; beast::flat_buffer m_buffer; // (Must persist between reads) std::optional> m_req; - + // Chunked content handling. std::optional> m_headerParser; std::optional> m_chunkParser; std::optional> m_textParser; asio::io_context::strand m_strand; url::Url m_url; - + std::function - m_chunkHandler; + m_chunkHandler; std::function - m_chunkHeaderHandler; - + m_chunkHeaderHandler; + std::string m_boundary; std::string m_contentType; - + size_t m_chunkLength; bool m_hasHeader = false; boost::asio::streambuf m_chunk; - + // For request queuing std::optional m_request; RequestQueue m_queue; }; - + } // namespace mtconnect::source::adapter::agent_adapter diff --git a/src/mtconnect/source/adapter/mqtt/mqtt_adapter.cpp b/src/mtconnect/source/adapter/mqtt/mqtt_adapter.cpp index 964b0de52..305bab57a 100644 --- a/src/mtconnect/source/adapter/mqtt/mqtt_adapter.cpp +++ b/src/mtconnect/source/adapter/mqtt/mqtt_adapter.cpp @@ -197,7 +197,7 @@ namespace mtconnect { { stringstream msg; msg << "MQTT Adapter requires at least one topic to subscribe to. Provide 'Topics = " - "' or Topics block"; + "' or Topics block"; LOG(fatal) << msg.str(); throw FatalException(msg.str()); } diff --git a/src/mtconnect/source/adapter/shdr/connector.hpp b/src/mtconnect/source/adapter/shdr/connector.hpp index ad0e9ed14..66d4ff3c4 100644 --- a/src/mtconnect/source/adapter/shdr/connector.hpp +++ b/src/mtconnect/source/adapter/shdr/connector.hpp @@ -105,7 +105,7 @@ namespace mtconnect::source::adapter::shdr { void resolved(const boost::system::error_code &error, boost::asio::ip::tcp::resolver::results_type results); void connected(const boost::system::error_code &error, - const boost::asio::ip::tcp::endpoint& endpoint); + const boost::asio::ip::tcp::endpoint &endpoint); void writer(boost::system::error_code ec, std::size_t length); void reader(boost::system::error_code ec, std::size_t length); bool parseSocketBuffer(); diff --git a/src/mtconnect/source/source.cpp b/src/mtconnect/source/source.cpp index d67880b97..97d5ebb83 100644 --- a/src/mtconnect/source/source.cpp +++ b/src/mtconnect/source/source.cpp @@ -15,12 +15,12 @@ // limitations under the License. // -#include - #include "mtconnect/source/source.hpp" #include +#include + #include "mtconnect/logging.hpp" namespace mtconnect::source { @@ -45,18 +45,18 @@ namespace mtconnect::source { std::string CreateIdentityHash(const std::string &input) { using namespace std; - + boost::uuids::detail::sha1 sha1; sha1.process_bytes(input.c_str(), input.length()); boost::uuids::detail::sha1::digest_type digest; sha1.get_digest(digest); - + ostringstream identity; identity << '_' << std::hex; for (int i = 0; i < 5; i++) - identity << (uint16_t) digest[i]; + identity << (uint16_t)digest[i]; return identity.str(); } - + } // namespace mtconnect::source diff --git a/src/mtconnect/source/source.hpp b/src/mtconnect/source/source.hpp index 8ff888bb4..cdef79b11 100644 --- a/src/mtconnect/source/source.hpp +++ b/src/mtconnect/source/source.hpp @@ -95,7 +95,7 @@ namespace mtconnect { std::string m_name; boost::asio::io_context::strand m_strand; }; - + /// @brief create a unique identity hash for an XML id starting with an `_` and 10 hex digits /// @param text the text to create the hashed id /// @returns a string with the hashed result diff --git a/src/mtconnect/utilities.hpp b/src/mtconnect/utilities.hpp index 6187923b7..8ccbeb09e 100644 --- a/src/mtconnect/utilities.hpp +++ b/src/mtconnect/utilities.hpp @@ -66,9 +66,10 @@ namespace boost::asio { namespace mtconnect { // Message for when enumerations do not exist in an array/enumeration const int ENUM_MISS = -1; - + /// @brtief Fatal Error Exception - /// An exception that get thrown to shut down the application. Only caught by the top level worker thread. + /// An exception that get thrown to shut down the application. Only caught by the top level worker + /// thread. class FatalException : public std::exception { public: @@ -83,7 +84,7 @@ namespace mtconnect { FatalException(const char *str) : m_what(str) {} /// @brief Create a default fatal exception /// Has the message `Fatal Exception Occurred` - FatalException() : m_what("Fatal Exception Occurred") {} + FatalException() : m_what("Fatal Exception Occurred") {} /// @brief Copy construction from an exception /// @param ex the exception FatalException(const std::exception &ex) : m_what(ex.what()) {} @@ -91,17 +92,15 @@ namespace mtconnect { FatalException(const FatalException &) = default; /// @brief Default destructor ~FatalException() = default; - + /// @brief gets the message /// @returns the message as a string - const char* what() const noexcept override { return m_what.c_str(); } - + const char *what() const noexcept override { return m_what.c_str(); } + protected: std::string m_what; - }; - - + /// @brief Time formats enum TimeFormat { @@ -110,7 +109,7 @@ namespace mtconnect { GMT_UV_SEC, ///< GMT with microsecond resolution LOCAL ///< Time using local time zone }; - + /// @brief Converts string to floating point numberss /// @param[in] text the number /// @return the converted value or 0.0 if incorrect. @@ -131,7 +130,7 @@ namespace mtconnect { } return value; } - + /// @brief Converts string to integer /// @param[in] text the number /// @return the converted value or 0 if incorrect. @@ -152,7 +151,7 @@ namespace mtconnect { } return value; } - + /// @brief converts a double to a string /// @param[in] value the double /// @return the string representation of the double (10 places max) @@ -163,18 +162,18 @@ namespace mtconnect { s << std::setprecision(precision) << value; return s.str(); } - + /// @brief inline formattor support for doubles class format_double_stream { protected: double val; - + public: /// @brief create a formatter /// @param[in] v the value format_double_stream(double v) { val = v; } - + /// @brief writes a double to an output stream with up to 10 digits of precision /// @tparam _CharT from std::basic_ostream /// @tparam _Traits from std::basic_ostream @@ -183,19 +182,19 @@ namespace mtconnect { /// @return reference to the output stream template inline friend std::basic_ostream<_CharT, _Traits> &operator<<( - std::basic_ostream<_CharT, _Traits> &os, const format_double_stream &fmter) + std::basic_ostream<_CharT, _Traits> &os, const format_double_stream &fmter) { constexpr int precision = std::numeric_limits::digits10; os << std::setprecision(precision) << fmter.val; return os; } }; - + /// @brief create a `format_doulble_stream` /// @param[in] v the value /// @return the format_double_stream inline format_double_stream formatted(double v) { return format_double_stream(v); } - + /// @brief Convert text to upper case /// @param[in,out] text text /// @return upper-case of text as string @@ -203,10 +202,10 @@ namespace mtconnect { { std::transform(text.begin(), text.end(), text.begin(), [](unsigned char c) { return std::toupper(c); }); - + return text; } - + /// @brief Simple check if a number as a string is negative /// @param s the numbeer /// @return `true` if positive @@ -217,10 +216,10 @@ namespace mtconnect { if (!isdigit(c)) return false; } - + return true; } - + /// @brief Checks if a string is a valid integer /// @param s the string /// @return `true` if is `[+-]\d+` @@ -229,21 +228,21 @@ namespace mtconnect { auto iter = s.cbegin(); if (*iter == '-' || *iter == '+') ++iter; - + for (; iter != s.end(); iter++) { if (!isdigit(*iter)) return false; } - + return true; } - + /// @brief Gets the local time /// @param[in] time the time /// @param[out] buf struct tm AGENT_LIB_API void mt_localtime(const time_t *time, struct tm *buf); - + /// @brief Formats the timePoint as string given the format /// @param[in] timePoint the time /// @param[in] format the format @@ -259,7 +258,7 @@ namespace mtconnect { #else namespace tzchrono = date; #endif - + switch (format) { case HUM_READ: @@ -275,10 +274,10 @@ namespace mtconnect { return date::format("%Y-%m-%dT%H:%M:%S%z", zt); } } - + return ""; } - + /// @brief get the current time in the given format /// /// cover method for `getCurrentTime()` with `system_clock::now()` @@ -289,7 +288,7 @@ namespace mtconnect { { return getCurrentTime(std::chrono::system_clock::now(), format); } - + /// @brief Get the current time as a unsigned uns64 since epoch /// @tparam timePeriod the resolution type of time /// @return the time as an uns64 @@ -297,18 +296,18 @@ namespace mtconnect { inline uint64_t getCurrentTimeIn() { return std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); + std::chrono::system_clock::now().time_since_epoch()) + .count(); } - + /// @brief Current time in microseconds since epoch /// @return the time as uns64 in microsecnods inline uint64_t getCurrentTimeInMicros() { return getCurrentTimeIn(); } - + /// @brief Current time in seconds since epoch /// @return the time as uns64 in seconds inline uint64_t getCurrentTimeInSec() { return getCurrentTimeIn(); } - + /// @brief Parse the given time /// @param aTime the time in text /// @return uns64 in microseconds since epoch @@ -328,12 +327,12 @@ namespace mtconnect { date::from_stream(str, "%FT%T%Z", fields, &abbrev, &offset); if (!fields.ymd.ok() || !fields.tod.in_conventional_range()) return 0; - + micros microdays {date::sys_days(fields.ymd)}; auto us = fields.tod.to_duration().count() + microdays.time_since_epoch().count(); return us; } - + /// @brief escaped reserved XML characters from text /// @param data text with reserved characters escaped inline void replaceIllegalCharacters(std::string &data) @@ -341,30 +340,30 @@ namespace mtconnect { for (auto i = 0u; i < data.length(); i++) { char c = data[i]; - + switch (c) { case '&': data.replace(i, 1, "&"); break; - + case '<': data.replace(i, 1, "<"); break; - + case '>': data.replace(i, 1, ">"); break; } } } - + /// @brief add namespace prefixes to each element of the XPath /// @param[in] aPath the path to modify /// @param[in] aPrefix the prefix to add /// @return the modified path prefixed AGENT_LIB_API std::string addNamespace(const std::string aPath, const std::string aPrefix); - + /// @brief removes white space at the beginning of a string /// @param[in,out] s the string /// @return string with spaces removed @@ -373,7 +372,7 @@ namespace mtconnect { boost::algorithm::trim_left(s); return s; } - + /// @brief removes whitespace from the end of the string /// @param[in,out] s the string /// @return string with spaces removed @@ -382,7 +381,7 @@ namespace mtconnect { boost::algorithm::trim_right(s); return s; } - + /// @brief removes spaces from the beginning and end of a string /// @param[in] s the string /// @return string with spaces removed @@ -391,7 +390,7 @@ namespace mtconnect { boost::algorithm::trim(s); return s; } - + /// @brief split a string into two parts using a ':' separator /// @param key the key to split /// @return a pair of the key and an optional prefix. @@ -403,7 +402,7 @@ namespace mtconnect { else return {key, std::nullopt}; } - + /// @brief Case insensitive equals /// @param a first string /// @param b second string @@ -412,14 +411,14 @@ namespace mtconnect { { if (a.size() != b.size()) return false; - + return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin(), [](char a, char b) { - return tolower(a) == tolower(b); - }); + return tolower(a) == tolower(b); + }); } - + using Attributes = std::map; - + /// @brief overloaded pattern for variant visitors using list of lambdas /// @tparam ...Ts list of lambda classes template @@ -429,7 +428,7 @@ namespace mtconnect { }; template overloaded(Ts...) -> overloaded; - + /// @brief Reverse an iterable /// @tparam T The iterable type template @@ -437,13 +436,13 @@ namespace mtconnect { { private: T &m_iterable; - + public: explicit reverse(T &iterable) : m_iterable(iterable) {} auto begin() const { return std::rbegin(m_iterable); } auto end() const { return std::rend(m_iterable); } }; - + /// @brief observation sequence type using SequenceNumber_t = uint64_t; /// @brief set of data item ids for filtering @@ -455,16 +454,16 @@ namespace mtconnect { using Timestamp = std::chrono::time_point; using Timestamp = std::chrono::time_point; using StringList = std::list; - + /// @name Configuration related methods ///@{ - + /// @brief Variant for configuration options using ConfigOption = std::variant; + Milliseconds, StringList>; /// @brief A map of name to option value using ConfigOptions = std::map; - + /// @brief Get an option if available /// @tparam T the option type /// @param options the set of options @@ -479,7 +478,7 @@ namespace mtconnect { else return std::nullopt; } - + /// @brief checks if a boolean option is set /// @param options the set of options /// @param name the name of the option @@ -492,7 +491,7 @@ namespace mtconnect { else return false; } - + /// @brief checks if there is an option /// @param[in] options the set of options /// @param[in] name the name of the option @@ -502,7 +501,7 @@ namespace mtconnect { auto v = options.find(name); return v != options.end(); } - + /// @brief convert an option from a string to a typed option /// @param[in] s the /// @param[in] def template for the option @@ -515,29 +514,29 @@ namespace mtconnect { { std::string sv = std::get(option); visit(overloaded {[&option, &sv](const std::string &) { - if (sv.empty()) - option = std::monostate(); - else - option = sv; - }, - [&option, &sv](const int &) { option = stoi(sv); }, - [&option, &sv](const Milliseconds &) { option = Milliseconds {stoi(sv)}; }, - [&option, &sv](const Seconds &) { option = Seconds {stoi(sv)}; }, - [&option, &sv](const double &) { option = stod(sv); }, - [&option, &sv](const bool &) { option = sv == "yes" || sv == "true"; }, - [&option, &sv](const StringList &) { - StringList list; - boost::split(list, sv, boost::is_any_of(",")); - for (auto &s : list) - boost::trim(s); - option = list; - }, - [](const auto &) {}}, + if (sv.empty()) + option = std::monostate(); + else + option = sv; + }, + [&option, &sv](const int &) { option = stoi(sv); }, + [&option, &sv](const Milliseconds &) { option = Milliseconds {stoi(sv)}; }, + [&option, &sv](const Seconds &) { option = Seconds {stoi(sv)}; }, + [&option, &sv](const double &) { option = stod(sv); }, + [&option, &sv](const bool &) { option = sv == "yes" || sv == "true"; }, + [&option, &sv](const StringList &) { + StringList list; + boost::split(list, sv, boost::is_any_of(",")); + for (auto &s : list) + boost::trim(s); + option = list; + }, + [](const auto &) {}}, def); } return option; } - + /// @brief convert from a string option to a size /// /// Recognizes the following suffixes: @@ -555,7 +554,7 @@ namespace mtconnect { using namespace std; using boost::regex; using boost::smatch; - + auto value = GetOption(options, name); if (value) { @@ -572,11 +571,11 @@ namespace mtconnect { case 'G': case 'g': size *= 1024; - + case 'M': case 'm': size *= 1024; - + case 'K': case 'k': size *= 1024; @@ -590,10 +589,10 @@ namespace mtconnect { throw std::runtime_error(msg.str()); } } - + return size; } - + /// @brief adds a property tree node to an option set /// @param[in] tree the property tree coming from configuration parser /// @param[in,out] options the options set @@ -619,7 +618,7 @@ namespace mtconnect { } } } - + /// @brief adds a property tree node to an option set with defaults /// @param[in] tree the property tree coming from configuration parser /// @param[in,out] options the option set @@ -647,7 +646,7 @@ namespace mtconnect { options.insert_or_assign(e.first, e.second); } } - + /// @brief combine two option sets /// @param[in,out] options existing set of options /// @param[in] entries options to add or update @@ -658,7 +657,7 @@ namespace mtconnect { options.insert_or_assign(e.first, e.second); } } - + /// @brief get options from a property tree and create typed options /// @param[in] tree the property tree coming from configuration parser /// @param[in,out] options option set to modify @@ -676,9 +675,9 @@ namespace mtconnect { } AddOptions(tree, options, entries); } - + /// @} - + /// @brief Format a timestamp as a string in microseconds /// @param[in] ts the timestamp /// @return the time with microsecond resolution @@ -696,7 +695,7 @@ namespace mtconnect { time.append("Z"); return time; } - + /// @brief Capitalize a word /// /// Has special treatment of acronyms like AC, DC, PH, etc. @@ -707,12 +706,12 @@ namespace mtconnect { std::string::const_iterator end) { using namespace std; - + // Exceptions to the rule const static std::unordered_map exceptions = { - {"AC", "AC"}, {"DC", "DC"}, {"PH", "PH"}, - {"IP", "IP"}, {"URI", "URI"}, {"MTCONNECT", "MTConnect"}}; - + {"AC", "AC"}, {"DC", "DC"}, {"PH", "PH"}, + {"IP", "IP"}, {"URI", "URI"}, {"MTCONNECT", "MTConnect"}}; + std::string_view s(&*start, distance(start, end)); const auto &w = exceptions.find(s); ostream_iterator out(camel); @@ -726,7 +725,7 @@ namespace mtconnect { transform(start + 1, end, out, ::tolower); } } - + /// @brief creates an upper-camel-case string from words separated by an underscore (`_`) with /// optional prefix /// @@ -740,20 +739,20 @@ namespace mtconnect { using namespace std; if (type.empty()) return ""; - + ostringstream camel; - + auto start = type.begin(); decltype(start) end; - + auto colon = type.find(':'); - + if (colon != string::npos) { prefix = type.substr(0ul, colon); start += colon + 1; } - + bool done; do { @@ -766,10 +765,10 @@ namespace mtconnect { start = end + 1; } } while (!done); - + return camel.str(); } - + /// @brief parse a string timestamp to a `Timestamp` /// @param timestamp[in] the timestamp as a string /// @return converted `Timestamp` @@ -784,22 +783,22 @@ namespace mtconnect { } return ts; } - + /// @brief Creates a comparable schema version from a major and minor number #define SCHEMA_VERSION(major, minor) (major * 100 + minor) - + /// @brief Get the default schema version of the agent as a string /// @return the version inline std::string StrDefaultSchemaVersion() { return std::to_string(AGENT_VERSION_MAJOR) + "." + std::to_string(AGENT_VERSION_MINOR); } - + inline constexpr int32_t IntDefaultSchemaVersion() { return SCHEMA_VERSION(AGENT_VERSION_MAJOR, AGENT_VERSION_MINOR); } - + /// @brief convert a string version to a major and minor as two integers separated by a char. /// @param s the version inline int32_t IntSchemaVersion(const std::string &s) @@ -817,12 +816,12 @@ namespace mtconnect { return SCHEMA_VERSION(major, minor); } } - + /// @brief Retrieve the best Host IP address from the network interfaces. /// @param[in] context the boost asio io_context for resolving the address /// @param[in] onlyV4 only consider IPV4 addresses if `true` std::string GetBestHostAddress(boost::asio::io_context &context, bool onlyV4 = false); - + /// @brief Function to create a unique id given a sha1 namespace and an id. /// /// Creates a base 64 encoded version of the string and removes any illegal characters @@ -832,31 +831,32 @@ namespace mtconnect { /// @param[in] sha the sha1 namespace to use as context /// @param[in] id the id to use transform /// @returns Returns the first 16 characters of the base 64 encoded sha1 - inline std::string makeUniqueId(const ::boost::uuids::detail::sha1 &contextSha, const std::string &id) + inline std::string makeUniqueId(const ::boost::uuids::detail::sha1 &contextSha, + const std::string &id) { using namespace std; using namespace boost::uuids::detail; - + sha1 sha(contextSha); - + constexpr string_view startc("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_"); constexpr auto isIDStartChar = [](unsigned char c) -> bool { return isalpha(c) || c == '_'; }; constexpr auto isIDChar = [isIDStartChar](unsigned char c) -> bool { return isIDStartChar(c) || isdigit(c) || c == '.' || c == '-'; }; - + sha.process_bytes(id.data(), id.length()); - sha1::digest_type digest; + sha1::digest_type digest; sha.get_digest(digest); - - auto data = (unsigned int *) digest; - + + auto data = (unsigned int *)digest; + string s(32, ' '); auto len = boost::beast::detail::base64::encode(s.data(), data, sizeof(digest)); - + s.erase(len - 1); s.erase(std::remove_if(++(s.begin()), s.end(), not_fn(isIDChar)), s.end()); - + // Check if the character is legal. if (!isIDStartChar(s[0])) { @@ -865,39 +865,39 @@ namespace mtconnect { s.erase(0, 1); s[0] = startc[c % startc.size()]; } - + s.erase(16); - + return s; } - + namespace url { using UrlQueryPair = std::pair; - + /// @brief A map of URL query parameters that can format as a string struct AGENT_LIB_API UrlQuery : public std::map { using std::map::map; - + /// @brief join the parameters as `=&=&...` /// @return std::string join() const { std::stringstream ss; bool has_pre = false; - + for (const auto &kv : *this) { if (has_pre) ss << '&'; - + ss << kv.first << '=' << kv.second; has_pre = true; } - + return ss.str(); } - + /// @brief Merge twos sets over-writing existing pairs set with `query` and adding new pairs /// @param query query to merge void merge(UrlQuery query) @@ -908,15 +908,15 @@ namespace mtconnect { } } }; - + /// @brief URL struct to parse and format URLs struct AGENT_LIB_API Url { /// @brief Variant for the Host that is either a host name or an ip address using Host = std::variant; - + std::string m_protocol; ///< either `http` or `https` - + Host m_host; ///< the host component std::optional m_username; ///< optional username std::optional m_password; ///< optional password @@ -924,22 +924,22 @@ namespace mtconnect { std::string m_path = "/"; ///< The path component UrlQuery m_query; ///< Query parameters std::string m_fragment; ///< The component after a `#` - + /// @brief Visitor to format the Host as a string struct HostVisitor { std::string operator()(std::string v) const { return v; } - + std::string operator()(boost::asio::ip::address v) const { return v.to_string(); } }; - + /// @brief Get the host as a string /// @return the host std::string getHost() const { return std::visit(HostVisitor(), m_host); } /// @brief Get the port as a string /// @return the port std::string getService() const { return boost::lexical_cast(getPort()); } - + /// @brief Get the path and the query portion of the URL /// @return the path and query std::string getTarget() const @@ -949,7 +949,7 @@ namespace mtconnect { else return m_path; } - + /// @brief Format a target using the existing host and port to make a request /// @param device an optional device /// @param operation the operation (probe,sample,current, or asset) @@ -961,7 +961,7 @@ namespace mtconnect { UrlQuery uq {m_query}; if (!query.empty()) uq.merge(query); - + std::stringstream path; path << m_path; if (m_path[m_path.size() - 1] != '/') @@ -972,10 +972,10 @@ namespace mtconnect { path << operation; if (uq.size() > 0) path << '?' << uq.join(); - + return path.str(); } - + int getPort() const { if (m_port) @@ -987,7 +987,7 @@ namespace mtconnect { else return 0; } - + /// @brief Format the URL as text /// @param device optional device to add to the URL /// @return formatted URL @@ -999,7 +999,7 @@ namespace mtconnect { url << *device; return url.str(); } - + /// @brief parse a string to a Url /// @return parsed URL static Url parse(const std::string_view &url); diff --git a/test_package/agent_test.cpp b/test_package/agent_test.cpp index 2a889c87a..41a60bf69 100644 --- a/test_package/agent_test.cpp +++ b/test_package/agent_test.cpp @@ -63,7 +63,7 @@ class AgentTest : public testing::Test public: typedef std::map map_type; using queue_type = list; - + protected: void SetUp() override { @@ -71,19 +71,19 @@ class AgentTest : public testing::Test m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "1.3", 25, true); m_agentId = to_string(getCurrentTimeInSec()); } - + void TearDown() override { m_agentTestHelper.reset(); } - + void addAdapter(ConfigOptions options = ConfigOptions {}) { m_agentTestHelper->addAdapter(options, "localhost", 7878, m_agentTestHelper->m_agent->getDefaultDevice()->getName()); } - + public: std::string m_agentId; std::unique_ptr m_agentTestHelper; - + std::chrono::milliseconds m_delay {}; }; @@ -91,18 +91,18 @@ TEST_F(AgentTest, Constructor) { using namespace configuration; ConfigOptions options {{BufferSize, 17}, {MaxAssets, 8}, {SchemaVersion, "1.7"s}}; - + unique_ptr agent = make_unique(m_agentTestHelper->m_ioContext, TEST_RESOURCE_DIR "/samples/badPath.xml", options); auto context = std::make_shared(); context->m_contract = agent->makePipelineContract(); - - ASSERT_THROW(agent->initialize(context), std::runtime_error); + + ASSERT_THROW(agent->initialize(context), FatalException); agent.reset(); - + agent = make_unique(m_agentTestHelper->m_ioContext, TEST_RESOURCE_DIR "/samples/test_config.xml", options); - + context = std::make_shared(); context->m_contract = agent->makePipelineContract(); ASSERT_NO_THROW(agent->initialize(context)); @@ -114,17 +114,17 @@ TEST_F(AgentTest, Probe) PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Devices/m:Device@name", "LinuxCNC"); } - + { PARSE_XML_RESPONSE("/"); ASSERT_XML_PATH_EQUAL(doc, "//m:Devices/m:Device@name", "LinuxCNC"); } - + { PARSE_XML_RESPONSE("/LinuxCNC"); ASSERT_XML_PATH_EQUAL(doc, "//m:Devices/m:Device@name", "LinuxCNC"); } - + { PARSE_XML_RESPONSE("/LinuxCNC/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Devices/m:Device@name", "LinuxCNC"); @@ -135,13 +135,13 @@ TEST_F(AgentTest, FailWithDuplicateDeviceUUID) { using namespace configuration; ConfigOptions options {{BufferSize, 17}, {MaxAssets, 8}, {SchemaVersion, "1.5"s}}; - + unique_ptr agent = make_unique(m_agentTestHelper->m_ioContext, TEST_RESOURCE_DIR "/samples/dup_uuid.xml", options); auto context = std::make_shared(); context->m_contract = agent->makePipelineContract(); - - ASSERT_THROW(agent->initialize(context), std::runtime_error); + + ASSERT_THROW(agent->initialize(context), FatalException); } TEST_F(AgentTest, should_return_error_for_unknown_device) @@ -159,7 +159,7 @@ TEST_F(AgentTest, should_return_2_6_error_for_unknown_device) { auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, false, true, {{configuration::Validation, false}}); - + { PARSE_XML_RESPONSE("/LinuxCN/probe"); string message = (string) "Could not find the device 'LinuxCN'"; @@ -180,7 +180,7 @@ TEST_F(AgentTest, should_return_error_when_path_cannot_be_parsed) ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "INVALID_XPATH"); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", message.c_str()); } - + { QueryMap query {{"path", "//Axes?//Linear"}}; PARSE_XML_RESPONSE_QUERY("/current", query); @@ -188,7 +188,7 @@ TEST_F(AgentTest, should_return_error_when_path_cannot_be_parsed) ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "INVALID_XPATH"); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", message.c_str()); } - + { QueryMap query {{"path", "//Devices/Device[@name=\"I_DON'T_EXIST\""}}; PARSE_XML_RESPONSE_QUERY("/current", query); @@ -203,7 +203,7 @@ TEST_F(AgentTest, should_return_2_6_error_when_path_cannot_be_parsed) { m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, false, true, {{configuration::Validation, false}}); - + { QueryMap query {{"path", "//////Linear"}}; PARSE_XML_RESPONSE_QUERY("/current", query); @@ -212,7 +212,7 @@ TEST_F(AgentTest, should_return_2_6_error_when_path_cannot_be_parsed) ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidXPath/m:ErrorMessage", message.c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidXPath/m:URI", "/current?path=//////Linear"); } - + { QueryMap query {{"path", "//Axes?//Linear"}}; PARSE_XML_RESPONSE_QUERY("/current", query); @@ -221,7 +221,7 @@ TEST_F(AgentTest, should_return_2_6_error_when_path_cannot_be_parsed) ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidXPath/m:ErrorMessage", message.c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidXPath/m:URI", "/current?path=//Axes?//Linear"); } - + { QueryMap query {{"path", "//Devices/Device[@name=\"I_DON'T_EXIST\""}}; PARSE_XML_RESPONSE_QUERY("/current", query); @@ -243,12 +243,12 @@ TEST_F(AgentTest, should_handle_a_correct_path) "UNAVAILABLE"); ASSERT_XML_PATH_COUNT(doc, "//m:ComponentStream", 1); } - + { QueryMap query { - {"path", "//Rotary[@name='C']//DataItem[@category='SAMPLE' or @category='CONDITION']"}}; + {"path", "//Rotary[@name='C']//DataItem[@category='SAMPLE' or @category='CONDITION']"}}; PARSE_XML_RESPONSE_QUERY("/current", query); - + ASSERT_XML_PATH_EQUAL(doc, "//m:ComponentStream[@component='Rotary']//m:SpindleSpeed", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:ComponentStream[@component='Rotary']//m:Load", "UNAVAILABLE"); @@ -266,7 +266,7 @@ TEST_F(AgentTest, should_report_an_invalid_uri) EXPECT_EQ(status::not_found, m_agentTestHelper->session()->m_code); EXPECT_FALSE(m_agentTestHelper->m_dispatched); } - + { PARSE_XML_RESPONSE("/bad/path/"); ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "INVALID_URI"); @@ -274,7 +274,7 @@ TEST_F(AgentTest, should_report_an_invalid_uri) EXPECT_EQ(status::not_found, m_agentTestHelper->session()->m_code); EXPECT_FALSE(m_agentTestHelper->m_dispatched); } - + { PARSE_XML_RESPONSE("/LinuxCNC/current/blah"); ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "INVALID_URI"); @@ -288,10 +288,10 @@ TEST_F(AgentTest, should_report_an_invalid_uri) TEST_F(AgentTest, should_report_a_2_6_invalid_uri) { using namespace rest_sink; - + m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, false, true, {{configuration::Validation, false}}); - + { PARSE_XML_RESPONSE("/bad_path"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidURI@errorCode", "INVALID_URI"); @@ -301,25 +301,25 @@ TEST_F(AgentTest, should_report_a_2_6_invalid_uri) EXPECT_EQ(status::not_found, m_agentTestHelper->session()->m_code); EXPECT_FALSE(m_agentTestHelper->m_dispatched); } - + { PARSE_XML_RESPONSE("/bad/path/"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidURI@errorCode", "INVALID_URI"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidURI/m:ErrorMessage", "0.0.0.0: Cannot find handler for: GET /bad/path/"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidURI/m:URI", "/bad/path/"); - + EXPECT_EQ(status::not_found, m_agentTestHelper->session()->m_code); EXPECT_FALSE(m_agentTestHelper->m_dispatched); } - + { PARSE_XML_RESPONSE("/LinuxCNC/current/blah"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidURI@errorCode", "INVALID_URI"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidURI/m:ErrorMessage", "0.0.0.0: Cannot find handler for: GET /LinuxCNC/current/blah"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidURI/m:URI", "/LinuxCNC/current/blah"); - + EXPECT_EQ(status::not_found, m_agentTestHelper->session()->m_code); EXPECT_FALSE(m_agentTestHelper->m_dispatched); } @@ -329,21 +329,21 @@ TEST_F(AgentTest, should_handle_current_at) { QueryMap query; PARSE_XML_RESPONSE_QUERY("/current", query); - + addAdapter(); - + // Get the current position auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); auto seq = circ.getSequence(); char line[80] = {0}; - + // Add many events for (int i = 1; i <= 100; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d", i); m_agentTestHelper->m_adapter->processData(line); } - + // Check each current at all the positions. for (int i = 0; i < 100; i++) { @@ -354,7 +354,7 @@ TEST_F(AgentTest, should_handle_current_at) // m_agentTestHelper->printsession(); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line", to_string(i + 1).c_str()); } - + // Test buffer wrapping // Add a large many events for (int i = 101; i <= 301; i++) @@ -362,7 +362,7 @@ TEST_F(AgentTest, should_handle_current_at) sprintf(line, "2021-02-01T12:00:00Z|line|%d", i); m_agentTestHelper->m_adapter->processData(line); } - + // Check each current at all the positions. for (int i = 100; i < 301; i++) { @@ -371,7 +371,7 @@ TEST_F(AgentTest, should_handle_current_at) PARSE_XML_RESPONSE_QUERY("/current", query); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line", to_string(i + 1).c_str()); } - + // Check the first couple of items in the list for (int j = 0; j < 10; j++) { @@ -381,7 +381,7 @@ TEST_F(AgentTest, should_handle_current_at) PARSE_XML_RESPONSE_QUERY("/current", query); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line", to_string(i + 1).c_str()); } - + // Test out of range... { auto i = circ.getSequence() - circ.getBufferSize() - seq - 1; @@ -397,25 +397,25 @@ TEST_F(AgentTest, should_handle_current_at) TEST_F(AgentTest, should_handle_64_bit_current_at) { QueryMap query; - + addAdapter(); - + // Get the current position char line[80] = {0}; - + // Initialize the sliding buffer at a very large number. auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); - + uint64_t start = (((uint64_t)1) << 48) + 1317; circ.setSequence(start); - + // Add many events for (int i = 1; i <= 500; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d", i); m_agentTestHelper->m_adapter->processData(line); } - + // Check each current at all the positions. for (uint64_t i = start + 300; i < start + 500; i++) { @@ -429,22 +429,22 @@ TEST_F(AgentTest, should_handle_64_bit_current_at) TEST_F(AgentTest, should_report_out_of_range_for_current_at) { QueryMap query; - + addAdapter(); - + // Get the current position char line[80] = {0}; - + // Add many events for (int i = 1; i <= 200; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d", i); m_agentTestHelper->m_adapter->processData(line); } - + auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); auto seq = circ.getSequence(); - + { query["at"] = to_string(seq); sprintf(line, "'at' must be less than %d", int32_t(seq)); @@ -452,9 +452,9 @@ TEST_F(AgentTest, should_report_out_of_range_for_current_at) ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "OUT_OF_RANGE"); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", line); } - + seq = circ.getFirstSequence() - 1; - + { query["at"] = to_string(seq); sprintf(line, "'at' must be greater than %d", int32_t(seq)); @@ -468,21 +468,21 @@ TEST_F(AgentTest, should_report_2_6_out_of_range_for_current_at) { m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, false, true, {{configuration::Validation, false}}); - + QueryMap query; - + addAdapter(); - + // Get the current position char line[80] = {0}; - + // Add many events for (int i = 1; i <= 200; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d", i); m_agentTestHelper->m_adapter->processData(line); } - + auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); auto seq = circ.getSequence(); auto max = seq - 1; @@ -500,9 +500,9 @@ TEST_F(AgentTest, should_report_2_6_out_of_range_for_current_at) ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Minimum", "1"); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Maximum", to_string(max).c_str()); } - + seq = circ.getFirstSequence() - 1; - + { query["at"] = to_string(seq); sprintf(line, "'at' must be greater than 0"); @@ -523,15 +523,15 @@ TEST_F(AgentTest, AddAdapter) { addAdapter(); } TEST_F(AgentTest, should_download_file) { QueryMap query; - + string uri("/schemas/MTConnectDevices_1.1.xsd"); - + // Register a file with the agent. auto rest = m_agentTestHelper->getRestService(); rest->getFileCache()->setMaxCachedFileSize(100 * 1024); rest->getFileCache()->registerFile( - uri, string(PROJECT_ROOT_DIR "/schemas/MTConnectDevices_1.1.xsd"), "1.1"); - + uri, string(PROJECT_ROOT_DIR "/schemas/MTConnectDevices_1.1.xsd"), "1.1"); + // Reqyest the file... PARSE_TEXT_RESPONSE(uri.c_str()); ASSERT_FALSE(m_agentTestHelper->session()->m_body.empty()); @@ -542,13 +542,13 @@ TEST_F(AgentTest, should_download_file) TEST_F(AgentTest, should_report_not_found_when_cannot_find_file) { QueryMap query; - + string uri("/schemas/MTConnectDevices_1.1.xsd"); - + // Register a file with the agent. auto rest = m_agentTestHelper->getRestService(); rest->getFileCache()->registerFile(uri, string("./BadFileName.xsd"), "1.1"); - + { PARSE_XML_RESPONSE(uri.c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectError/m:Errors/m:Error@errorCode", "INVALID_URI"); @@ -561,15 +561,15 @@ TEST_F(AgentTest, should_report_2_6_not_found_when_cannot_find_file) { m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, false, true, {{configuration::Validation, false}}); - + QueryMap query; - + string uri("/schemas/MTConnectDevices_1.1.xsd"); - + // Register a file with the agent. auto rest = m_agentTestHelper->getRestService(); rest->getFileCache()->registerFile(uri, string("./BadFileName.xsd"), "1.1"); - + { PARSE_XML_RESPONSE(uri.c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidURI@errorCode", "INVALID_URI"); @@ -583,20 +583,20 @@ TEST_F(AgentTest, should_include_composition_ids_in_observations) { auto agent = m_agentTestHelper->m_agent.get(); addAdapter(); - + DataItemPtr motor = agent->getDataItemForDevice("LinuxCNC", "zt1"); ASSERT_TRUE(motor); - + DataItemPtr amp = agent->getDataItemForDevice("LinuxCNC", "zt2"); ASSERT_TRUE(amp); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|zt1|100|zt2|200"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Temperature[@dataItemId='zt1']", "100"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Temperature[@dataItemId='zt2']", "200"); - + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Temperature[@dataItemId='zt1']@compositionId", "zmotor"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Temperature[@dataItemId='zt2']@compositionId", @@ -616,7 +616,7 @@ TEST_F(AgentTest, should_report_an_error_when_the_count_is_out_of_range) "query parameter 'count': cannot convert " "string 'NON_INTEGER' to integer"); } - + { QueryMap query {{"count", "-500"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); @@ -625,14 +625,14 @@ TEST_F(AgentTest, should_report_an_error_when_the_count_is_out_of_range) value += to_string(-size); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", value.c_str()); } - + { QueryMap query {{"count", "0"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "OUT_OF_RANGE"); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", "'count' must not be zero(0)"); } - + { QueryMap query {{"count", "500"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); @@ -641,7 +641,7 @@ TEST_F(AgentTest, should_report_an_error_when_the_count_is_out_of_range) value += to_string(size); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", value.c_str()); } - + { QueryMap query {{"count", "9999999"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); @@ -650,7 +650,7 @@ TEST_F(AgentTest, should_report_an_error_when_the_count_is_out_of_range) value += to_string(size); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", value.c_str()); } - + { QueryMap query {{"count", "-9999999"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); @@ -665,7 +665,7 @@ TEST_F(AgentTest, should_report_a_2_6_error_when_the_count_is_out_of_range) { m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, false, true, {{configuration::Validation, false}}); - + auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); int size = circ.getBufferSize() + 1; { @@ -681,7 +681,7 @@ TEST_F(AgentTest, should_report_a_2_6_error_when_the_count_is_out_of_range) ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidParameterValue/m:QueryParameter/m:Type", "integer"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidParameterValue/m:QueryParameter/m:Value", "NON_INTEGER"); } - + { QueryMap query {{"count", "-500"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); @@ -698,7 +698,7 @@ TEST_F(AgentTest, should_report_a_2_6_error_when_the_count_is_out_of_range) ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Minimum", to_string(-size + 1).c_str()); } - + { QueryMap query {{"count", "0"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); @@ -712,13 +712,13 @@ TEST_F(AgentTest, should_report_a_2_6_error_when_the_count_is_out_of_range) ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Minimum", to_string(-size + 1).c_str()); } - + { QueryMap query {{"count", "500"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); string value("'count' must be less than "); value += to_string(size); - + ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange@errorCode", "OUT_OF_RANGE"); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:ErrorMessage", value.c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:URI", "/sample?count=500"); @@ -729,13 +729,13 @@ TEST_F(AgentTest, should_report_a_2_6_error_when_the_count_is_out_of_range) ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Minimum", to_string(-size + 1).c_str()); } - + { QueryMap query {{"count", "9999999"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); string value("'count' must be less than "); value += to_string(size); - + ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange@errorCode", "OUT_OF_RANGE"); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:ErrorMessage", value.c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:URI", "/sample?count=9999999"); @@ -746,13 +746,13 @@ TEST_F(AgentTest, should_report_a_2_6_error_when_the_count_is_out_of_range) ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Minimum", to_string(-size + 1).c_str()); } - + { QueryMap query {{"count", "-9999999"}}; PARSE_XML_RESPONSE_QUERY("/sample", query); string value("'count' must be greater than "); value += to_string(-size); - + ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange@errorCode", "OUT_OF_RANGE"); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:ErrorMessage", value.c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:URI", "/sample?count=-9999999"); @@ -768,24 +768,24 @@ TEST_F(AgentTest, should_report_a_2_6_error_when_the_count_is_out_of_range) TEST_F(AgentTest, should_process_addapter_data) { addAdapter(); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[2]", "204"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Alarm[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|alarm|code|nativeCode|severity|state|description"); - + "2021-02-01T12:00:00Z|alarm|code|nativeCode|severity|state|description"); + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); @@ -799,17 +799,17 @@ TEST_F(AgentTest, should_get_samples_using_next_sequence) { QueryMap query; addAdapter(); - + // Get the current position char line[80] = {0}; - + // Add many events for (int i = 1; i <= 300; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d", i); m_agentTestHelper->m_adapter->processData(line); } - + auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); auto seq = circ.getSequence(); { @@ -825,27 +825,27 @@ TEST_F(AgentTest, should_give_correct_number_of_samples_with_count) addAdapter(); auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); auto seq = circ.getSequence(); - + // Get the current position char line[80] = {0}; - + // Add many events for (int i = 0; i < 128; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d|Xact|%d", i, i); m_agentTestHelper->m_adapter->processData(line); } - + { query["path"] = "//DataItem[@name='Xact']"; query["from"] = to_string(seq); query["count"] = "10"; - + PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@nextSequence", to_string(seq + 20).c_str()); - + ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Position", 10); - + // Make sure we got 10 lines for (int j = 0; j < 10; j++) { @@ -859,29 +859,29 @@ TEST_F(AgentTest, should_give_correct_number_of_samples_with_negative_count) { QueryMap query; addAdapter(); - + // Get the current position char line[80] = {0}; - + // Add many events for (int i = 0; i < 128; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d|Xact|%d", i, i); m_agentTestHelper->m_adapter->processData(line); } - + auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); auto seq = circ.getSequence() - 20; - + { query["path"] = "//DataItem[@name='Xact']"; query["count"] = "-10"; - + PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@nextSequence", to_string(seq).c_str()); - + ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Position", 10); - + // Make sure we got 10 lines for (int j = 0; j < 10; j++) { @@ -895,30 +895,30 @@ TEST_F(AgentTest, should_give_correct_number_of_samples_with_to_parameter) { QueryMap query; addAdapter(); - + // Get the current position char line[80] = {0}; - + // Add many events for (int i = 0; i < 128; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d|Xact|%d", i, i); m_agentTestHelper->m_adapter->processData(line); } - + auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); auto seq = circ.getSequence() - 20; - + { query["path"] = "//DataItem[@name='Xact']"; query["count"] = "10"; query["to"] = to_string(seq); - + PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@nextSequence", to_string(seq + 1).c_str()); - + ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Position", 10); - + // Make sure we got 10 lines auto start = seq - 20; for (int j = 0; j < 10; j++) @@ -927,18 +927,18 @@ TEST_F(AgentTest, should_give_correct_number_of_samples_with_to_parameter) ASSERT_XML_PATH_EQUAL(doc, line, to_string(start + j * 2 + 1).c_str()); } } - + { query["path"] = "//DataItem[@name='Xact']"; query["count"] = "10"; query["to"] = to_string(seq); query["from"] = to_string(seq - 10); - + PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@nextSequence", to_string(seq + 1).c_str()); - + ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Position", 5); - + // Make sure we got 10 lines auto start = seq - 10; for (int j = 0; j < 5; j++) @@ -947,7 +947,7 @@ TEST_F(AgentTest, should_give_correct_number_of_samples_with_to_parameter) ASSERT_XML_PATH_EQUAL(doc, line, to_string(start + j * 2 + 1).c_str()); } } - + // TODO: Test negative conditions // count < 0 // to > nextSequence @@ -963,11 +963,11 @@ TEST_F(AgentTest, should_give_empty_stream_with_no_new_samples) ASSERT_XML_PATH_EQUAL(doc, "//m:ComponentStream[@componentId='path']/m:Condition/m:Unavailable", nullptr); ASSERT_XML_PATH_EQUAL( - doc, "//m:ComponentStream[@componentId='path']/m:Condition/m:Unavailable@qualifier", - nullptr); + doc, "//m:ComponentStream[@componentId='path']/m:Condition/m:Unavailable@qualifier", + nullptr); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:RotaryMode", "SPINDLE"); } - + { auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); QueryMap query {{"from", to_string(circ.getSequence())}}; @@ -980,31 +980,31 @@ TEST_F(AgentTest, should_not_leak_observations_when_added_to_buffer) { auto agent = m_agentTestHelper->m_agent.get(); QueryMap query; - + string device("LinuxCNC"), key("badKey"), value("ON"); SequenceNumber_t seqNum {0}; auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); auto event1 = circ.getFromBuffer(seqNum); ASSERT_FALSE(event1); - + { query["from"] = to_string(circ.getSequence()); PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:Streams", nullptr); } - + key = "power"; - + auto di2 = agent->getDataItemForDevice(device, key); seqNum = m_agentTestHelper->addToBuffer(di2, {{"VALUE", value}}, chrono::system_clock::now()); auto event2 = circ.getFromBuffer(seqNum); ASSERT_EQ(3, event2.use_count()); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:PowerState", "ON"); } - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:PowerState[1]", "UNAVAILABLE"); @@ -1017,53 +1017,53 @@ TEST_F(AgentTest, should_int_64_sequences_should_not_truncate_at_32_bits) #ifndef WIN32 QueryMap query; addAdapter(); - + // Set the sequence number near MAX_UINT32 auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); circ.setSequence(0xFFFFFFA0); SequenceNumber_t seq = circ.getSequence(); ASSERT_EQ((int64_t)0xFFFFFFA0, seq); - + // Get the current position char line[80] = {0}; - + // Add many events for (int i = 0; i < 128; i++) { sprintf(line, "2021-02-01T12:00:00Z|line|%d", i); m_agentTestHelper->m_adapter->processData(line); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line@sequence", to_string(seq + i).c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@nextSequence", to_string(seq + i + 1).c_str()); } - + { query["from"] = to_string(seq); query["count"] = "128"; - + PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@nextSequence", to_string(seq + i + 1).c_str()); - + for (int j = 0; j <= i; j++) { sprintf(line, "//m:DeviceStream//m:Line[%d]@sequence", j + 1); ASSERT_XML_PATH_EQUAL(doc, line, to_string(seq + j).c_str()); } } - + for (int j = 0; j <= i; j++) { query["from"] = to_string(seq + j); query["count"] = "1"; - + PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line@sequence", to_string(seq + j).c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@nextSequence", to_string(seq + j + 1).c_str()); } } - + ASSERT_EQ(uint64_t(0xFFFFFFA0) + 128ul, circ.getSequence()); #endif } @@ -1071,22 +1071,22 @@ TEST_F(AgentTest, should_int_64_sequences_should_not_truncate_at_32_bits) TEST_F(AgentTest, should_not_allow_duplicates_values) { addAdapter(); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[2]", "204"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|205"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); @@ -1098,20 +1098,20 @@ TEST_F(AgentTest, should_not_allow_duplicates_values) TEST_F(AgentTest, should_not_duplicate_unavailable_when_disconnected) { addAdapter({{configuration::FilterDuplicates, true}}); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|205"); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[2]", "204"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[3]", "205"); } - + m_agentTestHelper->m_adapter->disconnected(); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); @@ -1119,11 +1119,11 @@ TEST_F(AgentTest, should_not_duplicate_unavailable_when_disconnected) ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[3]", "205"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[4]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->connected(); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|205"); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); @@ -1143,31 +1143,31 @@ TEST_F(AgentTest, should_handle_auto_available_if_adapter_option_is_set) auto d = agent->getDevices().front(); StringList devices; devices.emplace_back(*d->getComponentName()); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Availability[1]", "UNAVAILABLE"); } - + agent->connected(id, devices, true); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Availability[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Availability[2]", "AVAILABLE"); } - + agent->disconnected(id, devices, true); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Availability[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Availability[2]", "AVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Availability[3]", "UNAVAILABLE"); } - + agent->connected(id, devices, true); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Availability[1]", "UNAVAILABLE"); @@ -1183,66 +1183,66 @@ TEST_F(AgentTest, should_handle_multiple_disconnnects) auto agent = m_agentTestHelper->m_agent.get(); auto adapter = m_agentTestHelper->m_adapter; auto id = adapter->getIdentity(); - + auto d = agent->getDevices().front(); StringList devices; devices.emplace_back(*d->getComponentName()); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//*[@dataItemId='p1'][1]", "UNAVAILABLE"); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Unavailable[@dataItemId='cmp']", 1); } - + agent->connected(id, devices, false); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|block|GTH"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|cmp|normal||||"); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//*[@dataItemId='p1'][1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//*[@dataItemId='p1'][2]", "GTH"); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//*[@dataItemId='p1']", 2); - + ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Unavailable[@dataItemId='cmp']", 1); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Normal[@dataItemId='cmp']", 1); } - + agent->disconnected(id, devices, false); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Unavailable[@dataItemId='cmp']", 2); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Normal[@dataItemId='cmp']", 1); - + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//*[@dataItemId='p1'][2]", "GTH"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//*[@dataItemId='p1'][3]", "UNAVAILABLE"); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//*[@dataItemId='p1']", 3); } - + agent->disconnected(id, devices, false); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Unavailable[@dataItemId='cmp']", 2); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Normal[@dataItemId='cmp']", 1); - + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//*[@dataItemId='p1'][3]", "UNAVAILABLE"); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//*[@dataItemId='p1']", 3); } - + agent->connected(id, devices, false); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|block|GTH"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|cmp|normal||||"); - + agent->disconnected(id, devices, false); - + { PARSE_XML_RESPONSE("/LinuxCNC/sample"); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Unavailable[@dataItemId='cmp']", 3); ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//m:Normal[@dataItemId='cmp']", 2); - + ASSERT_XML_PATH_COUNT(doc, "//m:DeviceStream//*[@dataItemId='p1']", 5); } } @@ -1250,18 +1250,18 @@ TEST_F(AgentTest, should_handle_multiple_disconnnects) TEST_F(AgentTest, should_ignore_timestamps_if_configured_to_do_so) { addAdapter(); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[2]@timestamp", "2021-02-01T12:00:00Z"); } - + m_agentTestHelper->m_adapter->setOptions({{configuration::IgnoreTimestamps, true}}); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|205"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); @@ -1273,7 +1273,7 @@ TEST_F(AgentTest, should_ignore_timestamps_if_configured_to_do_so) TEST_F(AgentTest, InitialTimeSeriesValues) { addAdapter(); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:PositionTimeSeries[@dataItemId='x1ts']", @@ -1285,41 +1285,41 @@ TEST_F(AgentTest, should_support_dynamic_calibration_data) { addAdapter({{configuration::ConversionRequired, true}}); auto agent = m_agentTestHelper->getAgent(); - + // Add a 10.111000 seconds m_agentTestHelper->m_adapter->protocolCommand( - "* calibration:Yact|.01|200.0|Zact|0.02|300|Xts|0.01|500"); + "* calibration:Yact|.01|200.0|Zact|0.02|300|Xts|0.01|500"); auto di = agent->getDataItemForDevice("LinuxCNC", "Yact"); ASSERT_TRUE(di); - + // TODO: Fix conversions auto &conv1 = di->getConverter(); ASSERT_TRUE(conv1); ASSERT_EQ(0.01, conv1->factor()); ASSERT_EQ(200.0, conv1->offset()); - + di = agent->getDataItemForDevice("LinuxCNC", "Zact"); ASSERT_TRUE(di); - + auto &conv2 = di->getConverter(); ASSERT_TRUE(conv2); ASSERT_EQ(0.02, conv2->factor()); ASSERT_EQ(300.0, conv2->offset()); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|Yact|200|Zact|600"); m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|Xts|25|| 5118 5118 5118 5118 5118 5118 5118 5118 5118 5118 5118 5118 " - "5119 5119 5118 " - "5118 5117 5117 5119 5119 5118 5118 5118 5118 5118"); - + "2021-02-01T12:00:00Z|Xts|25|| 5118 5118 5118 5118 5118 5118 5118 5118 5118 5118 5118 5118 " + "5119 5119 5118 " + "5118 5117 5117 5119 5119 5118 5118 5118 5118 5118"); + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[@dataItemId='y1']", "4"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[@dataItemId='z1']", "18"); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:PositionTimeSeries[@dataItemId='x1ts']", - "56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.19 56.19 56.18 " - "56.18 56.17 56.17 56.19 56.19 56.18 56.18 56.18 56.18 56.18"); + doc, "//m:DeviceStream//m:PositionTimeSeries[@dataItemId='x1ts']", + "56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.18 56.19 56.19 56.18 " + "56.18 56.17 56.17 56.19 56.19 56.18 56.18 56.18 56.18 56.18"); } } @@ -1327,32 +1327,32 @@ TEST_F(AgentTest, should_filter_as_specified_in_1_3_test_1) { m_agentTestHelper->createAgent("/samples/filter_example_1.3.xml", 8, 4, "1.5", 25); addAdapter(); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|load|100"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[2]", "100"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|load|103"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|load|106"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[2]", "100"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[3]", "106"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|load|106|load|108|load|112"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); @@ -1366,15 +1366,15 @@ TEST_F(AgentTest, should_filter_as_specified_in_1_3_test_2) { m_agentTestHelper->createAgent("/samples/filter_example_1.3.xml", 8, 4, "1.5", 25); addAdapter(); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2018-04-27T05:00:26.555666|load|100|pos|20"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); @@ -1382,10 +1382,10 @@ TEST_F(AgentTest, should_filter_as_specified_in_1_3_test_2) ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[2]", "20"); } - + m_agentTestHelper->m_adapter->processData("2018-04-27T05:00:32.000666|load|103|pos|25"); m_agentTestHelper->m_adapter->processData("2018-04-27T05:00:36.888666|load|106|pos|30"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); @@ -1395,10 +1395,10 @@ TEST_F(AgentTest, should_filter_as_specified_in_1_3_test_2) ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[2]", "20"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[3]", "30"); } - + m_agentTestHelper->m_adapter->processData( - "2018-04-27T05:00:40.25|load|106|load|108|load|112|pos|35|pos|40"); - + "2018-04-27T05:00:40.25|load|106|load|108|load|112|pos|35|pos|40"); + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); @@ -1409,9 +1409,9 @@ TEST_F(AgentTest, should_filter_as_specified_in_1_3_test_2) ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[2]", "20"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[3]", "30"); } - + m_agentTestHelper->m_adapter->processData("2018-04-27T05:00:47.50|pos|45|pos|50"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Load[1]", "UNAVAILABLE"); @@ -1431,24 +1431,24 @@ TEST_F(AgentTest, period_filter_should_work_with_ignore_timestamps) // Test period filter with ignore timestamps m_agentTestHelper->createAgent("/samples/filter_example_1.3.xml", 8, 4, "1.5", 25); addAdapter({{configuration::IgnoreTimestamps, true}}); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2018-04-27T05:00:26.555666|load|100|pos|20"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[2]", "20"); } - + m_agentTestHelper->m_adapter->processData("2018-04-27T05:01:32.000666|load|103|pos|25"); this_thread::sleep_for(11s); m_agentTestHelper->m_adapter->processData("2018-04-27T05:01:40.888666|load|106|pos|30"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[1]", "UNAVAILABLE"); @@ -1462,23 +1462,23 @@ TEST_F(AgentTest, period_filter_should_work_with_relative_time) // Test period filter with relative time m_agentTestHelper->createAgent("/samples/filter_example_1.3.xml", 8, 4, "1.5", 25); addAdapter({{configuration::RelativeTime, true}}); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("0|load|100|pos|20"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[2]", "20"); } - + m_agentTestHelper->m_adapter->processData("5000|load|103|pos|25"); m_agentTestHelper->m_adapter->processData("11000|load|106|pos|30"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Position[1]", "UNAVAILABLE"); @@ -1490,13 +1490,13 @@ TEST_F(AgentTest, period_filter_should_work_with_relative_time) TEST_F(AgentTest, reset_triggered_should_work) { addAdapter(); - + m_agentTestHelper->m_adapter->processData("TIME1|pcount|0"); m_agentTestHelper->m_adapter->processData("TIME2|pcount|1"); m_agentTestHelper->m_adapter->processData("TIME3|pcount|2"); m_agentTestHelper->m_adapter->processData("TIME4|pcount|0:DAY"); m_agentTestHelper->m_adapter->processData("TIME3|pcount|5"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:PartCount[1]", "UNAVAILABLE"); @@ -1513,58 +1513,58 @@ TEST_F(AgentTest, reset_triggered_should_work) TEST_F(AgentTest, should_honor_references_when_getting_current_or_sample) { using namespace device_model; - + m_agentTestHelper->createAgent("/samples/reference_example.xml"); addAdapter(); auto agent = m_agentTestHelper->getAgent(); - + string id = "mf"; auto item = agent->getDataItemForDevice((string) "LinuxCNC", id); auto comp = item->getComponent(); - + auto references = comp->getList("References"); ASSERT_TRUE(references); ASSERT_EQ(3, references->size()); auto reference = references->begin(); - + EXPECT_EQ("DataItemRef", (*reference)->getName()); - + EXPECT_EQ("chuck", (*reference)->get("name")); EXPECT_EQ("c4", (*reference)->get("idRef")); - + auto ref = dynamic_pointer_cast(*reference); ASSERT_TRUE(ref); - + ASSERT_EQ(Reference::DATA_ITEM, ref->getReferenceType()); ASSERT_TRUE(ref->getDataItem().lock()) << "DataItem was not resolved"; reference++; - + EXPECT_EQ("door", (*reference)->get("name")); EXPECT_EQ("d2", (*reference)->get("idRef")); - + ref = dynamic_pointer_cast(*reference); ASSERT_TRUE(ref); - + ASSERT_EQ(Reference::DATA_ITEM, ref->getReferenceType()); ASSERT_TRUE(ref->getDataItem().lock()) << "DataItem was not resolved"; - + reference++; EXPECT_EQ("electric", (*reference)->get("name")); EXPECT_EQ("ele", (*reference)->get("idRef")); - + ref = dynamic_pointer_cast(*reference); ASSERT_TRUE(ref); - + ASSERT_EQ(Reference::COMPONENT, ref->getReferenceType()); ASSERT_TRUE(ref->getComponent().lock()) << "DataItem was not resolved"; - + // Additional data items should be included { QueryMap query {{"path", "//BarFeederInterface"}}; PARSE_XML_RESPONSE_QUERY("/current", query); - + ASSERT_XML_PATH_EQUAL( - doc, "//m:ComponentStream[@component='BarFeederInterface']//m:MaterialFeed", "UNAVAILABLE"); + doc, "//m:ComponentStream[@component='BarFeederInterface']//m:MaterialFeed", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:ComponentStream[@component='Door']//m:DoorState", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:ComponentStream[@component='Rotary']//m:ChuckState", @@ -1577,34 +1577,34 @@ TEST_F(AgentTest, should_honor_discrete_data_items_and_not_filter_dups) m_agentTestHelper->createAgent("/samples/discrete_example.xml"); addAdapter({{configuration::FilterDuplicates, true}}); auto agent = m_agentTestHelper->getAgent(); - + auto msg = agent->getDataItemForDevice("LinuxCNC", "message"); ASSERT_TRUE(msg); ASSERT_EQ(true, msg->isDiscreteRep()); - + // Validate we are dup checking. { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|205"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[2]", "204"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[3]", "205"); - + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:MessageDiscrete[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|message|Hi|Hello"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|message|Hi|Hello"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|message|Hi|Hello"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:MessageDiscrete[1]", "UNAVAILABLE"); @@ -1619,17 +1619,17 @@ TEST_F(AgentTest, should_honor_discrete_data_items_and_not_filter_dups) TEST_F(AgentTest, should_honor_upcase_values) { addAdapter({{configuration::FilterDuplicates, true}, {configuration::UpcaseDataItemValue, true}}); - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|mode|Hello"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:ControllerMode", "HELLO"); } - + m_agentTestHelper->m_adapter->setOptions({{configuration::UpcaseDataItemValue, false}}); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|mode|Hello"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:ControllerMode", "Hello"); @@ -1642,7 +1642,7 @@ TEST_F(AgentTest, should_handle_condition_activation) auto agent = m_agentTestHelper->getAgent(); auto logic = agent->getDataItemForDevice("LinuxCNC", "lp"); ASSERT_TRUE(logic); - + // Validate we are dup checking. { PARSE_XML_RESPONSE("/current"); @@ -1652,29 +1652,29 @@ TEST_F(AgentTest, should_handle_condition_activation) "m:Unavailable[@dataItemId='lp']", 1); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|lp|NORMAL||||XXX"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Normal", - "XXX"); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Normal", + "XXX"); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); } - + m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|lp|FAULT|2218|ALARM_B|HIGH|2218-1 ALARM_B UNUSABLE G-code A side " - "FFFFFFFF"); - + "2021-02-01T12:00:00Z|lp|FAULT|2218|ALARM_B|HIGH|2218-1 ALARM_B UNUSABLE G-code A side " + "FFFFFFFF"); + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault", - "2218-1 ALARM_B UNUSABLE G-code A side FFFFFFFF"); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault", + "2218-1 ALARM_B UNUSABLE G-code A side FFFFFFFF"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//" "m:ComponentStream[@component='Controller']/m:Condition/" @@ -1691,28 +1691,28 @@ TEST_F(AgentTest, should_handle_condition_activation) "m:Fault@qualifier", "HIGH"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|lp|NORMAL||||"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Normal", - 1); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Normal", + 1); } - + m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|lp|FAULT|4200|ALARM_D||4200 ALARM_D Power on effective parameter set"); - + "2021-02-01T12:00:00Z|lp|FAULT|4200|ALARM_D||4200 ALARM_D Power on effective parameter set"); + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault", - "4200 ALARM_D Power on effective parameter set"); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault", + "4200 ALARM_D Power on effective parameter set"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//" "m:ComponentStream[@component='Controller']/m:Condition/" @@ -1724,21 +1724,21 @@ TEST_F(AgentTest, should_handle_condition_activation) "m:Fault@nativeSeverity", "ALARM_D"); } - + m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|lp|FAULT|2218|ALARM_B|HIGH|2218-1 ALARM_B UNUSABLE G-code A side " - "FFFFFFFF"); - + "2021-02-01T12:00:00Z|lp|FAULT|2218|ALARM_B|HIGH|2218-1 ALARM_B UNUSABLE G-code A side " + "FFFFFFFF"); + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 2); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 2); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[1]", - "4200 ALARM_D Power on effective parameter set"); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[1]", + "4200 ALARM_D Power on effective parameter set"); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[2]", - "2218-1 ALARM_B UNUSABLE G-code A side FFFFFFFF"); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[2]", + "2218-1 ALARM_B UNUSABLE G-code A side FFFFFFFF"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//" "m:ComponentStream[@component='Controller']/m:Condition/" @@ -1755,18 +1755,18 @@ TEST_F(AgentTest, should_handle_condition_activation) "m:Fault[2]@qualifier", "HIGH"); } - + m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|lp|FAULT|4200|ALARM_D|LOW|4200 ALARM_D Power on effective parameter " - "set"); - + "2021-02-01T12:00:00Z|lp|FAULT|4200|ALARM_D|LOW|4200 ALARM_D Power on effective parameter " + "set"); + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 2); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 2); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[1]", - "2218-1 ALARM_B UNUSABLE G-code A side FFFFFFFF"); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[1]", + "2218-1 ALARM_B UNUSABLE G-code A side FFFFFFFF"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//" "m:ComponentStream[@component='Controller']/m:Condition/" @@ -1783,35 +1783,35 @@ TEST_F(AgentTest, should_handle_condition_activation) "m:Fault[1]@qualifier", "HIGH"); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[2]", - "4200 ALARM_D Power on effective parameter set"); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[2]", + "4200 ALARM_D Power on effective parameter set"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|lp|NORMAL|2218|||"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//" "m:ComponentStream[@component='Controller']/m:Condition/" "m:Fault[1]@nativeCode", "4200"); ASSERT_XML_PATH_EQUAL( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[1]", - "4200 ALARM_D Power on effective parameter set"); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Fault[1]", + "4200 ALARM_D Power on effective parameter set"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|lp|NORMAL||||"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/*", 1); ASSERT_XML_PATH_COUNT( - doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Normal", - 1); + doc, "//m:DeviceStream//m:ComponentStream[@component='Controller']/m:Condition/m:Normal", + 1); } } @@ -1819,55 +1819,55 @@ TEST_F(AgentTest, should_handle_empty_entry_as_last_pair_from_adapter) { addAdapter({{configuration::FilterDuplicates, true}}); auto agent = m_agentTestHelper->getAgent(); - + auto program = agent->getDataItemForDevice("LinuxCNC", "program"); ASSERT_TRUE(program); - + auto tool_id = agent->getDataItemForDevice("LinuxCNC", "block"); ASSERT_TRUE(tool_id); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Program", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Block", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|program|A|block|B"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Program", "A"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Block", "B"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|program||block|B"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Program", ""); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Block", "B"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|program||block|"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Program", ""); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Block", ""); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|program|A|block|B"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|program|A|block|"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Program", "A"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Block", ""); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|program|A|block|B|line|C"); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|program|D|block||line|E"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Program", "D"); @@ -1882,17 +1882,17 @@ TEST_F(AgentTest, should_handle_constant_values) auto agent = m_agentTestHelper->getAgent(); auto di = agent->getDataItemForDevice("LinuxCNC", "block"); ASSERT_TRUE(di); - + di->setConstantValue("UNAVAILABLE"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Block[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|block|G01X00|Smode|INDEX|line|204"); - + "2021-02-01T12:00:00Z|block|G01X00|Smode|INDEX|line|204"); + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Block[1]", "UNAVAILABLE"); @@ -1906,14 +1906,14 @@ TEST_F(AgentTest, should_handle_constant_values) TEST_F(AgentTest, should_handle_bad_data_item_from_adapter) { addAdapter(); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|bad|ignore|dummy|1244|line|204"); - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Line[1]", "UNAVAILABLE"); @@ -1927,18 +1927,18 @@ TEST_F(AgentTest, adapter_should_receive_commands) { addAdapter(); auto agent = m_agentTestHelper->getAgent(); - + auto device = agent->getDeviceByName("LinuxCNC"); ASSERT_TRUE(device); ASSERT_FALSE(device->preserveUuid()); - + m_agentTestHelper->m_adapter->parseBuffer("* uuid: MK-1234\n"); m_agentTestHelper->m_ioContext.run_for(2000ms); - + m_agentTestHelper->m_adapter->parseBuffer("* manufacturer: Big Tool\n"); m_agentTestHelper->m_adapter->parseBuffer("* serialNumber: XXXX-1234\n"); m_agentTestHelper->m_adapter->parseBuffer("* station: YYYY\n"); - + { PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Device@uuid", "MK-1234"); @@ -1946,19 +1946,19 @@ TEST_F(AgentTest, adapter_should_receive_commands) ASSERT_XML_PATH_EQUAL(doc, "//m:Description@serialNumber", "XXXX-1234"); ASSERT_XML_PATH_EQUAL(doc, "//m:Description@station", "YYYY"); } - + device = agent->getDeviceByName("LinuxCNC"); ASSERT_TRUE(device); - + device->setPreserveUuid(true); m_agentTestHelper->m_adapter->parseBuffer("* uuid: XXXXXXX\n"); m_agentTestHelper->m_ioContext.run_for(1000ms); - + { PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Device@uuid", "MK-1234"); } - + auto &options = m_agentTestHelper->m_adapter->getOptions(); ASSERT_EQ("MK-1234", *GetOption(options, configuration::Device)); } @@ -1968,21 +1968,21 @@ TEST_F(AgentTest, adapter_should_not_process_uuid_command_with_preserve_uuid) auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.3", 4, false, true, {{configuration::PreserveUUID, true}}); addAdapter(); - + auto device = agent->getDeviceByName("LinuxCNC"); ASSERT_TRUE(device); ASSERT_TRUE(device->preserveUuid()); - + m_agentTestHelper->m_adapter->parseBuffer("* uuid: MK-1234\n"); - + { PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Device@uuid", "000"); } - + m_agentTestHelper->m_adapter->processData( - "2021-02-01T12:00:00Z|block|G01X00|mode|AUTOMATIC|execution|READY"); - + "2021-02-01T12:00:00Z|block|G01X00|mode|AUTOMATIC|execution|READY"); + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:Block", "G01X00"); @@ -1995,37 +1995,37 @@ TEST_F(AgentTest, adapter_should_receive_device_commands) { m_agentTestHelper->createAgent("/samples/two_devices.xml"); auto agent = m_agentTestHelper->getAgent(); - + auto device1 = agent->getDeviceByName("Device1"); ASSERT_TRUE(device1); auto device2 = agent->getDeviceByName("Device2"); ASSERT_TRUE(device2); - + addAdapter(); - + auto device = - GetOption(m_agentTestHelper->m_adapter->getOptions(), configuration::Device); + GetOption(m_agentTestHelper->m_adapter->getOptions(), configuration::Device); ASSERT_EQ(device1->getComponentName(), device); - + m_agentTestHelper->m_adapter->parseBuffer("* device: device-2\n"); device = GetOption(m_agentTestHelper->m_adapter->getOptions(), configuration::Device); ASSERT_EQ(string(*device2->getUuid()), device); - + m_agentTestHelper->m_adapter->parseBuffer("* uuid: new-uuid\n"); - + device2 = agent->getDeviceByName("Device2"); ASSERT_TRUE(device2); - + ASSERT_EQ("new-uuid", string(*device2->getUuid())); - + m_agentTestHelper->m_adapter->parseBuffer("* device: device-1\n"); device = GetOption(m_agentTestHelper->m_adapter->getOptions(), configuration::Device); ASSERT_EQ(string(*device1->getUuid()), device); - + m_agentTestHelper->m_adapter->parseBuffer("* uuid: another-uuid\n"); device1 = agent->getDeviceByName("Device1"); ASSERT_TRUE(device1); - + ASSERT_EQ("another-uuid", string(*device1->getUuid())); } @@ -2033,19 +2033,19 @@ TEST_F(AgentTest, adapter_command_should_set_adapter_and_mtconnect_versions) { m_agentTestHelper->createAgent("/samples/kinematics.xml", 8, 4, "1.7", 25); addAdapter(); - + auto printer = m_agentTestHelper->m_agent->getPrinter("xml"); ASSERT_FALSE(printer->getModelChangeTime().empty()); - + { PARSE_XML_RESPONSE("/Agent/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:AdapterSoftwareVersion", "UNAVAILABLE"); ASSERT_XML_PATH_EQUAL(doc, "//m:MTConnectVersion", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->parseBuffer("* adapterVersion: 2.10\n"); m_agentTestHelper->m_adapter->parseBuffer("* mtconnectVersion: 1.7\n"); - + { PARSE_XML_RESPONSE("/Agent/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:AdapterSoftwareVersion", "2.10"); @@ -2054,24 +2054,24 @@ TEST_F(AgentTest, adapter_command_should_set_adapter_and_mtconnect_versions) printer->getModelChangeTime().c_str()); ; } - + // Test updating device change time string old = printer->getModelChangeTime(); m_agentTestHelper->m_adapter->parseBuffer("* uuid: another-uuid\n"); ASSERT_GT(printer->getModelChangeTime(), old); - + { PARSE_XML_RESPONSE("/Agent/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@deviceModelChangeTime", printer->getModelChangeTime().c_str()); ; } - + // Test Case insensitivity - + m_agentTestHelper->m_adapter->parseBuffer("* adapterversion: 3.10\n"); m_agentTestHelper->m_adapter->parseBuffer("* mtconnectversion: 1.6\n"); - + { PARSE_XML_RESPONSE("/Agent/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:AdapterSoftwareVersion", "3.10"); @@ -2085,14 +2085,14 @@ TEST_F(AgentTest, should_handle_uuid_change) auto device = agent->getDeviceByName("LinuxCNC"); ASSERT_TRUE(device); ASSERT_FALSE(device->preserveUuid()); - + addAdapter(); - + m_agentTestHelper->m_adapter->parseBuffer("* uuid: MK-1234\n"); m_agentTestHelper->m_adapter->parseBuffer("* manufacturer: Big Tool\n"); m_agentTestHelper->m_adapter->parseBuffer("* serialNumber: XXXX-1234\n"); m_agentTestHelper->m_adapter->parseBuffer("* station: YYYY\n"); - + { PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Device@uuid", "MK-1234"); @@ -2100,11 +2100,11 @@ TEST_F(AgentTest, should_handle_uuid_change) ASSERT_XML_PATH_EQUAL(doc, "//m:Description@serialNumber", "XXXX-1234"); ASSERT_XML_PATH_EQUAL(doc, "//m:Description@station", "YYYY"); } - + auto *pipe = static_cast(m_agentTestHelper->m_adapter->getPipeline()); - + ASSERT_EQ("MK-1234", pipe->getDevice()); - + { // TODO: Fix and make sure dom is updated so this xpath will parse correctly. // PARSE_XML_RESPONSE("/current?path=//Device[@uuid=\"MK-1234\"]"); @@ -2120,7 +2120,7 @@ TEST_F(AgentTest, should_handle_uuid_change) TEST_F(AgentTest, interval_should_be_a_valid_integer_value) { QueryMap query; - + /// - Cannot be test or a non-integer value { query["interval"] = "NON_INTEGER"; @@ -2130,7 +2130,7 @@ TEST_F(AgentTest, interval_should_be_a_valid_integer_value) "query parameter 'interval': cannot " "convert string 'NON_INTEGER' to integer"); } - + /// - Cannot be nagative { query["interval"] = "-123"; @@ -2138,7 +2138,7 @@ TEST_F(AgentTest, interval_should_be_a_valid_integer_value) ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "OUT_OF_RANGE"); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", "'interval' must be greater than -1"); } - + /// - Cannot be >= 2147483647 { query["interval"] = "2147483647"; @@ -2146,7 +2146,7 @@ TEST_F(AgentTest, interval_should_be_a_valid_integer_value) ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "OUT_OF_RANGE"); ASSERT_XML_PATH_EQUAL(doc, "//m:Error", "'interval' must be less than 2147483647"); } - + /// - Cannot wrap around and create a negative number was set as a int32 { query["interval"] = "999999999999999999"; @@ -2163,7 +2163,7 @@ TEST_F(AgentTest, interval_should_be_a_valid_integer_value_in_2_6) m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.6", 4, false, true, {{configuration::Validation, false}}); QueryMap query; - + /// - Cannot be test or a non-integer value { query["interval"] = "NON_INTEGER"; @@ -2178,7 +2178,7 @@ TEST_F(AgentTest, interval_should_be_a_valid_integer_value_in_2_6) ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidParameterValue/m:QueryParameter/m:Type", "integer"); ASSERT_XML_PATH_EQUAL(doc, "//m:InvalidParameterValue/m:QueryParameter/m:Value", "NON_INTEGER"); } - + /// - Cannot be nagative { query["interval"] = "-123"; @@ -2192,13 +2192,13 @@ TEST_F(AgentTest, interval_should_be_a_valid_integer_value_in_2_6) ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Minimum", "0"); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Maximum", "2147483646"); } - + /// - Cannot be >= 2147483647 { query["interval"] = "2147483647"; PARSE_XML_RESPONSE_QUERY("/sample", query); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange@errorCode", "OUT_OF_RANGE"); - + ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:URI", "/sample?interval=2147483647"); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:ErrorMessage", "'interval' must be less than 2147483647"); @@ -2207,7 +2207,7 @@ TEST_F(AgentTest, interval_should_be_a_valid_integer_value_in_2_6) ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Minimum", "0"); ASSERT_XML_PATH_EQUAL(doc, "//m:OutOfRange/m:QueryParameter/m:Maximum", "2147483646"); } - + /// - Cannot wrap around and create a negative number was set as a int32 { query["interval"] = "999999999999999999"; @@ -2231,18 +2231,18 @@ TEST_F(AgentTest, should_stream_data_with_interval) auto rest = m_agentTestHelper->getRestService(); auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); rest->start(); - + // Start a thread... QueryMap query; query["interval"] = "50"; query["heartbeat"] = to_string(heartbeatFreq.count()); query["from"] = to_string(circ.getSequence()); - + // Heartbeat test. Heartbeat should be sent in 200ms. Give // 25ms range. { auto slop {35ms}; - + auto startTime = system_clock::now(); PARSE_XML_STREAM_QUERY("/LinuxCNC/sample", query); while (m_agentTestHelper->m_session->m_chunkBody.empty() && @@ -2251,34 +2251,34 @@ TEST_F(AgentTest, should_stream_data_with_interval) auto delta = system_clock::now() - startTime; cout << "Delta after heartbeat: " << delta.count() << endl; ASSERT_FALSE(m_agentTestHelper->m_session->m_chunkBody.empty()); - + PARSE_XML_CHUNK(); ASSERT_XML_PATH_EQUAL(doc, "//m:Streams", nullptr); EXPECT_GT((heartbeatFreq + slop), delta) - << "delta " << delta.count() << " < hbf " << (heartbeatFreq + slop).count(); + << "delta " << delta.count() << " < hbf " << (heartbeatFreq + slop).count(); EXPECT_LT(heartbeatFreq, delta) << "delta > hbf: " << delta.count(); - + m_agentTestHelper->m_session->closeStream(); } - + // Set some data and make sure we get data within 40ms. // Again, allow for some slop. { auto delay {40ms}; auto slop {35ms}; - + PARSE_XML_STREAM_QUERY("/LinuxCNC/sample", query); m_agentTestHelper->m_ioContext.run_for(delay); - + auto startTime = system_clock::now(); m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); m_agentTestHelper->m_ioContext.run_for(5ms); auto delta = system_clock::now() - startTime; cout << "Delta after data: " << delta.count() << endl; - + ASSERT_FALSE(m_agentTestHelper->m_session->m_chunkBody.empty()); PARSE_XML_CHUNK(); - + auto deltaMS = duration_cast(delta); EXPECT_GT(slop, deltaMS) << "delta " << deltaMS.count() << " < delay " << slop.count(); } @@ -2290,9 +2290,9 @@ TEST_F(AgentTest, should_signal_observer_when_observations_arrive) addAdapter(); auto rest = m_agentTestHelper->getRestService(); rest->start(); - + auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); - + /// - Set up streaming every 100ms with a 1000ms heartbeat std::map query; query["interval"] = "100"; @@ -2300,7 +2300,7 @@ TEST_F(AgentTest, should_signal_observer_when_observations_arrive) query["count"] = "10"; query["from"] = to_string(circ.getSequence()); query["path"] = "//DataItem[@name='line']"; - + /// - Test to make sure the signal will push the sequence number forward and capture /// the new data. { @@ -2312,7 +2312,7 @@ TEST_F(AgentTest, should_signal_observer_when_observations_arrive) } m_agentTestHelper->m_adapter->processData("2021-02-01T12:00:00Z|line|204"); m_agentTestHelper->m_ioContext.run_for(200ms); - + PARSE_XML_CHUNK(); ASSERT_XML_PATH_EQUAL(doc, "//m:Line@sequence", seq.c_str()); } @@ -2324,9 +2324,9 @@ TEST_F(AgentTest, should_fail_if_from_is_out_of_range) addAdapter(); auto rest = m_agentTestHelper->getRestService(); rest->start(); - + auto &circ = m_agentTestHelper->getAgent()->getCircularBuffer(); - + // Start a thread... std::map query; query["interval"] = "100"; @@ -2334,14 +2334,14 @@ TEST_F(AgentTest, should_fail_if_from_is_out_of_range) query["count"] = "10"; query["from"] = to_string(circ.getSequence() + 5); query["path"] = "//DataItem[@name='line']"; - + // Test to make sure the signal will push the sequence number forward and capture // the new data. { PARSE_XML_RESPONSE_QUERY("/LinuxCNC/sample", query); auto seq = to_string(circ.getSequence() + 20ull); m_agentTestHelper->m_ioContext.run_for(200ms); - + ASSERT_XML_PATH_EQUAL(doc, "//m:Error@errorCode", "OUT_OF_RANGE"); } } @@ -2356,18 +2356,18 @@ TEST_F(AgentTest, should_fail_if_from_is_out_of_range) TEST_F(AgentTest, should_allow_making_observations_via_http_put) { m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "1.3", 4, true); - + QueryMap queries; string body; - + queries["time"] = "2021-02-01T12:00:00Z"; queries["line"] = "205"; queries["power"] = "ON"; - + { PARSE_XML_RESPONSE_PUT("/LinuxCNC", body, queries); } - + { PARSE_XML_RESPONSE("/LinuxCNC/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:Line@timestamp", "2021-02-01T12:00:00Z"); @@ -2380,17 +2380,17 @@ TEST_F(AgentTest, should_allow_making_observations_via_http_put) TEST_F(AgentTest, put_condition_should_parse_condition_data) { m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "1.3", 4, true); - + QueryMap queries; string body; - + queries["time"] = "2021-02-01T12:00:00Z"; queries["lp"] = "FAULT|2001|1||SCANHISTORYRESET"; - + { PARSE_XML_RESPONSE_PUT("/LinuxCNC", body, queries); } - + { PARSE_XML_RESPONSE("/LinuxCNC/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:Fault@timestamp", "2021-02-01T12:00:00Z"); @@ -2403,7 +2403,7 @@ TEST_F(AgentTest, put_condition_should_parse_condition_data) TEST_F(AgentTest, shound_add_asset_count_when_20) { m_agentTestHelper->createAgent("/samples/min_config.xml", 8, 4, "2.0", 25); - + { PARSE_XML_RESPONSE("/LinuxCNC/probe"); ASSERT_XML_PATH_COUNT(doc, "//m:DataItem[@type='ASSET_CHANGED']", 1); @@ -2423,7 +2423,7 @@ TEST_F(AgentTest, pre_start_hook_should_be_called) }; m_agentTestHelper->setAgentCreateHook(helperHook); auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.0", 4, true); - + ASSERT_FALSE(called); agent->start(); ASSERT_TRUE(called); @@ -2439,7 +2439,7 @@ TEST_F(AgentTest, pre_initialize_hooks_should_be_called) }; m_agentTestHelper->setAgentCreateHook(helperHook); m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.0", 4, true); - + ASSERT_TRUE(called); } @@ -2452,7 +2452,7 @@ TEST_F(AgentTest, post_initialize_hooks_should_be_called) }; m_agentTestHelper->setAgentCreateHook(helperHook); m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.0", 4, true); - + ASSERT_TRUE(called); } @@ -2465,7 +2465,7 @@ TEST_F(AgentTest, pre_stop_hook_should_be_called) }; m_agentTestHelper->setAgentCreateHook(helperHook); auto agent = m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.0", 4, true); - + ASSERT_FALSE(called); agent->start(); ASSERT_FALSE(called); @@ -2476,24 +2476,24 @@ TEST_F(AgentTest, pre_stop_hook_should_be_called) TEST_F(AgentTest, device_should_have_hash_for_2_2) { m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.2", 4, true); - + auto device = m_agentTestHelper->getAgent()->getDeviceByName("LinuxCNC"); ASSERT_TRUE(device); - + auto hash = device->get("hash"); ASSERT_EQ(28, hash.length()); - + { PARSE_XML_RESPONSE("/LinuxCNC/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Device@hash", hash.c_str()); } - + auto devices = m_agentTestHelper->getAgent()->getDevices(); auto di = devices.begin(); - + { PARSE_XML_RESPONSE("/Agent/sample"); - + ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceAdded[2]@hash", (*di++)->get("hash").c_str()); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceAdded[3]@hash", (*di)->get("hash").c_str()); } @@ -2502,19 +2502,19 @@ TEST_F(AgentTest, device_should_have_hash_for_2_2) TEST_F(AgentTest, should_not_add_spaces_to_output) { addAdapter(); - + m_agentTestHelper->m_adapter->processData("2024-01-22T20:00:00Z|program|"); m_agentTestHelper->m_adapter->processData("2024-01-22T20:00:00Z|block|"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Program", ""); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Block", ""); } - + m_agentTestHelper->m_adapter->processData( - "2024-01-22T20:00:00Z|program| |block| "); - + "2024-01-22T20:00:00Z|program| |block| "); + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:Program", ""); @@ -2531,7 +2531,7 @@ TEST_F(AgentTest, should_set_sender_from_config_in_XML_header) PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@sender", "MachineXXX"); } - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@sender", "MachineXXX"); @@ -2547,12 +2547,12 @@ TEST_F(AgentTest, should_not_set_validation_flag_in_header_when_validation_is_fa PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", nullptr); } - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", nullptr); } - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", nullptr); @@ -2568,12 +2568,12 @@ TEST_F(AgentTest, should_set_validation_flag_in_header_when_version_2_5_validati PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", "true"); } - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", "true"); } - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", "true"); @@ -2589,12 +2589,12 @@ TEST_F(AgentTest, should_not_set_validation_flag_in_header_when_version_below_2_ PARSE_XML_RESPONSE("/probe"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", nullptr); } - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", nullptr); } - + { PARSE_XML_RESPONSE("/sample"); ASSERT_XML_PATH_EQUAL(doc, "//m:Header@validation", nullptr); @@ -2604,19 +2604,19 @@ TEST_F(AgentTest, should_not_set_validation_flag_in_header_when_version_below_2_ TEST_F(AgentTest, should_initialize_observaton_to_initial_value_when_available) { m_agentTestHelper->createAgent("/samples/test_config.xml", 8, 4, "2.2", 4, true); - + auto device = m_agentTestHelper->getAgent()->getDeviceByName("LinuxCNC"); ASSERT_TRUE(device); - + addAdapter(); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:PartCount", "UNAVAILABLE"); } - + m_agentTestHelper->m_adapter->processData("2024-01-22T20:00:00Z|avail|AVAILABLE"); - + { PARSE_XML_RESPONSE("/current"); ASSERT_XML_PATH_EQUAL(doc, "//m:DeviceStream//m:PartCount", "0"); diff --git a/test_package/http_server_test.cpp b/test_package/http_server_test.cpp index ff73298c3..81c0a1c56 100644 --- a/test_package/http_server_test.cpp +++ b/test_package/http_server_test.cpp @@ -243,8 +243,9 @@ class Client cout << "spawnRequest: done: false" << endl; m_done = false; m_count = 0; - asio::spawn(m_context, std::bind(&Client::request, this, verb, target, body, close, contentType, - std::placeholders::_1), + asio::spawn(m_context, + std::bind(&Client::request, this, verb, target, body, close, contentType, + std::placeholders::_1), boost::asio::detached); while (!m_done && m_context.run_for(20ms) > 0) @@ -311,8 +312,7 @@ class HttpServerTest : public testing::Test asio::spawn(m_context, std::bind(&Client::connect, m_client.get(), static_cast(m_server->getPort()), std::placeholders::_1), - boost::asio::detached); - + boost::asio::detached); while (!m_client->m_connected) m_context.run_one(); diff --git a/test_package/json_printer_probe_test.cpp b/test_package/json_printer_probe_test.cpp index 1c2a1cb65..2aae84e22 100644 --- a/test_package/json_printer_probe_test.cpp +++ b/test_package/json_printer_probe_test.cpp @@ -362,7 +362,8 @@ TEST_F(JsonPrinterProbeTest, PrintDataItemRelationships) auto dir2 = load.at("/Relationships/1"_json_pointer); ASSERT_TRUE(dir2.is_object()); ASSERT_EQ(string("LIMIT"), dir2.at("/SpecificationRelationship/type"_json_pointer).get()); - ASSERT_EQ(string("spec1"), dir2.at("/SpecificationRelationship/idRef"_json_pointer).get()); + ASSERT_EQ(string("spec1"), + dir2.at("/SpecificationRelationship/idRef"_json_pointer).get()); auto limits = linear.at("/DataItems/5/DataItem"_json_pointer); ASSERT_TRUE(load.is_object()); @@ -371,7 +372,8 @@ TEST_F(JsonPrinterProbeTest, PrintDataItemRelationships) auto dir3 = limits.at("/Relationships/0"_json_pointer); ASSERT_TRUE(dir3.is_object()); ASSERT_EQ(string("bob"), dir3.at("/DataItemRelationship/name"_json_pointer).get()); - ASSERT_EQ(string("OBSERVATION"), dir3.at("/DataItemRelationship/type"_json_pointer).get()); + ASSERT_EQ(string("OBSERVATION"), + dir3.at("/DataItemRelationship/type"_json_pointer).get()); ASSERT_EQ(string("xlc"), dir3.at("/DataItemRelationship/idRef"_json_pointer).get()); } diff --git a/test_package/tls_http_server_test.cpp b/test_package/tls_http_server_test.cpp index 13a047719..262e25081 100644 --- a/test_package/tls_http_server_test.cpp +++ b/test_package/tls_http_server_test.cpp @@ -252,8 +252,10 @@ class Client cout << "spawnRequest: done: false" << endl; m_done = false; m_count = 0; - asio::spawn(m_context, std::bind(&Client::request, this, verb, target, body, close, contentType, - std::placeholders::_1), boost::asio::detached); + asio::spawn(m_context, + std::bind(&Client::request, this, verb, target, body, close, contentType, + std::placeholders::_1), + boost::asio::detached); while (!m_done && !m_failed && m_context.run_for(20ms) > 0) ; diff --git a/test_package/websockets_test.cpp b/test_package/websockets_test.cpp index 07044401f..91b7a0e0f 100644 --- a/test_package/websockets_test.cpp +++ b/test_package/websockets_test.cpp @@ -241,8 +241,9 @@ TEST_F(WebsocketsTest, should_make_simple_request) start(); startClient(); - asio::spawn(m_context, std::bind(&Client::request, m_client.get(), - "{\"id\":\"1\",\"request\":\"probe\"}"s, std::placeholders::_1), + asio::spawn(m_context, + std::bind(&Client::request, m_client.get(), "{\"id\":\"1\",\"request\":\"probe\"}"s, + std::placeholders::_1), boost::asio::detached); m_client->waitFor(2s, [this]() { return m_client->m_done; }); @@ -270,8 +271,9 @@ TEST_F(WebsocketsTest, should_return_error_when_there_is_no_id) start(); startClient(); - asio::spawn(m_context, std::bind(&Client::request, m_client.get(), "{\"request\":\"probe\"}"s, - std::placeholders::_1), + asio::spawn(m_context, + std::bind(&Client::request, m_client.get(), "{\"request\":\"probe\"}"s, + std::placeholders::_1), boost::asio::detached); m_client->waitFor(2s, [this]() { return m_client->m_done; }); @@ -362,8 +364,7 @@ TEST_F(WebsocketsTest, should_return_error_when_bad_json_is_sent) start(); startClient(); - asio::spawn(m_context, - std::bind(&Client::request, m_client.get(), "!}}"s, std::placeholders::_1), + asio::spawn(m_context, std::bind(&Client::request, m_client.get(), "!}}"s, std::placeholders::_1), boost::asio::detached); m_client->waitFor(2s, [this]() { return m_client->m_done; }); @@ -399,7 +400,7 @@ TEST_F(WebsocketsTest, should_return_multiple_errors_when_parameters_are_invalid std::bind(&Client::request, m_client.get(), R"DOC({"id": 3, "request": "sample", "interval": 99999999999,"to": -1 })DOC", std::placeholders::_1), - boost::asio::detached); + boost::asio::detached); m_client->waitFor(2s, [this]() { return m_client->m_done; }); diff --git a/test_package/xml_parser_test.cpp b/test_package/xml_parser_test.cpp index 88e61545a..f2735bead 100644 --- a/test_package/xml_parser_test.cpp +++ b/test_package/xml_parser_test.cpp @@ -87,7 +87,7 @@ TEST_F(XmlParserTest, Constructor) std::unique_ptr printer(new printer::XmlPrinter()); m_xmlParser = new parser::XmlParser(); ASSERT_THROW(m_xmlParser->parseFile(TEST_RESOURCE_DIR "/samples/badPath.xml", printer.get()), - std::runtime_error); + FatalException); delete m_xmlParser; m_xmlParser = nullptr; m_xmlParser = new parser::XmlParser(); From 587310623df58fadc46b6e89b07e67e7461846e9 Mon Sep 17 00:00:00 2001 From: Will Sobel Date: Fri, 31 Oct 2025 16:54:06 +0100 Subject: [PATCH 3/3] Version 2.6.0.4 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d1220305c..c89d74038 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ set(AGENT_VERSION_MAJOR 2) set(AGENT_VERSION_MINOR 6) set(AGENT_VERSION_PATCH 0) -set(AGENT_VERSION_BUILD 3) +set(AGENT_VERSION_BUILD 4) set(AGENT_VERSION_RC "") # This minimum version is to support Visual Studio 2019 and C++ feature checking and FetchContent