feat: initial commit (migrate from old flowy monorepo)
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
BasedOnStyle: Microsoft
|
||||
TabWidth: 2
|
||||
IndentWidth: 2
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
# This file is used to ignore files which are generated
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
*~
|
||||
*.autosave
|
||||
*.a
|
||||
*.core
|
||||
*.moc
|
||||
*.o
|
||||
*.obj
|
||||
*.orig
|
||||
*.rej
|
||||
*.so
|
||||
*.so.*
|
||||
*_pch.h.cpp
|
||||
*_resource.rc
|
||||
*.qm
|
||||
.#*
|
||||
*.*#
|
||||
core
|
||||
!core/
|
||||
tags
|
||||
.DS_Store
|
||||
.directory
|
||||
*.debug
|
||||
Makefile*
|
||||
*.prl
|
||||
*.app
|
||||
moc_*.cpp
|
||||
ui_*.h
|
||||
qrc_*.cpp
|
||||
Thumbs.db
|
||||
*.res
|
||||
*.rc
|
||||
/.qmake.cache
|
||||
/.qmake.stash
|
||||
|
||||
# qtcreator generated files
|
||||
*.pro.user*
|
||||
*.qbs.user*
|
||||
CMakeLists.txt.user*
|
||||
|
||||
# xemacs temporary files
|
||||
*.flc
|
||||
|
||||
# Vim temporary files
|
||||
.*.swp
|
||||
|
||||
# Visual Studio generated files
|
||||
*.ib_pdb_index
|
||||
*.idb
|
||||
*.ilk
|
||||
*.pdb
|
||||
*.sln
|
||||
*.suo
|
||||
*.vcproj
|
||||
*vcproj.*.*.user
|
||||
*.ncb
|
||||
*.sdf
|
||||
*.opensdf
|
||||
*.vcxproj
|
||||
*vcxproj.*
|
||||
|
||||
# MinGW generated files
|
||||
*.Debug
|
||||
*.Release
|
||||
|
||||
# Python byte code
|
||||
*.pyc
|
||||
|
||||
# Binaries
|
||||
# --------
|
||||
*.dll
|
||||
*.exe
|
||||
|
||||
# Directories with generated files
|
||||
.moc/
|
||||
.obj/
|
||||
.pch/
|
||||
.rcc/
|
||||
.uic/
|
||||
/build*/
|
||||
|
||||
.cache/
|
||||
@@ -0,0 +1,35 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
project(mdns VERSION 0.1 LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(CMAKE_EXPORT_COMPILATION_DATABASE ON)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
|
||||
|
||||
# https://doc.qt.io/qt-6/qt-cmake-policy-qtp0003.html
|
||||
option(BUILD_SHARED_LIBS "Build shared instead of static library" ON)
|
||||
|
||||
find_package(Qt6 6.5 REQUIRED COMPONENTS Core)
|
||||
qt_standard_project_setup(REQUIRES 6.5)
|
||||
qt_policy(SET QTP0003 NEW)
|
||||
|
||||
set(SOURCES
|
||||
src/broadcaster.h
|
||||
src/broadcaster.cpp
|
||||
src/scanner.h
|
||||
src/scanner.cpp
|
||||
)
|
||||
qt_add_library(mdns ${SOURCES})
|
||||
|
||||
target_include_directories(mdns PUBLIC src/)
|
||||
|
||||
target_link_libraries(mdns PRIVATE Qt6::Core)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
install(TARGETS mdns
|
||||
BUNDLE DESTINATION .
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
This package is a Qt wrapper over [zeroconf](https://github.com/yvz/zeroconf).
|
||||
@@ -0,0 +1,167 @@
|
||||
#include "broadcaster.h"
|
||||
#include <QDebug>
|
||||
#include <cstring>
|
||||
#include <dns_sd.h>
|
||||
#include <iostream>
|
||||
|
||||
ServiceInfo::ServiceInfo(QString sn, QString st, uint16_t p) : service_name(sn), service_type(st), service_port(p)
|
||||
{
|
||||
}
|
||||
|
||||
void BroadcastWorker::static_callback(DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType errorCode,
|
||||
const char *name, const char *regtype, const char *domain, void *context)
|
||||
{
|
||||
// Cast the context back to our class instance
|
||||
BroadcastWorker *self = static_cast<BroadcastWorker *>(context);
|
||||
if (errorCode == kDNSServiceErr_NoError)
|
||||
{
|
||||
std::cout << "Service registered successfully!" << std::endl;
|
||||
std::cout << "Service Name: " << name << std::endl;
|
||||
std::cout << "Service Type: " << regtype << std::endl;
|
||||
std::cout << "Domain: " << domain << std::endl;
|
||||
emit self->service_registered();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Error registering service: " << errorCode << std::endl;
|
||||
emit self->error_occurred("Error registering service.");
|
||||
}
|
||||
}
|
||||
|
||||
BroadcastWorker::BroadcastWorker(QObject *parent, ServiceInfo *service) : QThread(parent), m_service_info(service)
|
||||
{
|
||||
}
|
||||
|
||||
void BroadcastWorker::run()
|
||||
{
|
||||
DNSServiceRef serviceRef;
|
||||
DNSServiceErrorType error;
|
||||
|
||||
// Service parameters
|
||||
std::string service_name = m_service_info->service_name.toStdString();
|
||||
std::string service_type = m_service_info->service_type.toStdString();
|
||||
const char *serviceName = service_name.c_str(); // Can be NULL for automatic naming
|
||||
const char *serviceType = service_type.c_str(); // Service type and protocol
|
||||
const char *serviceDomain = NULL; // NULL for default domain
|
||||
const char *serviceHost = NULL; // NULL for default host
|
||||
uint16_t servicePort = m_service_info->service_port; // Service port in network byte order
|
||||
uint16_t txtLen = 0; // Length of TXT record
|
||||
const void *txtRecord = NULL; // TXT record (can be NULL)
|
||||
|
||||
// Convert port to network byte order
|
||||
servicePort = htons(servicePort);
|
||||
|
||||
// Register the service
|
||||
error = DNSServiceRegister(&serviceRef,
|
||||
0, // Default flags
|
||||
0, // Default interface
|
||||
serviceName, serviceType, serviceDomain, serviceHost, servicePort, txtLen, txtRecord,
|
||||
BroadcastWorker::static_callback,
|
||||
this); // Context pointer
|
||||
|
||||
if (error != kDNSServiceErr_NoError)
|
||||
{
|
||||
std::cerr << "Error registering service: " << error << std::endl;
|
||||
emit error_occurred("Error registering service.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Process the service registration
|
||||
int dns_sd_fd = DNSServiceRefSockFD(serviceRef);
|
||||
fd_set readfds;
|
||||
struct timeval tv;
|
||||
int result;
|
||||
|
||||
// Keep the service registered until the user terminates the program
|
||||
std::cout << "Service is being broadcasted." << std::endl;
|
||||
|
||||
while (1)
|
||||
{
|
||||
if (isInterruptionRequested())
|
||||
{
|
||||
// Clean up
|
||||
DNSServiceRefDeallocate(serviceRef);
|
||||
return;
|
||||
}
|
||||
|
||||
FD_ZERO(&readfds);
|
||||
FD_SET(dns_sd_fd, &readfds);
|
||||
|
||||
tv.tv_sec = 1;
|
||||
tv.tv_usec = 0;
|
||||
|
||||
result = select(dns_sd_fd + 1, &readfds, NULL, NULL, &tv);
|
||||
if (result > 0)
|
||||
{
|
||||
DNSServiceProcessResult(serviceRef);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Broadcaster::Broadcaster(QObject *parent) : QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
Broadcaster::~Broadcaster()
|
||||
{
|
||||
if (m_worker)
|
||||
{
|
||||
m_worker->requestInterruption();
|
||||
}
|
||||
}
|
||||
|
||||
void Broadcaster::start_broadcasting(ServiceInfo *service)
|
||||
{
|
||||
if (m_worker)
|
||||
{
|
||||
qWarning() << "Already have a worker for broadcasting";
|
||||
emit error_occurred("Already have a worker for broadcasting. Please stop first.");
|
||||
return;
|
||||
}
|
||||
|
||||
m_worker = new BroadcastWorker(this, service);
|
||||
connect(m_worker, &BroadcastWorker::error_occurred, this, [this](const QString &message) {
|
||||
emit broadcasting_failure();
|
||||
emit error_occurred(message);
|
||||
});
|
||||
connect(m_worker, &BroadcastWorker::started, this, [this]() { qDebug() << "Broadcast worker started"; });
|
||||
connect(m_worker, &BroadcastWorker::service_registered, this, [this] {
|
||||
set_is_broadcasting(true);
|
||||
qDebug() << "Service registered";
|
||||
});
|
||||
connect(m_worker, &BroadcastWorker::finished, [this] {
|
||||
qDebug() << "Broadcast worker finished";
|
||||
m_worker->deleteLater();
|
||||
m_worker = nullptr;
|
||||
|
||||
set_is_broadcasting(false);
|
||||
});
|
||||
m_worker->start();
|
||||
}
|
||||
|
||||
void Broadcaster::stop_broadcasting()
|
||||
{
|
||||
if (!m_worker || !m_worker->isRunning())
|
||||
{
|
||||
qWarning() << "Not broadcasting";
|
||||
emit error_occurred("Not broadcasting, can't stop");
|
||||
return;
|
||||
}
|
||||
|
||||
m_worker->requestInterruption();
|
||||
m_worker->wait();
|
||||
}
|
||||
|
||||
bool Broadcaster::is_broadcasting() const
|
||||
{
|
||||
return m_is_broadcasting;
|
||||
}
|
||||
|
||||
void Broadcaster::set_is_broadcasting(bool new_val)
|
||||
{
|
||||
if (m_is_broadcasting == new_val)
|
||||
return;
|
||||
|
||||
m_is_broadcasting = new_val;
|
||||
emit is_broadcasting_changed();
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
#ifndef BROADCASTER_H
|
||||
#define BROADCASTER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
#include <dns_sd.h>
|
||||
|
||||
struct ServiceInfo
|
||||
{
|
||||
QString service_name;
|
||||
/**
|
||||
* @brief e.g. _http._tcp
|
||||
*/
|
||||
QString service_type;
|
||||
uint16_t service_port;
|
||||
|
||||
ServiceInfo(QString sn, QString st, uint16_t sp);
|
||||
};
|
||||
|
||||
/**
|
||||
* @class BroadcastAdvertiser
|
||||
* @brief Runs in another thread to broadcast a given service
|
||||
*
|
||||
*/
|
||||
class BroadcastWorker : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit BroadcastWorker(QObject *parent, ServiceInfo *service_info);
|
||||
|
||||
private:
|
||||
ServiceInfo *m_service_info;
|
||||
void run() override;
|
||||
|
||||
// Static callback wrapper that will be called by the C API
|
||||
static void static_callback(DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType errorCode,
|
||||
const char *name, const char *regtype, const char *domain, void *context);
|
||||
|
||||
signals:
|
||||
void error_occurred(const QString &message);
|
||||
void service_registered();
|
||||
};
|
||||
|
||||
/**
|
||||
* @class Broadcaster
|
||||
* @brief Broadcasts a service with given port. It does so continuously in another thread.
|
||||
*/
|
||||
class Broadcaster : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Broadcaster(QObject *parent = nullptr);
|
||||
~Broadcaster();
|
||||
|
||||
bool is_broadcasting() const;
|
||||
|
||||
public slots:
|
||||
/**
|
||||
* @brief Starts broadcasting using the information from the given service.
|
||||
*/
|
||||
void start_broadcasting(ServiceInfo *);
|
||||
/**
|
||||
* @brief Stops broadcasting and does cleanup. Gracefully handles if not currently broadcasting.
|
||||
*/
|
||||
void stop_broadcasting();
|
||||
|
||||
signals:
|
||||
void error_occurred(const QString &);
|
||||
void broadcasting_failure();
|
||||
void is_broadcasting_changed();
|
||||
|
||||
private:
|
||||
BroadcastWorker *m_worker;
|
||||
|
||||
bool m_is_broadcasting;
|
||||
|
||||
void set_is_broadcasting(bool);
|
||||
};
|
||||
|
||||
#endif // BROADCASTER_H
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
#include "scanner.h"
|
||||
#include <arpa/inet.h> // For inet_ntop
|
||||
#include <netinet/in.h>
|
||||
#include <iostream>
|
||||
|
||||
Scanner::Scanner(QString query, QObject *parent) : m_query(query), QThread(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void Scanner::run()
|
||||
{
|
||||
DNSServiceRef serviceRef;
|
||||
std::string query = m_query.toStdString();
|
||||
|
||||
DNSServiceErrorType error =
|
||||
DNSServiceBrowse(&serviceRef, 0, 0, query.c_str(), NULL, Scanner::service_browse_callback, this);
|
||||
|
||||
if (error != kDNSServiceErr_NoError)
|
||||
{
|
||||
fprintf(stderr, "Error browsing services: %d\n", error);
|
||||
emit error_occurred("Error browing services");
|
||||
return;
|
||||
}
|
||||
|
||||
// Process the service registration
|
||||
int dns_sd_fd = DNSServiceRefSockFD(serviceRef);
|
||||
fd_set readfds;
|
||||
struct timeval tv;
|
||||
int result;
|
||||
|
||||
// Keep the service registered until the user terminates the program
|
||||
std::cout << "Service is being broadcasted." << std::endl;
|
||||
|
||||
while (1)
|
||||
{
|
||||
if (isInterruptionRequested())
|
||||
{
|
||||
// Clean up
|
||||
DNSServiceRefDeallocate(serviceRef);
|
||||
return;
|
||||
}
|
||||
|
||||
FD_ZERO(&readfds);
|
||||
FD_SET(dns_sd_fd, &readfds);
|
||||
|
||||
tv.tv_sec = 1;
|
||||
tv.tv_usec = 0;
|
||||
|
||||
result = select(dns_sd_fd + 1, &readfds, NULL, NULL, &tv);
|
||||
if (result > 0)
|
||||
{
|
||||
DNSServiceProcessResult(serviceRef);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Scanner::service_resolve_callback(DNSServiceRef service, DNSServiceFlags flags, uint32_t interface_index,
|
||||
DNSServiceErrorType error_code, const char *full_name, const char *host_target,
|
||||
uint16_t port, uint16_t txt_len, const unsigned char *txt_record, void *context)
|
||||
{
|
||||
Scanner *self = static_cast<Scanner *>(context);
|
||||
if (error_code == kDNSServiceErr_NoError)
|
||||
{
|
||||
printf("Resolved Service: %s\n", full_name);
|
||||
printf("Host: %s\n", host_target);
|
||||
printf("Port: %u\n", ntohs(port)); // Convert the port to host byte order
|
||||
//
|
||||
self->m_current_service_name = QString(full_name);
|
||||
self->m_current_host_target = QString(host_target);
|
||||
self->m_current_port = ntohs(port);
|
||||
|
||||
DNSServiceRef addrRef;
|
||||
DNSServiceErrorType addrError =
|
||||
DNSServiceGetAddrInfo(&addrRef, 0, interface_index, kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6,
|
||||
host_target, Scanner::address_info_callback, self);
|
||||
|
||||
if (addrError != kDNSServiceErr_NoError)
|
||||
{
|
||||
printf("Error getting address info: %d\n", addrError);
|
||||
emit self->error_occurred("Error resolving IP address");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Process the result to get the IP
|
||||
DNSServiceProcessResult(addrRef);
|
||||
DNSServiceRefDeallocate(addrRef);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Failed to resolve service: %d\n", error_code);
|
||||
emit self->error_occurred("Error resolving service");
|
||||
}
|
||||
}
|
||||
|
||||
void Scanner::address_info_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex,
|
||||
DNSServiceErrorType errorCode, const char *hostname, const struct sockaddr *address,
|
||||
uint32_t ttl, void *context)
|
||||
{
|
||||
Scanner *self = static_cast<Scanner *>(context);
|
||||
if (errorCode == kDNSServiceErr_NoError)
|
||||
{
|
||||
// Convert sockaddr to readable IP address
|
||||
char ip_str[INET6_ADDRSTRLEN]; // Big enough for both IPv4 and IPv6
|
||||
bool is_good_address = false;
|
||||
|
||||
if (address->sa_family == AF_INET)
|
||||
{
|
||||
// IPv4
|
||||
struct sockaddr_in *addr_in = (struct sockaddr_in *)address;
|
||||
inet_ntop(AF_INET, &(addr_in->sin_addr), ip_str, INET_ADDRSTRLEN);
|
||||
|
||||
// Skip link-local addresses (169.254.x.x)
|
||||
uint8_t first_octet = addr_in->sin_addr.s_addr & 0xFF;
|
||||
uint8_t second_octet = (addr_in->sin_addr.s_addr >> 8) & 0xFF;
|
||||
|
||||
if (!(first_octet == 169 && second_octet == 254))
|
||||
{
|
||||
is_good_address = true;
|
||||
}
|
||||
}
|
||||
else if (address->sa_family == AF_INET6)
|
||||
{
|
||||
// IPv6
|
||||
struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)address;
|
||||
inet_ntop(AF_INET6, &(addr_in6->sin6_addr), ip_str, INET6_ADDRSTRLEN);
|
||||
|
||||
// Skip link-local addresses (fe80::)
|
||||
if (!IN6_IS_ADDR_LINKLOCAL(&addr_in6->sin6_addr))
|
||||
{
|
||||
// Only use IPv6 if we have no IPv4 (lower priority)
|
||||
is_good_address = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
strcpy(ip_str, "Unknown address type");
|
||||
}
|
||||
|
||||
printf("IP Address: %s\n", ip_str);
|
||||
|
||||
// Only emit the signal if this is a good address
|
||||
if (is_good_address)
|
||||
{
|
||||
emit self->service_found(self->m_current_service_name, QString(ip_str), QString(hostname), self->m_current_port);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Failed to resolve address: %d\n", errorCode);
|
||||
emit self->error_occurred("Error resolving IP address");
|
||||
}
|
||||
}
|
||||
|
||||
void Scanner::service_browse_callback(DNSServiceRef service, DNSServiceFlags flags, uint32_t interface_index,
|
||||
DNSServiceErrorType error_code, const char *service_name, const char *regtype,
|
||||
const char *reply_domain, void *context)
|
||||
{
|
||||
Scanner *self = static_cast<Scanner *>(context);
|
||||
|
||||
if (error_code == kDNSServiceErr_NoError)
|
||||
{
|
||||
printf("Service found: %s.%s%s\n", service_name, regtype, reply_domain ? reply_domain : "");
|
||||
|
||||
// Now resolve the service to get port and other details
|
||||
DNSServiceRef resolveRef;
|
||||
DNSServiceErrorType resolveError = DNSServiceResolve(&resolveRef, 0, interface_index, service_name, regtype,
|
||||
reply_domain, Scanner::service_resolve_callback, self);
|
||||
|
||||
if (resolveError != kDNSServiceErr_NoError)
|
||||
{
|
||||
printf("Error resolving service: %d\n", resolveError);
|
||||
self->error_occurred("Error resolving service");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Process the resolved service
|
||||
DNSServiceProcessResult(resolveRef);
|
||||
DNSServiceRefDeallocate(resolveRef);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
self->error_occurred("Error browsing dns");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
#ifndef SCANNER_H
|
||||
#define SCANNER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
#include <dns_sd.h>
|
||||
|
||||
struct Record
|
||||
{
|
||||
};
|
||||
|
||||
/**
|
||||
* @class Scanner
|
||||
* @brief A thread that handles scanning with given mDNS query and returns.
|
||||
*
|
||||
*/
|
||||
class Scanner : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
* @param query: e.g. "_http._tcp"
|
||||
*/
|
||||
explicit Scanner(QString query, QObject *parent = nullptr);
|
||||
|
||||
private:
|
||||
void run() override;
|
||||
|
||||
QString m_query;
|
||||
|
||||
// Caching values to use across callbacks
|
||||
// TODO: pass a context struct around instead of this? or does every callback run sequentially?
|
||||
QString m_current_service_name;
|
||||
QString m_current_host_target;
|
||||
uint16_t m_current_port;
|
||||
|
||||
static void DNSSD_API service_resolve_callback(DNSServiceRef service, DNSServiceFlags flags, uint32_t interface_index,
|
||||
DNSServiceErrorType error_code, const char *full_name,
|
||||
const char *host_target, uint16_t port, uint16_t txt_len,
|
||||
const unsigned char *txt_record, void *context);
|
||||
static void DNSSD_API service_browse_callback(DNSServiceRef service, DNSServiceFlags flags, uint32_t interface_index,
|
||||
DNSServiceErrorType error_code, const char *service_name,
|
||||
const char *regtype, const char *reply_domain, void *context);
|
||||
static void DNSSD_API address_info_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex,
|
||||
DNSServiceErrorType errorCode, const char *hostname,
|
||||
const struct sockaddr *address, uint32_t ttl, void *context);
|
||||
|
||||
public slots:
|
||||
|
||||
signals:
|
||||
void service_found(const QString &service_name, const QString &ip, const QString &host, const uint16_t port);
|
||||
void error_occurred(const QString &message);
|
||||
};
|
||||
|
||||
#endif // SCANNER_H
|
||||
Reference in New Issue
Block a user