diff --git a/FactoryTestTool/FactoryTestTool.vcxproj b/FactoryTestTool/FactoryTestTool.vcxproj index 50b6339..c7a3424 100644 --- a/FactoryTestTool/FactoryTestTool.vcxproj +++ b/FactoryTestTool/FactoryTestTool.vcxproj @@ -42,6 +42,22 @@ + + + + + + + + + + + + + + + + @@ -82,10 +98,41 @@ + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/FactoryTestTool/FactoryTestTool.vcxproj.filters b/FactoryTestTool/FactoryTestTool.vcxproj.filters index 11652d2..c9b8d1a 100644 --- a/FactoryTestTool/FactoryTestTool.vcxproj.filters +++ b/FactoryTestTool/FactoryTestTool.vcxproj.filters @@ -32,6 +32,54 @@ LicenseGenerate + + Network\mdns + + + Network\mdns\qmdnsengine\src + + + Network\mdns\qmdnsengine\src + + + Network\mdns\qmdnsengine\src + + + Network\mdns\qmdnsengine\src + + + Network\mdns\qmdnsengine\src + + + Network\mdns\qmdnsengine\src + + + Network\mdns\qmdnsengine\src + + + Network\mdns\qmdnsengine\src + + + Network\mdns\qmdnsengine\src + + + Network\mdns\qmdnsengine\src + + + Network\mdns\qmdnsengine\src + + + Network\mdns\qmdnsengine\src + + + Network\mdns\qmdnsengine\src + + + Network\mdns\qmdnsengine\src + + + Network\mdns\qmdnsengine\src + @@ -55,9 +103,63 @@ Network - + + LicenseGenerate + + Network + + Network + + + Network\mdns + + + Network\mdns\qmdnsengine\include + + + Network\mdns\qmdnsengine\include + + + Network\mdns\qmdnsengine\include + + + Network\mdns\qmdnsengine\include + + + Network\mdns\qmdnsengine\include + + + Network\mdns\qmdnsengine\include + + + Network\mdns\qmdnsengine\include + + + Network\mdns\qmdnsengine\include + + + Network\mdns\qmdnsengine\src + + + Network\mdns\qmdnsengine\src + + + Network\mdns\qmdnsengine\src + + + Network\mdns\qmdnsengine\src + + + Network\mdns\qmdnsengine\src + + + Network\mdns\qmdnsengine\src + + + Network\mdns\qmdnsengine\src + @@ -135,6 +237,18 @@ {463f4c83-c88f-4815-b160-19430dd30eda} + + {3570ab1e-6ede-402b-8dd0-1c53afff0bd8} + + + {d437b89e-76f2-495a-9434-e6ab8479c960} + + + {7f4c4097-0c9f-4b37-967c-5efae6108a91} + + + {e8857e15-2519-4a60-9fd5-eff901a76ba6} + @@ -161,5 +275,44 @@ LicenseGenerate + + Network\mdns\qmdnsengine\include + + + Network\mdns\qmdnsengine\include + + + Network\mdns\qmdnsengine\include + + + Network\mdns\qmdnsengine\include + + + Network\mdns\qmdnsengine\include + + + Network\mdns\qmdnsengine\include + + + Network\mdns\qmdnsengine\include + + + Network\mdns\qmdnsengine\src + + + Network\mdns\qmdnsengine\src + + + Network\mdns\qmdnsengine\src + + + Network\mdns\qmdnsengine\src + + + Network\mdns\qmdnsengine\src + + + Network\mdns\qmdnsengine\include + \ No newline at end of file diff --git a/FactoryTestTool/SourceCode/Json/JsonFile/backBoardFuncConfig.json b/FactoryTestTool/SourceCode/Json/JsonFile/backBoardFuncConfig.json index e69de29..4e242d8 100644 --- a/FactoryTestTool/SourceCode/Json/JsonFile/backBoardFuncConfig.json +++ b/FactoryTestTool/SourceCode/Json/JsonFile/backBoardFuncConfig.json @@ -0,0 +1,8 @@ +[ + { + "cmd": "V851_SPK_TEST", + "val": 0, + "lable": "V851²⊔SPK", + "timeout": 2000 + } +] \ No newline at end of file diff --git a/FactoryTestTool/SourceCode/Json/JsonFile/backBoardOneClickTest.json b/FactoryTestTool/SourceCode/Json/JsonFile/backBoardOneClickTest.json index e69de29..dcc0e8e 100644 --- a/FactoryTestTool/SourceCode/Json/JsonFile/backBoardOneClickTest.json +++ b/FactoryTestTool/SourceCode/Json/JsonFile/backBoardOneClickTest.json @@ -0,0 +1,118 @@ +[ + { + "cmd": "V851_SPK_TEST", + "val": 0, + "lable": "V851测试SPK", + "timeout": 2000 + }, + { + "cmd": "806_SPK_TEST", + "val": 0, + "lable": "806测试SPK", + "timeout": 2000 + }, + { + "cmd": "MIC_TEST", + "val": 0, + "lable": "MIC测试", + "timeout": 2000 + }, + { + "cmd": "UNLOCK_LOCK_TEST", + "val": 0, + "lable": "开锁测试", + "timeout": 2000 + }, + { + "cmd": "UNLOCK_LOCK_TEST", + "val": 1, + "lable": "关锁测试", + "timeout": 2000 + }, + { + "cmd": "BUTTON_TEST", + "val": 0, + "lable": "开锁按键测试", + "timeout": 2000 + }, + { + "cmd": "BUTTON_TEST", + "val": 1, + "lable": "关锁按键测试", + "timeout": 2000 + }, + { + "cmd": "BUTTON_TEST", + "val": 2, + "lable": "猫眼按键测试", + "timeout": 2000 + }, + { + "cmd": "BACKLIGHT_TEST", + "val": 0, + "lable": "后屏背光测试", + "timeout": 2000 + }, + { + "cmd": "TOUCH_TEST", + "x1": 100, + "y1": 150, + "r1": 50, + "x2": 620, + "y2": 150, + "r2": 50, + "x3": 360, + "y3": 640, + "r3": 50, + "x4": 100, + "y4": 1130, + "r4": 50, + "x5": 620, + "y5": 1130, + "r5": 50, + "lable": "后触摸屏测试", + "timeout": 2000 + }, + { + "cmd": "START_CAT_EYE_TEST", + "val": 0, + "lable": "开猫眼测试", + "timeout": 2000 + }, + { + "cmd": "UART_TEST", + "val": 0, + "lable": "串口测试", + "timeout": 2000 + }, + { + "cmd": "VIDEO_TEST", + "val": 0, + "lable": "开启视频", + "timeout": 2000 + }, + { + "cmd": "VIDEO_TEST", + "val": 1, + "lable": "关闭视频", + "timeout": 2000 + }, + { + "cmd": "WIFI_SIGNAL_TEST", + "val": 0, + "lable": "wifi信号测试", + "timeout": 2000 + }, + { + "cmd": "ENTER_CONFIG_NET", + "val": 0, + "lable": "进入配网测试", + "timeout": 2000 + }, + { + "cmd": "NETWORK_TEST", + "val": 0, + "lable": "联网测试", + "timeout": 2000 + } +] diff --git a/FactoryTestTool/SourceCode/Json/JsonFile/backBoardTest.json b/FactoryTestTool/SourceCode/Json/JsonFile/backBoardTest.json index 1349541..dcc0e8e 100644 --- a/FactoryTestTool/SourceCode/Json/JsonFile/backBoardTest.json +++ b/FactoryTestTool/SourceCode/Json/JsonFile/backBoardTest.json @@ -1,79 +1,118 @@ [ { - "msdID": "text", - "val": "text", - "lable": "�״����" - }, - { - "cmd": "text", - "val": "text", - "lable": "NFC测试", + "cmd": "V851_SPK_TEST", + "val": 0, + "lable": "V851测试SPK", "timeout": 2000 }, { - "cmd": "text", - "val": "text", - "lable": "����ע��", + "cmd": "806_SPK_TEST", + "val": 0, + "lable": "806测试SPK", "timeout": 2000 }, { - "cmd": "text", - "val": "text", - "lable": "����ʶ��", + "cmd": "MIC_TEST", + "val": 0, + "lable": "MIC测试", "timeout": 2000 }, { - "cmd": "text", - "val": "text", - "lable": "�ƾ���ע��", + "cmd": "UNLOCK_LOCK_TEST", + "val": 0, + "lable": "开锁测试", "timeout": 2000 }, { - "cmd": "text", - "val": "text", - "lable": "�ƾ���ʶ��", + "cmd": "UNLOCK_LOCK_TEST", + "val": 1, + "lable": "关锁测试", "timeout": 2000 }, { - "cmd": "text", - "val": "text", - "lable": "�������", + "cmd": "BUTTON_TEST", + "val": 0, + "lable": "开锁按键测试", "timeout": 2000 }, { - "cmd": "text", - "val": "text", - "lable": "��������", + "cmd": "BUTTON_TEST", + "val": 1, + "lable": "关锁按键测试", "timeout": 2000 }, { - "cmd": "text", - "val": "text", - "lable": "��������", + "cmd": "BUTTON_TEST", + "val": 2, + "lable": "猫眼按键测试", "timeout": 2000 }, { - "cmd": "text", - "val": "text", - "lable": "MIC����", + "cmd": "BACKLIGHT_TEST", + "val": 0, + "lable": "后屏背光测试", "timeout": 2000 }, { - "cmd": "text", - "val": "text", - "lable": "SPK����", + "cmd": "TOUCH_TEST", + "x1": 100, + "y1": 150, + "r1": 50, + "x2": 620, + "y2": 150, + "r2": 50, + "x3": 360, + "y3": 640, + "r3": 50, + "x4": 100, + "y4": 1130, + "r4": 50, + "x5": 620, + "y5": 1130, + "r5": 50, + "lable": "后触摸屏测试", "timeout": 2000 }, { - "cmd": "text", - "val": "text", - "lable": "��è��", + "cmd": "START_CAT_EYE_TEST", + "val": 0, + "lable": "开猫眼测试", "timeout": 2000 }, { - "cmd": "set_volume", - "val": 50, - "lable": "��������", + "cmd": "UART_TEST", + "val": 0, + "lable": "串口测试", + "timeout": 2000 + }, + { + "cmd": "VIDEO_TEST", + "val": 0, + "lable": "开启视频", + "timeout": 2000 + }, + { + "cmd": "VIDEO_TEST", + "val": 1, + "lable": "关闭视频", + "timeout": 2000 + }, + { + "cmd": "WIFI_SIGNAL_TEST", + "val": 0, + "lable": "wifi信号测试", + "timeout": 2000 + }, + { + "cmd": "ENTER_CONFIG_NET", + "val": 0, + "lable": "进入配网测试", + "timeout": 2000 + }, + { + "cmd": "NETWORK_TEST", + "val": 0, + "lable": "联网测试", "timeout": 2000 } ] diff --git a/FactoryTestTool/SourceCode/Json/JsonFile/backDevInfo.json b/FactoryTestTool/SourceCode/Json/JsonFile/backDevInfo.json index cc23980..fb9b018 100644 --- a/FactoryTestTool/SourceCode/Json/JsonFile/backDevInfo.json +++ b/FactoryTestTool/SourceCode/Json/JsonFile/backDevInfo.json @@ -1,12 +1,12 @@ [ { - "cmd": "GET_BACK_SW_VERSION", + "cmd": "GET_BACK_V851_VERSION", "val": 0, "lable": "后板V851版本:", "timeout": 2000 }, { - "cmd": "GET_BACK_SW_VERSION", + "cmd": "GET_BACK_806_VERSION", "val": 0, "lable": "后板806版本:", "timeout": 2000 @@ -24,9 +24,9 @@ "timeout": 2000 }, { - "cmd": "GET_BACK_UID", + "cmd": "GET_BACK_UUID", "val": 0, - "lable": "UID:", + "lable": "UUID:", "timeout": 2000 } ] diff --git a/FactoryTestTool/SourceCode/Json/JsonFile/frontBoardOneClickTest.json b/FactoryTestTool/SourceCode/Json/JsonFile/frontBoardOneClickTest.json index a629c17..bf09057 100644 --- a/FactoryTestTool/SourceCode/Json/JsonFile/frontBoardOneClickTest.json +++ b/FactoryTestTool/SourceCode/Json/JsonFile/frontBoardOneClickTest.json @@ -61,26 +61,40 @@ }, { "cmd": "IMG_ENROLL", - "val": 0, + "val": "", "lable": "图片注册", "timeout": 2000 }, { "cmd": "GET_IMG", "val": 0, - "lable": "取图(L)", + "lable": "左边镜头取图", "timeout": 2000 }, { "cmd": "GET_IMG", "val": 1, - "lable": "取图(R)", + "lable": "右边镜头取图", "timeout": 2000 }, { "cmd": "TOUCH_TEST", - "val": 0, - "lable": "触摸屏测试", + "x1": 100, + "y1": 150, + "r1": 50, + "x2": 620, + "y2": 150, + "r2": 50, + "x3": 360, + "y3": 640, + "r3": 50, + "x4": 100, + "y4": 1130, + "r4": 50, + "x5": 620, + "y5": 1130, + "r5": 50, + "lable": "前触摸屏测试", "timeout": 2000 }, { diff --git a/FactoryTestTool/SourceCode/Json/JsonFile/frontBoardTest.json b/FactoryTestTool/SourceCode/Json/JsonFile/frontBoardTest.json index 2e381b7..aa7b36c 100644 --- a/FactoryTestTool/SourceCode/Json/JsonFile/frontBoardTest.json +++ b/FactoryTestTool/SourceCode/Json/JsonFile/frontBoardTest.json @@ -55,7 +55,7 @@ }, { "cmd": "PASSWD_ENROLL", - "val": 0, + "val": "123456", "lable": "密码注册", "timeout": 2000 }, diff --git a/FactoryTestTool/SourceCode/Json/readJsonFile.cpp b/FactoryTestTool/SourceCode/Json/readJsonFile.cpp index 651b906..9bfdc69 100644 --- a/FactoryTestTool/SourceCode/Json/readJsonFile.cpp +++ b/FactoryTestTool/SourceCode/Json/readJsonFile.cpp @@ -51,6 +51,18 @@ QJsonArray readJson_frontLicense() { return readJsonArrayFromFile("./SourceCode/Json/JsonFile/frontBoardLicense.json"); } +QJsonArray readJson_backBoardOneClickTest() { + return readJsonArrayFromFile("./SourceCode/Json/JsonFile/backBoardOneClickTest.json"); +} + +QJsonArray readJson_backBoardTest() { + return readJsonArrayFromFile("./SourceCode/Json/JsonFile/backBoardTest.json"); +} + +QJsonArray readJson_backBoardFuncConfig() { + return readJsonArrayFromFile("./SourceCode/Json/JsonFile/backBoardFuncConfig.json"); +} + QJsonArray readJson_backDevInfo() { return readJsonArrayFromFile("./SourceCode/Json/JsonFile/backDevInfo.json"); } diff --git a/FactoryTestTool/SourceCode/Json/readJsonFile.h b/FactoryTestTool/SourceCode/Json/readJsonFile.h index f240436..1258c1c 100644 --- a/FactoryTestTool/SourceCode/Json/readJsonFile.h +++ b/FactoryTestTool/SourceCode/Json/readJsonFile.h @@ -12,6 +12,10 @@ QJsonArray readJson_frontBoardTest(); QJsonArray readJson_frontBoardFuncConfig(); QJsonArray readJson_frontDevInfo(); QJsonArray readJson_frontLicense(); + +QJsonArray readJson_backBoardOneClickTest(); +QJsonArray readJson_backBoardTest(); +QJsonArray readJson_backBoardFuncConfig(); QJsonArray readJson_backDevInfo(); QJsonArray readJson_testConfig(); diff --git a/FactoryTestTool/SourceCode/Network/LicenseConfirmWindow.h b/FactoryTestTool/SourceCode/LicenseGenerate/LicenseConfirmWindow.h similarity index 100% rename from FactoryTestTool/SourceCode/Network/LicenseConfirmWindow.h rename to FactoryTestTool/SourceCode/LicenseGenerate/LicenseConfirmWindow.h diff --git a/FactoryTestTool/SourceCode/LicenseGenerate/LicenseGenerate.cpp b/FactoryTestTool/SourceCode/LicenseGenerate/LicenseGenerate.cpp index f9f8260..efc366f 100644 --- a/FactoryTestTool/SourceCode/LicenseGenerate/LicenseGenerate.cpp +++ b/FactoryTestTool/SourceCode/LicenseGenerate/LicenseGenerate.cpp @@ -1,17 +1,11 @@ // LicenseGenerate.cpp #include "LicenseGenerate.h" -#define PIX_HARDWARE_INFO_BYTES 32 -#define PIX_LICENCE_BYTES 128 - typedef const char* (*pix_license_generate_version_func)(); typedef int (*pix_license_generate_func)(const unsigned char*, int, unsigned char*, int); -void licenseGenerate() +bool licenseGenerate(const unsigned char* hardware_info, unsigned char* license_info) { - unsigned char hardware_info[PIX_HARDWARE_INFO_BYTES] = { }; - unsigned char license_info[PIX_LICENCE_BYTES] = { 0 }; - // 获取当前路径 wchar_t currentPath[MAX_PATH]; GetCurrentDirectoryW(MAX_PATH, currentPath); @@ -25,8 +19,7 @@ void licenseGenerate() HINSTANCE hDLL = LoadLibraryW(dllPath.c_str()); if (hDLL == NULL) { std::cerr << "Failed to load DLL. Error code: " << GetLastError() << std::endl; - FreeLibrary(hDLL); - return; + return false; } pix_license_generate_version_func pix_license_generate_version = (pix_license_generate_version_func)GetProcAddress(hDLL, "pix_license_generate_version"); @@ -35,23 +28,25 @@ void licenseGenerate() if (pix_license_generate_version == NULL || pix_license_generate == NULL) { std::cerr << "Failed to find one or more functions." << std::endl; FreeLibrary(hDLL); - return; + return false; } printf("pix_license_generate_version is %s\n", pix_license_generate_version()); printf("Hardware info:"); for (int j = 0; j < PIX_HARDWARE_INFO_BYTES; ++j) { - hardware_info[j] = j; + //hardware_info[j] = j; printf("0x%02x, ", hardware_info[j]); } printf("\n"); // return 限制不调用 pix_license_generate, 防止减少license个数 - return; + return false; int ret = pix_license_generate(hardware_info, PIX_HARDWARE_INFO_BYTES, license_info, PIX_LICENCE_BYTES); if (ret != SDK_CODE_OK) { printf("Fail to generate license with %d\n", ret); + FreeLibrary(hDLL); + return false; } else { printf("License is\n"); @@ -62,7 +57,6 @@ void licenseGenerate() } FreeLibrary(hDLL); - - return; + return true; } diff --git a/FactoryTestTool/SourceCode/LicenseGenerate/LicenseGenerate.h b/FactoryTestTool/SourceCode/LicenseGenerate/LicenseGenerate.h index 80efc77..5a90c7d 100644 --- a/FactoryTestTool/SourceCode/LicenseGenerate/LicenseGenerate.h +++ b/FactoryTestTool/SourceCode/LicenseGenerate/LicenseGenerate.h @@ -10,6 +10,9 @@ #include #include "p_code.h" -void licenseGenerate(); +#define PIX_HARDWARE_INFO_BYTES 32 +#define PIX_LICENCE_BYTES 128 + +bool licenseGenerate(const unsigned char* hardware_info, unsigned char* license_info); #endif diff --git a/FactoryTestTool/SourceCode/Media/VideoDecoder/FFmpegDecoder.cpp b/FactoryTestTool/SourceCode/Media/VideoDecoder/FFmpegDecoder.cpp index d323d06..63a0b82 100644 --- a/FactoryTestTool/SourceCode/Media/VideoDecoder/FFmpegDecoder.cpp +++ b/FactoryTestTool/SourceCode/Media/VideoDecoder/FFmpegDecoder.cpp @@ -1,255 +1,3 @@ -// FFmpegDecoder.cpp -//#include "FFmpegDecoder.h" -// -//FFmpegDecoder::FFmpegDecoder(QObject* parent) : -// QThread(parent), -// videoLabel(nullptr), -// abort(false), -// restart(false), -// formatContext(nullptr), -// codecContext(nullptr), -// frame(nullptr), -// packet(nullptr), -// swsContext(nullptr), -// videoStreamIndex(-1) // 初始化成员变量 -//{ -// av_log_set_level(AV_LOG_QUIET); // 设置日志级别为安静模式 -// avformat_network_init(); // 初始化网络 -//} -// -//FFmpegDecoder::~FFmpegDecoder() -//{ -// mutex.lock(); -// abort = true; -// condition.wakeOne(); -// mutex.unlock(); -// wait(); -// if (codecContext) { -// avcodec_free_context(&codecContext); -// } -// if (frame) { -// av_frame_free(&frame); -// } -// if (packet) { -// av_packet_free(&packet); -// } -// if (swsContext) { -// sws_freeContext(swsContext); -// } -// if (formatContext) { -// avformat_close_input(&formatContext); -// } -// avformat_network_deinit(); // 反初始化网络 -//} -// -//void FFmpegDecoder::initialize() -//{ -// // 初始化FFmpeg库 -// avformat_network_init(); -//} -// -//void FFmpegDecoder::decodeFile(const QString& filePath, QLabel* videoLabel) -//{ -// QMutexLocker locker(&mutex); -// this->filePath = filePath; -// this->videoLabel = videoLabel; -// if (!isRunning()) { -// //start(LowPriority); -// start(NormalPriority); -// } -// restart = true; -// condition.wakeOne(); -//} -// -//void FFmpegDecoder::run() -//{ -// QFile file(filePath); -// qint64 fileSize = 0; -// -// for (;;) { -// mutex.lock(); -// while (!restart && !abort) { -// condition.wait(&mutex); -// } -// if (abort) { -// mutex.unlock(); -// break; -// } -// restart = false; -// QLabel* currentVideoLabel = videoLabel; -// QSize labelSize = currentVideoLabel->size(); -// mutex.unlock(); -// qDebug() << "Video label size: Width =" << labelSize.width() << ", Height =" << labelSize.height(); -// -// if (!file.open(QIODevice::ReadOnly)) { -// qWarning() << "Failed to open file:" << filePath; -// continue; -// } -// // 从上次处理的位置开始读取 -// file.seek(fileSize); -// -// formatContext = nullptr; -// codecContext = nullptr; -// frame = nullptr; -// packet = nullptr; -// -// // 通过 FFmpeg 初始化格式上下文和解码器 -// if (avformat_open_input(&formatContext, filePath.toStdString().c_str(), nullptr, nullptr) != 0) { -// qWarning() << "Failed to open file with FFmpeg:" << filePath; -// file.close(); -// continue; -// } -// -// if (avformat_find_stream_info(formatContext, nullptr) < 0) { -// qWarning() << "Failed to retrieve stream info"; -// avformat_close_input(&formatContext); -// file.close(); -// continue; -// } -// -// videoStreamIndex = -1; -// for (unsigned int i = 0; i < formatContext->nb_streams; ++i) { -// if (formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { -// videoStreamIndex = i; -// break; -// } -// } -// if (videoStreamIndex == -1) { -// qWarning() << "No video stream found"; -// avformat_close_input(&formatContext); -// file.close(); -// continue; -// } -// -// AVCodecParameters* codecParameters = formatContext->streams[videoStreamIndex]->codecpar; -// const AVCodec* codec = avcodec_find_decoder(codecParameters->codec_id); -// if (!codec) { -// qWarning() << "Unsupported codec"; -// avformat_close_input(&formatContext); -// file.close(); -// continue; -// } -// -// codecContext = avcodec_alloc_context3(codec); -// if (!codecContext) { -// qWarning() << "Failed to allocate codec context"; -// avformat_close_input(&formatContext); -// file.close(); -// continue; -// } -// -// if (avcodec_parameters_to_context(codecContext, codecParameters) < 0) { -// qWarning() << "Failed to copy codec parameters to context"; -// avcodec_free_context(&codecContext); -// avformat_close_input(&formatContext); -// file.close(); -// continue; -// } -// -// if (avcodec_open2(codecContext, codec, nullptr) < 0) { -// qWarning() << "Failed to open codec"; -// avcodec_free_context(&codecContext); -// avformat_close_input(&formatContext); -// file.close(); -// continue; -// } -// -// frame = av_frame_alloc(); -// packet = av_packet_alloc(); -// -// // 主解码循环 -// while (!abort) { -// qint64 currentFileSize = file.size(); -// if (currentFileSize > fileSize) { -// fileSize = currentFileSize; -// file.seek(fileSize); // 设置文件读取位置到末尾 -// -// // 读取并处理数据包 -// while (av_read_frame(formatContext, packet) >= 0) { -// if (packet->stream_index == videoStreamIndex) { -// int ret = avcodec_send_packet(codecContext, packet); -// if (ret < 0) { -// qWarning() << "Error sending packet for decoding"; -// av_packet_unref(packet); -// continue; -// } -// while (ret >= 0) { -// ret = avcodec_receive_frame(codecContext, frame); -// if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { -// qWarning() << "----------- break"; -// av_packet_unref(packet); -// continue; -// //break; -// } -// else if (ret < 0) { -// qWarning() << "Error during decoding"; -// break; -// } -// -// /*mutex.lock(); -// QSize labelSize = currentVideoLabel->size(); -// mutex.unlock();*/ -// //qDebug() << "Video label size: Width =" << labelSize.width() << ", Height =" << labelSize.height(); -// QImage img = avFrameToQImage(frame); -// QImage scaledImage = img.scaled(labelSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); -// currentVideoLabel->setPixmap(QPixmap::fromImage(scaledImage)); -// //currentVideoLabel->setPixmap(QPixmap::fromImage(img)); -// QThread::msleep(10); // Simulate 25 FPS frame rate -// } -// } -// av_packet_unref(packet); -// } -// } -// -// mutex.lock(); -// if (restart) { -// restart = false; -// mutex.unlock(); -// break; -// } -// mutex.unlock(); -// } -// -// avcodec_free_context(&codecContext); -// avformat_close_input(&formatContext); -// av_frame_free(&frame); -// av_packet_free(&packet); -// file.close(); -// sws_freeContext(swsContext); -// -// mutex.lock(); -// if (!restart) { -// condition.wait(&mutex); -// } -// mutex.unlock(); -// } -//} -// -// -//QImage FFmpegDecoder::avFrameToQImage(AVFrame* frame) -//{ -// int width = frame->width; -// int height = frame->height; -// AVPixelFormat pixFmt = (AVPixelFormat)frame->format; -// -// SwsContext* swsCtx = sws_getContext(width, height, pixFmt, width, height, AV_PIX_FMT_RGB24, SWS_BILINEAR, nullptr, nullptr, nullptr); -// if (!swsCtx) { -// qWarning() << "Failed to initialize the conversion context"; -// return QImage(); -// } -// -// QImage img(width, height, QImage::Format_RGB888); -// uint8_t* dest[4] = { img.bits(), nullptr, nullptr, nullptr }; -// int destLinesize[4] = { img.bytesPerLine(), 0, 0, 0 }; -// -// sws_scale(swsCtx, frame->data, frame->linesize, 0, height, dest, destLinesize); -// sws_freeContext(swsCtx); -// -// return img; -//} -// -// - // FFmpegDecoder.cpp #include "FFmpegDecoder.h" @@ -267,7 +15,7 @@ FFmpegDecoder::FFmpegDecoder(QObject* parent) : { av_log_set_level(AV_LOG_QUIET); // 设置日志级别为安静模式 avformat_network_init(); // 初始化网络 - qDebug() << "FFmpegDecoder created"; + //qDebug() << "FFmpegDecoder created"; } FFmpegDecoder::~FFmpegDecoder() @@ -285,7 +33,6 @@ FFmpegDecoder::~FFmpegDecoder() void FFmpegDecoder::initialize() { - qDebug() << "Initializing FFmpeg library"; // 初始化FFmpeg库 avformat_network_init(); } @@ -328,15 +75,12 @@ void FFmpegDecoder::run() qWarning() << "Failed to open file:" << filePath; continue; } - if (!initializeFFmpeg(filePath)) { qDebug() << "Failed to initialize FFmpeg for file:" << filePath; cleanup(); file.close(); continue; } - - // 主解码循环 while (!abort) { qint64 currentFileSize = file.size(); if (currentFileSize > fileSize) { diff --git a/FactoryTestTool/SourceCode/Network/ClientHandler.cpp b/FactoryTestTool/SourceCode/Network/ClientHandler.cpp index 7aeb43f..759efd9 100644 --- a/FactoryTestTool/SourceCode/Network/ClientHandler.cpp +++ b/FactoryTestTool/SourceCode/Network/ClientHandler.cpp @@ -1,6 +1,8 @@ // ClientHandler.cpp #include "ClientHandler.h" -#include "LicenseConfirmWindow.h" +#include "../LicenseGenerate/LicenseConfirmWindow.h" +#include "ImageEnrollWindow.h" +#include "PasswordEnrollWindow.h" ClientHandler::ClientHandler(QTcpSocket* socket, QJsonArray frontBoardOneClickTest, QJsonArray frontBoardTest, QJsonArray frontBoardFuncConfig, QJsonArray frontBoardDevInfoJson, QJsonArray frontBoardLicenseJson, QJsonArray backBoardDevInfoJson, @@ -14,14 +16,13 @@ ClientHandler::ClientHandler(QTcpSocket* socket, QJsonArray frontBoardOneClickTe isManualSend(false), isSingleSend(false), isClickedSend(false), size(0), isFirstDataReceived(true), processDataFunction(nullptr), isDataStuck(false), dataProcessingActive(false), isRecvVideoData(false), - currentFrontBoardIndex(0), // 初始化为0 + currentFrontBoardIndex(0), currentBackBoardIndex(0) { connect(socket, &QTcpSocket::readyRead, this, &ClientHandler::onDataReceived); connect(socket, &QTcpSocket::disconnected, this, &ClientHandler::onDisconnected); qint64 bufferSize = socket->socketOption(QAbstractSocket::ReceiveBufferSizeSocketOption).toLongLong(); - qDebug() << "------Receive buffer size:" << bufferSize; /*connect(socket, QOverload::of(&QTcpSocket::error), this, &ClientHandler::onSocketError);*/ @@ -88,14 +89,14 @@ QString ClientHandler::getCurrentFuncItemData() const QString ClientHandler::getCurrentItemLable() const { QString lable = currentItem.value("lable").toString(); - qDebug() << "Getting current item lable:" << lable; + //qDebug() << "Getting current item lable:" << lable; return lable; // 返回当前项的 "data" 字段 } QString ClientHandler::getCurrentFuncItemLable() const { QString lable = currentFuncItem.value("lable").toString(); - qDebug() << "Getting current funcItem lable:" << lable; + //qDebug() << "Getting current funcItem lable:" << lable; return lable; // 返回当前项的 "data" 字段 } @@ -131,8 +132,11 @@ void ClientHandler::sendJsonItem(const QJsonArray& jsonArray, int itemIndex, con QMutexLocker locker(&mutex); isSingleSend = true; isClickedSend = true; - if (itemType == "getVideo") { - isRecvVideoData = 1; + if (itemType == "handleVideo") { + if(itemIndex == 0) + isRecvVideoData = 1; + else + isRecvVideoData = 0; startReadVideoDataTimer(); } qDebug() << "itemIndex:" << itemIndex; @@ -177,15 +181,16 @@ void ClientHandler::sendGetDevInfoItem(int itemIndex) } // 发送取图按键 -void ClientHandler::sendGetPicItem(int itemIndex) +void ClientHandler::sendGetPicItem(int itemIndex, int lastClickedGetVideoCamIndex) { - sendJsonItem(getPicJson, itemIndex, "", "getPic"); + sendJsonItem(getPicJson, itemIndex, QString::number(lastClickedGetVideoCamIndex), "getPic"); } // 发送拉视频按键 void ClientHandler::sendGetVideoItem(int itemIndex, int video_flag) { - sendJsonItem(getVideoJson, itemIndex, "", "getVideo"); + qDebug() << "sendGetVideoItem itemIndex:" << itemIndex; + sendJsonItem(getVideoJson, itemIndex, "", "handleVideo"); } // 发送License处理按键 @@ -195,16 +200,12 @@ void ClientHandler::sendLicenseItem(int itemIndex) qDebug() << "Invalid itemIndex"; return; } - QJsonObject item = frontBoardLicenseJson[itemIndex].toObject(); QString label = item["lable"].toString(); - - if (label == "write_license" || label == "get_license") { + if (label == "write_license") { LicenseConfirmWindow dialog("你确定要发送此授权项吗?"); - int result = dialog.exec(); - - if (result == QDialog::Accepted) { - sendJsonItem(frontBoardLicenseJson, itemIndex, "", "License"); + if (dialog.exec() == QDialog::Accepted) { + sendJsonItem(frontBoardLicenseJson, itemIndex, "", "License"); } } else { @@ -222,7 +223,43 @@ void ClientHandler::sendFuncItem(int itemIndex, const QString text) // 发送单独一个测试配置 JSON 项目 void ClientHandler::sendItem(int itemIndex) { - sendJsonItem(frontBoardTest, itemIndex, "", "test"); + QString text = ""; + QJsonObject currentItem = frontBoardTest[itemIndex].toObject(); + if (currentItem.contains("cmd") && currentItem["cmd"].toString() == "IMG_ENROLL") { + ImageEnrollWindow dialog; + if (dialog.exec() == QDialog::Accepted) { + text = dialog.getFilePath(); + QByteArray imageData = dialog.getImageData(); + if (!imageData.isEmpty()) { + text = QString::fromUtf8(imageData); + } + } + else { + return; + } + } + else if (currentItem.contains("cmd") && currentItem["cmd"].toString() == "DEL_USER") { + DelUserWindow dialog; + if (dialog.exec() == QDialog::Accepted) { + QString userInput = dialog.getUserInput(); + if (!userInput.isEmpty() && currentItem.contains("val")) { + text = userInput; + } + } + else { + return; + } + } + else if (currentItem.contains("cmd") && currentItem["cmd"].toString() == "PASSWD_ENROLL") { + PasswordEnrollWindow dialog; + if (dialog.exec() == QDialog::Accepted) { + text = dialog.getPassword(); + } + else { + return; + } + } + sendJsonItem(frontBoardTest, itemIndex, text, "test"); } void ClientHandler::sendDevInfoJsonItem(const QJsonObject& jsonItem, int itemIndex) @@ -406,6 +443,10 @@ void ClientHandler::setThreadPriority(QThread::Priority priority) { void ClientHandler::onDataReceived() { // 接收其他数据 添加区分 视频与其他数据 的标志位 + qDebug() << "isRecvVideoData:" << isRecvVideoData; + qDebug() << "isPowerOnSend:" << isPowerOnSend; + qDebug() << "isClickedSend:" << isClickedSend; + qDebug() << "isSingleSend:" << isSingleSend; if (!isRecvVideoData && (isPowerOnSend || isClickedSend || (isSingleSend && (currentItemIndex < frontBoardTest.size())))) { QByteArray allData; @@ -425,7 +466,8 @@ void ClientHandler::onDataReceived() emit startTimeout(0); } if (!allData.isEmpty()) { - emit dataReceived(getClientAddress(), allData, 0xFF, currentItemIndex, currentFuncItemIndex, "", ""); + //emit dataReceived(getClientAddress(), allData, 0xFF, currentItemIndex, currentFuncItemIndex, "", ""); + emit dataReceived(getClientAddress(), allData, 0xFF, currentItemIndex, currentFuncItemIndex, getCurrentItemLable(), ""); if (!isSingleSend && !isPowerOnSend) { currentItemIndex ++; itemsProcessedCount ++; @@ -458,10 +500,13 @@ void ClientHandler::onDataReceived() //qDebug() << "" << __FUNCTION__ << "------> itemsProcessedCount :" << itemsProcessedCount; } // 接收视频流数据 isRecvVideoData 置 0 - else if (!dataProcessingActive) { + else if (isRecvVideoData && (!dataProcessingActive)) { dataProcessingActive = true; QTimer::singleShot(0, this, &ClientHandler::processPendingData); } + else { + socket->readAll(); + } } void ClientHandler::processPendingData() diff --git a/FactoryTestTool/SourceCode/Network/ClientHandler.h b/FactoryTestTool/SourceCode/Network/ClientHandler.h index 8a3b1b3..ddbaa2a 100644 --- a/FactoryTestTool/SourceCode/Network/ClientHandler.h +++ b/FactoryTestTool/SourceCode/Network/ClientHandler.h @@ -50,7 +50,7 @@ public: // 发送获取设备信息按键 void sendGetDevInfoItem(int itemIndex); // 发送取图按键 - void sendGetPicItem(int itemIndex); + void sendGetPicItem(int itemIndex, int lastClickedGetVideoCamIndex); // 发送拉视频流按键 void sendGetVideoItem(int itemIndex, int video_flag); // 发送License处理按键 diff --git a/FactoryTestTool/SourceCode/Network/ImageEnrollWindow.h b/FactoryTestTool/SourceCode/Network/ImageEnrollWindow.h new file mode 100644 index 0000000..c55641c --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/ImageEnrollWindow.h @@ -0,0 +1,80 @@ +#ifndef IMAGEENROLLWINDOW_H +#define IMAGEENROLLWINDOW_H + +#include +#include +#include +#include +#include +#include +#include + +class ImageEnrollWindow : public QDialog +{ + Q_OBJECT + +public: + explicit ImageEnrollWindow(QWidget* parent = nullptr) + : QDialog(parent) + { + QVBoxLayout* mainLayout = new QVBoxLayout(this); + + QLabel* label = new QLabel("请选择注册的图片:", this); + mainLayout->addWidget(label); + + QHBoxLayout* inputLayout = new QHBoxLayout; + filePathLineEdit = new QLineEdit(this); + filePathLineEdit->setReadOnly(true); + filePathLineEdit->setPlaceholderText("请选择PNG图片路径..."); + QPushButton* browseButton = new QPushButton("选择图片路径", this); + + inputLayout->addWidget(filePathLineEdit); + inputLayout->addWidget(browseButton); + + mainLayout->addLayout(inputLayout); + + QHBoxLayout* buttonLayout = new QHBoxLayout; + QPushButton* confirmButton = new QPushButton("确认", this); + QPushButton* cancelButton = new QPushButton("取消", this); + + buttonLayout->addWidget(confirmButton); + buttonLayout->addWidget(cancelButton); + + mainLayout->addLayout(buttonLayout); + setWindowTitle("选择图片"); + resize(460, 110); // 设置对话框的大小 + + connect(browseButton, &QPushButton::clicked, this, &ImageEnrollWindow::onBrowseButtonClicked); + connect(confirmButton, &QPushButton::clicked, this, &QDialog::accept); + connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject); + } + + QString getFilePath() const { + return filePathLineEdit->text(); + } + + QByteArray getImageData() const { + QString filePath = filePathLineEdit->text(); + if (!filePath.isEmpty()) { + QFile file(filePath); + if (file.open(QIODevice::ReadOnly)) { + QByteArray imageData = file.readAll(); + return imageData.toBase64(); + } + } + return QByteArray(); + } + +private slots: + void onBrowseButtonClicked() { + QString filePath = QFileDialog::getOpenFileName(this, tr("选择图片"), "", tr("Images (*.png *.xpm *.jpg *.bmp);;All Files (*)")); + if (!filePath.isEmpty()) { + filePathLineEdit->setText(filePath); + } + } + +private: + QLineEdit* filePathLineEdit; +}; + +#endif // IMAGEENROLLWINDOW_H diff --git a/FactoryTestTool/SourceCode/Network/PasswordEnrollWindow.h b/FactoryTestTool/SourceCode/Network/PasswordEnrollWindow.h new file mode 100644 index 0000000..2e408e4 --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/PasswordEnrollWindow.h @@ -0,0 +1,48 @@ +// PasswordEnrollWindow.h +#ifndef PASSWORDENROLLWINDOW_H +#define PASSWORDENROLLWINDOW_H + +#include +#include +#include +#include +#include +#include + +class PasswordEnrollWindow : public QDialog +{ + Q_OBJECT + +public: + explicit PasswordEnrollWindow(QWidget* parent = nullptr) : QDialog(parent) { + QVBoxLayout* layout = new QVBoxLayout(this); + + QLabel* label = new QLabel("请输入要注册的密码:", this); + layout->addWidget(label); + + passwordLineEdit = new QLineEdit(this); + layout->addWidget(passwordLineEdit); + + QHBoxLayout* buttonLayout = new QHBoxLayout; + QPushButton* okButton = new QPushButton("确认", this); + QPushButton* cancelButton = new QPushButton("取消", this); + buttonLayout->addWidget(okButton); + buttonLayout->addWidget(cancelButton); + + layout->addLayout(buttonLayout); + setWindowTitle("输入密码"); + resize(300, 110); // 设置对话框的大小 + + connect(okButton, &QPushButton::clicked, this, &QDialog::accept); + connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject); + } + + QString getPassword() const { + return passwordLineEdit->text(); + } + +private: + QLineEdit* passwordLineEdit; +}; + +#endif // PASSWORDENROLLWINDOW_H diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/abstractserver.h b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/abstractserver.h new file mode 100644 index 0000000..ca42ed2 --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/abstractserver.h @@ -0,0 +1,88 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef QMDNSENGINE_ABSTRACTSERVER_H +#define QMDNSENGINE_ABSTRACTSERVER_H + +#include + +#include "qmdnsengine_export.h" + +namespace QMdnsEngine +{ + +class Message; + +/** + * @brief Base class for sending and receiving DNS messages + * + * Many of the other classes in this library require the ability to send and + * receive DNS messages. By having them use this base class, they become far + * easier to test. Any class derived from this one that implements the pure + * virtual methods can be used for sending and receiving DNS messages. + */ +class QMDNSENGINE_EXPORT AbstractServer : public QObject +{ + Q_OBJECT + +public: + + /** + * @brief Abstract constructor + */ + explicit AbstractServer(QObject *parent = 0); + + /** + * @brief Send a message to its provided destination + * + * The message should be sent over the IP protocol specified in the + * message and to the target address and port specified in the message. + */ + virtual void sendMessage(const Message &message) = 0; + + /** + * @brief Send a message to the multicast address on each interface + * + * The message should be sent over both IPv4 and IPv6 on all interfaces. + */ + virtual void sendMessageToAll(const Message &message) = 0; + +Q_SIGNALS: + + /** + * @brief Indicate that a DNS message was received + * @param message newly received message + */ + void messageReceived(const Message &message); + + /** + * @brief Indicate that an error has occurred + * @param message brief description of the error + */ + void error(const QString &message); +}; + +} + +#endif // QMDNSENGINE_ABSTRACTSERVER_H diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/bitmap.h b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/bitmap.h new file mode 100644 index 0000000..460f1c7 --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/bitmap.h @@ -0,0 +1,100 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef QMDNSENGINE_BITMAP_H +#define QMDNSENGINE_BITMAP_H + +#include "qmdnsengine_export.h" + +namespace QMdnsEngine +{ + +class QMDNSENGINE_EXPORT BitmapPrivate; + +/** + * @brief 256-bit bitmap + * + * Bitmaps are used in QMdnsEngine::NSEC records to indicate which records are + * available. Bitmaps in mDNS records use only the first block (block 0). + */ +class QMDNSENGINE_EXPORT Bitmap +{ +public: + + /** + * @brief Create an empty bitmap + */ + Bitmap(); + + /** + * @brief Create a copy of an existing bitmap + */ + Bitmap(const Bitmap &other); + + /** + * @brief Assignment operator + */ + Bitmap &operator=(const Bitmap &other); + + /** + * @brief Equality operator + */ + bool operator==(const Bitmap &other); + + /** + * @brief Destroy the bitmap + */ + virtual ~Bitmap(); + + /** + * @brief Retrieve the length of the block in bytes + * + * This method indicates how many bytes are pointed to by the data() + * method. + */ + quint8 length() const; + + /** + * @brief Retrieve a pointer to the underlying data in the bitmap + * + * Use the length() method to determine how many bytes contain valid data. + */ + const quint8 *data() const; + + /** + * @brief Set the data to be stored in the bitmap + * + * The length parameter indicates how many bytes of data are valid. The + * actual bytes are copied to the bitmap. + */ + void setData(quint8 length, const quint8 *data); + +private: + + BitmapPrivate *const d; +}; + +} + +#endif // QMDNSENGINE_BITMAP_H diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/browser.h b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/browser.h new file mode 100644 index 0000000..7cecee3 --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/browser.h @@ -0,0 +1,122 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef QMDNSENGINE_BROWSER_H +#define QMDNSENGINE_BROWSER_H + +#include +#include + +#include "qmdnsengine_export.h" + +namespace QMdnsEngine +{ + +class AbstractServer; +class Cache; +class Service; + +class QMDNSENGINE_EXPORT BrowserPrivate; + +/** + * @brief %Browser for local services + * + * This class provides a simple way to discover services on the local network. + * A cache may be provided in the constructor to store records for future + * queries. + * + * To browse for services of any type: + * + * @code + * QMdnsEngine::Browser browser(&server, QMdnsEngine::MdnsBrowseType); + * @endcode + * + * To browse for services of a specific type: + * + * @code + * QMdnsEngine::Browser browser(&server, "_http._tcp.local."); + * @endcode + * + * When a service is found, the serviceAdded() signal is emitted: + * + * @code + * connect(&browser, &QMdnsEngine::Browser::serviceAdded, [](const QMdnsEngine::Service &service) { + * qDebug() << "Service added:" << service.name(); + * }); + * @endcode + * + * The serviceUpdated() and serviceRemoved() signals are emitted when services + * are updated (their properties change) or are removed, respectively. + */ +class QMDNSENGINE_EXPORT Browser : public QObject +{ + Q_OBJECT + +public: + + /** + * @brief Create a new browser instance + * @param server server to use for receiving and sending mDNS messages + * @param type service type to browse for + * @param cache DNS cache to use or null to create one + * @param parent QObject + */ + Browser(AbstractServer *server, const QByteArray &type, Cache *cache = 0, QObject *parent = 0); + +Q_SIGNALS: + + /** + * @brief Indicate that a new service has been added + * + * This signal is emitted when the PTR and SRV records for a service are + * received. If TXT records are received later, the serviceUpdated() + * signal will be emitted. + */ + void serviceAdded(const Service &service); + + /** + * @brief Indicate that the specified service was updated + * + * This signal is emitted when the SRV record for a service (identified by + * its name and type) or a TXT record has changed. + */ + void serviceUpdated(const Service &service); + + /** + * @brief Indicate that the specified service was removed + * + * This signal is emitted when an essential record (PTR or SRV) is + * expiring from the cache. This will also occur when an updated PTR or + * SRV record is received with a TTL of 0. + */ + void serviceRemoved(const Service &service); + +private: + + BrowserPrivate *const d; +}; + +} + +#endif // QMDNSENGINE_BROWSER_H diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/cache.h b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/cache.h new file mode 100644 index 0000000..cecfddd --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/cache.h @@ -0,0 +1,132 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef QMDNSENGINE_CACHE_H +#define QMDNSENGINE_CACHE_H + +#include +#include + +#include "qmdnsengine_export.h" + +namespace QMdnsEngine +{ + +class Record; + +class QMDNSENGINE_EXPORT CachePrivate; + +/** + * @brief %Cache for DNS records + * + * Records are added to the cache using the addRecord() method which are then + * stored in the cache until they are considered to have expired, at which + * point they are purged. The shouldQuery() signal is used to indicate when a + * record is approaching expiry and the recordExpired() signal indicates when + * a record has expired (at which point it is removed). + * + * The cache can be queried to retrieve one or more records matching a given + * type. For example, to retrieve all TXT records that match a given name: + * + * @code + * Cache cache; + * + * QList records; + * cache.lookupRecords("My Service._http._tcp.local.", QMdnsEngine::TXT, records); + * + * for (const QMdnsEngine::Record &record : records) { + * qDebug() << "Record:" << record.name(); + * } + * @endcode + * + * Alternatively, lookupRecord() can be used to find a single record. + */ +class QMDNSENGINE_EXPORT Cache : public QObject +{ + Q_OBJECT + +public: + + /** + * @brief Create an empty cache. + */ + explicit Cache(QObject *parent = 0); + + /** + * @brief Add a record to the cache + * @param record add this record to the cache + * + * The TTL for the record will be added to the current time to calculate + * when the record expires. Existing records of the same name and type + * will be replaced, resetting their expiration. + */ + void addRecord(const Record &record); + + /** + * @brief Retrieve a single record from the cache + * @param name name of record to retrieve or null for any + * @param type type of record to retrieve or ANY for all types + * @param record storage for the record retrieved + * @return true if a record was retrieved + * + * Some record types allow multiple records to be stored with identical + * names and types. This method will only retrieve the first matching + * record. Use lookupRecords() to obtain all of the records. + */ + bool lookupRecord(const QByteArray &name, quint16 type, Record &record) const; + + /** + * @brief Retrieve multiple records from the cache + * @param name name of records to retrieve or null for any + * @param type type of records to retrieve or ANY for all types + * @param records storage for the records retrieved + * @return true if records were retrieved + */ + bool lookupRecords(const QByteArray &name, quint16 type, QList &records) const; + +Q_SIGNALS: + + /** + * @brief Indicate that a record will expire soon and a new query should be issued + * @param record reference to the record that will soon expire + * + * This signal is emitted when a record reaches approximately 50%, 85%, + * 90%, and 95% of its lifetime. + */ + void shouldQuery(const Record &record); + + /** + * @brief Indicate that the specified record expired + * @param record reference to the record that has expired + */ + void recordExpired(const Record &record); + +private: + + CachePrivate *const d; +}; + +} + +#endif // QMDNSENGINE_CACHE_H diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/dns.h b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/dns.h new file mode 100644 index 0000000..f19d275 --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/dns.h @@ -0,0 +1,123 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef QMDNSENGINE_DNS_H +#define QMDNSENGINE_DNS_H + +#include +#include + +#include "qmdnsengine_export.h" + +namespace QMdnsEngine +{ + +class Message; +class Record; + +enum { + /// IPv4 address record + A = 1, + /// IPv6 address record + AAAA = 28, + /// Wildcard for cache lookups + ANY = 255, + /// List of records + NSEC = 47, + /// Pointer to hostname + PTR = 12, + /// %Service information + SRV = 33, + /// Arbitrary metadata + TXT = 16 +}; + +/** + * @brief Parse a name from a raw DNS packet + * @param packet raw DNS packet data + * @param offset offset into the packet where the name begins + * @param name reference to QByteArray to store the name in + * @return true if no errors occurred + * + * The offset will be incremented by the number of bytes read. Name + * compression requires access to the contents of the packet. + */ +QMDNSENGINE_EXPORT bool parseName(const QByteArray &packet, quint16 &offset, QByteArray &name); + +/** + * @brief Write a name to a raw DNS packet + * @param packet raw DNS packet to write to + * @param offset offset to update with the number of bytes written + * @param name name to write to the packet + * @param nameMap map of names already written to their offsets + * + * The offset will be incremented by the number of bytes read. The name map + * will be updated with offsets of any names written so that it can be passed + * to future invocations of this function. + */ +QMDNSENGINE_EXPORT void writeName(QByteArray &packet, quint16 &offset, const QByteArray &name, QMap &nameMap); + +/** + * @brief Parse a record from a raw DNS packet + * @param packet raw DNS packet data + * @param offset offset into the packet where the record begins + * @param record reference to Record to populate + * @return true if no errors occurred + */ +QMDNSENGINE_EXPORT bool parseRecord(const QByteArray &packet, quint16 &offset, Record &record); + +/** + * @brief Write a record to a raw DNS packet + * @param packet raw DNS packet to write to + * @param offset offset to update with the number of bytes written + * @param record record to write to the packet + * @param nameMap map of names already written to their offsets + */ +QMDNSENGINE_EXPORT void writeRecord(QByteArray &packet, quint16 &offset, Record &record, QMap &nameMap); + +/** + * @brief Populate a Message with data from a raw DNS packet + * @param packet raw DNS packet data + * @param message reference to Message to populate + * @return true if no errors occurred + */ +QMDNSENGINE_EXPORT bool fromPacket(const QByteArray &packet, Message &message); + +/** + * @brief Create a raw DNS packet from a Message + * @param message Message to create the packet from + * @param packet storage for raw DNS packet + */ +QMDNSENGINE_EXPORT void toPacket(const Message &message, QByteArray &packet); + +/** + * @brief Retrieve the string representation of a DNS type + * @param type integer type + * @return human-readable name for the type + */ +QMDNSENGINE_EXPORT QString typeName(quint16 type); + +} + +#endif // QMDNSENGINE_DNS_H diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/hostname.h b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/hostname.h new file mode 100644 index 0000000..fe3c79b --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/hostname.h @@ -0,0 +1,95 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef QMDNSENGINE_HOSTNAME_H +#define QMDNSENGINE_HOSTNAME_H + +#include + +#include "qmdnsengine_export.h" + +namespace QMdnsEngine +{ + +class AbstractServer; + +class QMDNSENGINE_EXPORT HostnamePrivate; + +/** + * @brief %Hostname reserved for exclusive use + * + * In order to provide services on the local network, a unique hostname must + * be used. This class asserts a hostname (by first confirming that it is not + * in use) and then responds to A and AAAA queries for the hostname. + * + * @code + * QMdnsEngine::Hostname hostname(&server); + * + * connect(&hostname, &QMdnsEngine::Hostname::hostnameChanged, [](const QByteArray &hostname) { + * qDebug() << "New hostname:" << hostname; + * }); + * @endcode + */ +class QMDNSENGINE_EXPORT Hostname : public QObject +{ + Q_OBJECT + +public: + + /** + * @brief Create a new hostname + */ + Hostname(AbstractServer *server, QObject *parent = 0); + + /** + * @brief Determine if a hostname has been registered + * + * A hostname is not considered registered until a probe for the desired + * name has been completed and no matching records were received. + */ + bool isRegistered() const; + + /** + * @brief Retrieve the current hostname + * + * This value is only valid when isRegistered() returns true. + */ + QByteArray hostname() const; + +Q_SIGNALS: + + /** + * @brief Indicate that the current hostname has changed + * @param hostname new hostname + */ + void hostnameChanged(const QByteArray &hostname); + +private: + + HostnamePrivate *const d; +}; + +} + +#endif // QMDNSENGINE_HOSTNAME_H diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/mdns.h b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/mdns.h new file mode 100644 index 0000000..339f28e --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/mdns.h @@ -0,0 +1,58 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef QMDNSENGINE_MDNS_H +#define QMDNSENGINE_MDNS_H + +#include +#include + +#include "qmdnsengine_export.h" + +namespace QMdnsEngine +{ + +/** + * @brief Standard port for mDNS + */ +QMDNSENGINE_EXPORT extern const quint16 MdnsPort; + +/** + * @brief Standard IPv4 address for mDNS + */ +QMDNSENGINE_EXPORT extern const QHostAddress MdnsIpv4Address; + +/** + * @brief Standard IPv6 address for mDNS + */ +QMDNSENGINE_EXPORT extern const QHostAddress MdnsIpv6Address; + +/** + * @brief Service type for browsing service types + */ +QMDNSENGINE_EXPORT extern const QByteArray MdnsBrowseType; + +} + +#endif // QMDNSENGINE_MDNS_H diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/message.h b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/message.h new file mode 100644 index 0000000..e89e3f5 --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/message.h @@ -0,0 +1,191 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef QMDNSENGINE_MESSAGE_H +#define QMDNSENGINE_MESSAGE_H + +#include +#include + +#include "qmdnsengine_export.h" + +namespace QMdnsEngine +{ + +class Query; +class Record; + +class QMDNSENGINE_EXPORT MessagePrivate; + +/** + * @brief DNS message + * + * A DNS message consists of a header and zero or more queries and records. + * Instances of this class are created and initialized by + * [AbstractServer](@ref QMdnsEngine::AbstractServer) when messages are + * received from the network. + * + * If a message is being constructed in reply to one received from the + * network, the reply() method can be used to simplify initialization: + * + * @code + * connect(&server, &QMdnsEngine::Server::messageReceived, [](const QMdnsEngine::Message &message) { + * QMdnsEngine::Message reply; + * reply.reply(message); + * server.sendMessage(reply); + * }); + * @endcode + */ +class QMDNSENGINE_EXPORT Message +{ +public: + + /** + * @brief Create an empty message + */ + Message(); + + /** + * @brief Create a copy of an existing message + */ + Message(const Message &other); + + /** + * @brief Assignment operator + */ + Message &operator=(const Message &other); + + /** + * @brief Destroy the message + */ + virtual ~Message(); + + /** + * @brief Retrieve the address for the message + * + * When receiving messages, this is the address that the message was + * received from. + */ + QHostAddress address() const; + + /** + * @brief Set the address for the message + * + * When sending messages, this is the address that the message will be + * sent to. QMdnsEngine::MdnsIpv4Address and QMdnsEngine::MdnsIpv6Address + * can be used for mDNS messages. + */ + void setAddress(const QHostAddress &address); + + /** + * @brief Retrieve the port for the message + * + * When receiving messages, this is the port that the message was received + * from. For traditional queries, this will be an ephemeral port. For mDNS + * queries, this will always equal QMdnsEngine::MdnsPort. + */ + quint16 port() const; + + /** + * @brief Set the port for the message + * + * When sending messages, this is the port that the message will be sent + * to. This should be set to QMdnsEngine::MdnsPort unless the message is a + * reply to a traditional DNS query. + */ + void setPort(quint16 port); + + /** + * @brief Retrieve the transaction ID for the message + * + * This is always set to 1 for mDNS messages. Traditional DNS queries may + * set this to an arbitrary integer. + */ + quint16 transactionId() const; + + /** + * @brief Set the transaction ID for the message + * + * The default transaction ID is 0. This value should not be changed + * unless responding to a traditional DNS query. + */ + void setTransactionId(quint16 transactionId); + + /** + * @brief Determine if the message is a response + */ + bool isResponse() const; + + /** + * @brief Set whether the message is a response + */ + void setResponse(bool isResponse); + + /** + * @brief Determine if the message is truncated + */ + bool isTruncated() const; + + /** + * @brief Set whether the message is truncated + */ + void setTruncated(bool isTruncated); + + /** + * @brief Retrieve a list of queries in the message + */ + QList queries() const; + + /** + * @brief Add a query to the message + */ + void addQuery(const Query &query); + + /** + * @brief Retrieve a list of records in the message + */ + QList records() const; + + /** + * @brief Add a record to the message + */ + void addRecord(const Record &record); + + /** + * @brief Reply to another message + * + * The message will be correctly initialized to respond to the other + * message. This includes setting the target address, port, and + * transaction ID. + */ + void reply(const Message &other); + +private: + + MessagePrivate *const d; +}; + +} + +#endif // QMDNSENGINE_MESSAGE_H diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/prober.h b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/prober.h new file mode 100644 index 0000000..8a7b1e9 --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/prober.h @@ -0,0 +1,88 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef QMDNSENGINE_PROBER_H +#define QMDNSENGINE_PROBER_H + +#include + +#include "qmdnsengine_export.h" + +namespace QMdnsEngine +{ + +class AbstractServer; +class Record; + +class QMDNSENGINE_EXPORT ProberPrivate; + +/** + * @brief %Prober to confirm that a record is unique + * + * Before responding to queries for a record, its uniqueness on the network + * must be confirmed. This class takes care of probing for existing records + * that match and adjusts the record's name until a unique one is found. + * + * For example, to probe for a SRV record: + * + * @code + * QMdnsEngine::Record record; + * record.setName("My Service._http._tcp.local."); + * record.setType(QMdnsEngine::SRV); + * record.setPort(1234); + * record.setTarget(hostname.hostname()); + * + * QMdnsEngine::Prober prober(&server, record); + * connect(&prober, &QMdnsEngine::Prober::nameConfirmed, [](const QByteArray &name) { + * qDebug() << "Name confirmed:" << name; + * }); + * @endcode + */ +class QMDNSENGINE_EXPORT Prober : public QObject +{ + Q_OBJECT + +public: + + /** + * @brief Create a new prober + */ + Prober(AbstractServer *server, const Record &record, QObject *parent = 0); + +Q_SIGNALS: + + /** + * @brief Indicate that the name has been confirmed unique + * @param name that was confirmed to be unique + */ + void nameConfirmed(const QByteArray &name); + +private: + + ProberPrivate *const d; +}; + +} + +#endif // QMDNSENGINE_PROBER_H diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/provider.h b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/provider.h new file mode 100644 index 0000000..7990005 --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/provider.h @@ -0,0 +1,90 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef QMDNSENGINE_PROVIDER_H +#define QMDNSENGINE_PROVIDER_H + +#include + +#include "qmdnsengine_export.h" + +namespace QMdnsEngine +{ + +class AbstractServer; +class Hostname; +class Service; + +class QMDNSENGINE_EXPORT ProviderPrivate; + +/** + * @brief %Provider for a single mDNS service + * + * This class provide a [Service](@ref QMdnsEngine::Service) on the local + * network by responding to the appropriate DNS queries. A hostname is + * required for creating the SRV record. + * + * The provider needs to be given a reference to the service through the + * update() method so that it can construct DNS records: + * + * @code + * QMdnsEngine::Service service; + * service.setType("_http._tcp.local."); + * service.setName("My Service"); + * service.setPort(1234); + * + * QMdnsEngine::Provider provider; + * provider.update(service); + * @endcode + * + * This method can also be used to update the provider's records. + */ +class QMDNSENGINE_EXPORT Provider : public QObject +{ + Q_OBJECT + +public: + + /** + * @brief Create a new service provider + */ + Provider(AbstractServer *server, Hostname *hostname, QObject *parent = 0); + + /** + * @brief Update the service with the provided information + * @param service updated service description + * + * This class will not respond to any DNS queries until the hostname is + * confirmed and this method is called. + */ + void update(const Service &service); + +private: + + ProviderPrivate *const d; +}; + +} + +#endif // QMDNSENGINE_PROVIDER_H diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/qmdnsengine_export.h b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/qmdnsengine_export.h new file mode 100644 index 0000000..03d7334 --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/qmdnsengine_export.h @@ -0,0 +1,40 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef QMDNSENGINE_EXPORT_H +#define QMDNSENGINE_EXPORT_H + +#include + +#if defined(BUILD_SHARED_LIBS) +# if defined(QMDNSENGINE_LIBRARY) +# define QMDNSENGINE_EXPORT Q_DECL_EXPORT +# else +# define QMDNSENGINE_EXPORT Q_DECL_IMPORT +# endif +#else +# define QMDNSENGINE_EXPORT +#endif + +#endif // QMDNSENGINE_EXPORT_H diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/query.h b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/query.h new file mode 100644 index 0000000..d54899b --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/query.h @@ -0,0 +1,116 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef QMDNSENGINE_QUERY_H +#define QMDNSENGINE_QUERY_H + +#include + +#include "qmdnsengine_export.h" + +namespace QMdnsEngine +{ + +class QMDNSENGINE_EXPORT QueryPrivate; + +/** + * @brief DNS query + * + * This class represents a query for a DNS record. For example, to query for + * the IPv4 address of a local host: + * + * @code + * QMdnsEngine::Query query; + * query.setName("myserver.local."); + * query.setType(QMdnsEngine::A); + * + * message.addQuery(query); + * @endcode + */ +class QMDNSENGINE_EXPORT Query +{ +public: + + /** + * @brief Create an empty query + */ + Query(); + + /** + * @brief Create a copy of an existing query + */ + Query(const Query &other); + + /** + * @brief Assignment operator + */ + Query &operator=(const Query &other); + + /** + * @brief Destroy the query + */ + virtual ~Query(); + + /** + * @brief Retrieve the name being queried + */ + QByteArray name() const; + + /** + * @brief Set the name to query + */ + void setName(const QByteArray &name); + + /** + * @brief Retrieve the type of record being queried + */ + quint16 type() const; + + /** + * @brief Set the type of record to query + * + * Constants, such as QMdnsEngine::SRV are provided for convenience. + */ + void setType(quint16 type); + + /** + * @brief Determine if a unicast response is desired + */ + bool unicastResponse() const; + + /** + * @brief Set whether a unicast response is desired + */ + void setUnicastResponse(bool unicastResponse); + +private: + + QueryPrivate *const d; +}; + +QMDNSENGINE_EXPORT QDebug operator<<(QDebug dbg, const Query &query); + +} + +#endif // QMDNSENGINE_QUERY_H diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/record.h b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/record.h new file mode 100644 index 0000000..21d36f3 --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/record.h @@ -0,0 +1,255 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef QMDNSENGINE_RECORD_H +#define QMDNSENGINE_RECORD_H + +#include +#include +#include + +#include "bitmap.h" + +#include "qmdnsengine_export.h" + +namespace QMdnsEngine +{ + +class QMDNSENGINE_EXPORT RecordPrivate; + +/** + * @brief DNS record + * + * This class maintains information for an individual record. Not all record + * types use every field. + * + * For example, to create a TXT record: + * + * @code + * QMdnsEngine::Record record; + * record.setName("My Service._http._tcp.local."); + * record.setType(QMdnsEngine::TXT); + * record.addAttribute("a", "value1"); + * record.addAttribute("b", "value2"); + * + * message.addRecord(record); + * @endcode + */ +class QMDNSENGINE_EXPORT Record +{ +public: + + /** + * @brief Create an uninitialized record + */ + Record(); + + /** + * @brief Create a copy of an existing record + */ + Record(const Record &other); + + /** + * @brief Assignment operator + */ + Record &operator=(const Record &other); + + /** + * @brief Equality operator + */ + bool operator==(const Record &other) const; + + /** + * @brief Inequality operator + */ + bool operator!=(const Record &other) const; + + /** + * @brief Destroy the record + */ + virtual ~Record(); + + /** + * @brief Retrieve the name of the record + */ + QByteArray name() const; + + /** + * @brief Set the name of the record + */ + void setName(const QByteArray &name); + + /** + * @brief Retrieve the type of the record + */ + quint16 type() const; + + /** + * @brief Set the type of the record + * + * For convenience, constants for types used by mDNS, such as + * QMdnsEngine::A or QMdnsEngine::PTR, may be used here. + */ + void setType(quint16 type); + + /** + * @brief Determine whether to replace or append to existing records + * + * If true, this record replaces all previous records of the same name and + * type rather than appending to them. + */ + bool flushCache() const; + + /** + * @brief Set whether to replace or append to existing records + */ + void setFlushCache(bool flushCache); + + /** + * @brief Retrieve the TTL (time to live) for the record + */ + quint32 ttl() const; + + /** + * @brief Set the TTL (time to live) for the record + */ + void setTtl(quint32 ttl); + + /** + * @brief Retrieve the address for the record + * + * This field is used by QMdnsEngine::A and QMdnsEngine::AAAA records. + */ + QHostAddress address() const; + + /** + * @brief Set the address for the record + */ + void setAddress(const QHostAddress &address); + + /** + * @brief Retrieve the target for the record + * + * This field is used by QMdnsEngine::PTR and QMdnsEngine::SRV records. + */ + QByteArray target() const; + + /** + * @brief Set the target for the record + */ + void setTarget(const QByteArray &target); + + /** + * @brief Retrieve the next domain name + * + * This field is used by QMdnsEngine::NSEC records. + */ + QByteArray nextDomainName() const; + + /** + * @brief Set the next domain name + */ + void setNextDomainName(const QByteArray &nextDomainName); + + /** + * @brief Retrieve the priority for the record + * + * This field is used by QMdnsEngine::SRV records. + */ + quint16 priority() const; + + /** + * @brief Set the priority for the record + * + * Unless more than one QMdnsEngine::SRV record is being sent, this field + * should be set to 0. + */ + void setPriority(quint16 priority); + + /** + * @brief Retrieve the weight of the record + * + * This field is used by QMdnsEngine::SRV records. + */ + quint16 weight() const; + + /** + * @brief Set the weight of the record + * + * Unless more than one QMdnsEngine::SRV record is being sent, this field + * should be set to 0. + */ + void setWeight(quint16 weight); + + /** + * @brief Retrieve the port for the record + * + * This field is used by QMdnsEngine::SRV records. + */ + quint16 port() const; + + /** + * @brief Set the port for the record + */ + void setPort(quint16 port); + + /** + * @brief Retrieve attributes for the record + * + * This field is used by QMdnsEngine::TXT records. + */ + QMap attributes() const; + + /** + * @brief Set attributes for the record + */ + void setAttributes(const QMap &attributes); + + /** + * @brief Add an attribute to the record + */ + void addAttribute(const QByteArray &key, const QByteArray &value); + + /** + * @brief Retrieve the bitmap for the record + * + * This field is used by QMdnsEngine::NSEC records. + */ + Bitmap bitmap() const; + + /** + * @brief Set the bitmap for the record + */ + void setBitmap(const Bitmap &bitmap); + +private: + + RecordPrivate *const d; +}; + +QMDNSENGINE_EXPORT QDebug operator<<(QDebug dbg, const Record &record); + +} + +#endif // QMDNSENGINE_RECORD_H diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/resolver.h b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/resolver.h new file mode 100644 index 0000000..b393b66 --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/resolver.h @@ -0,0 +1,88 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef QMDNSENGINE_RESOLVER_H +#define QMDNSENGINE_RESOLVER_H + +#include +#include + +#include "qmdnsengine_export.h" + +namespace QMdnsEngine +{ + +class AbstractServer; +class Cache; + +class QMDNSENGINE_EXPORT ResolverPrivate; + +/** + * @brief %Resolver for services + * + * When [Browser](@ref QMdnsEngine::Browser) indicates that a new service has + * been found, it becomes necessary to resolve the service in order to connect + * to it. This class serves that role. A [Cache](@ref QMdnsEngine::Cache) can + * optionally be provided to speed up the resolving process. + * + * For example, assuming that `record` is a SRV record: + * + * @code + * QMdnsEngine::Resolver resolver(&server, record.target()); + * connect(&resolver, &QMdnsEngine::Resolver::resolved, [](const QHostAddress &address) { + * qDebug() << "Address:" << address; + * }); + * @endcode + */ +class QMDNSENGINE_EXPORT Resolver : public QObject +{ + Q_OBJECT + +public: + + /** + * @brief Create a new resolver + */ + Resolver(AbstractServer *server, const QByteArray &name, Cache *cache = 0, QObject *parent = 0); + +Q_SIGNALS: + + /** + * @brief Indicate that the host resolved to an address + * @param address service address + * + * This signal will be emitted once for each resolved address. For + * example, if a host provides both A and AAAA records, this signal will + * be emitted twice. + */ + void resolved(const QHostAddress &address); + +private: + + ResolverPrivate *const d; +}; + +} + +#endif // QMDNSENGINE_RESOLVER_H diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/server.h b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/server.h new file mode 100644 index 0000000..e3689ec --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/server.h @@ -0,0 +1,78 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef QMDNSENGINE_SERVER_H +#define QMDNSENGINE_SERVER_H + +#include "abstractserver.h" + +#include "qmdnsengine_export.h" + +namespace QMdnsEngine +{ + +class Message; + +class QMDNSENGINE_EXPORT ServerPrivate; + +/** + * @brief mDNS server + * + * This class provides an implementation of + * [AbstractServer](@ref QMdnsEngine::AbstractServer) that uses all available + * local network adapters to send and receive mDNS messages. + * + * The class takes care of watching for the addition and removal of network + * interfaces, automatically joining multicast groups when new interfaces are + * available. + */ +class QMDNSENGINE_EXPORT Server : public AbstractServer +{ + Q_OBJECT + +public: + + /** + * @brief Create a new server + */ + explicit Server(QObject *parent = 0); + + /** + * @brief Implementation of AbstractServer::sendMessage() + */ + virtual void sendMessage(const Message &message); + + /** + * @brief Implementation of AbstractServer::sendMessageToAll() + */ + virtual void sendMessageToAll(const Message &message); + +private: + + ServerPrivate *const d; +}; + +} + +#endif // QMDNSENGINE_SERVER_H diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/service.h b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/service.h new file mode 100644 index 0000000..48d7d10 --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/include/service.h @@ -0,0 +1,156 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef QMDNSENGINE_SERVICE_H +#define QMDNSENGINE_SERVICE_H + +#include +#include +#include +#include + +#include "qmdnsengine_export.h" + +namespace QMdnsEngine +{ + +class QMDNSENGINE_EXPORT ServicePrivate; + +/** + * @brief %Service available on the local network + * + * This class contains the descriptive information necessary to represent an + * individual service made available to the local network. Instances are + * provided by [Browser](@ref QMdnsEngine::Browser) as services are + * discovered. Instances must be created and passed to + * [Provider::update()](@ref QMdnsEngine::Provider::update) to provide a + * service. + */ +class QMDNSENGINE_EXPORT Service +{ +public: + + /** + * @brief Create an uninitialized service + */ + Service(); + + /** + * @brief Create a copy of an existing service + */ + Service(const Service &other); + + /** + * @brief Assignment operator + */ + Service &operator=(const Service &other); + + /** + * @brief Equality operator + */ + bool operator==(const Service &other) const; + + /** + * @brief Inequality operator + */ + bool operator!=(const Service &other) const; + + /** + * @brief Destroy the service + */ + virtual ~Service(); + + /** + * @brief Retrieve the service type + */ + QByteArray type() const; + + /** + * @brief Set the service type + * + * For example, an HTTP service might use "_http._tcp". + */ + void setType(const QByteArray &type); + + /** + * @brief Retrieve the service name + */ + QByteArray name() const; + + /** + * @brief Set the service name + * + * This is combined with the service type and domain to form the FQDN for + * the service. + */ + void setName(const QByteArray &name); + + /** + * @brief Retrieve the hostname of the device providing the service + */ + QByteArray hostname() const; + + /** + * @brief Set the hostname of the device providing the service + */ + void setHostname(const QByteArray &hostname); + + /** + * @brief Retrieve the service port + */ + quint16 port() const; + + /** + * @brief Set the service port + */ + void setPort(quint16 port); + + /** + * @brief Retrieve the attributes for the service + * + * Boolean attributes will have null values (invoking QByteArray::isNull() + * on the value will return true). + */ + QMap attributes() const; + + /** + * @brief Set the attributes for the service + */ + void setAttributes(const QMap &attributes); + + /** + * @brief Add an attribute to the service + */ + void addAttribute(const QByteArray &key, const QByteArray &value); + +private: + + ServicePrivate *const d; +}; + +QMDNSENGINE_EXPORT QDebug operator<<(QDebug debug, const Service &service); + +} + +#endif // QMDNSENGINE_SERVICE_H diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/qmdnsengine_export.h b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/qmdnsengine_export.h new file mode 100644 index 0000000..da22671 --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/qmdnsengine_export.h @@ -0,0 +1,40 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef QMDNSENGINE_EXPORT_H +#define QMDNSENGINE_EXPORT_H + +#include + +#if defined(BUILD_SHARED_LIBS) +# if defined(QMDNSENGINE_LIBRARY) +# define QMDNSENGINE_EXPORT Q_DECL_EXPORT +# else +# define QMDNSENGINE_EXPORT Q_DECL_IMPORT +# endif +#else +# define QMDNSENGINE_EXPORT +#endif + +#endif // QMDNSENGINE_EXPORT_H diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/abstractserver.cpp b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/abstractserver.cpp new file mode 100644 index 0000000..628b42f --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/abstractserver.cpp @@ -0,0 +1,32 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "../include/abstractserver.h" + +using namespace QMdnsEngine; + +AbstractServer::AbstractServer(QObject *parent) + : QObject(parent) +{ +} diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/bitmap.cpp b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/bitmap.cpp new file mode 100644 index 0000000..f786bfb --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/bitmap.cpp @@ -0,0 +1,108 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "../include/bitmap.h" + +#include "bitmap_p.h" + +using namespace QMdnsEngine; + +BitmapPrivate::BitmapPrivate() + : length(0), + data(nullptr) +{ +} + +BitmapPrivate::~BitmapPrivate() +{ + free(); +} + +void BitmapPrivate::free() +{ + if (data) { + delete[] data; + } +} + +void BitmapPrivate::fromData(quint8 newLength, const quint8 *newData) +{ + data = new quint8[newLength]; + for (int i = 0; i < newLength; ++i) { + data[i] = newData[i]; + } + length = newLength; +} + +Bitmap::Bitmap() + : d(new BitmapPrivate) +{ +} + +Bitmap::Bitmap(const Bitmap &other) + : d(new BitmapPrivate) +{ + d->fromData(other.d->length, other.d->data); +} + +Bitmap &Bitmap::operator=(const Bitmap &other) +{ + d->free(); + d->fromData(other.d->length, other.d->data); + return *this; +} + +bool Bitmap::operator==(const Bitmap &other) +{ + if (d->length != other.d->length) { + return false; + } + for (int i = 0; i < d->length; ++i) { + if (d->data[i] != other.d->data[i]) { + return false; + } + } + return true; +} + +Bitmap::~Bitmap() +{ + delete d; +} + +quint8 Bitmap::length() const +{ + return d->length; +} + +const quint8 *Bitmap::data() const +{ + return d->data; +} + +void Bitmap::setData(quint8 length, const quint8 *data) +{ + d->free(); + d->fromData(length, data); +} diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/bitmap_p.h b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/bitmap_p.h new file mode 100644 index 0000000..2444124 --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/bitmap_p.h @@ -0,0 +1,49 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef QMDNSENGINE_BITMAP_P_H +#define QMDNSENGINE_BITMAP_P_H + +#include + +namespace QMdnsEngine +{ + +class BitmapPrivate +{ +public: + + BitmapPrivate(); + virtual ~BitmapPrivate(); + + void free(); + void fromData(quint8 newLength, const quint8 *newData); + + quint8 length; + quint8 *data; +}; + +} + +#endif // QMDNSENGINE_BITMAP_P_H diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/browser.cpp b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/browser.cpp new file mode 100644 index 0000000..2c48c89 --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/browser.cpp @@ -0,0 +1,291 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "../include/abstractserver.h" +#include "../include/browser.h" +#include "../include/cache.h" +#include "../include/dns.h" +#include "../include/mdns.h" +#include "../include/message.h" +#include "../include/query.h" +#include "../include/record.h" + +#include "browser_p.h" + +using namespace QMdnsEngine; + +BrowserPrivate::BrowserPrivate(Browser *browser, AbstractServer *server, const QByteArray &type, Cache *existingCache) + : QObject(browser), + server(server), + type(type), + cache(existingCache ? existingCache : new Cache(this)), + q(browser) +{ + connect(server, &AbstractServer::messageReceived, this, &BrowserPrivate::onMessageReceived); + connect(cache, &Cache::shouldQuery, this, &BrowserPrivate::onShouldQuery); + connect(cache, &Cache::recordExpired, this, &BrowserPrivate::onRecordExpired); + connect(&queryTimer, &QTimer::timeout, this, &BrowserPrivate::onQueryTimeout); + connect(&serviceTimer, &QTimer::timeout, this, &BrowserPrivate::onServiceTimeout); + + queryTimer.setInterval(60 * 1000); + queryTimer.setSingleShot(true); + + serviceTimer.setInterval(100); + serviceTimer.setSingleShot(true); + + // Immediately begin browsing for services + onQueryTimeout(); +} + +// TODO: multiple SRV records not supported + +bool BrowserPrivate::updateService(const QByteArray &fqName) +{ + // Split the FQDN into service name and type + int index = fqName.indexOf('.'); + QByteArray serviceName = fqName.left(index); + QByteArray serviceType = fqName.mid(index + 1); + + // Immediately return if a PTR record does not exist + Record ptrRecord; + if (!cache->lookupRecord(serviceType, PTR, ptrRecord)) { + return false; + } + + // If a SRV record is missing, query for it (by returning true) + Record srvRecord; + if (!cache->lookupRecord(fqName, SRV, srvRecord)) { + return true; + } + + Service service; + service.setName(serviceName); + service.setType(serviceType); + service.setHostname(srvRecord.target()); + service.setPort(srvRecord.port()); + + // If TXT records are available for the service, add their values + QList txtRecords; + if (cache->lookupRecords(fqName, TXT, txtRecords)) { + QMap attributes; + for (const Record &record : qAsConst(txtRecords)) { + for (auto i = record.attributes().constBegin(); + i != record.attributes().constEnd(); ++i) { + attributes.insert(i.key(), i.value()); + } + } + service.setAttributes(attributes); + } + + // If the service existed, this is an update; otherwise it is a new + // addition; emit the appropriate signal + if (!services.contains(fqName)) { + emit q->serviceAdded(service); + } else if(services.value(fqName) != service) { + emit q->serviceUpdated(service); + } + + services.insert(fqName, service); + hostnames.insert(service.hostname()); + + return false; +} + +void BrowserPrivate::onMessageReceived(const Message &message) +{ + if (!message.isResponse()) { + return; + } + + const bool any = type == MdnsBrowseType; + + // Use a set to track all services that are updated in the message to + // prevent unnecessary queries for SRV and TXT records + QSet updateNames; + const auto records = message.records(); + for (const Record &record : records) { + bool cacheRecord = false; + + switch (record.type()) { + case PTR: + if (any && record.name() == MdnsBrowseType) { + ptrTargets.insert(record.target()); + serviceTimer.start(); + cacheRecord = true; + } else if (any || record.name() == type) { + updateNames.insert(record.target()); + cacheRecord = true; + } + break; + case SRV: + case TXT: + if (any || record.name().endsWith("." + type)) { + updateNames.insert(record.name()); + cacheRecord = true; + } + break; + } + if (cacheRecord) { + cache->addRecord(record); + } + } + + // For each of the services marked to be updated, perform the update and + // make a list of all missing SRV records + QSet queryNames; + for (const QByteArray &name : qAsConst(updateNames)) { + if (updateService(name)) { + queryNames.insert(name); + } + } + + // Cache A / AAAA records after services are processed to ensure hostnames are known + for (const Record &record : records) { + bool cacheRecord = false; + + switch (record.type()) { + case A: + case AAAA: + cacheRecord = hostnames.contains(record.name()); + break; + } + if (cacheRecord) { + cache->addRecord(record); + } + } + + // Build and send a query for all of the SRV and TXT records + if (queryNames.count()) { + Message queryMessage; + for (const QByteArray &name : qAsConst(queryNames)) { + Query query; + query.setName(name); + query.setType(SRV); + queryMessage.addQuery(query); + query.setType(TXT); + queryMessage.addQuery(query); + } + server->sendMessageToAll(queryMessage); + } +} + +void BrowserPrivate::onShouldQuery(const Record &record) +{ + // Assume that all messages in the cache are still in use (by the browser) + // and attempt to renew them immediately + + Query query; + query.setName(record.name()); + query.setType(record.type()); + Message message; + message.addQuery(query); + server->sendMessageToAll(message); +} + +void BrowserPrivate::onRecordExpired(const Record &record) +{ + // If the SRV record has expired for a service, then it must be + // removed - TXT records on the other hand, cause an update + + QByteArray serviceName; + switch (record.type()) { + case SRV: + serviceName = record.name(); + break; + case TXT: + updateService(record.name()); + return; + default: + return; + } + Service service = services.value(serviceName); + if (!service.name().isNull()) { + emit q->serviceRemoved(service); + services.remove(serviceName); + updateHostnames(); + } +} + +void BrowserPrivate::onQueryTimeout() +{ + Query query; + query.setName(type); + query.setType(PTR); + Message message; + message.addQuery(query); + + // TODO: including too many records could cause problems + + // Include PTR records for the target that are already known + QList records; + if (cache->lookupRecords(query.name(), PTR, records)) { + for (const Record &record : qAsConst(records)) { + message.addRecord(record); + } + } + + server->sendMessageToAll(message); + queryTimer.start(); +} + +void BrowserPrivate::onServiceTimeout() +{ + if (ptrTargets.count()) { + Message message; + for (const QByteArray &target : qAsConst(ptrTargets)) { + + // Add a query for PTR records + Query query; + query.setName(target); + query.setType(PTR); + message.addQuery(query); + + // Include PTR records for the target that are already known + QList records; + if (cache->lookupRecords(target, PTR, records)) { + for (const Record &record : qAsConst(records)) { + message.addRecord(record); + } + } + } + + server->sendMessageToAll(message); + ptrTargets.clear(); + } +} + +void BrowserPrivate::updateHostnames() +{ + hostnames.clear(); + + for (const auto& service : services) { + hostnames.insert(service.hostname()); + } +} + +Browser::Browser(AbstractServer *server, const QByteArray &type, Cache *cache, QObject *parent) + : QObject(parent), + d(new BrowserPrivate(this, server, type, cache)) +{ +} diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/browser_p.h b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/browser_p.h new file mode 100644 index 0000000..91e6cbd --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/browser_p.h @@ -0,0 +1,83 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef QMDNSENGINE_BROWSER_P_H +#define QMDNSENGINE_BROWSER_P_H + +#include +#include +#include +#include +#include + +#include "../include/service.h" + +namespace QMdnsEngine +{ + +class AbstractServer; +class Browser; +class Cache; +class Message; +class Record; + +class BrowserPrivate : public QObject +{ + Q_OBJECT + +public: + + explicit BrowserPrivate(Browser *browser, AbstractServer *server, const QByteArray &type, Cache *existingCache); + + bool updateService(const QByteArray &fqName); + + AbstractServer *server; + QByteArray type; + + Cache *cache; + QSet ptrTargets; + QMap services; + QSet hostnames; + + QTimer queryTimer; + QTimer serviceTimer; + +private Q_SLOTS: + + void onMessageReceived(const Message &message); + void onShouldQuery(const Record &record); + void onRecordExpired(const Record &record); + + void onQueryTimeout(); + void onServiceTimeout(); + +private: + void updateHostnames(); + + Browser *const q; +}; + +} + +#endif // QMDNSENGINE_BROWSER_P_H diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/cache.cpp b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/cache.cpp new file mode 100644 index 0000000..69a4830 --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/cache.cpp @@ -0,0 +1,173 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include +#if(QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) +#include +#define USE_QRANDOMGENERATOR +#endif + +#include "../include/cache.h" +#include "../include/dns.h" + +#include "cache_p.h" + +using namespace QMdnsEngine; + +CachePrivate::CachePrivate(Cache *cache) + : QObject(cache), + q(cache) +{ + connect(&timer, &QTimer::timeout, this, &CachePrivate::onTimeout); + + timer.setSingleShot(true); +} + +void CachePrivate::onTimeout() +{ + // Loop through all of the records in the cache, emitting the appropriate + // signal when a trigger has passed, determining when the next trigger + // will occur, and removing records that have expired + QDateTime now = QDateTime::currentDateTime(); + QDateTime newNextTrigger; + + for (auto i = entries.begin(); i != entries.end();) { + + // Loop through the triggers and remove ones that have already + // passed + bool shouldQuery = false; + for (auto j = i->triggers.begin(); j != i->triggers.end();) { + if ((*j) <= now) { + shouldQuery = true; + j = i->triggers.erase(j); + } else { + break; + } + } + + // If triggers remain, determine the next earliest one; if none + // remain, the record has expired and should be removed + if (i->triggers.length()) { + if (newNextTrigger.isNull() || i->triggers.at(0) < newNextTrigger) { + newNextTrigger = i->triggers.at(0); + } + if (shouldQuery) { + emit q->shouldQuery(i->record); + } + ++i; + } else { + emit q->recordExpired(i->record); + i = entries.erase(i); + } + } + + // If newNextTrigger contains a value, it will be the time for the next + // trigger and the timer should be started again + nextTrigger = newNextTrigger; + if (!nextTrigger.isNull()) { + timer.start(now.msecsTo(nextTrigger)); + } +} + +Cache::Cache(QObject *parent) + : QObject(parent), + d(new CachePrivate(this)) +{ +} + +void Cache::addRecord(const Record &record) +{ + // If a record exists that matches, remove it from the cache; if the TTL + // is nonzero, it will be added back to the cache with updated times + for (auto i = d->entries.begin(); i != d->entries.end();) { + if ((record.flushCache() && + (*i).record.name() == record.name() && + (*i).record.type() == record.type()) || + (*i).record == record) { + + // If the TTL is set to 0, indicate that the record was removed + if (record.ttl() == 0) { + emit recordExpired((*i).record); + } + + i = d->entries.erase(i); + + // No need to continue further if the TTL was set to 0 + if (record.ttl() == 0) { + return; + } + } else { + ++i; + } + } + + // Use the current time to calculate the triggers and add a random offset + QDateTime now = QDateTime::currentDateTime(); +#ifdef USE_QRANDOMGENERATOR + qint64 random = QRandomGenerator::global()->bounded(20); +#else + qint64 random = qrand() % 20; +#endif + + QList triggers{ + now.addMSecs(record.ttl() * 500 + random), // 50% + now.addMSecs(record.ttl() * 850 + random), // 85% + now.addMSecs(record.ttl() * 900 + random), // 90% + now.addMSecs(record.ttl() * 950 + random), // 95% + now.addSecs(record.ttl()) + }; + + // Append the record and its triggers + d->entries.append({record, triggers}); + + // Check if the new record's first trigger is earlier than the next + // scheduled trigger; if so, restart the timer + if (d->nextTrigger.isNull() || triggers.at(0) < d->nextTrigger) { + d->nextTrigger = triggers.at(0); + d->timer.start(now.msecsTo(d->nextTrigger)); + } +} + +bool Cache::lookupRecord(const QByteArray &name, quint16 type, Record &record) const +{ + QList records; + if (lookupRecords(name, type, records)) { + record = records.at(0); + return true; + } + return false; +} + +bool Cache::lookupRecords(const QByteArray &name, quint16 type, QList &records) const +{ + bool recordsAdded = false; + for (const CachePrivate::Entry &entry : d->entries) { + if ((name.isNull() || entry.record.name() == name) && + (type == ANY || entry.record.type() == type)) { + records.append(entry.record); + recordsAdded = true; + } + } + return recordsAdded; +} diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/cache_p.h b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/cache_p.h new file mode 100644 index 0000000..6c71879 --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/cache_p.h @@ -0,0 +1,69 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef QMDNSENGINE_CACHE_P_H +#define QMDNSENGINE_CACHE_P_H + +#include +#include +#include +#include + +#include "../include/record.h" + +namespace QMdnsEngine +{ + +class Cache; + +class CachePrivate : public QObject +{ + Q_OBJECT + +public: + + struct Entry + { + Record record; + QList triggers; + }; + + CachePrivate(Cache *cache); + + QTimer timer; + QList entries; + QDateTime nextTrigger; + +private Q_SLOTS: + + void onTimeout(); + +private: + + Cache *const q; +}; + +} + +#endif // QMDNSENGINE_CACHE_P_H diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/dns.cpp b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/dns.cpp new file mode 100644 index 0000000..f25bcbd --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/dns.cpp @@ -0,0 +1,376 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include +#include + +#include "../include/bitmap.h" +#include "../include/dns.h" +#include "../include/message.h" +#include "../include/query.h" +#include "../include/record.h" + +namespace QMdnsEngine +{ + +template +bool parseInteger(const QByteArray &packet, quint16 &offset, T &value) +{ + if (offset + sizeof(T) > static_cast(packet.length())) { + return false; // out-of-bounds + } + value = qFromBigEndian(reinterpret_cast(packet.constData() + offset)); + offset += sizeof(T); + return true; +} + +template +void writeInteger(QByteArray &packet, quint16 &offset, T value) +{ + value = qToBigEndian(value); + packet.append(reinterpret_cast(&value), sizeof(T)); + offset += sizeof(T); +} + +bool parseName(const QByteArray &packet, quint16 &offset, QByteArray &name) +{ + quint16 offsetEnd = 0; + quint16 offsetPtr = offset; + forever { + quint8 nBytes; + if (!parseInteger(packet, offset, nBytes)) { + return false; + } + if (!nBytes) { + break; + } + switch (nBytes & 0xc0) { + case 0x00: + if (offset + nBytes > packet.length()) { + return false; // length exceeds message + } + name.append(packet.mid(offset, nBytes)); + name.append('.'); + offset += nBytes; + break; + case 0xc0: + { + quint8 nBytes2; + quint16 newOffset; + if (!parseInteger(packet, offset, nBytes2)) { + return false; + } + newOffset = ((nBytes & ~0xc0) << 8) | nBytes2; + if (newOffset >= offsetPtr) { + return false; // prevent infinite loop + } + offsetPtr = newOffset; + if (!offsetEnd) { + offsetEnd = offset; + } + offset = newOffset; + break; + } + default: + return false; // no other types supported + } + } + if (offsetEnd) { + offset = offsetEnd; + } + return true; +} + +void writeName(QByteArray &packet, quint16 &offset, const QByteArray &name, QMap &nameMap) +{ + QByteArray fragment = name; + if (fragment.endsWith('.')) { + fragment.chop(1); + } + while (fragment.length()) { + if (nameMap.contains(fragment)) { + writeInteger(packet, offset, nameMap.value(fragment) | 0xc000); + return; + } + nameMap.insert(fragment, offset); + int index = fragment.indexOf('.'); + if (index == -1) { + index = fragment.length(); + } + writeInteger(packet, offset, index); + packet.append(fragment.left(index)); + offset += index; + fragment.remove(0, index + 1); + } + writeInteger(packet, offset, 0); +} + +bool parseRecord(const QByteArray &packet, quint16 &offset, Record &record) +{ + QByteArray name; + quint16 type, class_, dataLen; + quint32 ttl; + if (!parseName(packet, offset, name) || + !parseInteger(packet, offset, type) || + !parseInteger(packet, offset, class_) || + !parseInteger(packet, offset, ttl) || + !parseInteger(packet, offset, dataLen)) { + return false; + } + record.setName(name); + record.setType(type); + record.setFlushCache(class_ & 0x8000); + record.setTtl(ttl); + switch (type) { + case A: + { + quint32 ipv4Addr; + if (!parseInteger(packet, offset, ipv4Addr)) { + return false; + } + record.setAddress(QHostAddress(ipv4Addr)); + break; + } + case AAAA: + { + if (offset + 16 > packet.length()) { + return false; + } + record.setAddress(QHostAddress( + reinterpret_cast(packet.constData() + offset) + )); + offset += 16; + break; + } + case NSEC: + { + QByteArray nextDomainName; + quint8 number; + quint8 length; + if (!parseName(packet, offset, nextDomainName) || + !parseInteger(packet, offset, number) || + !parseInteger(packet, offset, length) || + number != 0 || + offset + length > packet.length()) { + return false; + } + Bitmap bitmap; + bitmap.setData(length, reinterpret_cast(packet.constData() + offset)); + record.setNextDomainName(nextDomainName); + record.setBitmap(bitmap); + offset += length; + break; + } + case PTR: + { + QByteArray target; + if (!parseName(packet, offset, target)) { + return false; + } + record.setTarget(target); + break; + } + case SRV: + { + quint16 priority, weight, port; + QByteArray target; + if (!parseInteger(packet, offset, priority) || + !parseInteger(packet, offset, weight) || + !parseInteger(packet, offset, port) || + !parseName(packet, offset, target)) { + return false; + } + record.setPriority(priority); + record.setWeight(weight); + record.setPort(port); + record.setTarget(target); + break; + } + case TXT: + { + quint16 start = offset; + while (offset < start + dataLen) { + quint8 nBytes; + if (!parseInteger(packet, offset, nBytes) || + offset + nBytes > packet.length()) { + return false; + } + if (nBytes == 0) { + break; + } + QByteArray attr(packet.constData() + offset, nBytes); + offset += nBytes; + int splitIndex = attr.indexOf('='); + if (splitIndex == -1) { + record.addAttribute(attr, QByteArray()); + } else { + record.addAttribute(attr.left(splitIndex), attr.mid(splitIndex + 1)); + } + } + break; + } + default: + offset += dataLen; + break; + } + return true; +} + +void writeRecord(QByteArray &packet, quint16 &offset, Record &record, QMap &nameMap) +{ + writeName(packet, offset, record.name(), nameMap); + writeInteger(packet, offset, record.type()); + writeInteger(packet, offset, record.flushCache() ? 0x8001 : 1); + writeInteger(packet, offset, record.ttl()); + offset += 2; + QByteArray data; + switch (record.type()) { + case A: + writeInteger(data, offset, record.address().toIPv4Address()); + break; + case AAAA: + { + Q_IPV6ADDR ipv6Addr = record.address().toIPv6Address(); + data.append(reinterpret_cast(&ipv6Addr), sizeof(Q_IPV6ADDR)); + offset += data.length(); + break; + } + case NSEC: + { + quint8 length = record.bitmap().length(); + writeName(data, offset, record.nextDomainName(), nameMap); + writeInteger(data, offset, 0); + writeInteger(data, offset, length); + data.append(reinterpret_cast(record.bitmap().data()), length); + offset += length; + break; + } + case PTR: + writeName(data, offset, record.target(), nameMap); + break; + case SRV: + writeInteger(data, offset, record.priority()); + writeInteger(data, offset, record.weight()); + writeInteger(data, offset, record.port()); + writeName(data, offset, record.target(), nameMap); + break; + case TXT: + if (!record.attributes().count()) { + writeInteger(data, offset, 0); + break; + } + for (auto i = record.attributes().constBegin(); i != record.attributes().constEnd(); ++i) { + QByteArray entry = i.value().isNull() ? i.key() : i.key() + "=" + i.value(); + writeInteger(data, offset, entry.length()); + data.append(entry); + offset += entry.length(); + } + break; + default: + break; + } + offset -= 2; + writeInteger(packet, offset, data.length()); + packet.append(data); +} + +bool fromPacket(const QByteArray &packet, Message &message) +{ + quint16 offset = 0; + quint16 transactionId, flags, nQuestion, nAnswer, nAuthority, nAdditional; + if (!parseInteger(packet, offset, transactionId) || + !parseInteger(packet, offset, flags) || + !parseInteger(packet, offset, nQuestion) || + !parseInteger(packet, offset, nAnswer) || + !parseInteger(packet, offset, nAuthority) || + !parseInteger(packet, offset, nAdditional)) { + return false; + } + message.setTransactionId(transactionId); + message.setResponse(flags & 0x8400); + message.setTruncated(flags & 0x0200); + for (int i = 0; i < nQuestion; ++i) { + QByteArray name; + quint16 type, class_; + if (!parseName(packet, offset, name) || + !parseInteger(packet, offset, type) || + !parseInteger(packet, offset, class_)) { + return false; + } + Query query; + query.setName(name); + query.setType(type); + query.setUnicastResponse(class_ & 0x8000); + message.addQuery(query); + } + quint16 nRecord = nAnswer + nAuthority + nAdditional; + for (int i = 0; i < nRecord; ++i) { + Record record; + if (!parseRecord(packet, offset, record)) { + return false; + } + message.addRecord(record); + } + return true; +} + +void toPacket(const Message &message, QByteArray &packet) +{ + quint16 offset = 0; + quint16 flags = (message.isResponse() ? 0x8400 : 0) | + (message.isTruncated() ? 0x200 : 0); + writeInteger(packet, offset, message.transactionId()); + writeInteger(packet, offset, flags); + writeInteger(packet, offset, message.queries().length()); + writeInteger(packet, offset, message.records().length()); + writeInteger(packet, offset, 0); + writeInteger(packet, offset, 0); + QMap nameMap; + const auto queries = message.queries(); + for (const Query &query : queries) { + writeName(packet, offset, query.name(), nameMap); + writeInteger(packet, offset, query.type()); + writeInteger(packet, offset, query.unicastResponse() ? 0x8001 : 1); + } + const auto records = message.records(); + for (Record record : records) { + writeRecord(packet, offset, record, nameMap); + } +} + +QString typeName(quint16 type) +{ + switch (type) { + case A: return "A"; + case AAAA: return "AAAA"; + case ANY: return "ANY"; + case NSEC: return "NSEC"; + case PTR: return "PTR"; + case SRV: return "SRV"; + case TXT: return "TXT"; + default: return "?"; + } +} + +} diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/hostname.cpp b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/hostname.cpp new file mode 100644 index 0000000..5d6c92c --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/hostname.cpp @@ -0,0 +1,183 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include +#include +#include +#include + +#include "../include/abstractserver.h" +#include "../include/dns.h" +#include "../include/hostname.h" +#include "../include/message.h" +#include "../include/query.h" +#include "../include/record.h" + +#include "hostname_p.h" + +using namespace QMdnsEngine; + +HostnamePrivate::HostnamePrivate(Hostname *hostname, AbstractServer *server) + : QObject(hostname), + server(server), + q(hostname) +{ + connect(server, &AbstractServer::messageReceived, this, &HostnamePrivate::onMessageReceived); + connect(®istrationTimer, &QTimer::timeout, this, &HostnamePrivate::onRegistrationTimeout); + connect(&rebroadcastTimer, &QTimer::timeout, this, &HostnamePrivate::onRebroadcastTimeout); + + registrationTimer.setInterval(2 * 1000); + registrationTimer.setSingleShot(true); + + rebroadcastTimer.setInterval(30 * 60 * 1000); + rebroadcastTimer.setSingleShot(true); + + // Immediately assert the hostname + onRebroadcastTimeout(); +} + +void HostnamePrivate::assertHostname() +{ + // Begin with the local hostname and replace any "." with "-" (I'm looking + // at you, macOS) + QByteArray localHostname = QHostInfo::localHostName().toUtf8(); + localHostname = localHostname.replace('.', '-'); + + // If the suffix > 1, then append a "-2", "-3", etc. to the hostname to + // aid in finding one that is unique and not in use + hostname = (hostnameSuffix == 1 ? localHostname: + localHostname + "-" + QByteArray::number(hostnameSuffix)) + ".local."; + + // Compose a query for A and AAAA records matching the hostname + Query ipv4Query; + ipv4Query.setName(hostname); + ipv4Query.setType(A); + Query ipv6Query; + ipv6Query.setName(hostname); + ipv6Query.setType(AAAA); + Message message; + message.addQuery(ipv4Query); + message.addQuery(ipv6Query); + + server->sendMessageToAll(message); + + // If no reply is received after two seconds, the hostname is available + registrationTimer.start(); +} + +bool HostnamePrivate::generateRecord(const QHostAddress &srcAddress, quint16 type, Record &record) +{ + // Attempt to find the interface that corresponds with the provided + // address and determine this device's address from the interface + + const auto interfaces = QNetworkInterface::allInterfaces(); + for (const QNetworkInterface &networkInterface : interfaces) { + const auto entries = networkInterface.addressEntries(); + for (const QNetworkAddressEntry &entry : entries) { + if (srcAddress.isInSubnet(entry.ip(), entry.prefixLength())) { + for (const QNetworkAddressEntry &newEntry : entries) { + QHostAddress address = newEntry.ip(); + if ((address.protocol() == QAbstractSocket::IPv4Protocol && type == A) || + (address.protocol() == QAbstractSocket::IPv6Protocol && type == AAAA)) { + record.setName(hostname); + record.setType(type); + record.setAddress(address); + return true; + } + } + } + } + } + return false; +} + +void HostnamePrivate::onMessageReceived(const Message &message) +{ + if (message.isResponse()) { + if (hostnameRegistered) { + return; + } + const auto records = message.records(); + for (const Record &record : records) { + if ((record.type() == A || record.type() == AAAA) && record.name() == hostname) { + ++hostnameSuffix; + assertHostname(); + } + } + } else { + if (!hostnameRegistered) { + return; + } + Message reply; + reply.reply(message); + const auto queries = message.queries(); + for (const Query &query : queries) { + if ((query.type() == A || query.type() == AAAA) && query.name() == hostname) { + Record record; + if (generateRecord(message.address(), query.type(), record)) { + reply.addRecord(record); + } + } + } + if (reply.records().count()) { + server->sendMessage(reply); + } + } +} + +void HostnamePrivate::onRegistrationTimeout() +{ + hostnameRegistered = true; + if (hostname != hostnamePrev) { + emit q->hostnameChanged(hostname); + } + + // Re-assert the hostname in half an hour + rebroadcastTimer.start(); +} + +void HostnamePrivate::onRebroadcastTimeout() +{ + hostnamePrev = hostname; + hostnameRegistered = false; + hostnameSuffix = 1; + + assertHostname(); +} + +Hostname::Hostname(AbstractServer *server, QObject *parent) + : QObject(parent), + d(new HostnamePrivate(this, server)) +{ +} + +bool Hostname::isRegistered() const +{ + return d->hostnameRegistered; +} + +QByteArray Hostname::hostname() const +{ + return d->hostname; +} diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/hostname_p.h b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/hostname_p.h new file mode 100644 index 0000000..33261db --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/hostname_p.h @@ -0,0 +1,75 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef QMDNSENGINE_HOSTNAME_P_H +#define QMDNSENGINE_HOSTNAME_P_H + +#include +#include + +class QHostAddress; + +namespace QMdnsEngine +{ + +class AbstractServer; +class Hostname; +class Message; +class Record; + +class HostnamePrivate : public QObject +{ + Q_OBJECT + +public: + + HostnamePrivate(Hostname *hostname, AbstractServer *server); + + void assertHostname(); + bool generateRecord(const QHostAddress &srcAddress, quint16 type, Record &record); + + AbstractServer *server; + + QByteArray hostnamePrev; + QByteArray hostname; + bool hostnameRegistered; + int hostnameSuffix; + + QTimer registrationTimer; + QTimer rebroadcastTimer; + +private Q_SLOTS: + + void onMessageReceived(const Message &message); + void onRegistrationTimeout(); + void onRebroadcastTimeout(); + +private: + + Hostname *const q; +}; + +} + +#endif // QMDNSENGINE_HOSTNAME_P_H diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/mdns.cpp b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/mdns.cpp new file mode 100644 index 0000000..af6e449 --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/mdns.cpp @@ -0,0 +1,35 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "../include/mdns.h" + +namespace QMdnsEngine +{ + +const quint16 MdnsPort = 5353; +const QHostAddress MdnsIpv4Address("224.0.0.251"); +const QHostAddress MdnsIpv6Address("ff02::fb"); +const QByteArray MdnsBrowseType("_services._dns-sd._udp.local."); + +} diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/message.cpp b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/message.cpp new file mode 100644 index 0000000..f273863 --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/message.cpp @@ -0,0 +1,148 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "../include/mdns.h" +#include "../include/message.h" +#include "../include/query.h" +#include "../include/record.h" + +#include "message_p.h" + +using namespace QMdnsEngine; + +MessagePrivate::MessagePrivate() + : port(0), + transactionId(0), + isResponse(false), + isTruncated(false) +{ +} + +Message::Message() + : d(new MessagePrivate) +{ +} + +Message::Message(const Message &other) + : d(new MessagePrivate) +{ + *this = other; +} + +Message &Message::operator=(const Message &other) +{ + *d = *other.d; + return *this; +} + +Message::~Message() +{ + delete d; +} + +QHostAddress Message::address() const +{ + return d->address; +} + +void Message::setAddress(const QHostAddress &address) +{ + d->address = address; +} + +quint16 Message::port() const +{ + return d->port; +} + +void Message::setPort(quint16 port) +{ + d->port = port; +} + +quint16 Message::transactionId() const +{ + return d->transactionId; +} + +void Message::setTransactionId(quint16 transactionId) +{ + d->transactionId = transactionId; +} + +bool Message::isResponse() const +{ + return d->isResponse; +} + +void Message::setResponse(bool isResponse) +{ + d->isResponse = isResponse; +} + +bool Message::isTruncated() const +{ + return d->isTruncated; +} + +void Message::setTruncated(bool isTruncated) +{ + d->isTruncated = isTruncated; +} + +QList Message::queries() const +{ + return d->queries; +} + +void Message::addQuery(const Query &query) +{ + d->queries.append(query); +} + +QList Message::records() const +{ + return d->records; +} + +void Message::addRecord(const Record &record) +{ + d->records.append(record); +} + +void Message::reply(const Message &other) +{ + if (other.port() == MdnsPort) { + if (other.address().protocol() == QAbstractSocket::IPv4Protocol) { + setAddress(MdnsIpv4Address); + } else { + setAddress(MdnsIpv6Address); + } + } else { + setAddress(other.address()); + } + setPort(other.port()); + setTransactionId(other.transactionId()); + setResponse(true); +} diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/message_p.h b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/message_p.h new file mode 100644 index 0000000..665d194 --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/message_p.h @@ -0,0 +1,54 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef QMDNSENGINE_MESSAGE_P_H +#define QMDNSENGINE_MESSAGE_P_H + +#include +#include + +namespace QMdnsEngine +{ + +class Query; +class Record; + +class MessagePrivate +{ +public: + + MessagePrivate(); + + QHostAddress address; + quint16 port; + quint16 transactionId; + bool isResponse; + bool isTruncated; + QList queries; + QList records; +}; + +} + +#endif // QMDNSENGINE_MESSAGE_P_H diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/prober.cpp b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/prober.cpp new file mode 100644 index 0000000..3a58f58 --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/prober.cpp @@ -0,0 +1,107 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "../include/abstractserver.h" +#include "../include/dns.h" +#include "../include/message.h" +#include "../include/prober.h" +#include "../include/query.h" + +#include "prober_p.h" + +using namespace QMdnsEngine; + +ProberPrivate::ProberPrivate(Prober *prober, AbstractServer *server, const Record &record) + : QObject(prober), + server(server), + confirmed(false), + proposedRecord(record), + suffix(1), + q(prober) +{ + // All records should contain at least one "." + int index = record.name().indexOf('.'); + name = record.name().left(index); + type = record.name().mid(index); + + connect(server, &AbstractServer::messageReceived, this, &ProberPrivate::onMessageReceived); + connect(&timer, &QTimer::timeout, this, &ProberPrivate::onTimeout); + + timer.setSingleShot(true); + + assertRecord(); +} + +void ProberPrivate::assertRecord() +{ + // Use the current suffix to set the name of the proposed record + QString tmpName = suffix == 1 + ? QString("%1%2").arg(name, type.constData()) + : QString("%1-%2%3").arg(name.constData(), QByteArray::number(suffix), type); + + proposedRecord.setName(tmpName.toUtf8()); + + // Broadcast a query for the proposed name (using an ANY query) and + // include the proposed record in the query + Query query; + query.setName(proposedRecord.name()); + query.setType(ANY); + Message message; + message.addQuery(query); + message.addRecord(proposedRecord); + server->sendMessageToAll(message); + + // Wait two seconds to confirm it is unique + timer.stop(); + timer.start(2 * 1000); +} + +void ProberPrivate::onMessageReceived(const Message &message) +{ + // If the response matches the proposed record, increment the suffix and + // try with the new name + + if (confirmed || !message.isResponse()) { + return; + } + const auto records = message.records(); + for (const Record &record : records) { + if (record.name() == proposedRecord.name() && record.type() == proposedRecord.type()) { + ++suffix; + assertRecord(); + } + } +} + +void ProberPrivate::onTimeout() +{ + confirmed = true; + emit q->nameConfirmed(proposedRecord.name()); +} + +Prober::Prober(AbstractServer *server, const Record &record, QObject *parent) + : QObject(parent), + d(new ProberPrivate(this, server, record)) +{ +} diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/prober_p.h b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/prober_p.h new file mode 100644 index 0000000..f82f9bc --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/prober_p.h @@ -0,0 +1,72 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef QMDNSENGINE_PROBER_P_H +#define QMDNSENGINE_PROBER_P_H + +#include +#include + +#include "../include/record.h" + +namespace QMdnsEngine +{ + +class AbstractServer; +class Message; +class Prober; + +class ProberPrivate : public QObject +{ + Q_OBJECT + +public: + + ProberPrivate(Prober *prober, AbstractServer *server, const Record &record); + + void assertRecord(); + + AbstractServer *server; + QTimer timer; + + bool confirmed; + + Record proposedRecord; + QByteArray name; + QByteArray type; + int suffix; + +private Q_SLOTS: + + void onMessageReceived(const Message &message); + void onTimeout(); + +private: + + Prober *const q; +}; + +} + +#endif // QMDNSENGINE_PROBER_P_H diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/provider.cpp b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/provider.cpp new file mode 100644 index 0000000..e1ed2a5 --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/provider.cpp @@ -0,0 +1,239 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "../include/abstractserver.h" +#include "../include/dns.h" +#include "../include/hostname.h" +#include "../include/mdns.h" +#include "../include/message.h" +#include "../include/prober.h" +#include "../include/provider.h" +#include "../include/query.h" + +#include "provider_p.h" + +using namespace QMdnsEngine; + +ProviderPrivate::ProviderPrivate(QObject *parent, AbstractServer *server, Hostname *hostname) + : QObject(parent), + server(server), + hostname(hostname), + prober(nullptr), + initialized(false), + confirmed(false) +{ + connect(server, &AbstractServer::messageReceived, this, &ProviderPrivate::onMessageReceived); + connect(hostname, &Hostname::hostnameChanged, this, &ProviderPrivate::onHostnameChanged); + + browsePtrProposed.setName(MdnsBrowseType); + browsePtrProposed.setType(PTR); + ptrProposed.setType(PTR); + srvProposed.setType(SRV); + txtProposed.setType(TXT); +} + +ProviderPrivate::~ProviderPrivate() +{ + if (confirmed) { + farewell(); + } +} + +void ProviderPrivate::announce() +{ + // Broadcast a message with each of the records + Message message; + message.setResponse(true); + message.addRecord(ptrRecord); + message.addRecord(srvRecord); + message.addRecord(txtRecord); + server->sendMessageToAll(message); +} + +void ProviderPrivate::confirm() +{ + // Confirm that the desired name is unique through probing + + if (prober) { + delete prober; + } + prober = new Prober(server, srvProposed, this); + connect(prober, &Prober::nameConfirmed, [this](const QByteArray &name) { + + // If existing records were confirmed, indicate that they are no + // longer valid + if (confirmed) { + farewell(); + } else { + confirmed = true; + } + // Update the proposed records + ptrProposed.setTarget(name); + srvProposed.setName(name); + txtProposed.setName(name); + + // Publish the proposed records and announce them + publish(); + + delete prober; + prober = nullptr; + }); +} + +void ProviderPrivate::farewell() +{ + // Send a message indicating that the existing records are no longer valid + // by setting their TTL to 0 + + ptrRecord.setTtl(0); + srvRecord.setTtl(0); + txtRecord.setTtl(0); + announce(); +} + +void ProviderPrivate::publish() +{ + // Copy the proposed records over and announce them + browsePtrRecord = browsePtrProposed; + ptrRecord = ptrProposed; + srvRecord = srvProposed; + txtRecord = txtProposed; + announce(); + +} + +void ProviderPrivate::onMessageReceived(const Message &message) +{ + if (!confirmed || message.isResponse()) { + return; + } + + bool sendBrowsePtr = false; + bool sendPtr = false; + bool sendSrv = false; + bool sendTxt = false; + + // Determine which records to send based on the queries + const auto queries = message.queries(); + for (const Query &query : queries) { + if (query.type() == PTR && query.name() == MdnsBrowseType) { + sendBrowsePtr = true; + } else if (query.type() == PTR && query.name() == ptrRecord.name()) { + sendPtr = true; + } else if (query.type() == SRV && query.name() == srvRecord.name()) { + sendSrv = true; + } else if (query.type() == TXT && query.name() == txtRecord.name()) { + sendTxt = true; + } + } + + // Remove records to send if they are already known + const auto records = message.records(); + for (const Record &record : records) { + if (record == ptrRecord) { + sendPtr = false; + } else if (record == srvRecord) { + sendSrv = false; + } else if (record == txtRecord) { + sendTxt = false; + } + } + + // Include the SRV and TXT if the PTR is being sent + if (sendPtr) { + sendSrv = sendTxt = true; + } + + // If any records should be sent, compose a message reply + if (sendBrowsePtr || sendPtr || sendSrv || sendTxt) { + Message reply; + reply.reply(message); + if (sendBrowsePtr) { + reply.addRecord(browsePtrRecord); + } + if (sendPtr) { + reply.addRecord(ptrRecord); + } + if (sendSrv) { + reply.addRecord(srvRecord); + } + if (sendTxt) { + reply.addRecord(txtRecord); + } + server->sendMessage(reply); + } +} + +void ProviderPrivate::onHostnameChanged(const QByteArray &newHostname) +{ + // Update the proposed SRV record + srvProposed.setTarget(newHostname); + + // If initialized, confirm the record + if (initialized) { + confirm(); + } +} + +Provider::Provider(AbstractServer *server, Hostname *hostname, QObject *parent) + : QObject(parent), + d(new ProviderPrivate(this, server, hostname)) +{ +} + +void Provider::update(const Service &service) +{ + d->initialized = true; + + // Clean the service name + QByteArray serviceName = service.name(); + serviceName = serviceName.replace('.', '-'); + + // Update the proposed records + QByteArray fqName = serviceName + "." + service.type(); + d->browsePtrProposed.setTarget(service.type()); + d->ptrProposed.setName(service.type()); + d->ptrProposed.setTarget(fqName); + d->srvProposed.setName(fqName); + d->srvProposed.setPort(service.port()); + d->srvProposed.setTarget(d->hostname->hostname()); + d->txtProposed.setName(fqName); + d->txtProposed.setAttributes(service.attributes()); + // Assuming a valid hostname exists, check to see if the new service uses + // a different name - if so, it must first be confirmed + if (d->hostname == nullptr) { + qDebug() << "hostname is nullptr"; + return; + } + bool registered = d->hostname->isRegistered(); + //qDebug() << "Hostname registered:" << registered; + if(registered) { + //if (d->hostname->isRegistered()) { + if (!d->confirmed || fqName != d->srvRecord.name()) { + d->confirm(); + } else { + d->publish(); + } + } +} diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/provider_p.h b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/provider_p.h new file mode 100644 index 0000000..86454d1 --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/provider_p.h @@ -0,0 +1,81 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef QMDNSENGINE_PROVIDER_P_H +#define QMDNSENGINE_PROVIDER_P_H + +#include + +#include "../include/record.h" +#include "../include/service.h" + +namespace QMdnsEngine +{ + +class AbstractServer; +class Hostname; +class Message; +class Prober; + +class ProviderPrivate : public QObject +{ + Q_OBJECT + +public: + + ProviderPrivate(QObject *parent, AbstractServer *server, Hostname *hostname); + virtual ~ProviderPrivate(); + + void announce(); + void confirm(); + void farewell(); + void publish(); + + AbstractServer *server; + Hostname *hostname; + Prober *prober; + + Service service; + bool initialized; + bool confirmed; + + Record browsePtrRecord; + Record ptrRecord; + Record srvRecord; + Record txtRecord; + + Record browsePtrProposed; + Record ptrProposed; + Record srvProposed; + Record txtProposed; + +private Q_SLOTS: + + void onMessageReceived(const Message &message); + void onHostnameChanged(const QByteArray &hostname); +}; + +} + +#endif // QMDNSENGINE_PROVIDER_P_H diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/query.cpp b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/query.cpp new file mode 100644 index 0000000..8b54b94 --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/query.cpp @@ -0,0 +1,100 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include + +#include "../include/dns.h" +#include "../include/query.h" + +#include "query_p.h" + +using namespace QMdnsEngine; + +QueryPrivate::QueryPrivate() + : type(0), + unicastResponse(false) +{ +} + +Query::Query() + : d(new QueryPrivate) +{ +} + +Query::Query(const Query &other) + : d(new QueryPrivate) +{ + *this = other; +} + +Query &Query::operator=(const Query &other) +{ + *d = *other.d; + return *this; +} + +Query::~Query() +{ + delete d; +} + +QByteArray Query::name() const +{ + return d->name; +} + +void Query::setName(const QByteArray &name) +{ + d->name = name; +} + +quint16 Query::type() const +{ + return d->type; +} + +void Query::setType(quint16 type) +{ + d->type = type; +} + +bool Query::unicastResponse() const +{ + return d->unicastResponse; +} + +void Query::setUnicastResponse(bool unicastResponse) +{ + d->unicastResponse = unicastResponse; +} + +QDebug QMdnsEngine::operator<<(QDebug dbg, const Query &query) +{ + QDebugStateSaver saver(dbg); + Q_UNUSED(saver); + + dbg.noquote().nospace() << "Query(" << typeName(query.type()) << " " << query.name() << ")"; + + return dbg; +} diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/query_p.h b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/query_p.h new file mode 100644 index 0000000..23d01f0 --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/query_p.h @@ -0,0 +1,46 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef QMDNSENGINE_QUERY_P_H +#define QMDNSENGINE_QUERY_P_H + +#include + +namespace QMdnsEngine +{ + +class QueryPrivate +{ +public: + + QueryPrivate(); + + QByteArray name; + quint16 type; + bool unicastResponse; +}; + +} + +#endif // QMDNSENGINE_QUERY_P_H diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/record.cpp b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/record.cpp new file mode 100644 index 0000000..42009b8 --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/record.cpp @@ -0,0 +1,218 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include + +#include "../include/dns.h" +#include "../include/record.h" + +#include "record_p.h" + +using namespace QMdnsEngine; + +RecordPrivate::RecordPrivate() + : type(0), + flushCache(false), + ttl(3600), + priority(0), + weight(0), + port(0) +{ +} + +Record::Record() + : d(new RecordPrivate) +{ +} + +Record::Record(const Record &other) + : d(new RecordPrivate) +{ + *this = other; +} + +Record &Record::operator=(const Record &other) +{ + *d = *other.d; + return *this; +} + +bool Record::operator==(const Record &other) const +{ + return d->name == other.d->name && + d->type == other.d->type && + d->address == other.d->address && + d->target == other.d->target && + d->nextDomainName == other.d->nextDomainName && + d->priority == other.d->priority && + d->weight == other.d->weight && + d->port == other.d->port && + d->attributes == other.d->attributes && + d->bitmap == other.d->bitmap; +} + +bool Record::operator!=(const Record &other) const +{ + return !(*this == other); +} + +Record::~Record() +{ + delete d; +} + +QByteArray Record::name() const +{ + return d->name; +} + +void Record::setName(const QByteArray &name) +{ + d->name = name; +} + +quint16 Record::type() const +{ + return d->type; +} + +void Record::setType(quint16 type) +{ + d->type = type; +} + +bool Record::flushCache() const +{ + return d->flushCache; +} + +void Record::setFlushCache(bool flushCache) +{ + d->flushCache = flushCache; +} + +quint32 Record::ttl() const +{ + return d->ttl; +} + +void Record::setTtl(quint32 ttl) +{ + d->ttl = ttl; +} + +QHostAddress Record::address() const +{ + return d->address; +} + +void Record::setAddress(const QHostAddress &address) +{ + d->address = address; +} + +QByteArray Record::target() const +{ + return d->target; +} + +void Record::setTarget(const QByteArray &target) +{ + d->target = target; +} + +QByteArray Record::nextDomainName() const +{ + return d->nextDomainName; +} + +void Record::setNextDomainName(const QByteArray &nextDomainName) +{ + d->nextDomainName = nextDomainName; +} + +quint16 Record::priority() const +{ + return d->priority; +} + +void Record::setPriority(quint16 priority) +{ + d->priority = priority; +} + +quint16 Record::weight() const +{ + return d->weight; +} + +void Record::setWeight(quint16 weight) +{ + d->weight = weight; +} + +quint16 Record::port() const +{ + return d->port; +} + +void Record::setPort(quint16 port) +{ + d->port = port; +} + +QMap Record::attributes() const +{ + return d->attributes; +} + +void Record::setAttributes(const QMap &attributes) +{ + d->attributes = attributes; +} + +void Record::addAttribute(const QByteArray &key, const QByteArray &value) +{ + d->attributes.insert(key, value); +} + +Bitmap Record::bitmap() const +{ + return d->bitmap; +} + +void Record::setBitmap(const Bitmap &bitmap) +{ + d->bitmap = bitmap; +} + +QDebug QMdnsEngine::operator<<(QDebug dbg, const Record &record) +{ + QDebugStateSaver saver(dbg); + Q_UNUSED(saver); + + dbg.noquote().nospace() << "Record(" << typeName(record.type()) << " " << record.name() << ")"; + + return dbg; +} diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/record_p.h b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/record_p.h new file mode 100644 index 0000000..674830a --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/record_p.h @@ -0,0 +1,59 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef QMDNSENGINE_RECORD_P_H +#define QMDNSENGINE_RECORD_P_H + +#include +#include +#include + +#include "../include/bitmap.h" + +namespace QMdnsEngine { + +class RecordPrivate +{ +public: + + RecordPrivate(); + + QByteArray name; + quint16 type; + bool flushCache; + quint32 ttl; + + QHostAddress address; + QByteArray target; + QByteArray nextDomainName; + quint16 priority; + quint16 weight; + quint16 port; + QMap attributes; + Bitmap bitmap; +}; + +} + +#endif // QMDNSENGINE_RECORD_P_H diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/resolver.cpp b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/resolver.cpp new file mode 100644 index 0000000..99ecc6b --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/resolver.cpp @@ -0,0 +1,116 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include + +#include "../include/abstractserver.h" +#include "../include/dns.h" +#include "../include/cache.h" +#include "../include/message.h" +#include "../include/query.h" +#include "../include/record.h" +#include "../include/resolver.h" + +#include "resolver_p.h" + +using namespace QMdnsEngine; + +ResolverPrivate::ResolverPrivate(Resolver *resolver, AbstractServer *server, const QByteArray &name, Cache *cache) + : QObject(resolver), + server(server), + name(name), + cache(cache ? cache : new Cache(this)), + q(resolver) +{ + connect(server, &AbstractServer::messageReceived, this, &ResolverPrivate::onMessageReceived); + connect(&timer, &QTimer::timeout, this, &ResolverPrivate::onTimeout); + + // Query for new records + query(); + + // Pull the existing records from the cache + timer.setSingleShot(true); + timer.start(0); +} + +QList ResolverPrivate::existing() const +{ + QList records; + cache->lookupRecords(name, A, records); + cache->lookupRecords(name, AAAA, records); + return records; +} + +void ResolverPrivate::query() const +{ + Message message; + + // Add a query for A and AAAA records + Query query; + query.setName(name); + query.setType(A); + message.addQuery(query); + query.setType(AAAA); + message.addQuery(query); + + // Add existing (known) records to the query + const auto records = existing(); + for (const Record &record : records) { + message.addRecord(record); + } + + // Send the query + server->sendMessageToAll(message); +} + +void ResolverPrivate::onMessageReceived(const Message &message) +{ + if (!message.isResponse()) { + return; + } + const auto records = message.records(); + for (const Record &record : records) { + if (record.name() == name && (record.type() == A || record.type() == AAAA)) { + cache->addRecord(record); + if (!addresses.contains(record.address())) { + emit q->resolved(record.address()); + addresses.insert(record.address()); + } + } + } +} + +void ResolverPrivate::onTimeout() +{ + const auto records = existing(); + for (const Record &record : records) { + emit q->resolved(record.address()); + } +} + +Resolver::Resolver(AbstractServer *server, const QByteArray &name, Cache *cache, QObject *parent) + : QObject(parent), + d(new ResolverPrivate(this, server, name, cache)) +{ +} diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/resolver_p.h b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/resolver_p.h new file mode 100644 index 0000000..0110d47 --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/resolver_p.h @@ -0,0 +1,71 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef QMDNSENGINE_RESOLVER_P_H +#define QMDNSENGINE_RESOLVER_P_H + +#include +#include +#include +#include + +namespace QMdnsEngine +{ + +class AbstractServer; +class Cache; +class Message; +class Record; +class Resolver; + +class ResolverPrivate : public QObject +{ + Q_OBJECT + +public: + + explicit ResolverPrivate(Resolver *resolver, AbstractServer *server, const QByteArray &name, Cache *cache); + + QList existing() const; + void query() const; + + AbstractServer *server; + QByteArray name; + Cache *cache; + QSet addresses; + QTimer timer; + +private Q_SLOTS: + + void onMessageReceived(const Message &message); + void onTimeout(); + +private: + + Resolver *const q; +}; + +} + +#endif // QMDNSENGINE_RESOLVER_P_H diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/server.cpp b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/server.cpp new file mode 100644 index 0000000..b295a15 --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/server.cpp @@ -0,0 +1,138 @@ +#include + +#ifdef Q_OS_UNIX +# include +# include +# include +#endif + +#include +#include + +#include "../include/dns.h" +#include "../include/mdns.h" +#include "../include/message.h" +#include "../include/server.h" + +#include "server_p.h" + +using namespace QMdnsEngine; + +ServerPrivate::ServerPrivate(Server *server) + : QObject(server), + q(server) +{ + connect(&timer, &QTimer::timeout, this, &ServerPrivate::onTimeout); + connect(&ipv4Socket, &QUdpSocket::readyRead, this, &ServerPrivate::onReadyRead); + connect(&ipv6Socket, &QUdpSocket::readyRead, this, &ServerPrivate::onReadyRead); + + timer.setInterval(60 * 1000); + timer.setSingleShot(true); + onTimeout(); +} + +bool ServerPrivate::bindSocket(QUdpSocket &socket, const QHostAddress &address) +{ + // Exit early if the socket is already bound + if (socket.state() == QAbstractSocket::BoundState) { + return true; + } + + // I cannot find the correct combination of flags that allows the socket + // to bind properly on Linux, so on that platform, we must manually create + // the socket and initialize the QUdpSocket with it + +#ifdef Q_OS_UNIX + if (!socket.bind(address, MdnsPort, QAbstractSocket::ShareAddress)) { + int arg = 1; + if (setsockopt(socket.socketDescriptor(), SOL_SOCKET, SO_REUSEADDR, + reinterpret_cast(&arg), sizeof(int))) { + emit q->error(strerror(errno)); + return false; + } +#endif + if (!socket.bind(address, MdnsPort, QAbstractSocket::ReuseAddressHint)) { + emit q->error(socket.errorString()); + return false; + } +#ifdef Q_OS_UNIX + } +#endif + + return true; +} + +void ServerPrivate::onTimeout() +{ + // A timer is used to run a set of operations once per minute; first, the + // two sockets are bound - if this fails, another attempt is made once per + // timeout; secondly, all network interfaces are enumerated; if the + // interface supports multicast, the socket will join the mDNS multicast + // groups + + bool ipv4Bound = bindSocket(ipv4Socket, QHostAddress::AnyIPv4); + bool ipv6Bound = bindSocket(ipv6Socket, QHostAddress::AnyIPv6); + + if (ipv4Bound || ipv6Bound) { + const auto interfaces = QNetworkInterface::allInterfaces(); + for (const QNetworkInterface &networkInterface : interfaces) { + if (networkInterface.flags() & QNetworkInterface::CanMulticast) { + if (ipv4Bound) { + ipv4Socket.joinMulticastGroup(MdnsIpv4Address, networkInterface); + } + if (ipv6Bound) { + ipv6Socket.joinMulticastGroup(MdnsIpv6Address, networkInterface); + } + } + } + } + timer.start(); +} + +void ServerPrivate::onReadyRead() +{ + // Read the packet from the socket + QUdpSocket *socket = qobject_cast(sender()); + QByteArray packet; + packet.resize(socket->pendingDatagramSize()); + QHostAddress address; + quint16 port; + socket->readDatagram(packet.data(), packet.size(), &address, &port); + + // Attempt to decode the packet + Message message; + if (fromPacket(packet, message)) { + message.setAddress(address); + message.setPort(port); + emit q->messageReceived(message); + } +} + +Server::Server(QObject *parent) + : AbstractServer(parent), + d(new ServerPrivate(this)) +{ +} + +void Server::sendMessage(const Message &message) +{ + QByteArray packet; + toPacket(message, packet); + if (message.address().protocol() == QAbstractSocket::IPv4Protocol) { + d->ipv4Socket.writeDatagram(packet, message.address(), message.port()); + } else { + d->ipv6Socket.writeDatagram(packet, message.address(), message.port()); + } +} + +void Server::sendMessageToAll(const Message &message) +{ + QByteArray packet; + toPacket(message, packet); + //qDebug() << "MdnsMsg :" << packet.toHex(); + qDebug() << "MdnsPort:" << MdnsPort; + qint64 sentBytes = d->ipv4Socket.writeDatagram(packet, MdnsIpv4Address, MdnsPort); + qDebug() << "ipv4Socket sentBytes:" << sentBytes; + sentBytes = d->ipv6Socket.writeDatagram(packet, MdnsIpv6Address, MdnsPort); + qDebug() << "ipv6Socket sentBytes:" << sentBytes; +} diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/server_p.h b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/server_p.h new file mode 100644 index 0000000..1158eec --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/server_p.h @@ -0,0 +1,65 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef QMDNSENGINE_SERVER_P_H +#define QMDNSENGINE_SERVER_P_H + +#include +#include +#include + +class QHostAddress; + +namespace QMdnsEngine +{ + +class Server; + +class ServerPrivate : public QObject +{ + Q_OBJECT + +public: + + explicit ServerPrivate(Server *server); + + bool bindSocket(QUdpSocket &socket, const QHostAddress &address); + + QTimer timer; + QUdpSocket ipv4Socket{ this }; + QUdpSocket ipv6Socket{ this }; + +private Q_SLOTS: + + void onTimeout(); + void onReadyRead(); + +private: + + Server *const q; +}; + +} + +#endif // QMDNSENGINE_SERVER_P_H diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/service.cpp b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/service.cpp new file mode 100644 index 0000000..dd4fa0e --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/service.cpp @@ -0,0 +1,139 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "../include/service.h" + +#include "service_p.h" + +using namespace QMdnsEngine; + +ServicePrivate::ServicePrivate() +{ +} + +Service::Service() + : d(new ServicePrivate) +{ +} + +Service::Service(const Service &other) + : d(new ServicePrivate) +{ + *this = other; +} + +Service &Service::operator=(const Service &other) +{ + *d = *other.d; + return *this; +} + +bool Service::operator==(const Service &other) const +{ + return d->type == other.d->type && + d->name == other.d->name && + d->port == other.d->port && + d->attributes == other.d->attributes; +} + +bool Service::operator!=(const Service &other) const +{ + return !(*this == other); +} + +Service::~Service() +{ + delete d; +} + +QByteArray Service::type() const +{ + return d->type; +} + +void Service::setType(const QByteArray &type) +{ + d->type = type; +} + +QByteArray Service::name() const +{ + return d->name; +} + +void Service::setName(const QByteArray &name) +{ + d->name = name; +} + +QByteArray Service::hostname() const +{ + return d->hostname; +} + +void Service::setHostname(const QByteArray &hostname) +{ + d->hostname = hostname; +} + +quint16 Service::port() const +{ + return d->port; +} + +void Service::setPort(quint16 port) +{ + d->port = port; +} + +QMap Service::attributes() const +{ + return d->attributes; +} + +void Service::setAttributes(const QMap &attributes) +{ + d->attributes = attributes; +} + +void Service::addAttribute(const QByteArray &key, const QByteArray &value) +{ + d->attributes.insert(key, value); +} + +QDebug QMdnsEngine::operator<<(QDebug debug, const Service &service) +{ + QDebugStateSaver saver(debug); + Q_UNUSED(saver); + + debug.noquote().nospace() + << "Service(name: " << service.name() + << ", type: " << service.type() + << ", hostname: " << service.hostname() + << ", port: " << service.port() + << ", attributes: " << service.attributes() + << ")"; + + return debug; +} diff --git a/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/service_p.h b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/service_p.h new file mode 100644 index 0000000..ffa2e4d --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/qmdnsengine/src/service_p.h @@ -0,0 +1,49 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017 Nathan Osman + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef QMDNSENGINE_SERVICE_P_H +#define QMDNSENGINE_SERVICE_P_H + +#include +#include + +namespace QMdnsEngine +{ + +class ServicePrivate +{ +public: + + ServicePrivate(); + + QByteArray type; + QByteArray name; + QByteArray hostname; + quint16 port; + QMap attributes; +}; + +} + +#endif // QMDNSENGINE_SERVICE_P_H diff --git a/FactoryTestTool/SourceCode/Network/mdns/servicemodel.cpp b/FactoryTestTool/SourceCode/Network/mdns/servicemodel.cpp new file mode 100644 index 0000000..38f05ae --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/servicemodel.cpp @@ -0,0 +1,36 @@ +// #include "servicemodel.cpp" +#include "servicemodel.h" + +Q_DECLARE_METATYPE(QMdnsEngine::Service) + +ServiceProvider::ServiceProvider(QObject* parent) + : QObject(parent), mHostname(&mServer), mProvider(nullptr) +{ + // Initialize the provider when the ServiceProvider is created +} + +void ServiceProvider::startServiceBroadcast(const QString& serviceName, const QString& serviceType, quint16 port) +{ + if (mProvider) { + delete mProvider; + mProvider = nullptr; + } + // Set up the service with the specified name, type, and port + mService.setName(serviceName.toUtf8()); + mService.setType(serviceType.toUtf8()); + mService.setPort(port); + // Create a new provider for this service + mProvider = new QMdnsEngine::Provider(&mServer, &mHostname, this); + mProvider->update(mService); + + qDebug() << "mDNS service broadcast started:" << serviceName << serviceType << "on port" << port; +} + +void ServiceProvider::stopServiceBroadcast() +{ + if (mProvider) { + delete mProvider; + mProvider = nullptr; + qDebug() << "mDNS service broadcast stopped."; + } +} diff --git a/FactoryTestTool/SourceCode/Network/mdns/servicemodel.h b/FactoryTestTool/SourceCode/Network/mdns/servicemodel.h new file mode 100644 index 0000000..0db827b --- /dev/null +++ b/FactoryTestTool/SourceCode/Network/mdns/servicemodel.h @@ -0,0 +1,29 @@ +#ifndef SERVICEMODEL_H +#define SERVICEMODEL_H + +#include + +#include "qmdnsengine/include/server.h" +#include "qmdnsengine/include/hostname.h" +#include "qmdnsengine/include/provider.h" +#include "qmdnsengine/include/service.h" + + +class ServiceProvider : public QObject +{ + Q_OBJECT + +public: + explicit ServiceProvider(QObject* parent = nullptr); + + void startServiceBroadcast(const QString& serviceName, const QString& serviceType, quint16 port); + void stopServiceBroadcast(); + +private: + QMdnsEngine::Server mServer; + QMdnsEngine::Hostname mHostname; + QMdnsEngine::Provider* mProvider; + QMdnsEngine::Service mService; +}; + +#endif // SERVICEPROVIDER_H diff --git a/FactoryTestTool/SourceCode/RecvDataHandler/MsgTpye.h b/FactoryTestTool/SourceCode/RecvDataHandler/MsgTpye.h index 5b90460..8db790d 100644 --- a/FactoryTestTool/SourceCode/RecvDataHandler/MsgTpye.h +++ b/FactoryTestTool/SourceCode/RecvDataHandler/MsgTpye.h @@ -56,6 +56,20 @@ #define SET_LOG_LEVEL 0x0360 +// 前后板设备信息 +#define GET_FRONT_V851_VERSION 0x0400 +#define GET_FRONT_MCU_VERSION 0x0401 +#define GET_FRONT_HW_VERSION 0x0402 +#define GET_FRONT_ALGO_VERSION 0x0403 +#define GET_FRONT_SN 0x0404 +#define GET_FRONT_HW_INFO 0x0420 +#define WRITE_FRONT_LICENSE 0x0421 + +#define GET_BACK_V851_VERSION 0x0450 +#define GET_BACK_806_VERSION 0x0451 +#define GET_BACK_HW_VERSION 0x0452 +#define GET_BACK_SN 0x0453 +#define GET_BACK_UID 0x0454 #endif diff --git a/FactoryTestTool/SourceCode/RecvDataHandler/RecvDataHandler.cpp b/FactoryTestTool/SourceCode/RecvDataHandler/RecvDataHandler.cpp index 2930a27..6a78b23 100644 --- a/FactoryTestTool/SourceCode/RecvDataHandler/RecvDataHandler.cpp +++ b/FactoryTestTool/SourceCode/RecvDataHandler/RecvDataHandler.cpp @@ -2,16 +2,20 @@ #include "RecvDataHandler.h" #include "../Network/ClientHandler.h" -DataHandler::DataHandler(QLabel* leftLens_imageLabel, QLabel* rightLens_imageLabel, QLabel* videoLabel, QObject* parent) +DataHandler::DataHandler(QLabel* leftLens_imageLabel, QLabel* rightLens_imageLabel, QLabel* videoLabel, + QTextEdit* licenseHwInfoEdit, QMap* devInfoLineEdits, QObject* parent) : QObject(parent), leftLens_m_imageLabel(leftLens_imageLabel), rightLens_m_imageLabel(rightLens_imageLabel), videoLabel(videoLabel), + licenseHwInfoEdit(licenseHwInfoEdit), + devInfoLineEdits(devInfoLineEdits), ffmpegDecoder(new FFmpegDecoder()), // 初始化FFmpeg解码器 buffer(new QByteArray()) { - ffmpegDecoder->initialize(); // 初始化解码器 + ffmpegDecoder->initialize(); // 初始化解码器 clearAllRecvData(); + initializeMsgIdToCmdMap(); } DataHandler::~DataHandler() @@ -26,7 +30,6 @@ DataHandler::~DataHandler() buffer = nullptr; } -// 将十六进制字符串转换为 QByteArray QByteArray DataHandler::hexStringToByteArray(const QString& hexString) { QByteArray byteArray; @@ -65,6 +68,19 @@ void DataHandler::showVideo(const QString& client, const QByteArray& valData) //ffmpegDecoder->decodeFile(filePath_1, videoLabel); } +void DataHandler::updateLineEdit(int msg_id, const QByteArray& actual_data) { + QString dataStr = QString(actual_data.toHex(' ')); + licenseHwInfoEdit->setPlainText(dataStr); + + if (msgIdToCmdMap.contains(msg_id)) { + QString cmd = msgIdToCmdMap[msg_id]; + if (devInfoLineEdits->contains(cmd)) { + QLineEdit* lineEdit = devInfoLineEdits->value(cmd); + lineEdit->setText(dataStr); + } + } +} + void DataHandler::clearAllRecvData() { allRecvData = QByteArray(); remain = 0; @@ -93,12 +109,10 @@ void DataHandler::handleData(const QString& client, const QByteArray& recvData, #endif //qDebug() << "---Received data size:" << recvData.size(); - // 将接收到的数据追加到buffer buffer->append(recvData); while (buffer->size() >= 11) { // 至少需要11个字节来解析数据头 - // 检查数据头 if (buffer->mid(0, 4) == QByteArray::fromHex("aa55aa55")) { msg_id = (static_cast(buffer->at(5)) << 8) | (static_cast(buffer->at(4))); @@ -122,7 +136,7 @@ void DataHandler::handleData(const QString& client, const QByteArray& recvData, // 同一个client仅当 msg_id 不连续为 0x11/0x21 或第一次处理时才执行 emit statusUpdated if ((msg_id != 0x0011 || clientLastMsgId.value(client, 0) != 0x0011) && (msg_id != 0x0021 || clientLastMsgId.value(client, 0) != 0x0021)){ - qDebug() << "Emitting statusUpdated for client:" << client << "with msg_id:" << msg_id; + qDebug() << "Emitting statusUpdated for client:" << client << "with msg_id:" << QString::number(msg_id, 16).toUpper(); emit statusUpdated(client, currentRecvItemIndex + 1, currentRecvFuncItemIndex + 1, true, itemData, funcItemData); } @@ -139,6 +153,33 @@ void DataHandler::handleData(const QString& client, const QByteArray& recvData, } } +void DataHandler::initializeMsgIdToCmdMap() { + msgIdToCmdMap[GET_FRONT_V851_VERSION] = "GET_FRONT_V851_VERSION"; + msgIdToCmdMap[GET_FRONT_MCU_VERSION] = "GET_FRONT_MCU_VERSION"; + msgIdToCmdMap[GET_FRONT_HW_VERSION] = "GET_FRONT_HW_VERSION"; + msgIdToCmdMap[GET_FRONT_ALGO_VERSION] = "GET_FRONT_ALGO_VERSION"; + msgIdToCmdMap[GET_FRONT_SN] = "GET_FRONT_SN"; + + msgIdToCmdMap[GET_BACK_V851_VERSION] = "GET_BACK_V851_VERSION"; + msgIdToCmdMap[GET_BACK_806_VERSION] = "GET_BACK_806_VERSION"; + msgIdToCmdMap[GET_BACK_HW_VERSION] = "GET_BACK_HW_VERSION"; + msgIdToCmdMap[GET_BACK_SN] = "GET_BACK_SN"; + msgIdToCmdMap[GET_BACK_UID] = "GET_BACK_UID"; +} + +void DataHandler::handleCmd(int msg_id, const QString& client, QByteArray actual_data) +{ + if (msg_id < 0x0400) { + handleFrontCmd(msg_id, client, actual_data); + } + else if (msg_id < 0x0500) { + handleDevInfo(msg_id, client, actual_data); + } + else if (msg_id < 0x0800) { + handleBackCmd(msg_id, client, actual_data); + } +} + void DataHandler::handleFrontCmd(int msg_id, const QString& client, QByteArray actual_data) { switch (msg_id) { @@ -381,26 +422,67 @@ void DataHandler::handleFrontCmd(int msg_id, const QString& client, QByteArray a { } - break; + break; + case SET_LOG_LEVEL: + { + + } + break; default: {} break; } } +void DataHandler::handleDevInfo(int msg_id, const QString& client, QByteArray actual_data) +{ + switch (msg_id) { + case GET_FRONT_V851_VERSION: + case GET_FRONT_MCU_VERSION: + case GET_FRONT_HW_VERSION: + case GET_FRONT_ALGO_VERSION: + case GET_FRONT_SN: + + case GET_BACK_V851_VERSION: + case GET_BACK_806_VERSION: + case GET_BACK_HW_VERSION: + case GET_BACK_SN: + case GET_BACK_UID: + { + // aa55aa5503041d00000048464d3231305f4b3431343234395f423230323031305f41302e302e38 + qDebug() << "GET_DEV_INFO"; + QString dataStr = QString::fromUtf8(actual_data); + if (msgIdToCmdMap.contains(msg_id)) { + QString cmd = msgIdToCmdMap[msg_id]; + if (devInfoLineEdits->contains(cmd)) { + QLineEdit* lineEdit = devInfoLineEdits->value(cmd); + lineEdit->setText(dataStr); + } + } + //qDebug() << "GET_DEV_INFO msg_id:" << QString::number(msg_id, 16).toUpper(); + } + break; + case GET_FRONT_HW_INFO: + { + qDebug() << "GET_FRONT_HW_INFO"; + QString dataStr = QString(actual_data.toHex(' ')); + QString displayText = "get_hw_info:\n" + dataStr; + licenseHwInfoEdit->setPlainText(displayText); + } + break; + case WRITE_FRONT_LICENSE: + { + qDebug() << "WRITE_FRONT_LICENSE"; + } + break; + default: + break; + } +} + void DataHandler::handleBackCmd(int msg_id, const QString& client, QByteArray actual_data) { switch (msg_id) { } } - -void DataHandler::handleCmd(int msg_id, const QString& client, QByteArray actual_data) -{ - if (msg_id < 0x0400) { - handleFrontCmd(msg_id, client, actual_data); - } - else if (msg_id < 0x0800) { - handleBackCmd(msg_id, client, actual_data); - } -} diff --git a/FactoryTestTool/SourceCode/RecvDataHandler/RecvDataHandler.h b/FactoryTestTool/SourceCode/RecvDataHandler/RecvDataHandler.h index 7b4b6c4..dab9afb 100644 --- a/FactoryTestTool/SourceCode/RecvDataHandler/RecvDataHandler.h +++ b/FactoryTestTool/SourceCode/RecvDataHandler/RecvDataHandler.h @@ -17,6 +17,8 @@ #include #include #include +#include +#include #include "../Media/Media.h" #include "../ParseDataHandler/msgID.h" #include "../Media/VideoDecoder/FFmpegDecoder.h" @@ -30,7 +32,8 @@ class DataHandler : public QObject Q_OBJECT public: - explicit DataHandler(QLabel* leftLens_imageLabel, QLabel* rightLens_imageLabel, QLabel* videoLabel, QObject* parent = nullptr); + explicit DataHandler(QLabel* leftLens_imageLabel, QLabel* rightLens_imageLabel, QLabel* videoLabel, + QTextEdit* licenseHwInfoEdit, QMap* devInfoLineEdits, QObject* parent = nullptr); ~DataHandler(); public slots: @@ -45,6 +48,7 @@ private: QLabel* leftLens_m_imageLabel; QLabel* rightLens_m_imageLabel; QLabel* videoLabel; + QTextEdit* licenseHwInfoEdit; QByteArray allRecvData; // 完整的一帧数据 int remain = 0; int start_run = 0; @@ -52,6 +56,8 @@ private: FFmpegDecoder* ffmpegDecoder; QByteArray *buffer; QHash clientLastMsgId; + QMap* devInfoLineEdits; + QMap msgIdToCmdMap; // 如果接收十六进制数据,转为二进制 QByteArray hexStringToByteArray(const QString& hexString); @@ -59,7 +65,10 @@ private: void clearAllRecvData(); void handleCmd(int msg_id, const QString& client, QByteArray actual_data); void handleFrontCmd(int msg_id, const QString& client, QByteArray actual_data); + void handleDevInfo(int msg_id, const QString& client, QByteArray actual_data); void handleBackCmd(int msg_id, const QString& client, QByteArray actual_data); + void initializeMsgIdToCmdMap(); + void updateLineEdit(int msg_id, const QByteArray& actual_data); }; #endif // DATAHANDLER_H diff --git a/FactoryTestTool/SourceCode/Widget/MainWidget.cpp b/FactoryTestTool/SourceCode/Widget/MainWidget.cpp index 758d9a6..47472bf 100644 --- a/FactoryTestTool/SourceCode/Widget/MainWidget.cpp +++ b/FactoryTestTool/SourceCode/Widget/MainWidget.cpp @@ -1,14 +1,12 @@ -// MainWidget.cpp + // MainWidget.cpp #include "MainWidget.h" -#include "../RecvDataHandler/RecvDataHandler.h" -#include "../Json/readJsonFile.h" -#include "../LicenseGenerate/LicenseGenerate.h" + void onThreadFinished(QThread* thread, ClientHandler* handler) { qDebug() << "Thread finished. Deleting handler and thread."; - handler->deleteLater(); - thread->deleteLater(); + handler->deleteLater(); + thread->deleteLater(); } // 初始化 UI 组件和服务器 @@ -20,31 +18,38 @@ MainWidget::MainWidget(QWidget* parent) : lastClickedGetPicCamIndex(-1), lastClickedGetPicDevIndex(-1), lastClickedGetVideoCamIndex(-1), - lastClickedGetVideoDevIndex(-1) + lastClickedGetVideoDevIndex(-1), + mServiceProvider(new ServiceProvider(this)), + mdnsTimer(new QTimer(this)) +#if TEST_UDP_BROADCAST + ,multicastSocket(new QUdpSocket(this)), + multicastTimer(new QTimer(this)) +#endif { - leftLens_imageLabel = new QLabel(this); + leftLens_imageLabel = new QLabel(this); rightLens_imageLabel = new QLabel(this); - videoLabel = new QLabel(this); - funcConfigLineEdit = new QLineEdit(this); + videoLabel = new QLabel(this); + funcConfigLineEdit = new QLineEdit(this); + licenseHwInfoEdit = new QTextEdit(this); setupUI(); // 打印线程池状态信息 setupTimerForThreadPoolInfo(); server = new QTcpServer(this); - connect(server, &QTcpServer::newConnection, this, [this]() { + + connect(server, &QTcpServer::newConnection, this, [this]() { // 检查是否有挂起的连接 while (server->hasPendingConnections()) { QTcpSocket* socket = server->nextPendingConnection(); - int clientId = nextClientId ++; + int clientId = nextClientId++; qDebug() << ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"; qDebug() << "> A client is connected. ID:" << clientId; qDebug() << ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"; - // 更新连接数量并更新按键文本 - connectedClientsCount ++; + stopMdnsService(); + connectedClientsCount++; updateServerButtonText(); - // 创建新的定时器 QTimer* timer = new QTimer(this); timer->setSingleShot(true); clientTimers[clientId] = timer; @@ -62,7 +67,7 @@ MainWidget::MainWidget(QWidget* parent) : } }); - QThread* thread = new QThread(this); + QThread* thread = new QThread(this); ClientHandler* handler = new ClientHandler(socket, frontBoardOneClickTest, frontBoardTest, frontBoardFuncConfig, frontBoardDevInfoJson, frontBoardLicenseJson, backBoardDevInfoJson, getPicJson, getVideoJson, clientId, nullptr); @@ -71,14 +76,12 @@ MainWidget::MainWidget(QWidget* parent) : handler->moveToThread(thread); // 当线程结束时删除 handler - //connect(thread, &QThread::finished, handler, &QObject::deleteLater); - //connect(thread, &QThread::finished, thread, &QObject::deleteLater); connect(thread, &QThread::finished, this, [=]() { onThreadFinished(thread, handler); }); // 将sendData信号连接到主线程中的槽上 - connect(handler, &ClientHandler::sendData, this, [socket](const QByteArray& data) { + connect(handler, &ClientHandler::sendData, this, [socket](const QByteArray& data) { socket->write(data); socket->flush(); }); @@ -93,15 +96,15 @@ MainWidget::MainWidget(QWidget* parent) : clients.append(handler); clients_1[clientId] = handler; clientThreads[clientId] = thread; - connect(handler, &ClientHandler::statusUpdated, this, &MainWidget::onStatusUpdated); - //connect(handler, &ClientHandler::clientDisconnected, this, &MainWidget::onClientDisconnected); - connect(handler, &ClientHandler::allItemsProcessed, this, &MainWidget::onAllItemsProcessed); + connect(handler, &ClientHandler::statusUpdated, this, &MainWidget::onStatusUpdated); + connect(handler, &ClientHandler::clientDisconnected, this, &MainWidget::onClientDisconnected); + connect(handler, &ClientHandler::allItemsProcessed, this, &MainWidget::onAllItemsProcessed); connect(handler, &ClientHandler::selectClientDisconnected, this, &MainWidget::onDisconnectClient); // 创建 DataHandler 对象并连接信号 - DataHandler* dataHandler = new DataHandler(leftLens_imageLabel, rightLens_imageLabel, videoLabel, this); - connect(handler, &ClientHandler::dataReceived, dataHandler, &DataHandler::handleData); - connect(dataHandler, &DataHandler::statusUpdated, this, &MainWidget::onStatusUpdated); + DataHandler* dataHandler = new DataHandler(leftLens_imageLabel, rightLens_imageLabel, videoLabel, licenseHwInfoEdit, &devInfoLineEdits, this); + connect(handler, &ClientHandler::dataReceived, dataHandler, &DataHandler::handleData); + connect(dataHandler, &DataHandler::statusUpdated, this, &MainWidget::onStatusUpdated); connect(handler, &ClientHandler::startReadTimer, this, &MainWidget::startClientReadTimer); connect(handler, &ClientHandler::stopReadTimer, this, &MainWidget::stopClientReadTimer); @@ -122,9 +125,43 @@ MainWidget::MainWidget(QWidget* parent) : connectionStatusCheckTimer = new QTimer(this); connect(connectionStatusCheckTimer, &QTimer::timeout, this, &MainWidget::onCheckConnectionStatus); //connectionStatusCheckTimer->start(100); // 每100ms检查一次连接状态 + + connect(mdnsTimer, &QTimer::timeout, this, &MainWidget::startMdnsService); + +#if TEST_UDP_BROADCAST + // 设置组播地址 + QHostAddress groupAddress("224.0.0.251"); + quint16 port = 5353; + + // 绑定UDP套接字 + if (!multicastSocket->bind(QHostAddress::AnyIPv4, port, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint)) { + qWarning() << "Failed to bind multicast socket"; + } + + // 加入组播组 + bool joined = multicastSocket->joinMulticastGroup(groupAddress); + if (!joined) { + qWarning() << "Failed to join multicast group"; + } + + // 定期发送组播消息 + connect(multicastTimer, &QTimer::timeout, this, &MainWidget::sendMulticastMessage); + multicastTimer->start(1000); // 每秒发送一次组播消息 +#endif } -MainWidget::~MainWidget() { +MainWidget::~MainWidget() +{ +#if TEST_UDP_BROADCAST + multicastTimer->stop(); + multicastSocket->leaveMulticastGroup(QHostAddress("224.0.0.251")); + multicastSocket->close(); +#endif + if (mdnsTimer->isActive()) { + mdnsTimer->stop(); + } + delete mdnsTimer; + stopMdnsService(); for (auto timer : clientReadTimers) { timer->stop(); delete timer; @@ -196,7 +233,7 @@ void MainWidget::onCheckThreadStatus() //qDebug() << "Checking thread status..."; int activeThreadCount = clientThreads.size(); //qDebug() << "Number of active threads:" << activeThreadCount; - for (auto it = clientThreads.begin(); it != clientThreads.end(); ++it) { + for (auto it = clientThreads.begin(); it != clientThreads.end(); ++it) { int clientId = it.key(); QThread* thread = it.value(); ClientHandler* handler = nullptr; @@ -236,17 +273,37 @@ void MainWidget::setupTimerForThreadPoolInfo() timer->start(5000); // 每5秒打印一次线程池信息 } +void MainWidget::readJsonConfig() +{ + frontBoardOneClickTest = readJson_frontBoardOneClickTest(); + frontBoardTest = readJson_frontBoardTest(); + frontBoardFuncConfig = readJson_frontBoardFuncConfig(); + frontBoardDevInfoJson = readJson_frontDevInfo(); + frontBoardLicenseJson = readJson_frontLicense(); + + backBoardOneClickTest = readJson_backBoardOneClickTest(); + backBoardTest = readJson_backBoardTest(); + backBoardFuncConfig = readJson_backBoardFuncConfig(); + backBoardDevInfoJson = readJson_backDevInfo(); + + testJsonConfig = readJson_testConfig(); + funcJsonConfig = readJson_funcConfig(); + + getPicJson = readJson_getPic(); + getVideoJson = readJson_getVideo(); +} + // 设置 UI void MainWidget::setupUI() { startServerButton = new QPushButton("开始监听\n(Start Listening...)", this); startServerButton->setFixedSize(190, 70); // 设置宽度为 190 像素,高度为 70 像素 - sendAllButton = new QPushButton("一键功能测试", this); + sendAllButton = new QPushButton("一键功能测试", this); sendAllButton->setFixedSize(190, 70); // 设置宽度为 190 像素,高度为 70 像素 sendAllButton->setEnabled(false); - statusListWidget = new QListWidget(this); + statusListWidget = new QListWidget(this); //statusListWidget->setMinimumSize(350, 880); statusListWidget->setMinimumSize(350, 680); statusListWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // 设置大小策略为扩展 @@ -254,18 +311,18 @@ void MainWidget::setupUI() QHBoxLayout* buttonLayout = new QHBoxLayout; buttonLayout->addWidget(startServerButton); buttonLayout->addWidget(sendAllButton); - buttonLayout->addStretch(); + buttonLayout->addStretch(); - QVBoxLayout* leftLayout = new QVBoxLayout; + QVBoxLayout* leftLayout = new QVBoxLayout; leftLayout->addLayout(buttonLayout); leftLayout->setStretch(0, 1); leftLayout->addWidget(statusListWidget); leftLayout->setStretch(1, 200); - saveCheckBox = new QCheckBox("", this); + saveCheckBox = new QCheckBox("", this); selectFileButton = new QPushButton("Save", this); selectFileButton->setFixedSize(45, 28); - clearLogButton = new QPushButton("Clear", this); + clearLogButton = new QPushButton("Clear", this); clearLogButton->setFixedSize(55, 28); filePathLineEdit = new QLineEdit(this); filePathLineEdit->setFixedSize(250, 28); @@ -277,13 +334,13 @@ void MainWidget::setupUI() fileLayout->addWidget(filePathLineEdit); fileLayout->addWidget(clearLogButton); - leftLayout->addLayout(fileLayout); + leftLayout->addLayout(fileLayout); leftLayout->setStretch(2, 1); // 读取 JSON 配置文件 readJsonConfig(); - QGroupBox* groupBox = new QGroupBox("算法 license", this); + QGroupBox* groupBox = new QGroupBox("算法 license", this); QHBoxLayout* buttonRowLayout = new QHBoxLayout; for (int i = 0; i < frontBoardLicenseJson.size(); ++i) { QJsonObject item = frontBoardLicenseJson[i].toObject(); @@ -293,14 +350,13 @@ void MainWidget::setupUI() buttonRowLayout->addWidget(button); connect(button, &QPushButton::clicked, this, &MainWidget::onLicenseButtonClicked); } - QLineEdit* readOnlyLineEdit = new QLineEdit(this); - readOnlyLineEdit->setReadOnly(true); - readOnlyLineEdit->setText("This is a read-only text"); - readOnlyLineEdit->setFixedHeight(80); + licenseHwInfoEdit->setReadOnly(true); + licenseHwInfoEdit->setText("This is a read-only text"); + licenseHwInfoEdit->setFixedHeight(80); QVBoxLayout* groupBoxLayout_license = new QVBoxLayout; groupBoxLayout_license->addLayout(buttonRowLayout); - groupBoxLayout_license->addWidget(readOnlyLineEdit); + groupBoxLayout_license->addWidget(licenseHwInfoEdit); groupBox->setLayout(groupBoxLayout_license); leftLayout->addWidget(groupBox); @@ -313,65 +369,67 @@ void MainWidget::setupUI() QTabWidget* tabWidget = new QTabWidget(this); tabWidget->setFixedSize(900, 315); // 设置 QTabWidget 的固定大小 - QGroupBox* frontDeviceInfoGroupBox = new QGroupBox("前板设备信息", this); - QVBoxLayout* frontDeviceInfoLayout = new QVBoxLayout(frontDeviceInfoGroupBox); + QGroupBox* frontDeviceInfoGroupBox = new QGroupBox("前板设备信息", this); + QVBoxLayout* frontDeviceInfoLayout = new QVBoxLayout(frontDeviceInfoGroupBox); QVBoxLayout* frontDeviceLabelsLayout = new QVBoxLayout; for (const QJsonValue& value : frontBoardDevInfoJson) { QJsonObject item = value.toObject(); - QString label = item["lable"].toString(); + QString label = item["lable"].toString(); + QString cmd = item["cmd"].toString(); QLabel* itemLabel = new QLabel(label, this); QLineEdit* itemLineEdit = new QLineEdit(this); - itemLineEdit->setReadOnly(true); + itemLineEdit->setReadOnly(true); QHBoxLayout* itemLayout = new QHBoxLayout; itemLayout->addWidget(itemLabel); itemLayout->addWidget(itemLineEdit); frontDeviceLabelsLayout->addLayout(itemLayout); + devInfoLineEdits[cmd] = itemLineEdit; } frontDeviceInfoLayout->addLayout(frontDeviceLabelsLayout); frontDeviceInfoLayout->addStretch(); - QGroupBox* backDeviceInfoGroupBox = new QGroupBox("后板设备信息", this); - QVBoxLayout* backDeviceInfoLayout = new QVBoxLayout(backDeviceInfoGroupBox); + QGroupBox* backDeviceInfoGroupBox = new QGroupBox("后板设备信息", this); + QVBoxLayout* backDeviceInfoLayout = new QVBoxLayout(backDeviceInfoGroupBox); QVBoxLayout* backDeviceLabelsLayout = new QVBoxLayout; for (const QJsonValue& value : backBoardDevInfoJson) { QJsonObject item = value.toObject(); - QString label = item["lable"].toString(); + QString label = item["lable"].toString(); + QString cmd = item["cmd"].toString(); QLabel* itemLabel = new QLabel(label, this); QLineEdit* itemLineEdit = new QLineEdit(this); - itemLineEdit->setReadOnly(true); + itemLineEdit->setReadOnly(true); QHBoxLayout* itemLayout = new QHBoxLayout; itemLayout->addWidget(itemLabel); itemLayout->addWidget(itemLineEdit); backDeviceLabelsLayout->addLayout(itemLayout); + devInfoLineEdits[cmd] = itemLineEdit; } backDeviceInfoLayout->addLayout(backDeviceLabelsLayout); backDeviceInfoLayout->addStretch(); QHBoxLayout* groupBoxLayout = new QHBoxLayout; groupBoxLayout->addWidget(frontDeviceInfoGroupBox, 1); - groupBoxLayout->addWidget(backDeviceInfoGroupBox, 1); + groupBoxLayout->addWidget(backDeviceInfoGroupBox, 1); - QWidget* functionTestTab = new QWidget; + QWidget* functionTestTab = new QWidget; QVBoxLayout* functionTestLayout = new QVBoxLayout(functionTestTab); functionTestTab->setLayout(functionTestLayout); tabWidget->addTab(functionTestTab, "功能测试区"); - QGridLayout* buttonGridLayout = new QGridLayout(); - + QGridLayout* buttonGridLayout = new QGridLayout(); int buttonsPerRow = 7; // 每行显示的按键数量,根据需要调整 - // 为每个 JSON 项目创建按键,并添加到 rightLayout // 先总共放 77 个按键 //qDebug() << "testJsonConfig.size():" << testJsonConfig.size(); for (int i = 0; i < frontBoardTest.size() + (77 - frontBoardTest.size()); ++i) { - QJsonObject item = frontBoardTest[i].toObject(); - QString buttonText = item["lable"].toString(); + QJsonObject item = frontBoardTest[i].toObject(); + QString buttonText = item["lable"].toString(); // 判断 buttonText 是否为空或者只有空白字符 if (buttonText.isEmpty()) { //qDebug() << "buttonText.isEmpty():" << buttonText.isEmpty(); @@ -392,14 +450,14 @@ void MainWidget::setupUI() QWidget* buttonContainer = new QWidget; buttonContainer->setLayout(buttonGridLayout); - QScrollArea* scrollArea = new QScrollArea; + QScrollArea* scrollArea = new QScrollArea; scrollArea->setWidget(buttonContainer); scrollArea->setWidgetResizable(true); functionTestLayout->addWidget(scrollArea); - QWidget* functionConfigTab = new QWidget; - QVBoxLayout* functionConfigLayout = new QVBoxLayout(functionConfigTab); + QWidget* functionConfigTab = new QWidget; + QVBoxLayout* functionConfigLayout = new QVBoxLayout(functionConfigTab); functionConfigTab->setLayout(functionConfigLayout); tabWidget->addTab(functionConfigTab, "功能配置区"); @@ -431,44 +489,44 @@ void MainWidget::setupUI() QWidget* configButtonContainer = new QWidget; configButtonContainer->setLayout(configButtonGridLayout); - QScrollArea* configScrollArea = new QScrollArea; + QScrollArea* configScrollArea = new QScrollArea; configScrollArea->setWidget(configButtonContainer); configScrollArea->setWidgetResizable(true); functionConfigLayout->addWidget(configScrollArea); - QTabWidget* tabWidget_media = new QTabWidget(this); - QWidget* imageDisplayTab = new QWidget; + QTabWidget* tabWidget_media = new QTabWidget(this); + QWidget* imageDisplayTab = new QWidget; QVBoxLayout* imageDisplayLayout = new QVBoxLayout(imageDisplayTab); imageDisplayTab->setLayout(imageDisplayLayout); tabWidget_media->addTab(imageDisplayTab, "图像显示区"); - QVBoxLayout* imageButtonsColumnLayout = new QVBoxLayout; + QVBoxLayout* imageButtonsColumnLayout = new QVBoxLayout; for (int i = 0; i < 5; ++i) { QHBoxLayout* imageButtonsRowLayout = new QHBoxLayout; for (int j = 0; j < 2; ++j) { QPushButton* button; if (i == 0 && j == 0) { button = new QPushButton(QString("IR"), this); - button->setFixedSize(73, 50); - imageButtonsRowLayout->addWidget(button); - button->setProperty("getPicIndex", i * 2 + j); - connect(button, &QPushButton::clicked, this, &MainWidget::onSendGetPicClicked); - getPicButtons.append(button); - continue; - } - else if (i == 0 && j == 1) { - button = new QPushButton(QString("RGB"), this); - button->setFixedSize(73, 50); + button->setFixedSize(73, 50); imageButtonsRowLayout->addWidget(button); button->setProperty("getPicIndex", i * 2 + j); connect(button, &QPushButton::clicked, this, &MainWidget::onSendGetPicClicked); getPicButtons.append(button); continue; } - + else if (i == 0 && j == 1) { + button = new QPushButton(QString("RGB"), this); + button->setFixedSize(73, 50); + imageButtonsRowLayout->addWidget(button); + button->setProperty("getPicIndex", i * 2 + j); + connect(button, &QPushButton::clicked, this, &MainWidget::onSendGetPicClicked); + getPicButtons.append(button); + continue; + } + button = new QPushButton(QString("Device %1").arg(i * 2 + j - 1), this); - button->setFixedSize(73, 50); + button->setFixedSize(73, 50); imageButtonsRowLayout->addWidget(button); button->setProperty("getPicIndex", i * 2 + j); connect(button, &QPushButton::clicked, this, &MainWidget::onSendGetPicClicked); @@ -478,8 +536,8 @@ void MainWidget::setupUI() } QHBoxLayout* lensesLayout = new QHBoxLayout; - leftLens_imageLabel = new QLabel(this); - rightLens_imageLabel = new QLabel(this); + leftLens_imageLabel = new QLabel(this); + rightLens_imageLabel = new QLabel(this); lensesLayout->addWidget(leftLens_imageLabel); lensesLayout->addWidget(rightLens_imageLabel); @@ -488,12 +546,12 @@ void MainWidget::setupUI() imageAndButtonsLayout->addLayout(lensesLayout, 4); imageDisplayLayout->addLayout(imageAndButtonsLayout); - QWidget* videoDisplayTab = new QWidget; + QWidget* videoDisplayTab = new QWidget; QVBoxLayout* videoDisplayLayout = new QVBoxLayout(videoDisplayTab); videoDisplayTab->setLayout(videoDisplayLayout); tabWidget_media->addTab(videoDisplayTab, "视频显示区"); - QVBoxLayout* videoButtonsColumnLayout = new QVBoxLayout; + QVBoxLayout* videoButtonsColumnLayout = new QVBoxLayout; for (int i = 0; i < 6; ++i) { QHBoxLayout* videoButtonsRowLayout = new QHBoxLayout; for (int j = 0; j < 2; ++j) { @@ -541,7 +599,7 @@ void MainWidget::setupUI() } videoButtonsColumnLayout->addLayout(videoButtonsRowLayout); } - + QHBoxLayout* videoAndButtonsLayout = new QHBoxLayout; videoLabel = new QLabel(this); //videoLabel->setFixedSize(640, 480); @@ -549,7 +607,7 @@ void MainWidget::setupUI() videoAndButtonsLayout->addWidget(videoLabel, 6); videoDisplayLayout->addLayout(videoAndButtonsLayout); - QVBoxLayout* rightVerticalLayout = new QVBoxLayout; + QVBoxLayout* rightVerticalLayout = new QVBoxLayout; rightVerticalLayout->addLayout(groupBoxLayout, 2); rightVerticalLayout->addWidget(tabWidget, 2, Qt::AlignTop | Qt::AlignLeft); rightVerticalLayout->addWidget(tabWidget_media, 5); @@ -563,12 +621,12 @@ void MainWidget::setupUI() setWindowTitle("SL100 工厂产测工具 - V0.0.1"); resize(1340, 1000); // 设置宽度为 1440 像素,高度为 900 像素 - connect(startServerButton, &QPushButton::clicked, this, &MainWidget::onStartServerClicked); - connect(sendAllButton, &QPushButton::clicked, this, &MainWidget::onSendAllClicked); - connect(statusListWidget, &QListWidget::itemChanged, this, &MainWidget::scrollToBottom); - connect(selectFileButton, &QPushButton::clicked, this, &MainWidget::onSelectFileButtonClicked); - connect(clearLogButton, &QPushButton::clicked, this, &MainWidget::onclearLogButtonClicked); - connect(saveCheckBox, &QCheckBox::stateChanged, this, &MainWidget::onSaveCheckBoxStateChanged); + connect(startServerButton, &QPushButton::clicked, this, &MainWidget::onStartServerClicked); + connect(sendAllButton, &QPushButton::clicked, this, &MainWidget::onSendAllClicked); + connect(statusListWidget, &QListWidget::itemChanged, this, &MainWidget::scrollToBottom); + connect(selectFileButton, &QPushButton::clicked, this, &MainWidget::onSelectFileButtonClicked); + connect(clearLogButton, &QPushButton::clicked, this, &MainWidget::onclearLogButtonClicked); + connect(saveCheckBox, &QCheckBox::stateChanged, this, &MainWidget::onSaveCheckBoxStateChanged); } void MainWidget::onSelectFileButtonClicked() @@ -629,21 +687,6 @@ void MainWidget::scrollToBottom() statusListWidget->scrollToBottom(); } -void MainWidget::readJsonConfig() -{ - frontBoardOneClickTest = readJson_frontBoardOneClickTest(); - frontBoardTest = readJson_frontBoardTest(); - frontBoardFuncConfig = readJson_frontBoardFuncConfig(); - frontBoardDevInfoJson = readJson_frontDevInfo(); - frontBoardLicenseJson = readJson_frontLicense(); - backBoardDevInfoJson = readJson_backDevInfo(); - testJsonConfig = readJson_testConfig(); - funcJsonConfig = readJson_funcConfig(); - - getPicJson = readJson_getPic(); - getVideoJson = readJson_getVideo(); -} - void MainWidget::onDisconnectClient(int clientId) { for (ClientHandler* handler : clients) { @@ -659,13 +702,15 @@ void MainWidget::onDisconnectClient(int clientId) clientTimers.remove(clientId); } handler->deleteLater(); - // 更新连接数并更新按键文本 - connectedClientsCount --; + connectedClientsCount--; updateServerButtonText(); - break; + break; } } + + // 启动 mDNS 服务广播 + startMdnsService(); } // 处理客户端断开连接信号 @@ -687,8 +732,8 @@ void MainWidget::onClientDisconnected(ClientHandler* handler) handler->deleteLater(); // 延迟删除 ClientHandler 对象 // 更新连接数并更新按键文本 - connectedClientsCount --; - if(nextClientId <= 2) nextClientId --; + connectedClientsCount--; + if (nextClientId <= 2) nextClientId--; deviceConnected = true; updateServerButtonText(); } @@ -699,6 +744,7 @@ void MainWidget::updateServerButtonText() if (deviceConnected) { deviceConnected = false; startServerButton->setText(tr("正在监听(Listening)")); + if (connectedClientsCount == 0) startMdnsService(); } else if (connectedClientsCount == 0) { startServerButton->setText(tr("开始监听\n(Start Listening...)")); @@ -708,7 +754,6 @@ void MainWidget::updateServerButtonText() } } -// 处理 License 按键点击事件 void MainWidget::onLicenseButtonClicked() { if (connectedClientsCount) { @@ -717,10 +762,23 @@ void MainWidget::onLicenseButtonClicked() int index = button->property("licenseIndex").toInt(); if (index >= 0 && index < frontBoardLicenseJson.size()) { QJsonObject jsonObject = frontBoardLicenseJson[index].toObject(); - QString jsonString = QJsonDocument(jsonObject).toJson(QJsonDocument::Compact); + //QString jsonString = QJsonDocument(jsonObject).toJson(QJsonDocument::Compact); //qDebug() << "license Button clicked, sending JSON:" << jsonString; - for (ClientHandler* handler : clients) { - handler->sendLicenseItem(index); + unsigned char license_info[PIX_LICENCE_BYTES] = { 0 }; + if (jsonObject["lable"].toString() == "get_license") { + LicenseConfirmWindow dialog("你确定要获取此授权吗?"); + if (dialog.exec() == QDialog::Accepted) + licenseGenerate(license_info, license_info); + } + else { + if (jsonObject["lable"].toString() == "write_license") { + LicenseConfirmWindow dialog("你确定要发送此授权吗?"); + if (dialog.exec() == QDialog::Accepted) + licenseGenerate(license_info, license_info); + } + for (ClientHandler* handler : clients) { + handler->sendLicenseItem(index); + } } } } @@ -731,17 +789,60 @@ void MainWidget::onLicenseButtonClicked() } } +void MainWidget::startMdnsService() +{ + QDateTime currentTime = QDateTime::currentDateTime(); + QString formattedTime = currentTime.toString("yyyy-MM-dd hh:mm:ss.zzz"); + qDebug() << "[" << formattedTime << "]:" << "Start Mdns Broadcast Service"; + QString serviceName = "SL100 FactoryTool Mdns Broadcast Service"; + QString serviceType = "_SL100_FactoryTool-service._tcp"; + quint16 port = TCP_CONNECT_PORT; + mServiceProvider->startServiceBroadcast(serviceName, serviceType, port); + + if (!mdnsTimer->isActive()) { + mdnsTimer->start(1000); + } +} + +void MainWidget::stopMdnsService() +{ + if (mdnsTimer->isActive()) { + mdnsTimer->stop(); // 停止定时器 + } + mServiceProvider->stopServiceBroadcast(); +} + +#if TEST_UDP_BROADCAST +void MainWidget::sendMulticastMessage() +{ + QByteArray datagram = "--------------------------------- Test multicast message from MainWidget"; + QHostAddress groupAddress("224.0.0.251"); + quint16 port = 5353; + + qint64 sentBytes = multicastSocket->writeDatagram(datagram, groupAddress, port); + if (sentBytes == -1) { + qWarning() << "Failed to send multicast message:" << multicastSocket->errorString(); + } + else { + qDebug() << "Multicast message sentBytes:" << sentBytes; + qDebug() << "Multicast message sent:" << datagram; + } +} +#endif + // 处理开始服务器按键点击事件 void MainWidget::onStartServerClicked() { if (!server->isListening()) { - // 根据需要修改 - //QHostAddress specifiedIpAddress("10.10.10.253"); - quint16 specifiedPort = 12412; + startMdnsService(); +#if TEST_UDP_BROADCAST + sendMulticastMessage(); +#endif + // QHostAddress specifiedIpAddress("10.10.10.253"); + quint16 specifiedPort = TCP_CONNECT_PORT; qDebug() << "" << specifiedPort; if (server->listen(QHostAddress::Any, specifiedPort)) { startServerButton->setText(tr("正在监听(Listening)")); - // 设置按钮背景色为绿色 startServerButton->setStyleSheet("background-color: green;"); sendAllButton->setEnabled(true); } @@ -754,6 +855,8 @@ void MainWidget::onStartServerClicked() startServerButton->setText(tr("开始监听\n(Start Listening...)")); startServerButton->setStyleSheet(""); sendAllButton->setEnabled(false); + + stopMdnsService(); } } @@ -785,7 +888,7 @@ void MainWidget::onSendGetPicClicked() if (connectedClientsCount) { QPushButton* button = qobject_cast(sender()); int itemIndex = button->property("getPicIndex").toInt(); - + if (itemIndex < 2) { button->setStyleSheet("background-color: green;"); if (lastClickedGetPicCamIndex != -1 && lastClickedGetPicCamIndex != itemIndex) { @@ -811,7 +914,7 @@ void MainWidget::onSendGetPicClicked() lastClickedGetPicDevIndex = itemIndex; //QMutexLocker locker(&mutex); for (ClientHandler* handler : clients) { - handler->sendGetPicItem(itemIndex - 2); + handler->sendGetPicItem(itemIndex - 2, lastClickedGetPicCamIndex); } } } @@ -843,7 +946,7 @@ void MainWidget::onSendGetVideoClicked() listItem->setBackground(Qt::red); } else { - if (itemIndex - 5 >= connectedClientsCount) { + if (itemIndex - 6 >= connectedClientsCount) { QListWidgetItem* listItem = new QListWidgetItem(QString("No device %1 is connected !!!").arg(itemIndex - 4), statusListWidget); listItem->setBackground(Qt::red); } @@ -855,7 +958,6 @@ void MainWidget::onSendGetVideoClicked() lastClickedGetVideoDevIndex = itemIndex; //QMutexLocker locker(&mutex); for (ClientHandler* handler : clients) { - // 发送当前设备取图的指令 handler->sendGetVideoItem(itemIndex - 5, 1); } getVideoButtons[2]->setEnabled(true); @@ -883,11 +985,6 @@ void MainWidget::onOpenFocusWindowClicked() } } -//void MainWidget::onPowerOnSend() -//{ -// -//} - // 处理一键发送按键点击事件 void MainWidget::onSendAllClicked() { @@ -901,14 +998,13 @@ void MainWidget::onSendAllClicked() for (ClientHandler* handler : clients) { // 重置索引 handler->resetCurrentItemIndex(); - //handler->sendNextItem(); - handler->sendDevInfoItem(); - //licenseGenerate(); + handler->sendNextItem(); + //handler->sendDevInfoItem(); } } else { sendAllButton->setText("一键功能测试"); - sendAllButton->setStyleSheet("background-color: white;"); + sendAllButton->setStyleSheet("background-color: white;"); manualSend = false; for (ClientHandler* handler : clients) { handler->resetCurrentItemIndex(); @@ -1003,7 +1099,7 @@ void MainWidget::onAllItemsProcessed(const QString& client, int itemsProcessedCo { isSendingAll = false; sendAllButton->setText("一键功能测试"); - sendAllButton->setStyleSheet("background-color: white;"); + sendAllButton->setStyleSheet("background-color: white;"); //qDebug() << "onAllItemsProcessed called for client:" << client << "itemsProcessedCount:" << itemsProcessedCount; int clientId = -1; for (ClientHandler* handler : clients) { @@ -1025,3 +1121,1112 @@ void MainWidget::onAllItemsProcessed(const QString& client, int itemsProcessedCo statusListWidget->scrollToBottom(); } + + +//#include "MainWidget.h" +// +// +//void onThreadFinished(QThread* thread, ClientHandler* handler) +//{ +// qDebug() << "Thread finished. Deleting handler and thread."; +// handler->deleteLater(); +// thread->deleteLater(); +//} +// +//// 初始化 UI 组件和服务器 +//MainWidget::MainWidget(QWidget* parent) : +// QWidget(parent), +// nextClientId(1), +// manualSend(false), +// isSendingAll(false), +// lastClickedGetPicCamIndex(-1), +// lastClickedGetPicDevIndex(-1), +// lastClickedGetVideoCamIndex(-1), +// lastClickedGetVideoDevIndex(-1), +// mServiceProvider(new ServiceProvider(this)) +//#if TEST_UDP_BROADCAST +// , multicastSocket(new QUdpSocket(this)), +// multicastTimer(new QTimer(this)) +//#endif +//{ +// leftLens_imageLabel = new QLabel(this); +// rightLens_imageLabel = new QLabel(this); +// videoLabel = new QLabel(this); +// funcConfigLineEdit = new QLineEdit(this); +// licenseHwInfoEdit = new QTextEdit(this); +// +// setupUI(); +// // 打印线程池状态信息 +// setupTimerForThreadPoolInfo(); +// server = new QTcpServer(this); +// +// connect(server, &QTcpServer::newConnection, this, [this]() { +// // 检查是否有挂起的连接 +// while (server->hasPendingConnections()) { +// QTcpSocket* socket = server->nextPendingConnection(); +// int clientId = nextClientId++; +// qDebug() << ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"; +// qDebug() << "> A client is connected. ID:" << clientId; +// qDebug() << ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"; +// +// // 更新连接数量并更新按键文本 +// connectedClientsCount++; +// updateServerButtonText(); +// +// // 创建新的定时器 +// QTimer* timer = new QTimer(this); +// timer->setSingleShot(true); +// clientTimers[clientId] = timer; +// +// // 连接定时器超时信号到槽函数 +// connect(timer, &QTimer::timeout, this, [this, clientId]() { +// //qDebug() << "Timeout occurred for client ID:" << clientId; +// for (ClientHandler* handler : clients) { +// if (handler->getClientId() == clientId) { +// bool invoked = QMetaObject::invokeMethod(handler, "onTimeout", Qt::DirectConnection); +// if (!invoked) { +// qWarning() << "Failed to invoke onTimeout for client ID:" << clientId; +// } +// } +// } +// }); +// +// QThread* thread = new QThread(this); +// ClientHandler* handler = new ClientHandler(socket, frontBoardOneClickTest, frontBoardTest, frontBoardFuncConfig, +// frontBoardDevInfoJson, frontBoardLicenseJson, backBoardDevInfoJson, +// getPicJson, getVideoJson, clientId, nullptr); +// +// // 将 ClientHandler 移动到线程池中的线程 +// handler->moveToThread(thread); +// +// // 当线程结束时删除 handler +// connect(thread, &QThread::finished, this, [=]() { +// onThreadFinished(thread, handler); +// }); +// +// // 将sendData信号连接到主线程中的槽上 +// connect(handler, &ClientHandler::sendData, this, [socket](const QByteArray& data) { +// socket->write(data); +// socket->flush(); +// }); +// +// connect(handler, &ClientHandler::startTimeout, this, [this, clientId](int timeout) { +// this->onStartTimeout(clientId, timeout); +// }); +// +// // 启动新的线程 +// thread->start(); +// +// clients.append(handler); +// clients_1[clientId] = handler; +// clientThreads[clientId] = thread; +// connect(handler, &ClientHandler::statusUpdated, this, &MainWidget::onStatusUpdated); +// connect(handler, &ClientHandler::clientDisconnected, this, &MainWidget::onClientDisconnected); +// connect(handler, &ClientHandler::allItemsProcessed, this, &MainWidget::onAllItemsProcessed); +// connect(handler, &ClientHandler::selectClientDisconnected, this, &MainWidget::onDisconnectClient); +// +// // 创建 DataHandler 对象并连接信号 +// DataHandler* dataHandler = new DataHandler(leftLens_imageLabel, rightLens_imageLabel, videoLabel, licenseHwInfoEdit, &devInfoLineEdits, this); +// connect(handler, &ClientHandler::dataReceived, dataHandler, &DataHandler::handleData); +// connect(dataHandler, &DataHandler::statusUpdated, this, &MainWidget::onStatusUpdated); +// +// connect(handler, &ClientHandler::startReadTimer, this, &MainWidget::startClientReadTimer); +// connect(handler, &ClientHandler::stopReadTimer, this, &MainWidget::stopClientReadTimer); +// +// // 创建和管理定时器 +// QTimer* readTimer = new QTimer(this); +// connect(readTimer, &QTimer::timeout, handler, &ClientHandler::onTimeoutRead); +// // readTimer->start(10); // 每 10ms 触发一次 +// // 将定时器存储到哈希表中,方便管理 +// clientReadTimers[clientId] = readTimer; +// } +// }); +// +// threadStatusTimer = new QTimer(this); +// connect(threadStatusTimer, &QTimer::timeout, this, &MainWidget::onCheckThreadStatus); +// //threadStatusTimer->start(100); // 每100ms检查一次线程状态 +// +// connectionStatusCheckTimer = new QTimer(this); +// connect(connectionStatusCheckTimer, &QTimer::timeout, this, &MainWidget::onCheckConnectionStatus); +// //connectionStatusCheckTimer->start(100); // 每100ms检查一次连接状态 +// +//#if TEST_UDP_BROADCAST +// // 设置组播地址 +// QHostAddress groupAddress("224.0.0.251"); +// quint16 port = 5353; +// +// // 绑定UDP套接字 +// if (!multicastSocket->bind(QHostAddress::AnyIPv4, port, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint)) { +// qWarning() << "Failed to bind multicast socket"; +// } +// +// // 加入组播组 +// bool joined = multicastSocket->joinMulticastGroup(groupAddress); +// if (!joined) { +// qWarning() << "Failed to join multicast group"; +// } +// +// // 定期发送组播消息 +// connect(multicastTimer, &QTimer::timeout, this, &MainWidget::sendMulticastMessage); +// //multicastTimer->start(1000); // 每秒发送一次组播消息 +//#endif +//} +// +//MainWidget::~MainWidget() +//{ +//#if TEST_UDP_BROADCAST +// multicastTimer->stop(); +// multicastSocket->leaveMulticastGroup(QHostAddress("224.0.0.251")); +// multicastSocket->close(); +//#endif +// stopMdnsService(); +// for (auto timer : clientReadTimers) { +// timer->stop(); +// delete timer; +// } +// clientReadTimers.clear(); +// for (ClientHandler* handler : clients) { +// handler->deleteLater(); +// } +// server->close(); +// +// for (auto thread : clientThreads) { +// thread->quit(); +// thread->wait(); +// delete thread; +// } +// clientThreads.clear(); +// +// for (auto handler : clients_1) { +// handler->deleteLater(); +// } +// clients_1.clear(); +// +// for (auto timer : clientTimers) { +// timer->stop(); +// delete timer; +// } +// clientTimers.clear(); +// +// if (server->isListening()) { +// server->close(); +// } +// qDebug() << "MainWidget destroyed"; +//} +// +//void MainWidget::startClientReadTimer(int clientId) { +// qDebug() << "------ startClientReadTimer clientId:" << clientId; +// if (clientReadTimers.contains(clientId)) { +// clientReadTimers[clientId]->start(10); +// } +//} +// +//void MainWidget::stopClientReadTimer(int clientId) { +// qDebug() << "------ stopClientReadTimer clientId:" << clientId; +// if (clientReadTimers.contains(clientId)) { +// clientReadTimers[clientId]->stop(); +// } +//} +// +//void MainWidget::onCheckConnectionStatus() +//{ +// int activeThreadCount = clients_1.size(); +// //qDebug() << "------Number of active threads:" << activeThreadCount; +// for (auto it = clients_1.begin(); it != clients_1.end(); ++it) { +// int clientId = it.key(); +// ClientHandler* handler = it.value(); +// QTcpSocket* socket = handler->getSocket(); +// if (socket->state() != QTcpSocket::ConnectedState) { +// qCritical() << "--------------Connection lost for clientId:" << clientId << ". Socket state:" << socket->state(); +// emit handler->clientDisconnected(handler); +// } +// else { +// qDebug() << "Connection for clientId:" << clientId << "is active."; +// } +// } +//} +// +//void MainWidget::onCheckThreadStatus() +//{ +// //qDebug() << "Checking thread status..."; +// int activeThreadCount = clientThreads.size(); +// //qDebug() << "Number of active threads:" << activeThreadCount; +// for (auto it = clientThreads.begin(); it != clientThreads.end(); ++it) { +// int clientId = it.key(); +// QThread* thread = it.value(); +// ClientHandler* handler = nullptr; +// for (ClientHandler* h : clients) { +// if (h->getClientId() == clientId) { +// handler = h; +// break; +// } +// } +// if (handler && handler->isData_Stuck()) { +// qDebug() << "Thread for clientId:" << clientId << "is stuck."; +// // 处理线程卡住的情况,比如重启线程或记录更多日志 +// } +// else if (thread->isRunning()) { +// qDebug() << "isData_Stuck = :" << handler->isData_Stuck(); +// qDebug() << "Thread for clientId:" << clientId << "is running."; +// } +// else { +// qDebug() << "Thread for clientId:" << clientId << "is not running."; +// // 处理线程意外停止的情况 +// } +// } +//} +// +//// 打印线程池信息 +//void MainWidget::printThreadPoolInfo() +//{ +// QThreadPool* threadPool = QThreadPool::globalInstance(); +// //qDebug() << "Active threads:" << threadPool->activeThreadCount(); +// //qDebug() << "Max threads:" << threadPool->maxThreadCount(); +//} +// +//void MainWidget::setupTimerForThreadPoolInfo() +//{ +// QTimer* timer = new QTimer(this); +// connect(timer, &QTimer::timeout, this, &MainWidget::printThreadPoolInfo); +// timer->start(5000); // 每5秒打印一次线程池信息 +//} +// +//void MainWidget::readJsonConfig() +//{ +// frontBoardOneClickTest = readJson_frontBoardOneClickTest(); +// frontBoardTest = readJson_frontBoardTest(); +// frontBoardFuncConfig = readJson_frontBoardFuncConfig(); +// frontBoardDevInfoJson = readJson_frontDevInfo(); +// frontBoardLicenseJson = readJson_frontLicense(); +// backBoardDevInfoJson = readJson_backDevInfo(); +// testJsonConfig = readJson_testConfig(); +// funcJsonConfig = readJson_funcConfig(); +// +// getPicJson = readJson_getPic(); +// getVideoJson = readJson_getVideo(); +//} +// +//// 设置 UI +//void MainWidget::setupUI() +//{ +// startServerButton = new QPushButton("开始监听\n(Start Listening...)", this); +// startServerButton->setFixedSize(190, 70); // 设置宽度为 190 像素,高度为 70 像素 +// +// sendAllButton = new QPushButton("一键功能测试", this); +// sendAllButton->setFixedSize(190, 70); // 设置宽度为 190 像素,高度为 70 像素 +// sendAllButton->setEnabled(false); +// +// statusListWidget = new QListWidget(this); +// //statusListWidget->setMinimumSize(350, 880); +// statusListWidget->setMinimumSize(350, 680); +// statusListWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // 设置大小策略为扩展 +// +// QHBoxLayout* buttonLayout = new QHBoxLayout; +// buttonLayout->addWidget(startServerButton); +// buttonLayout->addWidget(sendAllButton); +// buttonLayout->addStretch(); +// +// QVBoxLayout* leftLayout = new QVBoxLayout; +// leftLayout->addLayout(buttonLayout); +// leftLayout->setStretch(0, 1); +// leftLayout->addWidget(statusListWidget); +// leftLayout->setStretch(1, 200); +// +// saveCheckBox = new QCheckBox("", this); +// selectFileButton = new QPushButton("Save", this); +// selectFileButton->setFixedSize(45, 28); +// clearLogButton = new QPushButton("Clear", this); +// clearLogButton->setFixedSize(55, 28); +// filePathLineEdit = new QLineEdit(this); +// filePathLineEdit->setFixedSize(250, 28); +// filePathLineEdit->setReadOnly(true); +// +// QHBoxLayout* fileLayout = new QHBoxLayout; +// fileLayout->addWidget(saveCheckBox); +// fileLayout->addWidget(selectFileButton); +// fileLayout->addWidget(filePathLineEdit); +// fileLayout->addWidget(clearLogButton); +// +// leftLayout->addLayout(fileLayout); +// leftLayout->setStretch(2, 1); +// +// // 读取 JSON 配置文件 +// readJsonConfig(); +// +// QGroupBox* groupBox = new QGroupBox("算法 license", this); +// QHBoxLayout* buttonRowLayout = new QHBoxLayout; +// for (int i = 0; i < frontBoardLicenseJson.size(); ++i) { +// QJsonObject item = frontBoardLicenseJson[i].toObject(); +// QString buttonText = item["lable"].toString(); +// QPushButton* button = new QPushButton(buttonText, this); +// button->setProperty("licenseIndex", i); +// buttonRowLayout->addWidget(button); +// connect(button, &QPushButton::clicked, this, &MainWidget::onLicenseButtonClicked); +// } +// licenseHwInfoEdit->setReadOnly(true); +// licenseHwInfoEdit->setText("This is a read-only text"); +// licenseHwInfoEdit->setFixedHeight(80); +// +// QVBoxLayout* groupBoxLayout_license = new QVBoxLayout; +// groupBoxLayout_license->addLayout(buttonRowLayout); +// groupBoxLayout_license->addWidget(licenseHwInfoEdit); +// groupBox->setLayout(groupBoxLayout_license); +// +// leftLayout->addWidget(groupBox); +// leftLayout->setStretch(3, 1); +// +// QWidget* leftContainer = new QWidget(this); +// leftContainer->setLayout(leftLayout); +// leftContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); +// +// QTabWidget* tabWidget = new QTabWidget(this); +// tabWidget->setFixedSize(900, 315); // 设置 QTabWidget 的固定大小 +// +// QGroupBox* frontDeviceInfoGroupBox = new QGroupBox("前板设备信息", this); +// QVBoxLayout* frontDeviceInfoLayout = new QVBoxLayout(frontDeviceInfoGroupBox); +// +// QVBoxLayout* frontDeviceLabelsLayout = new QVBoxLayout; +// for (const QJsonValue& value : frontBoardDevInfoJson) { +// QJsonObject item = value.toObject(); +// QString label = item["lable"].toString(); +// QString cmd = item["cmd"].toString(); +// QLabel* itemLabel = new QLabel(label, this); +// QLineEdit* itemLineEdit = new QLineEdit(this); +// itemLineEdit->setReadOnly(true); +// +// QHBoxLayout* itemLayout = new QHBoxLayout; +// itemLayout->addWidget(itemLabel); +// itemLayout->addWidget(itemLineEdit); +// +// frontDeviceLabelsLayout->addLayout(itemLayout); +// devInfoLineEdits[cmd] = itemLineEdit; +// } +// frontDeviceInfoLayout->addLayout(frontDeviceLabelsLayout); +// frontDeviceInfoLayout->addStretch(); +// +// QGroupBox* backDeviceInfoGroupBox = new QGroupBox("后板设备信息", this); +// QVBoxLayout* backDeviceInfoLayout = new QVBoxLayout(backDeviceInfoGroupBox); +// +// QVBoxLayout* backDeviceLabelsLayout = new QVBoxLayout; +// for (const QJsonValue& value : backBoardDevInfoJson) { +// QJsonObject item = value.toObject(); +// QString label = item["lable"].toString(); +// QString cmd = item["cmd"].toString(); +// QLabel* itemLabel = new QLabel(label, this); +// QLineEdit* itemLineEdit = new QLineEdit(this); +// itemLineEdit->setReadOnly(true); +// +// QHBoxLayout* itemLayout = new QHBoxLayout; +// itemLayout->addWidget(itemLabel); +// itemLayout->addWidget(itemLineEdit); +// +// backDeviceLabelsLayout->addLayout(itemLayout); +// devInfoLineEdits[cmd] = itemLineEdit; +// } +// backDeviceInfoLayout->addLayout(backDeviceLabelsLayout); +// backDeviceInfoLayout->addStretch(); +// +// QHBoxLayout* groupBoxLayout = new QHBoxLayout; +// groupBoxLayout->addWidget(frontDeviceInfoGroupBox, 1); +// groupBoxLayout->addWidget(backDeviceInfoGroupBox, 1); +// +// QWidget* functionTestTab = new QWidget; +// QVBoxLayout* functionTestLayout = new QVBoxLayout(functionTestTab); +// functionTestTab->setLayout(functionTestLayout); +// tabWidget->addTab(functionTestTab, "功能测试区"); +// +// QGridLayout* buttonGridLayout = new QGridLayout(); +// int buttonsPerRow = 7; // 每行显示的按键数量,根据需要调整 +// // 为每个 JSON 项目创建按键,并添加到 rightLayout +// // 先总共放 77 个按键 +// //qDebug() << "testJsonConfig.size():" << testJsonConfig.size(); +// for (int i = 0; i < frontBoardTest.size() + (77 - frontBoardTest.size()); ++i) { +// QJsonObject item = frontBoardTest[i].toObject(); +// QString buttonText = item["lable"].toString(); +// // 判断 buttonText 是否为空或者只有空白字符 +// if (buttonText.isEmpty()) { +// //qDebug() << "buttonText.isEmpty():" << buttonText.isEmpty(); +// buttonText = QString("Send Item %1").arg(i + 1); +// } +// QPushButton* button = new QPushButton(buttonText, this); +// button->setProperty("frontBoardTest", i); +// button->setFixedSize(110, 35); // 设置按键宽度和高度 +// connect(button, &QPushButton::clicked, this, &MainWidget::onSendItemClicked); +// +// int row = i / buttonsPerRow; +// int col = i % buttonsPerRow; +// buttonGridLayout->addWidget(button, row, col, Qt::AlignLeft); +// +// itemButtons.append(button); +// } +// +// QWidget* buttonContainer = new QWidget; +// buttonContainer->setLayout(buttonGridLayout); +// +// QScrollArea* scrollArea = new QScrollArea; +// scrollArea->setWidget(buttonContainer); +// scrollArea->setWidgetResizable(true); +// +// functionTestLayout->addWidget(scrollArea); +// +// QWidget* functionConfigTab = new QWidget; +// QVBoxLayout* functionConfigLayout = new QVBoxLayout(functionConfigTab); +// functionConfigTab->setLayout(functionConfigLayout); +// tabWidget->addTab(functionConfigTab, "功能配置区"); +// +// funcConfigLineEdit->setPlaceholderText("请输入配置参数..."); +// functionConfigLayout->addWidget(funcConfigLineEdit); +// +// QGridLayout* configButtonGridLayout = new QGridLayout(); +// +// for (int i = 0; i < frontBoardFuncConfig.size() + (77 - frontBoardFuncConfig.size()); ++i) { +// QJsonObject item = frontBoardFuncConfig[i].toObject(); +// QString buttonText = item["lable"].toString(); +// // 判断 buttonText 是否为空或者只有空白字符 +// if (buttonText.isEmpty()) { +// //qDebug() << "buttonText.isEmpty():" << buttonText.isEmpty(); +// buttonText = QString("Cfg Item %1").arg(i + 1); +// } +// QPushButton* button = new QPushButton(buttonText, this); +// button->setProperty("frontBoardFuncConfig", i); +// button->setFixedSize(110, 35); // 设置按键宽度和高度 +// connect(button, &QPushButton::clicked, this, &MainWidget::onSendFuncItemClicked); +// +// int row = i / buttonsPerRow; +// int col = i % buttonsPerRow; +// configButtonGridLayout->addWidget(button, row, col, Qt::AlignLeft); +// +// funcItemButtons.append(button); +// } +// +// QWidget* configButtonContainer = new QWidget; +// configButtonContainer->setLayout(configButtonGridLayout); +// +// QScrollArea* configScrollArea = new QScrollArea; +// configScrollArea->setWidget(configButtonContainer); +// configScrollArea->setWidgetResizable(true); +// +// functionConfigLayout->addWidget(configScrollArea); +// +// QTabWidget* tabWidget_media = new QTabWidget(this); +// QWidget* imageDisplayTab = new QWidget; +// QVBoxLayout* imageDisplayLayout = new QVBoxLayout(imageDisplayTab); +// imageDisplayTab->setLayout(imageDisplayLayout); +// tabWidget_media->addTab(imageDisplayTab, "图像显示区"); +// +// QVBoxLayout* imageButtonsColumnLayout = new QVBoxLayout; +// for (int i = 0; i < 5; ++i) { +// QHBoxLayout* imageButtonsRowLayout = new QHBoxLayout; +// for (int j = 0; j < 2; ++j) { +// QPushButton* button; +// if (i == 0 && j == 0) { +// button = new QPushButton(QString("IR"), this); +// button->setFixedSize(73, 50); +// imageButtonsRowLayout->addWidget(button); +// button->setProperty("getPicIndex", i * 2 + j); +// connect(button, &QPushButton::clicked, this, &MainWidget::onSendGetPicClicked); +// getPicButtons.append(button); +// continue; +// } +// else if (i == 0 && j == 1) { +// button = new QPushButton(QString("RGB"), this); +// button->setFixedSize(73, 50); +// imageButtonsRowLayout->addWidget(button); +// button->setProperty("getPicIndex", i * 2 + j); +// connect(button, &QPushButton::clicked, this, &MainWidget::onSendGetPicClicked); +// getPicButtons.append(button); +// continue; +// } +// +// button = new QPushButton(QString("Device %1").arg(i * 2 + j - 1), this); +// button->setFixedSize(73, 50); +// imageButtonsRowLayout->addWidget(button); +// button->setProperty("getPicIndex", i * 2 + j); +// connect(button, &QPushButton::clicked, this, &MainWidget::onSendGetPicClicked); +// getPicButtons.append(button); +// } +// imageButtonsColumnLayout->addLayout(imageButtonsRowLayout); +// } +// +// QHBoxLayout* lensesLayout = new QHBoxLayout; +// leftLens_imageLabel = new QLabel(this); +// rightLens_imageLabel = new QLabel(this); +// lensesLayout->addWidget(leftLens_imageLabel); +// lensesLayout->addWidget(rightLens_imageLabel); +// +// QHBoxLayout* imageAndButtonsLayout = new QHBoxLayout; +// imageAndButtonsLayout->addLayout(imageButtonsColumnLayout, 1); +// imageAndButtonsLayout->addLayout(lensesLayout, 4); +// imageDisplayLayout->addLayout(imageAndButtonsLayout); +// +// QWidget* videoDisplayTab = new QWidget; +// QVBoxLayout* videoDisplayLayout = new QVBoxLayout(videoDisplayTab); +// videoDisplayTab->setLayout(videoDisplayLayout); +// tabWidget_media->addTab(videoDisplayTab, "视频显示区"); +// +// QVBoxLayout* videoButtonsColumnLayout = new QVBoxLayout; +// for (int i = 0; i < 6; ++i) { +// QHBoxLayout* videoButtonsRowLayout = new QHBoxLayout; +// for (int j = 0; j < 2; ++j) { +// QPushButton* button; +// if (i == 0 && j == 0) { +// button = new QPushButton(QString("IR"), this); +// button->setFixedSize(73, 50); +// videoButtonsRowLayout->addWidget(button); +// button->setProperty("getVideoIndex", i * 2 + j); +// connect(button, &QPushButton::clicked, this, &MainWidget::onSendGetVideoClicked); +// getVideoButtons.append(button); +// continue; +// } +// else if (i == 0 && j == 1) { +// button = new QPushButton(QString("RGB"), this); +// button->setFixedSize(73, 50); +// videoButtonsRowLayout->addWidget(button); +// button->setProperty("getVideoIndex", i * 2 + j); +// connect(button, &QPushButton::clicked, this, &MainWidget::onSendGetVideoClicked); +// getVideoButtons.append(button); +// continue; +// } +// +// // 调价调焦窗口按键 +// if (i == 1 && j == 0) { +// button = new QPushButton(QString("大窗口播放视频"), this); +// button->setFixedSize(150, 50); +// button->setEnabled(false); +// videoButtonsRowLayout->addWidget(button); +// button->setProperty("getVideoIndex", i * 2 + j); +// connect(button, &QPushButton::clicked, this, &MainWidget::onOpenFocusWindowClicked); +// getVideoButtons.append(button); +// break; // 跳出内层循环,只添加一个按键 +// } +// +// //int adjustedIndex = (i > 1) ? i * 2 + j - 1 : i * 2 + j; +// button = new QPushButton(QString("Device %1").arg(i * 2 + j - 3), this); +// //button = new QPushButton(QString("Device %1").arg(adjustedIndex), this); +// button->setFixedSize(73, 50); +// videoButtonsRowLayout->addWidget(button); +// button->setProperty("getVideoIndex", i * 2 + j + 1); +// //button->setProperty("getVideoIndex", adjustedIndex); +// connect(button, &QPushButton::clicked, this, &MainWidget::onSendGetVideoClicked); +// getVideoButtons.append(button); +// } +// videoButtonsColumnLayout->addLayout(videoButtonsRowLayout); +// } +// +// QHBoxLayout* videoAndButtonsLayout = new QHBoxLayout; +// videoLabel = new QLabel(this); +// //videoLabel->setFixedSize(640, 480); +// videoAndButtonsLayout->addLayout(videoButtonsColumnLayout, 1); +// videoAndButtonsLayout->addWidget(videoLabel, 6); +// videoDisplayLayout->addLayout(videoAndButtonsLayout); +// +// QVBoxLayout* rightVerticalLayout = new QVBoxLayout; +// rightVerticalLayout->addLayout(groupBoxLayout, 2); +// rightVerticalLayout->addWidget(tabWidget, 2, Qt::AlignTop | Qt::AlignLeft); +// rightVerticalLayout->addWidget(tabWidget_media, 5); +// +// QHBoxLayout* mainLayout = new QHBoxLayout; +// // 参数二:伸缩因子,leftContainer:1/3 ,rightLayout:2/3 +// mainLayout->addWidget(leftContainer, 1, Qt::AlignTop | Qt::AlignLeft); // 设置 leftContainer 左上对齐 +// mainLayout->addLayout(rightVerticalLayout, 3); +// +// setLayout(mainLayout); +// setWindowTitle("SL100 工厂产测工具 - V0.0.1"); +// resize(1340, 1000); // 设置宽度为 1440 像素,高度为 900 像素 +// +// connect(startServerButton, &QPushButton::clicked, this, &MainWidget::onStartServerClicked); +// connect(sendAllButton, &QPushButton::clicked, this, &MainWidget::onSendAllClicked); +// connect(statusListWidget, &QListWidget::itemChanged, this, &MainWidget::scrollToBottom); +// connect(selectFileButton, &QPushButton::clicked, this, &MainWidget::onSelectFileButtonClicked); +// connect(clearLogButton, &QPushButton::clicked, this, &MainWidget::onclearLogButtonClicked); +// connect(saveCheckBox, &QCheckBox::stateChanged, this, &MainWidget::onSaveCheckBoxStateChanged); +//} +// +//void MainWidget::onSelectFileButtonClicked() +//{ +// QString filePath = QFileDialog::getSaveFileName(this, tr("选择文件路径"), "", tr("Text Files (*.txt);;All Files (*)")); +// if (!filePath.isEmpty()) { +// filePathLineEdit->setText(filePath); +// } +//} +// +//void MainWidget::onclearLogButtonClicked() +//{ +// statusListWidget->clear(); +//} +// +//void MainWidget::onSaveCheckBoxStateChanged(int state) +//{ +// if (state == Qt::Checked) { +// saveStatusListToFile(filePathLineEdit->text()); +// } +//} +// +//void MainWidget::saveStatusListToFile(const QString& filePath) +//{ +// if (filePath.isEmpty()) { +// return; +// } +// +// QFile file(filePath); +// if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { +// return; +// } +// +// QTextStream out(&file); +// for (int i = 0; i < statusListWidget->count(); ++i) { +// QListWidgetItem* item = statusListWidget->item(i); +// out << item->text() << "\n"; +// } +// file.close(); +//} +// +//void MainWidget::onStartTimeout(int clientId, int timeout) +//{ +// //qDebug() << "---------------> onStartTimeout :" << clientId << "timeout :" << timeout; +// if (clientTimers.contains(clientId)) { +// QTimer* timer = clientTimers[clientId]; +// if (timeout > 0) { +// timer->start(timeout); +// } +// else { +// timer->stop(); +// } +// } +//} +// +//void MainWidget::scrollToBottom() +//{ +// statusListWidget->scrollToBottom(); +//} +// +//void MainWidget::onDisconnectClient(int clientId) +//{ +// for (ClientHandler* handler : clients) { +// if (handler->getClientId() == clientId) { +// // 断开与该客户端的连接 +// handler->getSocket()->disconnectFromHost(); +// handler->getSocket()->waitForDisconnected(); +// clients.removeOne(handler); +// if (clientTimers.contains(clientId)) { +// QTimer* timer = clientTimers[clientId]; +// timer->stop(); +// delete timer; +// clientTimers.remove(clientId); +// } +// handler->deleteLater(); +// // 更新连接数并更新按键文本 +// connectedClientsCount--; +// updateServerButtonText(); +// +// break; +// } +// } +// +// // 启动 mDNS 服务广播 +// startMdnsService(); +//} +// +//// 处理客户端断开连接信号 +//void MainWidget::onClientDisconnected(ClientHandler* handler) +//{ +// int clientId = handler->getClientId(); +// if (clientTimers.contains(clientId)) { +// QTimer* timer = clientTimers[clientId]; +// timer->stop(); +// delete timer; +// clientTimers.remove(clientId); +// } +// +// clients.removeOne(handler); +// /* +// 将 ClientHandler 对象的删除操作放入事件队列中,等待事件循环处理。 +// 在事件循环执行时,会安全地删除 ClientHandler 对象,这包括释放其占用的资源和内存 +// */ +// handler->deleteLater(); // 延迟删除 ClientHandler 对象 +// +// // 更新连接数并更新按键文本 +// connectedClientsCount--; +// if (nextClientId <= 2) nextClientId--; +// deviceConnected = true; +// updateServerButtonText(); +//} +// +//// 更新按键文本的函数 +//void MainWidget::updateServerButtonText() +//{ +// if (deviceConnected) { +// deviceConnected = false; +// startServerButton->setText(tr("正在监听(Listening)")); +// } +// else if (connectedClientsCount == 0) { +// startServerButton->setText(tr("开始监听\n(Start Listening...)")); +// } +// else { +// startServerButton->setText(tr("正在监听(Listening)\n%1 台设备连接").arg(connectedClientsCount)); +// } +//} +// +//void MainWidget::onLicenseButtonClicked() +//{ +// if (connectedClientsCount) { +// QPushButton* button = qobject_cast(sender()); +// if (button) { +// int index = button->property("licenseIndex").toInt(); +// if (index >= 0 && index < frontBoardLicenseJson.size()) { +// QJsonObject jsonObject = frontBoardLicenseJson[index].toObject(); +// //QString jsonString = QJsonDocument(jsonObject).toJson(QJsonDocument::Compact); +// //qDebug() << "license Button clicked, sending JSON:" << jsonString; +// unsigned char license_info[PIX_LICENCE_BYTES] = { 0 }; +// if (jsonObject["lable"].toString() == "get_license") { +// LicenseConfirmWindow dialog("你确定要获取此授权吗?"); +// if (dialog.exec() == QDialog::Accepted) +// licenseGenerate(license_info, license_info); +// } +// else { +// if (jsonObject["lable"].toString() == "write_license") { +// LicenseConfirmWindow dialog("你确定要发送此授权吗?"); +// if (dialog.exec() == QDialog::Accepted) +// licenseGenerate(license_info, license_info); +// } +// for (ClientHandler* handler : clients) { +// handler->sendLicenseItem(index); +// } +// } +// } +// } +// } +// else { +// QListWidgetItem* listItem = new QListWidgetItem(QString("No device is connected !!!"), statusListWidget); +// listItem->setBackground(Qt::red); +// } +//} +// +//void MainWidget::startMdnsService() +//{ +// QString serviceName = "My Mdns Broadcast Service"; // 替换为你想要的服务名称 +// QString serviceType = "_my-service._tcp"; // 替换为实际的服务类型 +// quint16 port = 12412; // 使用的端口号,确保与 TCP 服务器监听的端口号一致 +// mServiceProvider->startServiceBroadcast(serviceName, serviceType, port); +//} +// +//void MainWidget::stopMdnsService() +//{ +// mServiceProvider->stopServiceBroadcast(); +//} +// +//#if TEST_UDP_BROADCAST +//void MainWidget::sendMulticastMessage() +//{ +// QByteArray datagram = "Test multicast message from MainWidget"; +// QHostAddress groupAddress("224.0.0.251"); +// quint16 port = 5353; +// +// qint64 sentBytes = multicastSocket->writeDatagram(datagram, groupAddress, port); +// if (sentBytes == -1) { +// qWarning() << "Failed to send multicast message:" << multicastSocket->errorString(); +// } +// else { +// qDebug() << "Multicast message sentBytes:" << sentBytes; +// qDebug() << "Multicast message sent:" << datagram; +// } +//} +//#endif +// +//// 处理开始服务器按键点击事件 +//void MainWidget::onStartServerClicked() +//{ +// if (!server->isListening()) { +// // 启动 mDNS 服务广播 +// startMdnsService(); +//#if TEST_UDP_BROADCAST +// sendMulticastMessage(); +//#endif +// // 根据需要修改 +// // QHostAddress specifiedIpAddress("10.10.10.253"); +// quint16 specifiedPort = 12412; +// qDebug() << "" << specifiedPort; +// if (server->listen(QHostAddress::Any, specifiedPort)) { +// startServerButton->setText(tr("正在监听(Listening)")); +// // 设置按钮背景色为绿色 +// startServerButton->setStyleSheet("background-color: green;"); +// sendAllButton->setEnabled(true); +// +// // 启动 mDNS 服务广播 +// //startMdnsService(); +// } +// else { +// qDebug() << "Failed to start server. Error:" << server->errorString(); +// } +// } +// else { +// server->close(); +// startServerButton->setText(tr("开始监听\n(Start Listening...)")); +// startServerButton->setStyleSheet(""); +// sendAllButton->setEnabled(false); +// +// // 停止 mDNS 服务广播 +// //stopMdnsService(); +// } +//} +// +//// 处理发送获取设备信息按键点击事件 +//void MainWidget::onSendGetDevInfoClicked() +//{ +// if (connectedClientsCount) { +// QPushButton* button = qobject_cast(sender()); +// int itemIndex = button->property("deviceInfoIndex").toInt(); +// if (itemIndex + 1 > connectedClientsCount) { +// QListWidgetItem* listItem = new QListWidgetItem(QString("No device %1 is connected !!!").arg(itemIndex + 1), statusListWidget); +// listItem->setBackground(Qt::red); +// } +// else { +// for (ClientHandler* handler : clients) { +// handler->sendGetDevInfoItem(itemIndex); +// } +// } +// } +// else { +// QListWidgetItem* listItem = new QListWidgetItem(QString("No device is connected !!!"), statusListWidget); +// listItem->setBackground(Qt::red); +// } +//} +// +//// 处理发送取图按键点击事件 +//void MainWidget::onSendGetPicClicked() +//{ +// if (connectedClientsCount) { +// QPushButton* button = qobject_cast(sender()); +// int itemIndex = button->property("getPicIndex").toInt(); +// +// if (itemIndex < 2) { +// button->setStyleSheet("background-color: green;"); +// if (lastClickedGetPicCamIndex != -1 && lastClickedGetPicCamIndex != itemIndex) { +// getPicButtons[lastClickedGetPicCamIndex]->setStyleSheet(""); +// } +// lastClickedGetPicCamIndex = itemIndex; +// } +// else { +// if (lastClickedGetPicCamIndex == -1) { +// QListWidgetItem* listItem = new QListWidgetItem(QString("Please select IR or RGB lens to get image!!!"), statusListWidget); +// listItem->setBackground(Qt::red); +// } +// else { +// if (itemIndex - 2 >= connectedClientsCount) { +// QListWidgetItem* listItem = new QListWidgetItem(QString("No device %1 is connected !!!").arg(itemIndex - 1), statusListWidget); +// listItem->setBackground(Qt::red); +// } +// else { +// button->setStyleSheet("background-color: green;"); +// if (lastClickedGetPicDevIndex != -1 && lastClickedGetPicDevIndex != itemIndex) { +// getPicButtons[lastClickedGetPicDevIndex]->setStyleSheet(""); +// } +// lastClickedGetPicDevIndex = itemIndex; +// //QMutexLocker locker(&mutex); +// for (ClientHandler* handler : clients) { +// handler->sendGetPicItem(itemIndex - 2, lastClickedGetPicCamIndex); +// } +// } +// } +// } +// } +// else { +// QListWidgetItem* listItem = new QListWidgetItem(QString("No device is connected !!!"), statusListWidget); +// listItem->setBackground(Qt::red); +// } +//} +// +//// 处理发送拉视频流按键点击事件 +//void MainWidget::onSendGetVideoClicked() +//{ +// if (connectedClientsCount) { +// QPushButton* button = qobject_cast(sender()); +// int itemIndex = button->property("getVideoIndex").toInt(); +// +// if (itemIndex < 2) { +// button->setStyleSheet("background-color: green;"); +// if (lastClickedGetVideoCamIndex != -1 && lastClickedGetVideoCamIndex != itemIndex) { +// getVideoButtons[lastClickedGetVideoCamIndex]->setStyleSheet(""); +// } +// lastClickedGetVideoCamIndex = itemIndex; +// } +// else { +// if (lastClickedGetVideoCamIndex == -1) { +// QListWidgetItem* listItem = new QListWidgetItem(QString("Please select IR or RGB lens to get video!!!"), statusListWidget); +// listItem->setBackground(Qt::red); +// } +// else { +// if (itemIndex - 6 >= connectedClientsCount) { +// QListWidgetItem* listItem = new QListWidgetItem(QString("No device %1 is connected !!!").arg(itemIndex - 4), statusListWidget); +// listItem->setBackground(Qt::red); +// } +// else { +// button->setStyleSheet("background-color: green;"); +// if (lastClickedGetVideoDevIndex != -1 && lastClickedGetVideoDevIndex != itemIndex) { +// getVideoButtons[lastClickedGetVideoDevIndex]->setStyleSheet(""); +// } +// lastClickedGetVideoDevIndex = itemIndex; +// //QMutexLocker locker(&mutex); +// for (ClientHandler* handler : clients) { +// handler->sendGetVideoItem(itemIndex - 5, 1); +// } +// getVideoButtons[2]->setEnabled(true); +// } +// } +// } +// } +// else { +// QListWidgetItem* listItem = new QListWidgetItem(QString("No device is connected !!!"), statusListWidget); +// listItem->setBackground(Qt::red); +// } +//} +// +//void MainWidget::onOpenFocusWindowClicked() +//{ +// QPushButton* button = qobject_cast(sender()); +// if (button) { +// int itemIndex = button->property("getVideoIndex").toInt(); +// //qDebug() << "New Button clicked with itemIndex:" << itemIndex; +// if (itemIndex == 2) { +// // 创建并显示新窗口 +// NewButtonDialog dialog(this); +// dialog.exec(); +// } +// } +//} +// +//// 处理一键发送按键点击事件 +//void MainWidget::onSendAllClicked() +//{ +// if (connectedClientsCount) { +// QMutexLocker locker(&mutex); +// isSendingAll = !isSendingAll; +// if (isSendingAll) { +// sendAllButton->setText("一键功能测试中...\n再次点击取消"); +// sendAllButton->setStyleSheet("background-color: green;"); +// manualSend = true; +// for (ClientHandler* handler : clients) { +// // 重置索引 +// handler->resetCurrentItemIndex(); +// handler->sendNextItem(); +// //handler->sendDevInfoItem(); +// } +// } +// else { +// sendAllButton->setText("一键功能测试"); +// sendAllButton->setStyleSheet("background-color: white;"); +// manualSend = false; +// for (ClientHandler* handler : clients) { +// handler->resetCurrentItemIndex(); +// } +// } +// } +// else { +// QListWidgetItem* listItem = new QListWidgetItem(QString("No device is connected !!!"), statusListWidget); +// listItem->setBackground(Qt::red); +// } +//} +// +//// 处理单独发送功能项按键点击事件 +//void MainWidget::onSendFuncItemClicked() +//{ +// if (connectedClientsCount) { +// QPushButton* button = qobject_cast(sender()); +// int itemIndex = button->property("frontBoardFuncConfig").toInt(); +// for (ClientHandler* handler : clients) { +// QString text = funcConfigLineEdit->text(); +// qDebug() << "Text in funcConfigLineEdit:" << text; +// handler->sendFuncItem(itemIndex, text); +// } +// } +// else { +// QListWidgetItem* listItem = new QListWidgetItem(QString("No device is connected !!!"), statusListWidget); +// listItem->setBackground(Qt::red); +// } +//} +// +//// 处理单独发送项按键点击事件 +//void MainWidget::onSendItemClicked() +//{ +// if (connectedClientsCount) { +// QPushButton* button = qobject_cast(sender()); +// int itemIndex = button->property("frontBoardTest").toInt(); +// for (ClientHandler* handler : clients) { +// handler->sendItem(itemIndex); +// } +// } +// else { +// QListWidgetItem* listItem = new QListWidgetItem(QString("No device is connected !!!"), statusListWidget); +// listItem->setBackground(Qt::red); +// } +//} +// +//// 处理状态更新信号 +//void MainWidget::onStatusUpdated(const QString& client, int itemIndex, int FuncItemIndex, +// bool success, const QString& itemData, const QString& funcItemData) +//{ +// int clientId = -1; +// // 遍历所有的 ClientHandler,找到匹配的 client +// for (ClientHandler* handler : clients) { +// if (handler->getClientAddress() == client) { +// clientId = handler->getClientId(); +// QString lable = handler->getCurrentItemLable(); +// lable = handler->getCurrentFuncItemLable(); +// break; +// } +// else +// { +// //qDebug() << "" << __FUNCTION__ << "handler->getClientAddress() != client" ; +// } +// } +// //qDebug() << "itemIndex :" << itemIndex; +// //qDebug() << "FuncItemIndex:" << FuncItemIndex; +// if (itemIndex > 0) { +// QListWidgetItem* listItem = new QListWidgetItem(QString("device ID: %1 - Item %2: %3 ---> %4") +// .arg(clientId) +// .arg(itemIndex) +// .arg(itemData) // data 字段 +// .arg(success ? "OK" : "NG"), statusListWidget); +// +// listItem->setBackground(success ? Qt::green : Qt::red); +// statusListWidget->addItem(listItem); +// } +// else if (FuncItemIndex > 0) { +// QListWidgetItem* listItem = new QListWidgetItem(QString("device ID: %1 - funcItem %2: %3 ---> %4") +// .arg(clientId) +// .arg(FuncItemIndex) +// .arg(funcItemData) // data 字段 +// .arg(success ? "OK" : "NG"), statusListWidget); +// +// listItem->setBackground(success ? Qt::green : Qt::red); +// statusListWidget->addItem(listItem); +// } +// statusListWidget->scrollToBottom(); +//} +// +//// json文件里面的配置项都测试结束 +//void MainWidget::onAllItemsProcessed(const QString& client, int itemsProcessedCount) +//{ +// isSendingAll = false; +// sendAllButton->setText("一键功能测试"); +// sendAllButton->setStyleSheet("background-color: white;"); +// //qDebug() << "onAllItemsProcessed called for client:" << client << "itemsProcessedCount:" << itemsProcessedCount; +// int clientId = -1; +// for (ClientHandler* handler : clients) { +// if (handler->getClientAddress() == client) { +// clientId = handler->getClientId(); +// qDebug() << "Current clientId:" << clientId; +// break; +// } +// else +// { +// //qDebug() << "" << __FUNCTION__ << "handler->getClientAddress() != client"; +// } +// } +// +// QListWidgetItem* listItem = new QListWidgetItem(QString("device ID:-%1 ---> All %2 items test completed !!!") +// .arg(clientId) +// .arg(itemsProcessedCount), statusListWidget); +// statusListWidget->addItem(listItem); +// statusListWidget->scrollToBottom(); +//} diff --git a/FactoryTestTool/SourceCode/Widget/MainWidget.h b/FactoryTestTool/SourceCode/Widget/MainWidget.h index d3e95d5..884de3a 100644 --- a/FactoryTestTool/SourceCode/Widget/MainWidget.h +++ b/FactoryTestTool/SourceCode/Widget/MainWidget.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -23,13 +24,25 @@ #include #include #include +#include #include #include #include +#include +#include "../Json/readJsonFile.h" +#include "../RecvDataHandler/RecvDataHandler.h" +#include "../LicenseGenerate/LicenseGenerate.h" +#include "../LicenseGenerate/LicenseConfirmWindow.h" +#include "../Network/mdns/servicemodel.h" #include "../Network/ClientHandler.h" #include "FocusWindow.h" +// 用于测试 UDP 组播实现 mdns 功能 非标准 mdns +#define TEST_UDP_BROADCAST 0 + +#define TCP_CONNECT_PORT 12412 + class MainWidget : public QWidget { Q_OBJECT @@ -81,7 +94,9 @@ private slots: void onCheckConnectionStatus(); void startClientReadTimer(int clientId); void stopClientReadTimer(int clientId); - +#if TEST_UDP_BROADCAST + void sendMulticastMessage(); +#endif private: // 读取 test JSON 配置文件 @@ -95,62 +110,77 @@ private: void onDisconnectClient(int clientId); void setupTimerForThreadPoolInfo(); - bool isSendingAll; // 一键功能测试 状态 - QTcpServer* server; // TCP 服务器 - QJsonArray frontBoardOneClickTest; // 前板一键功能测试 JSON - QJsonArray frontBoardTest; // 前板单项测试 JSON - QJsonArray frontBoardFuncConfig; // 前板功能配置参数 JSON - QJsonArray frontBoardDevInfoJson; // 前板设备信息参数 JSON - QJsonArray frontBoardLicenseJson; // 前板License JSON - QJsonArray backBoardDevInfoJson; // 后板设备信息参数 JSON - QJsonArray testJsonConfig; // 功能测试区 JSON 配置 - QJsonArray funcJsonConfig; // 功能配置区 JSON 配置 - QJsonArray getDevInfoJson; // 获取设备信息 JSON 配置 - QJsonArray getPicJson; // 发送取图指令 JSON 配置 - QJsonArray getVideoJson; // 发送拉视频指令 JSON 配置 - QVBoxLayout* mainLayout; // 主布局 - QListWidget* statusListWidget; // 状态列表 - QPushButton* startServerButton; // 开始服务器按键 - QPushButton* focusWindowsButton; // 调焦窗口按键 - QPushButton* sendAllButton; // 一键发送按键 - QThreadPool threadPool; // 线程池 - QMutex mutex; // 互斥锁 - int nextClientId; // 新增的客户端编号 - int connectedClientsCount = 0; // 连接客户端的数量 - bool manualSend; // 判断是否是手动触发的发送 - bool deviceConnected = false; // 判断是否有设备连接过 + bool isSendingAll; // 一键功能测试 状态 + QTcpServer* server; // TCP 服务器 + //QUdpSocket* udpSocket; + QJsonArray frontBoardOneClickTest; // 前板一键功能测试 JSON + QJsonArray frontBoardTest; // 前板单项测试 JSON + QJsonArray frontBoardFuncConfig; // 前板功能配置参数 JSON + QJsonArray frontBoardDevInfoJson; // 前板设备信息参数 JSON + QJsonArray frontBoardLicenseJson; // 前板License JSON + QJsonArray backBoardOneClickTest; // 后板一键功能测试 JSON + QJsonArray backBoardTest; // 后板单项测试 JSON + QJsonArray backBoardFuncConfig; // 后板功能配置参数 JSON + QJsonArray backBoardDevInfoJson; // 后板设备信息参数 JSON + QJsonArray testJsonConfig; // 功能测试区 JSON 配置 + QJsonArray funcJsonConfig; // 功能配置区 JSON 配置 + QJsonArray getDevInfoJson; // 获取设备信息 JSON 配置 + QJsonArray getPicJson; // 发送取图指令 JSON 配置 + QJsonArray getVideoJson; // 发送拉视频指令 JSON 配置 + QVBoxLayout* mainLayout; // 主布局 + QListWidget* statusListWidget; // 状态列表 + QPushButton* startServerButton; // 开始服务器按键 + QPushButton* focusWindowsButton; // 调焦窗口按键 + QPushButton* sendAllButton; // 一键发送按键 + QThreadPool threadPool; // 线程池 + QMutex mutex; // 互斥锁 + int nextClientId; // 新增的客户端编号 + int connectedClientsCount = 0; // 连接客户端的数量 + bool manualSend; // 判断是否是手动触发的发送 + bool deviceConnected = false; // 判断是否有设备连接过 - QVector itemButtons; // 项目按键集合 - QVector funcItemButtons; // 功能配置项目按键集合 - QVector getPicButtons; // 保存两个取图的摄像头的按键的指针 - QVector getVideoButtons; // 保存拉视频设备的按键的指针 - QList clients; // 客户端处理器集合 - QMap clientTimers; // 每个客户端的定时器 + QVector itemButtons; // 项目按键集合 + QVector funcItemButtons; // 功能配置项目按键集合 + QVector getPicButtons; // 保存两个取图的摄像头的按键的指针 + QVector getVideoButtons; // 保存拉视频设备的按键的指针 + QList clients; // 客户端处理器集合 + QMap clientTimers; // 每个客户端的定时器 + QMap devInfoLineEdits;// msg_id 和对应的 QLineEdit 的映射关系 - QCheckBox* saveCheckBox; // 保存文件复选框 - QPushButton* selectFileButton; // Save Log 按键 - QPushButton* clearLogButton; // clear Log 按键 - QLineEdit* filePathLineEdit; // 文件路径显示 - QLabel* leftLens_imageLabel; // 左边镜头图像显示 - QLabel* rightLens_imageLabel; // 右边镜头图像显示 - QLabel* videoLabel; // 视频显示 - QLineEdit* funcConfigLineEdit; // 功能配置编辑框 - - QTabWidget* tabWidget; // 标签页 - QWidget* functionTestArea; // 功能测试区 - QWidget* functionConfigArea; // 功能配置区 + QCheckBox* saveCheckBox; // 保存文件复选框 + QPushButton* selectFileButton; // Save Log 按键 + QPushButton* clearLogButton; // clear Log 按键 + QLineEdit* filePathLineEdit; // 文件路径显示 + QLabel* leftLens_imageLabel; // 左边镜头图像显示 + QLabel* rightLens_imageLabel; // 右边镜头图像显示 + QLabel* videoLabel; // 视频显示 + QLineEdit* funcConfigLineEdit; // 功能配置编辑框 + QTextEdit* licenseHwInfoEdit; // 获取license的硬件信息 + MainWidget* mainWidget; + QTabWidget* tabWidget; // 标签页 + QWidget* functionTestArea; // 功能测试区 + QWidget* functionConfigArea; // 功能配置区 - int lastClickedGetPicCamIndex; // 记录上一次点击取图的摄像头的按键索引 - int lastClickedGetPicDevIndex; // 记录上一次点击取图的设备的按键索引 - int lastClickedGetVideoCamIndex; // 记录上一次点击拉视频的摄像头的按键索引 - int lastClickedGetVideoDevIndex; // 记录上一次点击拉视频的设备的按键索引 + int lastClickedGetPicCamIndex; // 记录上一次点击取图的摄像头的按键索引 + int lastClickedGetPicDevIndex; // 记录上一次点击取图的设备的按键索引 + int lastClickedGetVideoCamIndex; // 记录上一次点击拉视频的摄像头的按键索引 + int lastClickedGetVideoDevIndex; // 记录上一次点击拉视频的设备的按键索引 - QTimer* threadStatusTimer; - QTimer* connectionStatusCheckTimer; // 添加检查连接状态的定时器 - QMap clientThreads; + QTimer* threadStatusTimer; // 检查线程状态的定时器 + QTimer* connectionStatusCheckTimer; // 检查连接状态的定时器 + QMap clientThreads; + QMap clientReadTimers; QMap clients_1; - QMap clientReadTimers; + + ServiceProvider* mServiceProvider; + QTimer* mdnsTimer; + void startMdnsService(); + void stopMdnsService(); +#if TEST_UDP_BROADCAST + QUdpSocket* multicastSocket; + QTimer* multicastTimer; +#endif }; #endif // MAINWIDGET_H diff --git a/FactoryTestTool/ThirdParty/qmdnsengine-master/.gitignore b/FactoryTestTool/ThirdParty/qmdnsengine-master/.gitignore new file mode 100644 index 0000000..02821b4 --- /dev/null +++ b/FactoryTestTool/ThirdParty/qmdnsengine-master/.gitignore @@ -0,0 +1 @@ +/CMakeLists.txt.user diff --git a/FactoryTestTool/ThirdParty/qmdnsengine-master/CMakeLists.txt b/FactoryTestTool/ThirdParty/qmdnsengine-master/CMakeLists.txt new file mode 100644 index 0000000..10e090b --- /dev/null +++ b/FactoryTestTool/ThirdParty/qmdnsengine-master/CMakeLists.txt @@ -0,0 +1,86 @@ +cmake_minimum_required(VERSION 3.2.0) +project(qmdnsengine) + +set(PROJECT_NAME "QMdnsEngine") +set(PROJECT_DESCRIPTION "Multicast DNS library for Qt applications") +set(PROJECT_AUTHOR "Nathan Osman") +set(PROJECT_URL "https://github.com/nitroshare/qmdnsengine") + +set(PROJECT_VERSION_MAJOR 0) +set(PROJECT_VERSION_MINOR 2) +set(PROJECT_VERSION_PATCH 0) +set(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}) + +# Build a shared library by default +option(BUILD_SHARED_LIBS "Build QMdnsEngine as a shared library" ON) + +set(BIN_INSTALL_DIR bin CACHE STRING "Binary installation directory relative to the install prefix") +set(LIB_INSTALL_DIR lib CACHE STRING "Library installation directory relative to the install prefix") +set(INCLUDE_INSTALL_DIR include CACHE STRING "Header installation directory relative to the install prefix") + +set(DOC_INSTALL_DIR share/doc/qmdnsengine CACHE STRING "Documentation installation directory relative to the install prefix") +set(EXAMPLES_INSTALL_DIR "${LIB_INSTALL_DIR}/qmdnsengine/examples" CACHE STRING "Examples installation directory relative to the install prefix") + +if (CMAKE_PREFIX_PATH) + message( STATUS "QMdnsEngine: Use CMAKE_PREFIX_PATH to locate Qt version to be used: ${CMAKE_PREFIX_PATH}" ) +endif() + +find_package(QT NAMES Qt6 Qt5 COMPONENTS Network REQUIRED) +message( STATUS "QMdnsEngine: Found Qt Version: ${QT_VERSION}" ) + +if (${QT_VERSION_MAJOR} GREATER_EQUAL 6 ) + SET(QT_MIN_VERSION "6.0.0") +else() + SET(QT_MIN_VERSION "5.7.0") +endif() + +if ( "${QT_VERSION}" VERSION_LESS "${QT_MIN_VERSION}" ) + message( FATAL_ERROR "Your Qt version is to old! Minimum required ${QT_MIN_VERSION}" ) +endif() + +find_package(Qt${QT_VERSION_MAJOR} ${QT_VERSION} COMPONENTS Network REQUIRED) + +message( STATUS "QMdnsEngine: Qt version used: ${QT_VERSION}" ) + +set(CMAKE_AUTOMOC ON) + +# Add compilation flags for warnings +if (MSVC) + add_compile_options(/W4) +else() + add_compile_options(-Wall -Wextra -pedantic) +endif() + +add_subdirectory(src) + +option(BUILD_DOC "Build Doxygen documentation" OFF) +if(BUILD_DOC) + find_package(Doxygen REQUIRED) + add_subdirectory(doc) +endif() + +option(BUILD_EXAMPLES "Build example applications" OFF) +if(BUILD_EXAMPLES) + find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets REQUIRED) + add_subdirectory(examples) +endif() + +option(BUILD_TESTS "Build test suite" OFF) +if(BUILD_TESTS) + find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Test REQUIRED) + enable_testing() + add_subdirectory(tests) +endif() + +set(CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME}") +set(CPACK_PACKAGE_VENDOR "${PROJECT_AUTHOR}") +set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) +set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) +set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) + +set(CPACK_COMPONENT_DOCUMENTATION_DISPLAY_NAME "Documentation") +set(CPACK_COMPONENT_DOCUMENTATION_DESCRIPTION "Documentation generated for the library") +set(CPACK_COMPONENT_EXAMPLES_DISPLAY_NAME "Examples") +set(CPACK_COMPONENT_EXAMPLES_DESCRIPTION "Sample applications using the library") + +include(CPack) diff --git a/FactoryTestTool/ThirdParty/qmdnsengine-master/LICENSE.txt b/FactoryTestTool/ThirdParty/qmdnsengine-master/LICENSE.txt new file mode 100644 index 0000000..6634782 --- /dev/null +++ b/FactoryTestTool/ThirdParty/qmdnsengine-master/LICENSE.txt @@ -0,0 +1,9 @@ +The MIT License (MIT) + +Copyright (c) 2018 Nathan Osman + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/FactoryTestTool/ThirdParty/qmdnsengine-master/README.md b/FactoryTestTool/ThirdParty/qmdnsengine-master/README.md new file mode 100644 index 0000000..f9a3d28 --- /dev/null +++ b/FactoryTestTool/ThirdParty/qmdnsengine-master/README.md @@ -0,0 +1,12 @@ +## QMdnsEngine + +[![Build Status](https://ci.quickmediasolutions.com/job/qmdnsengine/badge/icon)](https://ci.quickmediasolutions.com/job/qmdnsengine/) +[![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](http://opensource.org/licenses/MIT) + +This library provides an implementation of multicast DNS as per [RFC 6762](https://tools.ietf.org/html/rfc6762). + +### Documentation + +To learn more about building and using the library, please visit this page: + +https://ci.quickmediasolutions.com/job/qmdnsengine-documentation/doxygen/ diff --git a/FactoryTestTool/ThirdParty/qmdnsengine-master/doc/CMakeLists.txt b/FactoryTestTool/ThirdParty/qmdnsengine-master/doc/CMakeLists.txt new file mode 100644 index 0000000..da22616 --- /dev/null +++ b/FactoryTestTool/ThirdParty/qmdnsengine-master/doc/CMakeLists.txt @@ -0,0 +1,10 @@ +configure_file(Doxyfile.in "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile") + +add_custom_target(doc ALL + "${DOXYGEN_EXECUTABLE}" \"${CMAKE_CURRENT_BINARY_DIR}/Doxyfile\" +) + +install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/html" + DESTINATION "${DOC_INSTALL_DIR}" + COMPONENT documentation +) diff --git a/FactoryTestTool/ThirdParty/qmdnsengine-master/doc/Doxyfile.in b/FactoryTestTool/ThirdParty/qmdnsengine-master/doc/Doxyfile.in new file mode 100644 index 0000000..2d348ab --- /dev/null +++ b/FactoryTestTool/ThirdParty/qmdnsengine-master/doc/Doxyfile.in @@ -0,0 +1,2494 @@ +# Doxyfile 1.8.13 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv +# for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "@PROJECT_NAME@" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = "@PROJECT_VERSION@" + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = "@PROJECT_DESCRIPTION@" + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = NO + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = NO + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = "@CMAKE_SOURCE_DIR@/src/include" + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: +# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: +# Fortran. In the later case the parser tries to guess whether the code is fixed +# or free formatted code, this is the default for Fortran type files), VHDL. For +# instance to make doxygen treat .inc files as Fortran files (default is PHP), +# and .f files as C (default is Fortran), use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 0. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 0 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO, these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES, upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = YES + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = YES + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = NO + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = YES + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = "@CMAKE_CURRENT_SOURCE_DIR@/index.md" "@CMAKE_SOURCE_DIR@/src/include/qmdnsengine" + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f \ + *.for \ + *.tcl \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = "@CMAKE_CURRENT_SOURCE_DIR@/index.md" + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see http://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the +# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the +# cost of reduced performance. This can be particularly helpful with template +# rich C++ code for which doxygen's built-in parser lacks the necessary type +# information. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse-libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = NO + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = "@CMAKE_CURRENT_SOURCE_DIR@/overrides.css" + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the master .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 1 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /