Add mac address class and unit tests

Finish updated network discovery
Improve ARP and monitor handling
Introduce network device info cache housekeeping
pull/511/head
Simon Stürz 2022-04-08 11:42:00 +02:00
parent 14ea1bef4a
commit c59185672f
24 changed files with 938 additions and 241 deletions

View File

@ -1,4 +1,4 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2022, nymea GmbH
* Contact: contact@nymea.io
@ -37,8 +37,6 @@
#include <network/arpsocket.h>
#include <network/networkutils.h>
#include <QDateTime>
NYMEA_LOGGING_CATEGORY(dcNetworkDeviceDiscovery, "NetworkDeviceDiscovery")
namespace nymeaserver {
@ -50,9 +48,8 @@ NetworkDeviceDiscoveryImpl::NetworkDeviceDiscoveryImpl(QObject *parent) :
m_arpSocket = new ArpSocket(this);
connect(m_arpSocket, &ArpSocket::arpResponse, this, &NetworkDeviceDiscoveryImpl::onArpResponseReceived);
bool arpAvailable = m_arpSocket->openSocket();
if (!arpAvailable) {
if (!arpAvailable)
m_arpSocket->closeSocket();
}
// Create ping socket
m_ping = new Ping(this);
@ -86,6 +83,9 @@ NetworkDeviceDiscoveryImpl::NetworkDeviceDiscoveryImpl(QObject *parent) :
m_cacheSettings = new QSettings(NymeaSettings::cachePath() + "/network-device-discovery.cache", QSettings::IniFormat);
loadNetworkDeviceCache();
// Start the monitor timer in any case, we do also the cache cleanup there...
m_monitorTimer->start();
}
NetworkDeviceDiscoveryImpl::~NetworkDeviceDiscoveryImpl()
@ -133,42 +133,56 @@ bool NetworkDeviceDiscoveryImpl::running() const
return m_running;
}
NetworkDeviceMonitor *NetworkDeviceDiscoveryImpl::registerMonitor(const QString &macAddress)
NetworkDeviceMonitor *NetworkDeviceDiscoveryImpl::registerMonitor(const MacAddress &macAddress)
{
qCDebug(dcNetworkDeviceDiscovery()) << "Register new network device monitor for" << macAddress;
// Make sure we creare only one monitor per mac
if (m_monitors.contains(macAddress))
return m_monitors.value(macAddress);
if (macAddress.isNull()) {
qCWarning(dcNetworkDeviceDiscovery()) << "Could not register monitor for invalid" << macAddress.toString();
return nullptr;
}
// Fill in cached information
NetworkDeviceInfo info;
if (m_networkInfoCache.contains(macAddress)) {
info = m_networkInfoCache.value(macAddress);
} else {
info.setMacAddress(macAddress);
qCDebug(dcNetworkDeviceDiscovery()) << "Adding mac address monitor for unresolved mac address. Starting a discovery...";
info.setMacAddress(macAddress.toString());
}
NetworkDeviceMonitorImpl *monitor = new NetworkDeviceMonitorImpl(macAddress, this);
monitor->setNetworkDeviceInfo(info);
m_monitors.insert(macAddress, 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...";
discover();
}
NetworkDeviceMonitorImpl *monitor = new NetworkDeviceMonitorImpl(info, this);
bool initRequired = m_monitors.isEmpty();
m_monitors.insert(macAddress, monitor);
if (initRequired) {
m_monitorTimer->start();
evaluateMonitors();
}
evaluateMonitor(monitor);
return monitor;
}
void NetworkDeviceDiscoveryImpl::unregisterMonitor(const QString &macAddress)
void NetworkDeviceDiscoveryImpl::unregisterMonitor(const MacAddress &macAddress)
{
if (m_monitors.contains(macAddress)) {
NetworkDeviceMonitor *monitor = m_monitors.take(macAddress);
qCDebug(dcNetworkDeviceDiscovery()) << "Unregister" << monitor;
monitor->deleteLater();
}
}
void NetworkDeviceDiscoveryImpl::unregisterMonitor(NetworkDeviceMonitor *networkDeviceMonitor)
{
unregisterMonitor(networkDeviceMonitor->networkDeviceInfo().macAddress());
unregisterMonitor(MacAddress(networkDeviceMonitor->networkDeviceInfo().macAddress()));
}
PingReply *NetworkDeviceDiscoveryImpl::ping(const QHostAddress &address)
@ -176,12 +190,29 @@ PingReply *NetworkDeviceDiscoveryImpl::ping(const QHostAddress &address)
// Note: we use any ping used trough this method also for the monitor evaluation
PingReply *reply = m_ping->ping(address);
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() == address) {
// 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()) {
if (monitor->networkDeviceInfo().address() == address) {
processMonitorPingResult(reply, monitor);
}
}
});
return reply;
}
@ -190,6 +221,11 @@ MacAddressDatabaseReply *NetworkDeviceDiscoveryImpl::lookupMacAddress(const QStr
return m_macAddressDatabase->lookupMacAddress(macAddress);
}
MacAddressDatabaseReply *NetworkDeviceDiscoveryImpl::lookupMacAddress(const MacAddress &macAddress)
{
return lookupMacAddress(macAddress.toString());
}
bool NetworkDeviceDiscoveryImpl::sendArpRequest(const QHostAddress &address)
{
if (m_arpSocket && m_arpSocket->isOpen())
@ -202,7 +238,6 @@ void NetworkDeviceDiscoveryImpl::setEnabled(bool enabled)
{
m_enabled = enabled;
emit enabledChanged(m_enabled);
// TODO: disable network discovery if false, not used for now
}
@ -259,7 +294,7 @@ void NetworkDeviceDiscoveryImpl::pingAllNetworkDevices()
}
}
if (m_runningPingRepies.isEmpty() && m_currentReply) {
if (m_runningPingRepies.isEmpty() && m_currentReply && !m_discoveryTimer->isActive()) {
qCWarning(dcNetworkDeviceDiscovery()) << "All ping replies finished for discovery." << m_currentReply->networkDeviceInfos().count();
finishDiscovery();
}
@ -269,22 +304,9 @@ void NetworkDeviceDiscoveryImpl::pingAllNetworkDevices()
}
}
void NetworkDeviceDiscoveryImpl::finishDiscovery()
{
m_discoveryTimer->stop();
m_running = false;
emit runningChanged(m_running);
emit networkDeviceInfoCacheUpdated();
m_currentReply->processDiscoveryFinished();
m_currentReply->deleteLater();
m_currentReply = nullptr;
}
void NetworkDeviceDiscoveryImpl::processMonitorPingResult(PingReply *reply, NetworkDeviceMonitorImpl *monitor)
{
// 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());
@ -298,36 +320,57 @@ void NetworkDeviceDiscoveryImpl::processMonitorPingResult(PingReply *reply, Netw
void NetworkDeviceDiscoveryImpl::loadNetworkDeviceCache()
{
qCDebug(dcNetworkDeviceDiscovery()) << "Loading cached network device information from" << m_cacheSettings->fileName();
m_networkInfoCache.clear();
QDateTime now = QDateTime::currentDateTime();
m_cacheSettings->beginGroup("NetworkDeviceInfos");
foreach (const QString &macAddress, m_cacheSettings->childGroups()) {
m_cacheSettings->beginGroup(macAddress);
NetworkDeviceInfo info(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("");
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;
m_networkInfoCache.insert(info.macAddress(), info);
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->endGroup(); // NetworkDeviceInfos
// We just did some housekeeping while loading from the cache
m_lastCacheHousekeeping = QDateTime::currentDateTime();
}
void NetworkDeviceDiscoveryImpl::removeFromNetworkDeviceCache(const QString &macAddress)
void NetworkDeviceDiscoveryImpl::removeFromNetworkDeviceCache(const MacAddress &macAddress)
{
if (macAddress.isEmpty())
if (macAddress.isNull())
return;
m_networkInfoCache.remove(macAddress);
m_lastSeen.remove(macAddress);
m_cacheSettings->beginGroup("NetworkDeviceInfos");
m_cacheSettings->beginGroup(macAddress);
m_cacheSettings->beginGroup(macAddress.toString());
m_cacheSettings->remove("");
m_cacheSettings->endGroup(); // mac address
m_cacheSettings->endGroup(); // NetworkDeviceInfos
@ -341,54 +384,82 @@ void NetworkDeviceDiscoveryImpl::saveNetworkDeviceCache(const NetworkDeviceInfo
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
}
void NetworkDeviceDiscoveryImpl::updateCache(const NetworkDeviceInfo &deviceInfo)
{
if (deviceInfo.macAddress().isEmpty())
MacAddress macAddress(deviceInfo.macAddress());
if (macAddress.isNull())
return;
if (m_monitors.contains(deviceInfo.macAddress())) {
NetworkDeviceMonitorImpl *monitor = m_monitors.value(deviceInfo.macAddress());
monitor->updateNetworkDeviceInfo(deviceInfo);
if (m_monitors.contains(macAddress)) {
NetworkDeviceMonitorImpl *monitor = m_monitors.value(macAddress);
monitor->setNetworkDeviceInfo(deviceInfo);
}
if (m_networkInfoCache.value(deviceInfo.macAddress()) == deviceInfo)
if (m_networkInfoCache.value(macAddress) == deviceInfo)
return;
m_networkInfoCache[deviceInfo.macAddress()] = deviceInfo;
m_networkInfoCache[macAddress] = deviceInfo;
saveNetworkDeviceCache(deviceInfo);
}
void NetworkDeviceDiscoveryImpl::onArpResponseReceived(const QNetworkInterface &interface, const QHostAddress &address, const QString &macAddress)
void NetworkDeviceDiscoveryImpl::evaluateMonitor(NetworkDeviceMonitorImpl *monitor)
{
// Ignore ARP from zero mac
if (macAddress == QString("00:00:00:00:00:00")) {
qCDebug(dcNetworkDeviceDiscovery()) << "Ignoring ARP reply from" << address.toString() << macAddress << interface.name();
return;
// Start action if we have not seen the device for gracePeriod seconds
QDateTime currentDateTime = QDateTime::currentDateTime();
bool requiresRefresh = false;
if (monitor->lastSeen().isNull()) {
qCDebug(dcNetworkDeviceDiscovery()) << monitor << "requires refresh. Not seen since application start.";
requiresRefresh = true;
}
qCDebug(dcNetworkDeviceDiscovery()) << "ARP reply received" << address.toString() << macAddress << interface.name();
if (!requiresRefresh && currentDateTime > monitor->lastSeen().addSecs(m_monitorInterval)) {
qCDebug(dcNetworkDeviceDiscovery()) << monitor << "requires refresh. Not see since" << (currentDateTime.toMSecsSinceEpoch() - monitor->lastSeen().toMSecsSinceEpoch()) / 1000.0 << "s";
requiresRefresh = true;
}
if (!requiresRefresh)
return;
if (monitor->networkDeviceInfo().address().isNull())
return;
// Try to ping first
qCDebug(dcNetworkDeviceDiscovery()) << monitor << "try to ping" << monitor->networkDeviceInfo().address().toString();
monitor->m_lastConnectionAttempt = currentDateTime;
PingReply *reply = m_ping->ping(monitor->networkDeviceInfo().address());
connect(reply, &PingReply::finished, monitor, [=](){
processMonitorPingResult(reply, monitor);
});
}
void NetworkDeviceDiscoveryImpl::processArpTraffic(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress)
{
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[macAddress]);
saveNetworkDeviceCache(m_networkInfoCache.value(macAddress));
}
}
// Update the monitors
foreach (NetworkDeviceMonitorImpl *monitor, m_monitors) {
if (monitor->networkDeviceInfo().macAddress() == macAddress) {
monitor->setLastSeen(QDateTime::currentDateTime());
monitor->setReachable(true);
if (monitor->networkDeviceInfo().address() != address) {
monitor->m_networkDeviceInfo.setAddress(address);
emit monitor->networkDeviceInfoChanged(monitor->networkDeviceInfo());
}
NetworkDeviceMonitorImpl *monitor = m_monitors.value(macAddress);
if (monitor) {
monitor->setLastSeen(now);
monitor->setReachable(true);
if (monitor->networkDeviceInfo().address() != address) {
monitor->m_networkDeviceInfo.setAddress(address);
qCDebug(dcNetworkDeviceDiscovery()) << "NetworkDeviceMonitor" << monitor << "ip address changed";
emit monitor->networkDeviceInfoChanged(monitor->networkDeviceInfo());
}
}
@ -408,7 +479,7 @@ void NetworkDeviceDiscoveryImpl::onArpResponseReceived(const QNetworkInterface &
} else {
// Lookup the mac address vendor if possible
if (m_macAddressDatabase->available()) {
MacAddressDatabaseReply *reply = m_macAddressDatabase->lookupMacAddress(macAddress);
MacAddressDatabaseReply *reply = m_macAddressDatabase->lookupMacAddress(macAddress.toString());
connect(reply, &MacAddressDatabaseReply::finished, m_currentReply, [=](){
qCDebug(dcNetworkDeviceDiscovery()) << "MAC manufacturer lookup finished for" << macAddress << ":" << reply->manufacturer();
m_currentReply->processMacManufacturer(macAddress, reply->manufacturer());
@ -421,41 +492,101 @@ void NetworkDeviceDiscoveryImpl::onArpResponseReceived(const QNetworkInterface &
}
}
bool NetworkDeviceDiscoveryImpl::longerAgoThan(const QDateTime &dateTime, uint seconds)
{
uint duration = (QDateTime::currentDateTime().toMSecsSinceEpoch() - dateTime.toMSecsSinceEpoch()) / 1000.0;
return duration >= seconds;
}
QDateTime NetworkDeviceDiscoveryImpl::convertMinuteBased(const QDateTime &dateTime)
{
// We store the date time on minute accuracy to have
// less write cycles and a higher resolution is not necessary
// If the given datetime is null, use the current datetime
QDateTime dateTimeToConvert;
if (dateTime.isNull()) {
dateTimeToConvert = QDateTime::currentDateTime();
} else {
dateTimeToConvert = dateTime;
}
dateTimeToConvert.setTime(QTime(dateTimeToConvert.time().hour(), dateTimeToConvert.time().minute(), 0, 0));
return dateTimeToConvert;
}
void NetworkDeviceDiscoveryImpl::onArpResponseReceived(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress)
{
// Ignore ARP from zero mac
if (macAddress.isNull()) {
qCDebug(dcNetworkDeviceDiscovery()) << "Ignoring ARP reply from" << address.toString() << macAddress.toString() << interface.name();
return;
}
qCDebug(dcNetworkDeviceDiscovery()) << "ARP reply received" << address.toString() << macAddress.toString() << interface.name();
processArpTraffic(interface, address, macAddress);
}
void NetworkDeviceDiscoveryImpl::onArpRequstReceived(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress)
{
// Ignore ARP from zero mac
if (macAddress.isNull()) {
qCDebug(dcNetworkDeviceDiscovery()) << "Ignoring ARP reply from" << address.toString() << macAddress.toString() << interface.name();
return;
}
qCDebug(dcNetworkDeviceDiscovery()) << "ARP request received" << address.toString() << macAddress.toString() << interface.name();
processArpTraffic(interface, address, macAddress);
}
void NetworkDeviceDiscoveryImpl::evaluateMonitors()
{
bool monitorRequiresRediscovery = false;
foreach (NetworkDeviceMonitorImpl *monitor, m_monitors) {
// Start action if we have not seen the device for gracePeriod seconds
const int gracePeriod = 60;
QDateTime currentDateTime = QDateTime::currentDateTime();
evaluateMonitor(monitor);
bool requiresRefresh = false;
// Check if there is any monitor which has not be seen since
if (monitor->m_lastConnectionAttempt.isValid() && longerAgoThan(monitor->lastSeen(), m_monitorInterval)) {
monitorRequiresRediscovery = true;
}
}
if (monitor->lastSeen().isNull()) {
qCDebug(dcNetworkDeviceDiscovery()) << monitor << "requires refresh. Not seen yet";
requiresRefresh = true;
if (monitorRequiresRediscovery && longerAgoThan(m_lastDiscovery, m_rediscoveryInterval)) {
qCDebug(dcNetworkDeviceDiscovery()) << "There are unreachable monitors and the last discovery is more than" << m_rediscoveryInterval << "s ago. Starting network discovery to search the monitored network devices...";
discover();
}
// Do some cache housekeeping if required
if (m_lastCacheHousekeeping.addDays(1) < QDateTime::currentDateTime()) {
qCDebug(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);
}
}
if (!requiresRefresh && currentDateTime > monitor->lastSeen().addSecs(gracePeriod)) {
qCDebug(dcNetworkDeviceDiscovery()) << monitor << "requires refresh. Not see since" << (currentDateTime.toMSecsSinceEpoch() - monitor->lastSeen().toMSecsSinceEpoch()) / 1000.0 << "s";
requiresRefresh = true;
}
if (!requiresRefresh)
continue;
if (monitor->networkDeviceInfo().address().isNull()) {
// Not known yet
// TODO: load from cache
continue;
}
// Try to ping first
qCDebug(dcNetworkDeviceDiscovery()) << monitor << "try to ping" << monitor->networkDeviceInfo().address().toString();
PingReply *reply = m_ping->ping(monitor->networkDeviceInfo().address());
connect(reply, &PingReply::finished, monitor, [=](){
processMonitorPingResult(reply, monitor);
});
m_lastCacheHousekeeping = now;
}
}
void NetworkDeviceDiscoveryImpl::finishDiscovery()
{
qCDebug(dcNetworkDeviceDiscovery()) << "Finishing discovery...";
m_discoveryTimer->stop();
m_running = false;
emit runningChanged(m_running);
emit networkDeviceInfoCacheUpdated();
m_lastDiscovery = QDateTime::currentDateTime();
m_currentReply->processDiscoveryFinished();
m_currentReply->deleteLater();
m_currentReply = nullptr;
}
}

View File

@ -34,6 +34,7 @@
#include <QObject>
#include <QSettings>
#include <QLoggingCategory>
#include <QDateTime>
#include <network/networkdeviceinfo.h>
#include <network/networkdevicediscovery.h>
@ -65,13 +66,15 @@ public:
NetworkDeviceDiscoveryReply *discover() override;
NetworkDeviceMonitor *registerMonitor(const QString &macAddress) override;
void unregisterMonitor(const QString &macAddress) override;
NetworkDeviceMonitor *registerMonitor(const MacAddress &macAddress) override;
void unregisterMonitor(const MacAddress &macAddress) override;
void unregisterMonitor(NetworkDeviceMonitor *networkDeviceMonitor) override;
PingReply *ping(const QHostAddress &address) override;
MacAddressDatabaseReply *lookupMacAddress(const QString &macAddress) override;
MacAddressDatabaseReply *lookupMacAddress(const MacAddress &macAddress) override;
bool sendArpRequest(const QHostAddress &address) override;
@ -84,31 +87,48 @@ private:
Ping *m_ping = nullptr;
bool m_enabled = true;
bool m_running = false;
QTimer *m_discoveryTimer = nullptr;
QTimer *m_monitorTimer = nullptr;
QDateTime m_lastDiscovery;
QDateTime m_lastCacheHousekeeping;
uint m_rediscoveryInterval = 300; // 5 min
uint m_monitorInterval = 60; // 1 min
uint m_cacheCleanupPeriod = 30; // days
NetworkDeviceDiscoveryReplyImpl *m_currentReply = nullptr;
QList<PingReply *> m_runningPingRepies;
QHash<QString, NetworkDeviceMonitorImpl *> m_monitors;
QHash<MacAddress, NetworkDeviceMonitorImpl *> m_monitors;
QHash<MacAddress, QDateTime> m_lastSeen;
QSettings *m_cacheSettings;
QHash<QString, NetworkDeviceInfo> m_networkInfoCache;
QHash<MacAddress, NetworkDeviceInfo> m_networkInfoCache;
void pingAllNetworkDevices();
void finishDiscovery();
void processMonitorPingResult(PingReply *reply, NetworkDeviceMonitorImpl *monitor);
void loadNetworkDeviceCache();
void removeFromNetworkDeviceCache(const QString &macAddress);
void removeFromNetworkDeviceCache(const MacAddress &macAddress);
void saveNetworkDeviceCache(const NetworkDeviceInfo &deviceInfo);
void updateCache(const NetworkDeviceInfo &deviceInfo);
void evaluateMonitor(NetworkDeviceMonitorImpl *monitor);
void processArpTraffic(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress);
// Time helpers
bool longerAgoThan(const QDateTime &dateTime, uint minutes);
QDateTime convertMinuteBased(const QDateTime &dateTime = QDateTime());
private slots:
void onArpResponseReceived(const QNetworkInterface &interface, const QHostAddress &address, const QString &macAddress);
void onArpResponseReceived(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress);
void onArpRequstReceived(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress);
void evaluateMonitors();
void finishDiscovery();
};

View File

@ -69,23 +69,38 @@ bool NetworkDeviceDiscoveryReplyImpl::hasHostAddress(const QHostAddress &address
return ! macAddressFromHostAddress(address).isEmpty();
}
void NetworkDeviceDiscoveryReplyImpl::verifyComplete(const MacAddress &macAddress)
{
if (!m_networkDeviceCache.contains(macAddress))
return;
if (m_networkDeviceCache[macAddress].isComplete() && m_networkDeviceCache[macAddress].isValid()) {
if (m_networkDeviceInfos.hasMacAddress(macAddress)) {
if (m_networkDeviceInfos.get(macAddress) != m_networkDeviceCache.value(macAddress)) {
qCWarning(dcNetworkDeviceDiscovery()) << "Already complete network device info changed during discovery process! Please report a bug if you see this message.";
qCWarning(dcNetworkDeviceDiscovery()) << m_networkDeviceInfos.get(macAddress);
qCWarning(dcNetworkDeviceDiscovery()) << m_networkDeviceCache.value(macAddress);
}
} else {
m_networkDeviceInfos.append(m_networkDeviceCache.value(macAddress));
emit networkDeviceInfoAdded(m_networkDeviceCache[macAddress]);
}
}
}
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
m_networkDeviceCache[info.macAddress()].setHostName(hostName);
if (m_networkDeviceCache[info.macAddress()].isComplete() && m_networkDeviceCache[info.macAddress()].isValid()) {
emit networkDeviceInfoAdded(m_networkDeviceCache[info.macAddress()]);
m_networkDeviceInfos.append(m_networkDeviceCache.take(info.macAddress()));
}
emit hostAddressDiscovered(address);
MacAddress macAddress(info.macAddress());
m_networkDeviceCache[macAddress].setHostName(hostName);
verifyComplete(macAddress);
return;
}
}
// Not added yet
// Unknown and we have no mac address yet, add it to the ping cache
NetworkDeviceInfo info;
info.setAddress(address);
info.setHostName(hostName);
@ -93,50 +108,78 @@ void NetworkDeviceDiscoveryReplyImpl::processPingResponse(const QHostAddress &ad
emit hostAddressDiscovered(address);
}
void NetworkDeviceDiscoveryReplyImpl::processArpResponse(const QNetworkInterface &interface, const QHostAddress &address, const QString &macAddress)
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);
info.setAddress(address);
info.setNetworkInterface(interface);
info.setMacAddress(macAddress);
info.setMacAddress(macAddress.toString());
m_networkDeviceCache[macAddress] = info;
emit hostAddressDiscovered(address);
} else {
if (m_networkDeviceCache.contains(macAddress)) {
m_networkDeviceCache[macAddress].setNetworkInterface(interface);
m_networkDeviceCache[macAddress].setAddress(address);
m_networkDeviceCache[macAddress].setNetworkInterface(interface);
} else {
NetworkDeviceInfo info = m_pingCache.take(address);
NetworkDeviceInfo info(macAddress.toString());
info.setAddress(address);
info.setNetworkInterface(interface);
info.setMacAddress(macAddress);
m_networkDeviceCache[macAddress] = info;
}
}
if (m_networkDeviceCache[macAddress].isComplete() && m_networkDeviceCache[macAddress].isValid()) {
m_networkDeviceInfos.append(m_networkDeviceCache.take(macAddress));
emit networkDeviceInfoAdded(m_networkDeviceCache[macAddress]);
}
verifyComplete(macAddress);
}
void NetworkDeviceDiscoveryReplyImpl::processMacManufacturer(const QString &macAddress, const QString &manufacturer)
void NetworkDeviceDiscoveryReplyImpl::processMacManufacturer(const MacAddress &macAddress, const QString &manufacturer)
{
if (macAddress.isNull())
return;
if (m_networkDeviceCache.contains(macAddress)) {
m_networkDeviceCache[macAddress].setMacAddressManufacturer(manufacturer);
} else {
NetworkDeviceInfo info(macAddress);
NetworkDeviceInfo info(macAddress.toString());
info.setMacAddressManufacturer(manufacturer);
m_networkDeviceCache[macAddress] = info;
}
if (m_networkDeviceCache[macAddress].isComplete() && m_networkDeviceCache[macAddress].isValid()) {
m_networkDeviceInfos.append(m_networkDeviceCache.take(macAddress));
emit networkDeviceInfoAdded(m_networkDeviceCache[macAddress]);
}
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;
NetworkDeviceInfo info = m_networkDeviceCache.value(macAddress);
MacAddress infoMacAddress(info.macAddress());
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);
}
}
// Done, lets sort the result and inform
m_networkDeviceInfos.sortNetworkDevices();
qint64 durationMilliSeconds = QDateTime::currentMSecsSinceEpoch() - m_startTimestamp;
qCDebug(dcNetworkDeviceDiscovery()) << "Discovery finished. Found" << networkDeviceInfos().count() << "network devices in" << QTime::fromMSecsSinceStartOfDay(durationMilliSeconds).toString("mm:ss.zzz");
@ -164,8 +207,12 @@ void NetworkDeviceDiscoveryReplyImpl::processDiscoveryFinished()
}
qCDebug(dcNetworkDeviceDiscovery()) << "Rest:";
foreach (const NetworkDeviceInfo &info, m_networkDeviceCache) {
qCDebug(dcNetworkDeviceDiscovery()) << "--> " << info << "Complete:" << info.isComplete() << "Valid:" << info.isValid();
foreach (const MacAddress &macAddress, m_networkDeviceCache.keys()) {
if (m_networkDeviceInfos.hasMacAddress(macAddress))
continue;
NetworkDeviceInfo info = m_networkDeviceCache.value(macAddress);
qCDebug(dcNetworkDeviceDiscovery()) << "--> " << info << "Valid:" << info.isValid() << "Complete:" << info.isComplete() << info.incompleteProperties();
}
emit finished();

View File

@ -56,7 +56,7 @@ private:
NetworkDeviceInfos m_networkDeviceInfos; // Contains only complete and valid infos
NetworkDeviceInfos m_virtualNetworkDeviceInfos; // Contains ping responses without ARP, like VPN devices
QHash<QString, NetworkDeviceInfo> m_networkDeviceCache;
QHash<MacAddress, NetworkDeviceInfo> m_networkDeviceCache;
qint64 m_startTimestamp;
// Temporary cache for ping responses where the mac is not known yet (like VPN devices)
@ -65,10 +65,13 @@ private:
QString macAddressFromHostAddress(const QHostAddress &address);
bool hasHostAddress(const QHostAddress &address);
void verifyComplete(const MacAddress &macAddress);
// Add or update the network device info and verify if completed
void processPingResponse(const QHostAddress &address, const QString &hostName);
void processArpResponse(const QNetworkInterface &interface, const QHostAddress &address, const QString &macAddress);
void processMacManufacturer(const QString &macAddress, const QString &manufacturer);
void processArpResponse(const QNetworkInterface &interface, const QHostAddress &address, const MacAddress &macAddress);
void processMacManufacturer(const MacAddress &macAddress, const QString &manufacturer);
void processDiscoveryFinished();
};

View File

@ -32,9 +32,9 @@
namespace nymeaserver {
NetworkDeviceMonitorImpl::NetworkDeviceMonitorImpl(const NetworkDeviceInfo &networkDeviceInfo, QObject *parent) :
NetworkDeviceMonitorImpl::NetworkDeviceMonitorImpl(const MacAddress &macAddress, QObject *parent) :
NetworkDeviceMonitor(parent),
m_networkDeviceInfo(networkDeviceInfo)
m_macAddress(macAddress)
{
}
@ -44,11 +44,25 @@ NetworkDeviceMonitorImpl::~NetworkDeviceMonitorImpl()
}
MacAddress NetworkDeviceMonitorImpl::macAddress() const
{
return m_macAddress;
}
NetworkDeviceInfo NetworkDeviceMonitorImpl::networkDeviceInfo() const
{
return m_networkDeviceInfo;
}
void NetworkDeviceMonitorImpl::setNetworkDeviceInfo(const NetworkDeviceInfo &networkDeviceInfo)
{
if (m_networkDeviceInfo == networkDeviceInfo)
return;
m_networkDeviceInfo = networkDeviceInfo;
emit networkDeviceInfoChanged(m_networkDeviceInfo);
}
bool NetworkDeviceMonitorImpl::reachable() const
{
return m_reachable;
@ -74,16 +88,8 @@ void NetworkDeviceMonitorImpl::setLastSeen(const QDateTime lastSeen)
return;
m_lastSeen = lastSeen;
emit lastSeenChanged(m_lastSeen);
}
void NetworkDeviceMonitorImpl::updateNetworkDeviceInfo(const NetworkDeviceInfo &networkDeviceInfo)
{
if (m_networkDeviceInfo == networkDeviceInfo)
return;
m_networkDeviceInfo = networkDeviceInfo;
emit networkDeviceInfoChanged(m_networkDeviceInfo);
}
}

