Files
allwpilib/wpinet/src/main/native/windows/MulticastServiceAnnouncer.cpp

215 lines
6.8 KiB
C++
Raw Normal View History

// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#ifndef UNICODE
#define UNICODE
#endif
2025-11-07 19:56:21 -05:00
#include "wpi/net/MulticastServiceAnnouncer.h"
#include <Windows.h>
#include <WinDNS.h>
2024-09-20 17:43:39 -07:00
#include <memory>
#include <string>
2024-09-20 17:43:39 -07:00
#include <utility>
#include <vector>
2025-11-07 19:57:55 -05:00
#include "wpi/net/hostname.hpp"
2025-11-07 19:56:21 -05:00
#include "wpi/util/ConvertUTF.hpp"
#include "wpi/util/SmallString.hpp"
#include "wpi/util/SmallVector.hpp"
#include "wpi/util/StringExtras.hpp"
#pragma comment(lib, "dnsapi")
2025-11-07 20:00:05 -05:00
using namespace wpi::net;
struct ImplBase {
PDNS_SERVICE_INSTANCE serviceInstance = nullptr;
DNS_SERVICE_CANCEL serviceCancel;
HANDLE registerEvent = nullptr;
};
struct MulticastServiceAnnouncer::Impl : ImplBase {
std::wstring serviceType;
std::wstring serviceInstanceName;
std::wstring hostName;
int port;
std::vector<std::wstring> keys;
std::vector<PCWSTR> keyPtrs;
std::vector<std::wstring> values;
std::vector<PCWSTR> valuePtrs;
template <typename T>
Impl(std::string_view serviceName, std::string_view serviceType, int port,
std::span<const std::pair<T, T>> txt);
};
template <typename T>
MulticastServiceAnnouncer::Impl::Impl(std::string_view serviceName,
std::string_view serviceType, int port,
std::span<const std::pair<T, T>> txt) {
this->port = port;
2025-11-07 20:00:05 -05:00
wpi::util::SmallVector<wchar_t, 128> wideStorage;
std::string hostName = wpi::net::GetHostname() + ".local";
for (auto&& i : txt) {
wideStorage.clear();
2025-11-07 20:00:05 -05:00
wpi::util::sys::windows::UTF8ToUTF16(i.first, wideStorage);
this->keys.emplace_back(
std::wstring{wideStorage.data(), wideStorage.size()});
wideStorage.clear();
2025-11-07 20:00:05 -05:00
wpi::util::sys::windows::UTF8ToUTF16(i.second, wideStorage);
this->values.emplace_back(
std::wstring{wideStorage.data(), wideStorage.size()});
}
for (size_t i = 0; i < this->keys.size(); i++) {
this->keyPtrs.emplace_back(this->keys[i].c_str());
this->valuePtrs.emplace_back(this->values[i].c_str());
}
2025-11-07 20:00:05 -05:00
wpi::util::SmallString<128> storage;
wideStorage.clear();
2025-11-07 20:00:05 -05:00
wpi::util::sys::windows::UTF8ToUTF16(hostName, wideStorage);
this->hostName = std::wstring{wideStorage.data(), wideStorage.size()};
wideStorage.clear();
2025-11-07 20:00:05 -05:00
if (wpi::util::ends_with_lower(serviceType, ".local")) {
wpi::util::sys::windows::UTF8ToUTF16(serviceType, wideStorage);
} else {
storage.clear();
storage.append(serviceType);
storage.append(".local");
2025-11-07 20:00:05 -05:00
wpi::util::sys::windows::UTF8ToUTF16(storage.str(), wideStorage);
}
this->serviceType = std::wstring{wideStorage.data(), wideStorage.size()};
wideStorage.clear();
storage.clear();
storage.append(serviceName);
storage.append(".");
storage.append(serviceType);
2025-11-07 20:00:05 -05:00
if (!wpi::util::ends_with_lower(serviceType, ".local")) {
storage.append(".local");
}
2025-11-07 20:00:05 -05:00
wpi::util::sys::windows::UTF8ToUTF16(storage.str(), wideStorage);
this->serviceInstanceName =
std::wstring{wideStorage.data(), wideStorage.size()};
}
MulticastServiceAnnouncer::MulticastServiceAnnouncer(
std::string_view serviceName, std::string_view serviceType, int port) {
std::span<const std::pair<std::string_view, std::string_view>> txt;
pImpl = std::make_unique<Impl>(serviceName, serviceType, port, txt);
}
MulticastServiceAnnouncer::MulticastServiceAnnouncer(
std::string_view serviceName, std::string_view serviceType, int port,
std::span<const std::pair<std::string, std::string>> txt) {
pImpl = std::make_unique<Impl>(serviceName, serviceType, port, txt);
}
MulticastServiceAnnouncer::MulticastServiceAnnouncer(
std::string_view serviceName, std::string_view serviceType, int port,
std::span<const std::pair<std::string_view, std::string_view>> txt) {
pImpl = std::make_unique<Impl>(serviceName, serviceType, port, txt);
}
MulticastServiceAnnouncer::~MulticastServiceAnnouncer() noexcept {
Stop();
}
bool MulticastServiceAnnouncer::HasImplementation() const {
return true;
}
static void WINAPI DnsServiceRegisterCallback(DWORD /*Status*/,
PVOID pQueryContext,
PDNS_SERVICE_INSTANCE pInstance) {
HANDLE registerEvent = reinterpret_cast<HANDLE>(pQueryContext);
if (pInstance != nullptr) {
DnsServiceFreeInstance(pInstance);
}
SetEvent(registerEvent);
}
void MulticastServiceAnnouncer::Start() {
if (pImpl->serviceInstance) {
return;
}
PDNS_SERVICE_INSTANCE serviceInst = DnsServiceConstructInstance(
pImpl->serviceInstanceName.c_str(), pImpl->hostName.c_str(), nullptr,
nullptr, pImpl->port, 0, 0, static_cast<DWORD>(pImpl->keyPtrs.size()),
pImpl->keyPtrs.data(), pImpl->valuePtrs.data());
if (serviceInst == nullptr) {
return;
}
DNS_SERVICE_REGISTER_REQUEST registerRequest = {};
std::memset(&pImpl->serviceCancel, 0, sizeof(DNS_SERVICE_CANCEL));
pImpl->registerEvent = CreateEvent(NULL, true, false, NULL);
registerRequest.pQueryContext = pImpl->registerEvent;
registerRequest.pRegisterCompletionCallback = DnsServiceRegisterCallback;
registerRequest.Version = DNS_QUERY_REQUEST_VERSION1;
registerRequest.unicastEnabled = false;
registerRequest.pServiceInstance = serviceInst;
registerRequest.InterfaceIndex = 0;
if (DnsServiceRegister(&registerRequest, &pImpl->serviceCancel) !=
DNS_REQUEST_PENDING) {
DnsServiceFreeInstance(serviceInst);
CloseHandle(pImpl->registerEvent);
pImpl->registerEvent = nullptr;
return;
}
pImpl->serviceInstance = serviceInst;
}
static void WINAPI DnsServiceDeRegisterCallback(
DWORD /*Status*/, PVOID pQueryContext, PDNS_SERVICE_INSTANCE pInstance) {
HANDLE deregisterEvent = reinterpret_cast<HANDLE>(pQueryContext);
if (pInstance != nullptr) {
DnsServiceFreeInstance(pInstance);
}
SetEvent(deregisterEvent);
}
void MulticastServiceAnnouncer::Stop() {
if (pImpl->serviceInstance == nullptr) {
return;
}
DnsServiceRegisterCancel(&pImpl->serviceCancel);
WaitForSingleObject(pImpl->registerEvent, INFINITE);
CloseHandle(pImpl->registerEvent);
pImpl->registerEvent = nullptr;
HANDLE deregisterEvent = CreateEvent(NULL, true, false, NULL);
DNS_SERVICE_REGISTER_REQUEST registerRequest = {};
registerRequest.pQueryContext = deregisterEvent;
registerRequest.pRegisterCompletionCallback = DnsServiceDeRegisterCallback;
registerRequest.Version = DNS_QUERY_REQUEST_VERSION1;
registerRequest.unicastEnabled = false;
registerRequest.pServiceInstance = pImpl->serviceInstance;
registerRequest.InterfaceIndex = 0;
if (DnsServiceDeRegister(&registerRequest, nullptr) == DNS_REQUEST_PENDING) {
WaitForSingleObject(deregisterEvent, INFINITE);
}
DnsServiceFreeInstance(pImpl->serviceInstance);
pImpl->serviceInstance = nullptr;
CloseHandle(deregisterEvent);
}