mirror of
https://github.com/hyperion-project/hyperion.ng.git
synced 2025-03-01 10:33:28 +00:00
* Refactor to fix #1671 * Add GUI/NonGUI mode to info page * Do not show lock config, if in non-UI mode * Updae Changelog * Correct includes * Ensure key member initialization - RGB Channels * Ensure key member initialization - WebServer * Update RGBChannels * Fix initialization order * Fix key when inserting new logger in LoggerMap, Prepare logBuffer-JSON snapshot view in LoggerManager, Increase buffered loglines to 500 * Fix Memory leak in GrabberWrapper * Fix Memory leak in BlackBorderProcessor * Fix Memory leak in BlackBorderProcessor * use ninja generator under macos * Fix BGEffectHandler destruction * Fix Mdns code * Clear list after applying qDeleteAll * Fix deletion of CecHandler * Fix memory leak caused by wrong buffer allocation * Remove extra pixel consistently * Change mDNS to Qt SmartPointers * Correct removal * Fix usage of _width/_height (they are the output resolution, not the screen resolution) That avoids unnecessary resizing of the output image with every transferFrame call * Move main non Thread Objects to Smart Pointers * Revert "Move main non Thread Objects to Smart Pointers" This reverts commit 26102ca963982e2fbc4ffb8d4db6139f0128a3cc. * Add missing deletes * Revert MdnsBrowser chnage * Revert MdnsBrowser change * Fix memory leaks related standalone grabber * Address CodeQL finding * delete pointer OsxFrameGrabber --------- Co-authored-by: Paulchen-Panther <16664240+Paulchen-Panther@users.noreply.github.com>
473 lines
15 KiB
C++
473 lines
15 KiB
C++
#include <mdns/MdnsBrowser.h>
|
|
#include <qmdnsengine/message.h>
|
|
#include <qmdnsengine/service.h>
|
|
|
|
// Qt includes
|
|
#include <QThread>
|
|
#include <QJsonObject>
|
|
#include <QJsonArray>
|
|
#include <QJsonDocument>
|
|
#include <QHostAddress>
|
|
#include <QRegularExpression>
|
|
|
|
// Utility includes
|
|
#include <HyperionConfig.h>
|
|
#include <utils/Logger.h>
|
|
#include <utils/WaitTime.h>
|
|
#include <utils/NetUtils.h>
|
|
|
|
namespace {
|
|
const bool verboseBrowser = false;
|
|
const int SERVICE_LOOKUP_RETRIES = 5;
|
|
} // End of constants
|
|
|
|
MdnsBrowser::MdnsBrowser(QObject* parent)
|
|
: QObject(parent)
|
|
, _log(Logger::getInstance("MDNS"))
|
|
{
|
|
qRegisterMetaType<QHostAddress>("QHostAddress");
|
|
}
|
|
|
|
MdnsBrowser::~MdnsBrowser()
|
|
{
|
|
_browsedServiceTypes.clear();
|
|
}
|
|
|
|
void MdnsBrowser::browseForServiceType(const QByteArray& serviceType)
|
|
{
|
|
if (!_browsedServiceTypes.contains(serviceType))
|
|
{
|
|
DebugIf(verboseBrowser, _log, "Start new mDNS browser for serviceType [%s], Thread: %s", serviceType.constData(), QSTRING_CSTR(QThread::currentThread()->objectName()));
|
|
QSharedPointer<QMdnsEngine::Browser> newBrowser = QSharedPointer<QMdnsEngine::Browser>::create(&_server, serviceType, &_cache);
|
|
|
|
QObject::connect(newBrowser.get(), &QMdnsEngine::Browser::serviceAdded, this, &MdnsBrowser::onServiceAdded);
|
|
QObject::connect(newBrowser.get(), &QMdnsEngine::Browser::serviceUpdated, this, &MdnsBrowser::onServiceUpdated);
|
|
QObject::connect(newBrowser.get(), &QMdnsEngine::Browser::serviceRemoved, this, &MdnsBrowser::onServiceRemoved);
|
|
|
|
_browsedServiceTypes.insert(serviceType, newBrowser);
|
|
}
|
|
else
|
|
{
|
|
DebugIf(verboseBrowser, _log, "Use existing mDNS browser for serviceType [%s], Thread: %s", serviceType.constData(), QSTRING_CSTR(QThread::currentThread()->objectName()));
|
|
}
|
|
}
|
|
|
|
void MdnsBrowser::onServiceAdded(const QMdnsEngine::Service& service)
|
|
{
|
|
DebugIf(verboseBrowser, _log, "Discovered service [%s] at host: %s, port: %u, Thread: %s", service.name().constData(), service.hostname().constData(), service.port(), QSTRING_CSTR(QThread::currentThread()->objectName()));
|
|
emit serviceFound(service);
|
|
}
|
|
|
|
void MdnsBrowser::onServiceUpdated(const QMdnsEngine::Service& service)
|
|
{
|
|
DebugIf(verboseBrowser, _log, "[%s], Name: [%s], Port: [%u], Thread: %s", service.type().constData(), service.name().constData(), service.port(), QSTRING_CSTR(QThread::currentThread()->objectName()));
|
|
}
|
|
|
|
void MdnsBrowser::onServiceRemoved(const QMdnsEngine::Service& service)
|
|
{
|
|
DebugIf(verboseBrowser, _log, "[%s], Name: [%s], Port: [%u], Thread: %s", service.type().constData(), service.name().constData(), service.port(), QSTRING_CSTR(QThread::currentThread()->objectName()));
|
|
emit serviceRemoved(service);
|
|
}
|
|
|
|
QHostAddress MdnsBrowser::getHostFirstAddress(const QByteArray& hostname)
|
|
{
|
|
DebugIf(verboseBrowser, _log, "for hostname [%s], Thread: %s", hostname.constData(), QSTRING_CSTR(QThread::currentThread()->objectName()));
|
|
QByteArray toBeResolvedHostName {hostname};
|
|
|
|
QHostAddress hostAddress;
|
|
|
|
if (toBeResolvedHostName.endsWith(".local"))
|
|
{
|
|
toBeResolvedHostName.append('.');
|
|
}
|
|
if (toBeResolvedHostName.endsWith(".local."))
|
|
{
|
|
QList<QMdnsEngine::Record> aRecords;
|
|
if (_cache.lookupRecords(toBeResolvedHostName, QMdnsEngine::A, aRecords))
|
|
{
|
|
foreach(QMdnsEngine::Record record, aRecords)
|
|
{
|
|
// Do not publish link local addresses
|
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
|
|
if (!record.address().isLinkLocal())
|
|
#else
|
|
if (!record.address().toString().startsWith("fe80"))
|
|
#endif
|
|
{
|
|
hostAddress = record.address();
|
|
DebugIf(verboseBrowser, _log, "Hostname [%s] translates to IPv4-address [%s]", toBeResolvedHostName.constData(), QSTRING_CSTR(hostAddress.toString()));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QList<QMdnsEngine::Record> aaaaRecords;
|
|
if (_cache.lookupRecords(toBeResolvedHostName, QMdnsEngine::AAAA, aaaaRecords))
|
|
{
|
|
foreach(QMdnsEngine::Record record, aaaaRecords)
|
|
{
|
|
// Do not publish link local addresses
|
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
|
|
if (!record.address().isLinkLocal())
|
|
#else
|
|
if (!record.address().toString().startsWith("fe80"))
|
|
#endif
|
|
{
|
|
hostAddress = record.address();
|
|
DebugIf(verboseBrowser, _log, "Hostname [%s] translates to IPv6-address [%s]", toBeResolvedHostName.constData(), QSTRING_CSTR(hostAddress.toString()));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DebugIf(verboseBrowser, _log, "IP-address for hostname [%s] not yet in cache, start resolver.", toBeResolvedHostName.constData());
|
|
qRegisterMetaType<QMdnsEngine::Message>("Message");
|
|
_resolver.reset(new QMdnsEngine::Resolver(&_server, toBeResolvedHostName, &_cache));
|
|
connect(_resolver.get(), &QMdnsEngine::Resolver::resolved, this, &MdnsBrowser::onHostNameResolved);
|
|
}
|
|
}
|
|
}
|
|
return hostAddress;
|
|
}
|
|
|
|
void MdnsBrowser::onHostNameResolved(const QHostAddress& address)
|
|
{
|
|
DebugIf(verboseBrowser, _log, "for address [%s], Thread: %s", QSTRING_CSTR(address.toString()), QSTRING_CSTR(QThread::currentThread()->objectName()));
|
|
|
|
// Do not publish link local addresses
|
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
|
|
if (!address.isLinkLocal())
|
|
#else
|
|
if (!address.toString().startsWith("fe80"))
|
|
#endif
|
|
{
|
|
emit addressResolved(address);
|
|
}
|
|
}
|
|
|
|
bool MdnsBrowser::resolveAddress(Logger* log, const QString& hostname, QHostAddress& hostAddress, std::chrono::milliseconds timeout)
|
|
{
|
|
DebugIf(verboseBrowser, log, "Get address for hostname [%s], Thread: %s", QSTRING_CSTR(hostname), QSTRING_CSTR(QThread::currentThread()->objectName()));
|
|
|
|
bool isHostAddressOK{ false };
|
|
if (hostname.endsWith(".local") || hostname.endsWith(".local."))
|
|
{
|
|
hostAddress = getHostFirstAddress(hostname.toUtf8());
|
|
|
|
if (hostAddress.isNull())
|
|
{
|
|
DebugIf(verboseBrowser, log, "Wait for resolver on hostname [%s]", QSTRING_CSTR(hostname));
|
|
|
|
QEventLoop loop;
|
|
QTimer timer;
|
|
|
|
QObject::connect(&MdnsBrowser::getInstance(), &MdnsBrowser::addressResolved, &loop, &QEventLoop::quit);
|
|
weakConnect(&MdnsBrowser::getInstance(), &MdnsBrowser::addressResolved,
|
|
[&hostAddress, hostname, log](const QHostAddress& resolvedAddress) {
|
|
DebugIf(verboseBrowser, log, "Resolver resolved hostname [%s] to address [%s], Thread: %s", QSTRING_CSTR(hostname), QSTRING_CSTR(resolvedAddress.toString()), QSTRING_CSTR(QThread::currentThread()->objectName()));
|
|
hostAddress = resolvedAddress;
|
|
});
|
|
|
|
QTimer::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
|
|
timer.start(static_cast<int>(timeout.count()));
|
|
loop.exec();
|
|
}
|
|
|
|
if (!hostAddress.isNull())
|
|
{
|
|
Debug(log, "Resolved mDNS hostname [%s] to address [%s]", QSTRING_CSTR(hostname), QSTRING_CSTR(hostAddress.toString()));
|
|
isHostAddressOK = true;
|
|
}
|
|
else
|
|
{
|
|
QObject::disconnect(&MdnsBrowser::getInstance(), &MdnsBrowser::addressResolved, nullptr, nullptr);
|
|
Error(log, "Resolved mDNS hostname [%s] timed out", QSTRING_CSTR(hostname));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Error(log, "Hostname [%s] is not an mDNS hostname.", QSTRING_CSTR(hostname));
|
|
isHostAddressOK = false;
|
|
}
|
|
return isHostAddressOK;
|
|
}
|
|
|
|
QMdnsEngine::Record MdnsBrowser::getServiceInstanceRecord(const QByteArray& serviceInstance, const std::chrono::milliseconds waitTime) const
|
|
{
|
|
DebugIf(verboseBrowser, _log, "Get service instance [%s] details, Thread: %s",serviceInstance.constData(), QSTRING_CSTR(QThread::currentThread()->objectName()));
|
|
|
|
QByteArray service{ serviceInstance };
|
|
|
|
if (!service.endsWith('.'))
|
|
{
|
|
service.append('.');
|
|
}
|
|
|
|
QMdnsEngine::Record srvRecord;
|
|
bool found { false };
|
|
int retries { SERVICE_LOOKUP_RETRIES };
|
|
do
|
|
{
|
|
if (_cache.lookupRecord(service, QMdnsEngine::SRV, srvRecord))
|
|
{
|
|
found = true;
|
|
}
|
|
else
|
|
{
|
|
wait(waitTime);
|
|
--retries;
|
|
}
|
|
|
|
} while (!found && retries >= 0);
|
|
|
|
if (found)
|
|
{
|
|
DebugIf(verboseBrowser, _log, "Service record found for service instance [%s]", service.constData());
|
|
|
|
}
|
|
else
|
|
{
|
|
Debug(_log, "No service record found for service instance [%s]", service.constData());
|
|
}
|
|
return srvRecord;
|
|
}
|
|
|
|
QMdnsEngine::Service MdnsBrowser::getFirstService(const QByteArray& serviceType, const QString& filter, const std::chrono::milliseconds waitTime) const
|
|
{
|
|
DebugIf(verboseBrowser,_log, "Get first service of type [%s], matching name: [%s]", QSTRING_CSTR(QString(serviceType)), QSTRING_CSTR(filter));
|
|
|
|
QMdnsEngine::Service service;
|
|
|
|
QRegularExpression regEx(filter);
|
|
if (!regEx.isValid()) {
|
|
QString errorString = regEx.errorString();
|
|
int errorOffset = regEx.patternErrorOffset();
|
|
|
|
Error(_log, "Filtering regular expression [%s] error [%d]:[%s]", QSTRING_CSTR(filter), errorOffset, QSTRING_CSTR(errorString));
|
|
}
|
|
else
|
|
{
|
|
QList<QMdnsEngine::Record> ptrRecords;
|
|
|
|
bool found {false};
|
|
int retries = 3;
|
|
do
|
|
{
|
|
if (_cache.lookupRecords(serviceType, QMdnsEngine::PTR, ptrRecords))
|
|
{
|
|
for (int ptrCounter = 0; ptrCounter < ptrRecords.size(); ++ptrCounter)
|
|
{
|
|
QByteArray serviceNameFull = ptrRecords.at(ptrCounter).target();
|
|
|
|
QRegularExpressionMatch match = regEx.match(serviceNameFull.constData());
|
|
if (match.hasMatch())
|
|
{
|
|
QMdnsEngine::Record srvRecord;
|
|
if (!_cache.lookupRecord(serviceNameFull, QMdnsEngine::SRV, srvRecord))
|
|
{
|
|
DebugIf(verboseBrowser, _log, "No SRV record for [%s] found, skip entry", serviceNameFull.constData());
|
|
}
|
|
else
|
|
{
|
|
if (serviceNameFull.endsWith("." + serviceType))
|
|
{
|
|
service.setName(serviceNameFull.left(serviceNameFull.length() - serviceType.length() - 1));
|
|
}
|
|
else
|
|
{
|
|
service.setName(srvRecord.name());
|
|
}
|
|
service.setPort(srvRecord.port());
|
|
|
|
QByteArray hostName = srvRecord.target();
|
|
//Remove trailing dot
|
|
hostName.chop(1);
|
|
service.setHostname(hostName);
|
|
service.setAttributes(srvRecord.attributes());
|
|
found = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
wait(waitTime);
|
|
--retries;
|
|
}
|
|
|
|
} while (!found && retries >= 0);
|
|
|
|
if (found)
|
|
{
|
|
DebugIf(verboseBrowser,_log, "Service of type [%s] found", serviceType.constData());
|
|
}
|
|
else
|
|
{
|
|
Debug(_log, "No service of type [%s] found", serviceType.constData());
|
|
|
|
}
|
|
}
|
|
|
|
return service;
|
|
}
|
|
|
|
QJsonArray MdnsBrowser::getServicesDiscoveredJson(const QByteArray& serviceType, const QString& filter, const std::chrono::milliseconds waitTime) const
|
|
{
|
|
DebugIf(verboseBrowser,_log, "Get services of type [%s], matching name: [%s], Thread: %s", QSTRING_CSTR(QString(serviceType)), QSTRING_CSTR(filter), QSTRING_CSTR(QThread::currentThread()->objectName()));
|
|
|
|
QJsonArray result;
|
|
|
|
QRegularExpression regEx(filter);
|
|
if (!regEx.isValid()) {
|
|
QString errorString = regEx.errorString();
|
|
int errorOffset = regEx.patternErrorOffset();
|
|
|
|
Error(_log, "Filtering regular expression [%s] error [%d]:[%s]", QSTRING_CSTR(filter), errorOffset, QSTRING_CSTR(errorString));
|
|
}
|
|
else
|
|
{
|
|
QList<QMdnsEngine::Record> ptrRecords;
|
|
|
|
int retries = 3;
|
|
do
|
|
{
|
|
if (_cache.lookupRecords(serviceType, QMdnsEngine::PTR, ptrRecords))
|
|
{
|
|
for (int ptrCounter = 0; ptrCounter < ptrRecords.size(); ++ptrCounter)
|
|
{
|
|
QByteArray serviceName = ptrRecords.at(ptrCounter).target();
|
|
|
|
QRegularExpressionMatch match = regEx.match(serviceName.constData());
|
|
if (match.hasMatch())
|
|
{
|
|
QMdnsEngine::Record srvRecord;
|
|
if (!_cache.lookupRecord(serviceName, QMdnsEngine::SRV, srvRecord))
|
|
{
|
|
Debug(_log, "No SRV record for [%s] found, skip entry", serviceName.constData());
|
|
}
|
|
else
|
|
{
|
|
QJsonObject obj;
|
|
QString domain = "local.";
|
|
|
|
obj.insert("id", serviceName.constData());
|
|
|
|
QString service = serviceName;
|
|
service.chop(1);
|
|
obj.insert("service", service);
|
|
obj.insert("type", serviceType.constData());
|
|
|
|
QString name;
|
|
if (serviceName.endsWith("." + serviceType))
|
|
{
|
|
name = serviceName.left(serviceName.length() - serviceType.length() - 1);
|
|
obj.insert("name", QString(name));
|
|
}
|
|
|
|
QByteArray hostName = srvRecord.target();
|
|
//Remove trailing dot
|
|
hostName.chop(1);
|
|
|
|
obj.insert("hostname", QString(hostName));
|
|
obj.insert("domain", domain);
|
|
|
|
//Tag records where the service is provided by this host
|
|
QByteArray localHostname = QHostInfo::localHostName().toUtf8();
|
|
localHostname = localHostname.replace('.', '-');
|
|
|
|
bool isSameHost {false};
|
|
if ( name == localHostname )
|
|
{
|
|
isSameHost = true;
|
|
}
|
|
obj.insert("sameHost", isSameHost);
|
|
|
|
quint16 port = srvRecord.port();
|
|
obj.insert("port", port);
|
|
|
|
QMdnsEngine::Record txtRecord;
|
|
if (_cache.lookupRecord(serviceName, QMdnsEngine::TXT, txtRecord))
|
|
{
|
|
QMap<QByteArray, QByteArray> txtAttributes = txtRecord.attributes();
|
|
|
|
QVariantMap txtMap;
|
|
QMapIterator<QByteArray, QByteArray> iterator(txtAttributes);
|
|
while (iterator.hasNext()) {
|
|
iterator.next();
|
|
txtMap.insert(iterator.key(), iterator.value());
|
|
}
|
|
obj.insert("txt", QJsonObject::fromVariantMap(txtMap));
|
|
}
|
|
result << obj;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( result.isEmpty())
|
|
{
|
|
wait(waitTime);
|
|
--retries;
|
|
}
|
|
|
|
} while (result.isEmpty() && retries >= 0);
|
|
|
|
if (!result.isEmpty())
|
|
{
|
|
DebugIf(verboseBrowser,_log, "result: [%s]", QString(QJsonDocument(result).toJson(QJsonDocument::Compact)).toUtf8().constData());
|
|
}
|
|
else
|
|
{
|
|
Debug(_log, "No service of type [%s] found", serviceType.constData());
|
|
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void MdnsBrowser::printCache(const QByteArray& name, quint16 type) const
|
|
{
|
|
DebugIf(verboseBrowser,_log, "for type: %s", QSTRING_CSTR(QMdnsEngine::typeName(type)));
|
|
QList<QMdnsEngine::Record> records;
|
|
if (_cache.lookupRecords(name, type, records))
|
|
{
|
|
qDebug() << "";
|
|
foreach(QMdnsEngine::Record record, records)
|
|
{
|
|
qDebug() << QMdnsEngine::typeName(record.type()) << "," << record.name() << "], ttl : " << record.ttl();
|
|
|
|
switch (record.type()) {
|
|
case QMdnsEngine::PTR:
|
|
qDebug() << QMdnsEngine::typeName(record.type()) << "," << record.name() << ", target : " << record.target();
|
|
break;
|
|
|
|
case QMdnsEngine::SRV:
|
|
qDebug() << QMdnsEngine::typeName(record.type()) << "," << record.name() << ", target : " << record.target();
|
|
qDebug() << QMdnsEngine::typeName(record.type()) << "," << record.name() << ", port : " << record.port();
|
|
qDebug() << QMdnsEngine::typeName(record.type()) << "," << record.name() << ", priority : " << record.priority();
|
|
qDebug() << QMdnsEngine::typeName(record.type()) << "," << record.name() << ", weight : " << record.weight();
|
|
break;
|
|
case QMdnsEngine::TXT:
|
|
qDebug() << QMdnsEngine::typeName(record.type()) << "," << record.name() << ", attributes: " << record.attributes();
|
|
break;
|
|
|
|
case QMdnsEngine::NSEC:
|
|
qDebug() << QMdnsEngine::typeName(record.type()) << "," << record.name() << ", nextDomNam: " << record.nextDomainName();
|
|
break;
|
|
|
|
case QMdnsEngine::A:
|
|
case QMdnsEngine::AAAA:
|
|
qDebug() << QMdnsEngine::typeName(record.type()) << "," << record.name() << ", address : " << record.address();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DebugIf(verboseBrowser,_log, "Cash is empty for type: %s", QSTRING_CSTR(QMdnsEngine::typeName(type)));
|
|
}
|
|
}
|