View File

@ -45,10 +45,13 @@ class NetworkDeviceMonitorImpl : public NetworkDeviceMonitor
friend class NetworkDeviceDiscoveryImpl;
public:
explicit NetworkDeviceMonitorImpl(const NetworkDeviceInfo &networkDeviceInfo, QObject *parent = nullptr);
explicit NetworkDeviceMonitorImpl(const MacAddress &macAddress, QObject *parent = nullptr);
~NetworkDeviceMonitorImpl();
MacAddress macAddress() const override;
NetworkDeviceInfo networkDeviceInfo() const override;
void setNetworkDeviceInfo(const NetworkDeviceInfo &networkDeviceInfo);
bool reachable() const override;
void setReachable(bool reachable);
@ -58,10 +61,10 @@ public:
private:
NetworkDeviceInfo m_networkDeviceInfo;
MacAddress m_macAddress;
bool m_reachable = false;
QDateTime m_lastSeen;
void updateNetworkDeviceInfo(const NetworkDeviceInfo &networkDeviceInfo);
QDateTime m_lastConnectionAttempt;
};

View File

@ -44,6 +44,7 @@ HEADERS += \
network/apikeys/apikeysprovider.h \
network/apikeys/apikeystorage.h \
network/arpsocket.h \
network/macaddress.h \
network/macaddressdatabasereply.h \
network/networkdevicediscovery.h \
network/networkdevicediscoveryreply.h \
@ -150,6 +151,7 @@ SOURCES += \
network/apikeys/apikeysprovider.cpp \
network/apikeys/apikeystorage.cpp \
network/arpsocket.cpp \
network/macaddress.cpp \
network/macaddressdatabasereply.cpp \
network/networkdevicediscovery.cpp \
network/networkdeviceinfo.cpp \

