From 311fb7bfa493acd19ab9e89727c30533f4f858cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Tue, 3 Dec 2024 16:20:02 +0100 Subject: [PATCH 1/7] Introduce interfaces mechanism for ThingClass paramTypes --- libnymea/integrations/pluginmetadata.cpp | 66 +++++++++++++++++-- libnymea/integrations/pluginmetadata.h | 2 +- libnymea/integrations/thingutils.cpp | 40 ++++++++++- libnymea/libnymea.pro | 2 + libnymea/types/interface.cpp | 16 +++-- libnymea/types/interface.h | 7 +- libnymea/types/interfaceactiontype.cpp | 30 +++++++++ libnymea/types/interfaceactiontype.h | 30 +++++++++ libnymea/types/interfaceeventtype.cpp | 30 +++++++++ libnymea/types/interfaceeventtype.h | 30 +++++++++ libnymea/types/interfaceparamtype.cpp | 64 ++++++++++++++++++ libnymea/types/interfaceparamtype.h | 58 ++++++++++++++++ libnymea/types/interfacestatetype.cpp | 30 +++++++++ libnymea/types/interfacestatetype.h | 30 +++++++++ libnymea/types/paramtype.cpp | 34 +++++++++- libnymea/types/paramtype.h | 6 +- .../nymea-plugininfocompiler.pro | 2 + 17 files changed, 461 insertions(+), 16 deletions(-) create mode 100644 libnymea/types/interfaceparamtype.cpp create mode 100644 libnymea/types/interfaceparamtype.h diff --git a/libnymea/integrations/pluginmetadata.cpp b/libnymea/integrations/pluginmetadata.cpp index 2051bc41..3bf5faca 100644 --- a/libnymea/integrations/pluginmetadata.cpp +++ b/libnymea/integrations/pluginmetadata.cpp @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2020, nymea GmbH +* Copyright 2013 - 2024, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. @@ -202,9 +202,9 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) QJsonObject thingClassObject = thingClassJson.toObject(); /*! Returns a list of all valid JSON properties a ThingClass JSON definition can have. */ QStringList thingClassProperties = QStringList() << "id" << "name" << "displayName" << "createMethods" << "setupMethod" - << "interfaces" << "providedInterfaces" << "browsable" << "discoveryParamTypes" - << "paramTypes" << "settingsTypes" << "stateTypes" << "actionTypes" << "eventTypes" << "browserItemActionTypes" - << "discoveryType"; + << "interfaces" << "providedInterfaces" << "browsable" << "discoveryParamTypes" + << "paramTypes" << "settingsTypes" << "stateTypes" << "actionTypes" << "eventTypes" << "browserItemActionTypes" + << "discoveryType"; QStringList mandatoryThingClassProperties = QStringList() << "id" << "name" << "displayName"; QPair verificationResult = verifyFields(thingClassProperties, mandatoryThingClassProperties, thingClassObject); @@ -703,6 +703,64 @@ void PluginMetadata::parse(const QJsonObject &jsonObject) continue; } + foreach (const InterfaceParamType &ifaceParamType, iface.paramTypes()) { + if (!thingClass.paramTypes().contains(ifaceParamType.name())) { + if (!ifaceParamType.optional()) { + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + + "\" but doesn't implement param \"" + ifaceParamType.name() + "\""); + hasError = true; + } + continue; + } + ParamType ¶mType = thingClass.paramTypes()[ifaceParamType.name()]; + if (ifaceParamType.type() != paramType.type()) { + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + + "\" but param \"" + paramType.name() + "\" has not matching type: \"" + + QVariant::typeToName(paramType.type()) + "\" != \"" + QVariant::typeToName(ifaceParamType.type()) + "\""); + hasError = true; + } + if (ifaceParamType.minValue().isValid() && !ifaceParamType.minValue().isNull()) { + if (ifaceParamType.minValue().toString() == "any") { + if (paramType.minValue().isNull()) { + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + + "\" but param \"" + paramType.name() + "\" has no minimum value defined."); + hasError = true; + } + } else if (ifaceParamType.minValue() != paramType.minValue()) { + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + + "\" but param \"" + paramType.name() + "\" has not matching minimum value: \"" + + ifaceParamType.minValue().toString() + "\" != \"" + paramType.minValue().toString() + "\""); + hasError = true; + } + } + if (ifaceParamType.maxValue().isValid() && !ifaceParamType.maxValue().isNull()) { + if (ifaceParamType.maxValue().toString() == "any") { + if (paramType.maxValue().isNull()) { + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + + "\" but param \"" + paramType.name() + "\" has no maximum value defined."); + hasError = true; + } + } else if (ifaceParamType.maxValue() != paramType.maxValue()) { + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + + "\" but param \"" + paramType.name() + "\" has not matching maximum value: \"" + + ifaceParamType.maxValue().toString() + "\" != \"" + paramType.minValue().toString() + "\""); + hasError = true; + } + } + if (!ifaceParamType.allowedValues().isEmpty() && ifaceParamType.allowedValues() != paramType.allowedValues()) { + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but param \"" + + paramType.name() + "\" has not matching allowed values."); + hasError = true; + } + if (ifaceParamType.unit() != Types::UnitNone && ifaceParamType.unit() != paramType.unit()) { + QMetaEnum unitEnum = QMetaEnum::fromType(); + m_validationErrors.append("Thing class \"" + thingClass.name() + "\" claims to implement interface \"" + value.toString() + "\" but param \"" + + paramType.name() + "\" has not matching unit: \"" + unitEnum.valueToKey(ifaceParamType.unit()) + "\" != \"" + unitEnum.valueToKey(paramType.unit())); + hasError = true; + } + } + + foreach (const InterfaceStateType &ifaceStateType, iface.stateTypes()) { if (!stateTypes.contains(ifaceStateType.name())) { if (!ifaceStateType.optional()) { diff --git a/libnymea/integrations/pluginmetadata.h b/libnymea/integrations/pluginmetadata.h index 0ba68cfc..2c10e59c 100644 --- a/libnymea/integrations/pluginmetadata.h +++ b/libnymea/integrations/pluginmetadata.h @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2020, nymea GmbH +* Copyright 2013 - 2024, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. diff --git a/libnymea/integrations/thingutils.cpp b/libnymea/integrations/thingutils.cpp index 832b6c7b..e1a40b02 100644 --- a/libnymea/integrations/thingutils.cpp +++ b/libnymea/integrations/thingutils.cpp @@ -189,9 +189,39 @@ Interface ThingUtils::loadInterface(const QString &name) } } + InterfaceParamTypes paramTypes; InterfaceStateTypes stateTypes; InterfaceActionTypes actionTypes; InterfaceEventTypes eventTypes; + + foreach (const QVariant ¶mVariant, content.value("params").toList()) { + QVariantMap paramMap = paramVariant.toMap(); + + InterfaceParamType paramType; + paramType.setName(paramMap.value("name").toString()); + paramType.setType(QVariant::nameToType(paramMap.value("type").toByteArray())); + paramType.setMinValue(paramMap.value("minValue")); + paramType.setMaxValue(paramMap.value("maxValue")); + paramType.setDefaultValue(paramMap.value("defaultValue")); + paramType.setAllowedValues(paramMap.value("allowedValues").toList()); + paramType.setReadOnly(paramMap.value("readOnly").toBool()); + //paramType.setOptional(paramMap.value("optional", false).toBool()); + + if (paramMap.contains("unit")) { + QMetaEnum unitEnum = QMetaEnum::fromType(); + int enumValue = unitEnum.keyToValue("Unit" + paramMap.value("unit").toByteArray()); + if (enumValue == -1) { + qCWarning(dcThingManager) << "Invalid unit" << paramMap.value("unit").toString() << "in interface" << name; + } else { + paramType.setUnit(static_cast(enumValue)); + } + } else { + paramType.setUnit(Types::UnitNone); + } + + paramTypes.append(paramType); + } + foreach (const QVariant &stateVariant, content.value("states").toList()) { QVariantMap stateMap = stateVariant.toMap(); InterfaceStateType stateType; @@ -279,11 +309,17 @@ Interface ThingUtils::loadInterface(const QString &name) eventTypes.append(eventType); } - return Interface(name, iface.actionTypes() << actionTypes, iface.eventTypes() << eventTypes, iface.stateTypes() << stateTypes); + return Interface(name, iface.paramTypes() << paramTypes, iface.actionTypes() << actionTypes, iface.eventTypes() << eventTypes, iface.stateTypes() << stateTypes); } Interface ThingUtils::mergeInterfaces(const Interface &iface1, const Interface &iface2) { + InterfaceParamTypes paramTypes = iface1.paramTypes(); + foreach (const InterfaceParamType &pt, iface2.paramTypes()) { + if (paramTypes.findByName(pt.name()).name().isEmpty()) { + paramTypes.append(pt); + } + } InterfaceEventTypes eventTypes = iface1.eventTypes(); foreach (const InterfaceEventType &et, iface2.eventTypes()) { if (eventTypes.findByName(et.name()).name().isEmpty()) { @@ -302,7 +338,7 @@ Interface ThingUtils::mergeInterfaces(const Interface &iface1, const Interface & actionTypes.append(at); } } - return Interface(QString(), actionTypes, eventTypes, stateTypes); + return Interface(QString(), paramTypes, actionTypes, eventTypes, stateTypes); } QStringList ThingUtils::generateInterfaceParentList(const QString &interface) diff --git a/libnymea/libnymea.pro b/libnymea/libnymea.pro index a8752900..c183dda8 100644 --- a/libnymea/libnymea.pro +++ b/libnymea/libnymea.pro @@ -72,6 +72,7 @@ HEADERS += \ types/browseraction.h \ types/interfaceactiontype.h \ types/interfaceeventtype.h \ + types/interfaceparamtype.h \ types/interfacestatetype.h \ types/mediabrowseritem.h \ types/thingclass.h \ @@ -214,6 +215,7 @@ SOURCES += \ types/browseraction.cpp \ types/interfaceactiontype.cpp \ types/interfaceeventtype.cpp \ + types/interfaceparamtype.cpp \ types/interfacestatetype.cpp \ types/mediabrowseritem.cpp \ types/action.cpp \ diff --git a/libnymea/types/interface.cpp b/libnymea/types/interface.cpp index 83d72e75..72febf8a 100644 --- a/libnymea/types/interface.cpp +++ b/libnymea/types/interface.cpp @@ -30,11 +30,12 @@ #include "interface.h" -Interface::Interface(const QString &name, const InterfaceActionTypes &actionTypes, const InterfaceEventTypes &eventTypes, const InterfaceStateTypes &stateTypes): - m_name(name), - m_actionTypes(actionTypes), - m_eventTypes(eventTypes), - m_stateTypes(stateTypes) +Interface::Interface(const QString &name, const InterfaceParamTypes ¶mTypes, const InterfaceActionTypes &actionTypes, const InterfaceEventTypes &eventTypes, const InterfaceStateTypes &stateTypes): + m_name{name}, + m_paramTypes{paramTypes}, + m_actionTypes{actionTypes}, + m_eventTypes{eventTypes}, + m_stateTypes{stateTypes} { } @@ -44,6 +45,11 @@ QString Interface::name() const return m_name; } +InterfaceParamTypes Interface::paramTypes() const +{ + return m_paramTypes; +} + InterfaceActionTypes Interface::actionTypes() const { return m_actionTypes; diff --git a/libnymea/types/interface.h b/libnymea/types/interface.h index 8715fdf0..b95b7da7 100644 --- a/libnymea/types/interface.h +++ b/libnymea/types/interface.h @@ -31,6 +31,7 @@ #ifndef INTERFACE_H #define INTERFACE_H +#include "interfaceparamtype.h" #include "interfaceeventtype.h" #include "interfaceactiontype.h" #include "interfacestatetype.h" @@ -38,18 +39,20 @@ class Interface{ public: Interface() = default; - Interface(const QString &name, const InterfaceActionTypes &actionTypes, const InterfaceEventTypes &eventTypes, const InterfaceStateTypes &stateTypes); + Interface(const QString &name, const InterfaceParamTypes ¶mTypes, const InterfaceActionTypes &actionTypes, const InterfaceEventTypes &eventTypes, const InterfaceStateTypes &stateTypes); QString name() const; + InterfaceParamTypes paramTypes() const; InterfaceActionTypes actionTypes() const; InterfaceEventTypes eventTypes() const; InterfaceStateTypes stateTypes() const; bool isValid() const; + private: - QUuid m_id; QString m_name; + InterfaceParamTypes m_paramTypes; InterfaceActionTypes m_actionTypes; InterfaceEventTypes m_eventTypes; InterfaceStateTypes m_stateTypes; diff --git a/libnymea/types/interfaceactiontype.cpp b/libnymea/types/interfaceactiontype.cpp index ef66d56e..e793181e 100644 --- a/libnymea/types/interfaceactiontype.cpp +++ b/libnymea/types/interfaceactiontype.cpp @@ -1,3 +1,33 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2024, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + #include "interfaceactiontype.h" InterfaceActionType::InterfaceActionType() diff --git a/libnymea/types/interfaceactiontype.h b/libnymea/types/interfaceactiontype.h index 1e650722..792c9890 100644 --- a/libnymea/types/interfaceactiontype.h +++ b/libnymea/types/interfaceactiontype.h @@ -1,3 +1,33 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2024, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + #ifndef INTERFACEACTIONTYPE_H #define INTERFACEACTIONTYPE_H diff --git a/libnymea/types/interfaceeventtype.cpp b/libnymea/types/interfaceeventtype.cpp index 6b1017d3..97338da3 100644 --- a/libnymea/types/interfaceeventtype.cpp +++ b/libnymea/types/interfaceeventtype.cpp @@ -1,3 +1,33 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2024, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + #include "interfaceeventtype.h" InterfaceEventType::InterfaceEventType() diff --git a/libnymea/types/interfaceeventtype.h b/libnymea/types/interfaceeventtype.h index 84eca98f..5dd369c7 100644 --- a/libnymea/types/interfaceeventtype.h +++ b/libnymea/types/interfaceeventtype.h @@ -1,3 +1,33 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2024, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + #ifndef INTERFACEEVENTTYPE_H #define INTERFACEEVENTTYPE_H diff --git a/libnymea/types/interfaceparamtype.cpp b/libnymea/types/interfaceparamtype.cpp new file mode 100644 index 00000000..b1b6e4fa --- /dev/null +++ b/libnymea/types/interfaceparamtype.cpp @@ -0,0 +1,64 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2024, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "interfaceparamtype.h" + + +InterfaceParamType::InterfaceParamType() +{ + +} + +bool InterfaceParamType::optional() const +{ + return m_optional; +} + +void InterfaceParamType::setOptional(bool optional) +{ + m_optional = optional; +} + + +InterfaceParamTypes::InterfaceParamTypes(const QList &other) + : QList(other) +{ + +} + +InterfaceParamType InterfaceParamTypes::findByName(const QString &name) +{ + foreach (const InterfaceParamType &ipt, *this) { + if (ipt.name() == name) { + return ipt; + } + } + return InterfaceParamType(); +} diff --git a/libnymea/types/interfaceparamtype.h b/libnymea/types/interfaceparamtype.h new file mode 100644 index 00000000..f88ac036 --- /dev/null +++ b/libnymea/types/interfaceparamtype.h @@ -0,0 +1,58 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2024, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef INTERFACEPARAMTYPE_H +#define INTERFACEPARAMTYPE_H + +#include "paramtype.h" + +class InterfaceParamType : public ParamType +{ +public: + InterfaceParamType(); + + bool optional() const; + void setOptional(bool optional); + +private: + bool m_optional = false; + +}; + +class InterfaceParamTypes: public QList +{ +public: + InterfaceParamTypes() = default; + InterfaceParamTypes(const QList &other); + InterfaceParamType findByName(const QString &name); +}; + + +#endif // INTERFACEPARAMTYPE_H diff --git a/libnymea/types/interfacestatetype.cpp b/libnymea/types/interfacestatetype.cpp index 02c72567..6a6f84aa 100644 --- a/libnymea/types/interfacestatetype.cpp +++ b/libnymea/types/interfacestatetype.cpp @@ -1,3 +1,33 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2024, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + #include "interfacestatetype.h" InterfaceStateType::InterfaceStateType() diff --git a/libnymea/types/interfacestatetype.h b/libnymea/types/interfacestatetype.h index 946ea404..ed0c9e4b 100644 --- a/libnymea/types/interfacestatetype.h +++ b/libnymea/types/interfacestatetype.h @@ -1,3 +1,33 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2024, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + #ifndef INTERFACESTATETYPE_H #define INTERFACESTATETYPE_H diff --git a/libnymea/types/paramtype.cpp b/libnymea/types/paramtype.cpp index f78eb6c0..e941a851 100644 --- a/libnymea/types/paramtype.cpp +++ b/libnymea/types/paramtype.cpp @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2020, nymea GmbH +* Copyright 2013 - 2024, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. @@ -264,6 +264,26 @@ ParamTypes::ParamTypes(const QList &other): QList(other) { } +bool ParamTypes::contains(const ParamTypeId ¶mTypeId) +{ + foreach (const ParamType ¶mType, *this) { + if (paramType.id() == paramTypeId) { + return true; + } + } + return false; +} + +bool ParamTypes::contains(const QString &name) +{ + foreach (const ParamType ¶mType, *this) { + if (paramType.name() == name) { + return true; + } + } + return false; +} + QVariant ParamTypes::get(int index) const { return QVariant::fromValue(at(index)); @@ -293,3 +313,15 @@ ParamType ParamTypes::findById(const ParamTypeId &id) const } return ParamType(); } + +ParamType &ParamTypes::operator[](const QString &name) +{ + int index = -1; + for (int i = 0; i < count(); i++) { + if (at(i).name() == name) { + index = i; + break; + } + } + return QList::operator[](index); +} diff --git a/libnymea/types/paramtype.h b/libnymea/types/paramtype.h index 8f8cede2..c3bcacca 100644 --- a/libnymea/types/paramtype.h +++ b/libnymea/types/paramtype.h @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2020, nymea GmbH +* Copyright 2013 - 2024, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. @@ -122,10 +122,14 @@ class ParamTypes: public QList public: ParamTypes() = default; ParamTypes(const QList &other); + bool contains(const ParamTypeId ¶mTypeId); + bool contains(const QString &name); Q_INVOKABLE QVariant get(int index) const; Q_INVOKABLE void put(const QVariant &variant); ParamType findByName(const QString &name) const; ParamType findById(const ParamTypeId &id) const; + ParamType &operator[](const QString &name); + }; Q_DECLARE_METATYPE(QList) Q_DECLARE_METATYPE(ParamTypes) diff --git a/tools/nymea-plugininfocompiler/nymea-plugininfocompiler.pro b/tools/nymea-plugininfocompiler/nymea-plugininfocompiler.pro index e900232b..04bb1830 100644 --- a/tools/nymea-plugininfocompiler/nymea-plugininfocompiler.pro +++ b/tools/nymea-plugininfocompiler/nymea-plugininfocompiler.pro @@ -24,6 +24,7 @@ SOURCES += \ $$top_srcdir/libnymea/types/eventtype.cpp \ $$top_srcdir/libnymea/types/actiontype.cpp \ $$top_srcdir/libnymea/types/interface.cpp \ + $$top_srcdir/libnymea/types/interfaceparamtype.cpp \ $$top_srcdir/libnymea/types/interfacestatetype.cpp \ $$top_srcdir/libnymea/types/interfaceeventtype.cpp \ $$top_srcdir/libnymea/types/interfaceactiontype.cpp \ @@ -42,6 +43,7 @@ HEADERS += \ $$top_srcdir/libnymea/types/eventtype.h \ $$top_srcdir/libnymea/types/actiontype.h \ $$top_srcdir/libnymea/types/interface.h \ + $$top_srcdir/libnymea/types/interfaceparamtype.h \ $$top_srcdir/libnymea/types/interfacestatetype.h \ $$top_srcdir/libnymea/types/interfaceeventtype.h \ $$top_srcdir/libnymea/types/interfaceactiontype.h \ From c46b86088a72ca17df9241c87e0b033acef95895 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Tue, 3 Dec 2024 16:20:38 +0100 Subject: [PATCH 2/7] Add networkdevice interface --- libnymea/interfaces/interfaces.qrc | 1 + libnymea/interfaces/networkdevice.json | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 libnymea/interfaces/networkdevice.json diff --git a/libnymea/interfaces/interfaces.qrc b/libnymea/interfaces/interfaces.qrc index 45632ece..562fc4ce 100644 --- a/libnymea/interfaces/interfaces.qrc +++ b/libnymea/interfaces/interfaces.qrc @@ -100,5 +100,6 @@ alarm.json firesensor.json childlock.json + networkdevice.json diff --git a/libnymea/interfaces/networkdevice.json b/libnymea/interfaces/networkdevice.json new file mode 100644 index 00000000..b0036c06 --- /dev/null +++ b/libnymea/interfaces/networkdevice.json @@ -0,0 +1,17 @@ +{ + "description": "Things implementing this interface make use of the network device discovery resource and the network device monitor. This interface makes sure all relevant information will be stored. Depending on the MonitorMode of the discovered NetworkDeviceInfo the params of this thing shall be filled in. This makes sure the monitor uses the correct method to monitor the network device presence and IP. If the Mode is MAC, just sotre the mac address param and leave the others empty. If the Mode is HostName, just fill in the host name parameter. If the Mode is IP, just the address.", + "params": [ + { + "name": "address", + "type": "QString" + }, + { + "name": "hostName", + "type": "QString" + }, + { + "name": "macAddress", + "type": "QString" + } + ] +} From 9b4b2d9b204111fd71542dfa868093ec29d218fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Mon, 2 Dec 2024 16:18:23 +0100 Subject: [PATCH 3/7] Update network device info and make host address the primary idetifier instead of the MAC address Introduce monitor mode for network device info Update network device monitor introduce networkdevice interface --- .../network/networkdevicediscoveryimpl.cpp | 308 ++++++++++-------- .../network/networkdevicediscoveryimpl.h | 8 +- .../networkdevicediscoveryreplyimpl.cpp | 215 +++++------- .../network/networkdevicediscoveryreplyimpl.h | 20 +- libnymea/integrations/thingdiscoveryinfo.h | 2 +- libnymea/libnymea.pro | 4 + libnymea/network/macaddressinfo.cpp | 103 ++++++ libnymea/network/macaddressinfo.h | 64 ++++ libnymea/network/macaddressinfos.cpp | 118 +++++++ libnymea/network/macaddressinfos.h | 60 ++++ libnymea/network/networkdevicediscovery.h | 2 +- .../network/networkdevicediscoveryreply.h | 6 - libnymea/network/networkdeviceinfo.cpp | 135 +++++--- libnymea/network/networkdeviceinfo.h | 43 ++- libnymea/network/networkdeviceinfos.cpp | 70 ++-- libnymea/network/networkdeviceinfos.h | 14 +- libnymea/network/networkdevicemonitor.cpp | 5 +- nymea.pro | 2 +- 18 files changed, 791 insertions(+), 388 deletions(-) create mode 100644 libnymea/network/macaddressinfo.cpp create mode 100644 libnymea/network/macaddressinfo.h create mode 100644 libnymea/network/macaddressinfos.cpp create mode 100644 libnymea/network/macaddressinfos.h diff --git a/libnymea-core/hardware/network/networkdevicediscoveryimpl.cpp b/libnymea-core/hardware/network/networkdevicediscoveryimpl.cpp index 8d495cc6..60dbc1f5 100644 --- a/libnymea-core/hardware/network/networkdevicediscoveryimpl.cpp +++ b/libnymea-core/hardware/network/networkdevicediscoveryimpl.cpp @@ -38,6 +38,8 @@ #include #include +#define CACHE_VERSION 1 + NYMEA_LOGGING_CATEGORY(dcNetworkDeviceDiscovery, "NetworkDeviceDiscovery") namespace nymeaserver { @@ -128,7 +130,7 @@ NetworkDeviceDiscoveryReply *NetworkDeviceDiscoveryImpl::discover() } else { qCDebug(dcNetworkDeviceDiscovery()) << "Starting internally a new discovery."; m_currentDiscoveryReply = new NetworkDeviceDiscoveryReplyImpl(this); - connect(m_currentDiscoveryReply, &NetworkDeviceDiscoveryReplyImpl::networkDeviceInfoAdded, this, &NetworkDeviceDiscoveryImpl::updateCache); + // connect(m_currentDiscoveryReply, &NetworkDeviceDiscoveryReplyImpl::networkDeviceInfoAdded, this, &NetworkDeviceDiscoveryImpl::updateCache); connect(m_currentDiscoveryReply, &NetworkDeviceDiscoveryReplyImpl::finished, this, [this](){ // Finish all pending replies foreach (NetworkDeviceDiscoveryReplyImpl *reply, m_pendingDiscoveryReplies) { @@ -137,10 +139,6 @@ NetworkDeviceDiscoveryReply *NetworkDeviceDiscoveryImpl::discover() foreach (const NetworkDeviceInfo &info, m_currentDiscoveryReply->networkDeviceInfos()) { reply->addCompleteNetworkDeviceInfo(info); } - - foreach (const NetworkDeviceInfo &info, m_currentDiscoveryReply->virtualNetworkDeviceInfos()) { - reply->addVirtualNetworkDeviceInfo(info); - } } // Delete the current reply before finishing the pending replies. @@ -159,8 +157,6 @@ NetworkDeviceDiscoveryReply *NetworkDeviceDiscoveryImpl::discover() // Create the reply for the user NetworkDeviceDiscoveryReplyImpl *reply = new NetworkDeviceDiscoveryReplyImpl(this); - connect(m_currentDiscoveryReply, &NetworkDeviceDiscoveryReplyImpl::networkDeviceInfoAdded, reply, &NetworkDeviceDiscoveryReplyImpl::addCompleteNetworkDeviceInfo); - connect(m_currentDiscoveryReply, &NetworkDeviceDiscoveryReplyImpl::hostAddressDiscovered, reply, &NetworkDeviceDiscoveryReplyImpl::hostAddressDiscovered); m_pendingDiscoveryReplies.append(reply); if (!available()) { @@ -171,19 +167,23 @@ NetworkDeviceDiscoveryReply *NetworkDeviceDiscoveryImpl::discover() } if (alreadyRunning) { - // Add already discovered network device infos in the next event loop - // so any connections after this method call will work as expected + // Emit allready discovered hosts so any integration can implement safly a connect on the host hostAddressDiscovered signal + // even if the internal discvoery was already running QTimer::singleShot(0, reply, [this, reply](){ if (!m_currentDiscoveryReply) return; - foreach (const NetworkDeviceInfo &networkDeviceInfo, m_currentDiscoveryReply->networkDeviceInfos()) { - reply->addCompleteNetworkDeviceInfo(networkDeviceInfo); + foreach (const QHostAddress &address, m_currentDiscoveryReply->currentCache().keys()) { + reply->hostAddressDiscovered(address); } + + connect(m_currentDiscoveryReply, &NetworkDeviceDiscoveryReplyImpl::hostAddressDiscovered, reply, &NetworkDeviceDiscoveryReplyImpl::hostAddressDiscovered); }); } else { qCInfo(dcNetworkDeviceDiscovery()) << "Starting network device discovery ..."; + connect(m_currentDiscoveryReply, &NetworkDeviceDiscoveryReplyImpl::hostAddressDiscovered, reply, &NetworkDeviceDiscoveryReplyImpl::hostAddressDiscovered); + if (m_ping->available()) pingAllNetworkDevices(); @@ -220,49 +220,52 @@ NetworkDeviceMonitor *NetworkDeviceDiscoveryImpl::registerMonitor(const MacAddre return nullptr; } - // Make sure we create only one monitor per MAC and keep track how many user - // have access to this monitor otherwise an unregister could cause a crash in - // an other plugin plugin which might still need it - if (m_monitors.contains(macAddress)) { - m_monitorsReferenceCount[macAddress] += 1; - qCInfo(dcNetworkDeviceDiscovery()) << "Register network device monitor for" << macAddress << "which already exists. Returning existing monitor having now" << m_monitorsReferenceCount[macAddress] << "references."; - return m_monitors.value(macAddress); - } + // FIXME + return nullptr; - qCInfo(dcNetworkDeviceDiscovery()) << "Register new network device monitor for" << macAddress; + // // Make sure we create only one monitor per MAC and keep track how many user + // // have access to this monitor otherwise an unregister could cause a crash in + // // an other plugin plugin which might still need it + // if (m_monitors.contains(macAddress)) { + // m_monitorsReferenceCount[macAddress] += 1; + // qCInfo(dcNetworkDeviceDiscovery()) << "Register network device monitor for" << macAddress << "which already exists. Returning existing monitor having now" << m_monitorsReferenceCount[macAddress] << "references."; + // return m_monitors.value(macAddress); + // } - // Fill in cached information - NetworkDeviceInfo info; - if (m_networkInfoCache.contains(macAddress)) { - info = m_networkInfoCache.value(macAddress); - } else { - info.setMacAddress(macAddress.toString()); - } + // qCInfo(dcNetworkDeviceDiscovery()) << "Register new network device monitor for" << macAddress; - NetworkDeviceMonitorImpl *monitor = new NetworkDeviceMonitorImpl(macAddress, this); - monitor->setNetworkDeviceInfo(info); - monitor->setLastSeen(m_lastSeen.value(macAddress, QDateTime())); - m_monitors.insert(macAddress, monitor); - m_monitorsReferenceCount[macAddress] = 1; + // // Fill in cached information + // NetworkDeviceInfo info; + // if (m_networkInfoCache.contains(macAddress)) { + // info = m_networkInfoCache.value(macAddress); + // } else { + // info.setMacAddress(macAddress.toString()); + // } - if (!available()) { - qCWarning(dcNetworkDeviceDiscovery()) << "Registered monitor but the hardware resource is not available. The monitor will not work as expected" << monitor; - return monitor; - } + // NetworkDeviceMonitorImpl *monitor = new NetworkDeviceMonitorImpl(macAddress, this); + // monitor->setNetworkDeviceInfo(info); + // monitor->setLastSeen(m_lastSeen.value(macAddress, QDateTime())); + // m_monitors.insert(macAddress, monitor); + // m_monitorsReferenceCount[macAddress] = 1; - // Restart the monitor timer since we evaluate this one now - m_monitorTimer->start(); + // if (!available()) { + // qCWarning(dcNetworkDeviceDiscovery()) << "Registered monitor but the hardware resource is not available. The monitor will not work as expected" << monitor; + // return monitor; + // } - if (!monitor->networkDeviceInfo().isValid()) { - qCDebug(dcNetworkDeviceDiscovery()) << "Adding network device monitor for unresolved mac address. Starting a discovery..."; - NetworkDeviceDiscoveryReply *reply = discover(); - connect(reply, &NetworkDeviceDiscoveryReply::finished, reply, &NetworkDeviceDiscoveryReply::deleteLater); - } else { - evaluateMonitor(monitor); - } + // // Restart the monitor timer since we evaluate this one now + // m_monitorTimer->start(); - qCDebug(dcNetworkDeviceDiscovery()) << "Registered successfully" << monitor; - return monitor; + // if (!monitor->networkDeviceInfo().isValid()) { + // qCDebug(dcNetworkDeviceDiscovery()) << "Adding network device monitor for unresolved mac address. Starting a discovery..."; + // NetworkDeviceDiscoveryReply *reply = discover(); + // connect(reply, &NetworkDeviceDiscoveryReply::finished, reply, &NetworkDeviceDiscoveryReply::deleteLater); + // } else { + // evaluateMonitor(monitor); + // } + + // qCDebug(dcNetworkDeviceDiscovery()) << "Registered successfully" << monitor; + // return monitor; } void NetworkDeviceDiscoveryImpl::unregisterMonitor(const MacAddress &macAddress) @@ -291,7 +294,8 @@ void NetworkDeviceDiscoveryImpl::unregisterMonitor(NetworkDeviceMonitor *network if (!m_monitors.values().contains(qobject_cast(networkDeviceMonitor))) return; - unregisterMonitor(MacAddress(networkDeviceMonitor->networkDeviceInfo().macAddress())); + // FIXME + //unregisterMonitor(MacAddress(networkDeviceMonitor->networkDeviceInfo().macAddress())); } PingReply *NetworkDeviceDiscoveryImpl::ping(const QHostAddress &address, uint retries) @@ -328,7 +332,7 @@ bool NetworkDeviceDiscoveryImpl::sendArpRequest(const QHostAddress &address) return false; } -QHash NetworkDeviceDiscoveryImpl::cache() const +NetworkDeviceInfos NetworkDeviceDiscoveryImpl::cache() const { return m_networkInfoCache; } @@ -461,18 +465,20 @@ void NetworkDeviceDiscoveryImpl::watchPingReply(PingReply *reply) { connect(reply, &PingReply::finished, this, [=](){ // Search cache for mac address and update last seen - if (reply->error() == PingReply::ErrorNoError) { - foreach (const NetworkDeviceInfo &info, m_networkInfoCache) { - if (info.address() == reply->targetHostAddress()) { - // Found info for this ip, update the cache - MacAddress macAddress(info.macAddress()); - if (!macAddress.isNull() && m_networkInfoCache.contains(macAddress)) { - m_lastSeen[macAddress] = QDateTime::currentDateTime(); - saveNetworkDeviceCache(m_networkInfoCache.value(macAddress)); - } - } - } - } + + // FIXME + // if (reply->error() == PingReply::ErrorNoError) { + // foreach (const NetworkDeviceInfo &info, m_networkInfoCache) { + // if (info.address() == reply->targetHostAddress()) { + // // Found info for this ip, update the cache + // MacAddress macAddress(info.macAddress()); + // if (!macAddress.isNull() && m_networkInfoCache.contains(macAddress)) { + // m_lastSeen[macAddress] = QDateTime::currentDateTime(); + // saveNetworkDeviceCache(m_networkInfoCache.value(macAddress)); + // } + // } + // } + // } // Update any monitor foreach (NetworkDeviceMonitorImpl *monitor, m_monitors.values()) { @@ -490,39 +496,54 @@ void NetworkDeviceDiscoveryImpl::loadNetworkDeviceCache() m_networkInfoCache.clear(); QDateTime now = QDateTime::currentDateTime(); - m_cacheSettings->beginGroup("NetworkDeviceInfos"); - foreach (const QString &macAddress, m_cacheSettings->childGroups()) { - m_cacheSettings->beginGroup(macAddress); + uint cacheVersion = m_cacheSettings->value("version", 0).toUInt(); - MacAddress mac(macAddress); - QDateTime lastSeen = QDateTime::fromMSecsSinceEpoch(m_cacheSettings->value("lastSeen").toLongLong()); + // Load only the cache if we have the same format, otherwise we discard the cache and start over with the current version + // No need to migrate the caches, a new discovery will to that for us ... + + if (cacheVersion == CACHE_VERSION) { + m_cacheSettings->beginGroup("NetworkDeviceInfos"); + foreach (const QString &macAddress, m_cacheSettings->childGroups()) { + m_cacheSettings->beginGroup(macAddress); + + // MacAddress mac(macAddress); + // QDateTime lastSeen = QDateTime::fromMSecsSinceEpoch(m_cacheSettings->value("lastSeen").toLongLong()); + + // // Remove the info from the cache if not seen fo the last 30 days... + // if (lastSeen.date().addDays(m_cacheCleanupPeriod) < now.date()) { + // qCDebug(dcNetworkDeviceDiscovery()) << "Removing network device cache entry since it did not show up within the last" << m_cacheCleanupPeriod << "days" << mac.toString(); + // m_cacheSettings->remove(""); + // m_cacheSettings->endGroup(); // mac address + // continue; + // } + + // NetworkDeviceInfo info(mac.toString()); + // info.setAddress(QHostAddress(m_cacheSettings->value("address").toString())); + // info.setHostName(m_cacheSettings->value("hostName").toString()); + // info.setMacAddressManufacturer(m_cacheSettings->value("manufacturer").toString()); + // info.setNetworkInterface(QNetworkInterface::interfaceFromName(m_cacheSettings->value("interface").toString())); + + // if (info.isValid() && info.isComplete()) { + // qCDebug(dcNetworkDeviceDiscovery()) << "Loaded cached" << info << "last seen" << lastSeen.toString(); + // m_networkInfoCache[mac] = info; + // m_lastSeen[mac] = lastSeen; + // } else { + // qCWarning(dcNetworkDeviceDiscovery()) << "Clean up invalid cached network device info from cache" << info; + // m_cacheSettings->remove(""); + // } - // Remove the info from the cache if not seen fo the last 30 days... - if (lastSeen.date().addDays(m_cacheCleanupPeriod) < now.date()) { - qCDebug(dcNetworkDeviceDiscovery()) << "Removing network device cache entry since it did not show up within the last" << m_cacheCleanupPeriod << "days" << mac.toString(); - m_cacheSettings->remove(""); m_cacheSettings->endGroup(); // mac address - continue; } + m_cacheSettings->endGroup(); // NetworkDeviceInfos - NetworkDeviceInfo info(mac.toString()); - info.setAddress(QHostAddress(m_cacheSettings->value("address").toString())); - info.setHostName(m_cacheSettings->value("hostName").toString()); - info.setMacAddressManufacturer(m_cacheSettings->value("manufacturer").toString()); - info.setNetworkInterface(QNetworkInterface::interfaceFromName(m_cacheSettings->value("interface").toString())); + } else { + qCDebug(dcNetworkDeviceDiscovery()) << "The cache format version has changed. Discard the network device cache ..."; + m_cacheSettings->setValue("version", CACHE_VERSION); - if (info.isValid() && info.isComplete()) { - qCDebug(dcNetworkDeviceDiscovery()) << "Loaded cached" << info << "last seen" << lastSeen.toString(); - m_networkInfoCache[mac] = info; - m_lastSeen[mac] = lastSeen; - } else { - qCWarning(dcNetworkDeviceDiscovery()) << "Clean up invalid cached network device info from cache" << info; - m_cacheSettings->remove(""); - } - - m_cacheSettings->endGroup(); // mac address + m_cacheSettings->beginGroup("NetworkDeviceInfos"); + m_cacheSettings->remove(""); + m_cacheSettings->endGroup(); // NetworkDeviceInfos } - m_cacheSettings->endGroup(); // NetworkDeviceInfos qCInfo(dcNetworkDeviceDiscovery()) << "Loaded" << m_networkInfoCache.count() << "network device infos from cache."; @@ -535,14 +556,14 @@ void NetworkDeviceDiscoveryImpl::removeFromNetworkDeviceCache(const MacAddress & if (macAddress.isNull()) return; - m_networkInfoCache.remove(macAddress); - m_lastSeen.remove(macAddress); - m_cacheSettings->beginGroup("NetworkDeviceInfos"); - m_cacheSettings->beginGroup(macAddress.toString()); - m_cacheSettings->remove(""); - m_cacheSettings->endGroup(); // mac address - m_cacheSettings->endGroup(); // NetworkDeviceInfos - m_cacheSettings->sync(); + // m_networkInfoCache.remove(macAddress); + // m_lastSeen.remove(macAddress); + // m_cacheSettings->beginGroup("NetworkDeviceInfos"); + // m_cacheSettings->beginGroup(macAddress.toString()); + // m_cacheSettings->remove(""); + // m_cacheSettings->endGroup(); // mac address + // m_cacheSettings->endGroup(); // NetworkDeviceInfos + // m_cacheSettings->sync(); } void NetworkDeviceDiscoveryImpl::saveNetworkDeviceCache(const NetworkDeviceInfo &deviceInfo) @@ -550,33 +571,43 @@ void NetworkDeviceDiscoveryImpl::saveNetworkDeviceCache(const NetworkDeviceInfo if (!deviceInfo.isValid() || !deviceInfo.isComplete()) return; - m_cacheSettings->beginGroup("NetworkDeviceInfos"); - m_cacheSettings->beginGroup(deviceInfo.macAddress()); - m_cacheSettings->setValue("address", deviceInfo.address().toString()); - m_cacheSettings->setValue("hostName", deviceInfo.hostName()); - m_cacheSettings->setValue("manufacturer", deviceInfo.macAddressManufacturer()); - m_cacheSettings->setValue("interface", deviceInfo.networkInterface().name()); - m_cacheSettings->setValue("lastSeen", convertMinuteBased(m_lastSeen.value(MacAddress(deviceInfo.macAddress()))).toMSecsSinceEpoch()); - m_cacheSettings->endGroup(); // mac address - m_cacheSettings->endGroup(); // NetworkDeviceInfos - m_cacheSettings->sync(); + // m_cacheSettings->beginGroup("NetworkDeviceInfos"); + // m_cacheSettings->beginGroup(deviceInfo.macAddress()); + // m_cacheSettings->setValue("address", deviceInfo.address().toString()); + // m_cacheSettings->setValue("hostName", deviceInfo.hostName()); + // m_cacheSettings->setValue("manufacturer", deviceInfo.macAddressManufacturer()); + // m_cacheSettings->setValue("interface", deviceInfo.networkInterface().name()); + // m_cacheSettings->setValue("lastSeen", convertMinuteBased(m_lastSeen.value(MacAddress(deviceInfo.macAddress()))).toMSecsSinceEpoch()); + // m_cacheSettings->endGroup(); // mac address + // m_cacheSettings->endGroup(); // NetworkDeviceInfos + // m_cacheSettings->sync(); } void NetworkDeviceDiscoveryImpl::updateCache(const NetworkDeviceInfo &deviceInfo) { - MacAddress macAddress(deviceInfo.macAddress()); - if (macAddress.isNull()) + // MacAddress macAddress(deviceInfo.macAddress()); + // if (macAddress.isNull()) + // return; + + // FIXME + // if (m_monitors.contains(macAddress)) { + // NetworkDeviceMonitorImpl *monitor = m_monitors.value(macAddress); + // monitor->setNetworkDeviceInfo(deviceInfo); + // } + + + // Save only if changed + int index = m_networkInfoCache.indexFromHostAddress(deviceInfo.address()); + + if (index >= 0 && m_networkInfoCache.at(index) == deviceInfo) return; - if (m_monitors.contains(macAddress)) { - NetworkDeviceMonitorImpl *monitor = m_monitors.value(macAddress); - monitor->setNetworkDeviceInfo(deviceInfo); + if (index < 0) { + m_networkInfoCache.append(deviceInfo); + } else { + m_networkInfoCache[index] = deviceInfo; } - if (m_networkInfoCache.value(macAddress) == deviceInfo) - return; - - m_networkInfoCache[macAddress] = deviceInfo; saveNetworkDeviceCache(deviceInfo); } @@ -644,29 +675,31 @@ void NetworkDeviceDiscoveryImpl::processArpTraffic(const QNetworkInterface &inte QDateTime now = QDateTime::currentDateTime(); m_lastSeen[macAddress] = now; - if (m_networkInfoCache.contains(macAddress)) { - if (m_networkInfoCache.value(macAddress).address() != address) { - m_networkInfoCache[macAddress].setAddress(address); - saveNetworkDeviceCache(m_networkInfoCache.value(macAddress)); - } - } + // FIXME + + // if (m_networkInfoCache.hasMacAddress(macAddress)) { + // if (m_networkInfoCache.value(macAddress).address() != address) { + // m_networkInfoCache[macAddress].setAddress(address); + // saveNetworkDeviceCache(m_networkInfoCache.value(macAddress)); + // } + // } // Update the monitors - NetworkDeviceMonitorImpl *monitor = m_monitors.value(macAddress); - if (monitor) { - monitor->setLastSeen(now); - if (monitor->networkDeviceInfo().address() != address) { - NetworkDeviceInfo info = monitor->networkDeviceInfo(); - info.setAddress(address); - monitor->setNetworkDeviceInfo(info); - qCDebug(dcNetworkDeviceDiscovery()) << "NetworkDeviceMonitor" << monitor << "ip address changed"; - emit monitor->networkDeviceInfoChanged(monitor->networkDeviceInfo()); - } + // NetworkDeviceMonitorImpl *monitor = m_monitors.value(macAddress); + // if (monitor) { + // monitor->setLastSeen(now); + // if (monitor->networkDeviceInfo().address() != address) { + // NetworkDeviceInfo info = monitor->networkDeviceInfo(); + // info.setAddress(address); + // monitor->setNetworkDeviceInfo(info); + // qCDebug(dcNetworkDeviceDiscovery()) << "NetworkDeviceMonitor" << monitor << "ip address changed"; + // emit monitor->networkDeviceInfoChanged(monitor->networkDeviceInfo()); + // } - if (monitor->networkDeviceInfo().isComplete()) { - monitor->setReachable(true); - } - } + // if (monitor->networkDeviceInfo().isComplete()) { + // monitor->setReachable(true); + // } + // } // Check if we have currently reply running if (!m_currentDiscoveryReply) @@ -677,8 +710,8 @@ void NetworkDeviceDiscoveryImpl::processArpTraffic(const QNetworkInterface &inte // Check if we know the mac address manufacturer from the cache bool requiresMacAddressLookup = true; - if (m_networkInfoCache.contains(macAddress)) { - QString cachedManufacturer = m_networkInfoCache[macAddress].macAddressManufacturer(); + if (m_macVendorCache.contains(macAddress)) { + QString cachedManufacturer = m_macVendorCache.value(macAddress); if (!cachedManufacturer.isEmpty()) { // Found the mac address manufacturer in the cache, let's use that one... qCDebug(dcNetworkDeviceDiscovery()) << "Using cached manufacturer " << cachedManufacturer << "for" << macAddress.toString(); @@ -700,6 +733,9 @@ void NetworkDeviceDiscoveryImpl::processArpTraffic(const QNetworkInterface &inte // Note: set the mac manufacturer explicitly to make the info complete (even an empty sring) qCDebug(dcNetworkDeviceDiscovery()) << "MAC manufacturer lookup finished for" << macAddress << ":" << reply->manufacturer(); + if (!reply->manufacturer().isEmpty()) + m_macVendorCache.insert(macAddress, reply->manufacturer()); + if (m_currentDiscoveryReply) { m_currentDiscoveryReply->processMacManufacturer(macAddress, reply->manufacturer()); diff --git a/libnymea-core/hardware/network/networkdevicediscoveryimpl.h b/libnymea-core/hardware/network/networkdevicediscoveryimpl.h index e2f115d2..859eb102 100644 --- a/libnymea-core/hardware/network/networkdevicediscoveryimpl.h +++ b/libnymea-core/hardware/network/networkdevicediscoveryimpl.h @@ -79,7 +79,7 @@ public: bool sendArpRequest(const QHostAddress &address) override; - QHash cache() const override; + NetworkDeviceInfos cache() const override; protected: void setEnabled(bool enabled) override; @@ -116,8 +116,12 @@ private: QHash m_monitorsReferenceCount; QHash m_lastSeen; + QHash m_macVendorCache; + + QHash m_infos; + QSettings *m_cacheSettings; - QHash m_networkInfoCache; + NetworkDeviceInfos m_networkInfoCache; void pingAllNetworkDevices(); diff --git a/libnymea-core/hardware/network/networkdevicediscoveryreplyimpl.cpp b/libnymea-core/hardware/network/networkdevicediscoveryreplyimpl.cpp index 8503c86b..453b2b12 100644 --- a/libnymea-core/hardware/network/networkdevicediscoveryreplyimpl.cpp +++ b/libnymea-core/hardware/network/networkdevicediscoveryreplyimpl.cpp @@ -29,6 +29,7 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "networkdevicediscoveryreplyimpl.h" +#include "network/networkutils.h" #include "loggingcategories.h" #include @@ -48,11 +49,6 @@ NetworkDeviceInfos NetworkDeviceDiscoveryReplyImpl::networkDeviceInfos() const return m_networkDeviceInfos; } -NetworkDeviceInfos NetworkDeviceDiscoveryReplyImpl::virtualNetworkDeviceInfos() const -{ - return m_virtualNetworkDeviceInfos; -} - bool NetworkDeviceDiscoveryReplyImpl::isFinished() const { return m_isFinished; @@ -65,49 +61,40 @@ void NetworkDeviceDiscoveryReplyImpl::setFinished(bool finished) void NetworkDeviceDiscoveryReplyImpl::processPingResponse(const QHostAddress &address, const QString &hostName) { - foreach (const NetworkDeviceInfo &info, m_networkDeviceCache) { - if (info.address() == address) { - // Already found info, set host name and check if complete - MacAddress macAddress(info.macAddress()); - m_networkDeviceCache[macAddress].setHostName(hostName); - verifyComplete(macAddress); - return; - } + if (m_networkDeviceCache.contains(address)) { + // Update existing hostname... + m_networkDeviceCache[address].setHostName(hostName); + evaluateMonitorMode(address); + } else { + // Adding new host... + NetworkDeviceInfo info; + info.setAddress(address); + info.setHostName(hostName); + m_networkDeviceCache.insert(address, info); + evaluateMonitorMode(address); + + // First time seeing this host address + emit hostAddressDiscovered(address); } - // Unknown and we have no mac address yet, add it to the ping cache - NetworkDeviceInfo info; - info.setAddress(address); - info.setHostName(hostName); - m_pingCache.insert(address, info); - // First time seeing this host address - emit hostAddressDiscovered(address); } void NetworkDeviceDiscoveryReplyImpl::processArpResponse(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress) { - if (m_pingCache.contains(address)) { - // We know this device from a ping response - NetworkDeviceInfo info = m_pingCache.take(address); + if (m_networkDeviceCache.contains(address)) { + m_networkDeviceCache[address].addMacAddress(macAddress); + m_networkDeviceCache[address].setNetworkInterface(interface); + } else { + NetworkDeviceInfo info(macAddress.toString()); info.setAddress(address); info.setNetworkInterface(interface); - info.setMacAddress(macAddress.toString()); - m_networkDeviceCache[macAddress] = info; - } else { - if (m_networkDeviceCache.contains(macAddress)) { - m_networkDeviceCache[macAddress].setAddress(address); - m_networkDeviceCache[macAddress].setNetworkInterface(interface); - } else { - NetworkDeviceInfo info(macAddress.toString()); - info.setAddress(address); - info.setNetworkInterface(interface); - m_networkDeviceCache[macAddress] = info; - // First time seeing this host address - emit hostAddressDiscovered(address); - } + m_networkDeviceCache[address] = info; + + // First time seeing this host address + emit hostAddressDiscovered(address); } - verifyComplete(macAddress); + evaluateMonitorMode(address); } void NetworkDeviceDiscoveryReplyImpl::processMacManufacturer(const MacAddress &macAddress, const QString &manufacturer) @@ -115,43 +102,35 @@ void NetworkDeviceDiscoveryReplyImpl::processMacManufacturer(const MacAddress &m if (macAddress.isNull()) return; - if (m_networkDeviceCache.contains(macAddress)) { - m_networkDeviceCache[macAddress].setMacAddressManufacturer(manufacturer); - } else { - NetworkDeviceInfo info(macAddress.toString()); - info.setMacAddressManufacturer(manufacturer); - m_networkDeviceCache[macAddress] = info; + foreach (const NetworkDeviceInfo &info, m_networkDeviceCache) { + if (info.macAddressInfos().hasMacAddress(macAddress)) { + m_networkDeviceCache[info.address()].addMacAddress(macAddress, manufacturer); + evaluateMonitorMode(info.address()); + } } - - verifyComplete(macAddress); } void NetworkDeviceDiscoveryReplyImpl::processDiscoveryFinished() { - // Lets see if we have any incomplete infos but enougth data to be shown - foreach (const MacAddress &macAddress, m_networkDeviceCache.keys()) { - // If already in the result, ignore it - if (m_networkDeviceInfos.hasMacAddress(macAddress)) - continue; + // Add the discovery cache to the final result + foreach (const QHostAddress &address, m_networkDeviceCache.keys()) { - NetworkDeviceInfo info = m_networkDeviceCache.value(macAddress); - MacAddress infoMacAddress(info.macAddress()); + if (m_networkDeviceCache.value(address).macAddressInfos().isEmpty() && !m_networkDeviceCache.value(address).networkInterface().isValid()) { + // Set the network interface for the virtual hosts like VPN where we are not receiving any ARP information + m_networkDeviceCache[address].setNetworkInterface(NetworkUtils::getInterfaceForHostaddress(address)); + } + + NetworkDeviceInfo info = m_networkDeviceCache.value(address); qCDebug(dcNetworkDeviceDiscovery()) << "--> " << info << "Valid:" << info.isValid() << "Complete:" << info.isComplete() << info.incompleteProperties(); - // We need at least a valid mac address and a valid ip address, the rest ist pure informative - if (infoMacAddress == macAddress && !infoMacAddress.isNull() && !info.address().isNull()) { - qCDebug(dcNetworkDeviceDiscovery()) << "Adding incomplete" << info << "to the final result:" << info.incompleteProperties(); - // Note: makeing it complete - m_networkDeviceCache[macAddress].setAddress(info.address()); - m_networkDeviceCache[macAddress].setHostName(info.hostName()); - m_networkDeviceCache[macAddress].setMacAddress(info.macAddress()); - m_networkDeviceCache[macAddress].setMacAddressManufacturer(info.macAddressManufacturer()); - m_networkDeviceCache[macAddress].setNetworkInterface(info.networkInterface()); - verifyComplete(macAddress); - } + qCDebug(dcNetworkDeviceDiscovery()) << "Adding incomplete" << info << "to the final result:" << info.incompleteProperties(); + m_networkDeviceCache[address].forceComplete(); + + evaluateMonitorMode(address); + m_networkDeviceInfos.append(m_networkDeviceCache.value(address)); } // Done, lets sort the result and inform @@ -165,87 +144,69 @@ void NetworkDeviceDiscoveryReplyImpl::processDiscoveryFinished() qCDebug(dcNetworkDeviceDiscovery()) << "--> " << info; } - // Create valid infos from the ping cache and offer them in the virtual infos - foreach (const NetworkDeviceInfo &info, m_pingCache) { - NetworkDeviceInfo finalInfo = info; - finalInfo.setAddress(finalInfo.address()); - finalInfo.setHostName(finalInfo.hostName()); - finalInfo.setMacAddress(finalInfo.macAddress()); - finalInfo.setNetworkInterface(finalInfo.networkInterface()); - finalInfo.setMacAddressManufacturer(finalInfo.macAddressManufacturer()); - m_virtualNetworkDeviceInfos.append(info); - } - - m_virtualNetworkDeviceInfos.sortNetworkDevices(); - - qCDebug(dcNetworkDeviceDiscovery()) << "Virtual hosts (" << m_virtualNetworkDeviceInfos.count() << ")"; - foreach (const NetworkDeviceInfo &info, m_virtualNetworkDeviceInfos) { - qCDebug(dcNetworkDeviceDiscovery()) << "--> " << info; - } - - foreach (const MacAddress &macAddress, m_networkDeviceCache.keys()) { - if (m_networkDeviceInfos.hasMacAddress(macAddress)) - continue; - - NetworkDeviceInfo info = m_networkDeviceCache.value(macAddress); - qCDebug(dcNetworkDeviceDiscovery()) << "Unhandled information:" << info << "Valid:" << info.isValid() << "Complete:" << info.isComplete() << info.incompleteProperties(); - } - m_isFinished = true; emit finished(); } +QHash NetworkDeviceDiscoveryReplyImpl::currentCache() const +{ + return m_networkDeviceCache; +} + void NetworkDeviceDiscoveryReplyImpl::addCompleteNetworkDeviceInfo(const NetworkDeviceInfo &networkDeviceInfo) { - // Note: this method will be called only from the discovery - if (!m_networkDeviceInfos.hasMacAddress(networkDeviceInfo.macAddress())) { + if (!m_networkDeviceInfos.hasHostAddress(networkDeviceInfo.address())) m_networkDeviceInfos.append(networkDeviceInfo); - - emit hostAddressDiscovered(networkDeviceInfo.address()); - - emit networkDeviceInfoAdded(networkDeviceInfo); - } } -void NetworkDeviceDiscoveryReplyImpl::addVirtualNetworkDeviceInfo(const NetworkDeviceInfo &networkDeviceInfo) +void NetworkDeviceDiscoveryReplyImpl::evaluateMonitorMode(const QHostAddress &address) { - // Note: this method will be called only from the discovery - if (!m_networkDeviceInfos.hasHostAddress(networkDeviceInfo.address())) { - m_virtualNetworkDeviceInfos.append(networkDeviceInfo); - } -} + qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: Evaluating monitor mode for host" << address.toString(); -QString NetworkDeviceDiscoveryReplyImpl::macAddressFromHostAddress(const QHostAddress &address) -{ - foreach (const NetworkDeviceInfo &info, m_networkDeviceCache) { - if (info.address() == address) { - return info.macAddress(); + if (m_networkDeviceCache.value(address).macAddressInfos().isEmpty()) { + // Not discovered yet, or this is a virtual host like VPN + if (m_networkDeviceCache.value(address).hostName().isEmpty()) { + m_networkDeviceCache[address].setMonitorMode(NetworkDeviceInfo::MonitorModeIp); + qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: No MAC address and no hostname, using IP only"; + } else { + qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: No MAC address, but we have a hostname."; + m_networkDeviceCache[address].setMonitorMode(NetworkDeviceInfo::MonitorModeHostname); } - } - return QString(); -} + } else { + // We have at least one mac address, check if there are other network devices with this MAC or if we have multiple MAC addresses + if (m_networkDeviceCache.value(address).macAddressInfos().count() == 1) { -bool NetworkDeviceDiscoveryReplyImpl::hasHostAddress(const QHostAddress &address) -{ - return ! macAddressFromHostAddress(address).isEmpty(); -} + bool uniqueMac = true; + // Check if this mac is unique + foreach (const NetworkDeviceInfo &info, m_networkDeviceCache) { + if (info.address() == address) + continue; -void NetworkDeviceDiscoveryReplyImpl::verifyComplete(const MacAddress &macAddress) -{ - if (!m_networkDeviceCache.contains(macAddress)) - return; + if (info.macAddressInfos().hasMacAddress(m_networkDeviceCache.value(address).macAddressInfos().first().macAddress())) { + uniqueMac = false; + break; + } + } - if (m_networkDeviceCache[macAddress].isComplete() && m_networkDeviceCache[macAddress].isValid()) { - if (m_networkDeviceInfos.hasMacAddress(macAddress)) { - if (m_networkDeviceInfos.get(macAddress) != m_networkDeviceCache.value(macAddress)) { - qCDebug(dcNetworkDeviceDiscovery()) << "One MAC address seem to be reachable using 2 IP addresses, which is OK. Not updating the network device info and keeping the current information."; - qCDebug(dcNetworkDeviceDiscovery()) << "--> Keeping " << m_networkDeviceInfos.get(macAddress); - qCDebug(dcNetworkDeviceDiscovery()) << "--> Ignoring" << m_networkDeviceCache.value(macAddress); + if (!uniqueMac) { + if (m_networkDeviceCache.value(address).hostName().isEmpty()) { + m_networkDeviceCache[address].setMonitorMode(NetworkDeviceInfo::MonitorModeIp); + qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: The MAC address of" << address.toString() << "is not unique in this network and no hostname available, using IP only"; + } else { + qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: The MAC address of" << address.toString() << "is not unique in this network but we have a hostname"; + m_networkDeviceCache[address].setMonitorMode(NetworkDeviceInfo::MonitorModeHostname); + } } } else { - m_networkDeviceInfos.append(m_networkDeviceCache.value(macAddress)); - emit networkDeviceInfoAdded(m_networkDeviceCache[macAddress]); + // Multiple MAC addresses + if (m_networkDeviceCache.value(address).hostName().isEmpty()) { + m_networkDeviceCache[address].setMonitorMode(NetworkDeviceInfo::MonitorModeIp); + qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: Multiple MAC addresses and no hostname, using IP only"; + } else { + qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: Multiple MAC addresses, but we have a hostname."; + m_networkDeviceCache[address].setMonitorMode(NetworkDeviceInfo::MonitorModeHostname); + } } } } diff --git a/libnymea-core/hardware/network/networkdevicediscoveryreplyimpl.h b/libnymea-core/hardware/network/networkdevicediscoveryreplyimpl.h index e3c3e144..c41f2922 100644 --- a/libnymea-core/hardware/network/networkdevicediscoveryreplyimpl.h +++ b/libnymea-core/hardware/network/networkdevicediscoveryreplyimpl.h @@ -48,7 +48,6 @@ public: ~NetworkDeviceDiscoveryReplyImpl() override = default; NetworkDeviceInfos networkDeviceInfos() const override; - NetworkDeviceInfos virtualNetworkDeviceInfos() const override; bool isFinished() const override; void setFinished(bool finished); @@ -60,28 +59,19 @@ public: void processDiscoveryFinished(); + QHash currentCache() const; + public slots: void addCompleteNetworkDeviceInfo(const NetworkDeviceInfo &networkDeviceInfo); - void addVirtualNetworkDeviceInfo(const NetworkDeviceInfo &networkDeviceInfo); private: - NetworkDeviceInfos m_networkDeviceInfos; // Contains only complete and valid infos - NetworkDeviceInfos m_virtualNetworkDeviceInfos; // Contains ping responses without ARP, like VPN devices - - QHash m_networkDeviceCache; + QHash m_networkDeviceCache; qint64 m_startTimestamp; - bool m_isFinished = false; - // Temporary cache for ping responses where the mac is not known yet (like VPN devices) - QHash m_pingCache; - - QString macAddressFromHostAddress(const QHostAddress &address); - bool hasHostAddress(const QHostAddress &address); - - void verifyComplete(const MacAddress &macAddress); - + NetworkDeviceInfos m_networkDeviceInfos; + void evaluateMonitorMode(const QHostAddress &address); }; } diff --git a/libnymea/integrations/thingdiscoveryinfo.h b/libnymea/integrations/thingdiscoveryinfo.h index 48d03b45..63264f84 100644 --- a/libnymea/integrations/thingdiscoveryinfo.h +++ b/libnymea/integrations/thingdiscoveryinfo.h @@ -62,7 +62,7 @@ public slots: void addThingDescriptor(const ThingDescriptor &thingDescriptor); void addThingDescriptors(const ThingDescriptors &thingDescriptors); - void finish(Thing::ThingError status, const QString &displayMessage = QString()); + void finish(Thing::ThingError status, const QString &displayMessage = QString()); signals: void finished(); diff --git a/libnymea/libnymea.pro b/libnymea/libnymea.pro index c183dda8..f288deb2 100644 --- a/libnymea/libnymea.pro +++ b/libnymea/libnymea.pro @@ -57,6 +57,8 @@ HEADERS += \ network/arpsocket.h \ network/macaddress.h \ network/macaddressdatabasereply.h \ + network/macaddressinfo.h \ + network/macaddressinfos.h \ network/networkdevicediscovery.h \ network/networkdevicediscoveryreply.h \ network/networkdeviceinfo.h \ @@ -175,6 +177,8 @@ SOURCES += \ network/arpsocket.cpp \ network/macaddress.cpp \ network/macaddressdatabasereply.cpp \ + network/macaddressinfo.cpp \ + network/macaddressinfos.cpp \ network/networkdevicediscovery.cpp \ network/networkdeviceinfo.cpp \ network/networkdeviceinfos.cpp \ diff --git a/libnymea/network/macaddressinfo.cpp b/libnymea/network/macaddressinfo.cpp new file mode 100644 index 00000000..e41c0ead --- /dev/null +++ b/libnymea/network/macaddressinfo.cpp @@ -0,0 +1,103 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2024, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "macaddressinfo.h" + +#include + +MacAddressInfo::MacAddressInfo() +{ + +} + +MacAddressInfo::MacAddressInfo(const MacAddress &macAddress) + : m_macAddress{macAddress} +{ + +} + +MacAddressInfo::MacAddressInfo(const MacAddress &macAddress, const QString &vendorName) + : m_macAddress{macAddress}, + m_vendorName{vendorName}, + m_vendorNameSet{true} +{ + +} + +MacAddress MacAddressInfo::macAddress() const +{ + return m_macAddress; +} + +QString MacAddressInfo::vendorName() const +{ + return m_vendorName; +} + +void MacAddressInfo::setVendorName(const QString &vendorName) +{ + m_vendorName = vendorName; + m_vendorNameSet = true; +} + +bool MacAddressInfo::isValid() const +{ + return !m_macAddress.isNull(); +} + +bool MacAddressInfo::isComplete() const +{ + return isValid() && m_vendorNameSet; +} + +bool MacAddressInfo::operator==(const MacAddressInfo &other) const +{ + return m_macAddress == other.macAddress() && + m_vendorName == other.vendorName() && + isComplete() == other.isComplete(); +} + +bool MacAddressInfo::operator!=(const MacAddressInfo &other) const +{ + return !operator==(other); +} + +QDebug operator<<(QDebug debug, const MacAddressInfo &addressInfo) +{ + QDebugStateSaver saver(debug); + debug.nospace() << addressInfo.macAddress().toString() << " ("; + if (addressInfo.vendorName().isEmpty()) { + debug.nospace() << "unknown"; + } else { + debug.nospace() << addressInfo.vendorName(); + } + debug.nospace() << ")"; + return debug; +} diff --git a/libnymea/network/macaddressinfo.h b/libnymea/network/macaddressinfo.h new file mode 100644 index 00000000..d4cc73e3 --- /dev/null +++ b/libnymea/network/macaddressinfo.h @@ -0,0 +1,64 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2024, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef MACADDRESSINFO_H +#define MACADDRESSINFO_H + +#include "macaddress.h" + +class MacAddressInfo +{ +public: + explicit MacAddressInfo(); + explicit MacAddressInfo(const MacAddress &macAddress); + explicit MacAddressInfo(const MacAddress &macAddress, const QString &vendorName); + + MacAddress macAddress() const; + + QString vendorName() const; + void setVendorName(const QString &vendorName); + + bool isValid() const; + bool isComplete() const; + + bool operator==(const MacAddressInfo &other) const; + bool operator!=(const MacAddressInfo &other) const; + +private: + MacAddress m_macAddress; + QString m_vendorName; + + bool m_vendorNameSet = false; +}; + +QDebug operator<<(QDebug debug, const MacAddressInfo &addressInfo); + + +#endif // MACADDRESSINFO_H diff --git a/libnymea/network/macaddressinfos.cpp b/libnymea/network/macaddressinfos.cpp new file mode 100644 index 00000000..8bd171e6 --- /dev/null +++ b/libnymea/network/macaddressinfos.cpp @@ -0,0 +1,118 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2024, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "macaddressinfos.h" + +MacAddressInfos::MacAddressInfos() +{ + +} + +MacAddressInfos::MacAddressInfos(const QVector &other) + : QVector(other) +{ + +} + +int MacAddressInfos::indexFromMacAddress(const QString &macAddress) +{ + return indexFromMacAddress(MacAddress(macAddress)); +} + +int MacAddressInfos::indexFromMacAddress(const MacAddress &macAddress) +{ + for (int i = 0; i < size(); i++) { + if (MacAddress(at(i).macAddress()) == macAddress) { + return i; + } + } + + return -1; +} + +bool MacAddressInfos::hasMacAddress(const QString &macAddress) +{ + return indexFromMacAddress(macAddress) >= 0; +} + +bool MacAddressInfos::hasMacAddress(const MacAddress &macAddress) +{ + return indexFromMacAddress(macAddress) >= 0; +} + +MacAddressInfo MacAddressInfos::get(const QString &macAddress) const +{ + return get(MacAddress(macAddress)); +} + +MacAddressInfo MacAddressInfos::get(const MacAddress &macAddress) const +{ + foreach (const MacAddressInfo &info, *this) { + if (info.macAddress() == macAddress) { + return info; + } + } + + return MacAddressInfo(); +} + +void MacAddressInfos::removeMacAddress(const QString &macAddress) +{ + removeMacAddress(MacAddress(macAddress)); +} + +void MacAddressInfos::removeMacAddress(const MacAddress &macAddress) +{ + for (int i = 0; i < size(); i++) { + if (MacAddress(at(i).macAddress()) == macAddress) { + remove(i); + } + } +} + +void MacAddressInfos::sortInfos() +{ + std::sort(this->begin(), this->end(), [](const MacAddressInfo& a, const MacAddressInfo& b) { + return a.macAddress().toByteArray() < b.macAddress().toByteArray(); + }); +} + +bool MacAddressInfos::isComplete() const +{ + bool complete = true; + foreach (const MacAddressInfo &info, *this) { + if (!info.isComplete()) { + complete = false; + break; + } + } + + return complete; +} diff --git a/libnymea/network/macaddressinfos.h b/libnymea/network/macaddressinfos.h new file mode 100644 index 00000000..335b49ec --- /dev/null +++ b/libnymea/network/macaddressinfos.h @@ -0,0 +1,60 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2024, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea. +* This project including source code and documentation is protected by +* copyright law, and remains the property of nymea GmbH. All rights, including +* reproduction, publication, editing and translation, are reserved. The use of +* this project is subject to the terms of a license agreement to be concluded +* with nymea GmbH in accordance with the terms of use of nymea GmbH, available +* under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the +* terms of the GNU Lesser General Public License as published by the Free +* Software Foundation; version 3. This project is distributed in the hope that +* it will be useful, but WITHOUT ANY WARRANTY; without even the implied +* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License +* along with this project. If not, see . +* +* For any further details and any questions please contact us under +* contact@nymea.io or see our FAQ/Licensing Information on +* https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef MACADDRESSINFOS_H +#define MACADDRESSINFOS_H + +#include +#include "macaddressinfo.h" + +class MacAddressInfos : public QVector +{ +public: + explicit MacAddressInfos(); + MacAddressInfos(const QVector &other); + + int indexFromMacAddress(const QString &macAddress); + int indexFromMacAddress(const MacAddress &macAddress); + + bool hasMacAddress(const QString &macAddress); + bool hasMacAddress(const MacAddress &macAddress); + + MacAddressInfo get(const QString &macAddress) const; + MacAddressInfo get(const MacAddress &macAddress) const; + + void removeMacAddress(const QString &macAddress); + void removeMacAddress(const MacAddress &macAddress); + + void sortInfos(); + + bool isComplete() const; +}; + +#endif // MACADDRESSINFOS_H diff --git a/libnymea/network/networkdevicediscovery.h b/libnymea/network/networkdevicediscovery.h index c5450add..1934b960 100644 --- a/libnymea/network/networkdevicediscovery.h +++ b/libnymea/network/networkdevicediscovery.h @@ -67,7 +67,7 @@ public: virtual bool sendArpRequest(const QHostAddress &address) = 0; - virtual QHash cache() const = 0; + virtual NetworkDeviceInfos cache() const = 0; signals: void runningChanged(bool running); diff --git a/libnymea/network/networkdevicediscoveryreply.h b/libnymea/network/networkdevicediscoveryreply.h index bc7d205c..3e4bb952 100644 --- a/libnymea/network/networkdevicediscoveryreply.h +++ b/libnymea/network/networkdevicediscoveryreply.h @@ -44,17 +44,11 @@ public: virtual ~NetworkDeviceDiscoveryReply() = default; virtual NetworkDeviceInfos networkDeviceInfos() const = 0; - virtual NetworkDeviceInfos virtualNetworkDeviceInfos() const = 0; virtual bool isFinished() const = 0; signals: - // Emitted whenever a certain host address has been pinged successfully void hostAddressDiscovered(const QHostAddress &address); - - // Emited whenerver a valid NetworkDeviceInfo has been added - void networkDeviceInfoAdded(const NetworkDeviceInfo &networkDeviceInfo); - void finished(); }; diff --git a/libnymea/network/networkdeviceinfo.cpp b/libnymea/network/networkdeviceinfo.cpp index 658f98e1..e6e6e6e3 100644 --- a/libnymea/network/networkdeviceinfo.cpp +++ b/libnymea/network/networkdeviceinfo.cpp @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2022, nymea GmbH +* Copyright 2013 - 2024, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. @@ -36,38 +36,16 @@ NetworkDeviceInfo::NetworkDeviceInfo() } -NetworkDeviceInfo::NetworkDeviceInfo(const QString &macAddress): - m_macAddress(macAddress) +NetworkDeviceInfo::NetworkDeviceInfo(const QString &macAddress) { - m_macAddressSet = true; + addMacAddress(MacAddress(macAddress)); } NetworkDeviceInfo::NetworkDeviceInfo(const QHostAddress &address): - m_address(address) + m_address{address}, + m_addressSet{true} { - m_addressSet = true; -} -QString NetworkDeviceInfo::macAddress() const -{ - return m_macAddress; -} - -void NetworkDeviceInfo::setMacAddress(const QString &macAddress) -{ - m_macAddress = macAddress; - m_macAddressSet = true; -} - -QString NetworkDeviceInfo::macAddressManufacturer() const -{ - return m_macAddressManufacturer; -} - -void NetworkDeviceInfo::setMacAddressManufacturer(const QString &macAddressManufacturer) -{ - m_macAddressManufacturer = macAddressManufacturer; - m_macAddressManufacturerSet = true; } QHostAddress NetworkDeviceInfo::address() const @@ -92,6 +70,33 @@ void NetworkDeviceInfo::setHostName(const QString &hostName) m_hostNameSet = true; } +MacAddressInfos NetworkDeviceInfo::macAddressInfos() const +{ + return m_macAddressInfos; +} + +void NetworkDeviceInfo::addMacAddress(const MacAddress &macAddress) +{ + if (m_macAddressInfos.hasMacAddress(macAddress)) + return; + + m_macAddressInfos.append(MacAddressInfo(macAddress)); + // Note: we have to sort them in order to compare MacAddressInfos + m_macAddressInfos.sortInfos(); +} + +void NetworkDeviceInfo::addMacAddress(const MacAddress &macAddress, const QString &vendorName) +{ + int index = m_macAddressInfos.indexFromMacAddress(macAddress); + if (index >= 0) { + m_macAddressInfos[index].setVendorName(vendorName); + } else { + m_macAddressInfos.append(MacAddressInfo(macAddress, vendorName)); + // Note: we have to sort them in order to compare MacAddressInfos + m_macAddressInfos.sortInfos(); + } +} + QNetworkInterface NetworkDeviceInfo::networkInterface() const { return m_networkInterface; @@ -103,34 +108,60 @@ void NetworkDeviceInfo::setNetworkInterface(const QNetworkInterface &networkInte m_networkInterfaceSet = true; } +NetworkDeviceInfo::MonitorMode NetworkDeviceInfo::monitorMode() const +{ + return m_monitorMode; +} + +void NetworkDeviceInfo::setMonitorMode(MonitorMode monitorMode) +{ + m_monitorMode = monitorMode; +} + bool NetworkDeviceInfo::isValid() const { - return (!m_address.isNull() || !MacAddress(m_macAddress).isNull()) && m_networkInterface.isValid(); + return (!m_address.isNull() || m_macAddressInfos.isEmpty()) && m_networkInterface.isValid(); } bool NetworkDeviceInfo::isComplete() const { - return m_macAddressSet && m_macAddressManufacturerSet && m_addressSet && m_hostNameSet && m_networkInterfaceSet; + if (m_forceComplete) + return true; + + return !m_macAddressInfos.isEmpty() && m_macAddressInfos.isComplete() && m_addressSet && m_hostNameSet && m_networkInterfaceSet; +} + +void NetworkDeviceInfo::forceComplete() +{ + m_forceComplete = true; } QString NetworkDeviceInfo::incompleteProperties() const { QStringList list; - if (!m_macAddressSet) list.append("MAC not set"); - if (!m_macAddressManufacturerSet) list.append("MAC vendor not set"); - if (!m_hostNameSet) list.append("hostname not set"); - if (!m_networkInterfaceSet) list.append("nework interface not set"); + if (m_macAddressInfos.isEmpty()) + list.append("MAC address not set"); + + if (!m_macAddressInfos.isEmpty() && !m_macAddressInfos.isComplete()) + list.append("MAC infos incomplete"); + + if (!m_hostNameSet) + list.append("hostname not set"); + + if (!m_networkInterfaceSet) + list.append("nework interface not set"); + return list.join(", "); } bool NetworkDeviceInfo::operator==(const NetworkDeviceInfo &other) const { - return MacAddress(m_macAddress) == MacAddress(other.macAddress()) && - m_address == other.address() && - m_hostName == other.hostName() && - m_macAddressManufacturer == other.macAddressManufacturer() && - m_networkInterface.name() == other.networkInterface().name() && - isComplete() == other.isComplete(); + return m_address == other.address() && + m_macAddressInfos == other.macAddressInfos() && + m_hostName == other.hostName() && + m_networkInterface.name() == other.networkInterface().name() && + m_monitorMode == other.monitorMode() && + isComplete() == other.isComplete(); } bool NetworkDeviceInfo::operator!=(const NetworkDeviceInfo &other) const @@ -141,20 +172,30 @@ bool NetworkDeviceInfo::operator!=(const NetworkDeviceInfo &other) const QDebug operator<<(QDebug dbg, const NetworkDeviceInfo &networkDeviceInfo) { QDebugStateSaver saver(dbg); - dbg.nospace() << "NetworkDeviceInfo(" << networkDeviceInfo.address().toString(); + dbg.nospace().noquote() << "NetworkDeviceInfo(" << networkDeviceInfo.address().toString(); - if (!networkDeviceInfo.macAddress().isEmpty()) - dbg.nospace() << ", " << MacAddress(networkDeviceInfo.macAddress()).toString(); + dbg.nospace().noquote() << ", Monitor mode: "; + switch (networkDeviceInfo.monitorMode()) { + case NetworkDeviceInfo::MonitorModeMac: + dbg.nospace().noquote() << "MAC"; + break; + case NetworkDeviceInfo::MonitorModeHostname: + dbg.nospace().noquote() << "hostname"; + break; + case NetworkDeviceInfo::MonitorModeIp: + dbg.nospace().noquote() << "IP"; + break; + } - if (!networkDeviceInfo.macAddressManufacturer().isEmpty()) - dbg.nospace() << " (" << networkDeviceInfo.macAddressManufacturer() << ") "; + foreach (const MacAddressInfo &macInfo, networkDeviceInfo.macAddressInfos()) + dbg.nospace().noquote() << ", " << macInfo; if (!networkDeviceInfo.hostName().isEmpty()) - dbg.nospace() << ", hostname: " << networkDeviceInfo.hostName(); + dbg.nospace().noquote() << ", hostname: " << networkDeviceInfo.hostName(); if (networkDeviceInfo.networkInterface().isValid()) - dbg.nospace() << ", " << networkDeviceInfo.networkInterface().name(); + dbg.nospace().noquote() << ", " << networkDeviceInfo.networkInterface().name(); - dbg.nospace() << ")"; + dbg.nospace().noquote() << ")"; return dbg; } diff --git a/libnymea/network/networkdeviceinfo.h b/libnymea/network/networkdeviceinfo.h index 29f0418e..2d9e62df 100644 --- a/libnymea/network/networkdeviceinfo.h +++ b/libnymea/network/networkdeviceinfo.h @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2022, nymea GmbH +* Copyright 2013 - 2024, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. @@ -38,32 +38,52 @@ #include #include "libnymea.h" +#include "macaddressinfos.h" class LIBNYMEA_EXPORT NetworkDeviceInfo { + Q_GADGET public: + // Virtual hosts are devices with no MAC address available or not unique MAC address and the MAC address can not be used for the NetworkDeviceMonitor. + // Examples for virtual hosts are + // - VPN network hosts (no MAC address) + // - Webservers outside the network (domains) + // - Devices behind older wifi repeaters, multiple hosts (individual devices) have the same virtual MAC address + // - Hosts which are accessable over multiple interfaces within the same network (i.e. WLAN + LAN), + // they can be reached using both MAC addresses and both IP addresses (linux feature) + + enum MonitorMode { + MonitorModeMac = 0x01, // Unique MAC address within the network + MonitorModeHostname = 0x02, // DNS hostname available, but no MAC address or not unique MAC available + MonitorModeIp = 0x03 // Only the IP can be used to monitor, simple ping on reachable + }; + Q_ENUM(MonitorMode) + explicit NetworkDeviceInfo(); explicit NetworkDeviceInfo(const QString &macAddress); explicit NetworkDeviceInfo(const QHostAddress &address); - QString macAddress() const; - void setMacAddress(const QString &macAddress); - - QString macAddressManufacturer() const; - void setMacAddressManufacturer(const QString &macAddressManufacturer); - QHostAddress address() const; void setAddress(const QHostAddress &address); QString hostName() const; void setHostName(const QString &hostName); + MacAddressInfos macAddressInfos() const; + void addMacAddress(const MacAddress &macAddress); + void addMacAddress(const MacAddress &macAddress, const QString &vendorName); + QNetworkInterface networkInterface() const; void setNetworkInterface(const QNetworkInterface &networkInterface); + MonitorMode monitorMode() const; + void setMonitorMode(MonitorMode monitorMode); + bool isValid() const; bool isComplete() const; + void forceComplete(); + QString incompleteProperties() const; bool operator==(const NetworkDeviceInfo &other) const; @@ -71,20 +91,17 @@ public: private: QHostAddress m_address; - QString m_macAddress; - QString m_macAddressManufacturer; + MacAddressInfos m_macAddressInfos; QString m_hostName; QNetworkInterface m_networkInterface; + MonitorMode m_monitorMode = MonitorModeMac; - bool m_macAddressSet = false; - bool m_macAddressManufacturerSet = false; bool m_addressSet = false; bool m_hostNameSet = false; bool m_networkInterfaceSet = false; + bool m_forceComplete = false; }; - QDebug operator<<(QDebug debug, const NetworkDeviceInfo &networkDeviceInfo); - #endif // NETWORKDEVICEINFO_H diff --git a/libnymea/network/networkdeviceinfos.cpp b/libnymea/network/networkdeviceinfos.cpp index a1421205..0f0c750a 100644 --- a/libnymea/network/networkdeviceinfos.cpp +++ b/libnymea/network/networkdeviceinfos.cpp @@ -56,20 +56,21 @@ int NetworkDeviceInfos::indexFromHostAddress(const QHostAddress &address) return -1; } -int NetworkDeviceInfos::indexFromMacAddress(const QString &macAddress) +QList NetworkDeviceInfos::indexFromMacAddress(const QString &macAddress) { return indexFromMacAddress(MacAddress(macAddress)); } -int NetworkDeviceInfos::indexFromMacAddress(const MacAddress &macAddress) +QList NetworkDeviceInfos::indexFromMacAddress(const MacAddress &macAddress) { + QList indices; for (int i = 0; i < size(); i++) { - if (MacAddress(at(i).macAddress()) == macAddress) { - return i; + if (at(i).macAddressInfos().hasMacAddress(macAddress)) { + indices << i; } } - return -1; + return indices; } bool NetworkDeviceInfos::hasHostAddress(const QHostAddress &address) @@ -79,12 +80,12 @@ bool NetworkDeviceInfos::hasHostAddress(const QHostAddress &address) bool NetworkDeviceInfos::hasMacAddress(const QString &macAddress) { - return indexFromMacAddress(macAddress) >= 0; + return !indexFromMacAddress(macAddress).isEmpty(); } bool NetworkDeviceInfos::hasMacAddress(const MacAddress &macAddress) { - return indexFromMacAddress(macAddress) >= 0; + return !indexFromMacAddress(macAddress).isEmpty(); } NetworkDeviceInfo NetworkDeviceInfos::get(const QHostAddress &address) const @@ -98,36 +99,45 @@ NetworkDeviceInfo NetworkDeviceInfos::get(const QHostAddress &address) const return NetworkDeviceInfo(); } -NetworkDeviceInfo NetworkDeviceInfos::get(const QString &macAddress) const -{ - foreach (const NetworkDeviceInfo &networkDeviceInfo, *this) { - if (networkDeviceInfo.macAddress() == macAddress) { - return networkDeviceInfo; - } - } - - return NetworkDeviceInfo(); -} - -NetworkDeviceInfo NetworkDeviceInfos::get(const MacAddress &macAddress) const -{ - return get(macAddress.toString()); -} - -void NetworkDeviceInfos::removeMacAddress(const QString &macAddress) -{ - removeMacAddress(MacAddress(macAddress)); -} - -void NetworkDeviceInfos::removeMacAddress(const MacAddress &macAddress) +void NetworkDeviceInfos::removeHostAddress(const QHostAddress &address) { for (int i = 0; i < size(); i++) { - if (MacAddress(at(i).macAddress()) == macAddress) { + if (at(i).address() == address) { remove(i); } } } +// NetworkDeviceInfo NetworkDeviceInfos::get(const QString &macAddress) const +// { +// foreach (const NetworkDeviceInfo &networkDeviceInfo, *this) { +// if (networkDeviceInfo.macAddress() == macAddress) { +// return networkDeviceInfo; +// } +// } + +// return NetworkDeviceInfo(); +// } + +// NetworkDeviceInfo NetworkDeviceInfos::get(const MacAddress &macAddress) const +// { +// return get(macAddress.toString()); +// } + +// void NetworkDeviceInfos::removeMacAddress(const QString &macAddress) +// { +// removeMacAddress(MacAddress(macAddress)); +// } + +// void NetworkDeviceInfos::removeMacAddress(const MacAddress &macAddress) +// { +// for (int i = 0; i < size(); i++) { +// if (MacAddress(at(i).macAddress()) == macAddress) { +// remove(i); +// } +// } +// } + void NetworkDeviceInfos::sortNetworkDevices() { std::sort(this->begin(), this->end(), [](const NetworkDeviceInfo& a, const NetworkDeviceInfo& b) { diff --git a/libnymea/network/networkdeviceinfos.h b/libnymea/network/networkdeviceinfos.h index 95fedd83..22fd8c4e 100644 --- a/libnymea/network/networkdeviceinfos.h +++ b/libnymea/network/networkdeviceinfos.h @@ -39,25 +39,25 @@ class LIBNYMEA_EXPORT NetworkDeviceInfos : public QVector { - public: explicit NetworkDeviceInfos(); NetworkDeviceInfos(const QVector &other); int indexFromHostAddress(const QHostAddress &address); - int indexFromMacAddress(const QString &macAddress); - int indexFromMacAddress(const MacAddress &macAddress); + QList indexFromMacAddress(const QString &macAddress); + QList indexFromMacAddress(const MacAddress &macAddress); bool hasHostAddress(const QHostAddress &address); bool hasMacAddress(const QString &macAddress); bool hasMacAddress(const MacAddress &macAddress); NetworkDeviceInfo get(const QHostAddress &address) const; - NetworkDeviceInfo get(const QString &macAddress) const; - NetworkDeviceInfo get(const MacAddress &macAddress) const; + // NetworkDeviceInfo get(const QString &macAddress) const; + // NetworkDeviceInfo get(const MacAddress &macAddress) const; - void removeMacAddress(const QString &macAddress); - void removeMacAddress(const MacAddress &macAddress); + // void removeMacAddress(const QString &macAddress); + // void removeMacAddress(const MacAddress &macAddress); + void removeHostAddress(const QHostAddress &address); void sortNetworkDevices(); diff --git a/libnymea/network/networkdevicemonitor.cpp b/libnymea/network/networkdevicemonitor.cpp index 775922b6..453e4612 100644 --- a/libnymea/network/networkdevicemonitor.cpp +++ b/libnymea/network/networkdevicemonitor.cpp @@ -42,8 +42,9 @@ QDebug operator<<(QDebug dbg, NetworkDeviceMonitor *networkDeviceMonitor) QDebugStateSaver saver(dbg); dbg.nospace() << "NetworkDeviceMonitor(" << networkDeviceMonitor->macAddress().toString(); - if (!networkDeviceMonitor->networkDeviceInfo().macAddressManufacturer().isEmpty()) - dbg.nospace() << " - " << networkDeviceMonitor->networkDeviceInfo().macAddressManufacturer(); + // FIXME + // if (!networkDeviceMonitor->networkDeviceInfo().macAddressManufacturer().isEmpty()) + // dbg.nospace() << " - " << networkDeviceMonitor->networkDeviceInfo().macAddressManufacturer(); dbg.nospace() << ", " << networkDeviceMonitor->networkDeviceInfo().address().toString(); dbg.nospace() << ", " << (networkDeviceMonitor->reachable() ? "reachable" : "not reachable"); diff --git a/nymea.pro b/nymea.pro index bb1d03bf..748879b3 100644 --- a/nymea.pro +++ b/nymea.pro @@ -13,7 +13,7 @@ isEmpty(NYMEA_VERSION) { JSON_PROTOCOL_VERSION_MAJOR=8 JSON_PROTOCOL_VERSION_MINOR=1 JSON_PROTOCOL_VERSION="$${JSON_PROTOCOL_VERSION_MAJOR}.$${JSON_PROTOCOL_VERSION_MINOR}" -LIBNYMEA_API_VERSION_MAJOR=8 +LIBNYMEA_API_VERSION_MAJOR=9 LIBNYMEA_API_VERSION_MINOR=0 LIBNYMEA_API_VERSION_PATCH=0 LIBNYMEA_API_VERSION="$${LIBNYMEA_API_VERSION_MAJOR}.$${LIBNYMEA_API_VERSION_MINOR}.$${LIBNYMEA_API_VERSION_PATCH}" From abb656016d82a5cc4412f7385e39c44d8bbe9fac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Fri, 6 Dec 2024 14:58:13 +0100 Subject: [PATCH 4/7] Update monitor --- .../network/networkdevicediscoveryimpl.cpp | 420 ++++++++++++------ .../network/networkdevicediscoveryimpl.h | 19 +- .../networkdevicediscoveryreplyimpl.cpp | 97 ++-- .../network/networkdevicediscoveryreplyimpl.h | 2 +- .../network/networkdevicemonitorimpl.cpp | 63 ++- .../network/networkdevicemonitorimpl.h | 25 +- libnymea/network/macaddressinfos.cpp | 2 +- libnymea/network/networkdevicediscovery.h | 6 +- libnymea/network/networkdeviceinfo.cpp | 46 +- libnymea/network/networkdeviceinfo.h | 16 +- libnymea/network/networkdeviceinfos.cpp | 30 -- libnymea/network/networkdeviceinfos.h | 5 - libnymea/network/networkdevicemonitor.cpp | 17 +- libnymea/network/networkdevicemonitor.h | 9 +- 14 files changed, 504 insertions(+), 253 deletions(-) diff --git a/libnymea-core/hardware/network/networkdevicediscoveryimpl.cpp b/libnymea-core/hardware/network/networkdevicediscoveryimpl.cpp index 60dbc1f5..f6f07472 100644 --- a/libnymea-core/hardware/network/networkdevicediscoveryimpl.cpp +++ b/libnymea-core/hardware/network/networkdevicediscoveryimpl.cpp @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2022, nymea GmbH +* Copyright 2013 - 2024, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. @@ -32,6 +32,7 @@ #include "nymeasettings.h" #include "loggingcategories.h" + #include #include @@ -134,13 +135,17 @@ NetworkDeviceDiscoveryReply *NetworkDeviceDiscoveryImpl::discover() connect(m_currentDiscoveryReply, &NetworkDeviceDiscoveryReplyImpl::finished, this, [this](){ // Finish all pending replies foreach (NetworkDeviceDiscoveryReplyImpl *reply, m_pendingDiscoveryReplies) { - // Sync all network device infos with all pending replies foreach (const NetworkDeviceInfo &info, m_currentDiscoveryReply->networkDeviceInfos()) { reply->addCompleteNetworkDeviceInfo(info); } } + // Update local cache right after a discovery finished + foreach (const NetworkDeviceInfo &info, m_currentDiscoveryReply->networkDeviceInfos()) { + updateCache(info); + } + // Delete the current reply before finishing the pending replies. // Just in case some one restarts a discovery on finished, a new internal // object should be created @@ -213,77 +218,144 @@ bool NetworkDeviceDiscoveryImpl::running() const return m_running; } -NetworkDeviceMonitor *NetworkDeviceDiscoveryImpl::registerMonitor(const MacAddress &macAddress) +// NetworkDeviceMonitor *NetworkDeviceDiscoveryImpl::registerMonitor(const MacAddress &macAddress) +// { +// if (macAddress.isNull()) { +// qCWarning(dcNetworkDeviceDiscovery()) << "Could not register monitor for invalid" << macAddress; +// return nullptr; +// } + +// // Make sure we create only one monitor per MAC and keep track how many user +// // have access to this monitor otherwise an unregister could cause a crash in +// // an other plugin plugin which might still need it +// if (m_monitors.contains(macAddress)) { +// m_monitorsReferenceCount[macAddress] += 1; +// qCInfo(dcNetworkDeviceDiscovery()) << "Register network device monitor for" << macAddress << "which already exists. Returning existing monitor having now" << m_monitorsReferenceCount[macAddress] << "references."; +// return m_monitors.value(macAddress); +// } + +// qCInfo(dcNetworkDeviceDiscovery()) << "Register new network device monitor for" << macAddress; + +// // Fill in cached information +// NetworkDeviceInfo info; +// if (m_networkInfoCache.contains(macAddress)) { +// info = m_networkInfoCache.value(macAddress); +// } else { +// info.setMacAddress(macAddress.toString()); +// } + +// NetworkDeviceMonitorImpl *monitor = new NetworkDeviceMonitorImpl(macAddress, this); +// monitor->setNetworkDeviceInfo(info); +// monitor->setLastSeen(m_lastSeen.value(macAddress, QDateTime())); +// m_monitors.insert(macAddress, monitor); +// m_monitorsReferenceCount[macAddress] = 1; + +// if (!available()) { +// qCWarning(dcNetworkDeviceDiscovery()) << "Registered monitor but the hardware resource is not available. The monitor will not work as expected" << monitor; +// return monitor; +// } + +// // Restart the monitor timer since we evaluate this one now +// m_monitorTimer->start(); + +// if (!monitor->networkDeviceInfo().isValid()) { +// qCDebug(dcNetworkDeviceDiscovery()) << "Adding network device monitor for unresolved mac address. Starting a discovery..."; +// NetworkDeviceDiscoveryReply *reply = discover(); +// connect(reply, &NetworkDeviceDiscoveryReply::finished, reply, &NetworkDeviceDiscoveryReply::deleteLater); +// } else { +// evaluateMonitor(monitor); +// } + +// qCDebug(dcNetworkDeviceDiscovery()) << "Registered successfully" << monitor; +// return monitor; +// } + +NetworkDeviceMonitor *NetworkDeviceDiscoveryImpl::registerMonitor(Thing *thing) { - if (macAddress.isNull()) { - qCWarning(dcNetworkDeviceDiscovery()) << "Could not register monitor for invalid" << macAddress; + if (!thing->thingClass().interfaces().contains("networkdevice")) { + qCWarning(dcNetworkDeviceDiscovery()) << "Cannot register network device monitor because the thing" + << thing << "does not implement the \"networkdevice\" interface."; return nullptr; } - // FIXME - return nullptr; + MacAddress macAddress(thing->paramValue("macAddress").toString()); + QString hostName = thing->paramValue("hostName").toString(); + QHostAddress address(thing->paramValue("address").toString()); - // // Make sure we create only one monitor per MAC and keep track how many user - // // have access to this monitor otherwise an unregister could cause a crash in - // // an other plugin plugin which might still need it - // if (m_monitors.contains(macAddress)) { - // m_monitorsReferenceCount[macAddress] += 1; - // qCInfo(dcNetworkDeviceDiscovery()) << "Register network device monitor for" << macAddress << "which already exists. Returning existing monitor having now" << m_monitorsReferenceCount[macAddress] << "references."; - // return m_monitors.value(macAddress); - // } + NetworkDeviceInfo::MonitorMode mode = NetworkDeviceInfo::MonitorModeMac; + if (macAddress.isNull()) { + if (!hostName.isEmpty()) { + mode = NetworkDeviceInfo::MonitorModeHostName; + } else { + if (address.isNull()) { + qCWarning(dcNetworkDeviceDiscovery()) << "Cannot register monitor for thing" << thing << + "because there are not enough information available." << + "At least one parameter from the networkdevice interface needs to be provided." << thing->params(); + return nullptr; + } else { + mode = NetworkDeviceInfo::MonitorModeIp; + } + } + } // else we use the MAC address mode - // qCInfo(dcNetworkDeviceDiscovery()) << "Register new network device monitor for" << macAddress; - - // // Fill in cached information - // NetworkDeviceInfo info; - // if (m_networkInfoCache.contains(macAddress)) { - // info = m_networkInfoCache.value(macAddress); - // } else { - // info.setMacAddress(macAddress.toString()); - // } - - // NetworkDeviceMonitorImpl *monitor = new NetworkDeviceMonitorImpl(macAddress, this); - // monitor->setNetworkDeviceInfo(info); - // monitor->setLastSeen(m_lastSeen.value(macAddress, QDateTime())); - // m_monitors.insert(macAddress, monitor); - // m_monitorsReferenceCount[macAddress] = 1; - - // if (!available()) { - // qCWarning(dcNetworkDeviceDiscovery()) << "Registered monitor but the hardware resource is not available. The monitor will not work as expected" << monitor; - // return monitor; - // } - - // // Restart the monitor timer since we evaluate this one now - // m_monitorTimer->start(); - - // if (!monitor->networkDeviceInfo().isValid()) { - // qCDebug(dcNetworkDeviceDiscovery()) << "Adding network device monitor for unresolved mac address. Starting a discovery..."; - // NetworkDeviceDiscoveryReply *reply = discover(); - // connect(reply, &NetworkDeviceDiscoveryReply::finished, reply, &NetworkDeviceDiscoveryReply::deleteLater); - // } else { - // evaluateMonitor(monitor); - // } - - // qCDebug(dcNetworkDeviceDiscovery()) << "Registered successfully" << monitor; - // return monitor; -} - -void NetworkDeviceDiscoveryImpl::unregisterMonitor(const MacAddress &macAddress) -{ - if (m_monitorsReferenceCount.contains(macAddress)) { - m_monitorsReferenceCount[macAddress] -= 1; - if (m_monitorsReferenceCount[macAddress] > 0) { - qCDebug(dcNetworkDeviceDiscovery()) << "Unregistered monitor for" << macAddress.toString() << "but keeping the monitor. There are still" << m_monitorsReferenceCount[macAddress] << "references to it."; - return; + // Check if we already have a monitor for these settings... + NetworkDeviceMonitorImpl *internalMonitor = nullptr; + for (QHash>::const_iterator iterator = m_monitors.cbegin(), + end = m_monitors.cend(); iterator != end; ++iterator) { + if (iterator.key()->address() == address && iterator.key()->macAddress() == macAddress && iterator.key()->hostName() == hostName) { + internalMonitor = iterator.key(); + break; } } - if (m_monitors.contains(macAddress)) { - NetworkDeviceMonitorImpl *monitor = m_monitors.take(macAddress); - qCInfo(dcNetworkDeviceDiscovery()) << "Unregister" << monitor; - monitor->deleteLater(); - m_monitorsReferenceCount.remove(macAddress); + if (internalMonitor) { + qCDebug(dcNetworkDeviceDiscovery()) << "Already have an internal monitor for this network device" << internalMonitor; + } else { + // Create a new monitor for the internal use + internalMonitor = new NetworkDeviceMonitorImpl(macAddress, hostName, address, this); } + + internalMonitor->setMonitorMode(mode); + + if (m_networkInfoCache.isEmpty()) { + qCDebug(dcNetworkDeviceDiscovery()) << "Cache is empty. Starting an internal discovery..."; + NetworkDeviceDiscoveryReply *reply = discover(); + connect(reply, &NetworkDeviceDiscoveryReply::finished, reply, &NetworkDeviceDiscoveryReply::deleteLater); + } + + // Find and set the network device info + for (int i = 0; i < m_networkInfoCache.count(); i++) { + const NetworkDeviceInfo networkDeviceInfo = m_networkInfoCache.at(i); + + switch (internalMonitor->monitorMode()) { + case NetworkDeviceInfo::MonitorModeMac: + // Search the unique mac address + if (networkDeviceInfo.macAddressInfos().hasMacAddress(internalMonitor->macAddress())) { + qCDebug(dcNetworkDeviceDiscovery()) << "MAC monitor:" << networkDeviceInfo; + internalMonitor->setNetworkDeviceInfo(networkDeviceInfo); + } + break; + case NetworkDeviceInfo::MonitorModeHostName: + // Search the unique mac address + if (networkDeviceInfo.hostName() == internalMonitor->hostName()) { + qCDebug(dcNetworkDeviceDiscovery()) << "Host name monitor:" << networkDeviceInfo; + internalMonitor->setNetworkDeviceInfo(networkDeviceInfo); + } + break; + case NetworkDeviceInfo::MonitorModeIp: + // Search the unique mac address + if (networkDeviceInfo.address() == internalMonitor->address()) { + qCDebug(dcNetworkDeviceDiscovery()) << "IP monitor:" << networkDeviceInfo; + internalMonitor->setNetworkDeviceInfo(networkDeviceInfo); + } + break; + } + } + + // Create a new plugin monitor object we are going to return... + NetworkDeviceMonitorImpl *pluginMonitor = createPluginMonitor(internalMonitor); + qCDebug(dcNetworkDeviceDiscovery()) << "Registered successfully" << pluginMonitor; + return pluginMonitor; } void NetworkDeviceDiscoveryImpl::unregisterMonitor(NetworkDeviceMonitor *networkDeviceMonitor) @@ -291,11 +363,7 @@ void NetworkDeviceDiscoveryImpl::unregisterMonitor(NetworkDeviceMonitor *network if (!networkDeviceMonitor) return; - if (!m_monitors.values().contains(qobject_cast(networkDeviceMonitor))) - return; - - // FIXME - //unregisterMonitor(MacAddress(networkDeviceMonitor->networkDeviceInfo().macAddress())); + cleanupPluginMonitor(qobject_cast(networkDeviceMonitor)); } PingReply *NetworkDeviceDiscoveryImpl::ping(const QHostAddress &address, uint retries) @@ -481,11 +549,11 @@ void NetworkDeviceDiscoveryImpl::watchPingReply(PingReply *reply) // } // Update any monitor - foreach (NetworkDeviceMonitorImpl *monitor, m_monitors.values()) { - if (monitor->networkDeviceInfo().address() == reply->targetHostAddress()) { - processMonitorPingResult(reply, monitor); - } - } + // foreach (NetworkDeviceMonitorImpl *monitor, m_monitors.values()) { + // if (monitor->networkDeviceInfo().address() == reply->targetHostAddress()) { + // processMonitorPingResult(reply, monitor); + // } + // } }); } @@ -503,36 +571,41 @@ void NetworkDeviceDiscoveryImpl::loadNetworkDeviceCache() if (cacheVersion == CACHE_VERSION) { m_cacheSettings->beginGroup("NetworkDeviceInfos"); - foreach (const QString &macAddress, m_cacheSettings->childGroups()) { - m_cacheSettings->beginGroup(macAddress); + foreach (const QString &addressString, m_cacheSettings->childGroups()) { - // MacAddress mac(macAddress); - // QDateTime lastSeen = QDateTime::fromMSecsSinceEpoch(m_cacheSettings->value("lastSeen").toLongLong()); + m_cacheSettings->beginGroup(addressString); - // // Remove the info from the cache if not seen fo the last 30 days... - // if (lastSeen.date().addDays(m_cacheCleanupPeriod) < now.date()) { - // qCDebug(dcNetworkDeviceDiscovery()) << "Removing network device cache entry since it did not show up within the last" << m_cacheCleanupPeriod << "days" << mac.toString(); - // m_cacheSettings->remove(""); - // m_cacheSettings->endGroup(); // mac address - // continue; - // } + QHostAddress address(addressString); + QDateTime lastSeen = QDateTime::fromMSecsSinceEpoch(m_cacheSettings->value("lastSeen").toLongLong()); - // NetworkDeviceInfo info(mac.toString()); - // info.setAddress(QHostAddress(m_cacheSettings->value("address").toString())); - // info.setHostName(m_cacheSettings->value("hostName").toString()); - // info.setMacAddressManufacturer(m_cacheSettings->value("manufacturer").toString()); - // info.setNetworkInterface(QNetworkInterface::interfaceFromName(m_cacheSettings->value("interface").toString())); + // Remove the info from the cache if not seen fo the last 30 days... + if (lastSeen.date().addDays(m_cacheCleanupPeriod) < now.date()) { + qCDebug(dcNetworkDeviceDiscovery()) << "Removing network device cache entry since it did not show up within the last" << m_cacheCleanupPeriod << "days" << address.toString(); + m_cacheSettings->remove(""); + m_cacheSettings->endGroup(); // mac address + continue; + } - // if (info.isValid() && info.isComplete()) { - // qCDebug(dcNetworkDeviceDiscovery()) << "Loaded cached" << info << "last seen" << lastSeen.toString(); - // m_networkInfoCache[mac] = info; - // m_lastSeen[mac] = lastSeen; - // } else { - // qCWarning(dcNetworkDeviceDiscovery()) << "Clean up invalid cached network device info from cache" << info; - // m_cacheSettings->remove(""); - // } + NetworkDeviceInfo info(address); + info.setHostName(m_cacheSettings->value("hostName").toString()); + info.setNetworkInterface(QNetworkInterface::interfaceFromName(m_cacheSettings->value("interface").toString())); - m_cacheSettings->endGroup(); // mac address + int size = m_cacheSettings->beginReadArray("mac"); + for (int i = 0; i < size; i++) { + m_cacheSettings->setArrayIndex(i); + MacAddress macAddress(m_cacheSettings->value("mac").toString()); + QString vendor = m_cacheSettings->value("vendor").toString(); + info.addMacAddress(macAddress, vendor); + // Cache the mac information for less DB access + if (!macAddress.isNull() && !vendor.isEmpty()) { + m_macVendorCache[macAddress] = vendor; + } + } + m_cacheSettings->endArray(); + m_cacheSettings->endGroup(); // address + qCDebug(dcNetworkDeviceDiscovery()) << "Loaded cached" << info << "last seen" << lastSeen.toString(); + m_networkInfoCache.append(info); + m_lastSeen[info.address()] = lastSeen; } m_cacheSettings->endGroup(); // NetworkDeviceInfos @@ -566,35 +639,56 @@ void NetworkDeviceDiscoveryImpl::removeFromNetworkDeviceCache(const MacAddress & // m_cacheSettings->sync(); } +void NetworkDeviceDiscoveryImpl::removeFromNetworkDeviceCache(const QHostAddress &address) +{ + if (address.isNull()) + return; + + m_networkInfoCache.removeHostAddress(address); + m_lastSeen.remove(address); + m_cacheSettings->beginGroup("NetworkDeviceInfos"); + m_cacheSettings->beginGroup(address.toString()); + m_cacheSettings->remove(""); + m_cacheSettings->endGroup(); // address + m_cacheSettings->endGroup(); // NetworkDeviceInfos + m_cacheSettings->sync(); +} + void NetworkDeviceDiscoveryImpl::saveNetworkDeviceCache(const NetworkDeviceInfo &deviceInfo) { if (!deviceInfo.isValid() || !deviceInfo.isComplete()) return; - // m_cacheSettings->beginGroup("NetworkDeviceInfos"); - // m_cacheSettings->beginGroup(deviceInfo.macAddress()); - // m_cacheSettings->setValue("address", deviceInfo.address().toString()); - // m_cacheSettings->setValue("hostName", deviceInfo.hostName()); - // m_cacheSettings->setValue("manufacturer", deviceInfo.macAddressManufacturer()); - // m_cacheSettings->setValue("interface", deviceInfo.networkInterface().name()); - // m_cacheSettings->setValue("lastSeen", convertMinuteBased(m_lastSeen.value(MacAddress(deviceInfo.macAddress()))).toMSecsSinceEpoch()); - // m_cacheSettings->endGroup(); // mac address - // m_cacheSettings->endGroup(); // NetworkDeviceInfos - // m_cacheSettings->sync(); + m_cacheSettings->beginGroup("NetworkDeviceInfos"); + m_cacheSettings->beginGroup(deviceInfo.address().toString()); + m_cacheSettings->setValue("hostName", deviceInfo.hostName()); + m_cacheSettings->setValue("interface", deviceInfo.networkInterface().name()); + m_cacheSettings->setValue("lastSeen", convertMinuteBased(m_lastSeen.value(deviceInfo.address())).toMSecsSinceEpoch()); + + if (!deviceInfo.macAddressInfos().isEmpty()){ + m_cacheSettings->beginWriteArray("mac"); + for (int i = 0; i < deviceInfo.macAddressInfos().size(); i++) { + m_cacheSettings->setArrayIndex(i); + m_cacheSettings->setValue("mac", deviceInfo.macAddressInfos().at(i).macAddress().toString()); + m_cacheSettings->setValue("vendor", deviceInfo.macAddressInfos().at(i).vendorName()); + } + m_cacheSettings->endArray(); // mac + } + + m_cacheSettings->endGroup(); // address + m_cacheSettings->endGroup(); // NetworkDeviceInfos + m_cacheSettings->sync(); } void NetworkDeviceDiscoveryImpl::updateCache(const NetworkDeviceInfo &deviceInfo) { - // MacAddress macAddress(deviceInfo.macAddress()); - // if (macAddress.isNull()) - // return; - - // FIXME - // if (m_monitors.contains(macAddress)) { - // NetworkDeviceMonitorImpl *monitor = m_monitors.value(macAddress); - // monitor->setNetworkDeviceInfo(deviceInfo); - // } - + // Update monitors + foreach (NetworkDeviceMonitorImpl *monitor, m_monitors.keys()) { + if (monitor->isMyNetworkDeviceInfo(deviceInfo)) { + monitor->setNetworkDeviceInfo(deviceInfo); + break; + } + } // Save only if changed int index = m_networkInfoCache.indexFromHostAddress(deviceInfo.address()); @@ -613,8 +707,9 @@ void NetworkDeviceDiscoveryImpl::updateCache(const NetworkDeviceInfo &deviceInfo void NetworkDeviceDiscoveryImpl::evaluateMonitor(NetworkDeviceMonitorImpl *monitor) { - if (!monitor->networkDeviceInfo().isValid()) - return; + // if (!monitor->networkDeviceInfo().isValid()) + // return; + if (monitor->currentPingReply()) return; @@ -673,7 +768,7 @@ void NetworkDeviceDiscoveryImpl::evaluateMonitor(NetworkDeviceMonitorImpl *monit void NetworkDeviceDiscoveryImpl::processArpTraffic(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress) { QDateTime now = QDateTime::currentDateTime(); - m_lastSeen[macAddress] = now; + // m_lastSeen[macAddress] = now; // FIXME @@ -775,6 +870,57 @@ QDateTime NetworkDeviceDiscoveryImpl::convertMinuteBased(const QDateTime &dateTi return dateTimeToConvert; } +NetworkDeviceMonitorImpl *NetworkDeviceDiscoveryImpl::createPluginMonitor(NetworkDeviceMonitorImpl *internalMonitor) +{ + NetworkDeviceMonitorImpl *pluginMonitor = new NetworkDeviceMonitorImpl(internalMonitor->macAddress(), internalMonitor->hostName(), internalMonitor->address(), this); + pluginMonitor->setNetworkDeviceInfo(internalMonitor->networkDeviceInfo()); + pluginMonitor->setMonitorMode(internalMonitor->monitorMode()); + pluginMonitor->setLastSeen(internalMonitor->lastSeen()); + pluginMonitor->setLastConnectionAttempt(internalMonitor->lastConnectionAttempt()); + pluginMonitor->setPingRetries(internalMonitor->pingRetries()); + pluginMonitor->setReachable(internalMonitor->reachable()); + + // internal monitor --> plugin monitor + connect(internalMonitor, &NetworkDeviceMonitorImpl::reachableChanged, pluginMonitor, [pluginMonitor](bool reachable){ + pluginMonitor->setReachable(reachable); + }); + connect(internalMonitor, &NetworkDeviceMonitorImpl::lastSeenChanged, pluginMonitor, [pluginMonitor](const QDateTime &lastSeen){ + pluginMonitor->setLastSeen(lastSeen); + }); + connect(internalMonitor, &NetworkDeviceMonitorImpl::networkDeviceInfoChanged, pluginMonitor, [pluginMonitor](const NetworkDeviceInfo &networkDeviceInfo){ + pluginMonitor->setNetworkDeviceInfo(networkDeviceInfo); + }); + + // plugin monitor --> internal monitor + connect(pluginMonitor, &NetworkDeviceMonitorImpl::pingRetriesChanged, internalMonitor, [internalMonitor](uint pingRetries){ + internalMonitor->setPingRetries(pingRetries); + }); + + // In case the plugin user is deleting the monitor object, we need to clean up here and check if we can remove the internal monitor + connect(pluginMonitor, &NetworkDeviceDiscoveryImpl::destroyed, this, [this, pluginMonitor](QObject *) { + cleanupPluginMonitor(pluginMonitor); + }); + + return pluginMonitor; +} + +void NetworkDeviceDiscoveryImpl::cleanupPluginMonitor(NetworkDeviceMonitorImpl *pluginMonitor) +{ + qCDebug(dcNetworkDeviceDiscovery()) << "Unregister plugin monitor" << pluginMonitor; + foreach (NetworkDeviceMonitorImpl *internalMonitor, m_monitors.keys()) { + if (m_monitors.value(internalMonitor).contains(pluginMonitor)) { + m_monitors[internalMonitor].removeAll(pluginMonitor); + + if (m_monitors.value(internalMonitor).isEmpty()) { + qCDebug(dcNetworkDeviceDiscovery()) << "No monitor registered for this network device any more. Unregister internal monitor" << internalMonitor; + // Last refference for this monitor, nobody need this any more. Clean up... + m_monitors.remove(internalMonitor); + internalMonitor->deleteLater(); + } + } + } +} + void NetworkDeviceDiscoveryImpl::onArpResponseReceived(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress) { // Ignore ARP from zero mac @@ -802,11 +948,11 @@ void NetworkDeviceDiscoveryImpl::onArpRequstReceived(const QNetworkInterface &in void NetworkDeviceDiscoveryImpl::evaluateMonitors() { bool monitorRequiresRediscovery = false; - foreach (NetworkDeviceMonitorImpl *monitor, m_monitors) { + foreach (NetworkDeviceMonitorImpl *monitor, m_monitors.keys()) { evaluateMonitor(monitor); // Check if there is any monitor which has not be seen since - if (!monitor->reachable() && monitor->lastConnectionAttempt().isValid() && longerAgoThan(monitor->lastSeen(), m_monitorInterval)) { + if (!monitor->reachable() /*&& monitor->lastConnectionAttempt().isValid()*/ && longerAgoThan(monitor->lastSeen(), m_monitorInterval)) { monitorRequiresRediscovery = true; } } @@ -817,20 +963,20 @@ void NetworkDeviceDiscoveryImpl::evaluateMonitors() connect(reply, &NetworkDeviceDiscoveryReply::finished, reply, &NetworkDeviceDiscoveryReply::deleteLater); } - // Do some cache housekeeping if required - if (m_lastCacheHousekeeping.addDays(1) < QDateTime::currentDateTime()) { - qCInfo(dcNetworkDeviceDiscovery()) << "Starting cache housekeeping since it is more than one day since the last clanup..."; - QDateTime now = QDateTime::currentDateTime(); - foreach (const MacAddress &mac, m_lastSeen.keys()) { - // Remove the info from the cache if not seen fo the last 30 days... - if (m_lastSeen.value(mac).date().addDays(m_cacheCleanupPeriod) < QDateTime::currentDateTime().date()) { - qCDebug(dcNetworkDeviceDiscovery()) << "Removing network device cache entry since it did not show up within the last" << m_cacheCleanupPeriod << "days" << mac.toString(); - removeFromNetworkDeviceCache(mac); - } - } - - m_lastCacheHousekeeping = now; - } + // FIXME + // // Do some cache housekeeping if required + // if (m_lastCacheHousekeeping.addDays(1) < QDateTime::currentDateTime()) { + // qCInfo(dcNetworkDeviceDiscovery()) << "Starting cache housekeeping since it is more than one day since the last clanup..."; + // QDateTime now = QDateTime::currentDateTime(); + // foreach (const MacAddress &mac, m_lastSeen.keys()) { + // // Remove the info from the cache if not seen fo the last 30 days... + // if (m_lastSeen.value(mac).date().addDays(m_cacheCleanupPeriod) < QDateTime::currentDateTime().date()) { + // qCDebug(dcNetworkDeviceDiscovery()) << "Removing network device cache entry since it did not show up within the last" << m_cacheCleanupPeriod << "days" << mac.toString(); + // removeFromNetworkDeviceCache(mac); + // } + // } + // m_lastCacheHousekeeping = now; + // } } void NetworkDeviceDiscoveryImpl::finishDiscovery() diff --git a/libnymea-core/hardware/network/networkdevicediscoveryimpl.h b/libnymea-core/hardware/network/networkdevicediscoveryimpl.h index 859eb102..30c41ea3 100644 --- a/libnymea-core/hardware/network/networkdevicediscoveryimpl.h +++ b/libnymea-core/hardware/network/networkdevicediscoveryimpl.h @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2022, nymea GmbH +* Copyright 2013 - 2024, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. @@ -31,10 +31,11 @@ #ifndef NETWORKDEVICEDISCOVERYIMPL_H #define NETWORKDEVICEDISCOVERYIMPL_H +#include #include #include -#include #include +#include #include #include @@ -66,9 +67,7 @@ public: NetworkDeviceDiscoveryReply *discover() override; - NetworkDeviceMonitor *registerMonitor(const MacAddress &macAddress) override; - - void unregisterMonitor(const MacAddress &macAddress) override; + NetworkDeviceMonitor *registerMonitor(Thing *thing) override; void unregisterMonitor(NetworkDeviceMonitor *networkDeviceMonitor) override; PingReply *ping(const QHostAddress &address, uint retries = 3) override; @@ -94,6 +93,7 @@ private: MacAddressDatabase *m_macAddressDatabase = nullptr; ArpSocket *m_arpSocket = nullptr; Ping *m_ping = nullptr; + bool m_enabled = true; bool m_running = false; @@ -112,9 +112,8 @@ private: QList m_runningMacDatabaseReplies; QList m_runningPingReplies; - QHash m_monitors; - QHash m_monitorsReferenceCount; - QHash m_lastSeen; + QHash> m_monitors; + QHash m_lastSeen; QHash m_macVendorCache; @@ -131,6 +130,7 @@ private: void loadNetworkDeviceCache(); void removeFromNetworkDeviceCache(const MacAddress &macAddress); + void removeFromNetworkDeviceCache(const QHostAddress &address); void saveNetworkDeviceCache(const NetworkDeviceInfo &deviceInfo); void updateCache(const NetworkDeviceInfo &deviceInfo); @@ -142,6 +142,9 @@ private: bool longerAgoThan(const QDateTime &dateTime, uint minutes); QDateTime convertMinuteBased(const QDateTime &dateTime = QDateTime()); + NetworkDeviceMonitorImpl *createPluginMonitor(NetworkDeviceMonitorImpl *internalMonitor); + void cleanupPluginMonitor(NetworkDeviceMonitorImpl *pluginMonitor); + private slots: void onArpResponseReceived(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress); void onArpRequstReceived(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress); diff --git a/libnymea-core/hardware/network/networkdevicediscoveryreplyimpl.cpp b/libnymea-core/hardware/network/networkdevicediscoveryreplyimpl.cpp index 453b2b12..4192ecd3 100644 --- a/libnymea-core/hardware/network/networkdevicediscoveryreplyimpl.cpp +++ b/libnymea-core/hardware/network/networkdevicediscoveryreplyimpl.cpp @@ -62,16 +62,12 @@ void NetworkDeviceDiscoveryReplyImpl::setFinished(bool finished) void NetworkDeviceDiscoveryReplyImpl::processPingResponse(const QHostAddress &address, const QString &hostName) { if (m_networkDeviceCache.contains(address)) { - // Update existing hostname... m_networkDeviceCache[address].setHostName(hostName); - evaluateMonitorMode(address); } else { - // Adding new host... NetworkDeviceInfo info; info.setAddress(address); info.setHostName(hostName); m_networkDeviceCache.insert(address, info); - evaluateMonitorMode(address); // First time seeing this host address emit hostAddressDiscovered(address); @@ -93,8 +89,6 @@ void NetworkDeviceDiscoveryReplyImpl::processArpResponse(const QNetworkInterface // First time seeing this host address emit hostAddressDiscovered(address); } - - evaluateMonitorMode(address); } void NetworkDeviceDiscoveryReplyImpl::processMacManufacturer(const MacAddress &macAddress, const QString &manufacturer) @@ -105,7 +99,6 @@ void NetworkDeviceDiscoveryReplyImpl::processMacManufacturer(const MacAddress &m foreach (const NetworkDeviceInfo &info, m_networkDeviceCache) { if (info.macAddressInfos().hasMacAddress(macAddress)) { m_networkDeviceCache[info.address()].addMacAddress(macAddress, manufacturer); - evaluateMonitorMode(info.address()); } } } @@ -128,11 +121,12 @@ void NetworkDeviceDiscoveryReplyImpl::processDiscoveryFinished() qCDebug(dcNetworkDeviceDiscovery()) << "Adding incomplete" << info << "to the final result:" << info.incompleteProperties(); m_networkDeviceCache[address].forceComplete(); - - evaluateMonitorMode(address); - m_networkDeviceInfos.append(m_networkDeviceCache.value(address)); + m_networkDeviceInfos.append(m_networkDeviceCache.take(address)); } + // Evaluate overall monitor mode... + evaluateMonitorMode(); + // Done, lets sort the result and inform m_networkDeviceInfos.sortNetworkDevices(); @@ -159,55 +153,72 @@ void NetworkDeviceDiscoveryReplyImpl::addCompleteNetworkDeviceInfo(const Network m_networkDeviceInfos.append(networkDeviceInfo); } -void NetworkDeviceDiscoveryReplyImpl::evaluateMonitorMode(const QHostAddress &address) +void NetworkDeviceDiscoveryReplyImpl::evaluateMonitorMode() { - qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: Evaluating monitor mode for host" << address.toString(); + for (int i = 0; i < m_networkDeviceInfos.size(); i++) { - if (m_networkDeviceCache.value(address).macAddressInfos().isEmpty()) { - // Not discovered yet, or this is a virtual host like VPN - if (m_networkDeviceCache.value(address).hostName().isEmpty()) { - m_networkDeviceCache[address].setMonitorMode(NetworkDeviceInfo::MonitorModeIp); - qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: No MAC address and no hostname, using IP only"; - } else { - qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: No MAC address, but we have a hostname."; - m_networkDeviceCache[address].setMonitorMode(NetworkDeviceInfo::MonitorModeHostname); - } + const NetworkDeviceInfo info = m_networkDeviceInfos.at(i); + qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: Evaluating host" << info.address().toString(); - } else { - // We have at least one mac address, check if there are other network devices with this MAC or if we have multiple MAC addresses - if (m_networkDeviceCache.value(address).macAddressInfos().count() == 1) { + NetworkDeviceInfo::MonitorMode mode = NetworkDeviceInfo::MonitorModeMac; + + if (info.macAddressInfos().isEmpty()) { + + // No MAC address found, no ARP for this host, probably a VPN client + if (info.hostName().isEmpty()) { + qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: --> No MAC address and no host name, using MonitorModeIp"; + mode = NetworkDeviceInfo::MonitorModeIp; + } else { + qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: --> No MAC address, but we have a host name, suing MonitorModeHostName"; + mode = NetworkDeviceInfo::MonitorModeHostName; + } + + } else if (info.macAddressInfos().size() == 1) { + + // Single mac address for this host.. + MacAddress macAddress = info.macAddressInfos().constFirst().macAddress(); + bool macAddressIsUnique = true; - bool uniqueMac = true; // Check if this mac is unique - foreach (const NetworkDeviceInfo &info, m_networkDeviceCache) { - if (info.address() == address) + foreach (const NetworkDeviceInfo &networkDeviceInfo, m_networkDeviceInfos) { + + // Skip our self... + if (networkDeviceInfo.address() == info.address()) continue; - if (info.macAddressInfos().hasMacAddress(m_networkDeviceCache.value(address).macAddressInfos().first().macAddress())) { - uniqueMac = false; + if (networkDeviceInfo.macAddressInfos().hasMacAddress(macAddress)) { + macAddressIsUnique = false; break; } } - if (!uniqueMac) { - if (m_networkDeviceCache.value(address).hostName().isEmpty()) { - m_networkDeviceCache[address].setMonitorMode(NetworkDeviceInfo::MonitorModeIp); - qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: The MAC address of" << address.toString() << "is not unique in this network and no hostname available, using IP only"; + if (!macAddressIsUnique) { + if (info.hostName().isEmpty()) { + qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: --> the MAC address of" << info.address().toString() << "is not unique in this network and no host name available, usgin MonitorModeIp"; + mode = NetworkDeviceInfo::MonitorModeIp; } else { - qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: The MAC address of" << address.toString() << "is not unique in this network but we have a hostname"; - m_networkDeviceCache[address].setMonitorMode(NetworkDeviceInfo::MonitorModeHostname); + qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: --> the MAC address of" << info.address().toString() << "is not unique in this network but we have a host name, usgin MonitorModeHostName"; + mode = NetworkDeviceInfo::MonitorModeHostName; } - } - } else { - // Multiple MAC addresses - if (m_networkDeviceCache.value(address).hostName().isEmpty()) { - m_networkDeviceCache[address].setMonitorMode(NetworkDeviceInfo::MonitorModeIp); - qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: Multiple MAC addresses and no hostname, using IP only"; } else { - qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: Multiple MAC addresses, but we have a hostname."; - m_networkDeviceCache[address].setMonitorMode(NetworkDeviceInfo::MonitorModeHostname); + qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: --> the MAC address of" << info.address().toString() << "is unique in this network, usgin MonitorModeMac"; + mode = NetworkDeviceInfo::MonitorModeMac; + } + + } else if (info.macAddressInfos().size() > 1) { + + // Multiple MAC addresses + if (info.hostName().isEmpty()) { + qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: --> multiple MAC addresses and no host name, usgin MonitorModeIp"; + mode = NetworkDeviceInfo::MonitorModeIp; + } else { + qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: --> multiple MAC addresses, but we have a host name, usgin MonitorModeHostName"; + mode = NetworkDeviceInfo::MonitorModeHostName; } } + + m_networkDeviceInfos[i].setMonitorMode(mode); + qCDebug(dcNetworkDeviceDiscovery()) << "MonitorMode: --> Final" << m_networkDeviceInfos.at(i); } } diff --git a/libnymea-core/hardware/network/networkdevicediscoveryreplyimpl.h b/libnymea-core/hardware/network/networkdevicediscoveryreplyimpl.h index c41f2922..162ae6d9 100644 --- a/libnymea-core/hardware/network/networkdevicediscoveryreplyimpl.h +++ b/libnymea-core/hardware/network/networkdevicediscoveryreplyimpl.h @@ -71,7 +71,7 @@ private: NetworkDeviceInfos m_networkDeviceInfos; - void evaluateMonitorMode(const QHostAddress &address); + void evaluateMonitorMode(); }; } diff --git a/libnymea-core/hardware/network/networkdevicemonitorimpl.cpp b/libnymea-core/hardware/network/networkdevicemonitorimpl.cpp index 2bc0b19a..347d9a74 100644 --- a/libnymea-core/hardware/network/networkdevicemonitorimpl.cpp +++ b/libnymea-core/hardware/network/networkdevicemonitorimpl.cpp @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2022, nymea GmbH +* Copyright 2013 - 2024, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. @@ -35,9 +35,11 @@ Q_DECLARE_LOGGING_CATEGORY(dcNetworkDeviceDiscovery) namespace nymeaserver { -NetworkDeviceMonitorImpl::NetworkDeviceMonitorImpl(const MacAddress &macAddress, QObject *parent) : - NetworkDeviceMonitor(parent), - m_macAddress(macAddress) +NetworkDeviceMonitorImpl::NetworkDeviceMonitorImpl(const MacAddress &macAddress, const QString &hostName, const QHostAddress &address, QObject *parent) : + NetworkDeviceMonitor{parent}, + m_macAddress{macAddress}, + m_hostName{hostName}, + m_address{address} { } @@ -54,6 +56,26 @@ MacAddress NetworkDeviceMonitorImpl::macAddress() const return m_macAddress; } +QString NetworkDeviceMonitorImpl::hostName() const +{ + return m_hostName; +} + +QHostAddress NetworkDeviceMonitorImpl::address() const +{ + return m_address; +} + +NetworkDeviceInfo::MonitorMode NetworkDeviceMonitorImpl::monitorMode() const +{ + return m_monitorMode; +} + +void NetworkDeviceMonitorImpl::setMonitorMode(NetworkDeviceInfo::MonitorMode monitorMode) +{ + m_monitorMode = monitorMode; +} + NetworkDeviceInfo NetworkDeviceMonitorImpl::networkDeviceInfo() const { return m_networkDeviceInfo; @@ -127,4 +149,37 @@ void NetworkDeviceMonitorImpl::setLastConnectionAttempt(const QDateTime &lastCon m_lastConnectionAttempt = lastConnectionAttempt; } +bool NetworkDeviceMonitorImpl::isMyNetworkDeviceInfo(const NetworkDeviceInfo &networkDeviceInfo) const +{ + bool myNetworkDevice = false; + switch (m_monitorMode) { + case NetworkDeviceInfo::MonitorModeMac: + if (!m_macAddress.isNull() && networkDeviceInfo.macAddressInfos().count() == 1 && networkDeviceInfo.macAddressInfos().hasMacAddress(m_macAddress)) + myNetworkDevice = true; + + break; + case NetworkDeviceInfo::MonitorModeHostName: + if (!m_hostName.isEmpty() && networkDeviceInfo.hostName() == m_hostName) + myNetworkDevice = true; + + break; + case NetworkDeviceInfo::MonitorModeIp: + if (!m_address.isNull() && networkDeviceInfo.address() == m_address) + myNetworkDevice = true; + + break; + } + return myNetworkDevice; +} + +bool NetworkDeviceMonitorImpl::operator==(NetworkDeviceMonitorImpl *other) const +{ + return m_macAddress == other->macAddress() && m_hostName == other->hostName() && m_address == other->address(); +} + +bool NetworkDeviceMonitorImpl::operator!=(NetworkDeviceMonitorImpl *other) const +{ + return !operator==(other); +} + } diff --git a/libnymea-core/hardware/network/networkdevicemonitorimpl.h b/libnymea-core/hardware/network/networkdevicemonitorimpl.h index e438d2d7..6858bd0d 100644 --- a/libnymea-core/hardware/network/networkdevicemonitorimpl.h +++ b/libnymea-core/hardware/network/networkdevicemonitorimpl.h @@ -1,6 +1,6 @@ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -* Copyright 2013 - 2022, nymea GmbH +* Copyright 2013 - 2024, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. @@ -34,8 +34,8 @@ #include #include -#include "network/networkdevicemonitor.h" #include "network/pingreply.h" +#include "network/networkdevicemonitor.h" namespace nymeaserver { @@ -44,10 +44,16 @@ class NetworkDeviceMonitorImpl : public NetworkDeviceMonitor Q_OBJECT public: - explicit NetworkDeviceMonitorImpl(const MacAddress &macAddress, QObject *parent = nullptr); + explicit NetworkDeviceMonitorImpl(const MacAddress &macAddress, const QString &hostName, const QHostAddress &address, QObject *parent = nullptr); ~NetworkDeviceMonitorImpl() override; + // Properties from the thing MacAddress macAddress() const override; + QString hostName() const override; + QHostAddress address() const override; + + NetworkDeviceInfo::MonitorMode monitorMode() const override; + void setMonitorMode(NetworkDeviceInfo::MonitorMode monitorMode); NetworkDeviceInfo networkDeviceInfo() const override; void setNetworkDeviceInfo(const NetworkDeviceInfo &networkDeviceInfo); @@ -67,14 +73,25 @@ public: QDateTime lastConnectionAttempt() const; void setLastConnectionAttempt(const QDateTime &lastConnectionAttempt); + bool isMyNetworkDeviceInfo(const NetworkDeviceInfo &networkDeviceInfo) const; + + bool operator==(NetworkDeviceMonitorImpl *other) const; + bool operator!=(NetworkDeviceMonitorImpl *other) const; private: - NetworkDeviceInfo m_networkDeviceInfo; MacAddress m_macAddress; + QString m_hostName; + QHostAddress m_address; + + NetworkDeviceInfo::MonitorMode m_monitorMode = NetworkDeviceInfo::MonitorModeMac; + + NetworkDeviceInfo m_networkDeviceInfo; + bool m_reachable = false; QDateTime m_lastSeen; QDateTime m_lastConnectionAttempt; uint m_pingRetries = 5; + PingReply *m_currentPingReply = nullptr; }; diff --git a/libnymea/network/macaddressinfos.cpp b/libnymea/network/macaddressinfos.cpp index 8bd171e6..938ee016 100644 --- a/libnymea/network/macaddressinfos.cpp +++ b/libnymea/network/macaddressinfos.cpp @@ -49,7 +49,7 @@ int MacAddressInfos::indexFromMacAddress(const QString &macAddress) int MacAddressInfos::indexFromMacAddress(const MacAddress &macAddress) { for (int i = 0; i < size(); i++) { - if (MacAddress(at(i).macAddress()) == macAddress) { + if (at(i).macAddress() == macAddress) { return i; } } diff --git a/libnymea/network/networkdevicediscovery.h b/libnymea/network/networkdevicediscovery.h index 1934b960..fa216def 100644 --- a/libnymea/network/networkdevicediscovery.h +++ b/libnymea/network/networkdevicediscovery.h @@ -44,6 +44,8 @@ #include "macaddressdatabasereply.h" #include "networkdevicediscoveryreply.h" +#include "integrations/thing.h" + class LIBNYMEA_EXPORT NetworkDeviceDiscovery : public HardwareResource { Q_OBJECT @@ -55,9 +57,7 @@ public: virtual bool running() const = 0; - virtual NetworkDeviceMonitor *registerMonitor(const MacAddress &macAddress) = 0; - - virtual void unregisterMonitor(const MacAddress &macAddress) = 0; + virtual NetworkDeviceMonitor *registerMonitor(Thing *thing) = 0; virtual void unregisterMonitor(NetworkDeviceMonitor *networkDeviceMonitor) = 0; virtual PingReply *ping(const QHostAddress &address, uint retries = 3) = 0; diff --git a/libnymea/network/networkdeviceinfo.cpp b/libnymea/network/networkdeviceinfo.cpp index e6e6e6e3..adcbfc71 100644 --- a/libnymea/network/networkdeviceinfo.cpp +++ b/libnymea/network/networkdeviceinfo.cpp @@ -146,7 +146,7 @@ QString NetworkDeviceInfo::incompleteProperties() const list.append("MAC infos incomplete"); if (!m_hostNameSet) - list.append("hostname not set"); + list.append("host name not set"); if (!m_networkInterfaceSet) list.append("nework interface not set"); @@ -154,6 +154,48 @@ QString NetworkDeviceInfo::incompleteProperties() const return list.join(", "); } +QString NetworkDeviceInfo::thingParamValueMacAddress() const +{ + QString macString; + switch (m_monitorMode) { + case MonitorModeMac: + macString = m_macAddressInfos.constFirst().macAddress().toString(); + break; + default: + // In any other case we don't want to store the mac address since we can not relai on it + break; + } + return macString; +} + +QString NetworkDeviceInfo::thingParamValueHostName() const +{ + QString hostNameString; + switch (m_monitorMode) { + case MonitorModeMac: + case MonitorModeHostName: + hostNameString = m_hostName; + break; + default: + break; + } + return hostNameString; +} + +QString NetworkDeviceInfo::thingParamValueAddress() const +{ + QString addressString; + switch (m_monitorMode) { + case MonitorModeIp: + addressString = m_address.toString(); + break; + default: + // In any other case we don't want to store the IP address because we want to discover it + break; + } + return addressString; +} + bool NetworkDeviceInfo::operator==(const NetworkDeviceInfo &other) const { return m_address == other.address() && @@ -179,7 +221,7 @@ QDebug operator<<(QDebug dbg, const NetworkDeviceInfo &networkDeviceInfo) case NetworkDeviceInfo::MonitorModeMac: dbg.nospace().noquote() << "MAC"; break; - case NetworkDeviceInfo::MonitorModeHostname: + case NetworkDeviceInfo::MonitorModeHostName: dbg.nospace().noquote() << "hostname"; break; case NetworkDeviceInfo::MonitorModeIp: diff --git a/libnymea/network/networkdeviceinfo.h b/libnymea/network/networkdeviceinfo.h index 2d9e62df..9e6c8e13 100644 --- a/libnymea/network/networkdeviceinfo.h +++ b/libnymea/network/networkdeviceinfo.h @@ -44,17 +44,10 @@ class LIBNYMEA_EXPORT NetworkDeviceInfo { Q_GADGET public: - // Virtual hosts are devices with no MAC address available or not unique MAC address and the MAC address can not be used for the NetworkDeviceMonitor. - // Examples for virtual hosts are - // - VPN network hosts (no MAC address) - // - Webservers outside the network (domains) - // - Devices behind older wifi repeaters, multiple hosts (individual devices) have the same virtual MAC address - // - Hosts which are accessable over multiple interfaces within the same network (i.e. WLAN + LAN), - // they can be reached using both MAC addresses and both IP addresses (linux feature) enum MonitorMode { MonitorModeMac = 0x01, // Unique MAC address within the network - MonitorModeHostname = 0x02, // DNS hostname available, but no MAC address or not unique MAC available + MonitorModeHostName = 0x02, // DNS hostname available, but no MAC address or not unique MAC available MonitorModeIp = 0x03 // Only the IP can be used to monitor, simple ping on reachable }; Q_ENUM(MonitorMode) @@ -86,6 +79,13 @@ public: QString incompleteProperties() const; + // Helper methods for the networkdevice interface + // The fill in automaticlally the correct paramters for the + // right monitor + QString thingParamValueMacAddress() const; + QString thingParamValueHostName() const; + QString thingParamValueAddress() const; + bool operator==(const NetworkDeviceInfo &other) const; bool operator!=(const NetworkDeviceInfo &other) const; diff --git a/libnymea/network/networkdeviceinfos.cpp b/libnymea/network/networkdeviceinfos.cpp index 0f0c750a..91e54ee9 100644 --- a/libnymea/network/networkdeviceinfos.cpp +++ b/libnymea/network/networkdeviceinfos.cpp @@ -108,36 +108,6 @@ void NetworkDeviceInfos::removeHostAddress(const QHostAddress &address) } } -// NetworkDeviceInfo NetworkDeviceInfos::get(const QString &macAddress) const -// { -// foreach (const NetworkDeviceInfo &networkDeviceInfo, *this) { -// if (networkDeviceInfo.macAddress() == macAddress) { -// return networkDeviceInfo; -// } -// } - -// return NetworkDeviceInfo(); -// } - -// NetworkDeviceInfo NetworkDeviceInfos::get(const MacAddress &macAddress) const -// { -// return get(macAddress.toString()); -// } - -// void NetworkDeviceInfos::removeMacAddress(const QString &macAddress) -// { -// removeMacAddress(MacAddress(macAddress)); -// } - -// void NetworkDeviceInfos::removeMacAddress(const MacAddress &macAddress) -// { -// for (int i = 0; i < size(); i++) { -// if (MacAddress(at(i).macAddress()) == macAddress) { -// remove(i); -// } -// } -// } - void NetworkDeviceInfos::sortNetworkDevices() { std::sort(this->begin(), this->end(), [](const NetworkDeviceInfo& a, const NetworkDeviceInfo& b) { diff --git a/libnymea/network/networkdeviceinfos.h b/libnymea/network/networkdeviceinfos.h index 22fd8c4e..d1302873 100644 --- a/libnymea/network/networkdeviceinfos.h +++ b/libnymea/network/networkdeviceinfos.h @@ -52,11 +52,6 @@ public: bool hasMacAddress(const MacAddress &macAddress); NetworkDeviceInfo get(const QHostAddress &address) const; - // NetworkDeviceInfo get(const QString &macAddress) const; - // NetworkDeviceInfo get(const MacAddress &macAddress) const; - - // void removeMacAddress(const QString &macAddress); - // void removeMacAddress(const MacAddress &macAddress); void removeHostAddress(const QHostAddress &address); void sortNetworkDevices(); diff --git a/libnymea/network/networkdevicemonitor.cpp b/libnymea/network/networkdevicemonitor.cpp index 453e4612..ab8b9f50 100644 --- a/libnymea/network/networkdevicemonitor.cpp +++ b/libnymea/network/networkdevicemonitor.cpp @@ -40,13 +40,20 @@ NetworkDeviceMonitor::NetworkDeviceMonitor(QObject *parent) : QDebug operator<<(QDebug dbg, NetworkDeviceMonitor *networkDeviceMonitor) { QDebugStateSaver saver(dbg); - dbg.nospace() << "NetworkDeviceMonitor(" << networkDeviceMonitor->macAddress().toString(); + dbg.nospace() << "NetworkDeviceMonitor("; - // FIXME - // if (!networkDeviceMonitor->networkDeviceInfo().macAddressManufacturer().isEmpty()) - // dbg.nospace() << " - " << networkDeviceMonitor->networkDeviceInfo().macAddressManufacturer(); + switch (networkDeviceMonitor->monitorMode()) { + case NetworkDeviceInfo::MonitorModeMac: + dbg.nospace() << "Mode: MAC, " << networkDeviceMonitor->macAddress().toString(); + break; + case NetworkDeviceInfo::MonitorModeHostName: + dbg.nospace() << "Mode: host name, " << networkDeviceMonitor->hostName(); + break; + case NetworkDeviceInfo::MonitorModeIp: + dbg.nospace() << "Mode: IP address, " << networkDeviceMonitor->address().toString(); + break; + } - dbg.nospace() << ", " << networkDeviceMonitor->networkDeviceInfo().address().toString(); dbg.nospace() << ", " << (networkDeviceMonitor->reachable() ? "reachable" : "not reachable"); dbg.nospace() << ")"; return dbg; diff --git a/libnymea/network/networkdevicemonitor.h b/libnymea/network/networkdevicemonitor.h index dcd8f459..172c36c2 100644 --- a/libnymea/network/networkdevicemonitor.h +++ b/libnymea/network/networkdevicemonitor.h @@ -35,7 +35,6 @@ #include #include "libnymea.h" -#include "macaddress.h" #include "networkdeviceinfo.h" class LIBNYMEA_EXPORT NetworkDeviceMonitor : public QObject @@ -46,8 +45,14 @@ public: explicit NetworkDeviceMonitor(QObject *parent = nullptr); virtual ~NetworkDeviceMonitor() = default; + // Monitor parameters defining the monitor mode virtual MacAddress macAddress() const = 0; + virtual QString hostName() const = 0; + virtual QHostAddress address() const = 0; + virtual NetworkDeviceInfo::MonitorMode monitorMode() const = 0; + + // Actual network device information virtual NetworkDeviceInfo networkDeviceInfo() const = 0; virtual bool reachable() const = 0; @@ -60,7 +65,7 @@ signals: void reachableChanged(bool reachable); void lastSeenChanged(const QDateTime &lastSeen); void networkDeviceInfoChanged(const NetworkDeviceInfo &networkDeviceInfo); - + void pingRetriesChanged(uint pingRetries); }; QDebug operator<<(QDebug debug, NetworkDeviceMonitor *networkDeviceMonitor); From 341a07cd8500706c8f10249d5d240cbb65acf9bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Mon, 9 Dec 2024 17:08:22 +0100 Subject: [PATCH 5/7] Update ping and monitor handling --- .../network/networkdevicediscoveryimpl.cpp | 273 +++++++++--------- .../network/networkdevicediscoveryimpl.h | 3 + libnymea/network/networkdevicediscovery.h | 1 + libnymea/network/networkdeviceinfos.cpp | 11 + libnymea/network/networkdeviceinfos.h | 1 + libnymea/network/ping.cpp | 187 +++++++++--- libnymea/network/ping.h | 7 +- libnymea/network/pingreply.h | 4 +- 8 files changed, 306 insertions(+), 181 deletions(-) diff --git a/libnymea-core/hardware/network/networkdevicediscoveryimpl.cpp b/libnymea-core/hardware/network/networkdevicediscoveryimpl.cpp index f6f07472..38545742 100644 --- a/libnymea-core/hardware/network/networkdevicediscoveryimpl.cpp +++ b/libnymea-core/hardware/network/networkdevicediscoveryimpl.cpp @@ -89,7 +89,7 @@ NetworkDeviceDiscoveryImpl::NetworkDeviceDiscoveryImpl(QObject *parent) : // Timer for updating the monitors m_monitorTimer = new QTimer(this); - m_monitorTimer->setInterval(5000); + m_monitorTimer->setInterval(10000); m_monitorTimer->setSingleShot(false); connect(m_monitorTimer, &QTimer::timeout, this, &NetworkDeviceDiscoveryImpl::evaluateMonitors); @@ -218,58 +218,6 @@ bool NetworkDeviceDiscoveryImpl::running() const return m_running; } -// NetworkDeviceMonitor *NetworkDeviceDiscoveryImpl::registerMonitor(const MacAddress &macAddress) -// { -// if (macAddress.isNull()) { -// qCWarning(dcNetworkDeviceDiscovery()) << "Could not register monitor for invalid" << macAddress; -// return nullptr; -// } - -// // Make sure we create only one monitor per MAC and keep track how many user -// // have access to this monitor otherwise an unregister could cause a crash in -// // an other plugin plugin which might still need it -// if (m_monitors.contains(macAddress)) { -// m_monitorsReferenceCount[macAddress] += 1; -// qCInfo(dcNetworkDeviceDiscovery()) << "Register network device monitor for" << macAddress << "which already exists. Returning existing monitor having now" << m_monitorsReferenceCount[macAddress] << "references."; -// return m_monitors.value(macAddress); -// } - -// qCInfo(dcNetworkDeviceDiscovery()) << "Register new network device monitor for" << macAddress; - -// // Fill in cached information -// NetworkDeviceInfo info; -// if (m_networkInfoCache.contains(macAddress)) { -// info = m_networkInfoCache.value(macAddress); -// } else { -// info.setMacAddress(macAddress.toString()); -// } - -// NetworkDeviceMonitorImpl *monitor = new NetworkDeviceMonitorImpl(macAddress, this); -// monitor->setNetworkDeviceInfo(info); -// monitor->setLastSeen(m_lastSeen.value(macAddress, QDateTime())); -// m_monitors.insert(macAddress, monitor); -// m_monitorsReferenceCount[macAddress] = 1; - -// if (!available()) { -// qCWarning(dcNetworkDeviceDiscovery()) << "Registered monitor but the hardware resource is not available. The monitor will not work as expected" << monitor; -// return monitor; -// } - -// // Restart the monitor timer since we evaluate this one now -// m_monitorTimer->start(); - -// if (!monitor->networkDeviceInfo().isValid()) { -// qCDebug(dcNetworkDeviceDiscovery()) << "Adding network device monitor for unresolved mac address. Starting a discovery..."; -// NetworkDeviceDiscoveryReply *reply = discover(); -// connect(reply, &NetworkDeviceDiscoveryReply::finished, reply, &NetworkDeviceDiscoveryReply::deleteLater); -// } else { -// evaluateMonitor(monitor); -// } - -// qCDebug(dcNetworkDeviceDiscovery()) << "Registered successfully" << monitor; -// return monitor; -// } - NetworkDeviceMonitor *NetworkDeviceDiscoveryImpl::registerMonitor(Thing *thing) { if (!thing->thingClass().interfaces().contains("networkdevice")) { @@ -308,11 +256,14 @@ NetworkDeviceMonitor *NetworkDeviceDiscoveryImpl::registerMonitor(Thing *thing) } } + bool newMonitor = true; if (internalMonitor) { qCDebug(dcNetworkDeviceDiscovery()) << "Already have an internal monitor for this network device" << internalMonitor; + newMonitor = false; } else { // Create a new monitor for the internal use internalMonitor = new NetworkDeviceMonitorImpl(macAddress, hostName, address, this); + m_monitors.insert(internalMonitor, QVector()); } internalMonitor->setMonitorMode(mode); @@ -354,7 +305,13 @@ NetworkDeviceMonitor *NetworkDeviceDiscoveryImpl::registerMonitor(Thing *thing) // Create a new plugin monitor object we are going to return... NetworkDeviceMonitorImpl *pluginMonitor = createPluginMonitor(internalMonitor); + m_monitors[internalMonitor].append(pluginMonitor); qCDebug(dcNetworkDeviceDiscovery()) << "Registered successfully" << pluginMonitor; + + // In case this is a new monitor, let's evaluate it right the way so know asap if the device is reachable or not + if (newMonitor) + evaluateMonitor(internalMonitor); + return pluginMonitor; } @@ -374,6 +331,14 @@ PingReply *NetworkDeviceDiscoveryImpl::ping(const QHostAddress &address, uint re return reply; } +PingReply *NetworkDeviceDiscoveryImpl::ping(const QString &hostName, uint retries) +{ + PingReply *reply = m_ping->ping(hostName, retries); + // Note: we use any ping used trough this method also for the monitor evaluation + watchPingReply(reply); + return reply; +} + PingReply *NetworkDeviceDiscoveryImpl::ping(const QHostAddress &address, bool lookupHost, uint retries) { PingReply *reply = m_ping->ping(address, lookupHost, retries); @@ -521,7 +486,35 @@ void NetworkDeviceDiscoveryImpl::processMonitorPingResult(PingReply *reply, Netw // Save the last time we tried to communicate if (reply->error() == PingReply::ErrorNoError) { qCDebug(dcNetworkDeviceDiscovery()) << "Ping response from" << monitor << reply->duration() << "ms"; - monitor->setLastSeen(QDateTime::currentDateTime()); + + QDateTime currentDateTime = QDateTime::currentDateTime(); + m_lastSeen[reply->targetHostAddress()] = currentDateTime; + + for (int i = 0; i < m_networkInfoCache.count(); i++) { + + if (monitor->monitorMode() == NetworkDeviceInfo::MonitorModeHostName) { + if (m_networkInfoCache.at(i).hostName() == monitor->hostName() && m_networkInfoCache.at(i).address() != reply->targetHostAddress()) { + QHostAddress oldAddress = m_networkInfoCache.at(i).address(); + qCDebug(dcNetworkDeviceDiscovery()) << "Hostname" << monitor->hostName() << "changed the IP address from" + << oldAddress.toString() + << "-->" + << reply->targetHostAddress().toString(); + + removeFromNetworkDeviceCache(oldAddress); + + NetworkDeviceInfo info = m_networkInfoCache.at(i); + info.setAddress(reply->targetHostAddress()); + + monitor->setNetworkDeviceInfo(info); + m_networkInfoCache[i] = info; + m_networkInfoCache.sortNetworkDevices(); + saveNetworkDeviceCache(info); + break; + } + } + } + + monitor->setLastSeen(currentDateTime); monitor->setReachable(true); } else { qCDebug(dcNetworkDeviceDiscovery()) << "Failed to ping device from" << monitor << "retrying" << reply->retries() << "times:" << reply->error(); @@ -531,29 +524,25 @@ void NetworkDeviceDiscoveryImpl::processMonitorPingResult(PingReply *reply, Netw void NetworkDeviceDiscoveryImpl::watchPingReply(PingReply *reply) { - connect(reply, &PingReply::finished, this, [=](){ - // Search cache for mac address and update last seen + connect(reply, &PingReply::finished, this, [this, reply](){ + if (reply->error() == PingReply::ErrorNoError) { - // FIXME - // if (reply->error() == PingReply::ErrorNoError) { - // foreach (const NetworkDeviceInfo &info, m_networkInfoCache) { - // if (info.address() == reply->targetHostAddress()) { - // // Found info for this ip, update the cache - // MacAddress macAddress(info.macAddress()); - // if (!macAddress.isNull() && m_networkInfoCache.contains(macAddress)) { - // m_lastSeen[macAddress] = QDateTime::currentDateTime(); - // saveNetworkDeviceCache(m_networkInfoCache.value(macAddress)); - // } - // } - // } - // } + int index = m_networkInfoCache.indexFromHostAddress(reply->targetHostAddress()); + if (index < 0) + return; - // Update any monitor - // foreach (NetworkDeviceMonitorImpl *monitor, m_monitors.values()) { - // if (monitor->networkDeviceInfo().address() == reply->targetHostAddress()) { - // processMonitorPingResult(reply, monitor); - // } - // } + m_lastSeen[reply->targetHostAddress()] = QDateTime::currentDateTime(); + saveNetworkDeviceCache(m_networkInfoCache.at(index)); + } + + // Update any relevant monitor + foreach (NetworkDeviceMonitorImpl *monitor, m_monitors.keys()) { + if ((monitor->monitorMode() == NetworkDeviceInfo::MonitorModeIp && reply->targetHostAddress() == monitor->address()) || + (monitor->monitorMode() == NetworkDeviceInfo::MonitorModeHostName && reply->hostName() == monitor->hostName()) || + (monitor->monitorMode() == NetworkDeviceInfo::MonitorModeMac && reply->targetHostAddress() == monitor->networkDeviceInfo().address())) { + processMonitorPingResult(reply, monitor); + } + } }); } @@ -562,7 +551,7 @@ void NetworkDeviceDiscoveryImpl::loadNetworkDeviceCache() qCInfo(dcNetworkDeviceDiscovery()) << "Loading cached network device information from" << m_cacheSettings->fileName(); m_networkInfoCache.clear(); - QDateTime now = QDateTime::currentDateTime(); + QDateTime currentDateTime = QDateTime::currentDateTime(); uint cacheVersion = m_cacheSettings->value("version", 0).toUInt(); @@ -579,7 +568,7 @@ void NetworkDeviceDiscoveryImpl::loadNetworkDeviceCache() QDateTime lastSeen = QDateTime::fromMSecsSinceEpoch(m_cacheSettings->value("lastSeen").toLongLong()); // Remove the info from the cache if not seen fo the last 30 days... - if (lastSeen.date().addDays(m_cacheCleanupPeriod) < now.date()) { + if (lastSeen.date().addDays(m_cacheCleanupPeriod) < currentDateTime.date()) { qCDebug(dcNetworkDeviceDiscovery()) << "Removing network device cache entry since it did not show up within the last" << m_cacheCleanupPeriod << "days" << address.toString(); m_cacheSettings->remove(""); m_cacheSettings->endGroup(); // mac address @@ -624,21 +613,6 @@ void NetworkDeviceDiscoveryImpl::loadNetworkDeviceCache() m_lastCacheHousekeeping = QDateTime::currentDateTime(); } -void NetworkDeviceDiscoveryImpl::removeFromNetworkDeviceCache(const MacAddress &macAddress) -{ - if (macAddress.isNull()) - return; - - // m_networkInfoCache.remove(macAddress); - // m_lastSeen.remove(macAddress); - // m_cacheSettings->beginGroup("NetworkDeviceInfos"); - // m_cacheSettings->beginGroup(macAddress.toString()); - // m_cacheSettings->remove(""); - // m_cacheSettings->endGroup(); // mac address - // m_cacheSettings->endGroup(); // NetworkDeviceInfos - // m_cacheSettings->sync(); -} - void NetworkDeviceDiscoveryImpl::removeFromNetworkDeviceCache(const QHostAddress &address) { if (address.isNull()) @@ -707,17 +681,23 @@ void NetworkDeviceDiscoveryImpl::updateCache(const NetworkDeviceInfo &deviceInfo void NetworkDeviceDiscoveryImpl::evaluateMonitor(NetworkDeviceMonitorImpl *monitor) { - // if (!monitor->networkDeviceInfo().isValid()) - // return; - - - if (monitor->currentPingReply()) + if (monitor->currentPingReply()) { + qCDebug(dcNetworkDeviceDiscovery()) << "Monitor has still a ping reply pending:" << monitor; return; + } + + qCDebug(dcNetworkDeviceDiscovery()) << "Evaluate monitor" << monitor; // Start action if we have not seen the device for gracePeriod seconds QDateTime currentDateTime = QDateTime::currentDateTime(); bool requiresRefresh = false; + + if (!monitor->networkDeviceInfo().isValid()) { + qCDebug(dcNetworkDeviceDiscovery()) << "Network device info not valid for" << monitor; + requiresRefresh = true; + } + if (monitor->lastSeen().isNull()) { qCDebug(dcNetworkDeviceDiscovery()) << monitor << "requires refresh. Not seen since application start."; requiresRefresh = true; @@ -744,57 +724,85 @@ void NetworkDeviceDiscoveryImpl::evaluateMonitor(NetworkDeviceMonitorImpl *monit if (!requiresRefresh) return; - // Try to ping first - qCDebug(dcNetworkDeviceDiscovery()) << monitor << "try to ping" << monitor->networkDeviceInfo().address().toString(); - PingReply *reply = m_ping->ping(monitor->networkDeviceInfo().address(), monitor->pingRetries()); + // Try to ping first. + + // IMPORTANT: use the ping methods from this object, so the result will automatically + // be evaluated for the monitors and cache + PingReply *reply = nullptr; + if (monitor->monitorMode() == NetworkDeviceInfo::MonitorModeHostName) { + qCDebug(dcNetworkDeviceDiscovery()) << monitor << "try to ping" << monitor->hostName(); + reply = ping(monitor->hostName(), monitor->pingRetries()); + } else { + qCDebug(dcNetworkDeviceDiscovery()) << monitor << "try to ping" << monitor->networkDeviceInfo().address().toString(); + reply = ping(monitor->networkDeviceInfo().address(), monitor->pingRetries()); + } + monitor->setCurrentPingReply(reply); monitor->setLastConnectionAttempt(currentDateTime); - connect(reply, &PingReply::retry, monitor, [=](PingReply::Error error, uint retryCount){ + connect(reply, &PingReply::retry, monitor, [monitor](PingReply::Error error, uint retryCount){ Q_UNUSED(error) Q_UNUSED(retryCount) monitor->setLastConnectionAttempt(QDateTime::currentDateTime()); }); - connect(reply, &PingReply::destroyed, monitor, [=](){ - monitor->setCurrentPingReply(nullptr); + connect(reply, &PingReply::destroyed, monitor, [monitor, reply](){ + if (monitor->currentPingReply() == reply) { + monitor->setCurrentPingReply(nullptr); + } }); - connect(reply, &PingReply::finished, monitor, [=](){ - processMonitorPingResult(reply, monitor); + connect(reply, &PingReply::finished, monitor, [monitor, reply](){ + if (monitor->currentPingReply() == reply) { + monitor->setCurrentPingReply(nullptr); + } }); } void NetworkDeviceDiscoveryImpl::processArpTraffic(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress) { - QDateTime now = QDateTime::currentDateTime(); - // m_lastSeen[macAddress] = now; + QDateTime currentDateTime = QDateTime::currentDateTime(); + m_lastSeen[address] = currentDateTime; - // FIXME + // Update monitors and cache + for (int i = 0; i < m_networkInfoCache.count(); i++) { + switch (m_networkInfoCache.at(i).monitorMode()) { + case NetworkDeviceInfo::MonitorModeMac: + if (m_networkInfoCache.at(i).macAddressInfos().hasMacAddress(macAddress) && + m_networkInfoCache.at(i).address() != address) { - // if (m_networkInfoCache.hasMacAddress(macAddress)) { - // if (m_networkInfoCache.value(macAddress).address() != address) { - // m_networkInfoCache[macAddress].setAddress(address); - // saveNetworkDeviceCache(m_networkInfoCache.value(macAddress)); - // } - // } + QHostAddress oldAddress = m_networkInfoCache.at(i).address(); + qCDebug(dcNetworkDeviceDiscovery()) << "Host" << macAddress.toString() << "changed the IP address from" + << oldAddress.toString() + << "-->" + << address.toString(); - // Update the monitors - // NetworkDeviceMonitorImpl *monitor = m_monitors.value(macAddress); - // if (monitor) { - // monitor->setLastSeen(now); - // if (monitor->networkDeviceInfo().address() != address) { - // NetworkDeviceInfo info = monitor->networkDeviceInfo(); - // info.setAddress(address); - // monitor->setNetworkDeviceInfo(info); - // qCDebug(dcNetworkDeviceDiscovery()) << "NetworkDeviceMonitor" << monitor << "ip address changed"; - // emit monitor->networkDeviceInfoChanged(monitor->networkDeviceInfo()); - // } + removeFromNetworkDeviceCache(oldAddress); - // if (monitor->networkDeviceInfo().isComplete()) { - // monitor->setReachable(true); - // } - // } + NetworkDeviceInfo info = m_networkInfoCache.at(i); + info.setAddress(address); + + m_networkInfoCache[i] = info; + m_networkInfoCache.sortNetworkDevices(); + saveNetworkDeviceCache(info); + + foreach (NetworkDeviceMonitorImpl *monitor, m_monitors.keys()) { + if (monitor->macAddress() == macAddress) { + monitor->setNetworkDeviceInfo(info); + monitor->setLastSeen(currentDateTime); + monitor->setReachable(true); + break; + } + } + break; + } + break; + case NetworkDeviceInfo::MonitorModeHostName: + case NetworkDeviceInfo::MonitorModeIp: + saveNetworkDeviceCache(m_networkInfoCache.at(i)); + break; + } + } // Check if we have currently reply running if (!m_currentDiscoveryReply) @@ -910,6 +918,7 @@ void NetworkDeviceDiscoveryImpl::cleanupPluginMonitor(NetworkDeviceMonitorImpl * foreach (NetworkDeviceMonitorImpl *internalMonitor, m_monitors.keys()) { if (m_monitors.value(internalMonitor).contains(pluginMonitor)) { m_monitors[internalMonitor].removeAll(pluginMonitor); + pluginMonitor->deleteLater(); if (m_monitors.value(internalMonitor).isEmpty()) { qCDebug(dcNetworkDeviceDiscovery()) << "No monitor registered for this network device any more. Unregister internal monitor" << internalMonitor; @@ -952,7 +961,7 @@ void NetworkDeviceDiscoveryImpl::evaluateMonitors() evaluateMonitor(monitor); // Check if there is any monitor which has not be seen since - if (!monitor->reachable() /*&& monitor->lastConnectionAttempt().isValid()*/ && longerAgoThan(monitor->lastSeen(), m_monitorInterval)) { + if (!monitor->reachable() && monitor->lastConnectionAttempt().isValid() && longerAgoThan(monitor->lastSeen(), m_monitorInterval)) { monitorRequiresRediscovery = true; } } @@ -967,7 +976,7 @@ void NetworkDeviceDiscoveryImpl::evaluateMonitors() // // Do some cache housekeeping if required // if (m_lastCacheHousekeeping.addDays(1) < QDateTime::currentDateTime()) { // qCInfo(dcNetworkDeviceDiscovery()) << "Starting cache housekeeping since it is more than one day since the last clanup..."; - // QDateTime now = QDateTime::currentDateTime(); + // QDateTime currentDateTime = QDateTime::currentDateTime(); // foreach (const MacAddress &mac, m_lastSeen.keys()) { // // Remove the info from the cache if not seen fo the last 30 days... // if (m_lastSeen.value(mac).date().addDays(m_cacheCleanupPeriod) < QDateTime::currentDateTime().date()) { @@ -975,7 +984,7 @@ void NetworkDeviceDiscoveryImpl::evaluateMonitors() // removeFromNetworkDeviceCache(mac); // } // } - // m_lastCacheHousekeeping = now; + // m_lastCacheHousekeeping = currentDateTime; // } } diff --git a/libnymea-core/hardware/network/networkdevicediscoveryimpl.h b/libnymea-core/hardware/network/networkdevicediscoveryimpl.h index 30c41ea3..f17f04c6 100644 --- a/libnymea-core/hardware/network/networkdevicediscoveryimpl.h +++ b/libnymea-core/hardware/network/networkdevicediscoveryimpl.h @@ -71,6 +71,7 @@ public: void unregisterMonitor(NetworkDeviceMonitor *networkDeviceMonitor) override; PingReply *ping(const QHostAddress &address, uint retries = 3) override; + PingReply *ping(const QString &hostName, uint retries = 3) override; PingReply *ping(const QHostAddress &address, bool lookupHost, uint retries = 3); MacAddressDatabaseReply *lookupMacAddress(const QString &macAddress) override; @@ -138,6 +139,8 @@ private: void processArpTraffic(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress); + void testPingMonitor(NetworkDeviceMonitorImpl *monitor); + // Time helpers bool longerAgoThan(const QDateTime &dateTime, uint minutes); QDateTime convertMinuteBased(const QDateTime &dateTime = QDateTime()); diff --git a/libnymea/network/networkdevicediscovery.h b/libnymea/network/networkdevicediscovery.h index fa216def..e260e60b 100644 --- a/libnymea/network/networkdevicediscovery.h +++ b/libnymea/network/networkdevicediscovery.h @@ -61,6 +61,7 @@ public: virtual void unregisterMonitor(NetworkDeviceMonitor *networkDeviceMonitor) = 0; virtual PingReply *ping(const QHostAddress &address, uint retries = 3) = 0; + virtual PingReply *ping(const QString &hostName, uint retries = 3) = 0; virtual MacAddressDatabaseReply *lookupMacAddress(const QString &macAddress) = 0; virtual MacAddressDatabaseReply *lookupMacAddress(const MacAddress &macAddress) = 0; diff --git a/libnymea/network/networkdeviceinfos.cpp b/libnymea/network/networkdeviceinfos.cpp index 91e54ee9..4c824894 100644 --- a/libnymea/network/networkdeviceinfos.cpp +++ b/libnymea/network/networkdeviceinfos.cpp @@ -56,6 +56,17 @@ int NetworkDeviceInfos::indexFromHostAddress(const QHostAddress &address) return -1; } +int NetworkDeviceInfos::indexFromHostName(const QString &hostName) +{ + for (int i = 0; i < this->size(); i++) { + if (at(i).hostName() == hostName) { + return i; + } + } + + return -1; +} + QList NetworkDeviceInfos::indexFromMacAddress(const QString &macAddress) { return indexFromMacAddress(MacAddress(macAddress)); diff --git a/libnymea/network/networkdeviceinfos.h b/libnymea/network/networkdeviceinfos.h index d1302873..86d1433a 100644 --- a/libnymea/network/networkdeviceinfos.h +++ b/libnymea/network/networkdeviceinfos.h @@ -44,6 +44,7 @@ public: NetworkDeviceInfos(const QVector &other); int indexFromHostAddress(const QHostAddress &address); + int indexFromHostName(const QString &hostName); QList indexFromMacAddress(const QString &macAddress); QList indexFromMacAddress(const MacAddress &macAddress); diff --git a/libnymea/network/ping.cpp b/libnymea/network/ping.cpp index 54e6dc2d..94071503 100644 --- a/libnymea/network/ping.cpp +++ b/libnymea/network/ping.cpp @@ -141,6 +141,18 @@ PingReply *Ping::ping(const QHostAddress &hostAddress, bool lookupHost, uint ret return reply; } +PingReply *Ping::ping(const QString &hostName, uint retries) +{ + // Make first the host lookup, then the ping the first address and save it in the reply + PingReply *reply = createReply(hostName); + reply->m_retries = retries; + + // Note: due to a Qt bug < 5.9 we need to use old SLOT style and cannot make use of lambda here + int lookupId = QHostInfo::lookupHost(hostName, this, SLOT(onHostLookupFinished(QHostInfo))); + m_pendingHostAddressLookups.insert(lookupId, reply); + return reply; +} + void Ping::sendNextReply() { if (m_queueTimer->isActive()) @@ -150,7 +162,7 @@ void Ping::sendNextReply() return; m_currentReply = m_replyQueue.dequeue(); - qCDebug(dcPing()) << "Send next reply " << m_currentReply->targetHostAddress().toString() << QString("0x%1").arg(m_currentReply->requestId(), 4, 16, QChar('0')) << ", " << m_replyQueue.count() << "left in queue"; + qCDebug(dcPing()).nospace().noquote() << "Send next reply " << m_currentReply->targetHostAddress().toString() << " ID: " << QString("0x%1").arg(m_currentReply->requestId(), 4, 16, QChar('0')) << ", " << m_replyQueue.count() << "left in queue"; m_queueTimer->start(); QTimer::singleShot(0, this, [=]() { if (!m_currentReply) @@ -209,8 +221,8 @@ void Ping::performPing(PingReply *reply) } qCDebug(dcPingTraffic()) << "Send ICMP echo request" << reply->targetHostAddress().toString() << ICMP_PACKET_SIZE << "[Bytes]" - << "ID:" << QString("0x%1").arg(reply->requestId(), 4, 16, QChar('0')) - << "Sequence:" << reply->sequenceNumber(); + << "ID:" << QString("0x%1").arg(reply->requestId(), 4, 16, QChar('0')) + << "Sequence:" << reply->sequenceNumber(); // Send packet to the target ip int bytesSent = sendto(m_socketDescriptor, &requestPacket, sizeof(requestPacket), 0, (struct sockaddr *)&pingAddress, sizeof(pingAddress)); @@ -329,14 +341,43 @@ PingReply *Ping::createReply(const QHostAddress &hostAddress) return reply; } +PingReply *Ping::createReply(const QString &hostName) +{ + PingReply *reply = new PingReply(this); + reply->m_hostName = hostName; + + connect(reply, &PingReply::timeout, this, [=](){ + // Note: this is not the ICMP timeout, here we actually got nothing from anybody... + finishReply(reply, PingReply::ErrorTimeout); + }); + + connect(reply, &PingReply::aborted, this, [=](){ + finishReply(reply, PingReply::ErrorAborted); + }); + + connect(reply, &PingReply::finished, this, [=](){ + cleanUpReply(reply); + reply->deleteLater(); + }); + + // Just in case the reply get's deleted before beeing able to finish ... + connect(reply, &PingReply::destroyed, this, [this, reply](){ + cleanUpReply(reply); + }); + + return reply; +} + void Ping::finishReply(PingReply *reply, PingReply::Error error) { // Check if we should retry if (reply->m_retryCount >= reply->m_retries || - error == PingReply::ErrorNoError || - error == PingReply::ErrorAborted || - error == PingReply::ErrorInvalidHostAddress || - error == PingReply::ErrorPermissionDenied) { + error == PingReply::ErrorNoError || + error == PingReply::ErrorAborted || + error == PingReply::ErrorInvalidHostAddress || + error == PingReply::ErrorPermissionDenied|| + error == PingReply::ErrorHostNameLookupFailed || + error == PingReply::ErrorHostNameNotFound) { // No retry, we are done reply->m_error = error; reply->m_timer->stop(); @@ -374,11 +415,11 @@ void Ping::cleanUpReply(PingReply *reply) m_pendingReplies.remove(reply->requestId()); m_replyQueue.removeAll(reply); - if (m_pendingHostLookups.values().contains(reply)) { + if (m_pendingHostNameLookups.values().contains(reply)) { // Abort any pending host lookups, the reply has been finished - int lookupId = m_pendingHostLookups.key(reply); + int lookupId = m_pendingHostNameLookups.key(reply); QHostInfo::abortHostLookup(lookupId); - m_pendingHostLookups.remove(lookupId); + m_pendingHostNameLookups.remove(lookupId); } } @@ -404,19 +445,19 @@ void Ping::onSocketReadyRead(int socketDescriptor) QHostAddress destinationAddress(qFromBigEndian(ipHeader->daddr)); qCDebug(dcPingTraffic()) << "IP header: Lenght" << ipHeaderLength - << "Sender:" << senderAddress.toString() - << "Destination:" << destinationAddress.toString() - << "Size:" << htons(ipHeader->tot_len) << "B" - << "TTL" << ipHeader->ttl; + << "Sender:" << senderAddress.toString() + << "Destination:" << destinationAddress.toString() + << "Size:" << htons(ipHeader->tot_len) << "B" + << "TTL" << ipHeader->ttl; struct icmp *responsePacket = reinterpret_cast(receiveBuffer + ipHeaderLength); quint16 icmpId = htons(responsePacket->icmp_id); quint16 icmpSequnceNumber = htons(responsePacket->icmp_seq); qCDebug(dcPingTraffic()) << "ICMP packt (Size:" << icmpPacketSize << "Bytes):" - << "Type" << responsePacket->icmp_type - << "Code:" << responsePacket->icmp_code - << "ID:" << QString("0x%1").arg(icmpId, 4, 16, QChar('0')) - << "Sequence:" << icmpSequnceNumber; + << "Type" << responsePacket->icmp_type + << "Code:" << responsePacket->icmp_code + << "ID:" << QString("0x%1").arg(icmpId, 4, 16, QChar('0')) + << "Sequence:" << icmpSequnceNumber; if (responsePacket->icmp_type == ICMP_ECHOREPLY) { @@ -447,14 +488,14 @@ void Ping::onSocketReadyRead(int socketDescriptor) reply->m_duration = qRound((receiveTimeValue.tv_sec * 1000 + (double)receiveTimeValue.tv_usec / 1000) * 100) / 100.0; qCDebug(dcPing()) << "Received ICMP response" << reply->targetHostAddress().toString() << ICMP_PACKET_SIZE << "[Bytes]" - << "ID:" << QString("0x%1").arg(icmpId, 4, 16, QChar('0')) - << "Sequence:" << icmpSequnceNumber - << "Time:" << reply->duration() << "[ms]"; + << "ID:" << QString("0x%1").arg(icmpId, 4, 16, QChar('0')) + << "Sequence:" << icmpSequnceNumber + << "Time:" << reply->duration() << "[ms]"; if (reply->doHostLookup()) { // Note: due to a Qt bug < 5.9 we need to use old SLOT style and cannot make use of lambda here int lookupId = QHostInfo::lookupHost(senderAddress.toString(), this, SLOT(onHostLookupFinished(QHostInfo))); - m_pendingHostLookups.insert(lookupId, reply); + m_pendingHostNameLookups.insert(lookupId, reply); // Finish the reply after the host lookup has finished } else { finishReply(reply, PingReply::ErrorNoError); @@ -472,20 +513,20 @@ void Ping::onSocketReadyRead(int socketDescriptor) QHostAddress nestedDestinationAddress(qFromBigEndian(nestedIpHeader->daddr)); qCDebug(dcPingTraffic()) << "++ IP header: Lenght" << nestedIpHeaderLength - << "Sender:" << nestedSenderAddress.toString() - << "Destination:" << nestedDestinationAddress.toString() - << "Size:" << htons(nestedIpHeader->tot_len) << "B" - << "TTL" << ipHeader->ttl; + << "Sender:" << nestedSenderAddress.toString() + << "Destination:" << nestedDestinationAddress.toString() + << "Size:" << htons(nestedIpHeader->tot_len) << "B" + << "TTL" << ipHeader->ttl; struct icmp *nestedResponsePacket = reinterpret_cast(receiveBuffer + messageOffset + nestedIpHeaderLength); icmpId = htons(nestedResponsePacket->icmp_id); icmpSequnceNumber = htons(nestedResponsePacket->icmp_seq); qCDebug(dcPingTraffic()) << "++ ICMP packt (Size:" << nestedIcmpPacketSize << "Bytes):" - << "Type" << nestedResponsePacket->icmp_type - << "Code:" << nestedResponsePacket->icmp_code - << "ID:" << QString("0x%1").arg(icmpId, 4, 16, QChar('0')) - << "Sequence:" << icmpSequnceNumber; + << "Type" << nestedResponsePacket->icmp_type + << "Code:" << nestedResponsePacket->icmp_code + << "ID:" << QString("0x%1").arg(icmpId, 4, 16, QChar('0')) + << "Sequence:" << icmpSequnceNumber; qCDebug(dcPing()) << "ICMP destination unreachable" << nestedDestinationAddress.toString() << "Code:" << nestedResponsePacket->icmp_code @@ -495,9 +536,9 @@ void Ping::onSocketReadyRead(int socketDescriptor) PingReply *reply = m_pendingReplies.value(icmpId); if (!reply) { qCDebug(dcPingTraffic()) << "No pending reply for ping echo response unreachable with ID" - << QString("0x%1").arg(icmpId, 4, 16, QChar('0')) - << "Sequence:" << icmpSequnceNumber - << "from" << nestedSenderAddress.toString() << "to" << nestedDestinationAddress.toString(); + << QString("0x%1").arg(icmpId, 4, 16, QChar('0')) + << "Sequence:" << icmpSequnceNumber + << "from" << nestedSenderAddress.toString() << "to" << nestedDestinationAddress.toString(); return; } @@ -508,20 +549,74 @@ void Ping::onSocketReadyRead(int socketDescriptor) void Ping::onHostLookupFinished(const QHostInfo &info) { - PingReply *reply = m_pendingHostLookups.value(info.lookupId()); - if (!reply) { - qCWarning(dcPing()) << "Could not find reply after host lookup."; - return; - } + if (m_pendingHostNameLookups.contains(info.lookupId())) { - if (info.error() != QHostInfo::NoError) { - qCWarning(dcPing()) << "Failed to look up hostname after successfull ping" << reply->targetHostAddress().toString() << info.error(); - } else { - qCDebug(dcPing()) << "Looked up hostname after successfull ping" << reply->targetHostAddress().toString() << info.hostName(); - if (info.hostName() != reply->targetHostAddress().toString()) { - reply->m_hostName = info.hostName(); + PingReply *reply = m_pendingHostNameLookups.take(info.lookupId()); + if (!reply) { + qCWarning(dcPing()) << "Could not find ping reply after finished host lookup" << info.hostName() << info.addresses() << info.errorString(); + return; } - } - finishReply(reply, PingReply::ErrorNoError); + PingReply::Error pingError = PingReply::ErrorNoError; + switch(info.error()) { + case QHostInfo::NoError: + qCDebug(dcPing()) << "Looked up hostname after successfull ping" << reply->targetHostAddress().toString() << info.hostName(); + if (info.hostName() != reply->targetHostAddress().toString()) + reply->m_hostName = info.hostName(); + + break; + case QHostInfo::HostNotFound: + qCWarning(dcPing()) << "Unable to find hostname:" << reply->hostName() << info.errorString(); + pingError = PingReply::ErrorHostNameNotFound; + break; + default: + qCWarning(dcPing()) << "Failed to lookup hostname" << (reply->targetHostAddress().isNull() ? reply->hostName() : reply->targetHostAddress().toString()) << info.errorString(); + pingError = PingReply::ErrorHostNameLookupFailed; + break; + } + + finishReply(reply, pingError); + + } else if (m_pendingHostAddressLookups.contains(info.lookupId())) { + + PingReply *reply = m_pendingHostAddressLookups.take(info.lookupId()); + if (!reply) { + qCWarning(dcPing()) << "Could not find ping reply after finished host lookup" << info.hostName() << info.addresses() << info.errorString(); + return; + } + + PingReply::Error pingError = PingReply::ErrorNoError; + switch(info.error()) { + case QHostInfo::NoError: + qCDebug(dcPing()) << "Looked up address for hostname finished successfully" << info.hostName() << info.addresses(); + if (info.addresses().isEmpty()) { + qCWarning(dcPing()) << "Looked up address finished succesfully but there are no addresses available for" << info.hostName(); + pingError = PingReply::ErrorHostNameNotFound; + } else { + reply->m_targetHostAddress = info.addresses().first(); + reply->m_networkInterface = NetworkUtils::getInterfaceForHostaddress(reply->targetHostAddress()); + pingError = PingReply::ErrorNoError; + } + break; + case QHostInfo::HostNotFound: + qCDebug(dcPing()) << "Unable to find hostname:" << reply->hostName() << info.errorString(); + pingError = PingReply::ErrorHostNameNotFound; + break; + default: + qCWarning(dcPing()) << "Failed to lookup hostname" << (reply->targetHostAddress().isNull() ? reply->hostName() : reply->targetHostAddress().toString()) << info.errorString(); + pingError = PingReply::ErrorHostNameLookupFailed; + break; + } + + if (pingError != PingReply::ErrorNoError) { + // Host lookup failed, we cannot continue, nothing to ping + finishReply(reply, pingError); + } else { + // Ping the resolved host address + m_replyQueue.enqueue(reply); + sendNextReply(); + } + } else { + qCWarning(dcPing()) << "Host name lookup finished but we have no ping reply for it. Ignoring looked up information" << info.hostName() << info.addresses() << info.error(); + } } diff --git a/libnymea/network/ping.h b/libnymea/network/ping.h index 3545b196..26ae1c85 100644 --- a/libnymea/network/ping.h +++ b/libnymea/network/ping.h @@ -67,6 +67,8 @@ public: PingReply *ping(const QHostAddress &hostAddress, uint retries = 3); PingReply *ping(const QHostAddress &hostAddress, bool lookupHost, uint retries = 3); + PingReply *ping(const QString &hostName, uint retries = 3); + signals: void availableChanged(bool available); @@ -91,7 +93,8 @@ private: QTimer *m_queueTimer = nullptr; PingReply *m_currentReply = nullptr; void sendNextReply(); - QHash m_pendingHostLookups; + QHash m_pendingHostNameLookups; + QHash m_pendingHostAddressLookups; //Error performPing(const QString &address); void performPing(PingReply *reply); @@ -104,13 +107,13 @@ private: quint16 calculateRequestId(); PingReply *createReply(const QHostAddress &hostAddress); + PingReply *createReply(const QString &hostName); void finishReply(PingReply *reply, PingReply::Error error); void cleanUpReply(PingReply *reply); private slots: void onSocketReadyRead(int socketDescriptor); void onHostLookupFinished(const QHostInfo &info); - }; #endif // PING_H diff --git a/libnymea/network/pingreply.h b/libnymea/network/pingreply.h index 0276d824..e45f57d8 100644 --- a/libnymea/network/pingreply.h +++ b/libnymea/network/pingreply.h @@ -59,7 +59,9 @@ public: ErrorSocketError, ErrorTimeout, ErrorHostUnreachable, - ErrorInvalidHostAddress + ErrorInvalidHostAddress, + ErrorHostNameLookupFailed, + ErrorHostNameNotFound }; Q_ENUM(Error) From 46ccc8a46b9074190efd5a5cc5623c1201f5ae82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 18 Dec 2024 10:06:40 +0100 Subject: [PATCH 6/7] Update mock plugin info --- plugins/mock/plugininfo.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/mock/plugininfo.h b/plugins/mock/plugininfo.h index 3148fb12..d16ce9ba 100644 --- a/plugins/mock/plugininfo.h +++ b/plugins/mock/plugininfo.h @@ -14,7 +14,7 @@ #include #include -extern "C" const QString libnymea_api_version() { return QString("8.0.0");} +extern "C" const QString libnymea_api_version() { return QString("9.0.0");} Q_DECLARE_LOGGING_CATEGORY(dcMock) Q_LOGGING_CATEGORY(dcMock, "Mock") From 1c6fbc8d1eb886d151af0149b8332d6d17bc793a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Wed, 8 Jan 2025 16:36:38 +0100 Subject: [PATCH 7/7] Add network device mock thing --- plugins/mock/extern-plugininfo.h | 5 ++ plugins/mock/integrationpluginmock.cpp | 27 ++++++++++ plugins/mock/integrationpluginmock.json | 53 ++++++++++++++++++++ plugins/mock/plugininfo.h | 20 ++++++++ tests/auto/integrations/testintegrations.cpp | 4 +- 5 files changed, 107 insertions(+), 2 deletions(-) diff --git a/plugins/mock/extern-plugininfo.h b/plugins/mock/extern-plugininfo.h index 9138c85b..5924a803 100644 --- a/plugins/mock/extern-plugininfo.h +++ b/plugins/mock/extern-plugininfo.h @@ -232,5 +232,10 @@ extern StateTypeId virtualIoTemperatureSensorMockInputStateTypeId; extern StateTypeId virtualIoTemperatureSensorMockTemperatureStateTypeId; extern ActionTypeId virtualIoTemperatureSensorMockInputActionTypeId; extern ParamTypeId virtualIoTemperatureSensorMockInputActionInputParamTypeId; +extern ThingClassId networkDeviceMockThingClassId; +extern ParamTypeId networkDeviceMockThingMacAddressParamTypeId; +extern ParamTypeId networkDeviceMockThingHostNameParamTypeId; +extern ParamTypeId networkDeviceMockThingAddressParamTypeId; +extern ParamTypeId networkDeviceMockDiscoveryResultTypeParamTypeId; #endif // EXTERNPLUGININFO_H diff --git a/plugins/mock/integrationpluginmock.cpp b/plugins/mock/integrationpluginmock.cpp index 85127145..02424271 100644 --- a/plugins/mock/integrationpluginmock.cpp +++ b/plugins/mock/integrationpluginmock.cpp @@ -126,6 +126,27 @@ void IntegrationPluginMock::discoverThings(ThingDiscoveryInfo *info) return; } + if (info->thingClassId() == networkDeviceMockThingClassId) { + qCDebug(dcMock()) << "starting network device mock discovery:" << info->params(); + QTimer::singleShot(1000, info, [info](){ + QString resultType = info->params().paramValue(networkDeviceMockDiscoveryResultTypeParamTypeId).toString(); + ParamList params; + if (resultType == "MAC address") { + params.append(Param(networkDeviceMockThingMacAddressParamTypeId, "00:11:22:33:44:55")); + } else if (resultType == "Host name") { + params.append(Param(networkDeviceMockThingHostNameParamTypeId, "hostname.localhost")); + } else if (resultType == "IP address") { + params.append(Param(networkDeviceMockThingAddressParamTypeId, "127.0.0.1")); + } + + ThingDescriptor descriptor(networkDeviceMockThingClassId, "Mocked Thing (networkdevice)", QString()); + descriptor.setParams(params); + info->addThingDescriptor(descriptor); + info->finish(Thing::ThingErrorNoError); + }); + return; + } + qCWarning(dcMock()) << "Cannot discover for ThingClassId" << info->thingClassId(); info->finish(Thing::ThingErrorThingNotFound); } @@ -273,6 +294,12 @@ void IntegrationPluginMock::setupThing(ThingSetupInfo *info) return; } + if (info->thing()->thingClassId() == networkDeviceMockThingClassId) { + qCDebug(dcMock()) << "Network device mock setup complete"; + info->finish(Thing::ThingErrorNoError); + return; + } + qCWarning(dcMock()) << "Unhandled thing class" << info->thing()->thingClass(); info->finish(Thing::ThingErrorThingClassNotFound); } diff --git a/plugins/mock/integrationpluginmock.json b/plugins/mock/integrationpluginmock.json index 60af47ca..47f1fec9 100644 --- a/plugins/mock/integrationpluginmock.json +++ b/plugins/mock/integrationpluginmock.json @@ -1198,6 +1198,59 @@ "defaultValue": -20 } ] + }, + { + "id": "cd8fad43-174b-4ca4-a225-c07e4dbba10a", + "name": "networkDeviceMock", + "displayName": "Mocked Thing (network device)", + "interfaces": ["networkdevice"], + "createMethods": ["discovery", "user"], + "setupMethod": "justAdd", + "discoveryParamTypes": [ + { + "id": "762d9bc3-c07e-42e7-8dab-62c980998677", + "name": "resultType", + "displayName": "Result type", + "type": "QString", + "defaultValue": "MAC address", + "allowedValues": [ + "MAC address", + "Host name", + "IP address" + ] + } + ], + "paramTypes": [ + { + "id": "5c2462ca-883e-4fe1-91f2-7190f9363247", + "name": "macAddress", + "displayName": "MAC address", + "type": "QString", + "defaultValue":"", + "inputType": "MacAddress", + "defaultValue": "", + "readOnly": true + }, + { + "id": "ae867d45-c743-4185-87d3-1a027c985f11", + "name": "hostName", + "displayName": "Host name", + "type": "QString", + "inputType": "TextLine", + "defaultValue": "" + }, + { + "id": "619fb102-0ebd-497e-9960-615c5d347db9", + "name": "address", + "displayName": "IP address", + "type": "QString", + "inputType": "IPv4Address", + "defaultValue": "" + } + ], + "stateTypes": [ ], + "actionTypes": [ ], + "eventTypes": [ ] } ] } diff --git a/plugins/mock/plugininfo.h b/plugins/mock/plugininfo.h index d16ce9ba..c8ee9052 100644 --- a/plugins/mock/plugininfo.h +++ b/plugins/mock/plugininfo.h @@ -236,6 +236,11 @@ StateTypeId virtualIoTemperatureSensorMockInputStateTypeId = StateTypeId("{fd341 StateTypeId virtualIoTemperatureSensorMockTemperatureStateTypeId = StateTypeId("{db9cc518-1012-47e2-8212-6e616fed07a6}"); ActionTypeId virtualIoTemperatureSensorMockInputActionTypeId = ActionTypeId("{fd341f72-6d9a-4812-9f66-47197c48a935}"); ParamTypeId virtualIoTemperatureSensorMockInputActionInputParamTypeId = ParamTypeId("{fd341f72-6d9a-4812-9f66-47197c48a935}"); +ThingClassId networkDeviceMockThingClassId = ThingClassId("{cd8fad43-174b-4ca4-a225-c07e4dbba10a}"); +ParamTypeId networkDeviceMockThingMacAddressParamTypeId = ParamTypeId("{5c2462ca-883e-4fe1-91f2-7190f9363247}"); +ParamTypeId networkDeviceMockThingHostNameParamTypeId = ParamTypeId("{ae867d45-c743-4185-87d3-1a027c985f11}"); +ParamTypeId networkDeviceMockThingAddressParamTypeId = ParamTypeId("{619fb102-0ebd-497e-9960-615c5d347db9}"); +ParamTypeId networkDeviceMockDiscoveryResultTypeParamTypeId = ParamTypeId("{762d9bc3-c07e-42e7-8dab-62c980998677}"); const QString translations[] { //: The name of the Browser Item ActionType ({00b8f0a8-99ca-4aa4-833d-59eb8d4d6de3}) of ThingClass mock @@ -361,6 +366,12 @@ const QString translations[] { //: The name of the ThingClass ({f8917e12-c9cb-4ea1-a06e-1ce6db2194f3}) QT_TRANSLATE_NOOP("mock", "Generic Temperature Sensor (Mock)"), + //: The name of the ParamType (ThingClass: networkDeviceMock, Type: thing, ID: {ae867d45-c743-4185-87d3-1a027c985f11}) + QT_TRANSLATE_NOOP("mock", "Host name"), + + //: The name of the ParamType (ThingClass: networkDeviceMock, Type: thing, ID: {619fb102-0ebd-497e-9960-615c5d347db9}) + QT_TRANSLATE_NOOP("mock", "IP address"), + //: The name of the ParamType (ThingClass: inputTypeMock, Type: thing, ID: {9e5f86a0-4bb3-4892-bff8-3fc4032af6e2}) QT_TRANSLATE_NOOP("mock", "IPv4 address"), @@ -388,6 +399,9 @@ const QString translations[] { //: The name of the StateType ({23df3dce-bd10-4eb3-b5e3-221168440cd4}) of ThingClass inputTypeMock QT_TRANSLATE_NOOP("mock", "Localized list"), + //: The name of the ParamType (ThingClass: networkDeviceMock, Type: thing, ID: {5c2462ca-883e-4fe1-91f2-7190f9363247}) + QT_TRANSLATE_NOOP("mock", "MAC address"), + //: The name of the ParamType (ThingClass: inputTypeMock, Type: thing, ID: {e93db587-7919-48f3-8c88-1651de63c765}) QT_TRANSLATE_NOOP("mock", "Mac address"), @@ -481,6 +495,9 @@ const QString translations[] { //: The name of the ThingClass ({6fe07a77-9c07-4736-81e2-d504314bbcb9}) QT_TRANSLATE_NOOP("mock", "Mocked Thing (User & Password)"), + //: The name of the ThingClass ({cd8fad43-174b-4ca4-a225-c07e4dbba10a}) + QT_TRANSLATE_NOOP("mock", "Mocked Thing (network device)"), + //: The name of the plugin mock ({727a4a9a-c187-446f-aadf-f1b2220607d1}) QT_TRANSLATE_NOOP("mock", "Mocked things"), @@ -508,6 +525,9 @@ const QString translations[] { //: The name of the ParamType (ThingClass: mock, Type: discovery, ID: {d222adb4-2f9c-4c3f-8655-76400d0fb6ce}) QT_TRANSLATE_NOOP("mock", "Result count"), + //: The name of the ParamType (ThingClass: networkDeviceMock, Type: discovery, ID: {762d9bc3-c07e-42e7-8dab-62c980998677}) + QT_TRANSLATE_NOOP("mock", "Result type"), + //: The name of the ParamType (ThingClass: inputTypeMock, Type: thing, ID: {22add8c9-ee4f-43ad-8931-58e999313ac3}) QT_TRANSLATE_NOOP("mock", "Search text"), diff --git a/tests/auto/integrations/testintegrations.cpp b/tests/auto/integrations/testintegrations.cpp index 93f1db9f..4b1835f0 100644 --- a/tests/auto/integrations/testintegrations.cpp +++ b/tests/auto/integrations/testintegrations.cpp @@ -298,8 +298,8 @@ void TestIntegrations::getThingClasses_data() QTest::addColumn>("thingClassIds"); QTest::addColumn("resultCount"); - QTest::newRow("vendor nymea") << nymeaVendorId << QList() << 16; - QTest::newRow("no filter") << VendorId() << QList() << 16; + QTest::newRow("vendor nymea") << nymeaVendorId << QList() << 17; + QTest::newRow("no filter") << VendorId() << QList() << 17; QTest::newRow("invalid vendor") << VendorId("93e7d361-8025-4354-b17e-b68406c800bc") << QList() << 0; QTest::newRow("mockThingClassId") << VendorId() << (QList() << mockThingClassId) << 1; QTest::newRow("invalid thingClassId") << VendorId() << (QList() << ThingClassId("6c78ec28-09b6-476d-ac27-1d6966a45c57")) << 0;