react-native-zeroconf 0.16.1 → 0.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/NOTICE +0 -3
  2. package/README.md +3 -3
  3. package/android/build.gradle +0 -3
  4. package/android/src/main/java/com/balthazargronon/RCTZeroconf/ZeroConfImplFactory.java +1 -1
  5. package/android/src/main/java/com/balthazargronon/RCTZeroconf/Zeroconf.java +12 -2
  6. package/android/src/main/java/com/balthazargronon/RCTZeroconf/ZeroconfModule.java +129 -4
  7. package/android/src/main/java/com/balthazargronon/RCTZeroconf/ZeroconfOptions.java +77 -0
  8. package/android/src/main/java/com/balthazargronon/RCTZeroconf/dnssd/DnssdImpl.java +654 -0
  9. package/android/src/main/java/com/balthazargronon/RCTZeroconf/nsd/NsdServiceImpl.java +393 -39
  10. package/android/src/main/java/com/balthazargronon/RCTZeroconf/nsd/ServiceInfoCallbacks.java +50 -13
  11. package/android/src/main/java/com/balthazargronon/RCTZeroconf/nsd/ServiceTypesQuery.java +296 -0
  12. package/android/src/main/java/com/github/druk/dnssd/DNSSDEmbedded.java +1 -1
  13. package/android/src/main/jni/mdnsresponder/mDNSShared/dnssd_clientshim.c +59 -22
  14. package/dist/index.js +235 -48
  15. package/index.d.ts +105 -7
  16. package/ios/RNZeroconf/RNZeroconf.m +285 -22
  17. package/package.json +28 -4
  18. package/src/index.js +214 -44
  19. package/windows/ExperimentalFeatures.props +33 -0
  20. package/windows/NuGet.config +11 -0
  21. package/windows/RNZeroconf/RNZeroconf.cpp +228 -0
  22. package/windows/RNZeroconf/RNZeroconf.def +3 -0
  23. package/windows/RNZeroconf/RNZeroconf.h +70 -0
  24. package/windows/RNZeroconf/RNZeroconf.rc +0 -0
  25. package/windows/RNZeroconf/RNZeroconf.vcxproj +150 -0
  26. package/windows/RNZeroconf/RNZeroconf.vcxproj.filters +50 -0
  27. package/windows/RNZeroconf/ReactPackageProvider.cpp +20 -0
  28. package/windows/RNZeroconf/ReactPackageProvider.h +24 -0
  29. package/windows/RNZeroconf/ReactPackageProvider.idl +9 -0
  30. package/windows/RNZeroconf/ZeroconfCore.cpp +1095 -0
  31. package/windows/RNZeroconf/ZeroconfCore.h +167 -0
  32. package/windows/RNZeroconf/pch.cpp +1 -0
  33. package/windows/RNZeroconf/pch.h +30 -0
  34. package/windows/RNZeroconf/resource.h +5 -0
  35. package/windows/RNZeroconf/targetver.h +8 -0
  36. package/windows/RNZeroconf.sln +43 -0
  37. package/android/src/main/java/com/balthazargronon/RCTZeroconf/rx2dnssd/DnssdImpl.java +0 -305
  38. package/android/src/main/java/com/github/druk/rx2dnssd/BonjourService.java +0 -400
  39. package/android/src/main/java/com/github/druk/rx2dnssd/Rx2BrowseListener.java +0 -55
  40. package/android/src/main/java/com/github/druk/rx2dnssd/Rx2Dnssd.java +0 -127
  41. package/android/src/main/java/com/github/druk/rx2dnssd/Rx2DnssdCommon.java +0 -280
  42. package/android/src/main/java/com/github/druk/rx2dnssd/Rx2DnssdEmbedded.java +0 -31
  43. package/android/src/main/java/com/github/druk/rx2dnssd/Rx2QueryListener.java +0 -70
  44. package/android/src/main/java/com/github/druk/rx2dnssd/Rx2RegisterListener.java +0 -47
  45. package/android/src/main/java/com/github/druk/rx2dnssd/Rx2ResolveListener.java +0 -51
