| // 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 |
| |
| #include "wpi/MulticastServiceResolver.h" |
| |
| #include <string> |
| |
| #include "DynamicDns.h" |
| #include "wpi/ConvertUTF.h" |
| #include "wpi/SmallString.h" |
| #include "wpi/SmallVector.h" |
| #include "wpi/StringExtras.h" |
| |
| #pragma comment(lib, "dnsapi") |
| |
| using namespace wpi; |
| |
| struct MulticastServiceResolver::Impl { |
| wpi::DynamicDns& dynamicDns = wpi::DynamicDns::GetDynamicDns(); |
| std::wstring serviceType; |
| DNS_SERVICE_CANCEL serviceCancel{nullptr}; |
| |
| MulticastServiceResolver* resolver; |
| |
| void onFound(ServiceData&& data) { |
| resolver->PushData(std::forward<ServiceData>(data)); |
| } |
| }; |
| |
| MulticastServiceResolver::MulticastServiceResolver( |
| std::string_view serviceType) { |
| pImpl = std::make_unique<Impl>(); |
| pImpl->resolver = this; |
| |
| if (!pImpl->dynamicDns.CanDnsResolve) { |
| return; |
| } |
| |
| wpi::SmallVector<wchar_t, 128> wideStorage; |
| |
| if (wpi::ends_with_lower(serviceType, ".local")) { |
| wpi::sys::windows::UTF8ToUTF16(serviceType, wideStorage); |
| } else { |
| wpi::SmallString<128> storage; |
| storage.append(serviceType); |
| storage.append(".local"); |
| wpi::sys::windows::UTF8ToUTF16(storage.str(), wideStorage); |
| } |
| pImpl->serviceType = std::wstring{wideStorage.data(), wideStorage.size()}; |
| } |
| |
| MulticastServiceResolver::~MulticastServiceResolver() noexcept { |
| Stop(); |
| } |
| |
| bool MulticastServiceResolver::HasImplementation() const { |
| return pImpl->dynamicDns.CanDnsResolve; |
| } |
| |
| static _Function_class_(DNS_QUERY_COMPLETION_ROUTINE) VOID WINAPI |
| DnsCompletion(_In_ PVOID pQueryContext, |
| _Inout_ PDNS_QUERY_RESULT pQueryResults) { |
| MulticastServiceResolver::Impl* impl = |
| reinterpret_cast<MulticastServiceResolver::Impl*>(pQueryContext); |
| |
| wpi::SmallVector<DNS_RECORDW*, 4> PtrRecords; |
| wpi::SmallVector<DNS_RECORDW*, 4> SrvRecords; |
| wpi::SmallVector<DNS_RECORDW*, 4> TxtRecords; |
| wpi::SmallVector<DNS_RECORDW*, 4> ARecords; |
| |
| { |
| DNS_RECORDW* current = pQueryResults->pQueryRecords; |
| while (current != nullptr) { |
| switch (current->wType) { |
| case DNS_TYPE_PTR: |
| PtrRecords.push_back(current); |
| break; |
| case DNS_TYPE_SRV: |
| SrvRecords.push_back(current); |
| break; |
| case DNS_TYPE_TEXT: |
| TxtRecords.push_back(current); |
| break; |
| case DNS_TYPE_A: |
| ARecords.push_back(current); |
| break; |
| } |
| current = current->pNext; |
| } |
| } |
| |
| for (DNS_RECORDW* Ptr : PtrRecords) { |
| if (std::wstring_view{Ptr->pName} != impl->serviceType) { |
| continue; |
| } |
| |
| std::wstring_view nameHost = Ptr->Data.Ptr.pNameHost; |
| DNS_RECORDW* foundSrv = nullptr; |
| for (DNS_RECORDW* Srv : SrvRecords) { |
| if (std::wstring_view{Srv->pName} == nameHost) { |
| foundSrv = Srv; |
| break; |
| } |
| } |
| |
| if (!foundSrv) { |
| continue; |
| } |
| |
| for (DNS_RECORDW* A : ARecords) { |
| if (std::wstring_view{A->pName} == |
| std::wstring_view{foundSrv->Data.Srv.pNameTarget}) { |
| MulticastServiceResolver::ServiceData data; |
| wpi::SmallString<128> storage; |
| for (DNS_RECORDW* Txt : TxtRecords) { |
| if (std::wstring_view{Txt->pName} == nameHost) { |
| for (DWORD i = 0; i < Txt->Data.Txt.dwStringCount; i++) { |
| std::wstring_view wideView = Txt->Data.TXT.pStringArray[i]; |
| size_t splitIndex = wideView.find(L'='); |
| if (splitIndex == wideView.npos) { |
| // Todo make this just do key |
| continue; |
| } |
| storage.clear(); |
| wpi::span<const wpi::UTF16> wideStr{ |
| reinterpret_cast<const wpi::UTF16*>(wideView.data()), |
| splitIndex}; |
| wpi::convertUTF16ToUTF8String(wideStr, storage); |
| auto& pair = data.txt.emplace_back( |
| std::pair<std::string, std::string>{storage.string(), {}}); |
| storage.clear(); |
| wideStr = wpi::span<const wpi::UTF16>{ |
| reinterpret_cast<const wpi::UTF16*>(wideView.data() + |
| splitIndex + 1), |
| wideView.size() - splitIndex - 1}; |
| wpi::convertUTF16ToUTF8String(wideStr, storage); |
| pair.second = storage.string(); |
| } |
| } |
| } |
| |
| storage.clear(); |
| wpi::span<const wpi::UTF16> wideHostName{ |
| reinterpret_cast<const wpi::UTF16*>(A->pName), wcslen(A->pName)}; |
| wpi::convertUTF16ToUTF8String(wideHostName, storage); |
| storage.append("."); |
| |
| data.hostName = storage.string(); |
| storage.clear(); |
| |
| int len = nameHost.find(impl->serviceType.c_str()); |
| wpi::span<const wpi::UTF16> wideServiceName{ |
| reinterpret_cast<const wpi::UTF16*>(nameHost.data()), |
| nameHost.size()}; |
| if (len != nameHost.npos) { |
| wideServiceName = wideServiceName.subspan(0, len - 1); |
| } |
| wpi::convertUTF16ToUTF8String(wideServiceName, storage); |
| |
| data.serviceName = storage.string(); |
| data.port = foundSrv->Data.Srv.wPort; |
| data.ipv4Address = A->Data.A.IpAddress; |
| |
| impl->onFound(std::move(data)); |
| } |
| } |
| } |
| DnsFree(pQueryResults->pQueryRecords, DNS_FREE_TYPE::DnsFreeRecordList); |
| } |
| |
| void MulticastServiceResolver::Start() { |
| if (pImpl->serviceCancel.reserved != nullptr) { |
| return; |
| } |
| |
| if (!pImpl->dynamicDns.CanDnsResolve) { |
| return; |
| } |
| |
| DNS_SERVICE_BROWSE_REQUEST request = {}; |
| request.InterfaceIndex = 0; |
| request.pQueryContext = pImpl.get(); |
| request.QueryName = pImpl->serviceType.c_str(); |
| request.Version = 2; |
| request.pBrowseCallbackV2 = DnsCompletion; |
| pImpl->dynamicDns.DnsServiceBrowsePtr(&request, &pImpl->serviceCancel); |
| } |
| |
| void MulticastServiceResolver::Stop() { |
| if (!pImpl->dynamicDns.CanDnsResolve) { |
| return; |
| } |
| |
| if (pImpl->serviceCancel.reserved == nullptr) { |
| return; |
| } |
| |
| pImpl->dynamicDns.DnsServiceBrowseCancelPtr(&pImpl->serviceCancel); |
| pImpl->serviceCancel.reserved = nullptr; |
| } |