View File

@ -71,6 +71,9 @@ bool ArpSocket::sendRequest()
if (!m_isOpen)
return false;
// Initially load the arp cache before we start to broadcast
//loadArpCache();
// Send the ARP request trough each network interface
qCDebug(dcArpSocket()) << "Sending ARP request to all local network interfaces...";
foreach (const QNetworkInterface &networkInterface, QNetworkInterface::allInterfaces()) {
@ -94,7 +97,6 @@ bool ArpSocket::sendRequest(const QString &interfaceName)
return false;
}
loadArpCache(networkInterface);
return sendRequest(networkInterface);
}
@ -130,8 +132,6 @@ bool ArpSocket::sendRequest(const QNetworkInterface &networkInterface)
return false;
}
loadArpCache(networkInterface);
qCDebug(dcArpSocket()) << "Verifying network interface" << networkInterface.name() << networkInterface.hardwareAddress() << "...";
foreach (const QNetworkAddressEntry &entry, networkInterface.addressEntries()) {
// Only IPv4
@ -159,7 +159,7 @@ bool ArpSocket::sendRequest(const QNetworkInterface &networkInterface)
if (targetAddress == entry.ip())
continue;
sendRequestInternally(networkInterface.index(), networkInterface.hardwareAddress(), entry.ip(), "ff:ff:ff:ff:ff:ff", targetAddress);
sendRequestInternally(networkInterface.index(), MacAddress(networkInterface.hardwareAddress()), entry.ip(), MacAddress::broadcast(), targetAddress);
}
}
@ -183,8 +183,11 @@ bool ArpSocket::sendRequest(const QHostAddress &targetAddress)
if (entry.ip().protocol() != QAbstractSocket::IPv4Protocol)
continue;
if (targetAddress.isInSubnet(entry.ip(), entry.netmask().toIPv4Address())) {
return sendRequestInternally(networkInterface.index(), networkInterface.hardwareAddress(), entry.ip(), "ff:ff:ff:ff:ff:ff", targetAddress);
qCDebug(dcArpSocket()) << "Check subnet for" << networkInterface.name() << entry.ip() << entry.netmask();
if (targetAddress.isInSubnet(entry.ip(), entry.prefixLength())) {
return sendRequestInternally(networkInterface.index(), MacAddress(networkInterface.hardwareAddress()), entry.ip(), MacAddress::broadcast(), targetAddress);
} else {
qCDebug(dcArpSocket()) << targetAddress << "is not part of subnet" << entry.ip() << "netmask" << entry.netmask() << "netmask int" << entry.netmask().toIPv4Address();
}
}
}
@ -228,66 +231,17 @@ bool ArpSocket::openSocket()
return;
// Make sure to read all data from the socket...
while (true) {
char receiveBuffer[ETHER_ARP_PACKET_LEN];
int bytesReceived = 0;
while (bytesReceived >= 0) {
unsigned char receiveBuffer[ETHER_ARP_PACKET_LEN];
memset(&receiveBuffer, 0, sizeof(receiveBuffer));
// Read the buffer
int bytesReceived = recv(m_socketDescriptor, receiveBuffer, ETHER_ARP_PACKET_LEN, 0);
if (bytesReceived < 0) {
// Finished reading
return;
}
bytesReceived = recv(m_socketDescriptor, receiveBuffer, ETHER_ARP_PACKET_LEN, 0);
if (bytesReceived < 0)
continue;
// Parse data using structs header + arp
struct ether_header *etherHeader = (struct ether_header *)(receiveBuffer);
struct ether_arp *arpPacket = (struct ether_arp *)(receiveBuffer + ETHER_HEADER_LEN);
QString senderMacAddress = getMacAddressString(arpPacket->arp_sha);
QHostAddress senderHostAddress = getHostAddressString(arpPacket->arp_spa);
QString targetMacAddress = getMacAddressString(arpPacket->arp_tha);
QHostAddress targetHostAddress = getHostAddressString(arpPacket->arp_tpa);
uint16_t etherType = htons(etherHeader->ether_type);
if (etherType != ETHERTYPE_ARP) {
qCWarning(dcArpSocketTraffic()) << "Received ARP socket data header with invalid type" << etherType;
return;
}
// Filter for ARP replies
uint16_t arpOperationCode = htons(arpPacket->arp_op);
switch (arpOperationCode) {
case ARPOP_REQUEST:
//qCDebug(dcArpSocket()) << "ARP request from " << senderMacAddress << senderHostAddress.toString() << "-->" << targetMacAddress << targetHostAddress.toString();
break;
case ARPOP_REPLY: {
QNetworkInterface networkInterface = NetworkUtils::getInterfaceForMacAddress(targetMacAddress);
if (!networkInterface.isValid()) {
qCWarning(dcArpSocket()) << "Could not find interface from ARP response" << targetHostAddress.toString() << targetMacAddress;
return;
}
qCDebug(dcArpSocketTraffic()) << "ARP response from" << senderMacAddress << senderHostAddress.toString() << "on" << networkInterface.name();
emit arpResponse(networkInterface, senderHostAddress, senderMacAddress.toLower());
break;
}
case ARPOP_RREQUEST:
qCDebug(dcArpSocketTraffic()) << "RARP request from" << senderMacAddress << senderHostAddress.toString() << "-->" << targetMacAddress << targetHostAddress.toString();
break;
case ARPOP_RREPLY:
qCDebug(dcArpSocketTraffic()) << "PARP response from" << senderMacAddress << senderHostAddress.toString() << "-->" << targetMacAddress << targetHostAddress.toString();
break;
case ARPOP_InREQUEST:
qCDebug(dcArpSocketTraffic()) << "InARP request from" << senderMacAddress << senderHostAddress.toString() << "-->" << targetMacAddress << targetHostAddress.toString();
break;
case ARPOP_InREPLY:
qCDebug(dcArpSocketTraffic()) << "InARP response from" << senderMacAddress << senderHostAddress.toString() << "-->" << targetMacAddress << targetHostAddress.toString();
break;
case ARPOP_NAK:
qCDebug(dcArpSocketTraffic()) << "(ATM)ARP NAK from" << senderMacAddress << senderHostAddress.toString() << "-->" << targetMacAddress << targetHostAddress.toString();
break;
default:
qCWarning(dcArpSocketTraffic()) << "Received unhandled ARP operation code" << arpOperationCode << "from" << senderMacAddress << senderHostAddress.toString();
break;
}
processDataBuffer(receiveBuffer, bytesReceived);
}
});
@ -318,7 +272,7 @@ void ArpSocket::closeSocket()
qCDebug(dcArpSocket()) << "ARP disabled successfully";
}
bool ArpSocket::sendRequestInternally(int networkInterfaceIndex, const QString &senderMacAddress, const QHostAddress &senderHostAddress, const QString &targetMacAddress, const QHostAddress &targetHostAddress)
bool ArpSocket::sendRequestInternally(int networkInterfaceIndex, const MacAddress &senderMacAddress, const QHostAddress &senderHostAddress, const MacAddress &targetMacAddress, const QHostAddress &targetHostAddress)
{
// Set up data structures
unsigned char sendingBuffer[ETHER_ARP_PACKET_LEN];
@ -364,14 +318,80 @@ bool ArpSocket::sendRequestInternally(int networkInterfaceIndex, const QString &
return true;
}
QString ArpSocket::getMacAddressString(uint8_t *senderHardwareAddress)
void ArpSocket::processDataBuffer(unsigned char *receiveBuffer, int size)
{
QStringList hexValues;
for (int i = 0; i < ETHER_ADDR_LEN; i++) {
hexValues.append(QString("%1").arg(senderHardwareAddress[i], 2, 16, QLatin1Char('0')));
// Parse data using structs header + arp
QByteArray receivedBufferBytes;
for (int i = 0; i < size; i++) {
receivedBufferBytes.append(receiveBuffer[i]);
}
return hexValues.join(":");
struct ether_header *etherHeader = (struct ether_header *)(receiveBuffer);
struct ether_arp *arpPacket = (struct ether_arp *)(receiveBuffer + ETHER_HEADER_LEN);
MacAddress senderMacAddress = MacAddress(arpPacket->arp_sha);
QHostAddress senderHostAddress = getHostAddressString(arpPacket->arp_spa);
MacAddress targetMacAddress = MacAddress(arpPacket->arp_tha);
QHostAddress targetHostAddress = getHostAddressString(arpPacket->arp_tpa);
uint16_t etherType = htons(etherHeader->ether_type);
if (etherType != ETHERTYPE_ARP) {
qCWarning(dcArpSocketTraffic()) << "Received ARP socket data header with invalid type" << etherType;
return;
}
// Filter for ARP replies
uint16_t arpOperationCode = htons(arpPacket->arp_op);
switch (arpOperationCode) {
case ARPOP_REQUEST: {
// The sender of the arp request provides ip and mac.
// Lets find the corresponding interface and use it for the discovery and monitor
if (senderHostAddress.isNull())
return;
QNetworkInterface networkInterface = NetworkUtils::getInterfaceForHostaddress(senderHostAddress);
if (!networkInterface.isValid()) {
qCWarning(dcArpSocket()) << "Could not find local interface from ARP request" << senderHostAddress.toString() << senderMacAddress.toString();
return;
}
// Note: we are not interested in our own requests
if (senderMacAddress != MacAddress(networkInterface.hardwareAddress())) {
qCDebug(dcArpSocket()) << "ARP request" << receivedBufferBytes.toHex() << "from" << senderMacAddress.toString() << senderHostAddress.toString() << "-->" << targetMacAddress.toString() << targetHostAddress.toString() << "on" << networkInterface.name();
emit arpRequestReceived(networkInterface, senderHostAddress, senderMacAddress);
}
break;
}
case ARPOP_REPLY: {
QNetworkInterface networkInterface = NetworkUtils::getInterfaceForMacAddress(targetMacAddress);
if (!networkInterface.isValid()) {
qCWarning(dcArpSocket()) << "Could not find local interface from ARP response" << targetHostAddress.toString() << targetMacAddress.toString();
return;
}
qCDebug(dcArpSocket()) << "ARP reply" << receivedBufferBytes.toHex() << "from" << senderMacAddress.toString() << senderHostAddress.toString() << "-->" << targetMacAddress.toString() << targetHostAddress.toString() << "on" << networkInterface.name();
emit arpResponse(networkInterface, senderHostAddress, senderMacAddress);
break;
}
case ARPOP_RREQUEST:
qCDebug(dcArpSocketTraffic()) << "RARP request from" << senderMacAddress.toString() << senderHostAddress.toString() << "-->" << targetMacAddress.toString() << targetHostAddress.toString();
break;
case ARPOP_RREPLY:
qCDebug(dcArpSocketTraffic()) << "PARP response from" << senderMacAddress.toString() << senderHostAddress.toString() << "-->" << targetMacAddress.toString() << targetHostAddress.toString();
break;
case ARPOP_InREQUEST:
qCDebug(dcArpSocketTraffic()) << "InARP request from" << senderMacAddress.toString() << senderHostAddress.toString() << "-->" << targetMacAddress.toString() << targetHostAddress.toString();
break;
case ARPOP_InREPLY:
qCDebug(dcArpSocketTraffic()) << "InARP response from" << senderMacAddress.toString() << senderHostAddress.toString() << "-->" << targetMacAddress.toString() << targetHostAddress.toString();
break;
case ARPOP_NAK:
qCDebug(dcArpSocketTraffic()) << "(ATM)ARP NAK from" << senderMacAddress.toString() << senderHostAddress.toString() << "-->" << targetMacAddress.toString() << targetHostAddress.toString();
break;
default:
qCWarning(dcArpSocketTraffic()) << "Received unhandled ARP operation code" << arpOperationCode << "from" << senderMacAddress.toString() << senderHostAddress.toString();
break;
}
}
QHostAddress ArpSocket::getHostAddressString(uint8_t *senderIpAddress)
@ -403,52 +423,54 @@ bool ArpSocket::loadArpCache(const QNetworkInterface &interface)
while (!stream.atEnd()) {
QString line = stream.readLine();
lineCount += 1;
// Skip the first line since it's just the header
if (lineCount == 0)
continue;
//qCDebug(dcArpSocket()) << "Checking line" << line;
qCDebug(dcArpSocket()) << "Checking line" << line;
QStringList columns = line.split(QLatin1Char(' '));
columns.removeAll("");
// Make sure we have enought token
if (columns.count() < 6) {
qCWarning(dcArpSocket()) << "Line has invalid column count" << line;
qCWarning(dcArpSocket()) << "ARP cache line has invalid column count" << line;
continue;
}
QHostAddress address(columns.at(0).trimmed());
if (address.isNull()) {
qCWarning(dcArpSocket()) << "Line has invalid address";
qCWarning(dcArpSocket()) << "ARP cache line has invalid IP address";
continue;
}
QString macAddress = columns.at(3).trimmed();
if (macAddress.count() != 17) {
qCWarning(dcArpSocket()) << "Line has invalid mac address" << columns << macAddress;
QString macAddressString = columns.at(3).trimmed();
MacAddress macAddress(macAddressString);
if (macAddress.isNull()) {
qCDebug(dcArpSocket()) << "ARP cache line has invalid MAC address" << macAddressString;
continue;
}
QNetworkInterface addressInterface = QNetworkInterface::interfaceFromName(columns.at(5));
if (!addressInterface.isValid())
if (!addressInterface.isValid()) {
qCDebug(dcArpSocket()) << "ARP cache line has invalid network interface identifier" << columns << columns.at(5);
continue;
}
// Check if we filter for specific interfaces
if (interface.isValid() && addressInterface.name() != interface.name())
continue;
qCDebug(dcArpSocket()) << "Loaded from cache" << address.toString() << macAddress << addressInterface.name();
qCDebug(dcArpSocket()) << "Loaded from cache" << address.toString() << macAddress.toString() << addressInterface.name();
emit arpResponse(addressInterface, address, macAddress);
}
return true;
}
void ArpSocket::fillMacAddress(uint8_t *targetArray, const QString &macAddress)
void ArpSocket::fillMacAddress(uint8_t *targetArray, const MacAddress &macAddress)
{
QStringList macValues = macAddress.split(":");
QStringList macValues = macAddress.toString().split(":");
for (int i = 0; i < ETHER_ADDR_LEN; i++) {
targetArray[i] = macValues.at(i).toUInt(nullptr, 16);
}

View File

@ -39,6 +39,7 @@
#include <QNetworkInterface>
#include "libnymea.h"
#include "macaddress.h"
class LIBNYMEA_EXPORT ArpSocket : public QObject
{
@ -64,21 +65,23 @@ public:
void closeSocket();
signals:
void arpResponse(const QNetworkInterface &networkInterface, const QHostAddress &address, const QString &macAddress);
void arpResponse(const QNetworkInterface &networkInterface, const QHostAddress &address, const MacAddress &macAddress);
void arpRequestReceived(const QNetworkInterface &networkInterface, const QHostAddress &address, const MacAddress &macAddress);
private:
QSocketNotifier *m_socketNotifier = nullptr;
int m_socketDescriptor = -1;
bool m_isOpen = false;
bool sendRequestInternally(int networkInterfaceIndex, const QString &senderMacAddress, const QHostAddress &senderHostAddress, const QString &targetMacAddress, const QHostAddress &targetHostAddress);
bool sendRequestInternally(int networkInterfaceIndex, const MacAddress &senderMacAddress, const QHostAddress &senderHostAddress, const MacAddress &targetMacAddress, const QHostAddress &targetHostAddress);
void processDataBuffer(unsigned char *receiveBuffer, int size);
QString getMacAddressString(uint8_t *senderHardwareAddress);
QHostAddress getHostAddressString(uint8_t *senderIpAddress);
bool loadArpCache(const QNetworkInterface &interface = QNetworkInterface());
void fillMacAddress(uint8_t *targetArray, const QString &macAddress);
void fillMacAddress(uint8_t *targetArray, const MacAddress &macAddress);
void fillHostAddress(uint8_t *targetArray, const QHostAddress &hostAddress);
};

View File

@ -0,0 +1,162 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2022, 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 <https://www.gnu.org/licenses/>.
*
* 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 "macaddress.h"
#include <net/ethernet.h>
#include <QDebug>
#include <QRegularExpression>
MacAddress::MacAddress()
{
clear();
}
MacAddress::MacAddress(const QString &macAddress)
{
QString addressString;
// Filter out any non hex characters from the string (like any separators)
QRegularExpression hexMatcher("^[0-9A-F]", QRegularExpression::CaseInsensitiveOption);
for (int i = 0; i < macAddress.count(); i++) {
// Remove all possible separators
QRegularExpressionMatch match = hexMatcher.match(macAddress.at(i));
if (match.hasMatch()) {
addressString.append(macAddress.at(i));
}
}
// The remaining hex value has to be 12 characters (6 hex values)
if (addressString.size() == 12) {
m_rawData = QByteArray::fromHex(addressString.toUtf8());
} else {
// Make invalid and null
m_rawData.clear();
}
}
MacAddress::MacAddress(const QByteArray &macAddress)
{
m_rawData = macAddress;
}
MacAddress::MacAddress(unsigned char *rawData)
{
clear();
for (int i = 0; i < ETH_ALEN; i++) {
m_rawData[i] = rawData[i];
}
}
MacAddress::MacAddress(const MacAddress &other)
{
m_rawData = other.toByteArray();
}
MacAddress MacAddress::broadcast()
{
return MacAddress(QByteArray::fromHex("ffffffffffff"));
}
MacAddress MacAddress::fromString(const QString &macAddress)
{
return MacAddress(macAddress);
}
QString MacAddress::toString() const
{
QString macString(QStringLiteral("%1:%2:%3:%4:%5:%6"));
QByteArray data = m_rawData;
if (!isValid()) {
data = QByteArray(ETH_ALEN, '\0');
}
for (int i = 0; i < data.size(); i++) {
macString = macString.arg(static_cast<quint8>(data.at(i)), 2, 16, QLatin1Char('0'));
}
return macString.toLower();
}
QByteArray MacAddress::toByteArray() const
{
return m_rawData;
}
bool MacAddress::isNull() const
{
if (!isValid())
return true;
return m_rawData == QByteArray(ETH_ALEN, '\0');
}
bool MacAddress::isValid() const
{
return m_rawData.size() == ETH_ALEN;
}
void MacAddress::clear()
{
m_rawData.fill('\0', ETH_ALEN);
}
MacAddress &MacAddress::operator=(const MacAddress &other)
{
m_rawData = other.toByteArray();
return *this;
}
bool MacAddress::operator<(const MacAddress &other) const
{
return m_rawData < other.toByteArray();
}
bool MacAddress::operator>(const MacAddress &other) const
{
return m_rawData > other.toByteArray();
}
bool MacAddress::operator==(const MacAddress &other) const
{
return m_rawData == other.toByteArray();
}
bool MacAddress::operator!=(const MacAddress &other) const
{
return m_rawData != other.toByteArray();
}
QDebug operator<<(QDebug debug, const MacAddress &macAddress)
{
debug.nospace() << "MacAddress(" << macAddress.toString() << ")";
return debug.space();
}

View File

@ -0,0 +1,86 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2022, 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 <https://www.gnu.org/licenses/>.
*
* 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 MACADDRESS_H
#define MACADDRESS_H
#include <QHash>
#include <QString>
#include <QByteArray>
#include "libnymea.h"
class LIBNYMEA_EXPORT MacAddress
{
public:
explicit MacAddress();
explicit MacAddress(const QString &macAddress);
explicit MacAddress(const QByteArray &macAddress);
explicit MacAddress(unsigned char *rawData);
MacAddress(const MacAddress &other);
static MacAddress broadcast();
static MacAddress fromString(const QString &macAddress);
QString toString() const;
QByteArray toByteArray() const;
bool isNull() const;
bool isValid() const;
void clear();
MacAddress &operator=(const MacAddress &other);
bool operator<(const MacAddress &other) const;
bool operator>(const MacAddress &other) const;
bool operator==(const MacAddress &other) const;
bool operator!=(const MacAddress &other) const;
private:
QByteArray m_rawData = 0;
};
#if QT_VERSION < 0x0600000
using qhash_result_t = uint;
#else
using qhash_result_t = size_t;
#endif
inline qhash_result_t qHash(const MacAddress &macAddress, qhash_result_t seed)
{
return qHash(macAddress.toByteArray(), seed);
}
QDebug operator<<(QDebug debug, const MacAddress &address);
#endif // MACADDRESS_H

View File

@ -55,13 +55,15 @@ public:
virtual bool running() const = 0;
virtual NetworkDeviceMonitor *registerMonitor(const QString &macAddress) = 0;
virtual void unregisterMonitor(const QString &macAddress) = 0;
virtual NetworkDeviceMonitor *registerMonitor(const MacAddress &macAddress) = 0;
virtual void unregisterMonitor(const MacAddress &macAddress) = 0;
virtual void unregisterMonitor(NetworkDeviceMonitor *networkDeviceMonitor) = 0;
virtual PingReply *ping(const QHostAddress &address) = 0;
virtual MacAddressDatabaseReply *lookupMacAddress(const QString &macAddress) = 0;
virtual MacAddressDatabaseReply *lookupMacAddress(const MacAddress &macAddress) = 0;
virtual bool sendArpRequest(const QHostAddress &address) = 0;

View File

@ -29,7 +29,7 @@
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "networkdeviceinfo.h"
#include "macaddress.h"
NetworkDeviceInfo::NetworkDeviceInfo()
{
@ -42,6 +42,12 @@ NetworkDeviceInfo::NetworkDeviceInfo(const QString &macAddress):
m_macAddressSet = true;
}
NetworkDeviceInfo::NetworkDeviceInfo(const QHostAddress &address):
m_address(address)
{
m_addressSet = true;
}
QString NetworkDeviceInfo::macAddress() const
{
return m_macAddress;
@ -99,7 +105,7 @@ void NetworkDeviceInfo::setNetworkInterface(const QNetworkInterface &networkInte
bool NetworkDeviceInfo::isValid() const
{
return (!m_address.isNull() || !m_macAddress.isEmpty()) && m_networkInterface.isValid();
return (!m_address.isNull() || !MacAddress(m_macAddress).isNull()) && m_networkInterface.isValid();
}
bool NetworkDeviceInfo::isComplete() const
@ -107,9 +113,19 @@ bool NetworkDeviceInfo::isComplete() const
return m_macAddressSet && m_macAddressManufacturerSet && m_addressSet && m_hostNameSet && m_networkInterfaceSet;
}
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");
return list.join(", ");
}
bool NetworkDeviceInfo::operator==(const NetworkDeviceInfo &other) const
{
return m_macAddress == other.macAddress() &&
return MacAddress(m_macAddress) == MacAddress(other.macAddress()) &&
m_address == other.address() &&
m_hostName == other.hostName() &&
m_macAddressManufacturer == other.macAddressManufacturer() &&
@ -117,10 +133,18 @@ bool NetworkDeviceInfo::operator==(const NetworkDeviceInfo &other) const
isComplete() == other.isComplete();
}
bool NetworkDeviceInfo::operator!=(const NetworkDeviceInfo &other) const
{
return !operator==(other);
}
QDebug operator<<(QDebug dbg, const NetworkDeviceInfo &networkDeviceInfo)
{
dbg.nospace() << "NetworkDeviceInfo(" << networkDeviceInfo.address().toString();
dbg.nospace() << ", " << networkDeviceInfo.macAddress();
if (!networkDeviceInfo.macAddress().isEmpty())
dbg.nospace() << ", " << MacAddress(networkDeviceInfo.macAddress()).toString();
if (!networkDeviceInfo.macAddressManufacturer().isEmpty())
dbg.nospace() << " (" << networkDeviceInfo.macAddressManufacturer() << ") ";

View File

@ -33,6 +33,7 @@
#include <QDebug>
#include <QObject>
#include <QDateTime>
#include <QHostAddress>
#include <QNetworkInterface>
@ -43,6 +44,7 @@ class LIBNYMEA_EXPORT NetworkDeviceInfo
public:
explicit NetworkDeviceInfo();
explicit NetworkDeviceInfo(const QString &macAddress);
explicit NetworkDeviceInfo(const QHostAddress &address);
QString macAddress() const;
void setMacAddress(const QString &macAddress);
@ -62,7 +64,10 @@ public:
bool isValid() const;
bool isComplete() const;
QString incompleteProperties() const;
bool operator==(const NetworkDeviceInfo &other) const;
bool operator!=(const NetworkDeviceInfo &other) const;
private:
QHostAddress m_address;

View File

@ -57,9 +57,14 @@ int NetworkDeviceInfos::indexFromHostAddress(const QHostAddress &address)
}
int NetworkDeviceInfos::indexFromMacAddress(const QString &macAddress)
{
return indexFromMacAddress(MacAddress(macAddress));
}
int NetworkDeviceInfos::indexFromMacAddress(const MacAddress &macAddress)
{
for (int i = 0; i < size(); i++) {
if (at(i).macAddress().toLower() == macAddress.toLower()) {
if (MacAddress(at(i).macAddress()) == macAddress) {
return i;
}
}
@ -77,7 +82,12 @@ bool NetworkDeviceInfos::hasMacAddress(const QString &macAddress)
return indexFromMacAddress(macAddress) >= 0;
}
NetworkDeviceInfo NetworkDeviceInfos::get(const QHostAddress &address)
bool NetworkDeviceInfos::hasMacAddress(const MacAddress &macAddress)
{
return indexFromMacAddress(macAddress) >= 0;
}
NetworkDeviceInfo NetworkDeviceInfos::get(const QHostAddress &address) const
{
foreach (const NetworkDeviceInfo &networkDeviceInfo, *this) {
if (networkDeviceInfo.address() == address) {
@ -88,7 +98,7 @@ NetworkDeviceInfo NetworkDeviceInfos::get(const QHostAddress &address)
return NetworkDeviceInfo();
}
NetworkDeviceInfo NetworkDeviceInfos::get(const QString &macAddress)
NetworkDeviceInfo NetworkDeviceInfos::get(const QString &macAddress) const
{
foreach (const NetworkDeviceInfo &networkDeviceInfo, *this) {
if (networkDeviceInfo.macAddress() == macAddress) {
@ -99,10 +109,20 @@ NetworkDeviceInfo NetworkDeviceInfos::get(const QString &macAddress)
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 (at(i).macAddress().toLower() == macAddress.toLower()) {
if (MacAddress(at(i).macAddress()) == macAddress) {
remove(i);
}
}

View File

@ -34,6 +34,7 @@
#include <QObject>
#include "libnymea.h"
#include "macaddress.h"
#include "networkdeviceinfo.h"
class LIBNYMEA_EXPORT NetworkDeviceInfos : public QVector<NetworkDeviceInfo>
@ -45,14 +46,18 @@ public:
int indexFromHostAddress(const QHostAddress &address);
int indexFromMacAddress(const QString &macAddress);
int indexFromMacAddress(const MacAddress &macAddress);
bool hasHostAddress(const QHostAddress &address);
bool hasMacAddress(const QString &macAddress);
bool hasMacAddress(const MacAddress &macAddress);
NetworkDeviceInfo get(const QHostAddress &address);
NetworkDeviceInfo get(const QString &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 sortNetworkDevices();

View File

@ -39,14 +39,14 @@ NetworkDeviceMonitor::NetworkDeviceMonitor(QObject *parent) :
QDebug operator<<(QDebug dbg, NetworkDeviceMonitor *networkDeviceMonitor)
{
dbg.nospace() << "NetworkDeviceMonitor(" << networkDeviceMonitor->networkDeviceInfo().macAddress();
dbg.nospace() << "NetworkDeviceMonitor(" << networkDeviceMonitor->macAddress().toString();
if (!networkDeviceMonitor->networkDeviceInfo().macAddressManufacturer().isEmpty())
dbg.nospace() << " - " << networkDeviceMonitor->networkDeviceInfo().macAddressManufacturer();
dbg.nospace() << ", " << networkDeviceMonitor->networkDeviceInfo().address().toString();
dbg.nospace() << ") ";
return dbg.maybeSpace();
dbg.nospace() << ")";
return dbg.space();
}

View File

@ -35,6 +35,7 @@
#include <QDateTime>
#include "libnymea.h"
#include "macaddress.h"
#include "networkdeviceinfo.h"
class LIBNYMEA_EXPORT NetworkDeviceMonitor : public QObject
@ -44,6 +45,8 @@ class LIBNYMEA_EXPORT NetworkDeviceMonitor : public QObject
public:
explicit NetworkDeviceMonitor(QObject *parent = nullptr);
virtual MacAddress macAddress() const = 0;
virtual NetworkDeviceInfo networkDeviceInfo() const = 0;
virtual bool reachable() const = 0;
@ -58,5 +61,4 @@ signals:
QDebug operator<<(QDebug debug, NetworkDeviceMonitor *networkDeviceMonitor);
#endif // NETWORKDEVICEMONITOR_H

View File

@ -13,7 +13,7 @@ QNetworkInterface NetworkUtils::getInterfaceForHostaddress(const QHostAddress &a
if (entry.ip().protocol() != QAbstractSocket::IPv4Protocol)
continue;
if (address.isInSubnet(entry.ip(), entry.netmask().toIPv4Address())) {
if (address.isInSubnet(entry.ip(), entry.prefixLength())) {
return networkInterface;
}
}
@ -32,3 +32,14 @@ QNetworkInterface NetworkUtils::getInterfaceForMacAddress(const QString &macAddr
return QNetworkInterface();
}
QNetworkInterface NetworkUtils::getInterfaceForMacAddress(const MacAddress &macAddress)
{
foreach (const QNetworkInterface &networkInterface, QNetworkInterface::allInterfaces()) {
if (MacAddress(networkInterface.hardwareAddress()) == macAddress) {
return networkInterface;
}
}
return QNetworkInterface();
}

View File

@ -34,6 +34,8 @@
#include <QHostAddress>
#include <QNetworkInterface>
#include "macaddress.h"
class NetworkUtils
{
public:
@ -41,6 +43,7 @@ public:
static QNetworkInterface getInterfaceForHostaddress(const QHostAddress &address);
static QNetworkInterface getInterfaceForMacAddress(const QString &macAddress);
static QNetworkInterface getInterfaceForMacAddress(const MacAddress &macAddress);
};
#endif // NETWORKUTILS_H

View File

@ -8,6 +8,7 @@ SUBDIRS = \
logging \
loggingdirect \
loggingloading \
macaddressunittests \
mqttbroker \
plugins \
pythonplugins \
@ -21,4 +22,3 @@ SUBDIRS = \
webserver \
websocketserver \
#coap \ # temporary removed until fixed

View File

@ -1040,7 +1040,6 @@ void TestIntegrations::getEventTypes()
QVariantList eventTypes = response.toMap().value("params").toMap().value("eventTypes").toList();
QCOMPARE(eventTypes.count(), resultCount);
}
void TestIntegrations::getStateTypes_data()

View File

@ -0,0 +1,134 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright 2013 - 2022, 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 General Public License Usage
* Alternatively, this project may be redistributed and/or modified under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, GNU 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 General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this project. If not, see <https://www.gnu.org/licenses/>.
*
* 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 "nymeatestbase.h"
#include <network/macaddress.h>
using namespace nymeaserver;
class MacAddressUnitTests: public NymeaTestBase
{
Q_OBJECT
protected slots:
void initTestCase();
private:
QString m_zeroMacString = QString("00:00:00:00:00:00");
QByteArray m_zeroMacByteArray = QByteArray(6, '\0');
QString m_alphaNumericMacString = QString("01:23:45:ab:cd:ef");
QByteArray m_alphaNumericByteArray = QByteArray::fromHex("012345abcdef");
private slots:
void defaultConstructor();
void macAddressValidation_data();
void macAddressValidation();
};
void MacAddressUnitTests::initTestCase()
{
NymeaTestBase::initTestCase("*.debug=false\nTests.debug=true\n");
}
void MacAddressUnitTests::defaultConstructor()
{
MacAddress mac;
QVERIFY(mac.isNull());
QVERIFY(mac.isValid());
QCOMPARE(mac.toByteArray().count(), 6);
QCOMPARE(mac.toByteArray(), QByteArray(6, '\0'));
QVERIFY(mac.toString() == m_zeroMacString);
QVERIFY(!MacAddress(QString("acme")).isValid());
QVERIFY(MacAddress(QString("acme")).isNull());
QVERIFY(!MacAddress(QByteArray()).isValid());
QVERIFY(MacAddress(QByteArray()).isNull());
QVERIFY(MacAddress(QByteArray()).toByteArray().isEmpty());
QCOMPARE(MacAddress(m_zeroMacByteArray).toString(), m_zeroMacString);
QCOMPARE(MacAddress(m_zeroMacString).toByteArray(), m_zeroMacByteArray);
QCOMPARE(MacAddress(m_alphaNumericByteArray).toString(), m_alphaNumericMacString);
QCOMPARE(MacAddress(m_alphaNumericMacString).toByteArray(), m_alphaNumericByteArray);
QByteArray validRawData = QByteArray::fromHex("aabbccddeeff");
QCOMPARE(MacAddress(QString("aa:bb:cc:dd:ee:ff")).toByteArray(), validRawData);
QCOMPARE(MacAddress(QString("aa:bb:cc:dd:ee:ff")), MacAddress(validRawData));
QCOMPARE(MacAddress(QString("aabbccddeeff")).toByteArray(), validRawData);
QCOMPARE(MacAddress(QString("aabbccddeeff")), MacAddress(validRawData));
}
void MacAddressUnitTests::macAddressValidation_data()
{
QTest::addColumn<QString>("macString");
QTest::addColumn<bool>("isValid");
QTest::addColumn<bool>("isNull");
QTest::addColumn<QString>("toString");
QString mixedString = "11:22:33:dd:ee:ff";
QString aplhaString = "aa:bb:cc:dd:ee:ff";
QTest::newRow("Valid zero") << "00:00:00:00:00:00" << true << true << m_zeroMacString;
QTest::newRow("Valid zero no colon") << "000000000000" << true << true << m_zeroMacString;
QTest::newRow("Valid non zero lower") << "11:22:33:dd:ee:ff" << true << false << mixedString;
QTest::newRow("Valid non zero upper") << "11:22:33:DD:EE:FF" << true << false << mixedString;
QTest::newRow("Valid non zero mixed case") << "11:22:33:Dd:Ee:Ff" << true << false << mixedString;
QTest::newRow("Valid non zero space separator") << "aa bb cc dd ee ff" << true << false << aplhaString;
QTest::newRow("Valid non zero dash separator") << "aa-bb-cc-dd-ee-ff" << true << false << aplhaString;
QTest::newRow("Valid non zero dot separator") << "aa.bb.cc.dd.ee.ff" << true << false << aplhaString;
QTest::newRow("Valid non zero crazy separator") << "aa#bb?cclddxeeäff" << true << false << aplhaString;
QTest::newRow("Valid non zero mixed separators") << "aa-bb cc.dd ee-ff" << true << false << aplhaString;
QTest::newRow("Valid non zero without colon") << "aabbccddeeff" << true << false << aplhaString;
QTest::newRow("Invalid characters") << "xx:yy:zz:dd:ee:ff" << false << true << m_zeroMacString;
QTest::newRow("Too short") << "xx:yy:zz:dd:ee" << false << true << m_zeroMacString;
QTest::newRow("Too long") << "xx:yy:zz:dd:ee:ee:ee" << false << true << m_zeroMacString;
}
void MacAddressUnitTests::macAddressValidation()
{
QFETCH(QString, macString);
QFETCH(bool, isValid);
QFETCH(bool, isNull);
QFETCH(QString, toString);
MacAddress mac(macString);
qCDebug(dcTests()) << "Verify" << macString << "resulting in" << mac;
QCOMPARE(mac.isValid(), isValid);
QCOMPARE(mac.isNull(), isNull);
QCOMPARE(mac.toString(), toString);
}
#include "macaddressunittests.moc"
QTEST_MAIN(MacAddressUnitTests)

View File

@ -0,0 +1,7 @@
TARGET = macaddressunittests
include(../../../nymea.pri)
include(../autotests.pri)
SOURCES += macaddressunittests.cpp