@@ -0,0 +1,1095 @@
1
+ #include "ZeroconfCore.h"
2
+
3
+ #include <iphlpapi.h>
4
+
5
+ #include <algorithm>
6
+ #include <atomic>
7
+ #include <cstdio>
8
+ #include <cwchar>
9
+ #include <cwctype>
10
+ #include <thread>
11
+
12
+ #pragma comment(lib, "dnsapi.lib")
13
+ #pragma comment(lib, "iphlpapi.lib")
14
+ #pragma comment(lib, "ws2_32.lib")
15
+
16
+ namespace rnzeroconf {
17
+
18
+ using After = std::vector<std::function<void()>>;
19
+
20
+ enum class Kind { Browse, Resolve, Publication };
21
+
22
+ struct Operation {
23
+ Operation(Kind kind, Zeroconf *owner) : kind(kind), owner(owner) {}
24
+ virtual ~Operation() = default;
25
+ const Kind kind;
26
+ Zeroconf *owner;
27
+ };
28
+
29
+ struct Resolve : Operation {
30
+ explicit Resolve(Zeroconf *owner) : Operation(Kind::Resolve, owner) {}
31
+ // Empty for resolveService()
32
+ std::weak_ptr<Browse> browse;
33
+ std::string scanId;
34
+ std::wstring name;
35
+ std::wstring key;
36
+ std::wstring queryName;
37
+ ULONG interfaceIndex = 0;
38
+ DNS_SERVICE_RESOLVE_REQUEST request{};
39
+ DNS_SERVICE_CANCEL cancel{};
40
+ bool done = false;
41
+ // Scans: retried once after a timeout
42
+ bool retried = false;
43
+ ServiceCallback promiseResolve;
44
+ ErrorCallback promiseReject;
45
+ };
46
+
47
+ struct Browse : Operation {
48
+ explicit Browse(Zeroconf *owner) : Operation(Kind::Browse, owner) {}
49
+ std::string scanId;
50
+ std::wstring regType;
51
+ std::wstring domain;
52
+ std::wstring queryName;
53
+ bool typesOnly = false;
54
+ ULONG interfaceIndex = 0;
55
+ DWORD resolveTimeoutMs = 5000;
56
+ DNS_SERVICE_BROWSE_REQUEST request{};
57
+ DNS_SERVICE_CANCEL cancel{};
58
+ // Lower-case PTR target -> name reported to JavaScript
59
+ std::map<std::wstring, std::wstring> found;
60
+ std::map<std::wstring, std::shared_ptr<Resolve>> resolves;
61
+ // Service type scans: the types answered by the network, the others come from this app's publications
62
+ std::set<std::wstring> networkTypes;
63
+ // Instances resolved once, and their last TXT and SRV data, to resolve them again when they change
64
+ std::set<std::wstring> resolved;
65
+ std::map<std::wstring, std::wstring> recordData;
66
+ };
67
+
68
+ struct Publication : Operation {
69
+ enum class State { Registering, Registered, Deregistering, Done };
70
+ explicit Publication(Zeroconf *owner) : Operation(Kind::Publication, owner) {}
71
+ ~Publication() override {
72
+ if (instance) {
73
+ DnsServiceFreeInstance(instance);
74
+ }
75
+ }
76
+ State state = State::Registering;
77
+ std::wstring requestedName;
78
+ std::wstring name;
79
+ std::wstring regType;
80
+ std::wstring domain;
81
+ uint16_t port = 0;
82
+ TxtPairs txt;
83
+ ULONG interfaceIndex = 0;
84
+ // Updates publish again without emitting unpublished and published
85
+ bool announce = true;
86
+ PDNS_SERVICE_INSTANCE instance = nullptr;
87
+ DNS_SERVICE_REGISTER_REQUEST request{};
88
+ DNS_SERVICE_CANCEL cancel{};
89
+ ServiceCallback onRegistered;
90
+ ErrorCallback onRegisterFailed;
91
+ ServiceCallback onDeregistered;
92
+ };
93
+
94
+ namespace {
95
+
96
+ // windns.h callbacks receive a context pointer, they only use operations still in this registry,
97
+ // so a callback arriving after a cancel never touches a freed operation
98
+ std::mutex registryMutex;
99
+ std::map<void *, std::shared_ptr<Operation>> &Registry() {
100
+ static std::map<void *, std::shared_ptr<Operation>> registry;
101
+ return registry;
102
+ }
103
+
104
+ void Track(const std::shared_ptr<Operation> &operation) {
105
+ std::lock_guard<std::mutex> lock(registryMutex);
106
+ Registry()[operation.get()] = operation;
107
+ }
108
+
109
+ void Untrack(Operation *operation) {
110
+ std::lock_guard<std::mutex> lock(registryMutex);
111
+ Registry().erase(operation);
112
+ }
113
+
114
+ template <typename T>
115
+ std::shared_ptr<T> Tracked(void *context) {
116
+ std::lock_guard<std::mutex> lock(registryMutex);
117
+ auto it = Registry().find(context);
118
+ if (it == Registry().end() || it->second->owner == nullptr) {
119
+ return nullptr;
120
+ }
121
+ return std::static_pointer_cast<T>(it->second);
122
+ }
123
+
124
+ bool DebugEnabled() {
125
+ static const bool enabled = GetEnvironmentVariableW(L"RNZEROCONF_DEBUG", nullptr, 0) > 0;
126
+ return enabled;
127
+ }
128
+
129
+ std::wstring Lower(std::wstring value) {
130
+ std::transform(value.begin(), value.end(), value.begin(), [](wchar_t c) { return static_cast<wchar_t>(std::towlower(c)); });
131
+ return value;
132
+ }
133
+
134
+ bool EndsWith(const std::wstring &value, const std::wstring &suffix) {
135
+ return value.size() >= suffix.size() && value.compare(value.size() - suffix.size(), suffix.size(), suffix) == 0;
136
+ }
137
+
138
+ std::wstring WithoutTrailingDot(std::wstring value) {
139
+ while (!value.empty() && value.back() == L'.') {
140
+ value.pop_back();
141
+ }
142
+ return value;
143
+ }
144
+
145
+ // "local." -> "local"
146
+ std::wstring DomainName(const std::wstring &domain) {
147
+ std::wstring name = WithoutTrailingDot(domain);
148
+ return name.empty() ? L"local" : name;
149
+ }
150
+
151
+ // "printer" or "_printer" -> "_printer"
152
+ std::wstring Label(const std::wstring &value) {
153
+ return value.empty() || value[0] == L'_' ? value : L"_" + value;
154
+ }
155
+
156
+ // "My Printer._ipp._tcp.local" -> "My Printer"
157
+ std::wstring InstanceName(const std::wstring &fullName, const std::wstring &regType, const std::wstring &domain) {
158
+ std::wstring name = WithoutTrailingDot(fullName);
159
+ std::wstring suffix = Lower(L"." + regType + L"." + domain);
160
+ if (EndsWith(Lower(name), suffix)) {
161
+ return name.substr(0, name.size() - suffix.size());
162
+ }
163
+ size_t typeStart = name.find(L"._");
164
+ return typeStart == std::wstring::npos ? name : name.substr(0, typeStart);
165
+ }
166
+
167
+ // TXT strings or SRV target and port, to notice changes
168
+ std::wstring RecordData(const DNS_RECORD &record) {
169
+ std::wstring data;
170
+ if (record.wType == DNS_TYPE_TEXT) {
171
+ for (DWORD i = 0; i < record.Data.TXT.dwStringCount; i++) {
172
+ data += std::wstring(record.Data.TXT.pStringArray[i] ? record.Data.TXT.pStringArray[i] : L"") + L'\n';
173
+ }
174
+ } else if (record.wType == DNS_TYPE_SRV) {
175
+ data = std::wstring(record.Data.SRV.pNameTarget ? record.Data.SRV.pNameTarget : L"") + L":" + std::to_wstring(record.Data.SRV.wPort);
176
+ }
177
+ return data;
178
+ }
179
+
180
+ // "_http._tcp.local" -> "_http._tcp"
181
+ std::wstring ServiceTypeName(const std::wstring &target) {
182
+ size_t first = target.find(L'.');
183
+ if (first == std::wstring::npos) {
184
+ return target;
185
+ }
186
+ size_t second = target.find(L'.', first + 1);
187
+ return second == std::wstring::npos ? target : target.substr(0, second);
188
+ }
189
+
190
+ Error WindowsError(DWORD status, const std::wstring &prefix, const std::wstring &serviceName) {
191
+ Error error;
192
+ error.domain = L"Windows";
193
+ error.code = static_cast<int64_t>(status);
194
+ error.message = prefix + DescribeStatus(status);
195
+ error.serviceName = serviceName;
196
+ return error;
197
+ }
198
+
199
+ Error LibraryError(const std::wstring &code, const std::wstring &message, const std::wstring &serviceName) {
200
+ Error error;
201
+ error.domain = L"RNZeroconf";
202
+ error.stringCode = code;
203
+ error.message = message;
204
+ error.serviceName = serviceName;
205
+ return error;
206
+ }
207
+
208
+ std::wstring AddressString(int family, const void *address) {
209
+ wchar_t buffer[INET6_ADDRSTRLEN] = {};
210
+ return InetNtopW(family, address, buffer, INET6_ADDRSTRLEN) ? std::wstring(buffer) : std::wstring();
211
+ }
212
+
213
+ void Run(After &after) {
214
+ for (auto &action : after) {
215
+ action();
216
+ }
217
+ after.clear();
218
+ }
219
+
220
+ VOID WINAPI BrowseCallback(DWORD status, PVOID context, PDNS_RECORD records) {
221
+ auto browse = Tracked<Browse>(context);
222
+ if (!browse) {
223
+ if (records) {
224
+ DnsRecordListFree(records, DnsFreeRecordList);
225
+ }
226
+ return;
227
+ }
228
+ browse->owner->OnBrowse(browse, status, records);
229
+ }
230
+
231
+ VOID WINAPI ResolveCallback(DWORD status, PVOID context, PDNS_SERVICE_INSTANCE instance) {
232
+ auto resolve = Tracked<Resolve>(context);
233
+ if (!resolve) {
234
+ if (instance) {
235
+ DnsServiceFreeInstance(instance);
236
+ }
237
+ return;
238
+ }
239
+ resolve->owner->OnResolve(resolve, status, instance);
240
+ }
241
+
242
+ VOID WINAPI RegisterCallback(DWORD status, PVOID context, PDNS_SERVICE_INSTANCE instance) {
243
+ auto publication = Tracked<Publication>(context);
244
+ if (!publication) {
245
+ if (instance) {
246
+ DnsServiceFreeInstance(instance);
247
+ }
248
+ return;
249
+ }
250
+ publication->owner->OnRegister(publication, status, instance);
251
+ }
252
+
253
+ } // namespace
254
+
255
+ std::wstring Widen(const std::string &value) {
256
+ if (value.empty()) {
257
+ return std::wstring();
258
+ }
259
+ int size = MultiByteToWideChar(CP_UTF8, 0, value.data(), static_cast<int>(value.size()), nullptr, 0);
260
+ std::wstring result(size, L'\0');
261
+ MultiByteToWideChar(CP_UTF8, 0, value.data(), static_cast<int>(value.size()), &result[0], size);
262
+ return result;
263
+ }
264
+
265
+ std::string Narrow(const std::wstring &value) {
266
+ if (value.empty()) {
267
+ return std::string();
268
+ }
269
+ int size = WideCharToMultiByte(CP_UTF8, 0, value.data(), static_cast<int>(value.size()), nullptr, 0, nullptr, nullptr);
270
+ std::string result(size, '\0');
271
+ WideCharToMultiByte(CP_UTF8, 0, value.data(), static_cast<int>(value.size()), &result[0], size, nullptr, nullptr);
272
+ return result;
273
+ }
274
+
275
+ std::wstring DescribeStatus(DWORD status) {
276
+ wchar_t *buffer = nullptr;
277
+ DWORD length = FormatMessageW(
278
+ FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
279
+ nullptr,
280
+ status,
281
+ 0,
282
+ reinterpret_cast<LPWSTR>(&buffer),
283
+ 0,
284
+ nullptr);
285
+ std::wstring message = length && buffer ? std::wstring(buffer, length) : L"error " + std::to_wstring(status);
286
+ if (buffer) {
287
+ LocalFree(buffer);
288
+ }
289
+ while (!message.empty() && (message.back() == L'\n' || message.back() == L'\r' || message.back() == L'.' || message.back() == L' ')) {
290
+ message.pop_back();
291
+ }
292
+ return message;
293
+ }
294
+
295
+ Zeroconf::Zeroconf(Events events) : events_(std::move(events)), alive_(std::make_shared<std::atomic<bool>>(true)) {
296
+ wchar_t name[256] = {};
297
+ DWORD size = 256;
298
+ if (GetComputerNameExW(ComputerNameDnsHostname, name, &size) && size > 0) {
299
+ hostName_ = std::wstring(name, size) + L".local";
300
+ } else {
301
+ hostName_ = L"windows.local";
302
+ }
303
+ }
304
+
305
+ Zeroconf::~Zeroconf() {
306
+ *alive_ = false;
307
+ StopAll();
308
+ UnpublishAll();
309
+ After after;
310
+ {
311
+ std::lock_guard<std::recursive_mutex> lock(mutex_);
312
+ for (auto &resolve : singleResolves_) {
313
+ CancelResolve(resolve, after);
314
+ }
315
+ singleResolves_.clear();
316
+ }
317
+ Run(after);
318
+ // Late callbacks of deregistrations still in flight are ignored
319
+ std::lock_guard<std::mutex> lock(registryMutex);
320
+ for (auto &entry : Registry()) {
321
+ if (entry.second->owner == this) {
322
+ entry.second->owner = nullptr;
323
+ }
324
+ }
325
+ }
326
+
327
+ bool Zeroconf::InterfaceIndex(const std::wstring &networkInterface, ULONG &index, Error &error) const {
328
+ index = 0;
329
+ if (networkInterface.empty()) {
330
+ return true;
331
+ }
332
+ if (std::all_of(networkInterface.begin(), networkInterface.end(), [](wchar_t c) { return std::iswdigit(c); })) {
333
+ index = std::wcstoul(networkInterface.c_str(), nullptr, 10);
334
+ return true;
335
+ }
336
+ // By friendly name ("Wi-Fi", "Ethernet") or adapter name
337
+ ULONG size = 16 * 1024;
338
+ std::vector<BYTE> buffer(size);
339
+ ULONG flags = GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER;
340
+ ULONG result = GetAdaptersAddresses(AF_UNSPEC, flags, nullptr, reinterpret_cast<PIP_ADAPTER_ADDRESSES>(buffer.data()), &size);
341
+ if (result == ERROR_BUFFER_OVERFLOW) {
342
+ buffer.resize(size);
343
+ result = GetAdaptersAddresses(AF_UNSPEC, flags, nullptr, reinterpret_cast<PIP_ADAPTER_ADDRESSES>(buffer.data()), &size);
344
+ }
345
+ if (result == NO_ERROR) {
346
+ std::wstring wanted = Lower(networkInterface);
347
+ for (auto adapter = reinterpret_cast<PIP_ADAPTER_ADDRESSES>(buffer.data()); adapter; adapter = adapter->Next) {
348
+ bool friendly = adapter->FriendlyName && Lower(adapter->FriendlyName) == wanted;
349
+ bool adapterName = adapter->AdapterName && Lower(Widen(adapter->AdapterName)) == wanted;
350
+ if (friendly || adapterName) {
351
+ index = adapter->IfIndex ? adapter->IfIndex : adapter->Ipv6IfIndex;
352
+ return true;
353
+ }
354
+ }
355
+ }
356
+ error = LibraryError(L"UNKNOWN_INTERFACE", L"Unknown network interface " + networkInterface, L"");
357
+ return false;
358
+ }
359
+
360
+ // Scan
361
+
362
+ void Zeroconf::Scan(
363
+ const std::string &scanId,
364
+ const std::wstring &type,
365
+ const std::wstring &protocol,
366
+ const std::wstring &domain,
367
+ const std::wstring &subtype,
368
+ const std::wstring &networkInterface,
369
+ double resolveTimeoutSeconds) {
370
+ After after;
371
+ {
372
+ std::lock_guard<std::recursive_mutex> lock(mutex_);
373
+ StopLocked(scanId, after);
374
+
375
+ auto browse = std::make_shared<Browse>(this);
376
+ browse->scanId = scanId;
377
+ browse->resolveTimeoutMs = static_cast<DWORD>((resolveTimeoutSeconds > 0 ? resolveTimeoutSeconds : 5) * 1000);
378
+ browse->regType = L"_" + type + L"._" + protocol;
379
+ browse->domain = DomainName(domain);
380
+ browse->typesOnly = type == L"services._dns-sd" && protocol == L"udp";
381
+ Error interfaceError;
382
+ if (!InterfaceIndex(networkInterface, browse->interfaceIndex, interfaceError)) {
383
+ after.push_back([this, scanId, interfaceError] {
384
+ if (events_.error) events_.error(scanId, interfaceError);
385
+ });
386
+ } else {
387
+ // "_printer._sub._ipp._tcp.local" browses the _printer subtype
388
+ browse->queryName = (subtype.empty() ? L"" : Label(subtype) + L"._sub.") + browse->regType + L"." + browse->domain;
389
+ browse->request.Version = DNS_QUERY_REQUEST_VERSION1;
390
+ browse->request.InterfaceIndex = browse->interfaceIndex;
391
+ browse->request.QueryName = browse->queryName.c_str();
392
+ browse->request.pBrowseCallback = BrowseCallback;
393
+ browse->request.pQueryContext = browse.get();
394
+ Track(browse);
395
+ DNS_STATUS status = DnsServiceBrowse(&browse->request, &browse->cancel);
396
+ if (status != DNS_REQUEST_PENDING) {
397
+ Untrack(browse.get());
398
+ Error error = WindowsError(status, L"Browsing services failed: ", L"");
399
+ after.push_back([this, scanId, error] {
400
+ if (events_.error) events_.error(scanId, error);
401
+ });
402
+ } else {
403
+ browses_[scanId] = browse;
404
+ after.push_back([this, scanId] {
405
+ if (events_.start) events_.start(scanId);
406
+ });
407
+ if (browse->typesOnly) {
408
+ for (auto &entry : publications_) {
409
+ SyncLocalType(entry.second->regType, entry.second->domain, after);
410
+ }
411
+ }
412
+ }
413
+ }
414
+ }
415
+ Run(after);
416
+ }
417
+
418
+ void Zeroconf::Stop(const std::string &scanId) {
419
+ After after;
420
+ {
421
+ std::lock_guard<std::recursive_mutex> lock(mutex_);
422
+ StopLocked(scanId, after);
423
+ }
424
+ Run(after);
425
+ }
426
+
427
+ void Zeroconf::StopAll() {
428
+ After after;
429
+ {
430
+ std::lock_guard<std::recursive_mutex> lock(mutex_);
431
+ std::vector<std::string> scanIds;
432
+ for (auto &entry : browses_) {
433
+ scanIds.push_back(entry.first);
434
+ }
435
+ for (auto &scanId : scanIds) {
436
+ StopLocked(scanId, after);
437
+ }
438
+ }
439
+ Run(after);
440
+ }
441
+
442
+ void Zeroconf::StopLocked(const std::string &scanId, After &after) {
443
+ auto it = browses_.find(scanId);
444
+ if (it == browses_.end()) {
445
+ return;
446
+ }
447
+ auto browse = it->second;
448
+ browses_.erase(it);
449
+ Untrack(browse.get());
450
+ for (auto &entry : browse->resolves) {
451
+ CancelResolve(entry.second, after);
452
+ }
453
+ browse->resolves.clear();
454
+ // Cancelling outside the lock, a callback in flight may be waiting for it
455
+ after.push_back([this, browse, scanId] {
456
+ DnsServiceBrowseCancel(&browse->cancel);
457
+ if (events_.stop) events_.stop(scanId);
458
+ });
459
+ }
460
+
461
+ void Zeroconf::OnBrowse(const std::shared_ptr<Browse> &browse, DWORD status, PDNS_RECORD records) {
462
+ After after;
463
+ {
464
+ std::lock_guard<std::recursive_mutex> lock(mutex_);
465
+ auto current = browses_.find(browse->scanId);
466
+ if (current != browses_.end() && current->second == browse) {
467
+ if (status != ERROR_SUCCESS && status != ERROR_CANCELLED) {
468
+ Error error = WindowsError(status, L"Browsing services failed: ", L"");
469
+ std::string scanId = browse->scanId;
470
+ after.push_back([this, scanId, error] {
471
+ if (events_.error) events_.error(scanId, error);
472
+ });
473
+ }
474
+ for (PDNS_RECORD record = records; record; record = record->pNext) {
475
+ if (DebugEnabled()) {
476
+ fwprintf(stderr, L"[browse %hs] status=%lu type=%u ttl=%lu name=%ls target=%ls\n", browse->scanId.c_str(), status,
477
+ record->wType, record->dwTtl, record->pName ? record->pName : L"",
478
+ record->wType == DNS_TYPE_PTR && record->Data.PTR.pNameHost ? record->Data.PTR.pNameHost : L"");
479
+ }
480
+ // Live updates: a resolved service whose TXT or SRV record changes is resolved again
481
+ if (!browse->typesOnly && (record->wType == DNS_TYPE_TEXT || record->wType == DNS_TYPE_SRV) && record->pName && record->dwTtl > 0) {
482
+ std::wstring instanceKey = Lower(WithoutTrailingDot(record->pName));
483
+ std::wstring dataKey = instanceKey + (record->wType == DNS_TYPE_TEXT ? L"#txt" : L"#srv");
484
+ std::wstring data = RecordData(*record);
485
+ auto previous = browse->recordData.find(dataKey);
486
+ bool changed = previous != browse->recordData.end() && previous->second != data;
487
+ browse->recordData[dataKey] = data;
488
+ auto foundInstance = browse->found.find(instanceKey);
489
+ if (changed && foundInstance != browse->found.end() && browse->resolved.count(instanceKey) && !browse->resolves.count(instanceKey)) {
490
+ auto resolve = std::make_shared<Resolve>(this);
491
+ resolve->browse = browse;
492
+ resolve->scanId = browse->scanId;
493
+ resolve->name = foundInstance->second;
494
+ resolve->key = instanceKey;
495
+ resolve->queryName = WithoutTrailingDot(record->pName);
496
+ resolve->interfaceIndex = browse->interfaceIndex;
497
+ browse->resolves[instanceKey] = resolve;
498
+ StartScanResolve(browse, resolve, after);
499
+ }
500
+ continue;
501
+ }
502
+ if (record->wType != DNS_TYPE_PTR || !record->Data.PTR.pNameHost) {
503
+ continue;
504
+ }
505
+ std::wstring target = WithoutTrailingDot(record->Data.PTR.pNameHost);
506
+ std::wstring key = Lower(target);
507
+ auto found = browse->found.find(key);
508
+ std::string scanId = browse->scanId;
509
+
510
+ // A zero TTL is a goodbye: the service left
511
+ if (record->dwTtl == 0) {
512
+ if (browse->typesOnly) {
513
+ browse->networkTypes.erase(key);
514
+ // Still listed while this app publishes it
515
+ if (LocallyPublished(key)) {
516
+ continue;
517
+ }
518
+ }
519
+ if (found != browse->found.end()) {
520
+ std::wstring name = found->second;
521
+ browse->found.erase(found);
522
+ auto resolve = browse->resolves.find(key);
523
+ if (resolve != browse->resolves.end()) {
524
+ CancelResolve(resolve->second, after);
525
+ browse->resolves.erase(resolve);
526
+ }
527
+ browse->resolved.erase(key);
528
+ after.push_back([this, scanId, name] {
529
+ if (events_.remove) events_.remove(scanId, name);
530
+ });
531
+ }
532
+ continue;
533
+ }
534
+ if (browse->typesOnly) {
535
+ browse->networkTypes.insert(key);
536
+ }
537
+ if (found != browse->found.end()) {
538
+ continue;
539
+ }
540
+
541
+ std::wstring name = browse->typesOnly ? ServiceTypeName(target) : InstanceName(target, browse->regType, browse->domain);
542
+ // Service type answers can also announce subtypes ("_printer._sub._ipp._tcp"), only types are listed
543
+ if (browse->typesOnly && !EndsWith(Lower(name), L"._tcp") && !EndsWith(Lower(name), L"._udp")) {
544
+ continue;
545
+ }
546
+ browse->found[key] = name;
547
+ after.push_back([this, scanId, name] {
548
+ if (events_.found) events_.found(scanId, name);
549
+ });
550
+ // Service types are not resolved
551
+ if (!browse->typesOnly) {
552
+ auto resolve = std::make_shared<Resolve>(this);
553
+ resolve->browse = browse;
554
+ resolve->scanId = scanId;
555
+ resolve->name = name;
556
+ resolve->key = key;
557
+ resolve->queryName = target;
558
+ resolve->interfaceIndex = browse->interfaceIndex;
559
+ browse->resolves[key] = resolve;
560
+ StartScanResolve(browse, resolve, after);
561
+ }
562
+ }
563
+ }
564
+ }
565
+ if (records) {
566
+ DnsRecordListFree(records, DnsFreeRecordList);
567
+ }
568
+ Run(after);
569
+ }
570
+
571
+ // Resolve
572
+
573
+ void Zeroconf::StartResolve(const std::shared_ptr<Resolve> &resolve, After &after) {
574
+ resolve->request.Version = DNS_QUERY_REQUEST_VERSION1;
575
+ resolve->request.InterfaceIndex = resolve->interfaceIndex;
576
+ resolve->request.QueryName = &resolve->queryName[0];
577
+ resolve->request.pResolveCompletionCallback = ResolveCallback;
578
+ resolve->request.pQueryContext = resolve.get();
579
+ Track(resolve);
580
+ DNS_STATUS status = DnsServiceResolve(&resolve->request, &resolve->cancel);
581
+ if (status == DNS_REQUEST_PENDING) {
582
+ return;
583
+ }
584
+ Untrack(resolve.get());
585
+ resolve->done = true;
586
+ Error error = WindowsError(status, L"Resolving service " + resolve->name + L" failed: ", resolve->name);
587
+ if (resolve->promiseReject) {
588
+ auto reject = resolve->promiseReject;
589
+ after.push_back([reject, error] { reject(error); });
590
+ return;
591
+ }
592
+ std::string scanId = resolve->scanId;
593
+ after.push_back([this, scanId, error] {
594
+ if (events_.error) events_.error(scanId, error);
595
+ });
596
+ }
597
+
598
+ // Resolves of a scan time out after the scan's resolveTimeout, are retried once, then reported with a TIMEOUT error
599
+ void Zeroconf::StartScanResolve(const std::shared_ptr<Browse> &browse, const std::shared_ptr<Resolve> &resolve, After &after) {
600
+ StartResolve(resolve, after);
601
+ if (resolve->done) {
602
+ return;
603
+ }
604
+ std::weak_ptr<Resolve> weak = resolve;
605
+ auto alive = alive_;
606
+ DWORD timeout = browse->resolveTimeoutMs;
607
+ std::thread([this, weak, alive, timeout] {
608
+ Sleep(timeout);
609
+ if (!*alive) return;
610
+ auto expired = weak.lock();
611
+ if (!expired) return;
612
+ After timedOut;
613
+ {
614
+ std::lock_guard<std::recursive_mutex> lock(mutex_);
615
+ auto browse = expired->browse.lock();
616
+ auto current = browse ? browses_.find(browse->scanId) : browses_.end();
617
+ if (expired->done || current == browses_.end() || current->second != browse) return;
618
+ auto entry = browse->resolves.find(expired->key);
619
+ if (entry == browse->resolves.end() || entry->second != expired) return;
620
+ CancelResolve(expired, timedOut);
621
+ browse->resolves.erase(entry);
622
+ if (!expired->retried) {
623
+ // Slow devices can time out, retry once before reporting the error
624
+ auto retry = std::make_shared<Resolve>(this);
625
+ retry->browse = browse;
626
+ retry->scanId = expired->scanId;
627
+ retry->name = expired->name;
628
+ retry->key = expired->key;
629
+ retry->queryName = expired->queryName;
630
+ retry->interfaceIndex = expired->interfaceIndex;
631
+ retry->retried = true;
632
+ browse->resolves[retry->key] = retry;
633
+ StartScanResolve(browse, retry, timedOut);
634
+ } else {
635
+ Error error = LibraryError(L"TIMEOUT", L"Resolving service " + expired->name + L" failed: timed out", expired->name);
636
+ std::string scanId = expired->scanId;
637
+ timedOut.push_back([this, scanId, error] {
638
+ if (events_.error) events_.error(scanId, error);
639
+ });
640
+ }
641
+ }
642
+ Run(timedOut);
643
+ }).detach();
644
+ }
645
+
646
+ void Zeroconf::CancelResolve(const std::shared_ptr<Resolve> &resolve, After &after) {
647
+ if (resolve->done) {
648
+ return;
649
+ }
650
+ resolve->done = true;
651
+ Untrack(resolve.get());
652
+ after.push_back([resolve] { DnsServiceResolveCancel(&resolve->cancel); });
653
+ }
654
+
655
+ void Zeroconf::OnResolve(const std::shared_ptr<Resolve> &resolve, DWORD status, PDNS_SERVICE_INSTANCE instance) {
656
+ Service service;
657
+ bool resolved = status == ERROR_SUCCESS && instance != nullptr;
658
+ if (resolved) {
659
+ service.name = resolve->name;
660
+ service.fullName = instance->pszInstanceName ? instance->pszInstanceName : resolve->queryName;
661
+ service.host = instance->pszHostName ? instance->pszHostName : L"";
662
+ service.port = instance->wPort;
663
+ if (instance->ip4Address) {
664
+ IN_ADDR address{};
665
+ address.S_un.S_addr = *instance->ip4Address;
666
+ std::wstring text = AddressString(AF_INET, &address);
667
+ if (!text.empty()) service.addresses.push_back(text);
668
+ }
669
+ if (instance->ip6Address) {
670
+ IN6_ADDR address{};
671
+ memcpy(&address, instance->ip6Address->IP6Byte, sizeof(address));
672
+ std::wstring text = AddressString(AF_INET6, &address);
673
+ if (!text.empty()) service.addresses.push_back(text);
674
+ }
675
+ for (DWORD i = 0; i < instance->dwPropertyCount; i++) {
676
+ if (instance->keys && instance->keys[i]) {
677
+ service.txt.emplace_back(instance->keys[i], instance->values && instance->values[i] ? instance->values[i] : L"");
678
+ }
679
+ }
680
+ }
681
+ if (DebugEnabled()) {
682
+ fwprintf(stderr, L"[resolve %ls] status=%lu host=%ls port=%u addresses=%zu txt=%zu\n", resolve->name.c_str(), status,
683
+ service.host.c_str(), service.port, service.addresses.size(), service.txt.size());
684
+ }
685
+ if (instance) {
686
+ DnsServiceFreeInstance(instance);
687
+ }
688
+
689
+ After after;
690
+ {
691
+ std::lock_guard<std::recursive_mutex> lock(mutex_);
692
+ Untrack(resolve.get());
693
+ if (resolve->done) {
694
+ return;
695
+ }
696
+ resolve->done = true;
697
+ Error error = WindowsError(status, L"Resolving service " + resolve->name + L" failed: ", resolve->name);
698
+
699
+ if (resolve->promiseResolve) {
700
+ singleResolves_.erase(std::remove(singleResolves_.begin(), singleResolves_.end(), resolve), singleResolves_.end());
701
+ auto promiseResolve = resolve->promiseResolve;
702
+ auto promiseReject = resolve->promiseReject;
703
+ after.push_back([resolved, service, error, promiseResolve, promiseReject] {
704
+ if (resolved) {
705
+ promiseResolve(service);
706
+ } else {
707
+ promiseReject(error);
708
+ }
709
+ });
710
+ } else {
711
+ auto browse = resolve->browse.lock();
712
+ auto current = browse ? browses_.find(browse->scanId) : browses_.end();
713
+ if (current == browses_.end() || current->second != browse) {
714
+ return;
715
+ }
716
+ auto entry = browse->resolves.find(resolve->key);
717
+ if (entry == browse->resolves.end() || entry->second != resolve) {
718
+ return;
719
+ }
720
+ browse->resolves.erase(entry);
721
+ if (resolved) {
722
+ browse->resolved.insert(resolve->key);
723
+ }
724
+ std::string scanId = resolve->scanId;
725
+ after.push_back([this, resolved, service, error, scanId] {
726
+ if (resolved) {
727
+ if (events_.resolved) events_.resolved(scanId, service);
728
+ } else if (events_.error) {
729
+ events_.error(scanId, error);
730
+ }
731
+ });
732
+ }
733
+ }
734
+ Run(after);
735
+ }
736
+
737
+ void Zeroconf::ResolveService(
738
+ const std::wstring &name,
739
+ const std::wstring &type,
740
+ const std::wstring &protocol,
741
+ const std::wstring &domain,
742
+ const std::wstring &networkInterface,
743
+ double timeoutSeconds,
744
+ ServiceCallback resolve,
745
+ ErrorCallback reject) {
746
+ After after;
747
+ std::shared_ptr<Resolve> single;
748
+ {
749
+ std::lock_guard<std::recursive_mutex> lock(mutex_);
750
+ single = std::make_shared<Resolve>(this);
751
+ Error interfaceError;
752
+ if (!InterfaceIndex(networkInterface, single->interfaceIndex, interfaceError)) {
753
+ after.push_back([reject, interfaceError] { reject(interfaceError); });
754
+ single = nullptr;
755
+ } else {
756
+ single->name = name;
757
+ single->queryName = name + L"._" + type + L"._" + protocol + L"." + DomainName(domain);
758
+ single->promiseResolve = std::move(resolve);
759
+ single->promiseReject = std::move(reject);
760
+ singleResolves_.push_back(single);
761
+ StartResolve(single, after);
762
+ }
763
+ }
764
+ Run(after);
765
+ if (!single) {
766
+ return;
767
+ }
768
+
769
+ std::weak_ptr<Resolve> weak = single;
770
+ auto alive = alive_;
771
+ DWORD timeout = static_cast<DWORD>((timeoutSeconds > 0 ? timeoutSeconds : 5) * 1000);
772
+ std::thread([this, weak, alive, timeout] {
773
+ Sleep(timeout);
774
+ if (!*alive) return;
775
+ auto expired = weak.lock();
776
+ if (!expired) return;
777
+ After timedOut;
778
+ {
779
+ std::lock_guard<std::recursive_mutex> lock(mutex_);
780
+ if (expired->done) return;
781
+ CancelResolve(expired, timedOut);
782
+ singleResolves_.erase(std::remove(singleResolves_.begin(), singleResolves_.end(), expired), singleResolves_.end());
783
+ Error error = LibraryError(L"TIMEOUT", L"Resolving service " + expired->name + L" failed: timed out", expired->name);
784
+ auto reject = expired->promiseReject;
785
+ timedOut.push_back([reject, error] { reject(error); });
786
+ }
787
+ Run(timedOut);
788
+ }).detach();
789
+ }
790
+
791
+ // Publish
792
+
793
+ void Zeroconf::Publish(
794
+ const std::wstring &type,
795
+ const std::wstring &protocol,
796
+ const std::wstring &domain,
797
+ const std::wstring &name,
798
+ uint16_t port,
799
+ const TxtPairs &txt,
800
+ const std::wstring &networkInterface,
801
+ ServiceCallback resolve,
802
+ ErrorCallback reject) {
803
+ ULONG interfaceIndex = 0;
804
+ Error interfaceError;
805
+ if (!InterfaceIndex(networkInterface, interfaceIndex, interfaceError)) {
806
+ interfaceError.serviceName = name;
807
+ if (events_.error) events_.error("", interfaceError);
808
+ reject(interfaceError);
809
+ return;
810
+ }
811
+ PublishAs(L"_" + type + L"._" + protocol, DomainName(domain), name, port, txt, interfaceIndex, true, std::move(resolve), std::move(reject));
812
+ }
813
+
814
+ void Zeroconf::PublishAs(
815
+ const std::wstring &regType,
816
+ const std::wstring &domain,
817
+ const std::wstring &requestedName,
818
+ uint16_t port,
819
+ const TxtPairs &txt,
820
+ ULONG interfaceIndex,
821
+ bool announce,
822
+ ServiceCallback resolve,
823
+ ErrorCallback reject) {
824
+ After after;
825
+ {
826
+ std::lock_guard<std::recursive_mutex> lock(mutex_);
827
+ // The same app can't publish a name twice, rename like Bonjour does on conflicts
828
+ std::wstring name = requestedName;
829
+ for (int suffix = 2; publications_.count(name); suffix++) {
830
+ name = requestedName + L" (" + std::to_wstring(suffix) + L")";
831
+ }
832
+
833
+ auto publication = std::make_shared<Publication>(this);
834
+ publication->requestedName = name;
835
+ publication->name = name;
836
+ publication->regType = regType;
837
+ publication->domain = domain;
838
+ publication->port = port;
839
+ publication->txt = txt;
840
+ publication->interfaceIndex = interfaceIndex;
841
+ publication->announce = announce;
842
+ publication->onRegistered = std::move(resolve);
843
+ publication->onRegisterFailed = std::move(reject);
844
+
845
+ std::vector<PCWSTR> keys;
846
+ std::vector<PCWSTR> values;
847
+ for (auto &pair : publication->txt) {
848
+ keys.push_back(pair.first.c_str());
849
+ values.push_back(pair.second.c_str());
850
+ }
851
+ std::wstring fullName = name + L"." + regType + L"." + domain;
852
+ publication->instance = DnsServiceConstructInstance(
853
+ fullName.c_str(),
854
+ hostName_.c_str(),
855
+ nullptr,
856
+ nullptr,
857
+ port,
858
+ 0,
859
+ 0,
860
+ static_cast<DWORD>(keys.size()),
861
+ keys.empty() ? nullptr : keys.data(),
862
+ values.empty() ? nullptr : values.data());
863
+ DNS_STATUS status = publication->instance ? DNS_REQUEST_PENDING : GetLastError();
864
+ if (publication->instance) {
865
+ publication->request.Version = DNS_QUERY_REQUEST_VERSION1;
866
+ publication->request.InterfaceIndex = interfaceIndex;
867
+ publication->request.pServiceInstance = publication->instance;
868
+ publication->request.pRegisterCompletionCallback = RegisterCallback;
869
+ publication->request.pQueryContext = publication.get();
870
+ publication->request.hCredentials = nullptr;
871
+ publication->request.unicastEnabled = FALSE;
872
+ Track(publication);
873
+ status = DnsServiceRegister(&publication->request, &publication->cancel);
874
+ }
875
+ if (status == DNS_REQUEST_PENDING) {
876
+ publications_[name] = publication;
877
+ } else {
878
+ Untrack(publication.get());
879
+ Error error = WindowsError(status, L"Publishing service " + name + L" failed: ", name);
880
+ auto onFailed = publication->onRegisterFailed;
881
+ after.push_back([this, error, onFailed] {
882
+ if (events_.error) events_.error("", error);
883
+ if (onFailed) onFailed(error);
884
+ });
885
+ }
886
+ }
887
+ Run(after);
888
+ }
889
+
890
+ void Zeroconf::OnRegister(const std::shared_ptr<Publication> &publication, DWORD status, PDNS_SERVICE_INSTANCE instance) {
891
+ std::wstring registeredName = publication->name;
892
+ if (instance && instance->pszInstanceName) {
893
+ registeredName = InstanceName(instance->pszInstanceName, publication->regType, publication->domain);
894
+ }
895
+ if (DebugEnabled()) {
896
+ fwprintf(stderr, L"[register %ls] status=%lu state=%d name=%ls\n", publication->requestedName.c_str(), status,
897
+ static_cast<int>(publication->state), registeredName.c_str());
898
+ }
899
+ if (instance && instance != publication->instance) {
900
+ DnsServiceFreeInstance(instance);
901
+ }
902
+
903
+ After after;
904
+ {
905
+ std::lock_guard<std::recursive_mutex> lock(mutex_);
906
+ if (publication->state == Publication::State::Registering) {
907
+ auto entry = publications_.find(publication->name);
908
+ bool current = entry != publications_.end() && entry->second == publication;
909
+ if (status != ERROR_SUCCESS) {
910
+ Untrack(publication.get());
911
+ publication->state = Publication::State::Done;
912
+ if (current) publications_.erase(entry);
913
+ Error error = WindowsError(status, L"Publishing service " + publication->name + L" failed: ", publication->name);
914
+ auto onFailed = publication->onRegisterFailed;
915
+ after.push_back([this, error, onFailed] {
916
+ if (events_.error) events_.error("", error);
917
+ if (onFailed) onFailed(error);
918
+ });
919
+ } else {
920
+ publication->state = Publication::State::Registered;
921
+ if (current && registeredName != publication->name) {
922
+ publications_.erase(entry);
923
+ publications_[registeredName] = publication;
924
+ }
925
+ publication->name = registeredName;
926
+ SyncLocalType(publication->regType, publication->domain, after);
927
+ Service service = PublishedService(*publication);
928
+ auto onRegistered = publication->onRegistered;
929
+ bool announce = publication->announce;
930
+ after.push_back([this, service, onRegistered, announce] {
931
+ if (announce && events_.published) events_.published(service);
932
+ if (onRegistered) onRegistered(service);
933
+ });
934
+ }
935
+ publication->onRegistered = nullptr;
936
+ publication->onRegisterFailed = nullptr;
937
+ } else if (publication->state == Publication::State::Deregistering) {
938
+ publication->state = Publication::State::Done;
939
+ Untrack(publication.get());
940
+ // An update publishes it again right after, keep the type listed
941
+ if (publication->announce) {
942
+ SyncLocalType(publication->regType, publication->domain, after);
943
+ }
944
+ Service service = PublishedService(*publication);
945
+ auto onDeregistered = publication->onDeregistered;
946
+ bool announce = publication->announce;
947
+ after.push_back([this, service, onDeregistered, announce] {
948
+ if (announce && events_.unpublished) events_.unpublished(service);
949
+ if (onDeregistered) onDeregistered(service);
950
+ });
951
+ publication->onDeregistered = nullptr;
952
+ }
953
+ }
954
+ Run(after);
955
+ }
956
+
957
+ bool Zeroconf::LocallyPublished(const std::wstring &typeKey) const {
958
+ for (auto &entry : publications_) {
959
+ auto &publication = entry.second;
960
+ if (publication->state == Publication::State::Registered && Lower(publication->regType + L"." + publication->domain) == typeKey) {
961
+ return true;
962
+ }
963
+ }
964
+ return false;
965
+ }
966
+
967
+ void Zeroconf::SyncLocalType(const std::wstring &regType, const std::wstring &domain, After &after) {
968
+ std::wstring key = Lower(regType + L"." + domain);
969
+ bool published = LocallyPublished(key);
970
+ for (auto &entry : browses_) {
971
+ auto browse = entry.second;
972
+ if (!browse->typesOnly || Lower(browse->domain) != Lower(domain)) {
973
+ continue;
974
+ }
975
+ std::string scanId = browse->scanId;
976
+ auto listed = browse->found.find(key);
977
+ if (published && listed == browse->found.end()) {
978
+ browse->found[key] = regType;
979
+ after.push_back([this, scanId, regType] {
980
+ if (events_.found) events_.found(scanId, regType);
981
+ });
982
+ } else if (!published && listed != browse->found.end() && !browse->networkTypes.count(key)) {
983
+ std::wstring name = listed->second;
984
+ browse->found.erase(listed);
985
+ after.push_back([this, scanId, name] {
986
+ if (events_.remove) events_.remove(scanId, name);
987
+ });
988
+ }
989
+ }
990
+ }
991
+
992
+ Service Zeroconf::PublishedService(const Publication &publication) const {
993
+ Service service;
994
+ service.name = publication.name;
995
+ service.fullName = publication.name + L"." + publication.regType + L"." + publication.domain;
996
+ service.host = hostName_;
997
+ service.port = publication.port;
998
+ service.txt = publication.txt;
999
+ return service;
1000
+ }
1001
+
1002
+ void Zeroconf::Unpublish(const std::wstring &name, ServiceCallback resolve, ErrorCallback reject) {
1003
+ UnpublishWith(name, true, std::move(resolve), std::move(reject));
1004
+ }
1005
+
1006
+ void Zeroconf::UnpublishWith(const std::wstring &name, bool announce, ServiceCallback resolve, ErrorCallback reject) {
1007
+ After after;
1008
+ {
1009
+ std::lock_guard<std::recursive_mutex> lock(mutex_);
1010
+ auto entry = publications_.find(name);
1011
+ if (entry == publications_.end()) {
1012
+ for (auto it = publications_.begin(); it != publications_.end(); ++it) {
1013
+ if (it->second->requestedName == name) {
1014
+ entry = it;
1015
+ break;
1016
+ }
1017
+ }
1018
+ }
1019
+ if (entry == publications_.end()) {
1020
+ Error error = LibraryError(L"NOT_PUBLISHED", L"Service " + name + L" is not published", name);
1021
+ after.push_back([reject, error] { reject(error); });
1022
+ } else {
1023
+ auto publication = entry->second;
1024
+ publications_.erase(entry);
1025
+ if (publication->state == Publication::State::Registering) {
1026
+ // Not registered yet: cancel the registration
1027
+ publication->state = Publication::State::Done;
1028
+ Untrack(publication.get());
1029
+ Error error = LibraryError(L"NOT_PUBLISHED", L"Service " + name + L" was unpublished before it was published", name);
1030
+ auto onFailed = publication->onRegisterFailed;
1031
+ Service service = PublishedService(*publication);
1032
+ after.push_back([publication, onFailed, error, resolve, service] {
1033
+ DnsServiceRegisterCancel(&publication->cancel);
1034
+ if (onFailed) onFailed(error);
1035
+ resolve(service);
1036
+ });
1037
+ } else {
1038
+ publication->state = Publication::State::Deregistering;
1039
+ publication->announce = announce;
1040
+ publication->onDeregistered = std::move(resolve);
1041
+ after.push_back([this, publication, reject] {
1042
+ DWORD status = DnsServiceDeRegister(&publication->request, nullptr);
1043
+ if (status != DNS_REQUEST_PENDING) {
1044
+ Untrack(publication.get());
1045
+ reject(WindowsError(status, L"Unpublishing service " + publication->name + L" failed: ", publication->name));
1046
+ }
1047
+ });
1048
+ }
1049
+ }
1050
+ }
1051
+ Run(after);
1052
+ }
1053
+
1054
+ void Zeroconf::Update(const std::wstring &name, const TxtPairs &txt, ServiceCallback resolve, ErrorCallback reject) {
1055
+ std::shared_ptr<Publication> publication;
1056
+ {
1057
+ std::lock_guard<std::recursive_mutex> lock(mutex_);
1058
+ auto entry = publications_.find(name);
1059
+ if (entry != publications_.end() && entry->second->state == Publication::State::Registered) {
1060
+ publication = entry->second;
1061
+ }
1062
+ }
1063
+ if (!publication) {
1064
+ reject(LibraryError(L"NOT_PUBLISHED", L"Service " + name + L" is not published", name));
1065
+ return;
1066
+ }
1067
+ // No update in windns.h: unpublish, then publish again under the same name
1068
+ std::wstring regType = publication->regType;
1069
+ std::wstring domain = publication->domain;
1070
+ std::wstring registeredName = publication->name;
1071
+ uint16_t port = publication->port;
1072
+ ULONG interfaceIndex = publication->interfaceIndex;
1073
+ UnpublishWith(
1074
+ name,
1075
+ false,
1076
+ [this, regType, domain, registeredName, port, txt, interfaceIndex, resolve, reject](const Service &) {
1077
+ PublishAs(regType, domain, registeredName, port, txt, interfaceIndex, false, resolve, reject);
1078
+ },
1079
+ reject);
1080
+ }
1081
+
1082
+ void Zeroconf::UnpublishAll() {
1083
+ std::vector<std::wstring> names;
1084
+ {
1085
+ std::lock_guard<std::recursive_mutex> lock(mutex_);
1086
+ for (auto &entry : publications_) {
1087
+ names.push_back(entry.first);
1088
+ }
1089
+ }
1090
+ for (auto &name : names) {
1091
+ Unpublish(name, [](const Service &) {}, [](const Error &) {});
1092
+ }
1093
+ }
1094
+
1095
+ } // namespace rnzeroconf