blob: 2327b8b828fd6db70b6e37fa4ab13afe95551e4c [file] [log] [blame]
Austin Schuh75263e32022-02-22 18:05:32 -08001// Copyright (c) FIRST and other WPILib contributors.
2// Open Source Software; you can modify and/or share it under the terms of
3// the WPILib BSD license file in the root directory of this project.
4
5#ifndef UNICODE
6#define UNICODE
7#endif
8
9#include "wpi/MulticastServiceAnnouncer.h"
10
11#include <string>
12#include <vector>
13
14#include "DynamicDns.h"
15#include "wpi/ConvertUTF.h"
16#include "wpi/SmallString.h"
17#include "wpi/SmallVector.h"
18#include "wpi/StringExtras.h"
19#include "wpi/hostname.h"
20
21using namespace wpi;
22
23struct ImplBase {
24 wpi::DynamicDns& dynamicDns = wpi::DynamicDns::GetDynamicDns();
25 PDNS_SERVICE_INSTANCE serviceInstance = nullptr;
26 HANDLE event = nullptr;
27};
28
29struct MulticastServiceAnnouncer::Impl : ImplBase {
30 std::wstring serviceType;
31 std::wstring serviceInstanceName;
32 std::wstring hostName;
33 int port;
34 std::vector<std::wstring> keys;
35 std::vector<PCWSTR> keyPtrs;
36 std::vector<std::wstring> values;
37 std::vector<PCWSTR> valuePtrs;
38
39 template <typename T>
40 Impl(std::string_view serviceName, std::string_view serviceType, int port,
41 wpi::span<const std::pair<T, T>> txt);
42};
43
44template <typename T>
45MulticastServiceAnnouncer::Impl::Impl(std::string_view serviceName,
46 std::string_view serviceType, int port,
47 wpi::span<const std::pair<T, T>> txt) {
48 if (!dynamicDns.CanDnsAnnounce) {
49 return;
50 }
51
52 this->port = port;
53
54 wpi::SmallVector<wchar_t, 128> wideStorage;
55 std::string hostName = wpi::GetHostname() + ".local";
56
57 for (auto&& i : txt) {
58 wideStorage.clear();
59 wpi::sys::windows::UTF8ToUTF16(i.first, wideStorage);
60 this->keys.emplace_back(
61 std::wstring{wideStorage.data(), wideStorage.size()});
62 wideStorage.clear();
63 wpi::sys::windows::UTF8ToUTF16(i.second, wideStorage);
64 this->values.emplace_back(
65 std::wstring{wideStorage.data(), wideStorage.size()});
66 }
67
68 for (size_t i = 0; i < this->keys.size(); i++) {
69 this->keyPtrs.emplace_back(this->keys[i].c_str());
70 this->valuePtrs.emplace_back(this->values[i].c_str());
71 }
72
73 wpi::SmallString<128> storage;
74
75 wideStorage.clear();
76 wpi::sys::windows::UTF8ToUTF16(hostName, wideStorage);
77
78 this->hostName = std::wstring{wideStorage.data(), wideStorage.size()};
79
80 wideStorage.clear();
81 if (wpi::ends_with_lower(serviceType, ".local")) {
82 wpi::sys::windows::UTF8ToUTF16(serviceType, wideStorage);
83 } else {
84 storage.clear();
85 storage.append(serviceType);
86 storage.append(".local");
87 wpi::sys::windows::UTF8ToUTF16(storage.str(), wideStorage);
88 }
89 this->serviceType = std::wstring{wideStorage.data(), wideStorage.size()};
90
91 wideStorage.clear();
92 storage.clear();
93 storage.append(serviceName);
94 storage.append(".");
95 storage.append(serviceType);
96 if (!wpi::ends_with_lower(serviceType, ".local")) {
97 storage.append(".local");
98 }
99
100 wpi::sys::windows::UTF8ToUTF16(storage.str(), wideStorage);
101 this->serviceInstanceName =
102 std::wstring{wideStorage.data(), wideStorage.size()};
103}
104
105MulticastServiceAnnouncer::MulticastServiceAnnouncer(
106 std::string_view serviceName, std::string_view serviceType, int port,
107 wpi::span<const std::pair<std::string, std::string>> txt) {
108 pImpl = std::make_unique<Impl>(serviceName, serviceType, port, txt);
109}
110
111MulticastServiceAnnouncer::MulticastServiceAnnouncer(
112 std::string_view serviceName, std::string_view serviceType, int port,
113 wpi::span<const std::pair<std::string_view, std::string_view>> txt) {
114 pImpl = std::make_unique<Impl>(serviceName, serviceType, port, txt);
115}
116
117MulticastServiceAnnouncer::~MulticastServiceAnnouncer() noexcept {
118 Stop();
119}
120
121bool MulticastServiceAnnouncer::HasImplementation() const {
122 return pImpl->dynamicDns.CanDnsAnnounce;
123}
124
125static void WINAPI DnsServiceRegisterCallback(DWORD /*Status*/,
126 PVOID pQueryContext,
127 PDNS_SERVICE_INSTANCE pInstance) {
128 ImplBase* impl = reinterpret_cast<ImplBase*>(pQueryContext);
129
130 impl->serviceInstance = pInstance;
131
132 SetEvent(impl->event);
133}
134
135void MulticastServiceAnnouncer::Start() {
136 if (pImpl->serviceInstance) {
137 return;
138 }
139
140 if (!pImpl->dynamicDns.CanDnsAnnounce) {
141 return;
142 }
143
144 PDNS_SERVICE_INSTANCE serviceInst =
145 pImpl->dynamicDns.DnsServiceConstructInstancePtr(
146 pImpl->serviceInstanceName.c_str(), pImpl->hostName.c_str(), nullptr,
147 nullptr, pImpl->port, 0, 0, static_cast<DWORD>(pImpl->keyPtrs.size()),
148 pImpl->keyPtrs.data(), pImpl->valuePtrs.data());
149 if (serviceInst == nullptr) {
150 return;
151 }
152
153 DNS_SERVICE_REGISTER_REQUEST registerRequest = {};
154 registerRequest.pQueryContext = static_cast<ImplBase*>(pImpl.get());
155 registerRequest.pRegisterCompletionCallback = DnsServiceRegisterCallback;
156 registerRequest.Version = DNS_QUERY_REQUEST_VERSION1;
157 registerRequest.unicastEnabled = false;
158 registerRequest.pServiceInstance = serviceInst;
159 registerRequest.InterfaceIndex = 0;
160
161 pImpl->event = CreateEvent(NULL, true, false, NULL);
162
163 if (pImpl->dynamicDns.DnsServiceRegisterPtr(&registerRequest, nullptr) ==
164 DNS_REQUEST_PENDING) {
165 WaitForSingleObject(pImpl->event, INFINITE);
166 }
167
168 pImpl->dynamicDns.DnsServiceFreeInstancePtr(serviceInst);
169 CloseHandle(pImpl->event);
170 pImpl->event = nullptr;
171}
172
173static void WINAPI DnsServiceDeRegisterCallback(
174 DWORD /*Status*/, PVOID pQueryContext, PDNS_SERVICE_INSTANCE pInstance) {
175 ImplBase* impl = reinterpret_cast<ImplBase*>(pQueryContext);
176
177 if (pInstance != nullptr) {
178 impl->dynamicDns.DnsServiceFreeInstancePtr(pInstance);
179 pInstance = nullptr;
180 }
181
182 SetEvent(impl->event);
183}
184
185void MulticastServiceAnnouncer::Stop() {
186 if (!pImpl->dynamicDns.CanDnsAnnounce) {
187 return;
188 }
189
190 if (pImpl->serviceInstance == nullptr) {
191 return;
192 }
193
194 pImpl->event = CreateEvent(NULL, true, false, NULL);
195 DNS_SERVICE_REGISTER_REQUEST registerRequest = {};
196 registerRequest.pQueryContext = static_cast<ImplBase*>(pImpl.get());
197 registerRequest.pRegisterCompletionCallback = DnsServiceDeRegisterCallback;
198 registerRequest.Version = DNS_QUERY_REQUEST_VERSION1;
199 registerRequest.unicastEnabled = false;
200 registerRequest.pServiceInstance = pImpl->serviceInstance;
201 registerRequest.InterfaceIndex = 0;
202
203 if (pImpl->dynamicDns.DnsServiceDeRegisterPtr(&registerRequest, nullptr) ==
204 DNS_REQUEST_PENDING) {
205 WaitForSingleObject(pImpl->event, INFINITE);
206 }
207
208 pImpl->dynamicDns.DnsServiceFreeInstancePtr(pImpl->serviceInstance);
209 pImpl->serviceInstance = nullptr;
210 CloseHandle(pImpl->event);
211 pImpl->event = nullptr;
212}