react-native-zeroconf 0.17.3 → 1.0.0-rc.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.
- package/NOTICE +2 -6
- package/README.md +1 -1
- package/android/CMakeLists.txt +64 -0
- package/android/build.gradle +1 -17
- package/android/generated/java/com/balthazargronon/RCTZeroconf/NativeZeroconfSpec.java +96 -0
- package/android/generated/jni/CMakeLists.txt +28 -0
- package/android/generated/jni/RNZeroconfSpec-generated.cpp +77 -0
- package/android/generated/jni/RNZeroconfSpec.h +31 -0
- package/android/generated/jni/react/renderer/components/RNZeroconfSpec/RNZeroconfSpecJSI.h +798 -0
- package/android/src/main/java/com/balthazargronon/RCTZeroconf/{ZeroconfModule.java → NsdHost.java} +97 -140
- package/android/src/main/java/com/balthazargronon/RCTZeroconf/NsdPayload.java +87 -0
- package/android/src/main/java/com/balthazargronon/RCTZeroconf/NsdPromise.java +40 -0
- package/android/src/main/java/com/balthazargronon/RCTZeroconf/Zeroconf.java +4 -6
- package/android/src/main/java/com/balthazargronon/RCTZeroconf/ZeroconfNativeSupport.java +60 -0
- package/android/src/main/java/com/balthazargronon/RCTZeroconf/ZeroconfOptions.java +15 -26
- package/android/src/main/java/com/balthazargronon/RCTZeroconf/ZeroconfReactPackage.java +5 -15
- package/android/src/main/java/com/balthazargronon/RCTZeroconf/nsd/NsdServiceImpl.java +90 -96
- package/android/src/main/jni/mdnsresponder/mDNSShared/dnssd_clientshim.c +31 -6
- package/cpp/Backend.cpp +16 -0
- package/cpp/Backend.h +116 -0
- package/cpp/ZeroconfModule.cpp +280 -0
- package/cpp/ZeroconfModule.h +144 -0
- package/cpp/android/NsdBackend.cpp +462 -0
- package/cpp/android/NsdBackend.h +61 -0
- package/cpp/android/ZeroconfAndroidModule.cpp +49 -0
- package/cpp/android/ZeroconfAndroidModule.h +15 -0
- package/cpp/apple/DispatchExecutor.cpp +66 -0
- package/cpp/apple/DispatchExecutor.h +37 -0
- package/cpp/apple/LocalNetworkAccess.h +19 -0
- package/cpp/apple/LocalNetworkAccess.mm +87 -0
- package/cpp/dnssd/DnssdBackend.cpp +976 -0
- package/cpp/dnssd/DnssdBackend.h +114 -0
- package/cpp/dnssd/Executor.h +28 -0
- package/cpp/embedded/EmbeddedExecutor.cpp +124 -0
- package/cpp/embedded/EmbeddedExecutor.h +57 -0
- package/ios/ZeroconfOnLoad.mm +34 -0
- package/ios/generated/ReactCodegen/RNZeroconfSpec/RNZeroconfSpec-generated.mm +140 -0
- package/ios/generated/ReactCodegen/RNZeroconfSpec/RNZeroconfSpec.h +189 -0
- package/ios/generated/ReactCodegen/RNZeroconfSpecJSI.h +798 -0
- package/lib/module/NativeZeroconf.js +15 -0
- package/lib/module/NativeZeroconf.js.map +1 -0
- package/lib/module/bridge.js +124 -0
- package/lib/module/bridge.js.map +1 -0
- package/{dist → lib/module}/index.js +149 -149
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +24 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeZeroconf.d.ts +75 -0
- package/lib/typescript/src/NativeZeroconf.d.ts.map +1 -0
- package/lib/typescript/src/bridge.d.ts +22 -0
- package/lib/typescript/src/bridge.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +111 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +175 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/package.json +64 -29
- package/react-native-zeroconf.podspec +2 -1
- package/react-native.config.js +17 -0
- package/src/NativeZeroconf.ts +120 -0
- package/src/bridge.ts +153 -0
- package/src/index.ts +607 -0
- package/{index.d.ts → src/types.ts} +5 -99
- package/windows/RNZeroconf/RNZeroconf.vcxproj +17 -2
- package/windows/RNZeroconf/RNZeroconf.vcxproj.filters +0 -6
- package/windows/RNZeroconf/ReactPackageProvider.cpp +23 -20
- package/windows/RNZeroconf/WindowsBackend.cpp +156 -0
- package/windows/RNZeroconf/WindowsBackend.h +13 -0
- package/windows/RNZeroconf/ZeroconfCore.cpp +2 -2
- package/windows/RNZeroconf/ZeroconfCore.h +2 -2
- package/windows/RNZeroconf/ZeroconfWindowsModule.cpp +22 -0
- package/windows/RNZeroconf/ZeroconfWindowsModule.h +14 -0
- package/android/src/main/java/com/balthazargronon/RCTZeroconf/ZeroConfImplFactory.java +0 -56
- package/android/src/main/java/com/balthazargronon/RCTZeroconf/dnssd/DnssdImpl.java +0 -654
- package/android/src/main/java/com/github/druk/dnssd/BaseListener.java +0 -33
- package/android/src/main/java/com/github/druk/dnssd/BrowseListener.java +0 -67
- package/android/src/main/java/com/github/druk/dnssd/DNSRecord.java +0 -47
- package/android/src/main/java/com/github/druk/dnssd/DNSSD.java +0 -681
- package/android/src/main/java/com/github/druk/dnssd/DNSSDEmbedded.java +0 -198
- package/android/src/main/java/com/github/druk/dnssd/DNSSDException.java +0 -66
- package/android/src/main/java/com/github/druk/dnssd/DNSSDRecordRegistrar.java +0 -55
- package/android/src/main/java/com/github/druk/dnssd/DNSSDRegistration.java +0 -54
- package/android/src/main/java/com/github/druk/dnssd/DNSSDService.java +0 -35
- package/android/src/main/java/com/github/druk/dnssd/DomainListener.java +0 -57
- package/android/src/main/java/com/github/druk/dnssd/InternalBrowseListener.java +0 -76
- package/android/src/main/java/com/github/druk/dnssd/InternalDNSSD.java +0 -870
- package/android/src/main/java/com/github/druk/dnssd/InternalDNSSDRecordRegistrar.java +0 -30
- package/android/src/main/java/com/github/druk/dnssd/InternalDNSSDRegistration.java +0 -34
- package/android/src/main/java/com/github/druk/dnssd/InternalDNSSDService.java +0 -29
- package/android/src/main/java/com/github/druk/dnssd/InternalDomainListener.java +0 -62
- package/android/src/main/java/com/github/druk/dnssd/InternalQueryListener.java +0 -61
- package/android/src/main/java/com/github/druk/dnssd/InternalRegisterListener.java +0 -51
- package/android/src/main/java/com/github/druk/dnssd/InternalResolveListener.java +0 -58
- package/android/src/main/java/com/github/druk/dnssd/NSClass.java +0 -16
- package/android/src/main/java/com/github/druk/dnssd/NSType.java +0 -58
- package/android/src/main/java/com/github/druk/dnssd/QueryListener.java +0 -54
- package/android/src/main/java/com/github/druk/dnssd/RegisterListener.java +0 -46
- package/android/src/main/java/com/github/druk/dnssd/RegisterRecordListener.java +0 -35
- package/android/src/main/java/com/github/druk/dnssd/ResolveListener.java +0 -55
- package/android/src/main/java/com/github/druk/dnssd/TXTRecord.java +0 -293
- package/android/src/main/jni/Android.mk +0 -73
- package/android/src/main/jni/Application.mk +0 -3
- package/android/src/main/jni/DNSSD.java.h +0 -290
- package/android/src/main/jni/JNISupport.c +0 -1221
- package/ios/RNZeroconf/RNNetServiceSerializer.h +0 -31
- package/ios/RNZeroconf/RNNetServiceSerializer.m +0 -84
- package/ios/RNZeroconf/RNZeroconf.h +0 -17
- package/ios/RNZeroconf/RNZeroconf.m +0 -1025
- package/ios/RNZeroconf.xcodeproj/project.pbxproj +0 -261
- package/ios/RNZeroconf.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
- package/src/index.js +0 -563
- package/windows/RNZeroconf/RNZeroconf.cpp +0 -228
- package/windows/RNZeroconf/RNZeroconf.h +0 -70
|
@@ -0,0 +1,976 @@
|
|
|
1
|
+
#include "DnssdBackend.h"
|
|
2
|
+
|
|
3
|
+
#include <arpa/inet.h>
|
|
4
|
+
#include <net/if.h>
|
|
5
|
+
#include <netinet/in.h>
|
|
6
|
+
|
|
7
|
+
#include <algorithm>
|
|
8
|
+
#include <cstring>
|
|
9
|
+
#include <optional>
|
|
10
|
+
#include <vector>
|
|
11
|
+
|
|
12
|
+
namespace rnzeroconf {
|
|
13
|
+
|
|
14
|
+
// A browse, keyed by the id of the JS instance that started it, several can run at once
|
|
15
|
+
struct DnssdScan {
|
|
16
|
+
std::weak_ptr<DnssdBackend> owner;
|
|
17
|
+
std::weak_ptr<DnssdScan> self;
|
|
18
|
+
std::string scanId;
|
|
19
|
+
DNSServiceRef browseRef = nullptr;
|
|
20
|
+
double resolveTimeoutSeconds = 5;
|
|
21
|
+
uint32_t interfaceIndex = kDNSServiceInterfaceIndexAny;
|
|
22
|
+
// Services are reported once per network interface, count them so found/remove are emitted once
|
|
23
|
+
std::map<std::string, int> foundInterfaces;
|
|
24
|
+
std::map<std::string, std::shared_ptr<DnssdResolve>> resolving;
|
|
25
|
+
// Browsing _services._dns-sd._udp: results are service types, reported as "_http._tcp" and not resolved
|
|
26
|
+
bool typesOnly = false;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// A found service being resolved: DNSServiceResolve for host, port and TXT, then DNSServiceGetAddrInfo for
|
|
30
|
+
// addresses. Once resolved, the address query and a TXT record query stay open and changes are emitted again.
|
|
31
|
+
// Without a scan, it is a single resolveService() call settling its callbacks.
|
|
32
|
+
struct DnssdResolve {
|
|
33
|
+
std::weak_ptr<DnssdBackend> owner;
|
|
34
|
+
std::weak_ptr<DnssdResolve> self;
|
|
35
|
+
std::weak_ptr<DnssdScan> scan;
|
|
36
|
+
ServiceCallback promiseResolve;
|
|
37
|
+
ErrorCallback promiseReject;
|
|
38
|
+
bool single = false;
|
|
39
|
+
uint32_t interfaceIndex = kDNSServiceInterfaceIndexAny;
|
|
40
|
+
double timeoutSeconds = 5;
|
|
41
|
+
DNSServiceRef resolveRef = nullptr;
|
|
42
|
+
DNSServiceRef addressRef = nullptr;
|
|
43
|
+
DNSServiceRef txtRef = nullptr;
|
|
44
|
+
bool emitted = false;
|
|
45
|
+
std::optional<Service> lastEmitted;
|
|
46
|
+
std::string name;
|
|
47
|
+
std::string regtype;
|
|
48
|
+
std::string domain;
|
|
49
|
+
std::string host;
|
|
50
|
+
uint16_t port = 0;
|
|
51
|
+
TxtPairs txt;
|
|
52
|
+
std::vector<std::string> addresses;
|
|
53
|
+
bool retried = false;
|
|
54
|
+
bool finished = false;
|
|
55
|
+
Executor::TimerId timeoutTimer = 0;
|
|
56
|
+
Executor::TimerId emitTimer = 0;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// A service registered with DNSServiceRegister
|
|
60
|
+
struct DnssdPublication {
|
|
61
|
+
std::weak_ptr<DnssdBackend> owner;
|
|
62
|
+
std::weak_ptr<DnssdPublication> self;
|
|
63
|
+
std::string name;
|
|
64
|
+
std::string regtype;
|
|
65
|
+
std::string domain;
|
|
66
|
+
uint16_t port = 0;
|
|
67
|
+
TxtPairs txt;
|
|
68
|
+
DNSServiceRef ref = nullptr;
|
|
69
|
+
ServiceCallback resolve;
|
|
70
|
+
ErrorCallback reject;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
namespace {
|
|
74
|
+
|
|
75
|
+
bool operator==(const Service &a, const Service &b) {
|
|
76
|
+
return a.name == b.name && a.fullName == b.fullName && a.host == b.host && a.port == b.port &&
|
|
77
|
+
a.addresses == b.addresses && a.txt == b.txt;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// dns_sd callbacks copy what they receive and hand it to the executor: the embedded mDNSResponder of Android
|
|
81
|
+
// calls back on its own thread (and sometimes from inside the call that starts the operation), Apple's on
|
|
82
|
+
// the executor's queue. Either way the operation is looked up again before it is used.
|
|
83
|
+
template <typename Operation, typename Handler>
|
|
84
|
+
void Deliver(Operation *operation, Handler handler) {
|
|
85
|
+
auto owner = operation->owner.lock();
|
|
86
|
+
if (!owner) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
owner->Post([weakOwner = operation->owner, weakOperation = operation->self, handler = std::move(handler)] {
|
|
90
|
+
auto backend = weakOwner.lock();
|
|
91
|
+
auto current = weakOperation.lock();
|
|
92
|
+
if (backend && current) {
|
|
93
|
+
handler(*backend, current);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
std::string AddressString(const struct sockaddr *address);
|
|
99
|
+
|
|
100
|
+
void DNSSD_API BrowseReply(DNSServiceRef, DNSServiceFlags flags, uint32_t, DNSServiceErrorType error, const char *name,
|
|
101
|
+
const char *regtype, const char *domain, void *context) {
|
|
102
|
+
std::optional<std::string> nameCopy = name ? std::optional<std::string>(name) : std::nullopt;
|
|
103
|
+
std::string regtypeCopy = regtype ? regtype : "";
|
|
104
|
+
std::string domainCopy = domain ? domain : "";
|
|
105
|
+
Deliver(static_cast<DnssdScan *>(context), [=](DnssdBackend &backend, const std::shared_ptr<DnssdScan> &scan) {
|
|
106
|
+
backend.OnBrowse(scan, flags, error, nameCopy, regtypeCopy, domainCopy);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
void DNSSD_API ResolveReply(DNSServiceRef, DNSServiceFlags, uint32_t, DNSServiceErrorType error, const char *,
|
|
111
|
+
const char *host, uint16_t port, uint16_t txtLength, const unsigned char *txt, void *context) {
|
|
112
|
+
std::string hostCopy = host ? host : "";
|
|
113
|
+
std::string txtCopy = txt ? std::string(reinterpret_cast<const char *>(txt), txtLength) : "";
|
|
114
|
+
uint16_t hostPort = ntohs(port);
|
|
115
|
+
Deliver(static_cast<DnssdResolve *>(context), [=](DnssdBackend &backend, const std::shared_ptr<DnssdResolve> &resolve) {
|
|
116
|
+
backend.OnResolve(resolve, error, hostCopy, hostPort, txtCopy);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
void DNSSD_API AddressReply(DNSServiceRef, DNSServiceFlags flags, uint32_t, DNSServiceErrorType error, const char *,
|
|
121
|
+
const struct sockaddr *address, uint32_t, void *context) {
|
|
122
|
+
std::string addressCopy = AddressString(address);
|
|
123
|
+
Deliver(static_cast<DnssdResolve *>(context), [=](DnssdBackend &backend, const std::shared_ptr<DnssdResolve> &resolve) {
|
|
124
|
+
backend.OnAddress(resolve, flags, error, addressCopy);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
void DNSSD_API TxtReply(DNSServiceRef, DNSServiceFlags flags, uint32_t, DNSServiceErrorType error, const char *,
|
|
129
|
+
uint16_t, uint16_t, uint16_t length, const void *data, uint32_t, void *context) {
|
|
130
|
+
std::string dataCopy = data ? std::string(static_cast<const char *>(data), length) : "";
|
|
131
|
+
Deliver(static_cast<DnssdResolve *>(context), [=](DnssdBackend &backend, const std::shared_ptr<DnssdResolve> &resolve) {
|
|
132
|
+
backend.OnTxt(resolve, flags, error, dataCopy);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
void DNSSD_API RegisterReply(DNSServiceRef, DNSServiceFlags flags, DNSServiceErrorType error, const char *name,
|
|
137
|
+
const char *, const char *, void *context) {
|
|
138
|
+
std::optional<std::string> nameCopy = name ? std::optional<std::string>(name) : std::nullopt;
|
|
139
|
+
Deliver(static_cast<DnssdPublication *>(context), [=](DnssdBackend &backend, const std::shared_ptr<DnssdPublication> &publication) {
|
|
140
|
+
backend.OnRegister(publication, flags, error, nameCopy);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
std::string AddressString(const struct sockaddr *address) {
|
|
145
|
+
if (address == nullptr) {
|
|
146
|
+
return "";
|
|
147
|
+
}
|
|
148
|
+
char buffer[INET6_ADDRSTRLEN] = {};
|
|
149
|
+
const char *result = nullptr;
|
|
150
|
+
if (address->sa_family == AF_INET) {
|
|
151
|
+
result = inet_ntop(AF_INET, &reinterpret_cast<const sockaddr_in *>(address)->sin_addr, buffer, sizeof(buffer));
|
|
152
|
+
} else if (address->sa_family == AF_INET6) {
|
|
153
|
+
result = inet_ntop(AF_INET6, &reinterpret_cast<const sockaddr_in6 *>(address)->sin6_addr, buffer, sizeof(buffer));
|
|
154
|
+
}
|
|
155
|
+
return result ? std::string(result) : "";
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// TXT values are UTF-8 by convention, older devices send Latin-1
|
|
159
|
+
bool IsUtf8(const std::string &value) {
|
|
160
|
+
size_t i = 0;
|
|
161
|
+
while (i < value.size()) {
|
|
162
|
+
auto c = static_cast<unsigned char>(value[i]);
|
|
163
|
+
size_t extra;
|
|
164
|
+
if (c < 0x80) {
|
|
165
|
+
extra = 0;
|
|
166
|
+
} else if ((c >> 5) == 0x6) {
|
|
167
|
+
extra = 1;
|
|
168
|
+
} else if ((c >> 4) == 0xE) {
|
|
169
|
+
extra = 2;
|
|
170
|
+
} else if ((c >> 3) == 0x1E) {
|
|
171
|
+
extra = 3;
|
|
172
|
+
} else {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
if (i + extra >= value.size() && extra > 0) {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
for (size_t k = 1; k <= extra; k++) {
|
|
179
|
+
if ((static_cast<unsigned char>(value[i + k]) >> 6) != 0x2) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
i += extra + 1;
|
|
184
|
+
}
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
std::string Latin1ToUtf8(const std::string &value) {
|
|
189
|
+
std::string out;
|
|
190
|
+
for (unsigned char c : value) {
|
|
191
|
+
if (c < 0x80) {
|
|
192
|
+
out += static_cast<char>(c);
|
|
193
|
+
} else {
|
|
194
|
+
out += static_cast<char>(0xC0 | (c >> 6));
|
|
195
|
+
out += static_cast<char>(0x80 | (c & 0x3F));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return out;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
Error BrowseError(DNSServiceErrorType code, bool typesOnly) {
|
|
202
|
+
if (!typesOnly) {
|
|
203
|
+
return DnssdError(code, "Browsing services");
|
|
204
|
+
}
|
|
205
|
+
if (code == kDNSServiceErr_NoAuth) {
|
|
206
|
+
// Browsing for service types requires the multicast entitlement (Apple's Local Network Privacy FAQ)
|
|
207
|
+
return Error{"DNSSD", std::to_string(code),
|
|
208
|
+
"Listing service types failed: not authorized, it requires the com.apple.developer.networking.multicast entitlement", ""};
|
|
209
|
+
}
|
|
210
|
+
return DnssdError(code, "Listing service types");
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
Error NotPublished(const std::string &name) {
|
|
214
|
+
return LibraryError("NOT_PUBLISHED", "Service " + name + " is not published", name);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
Error UnknownInterface(const std::string &name) {
|
|
218
|
+
return LibraryError("UNKNOWN_INTERFACE", "Unknown network interface " + name);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
} // namespace
|
|
222
|
+
|
|
223
|
+
std::string DescribeDnssdError(DNSServiceErrorType code) {
|
|
224
|
+
switch (code) {
|
|
225
|
+
case kDNSServiceErr_NoAuth:
|
|
226
|
+
return "not authorized, add the service type to NSBonjourServices in Info.plist";
|
|
227
|
+
// kDNSServiceErr_PolicyDenied, missing from the embedded responder's older dns_sd.h
|
|
228
|
+
case -65570:
|
|
229
|
+
return "Local Network access denied";
|
|
230
|
+
case kDNSServiceErr_NameConflict:
|
|
231
|
+
return "name already in use";
|
|
232
|
+
case kDNSServiceErr_BadParam:
|
|
233
|
+
return "bad parameter";
|
|
234
|
+
case kDNSServiceErr_NoSuchName:
|
|
235
|
+
return "no such name";
|
|
236
|
+
case kDNSServiceErr_NoSuchRecord:
|
|
237
|
+
return "no such record";
|
|
238
|
+
case kDNSServiceErr_ServiceNotRunning:
|
|
239
|
+
return "mDNSResponder is not running";
|
|
240
|
+
case kDNSServiceErr_Timeout:
|
|
241
|
+
return "timed out";
|
|
242
|
+
case kDNSServiceErr_NoMemory:
|
|
243
|
+
return "out of memory";
|
|
244
|
+
case kDNSServiceErr_Unsupported:
|
|
245
|
+
return "unsupported";
|
|
246
|
+
default:
|
|
247
|
+
return "error " + std::to_string(code);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
Error DnssdError(DNSServiceErrorType code, const std::string &action, const std::string &serviceName) {
|
|
252
|
+
std::string subject = serviceName.empty() ? action : action + " " + serviceName;
|
|
253
|
+
return Error{"DNSSD", std::to_string(code), subject + " failed: " + DescribeDnssdError(code), serviceName};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
std::string EncodeTxt(const TxtPairs &txt, std::vector<std::string> *tooLong) {
|
|
257
|
+
std::string record;
|
|
258
|
+
for (const auto &[key, value] : txt) {
|
|
259
|
+
std::string entry = key + "=" + value;
|
|
260
|
+
if (entry.size() > 255) {
|
|
261
|
+
if (tooLong) {
|
|
262
|
+
tooLong->push_back(key);
|
|
263
|
+
}
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
record += static_cast<char>(entry.size());
|
|
267
|
+
record += entry;
|
|
268
|
+
}
|
|
269
|
+
return record;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
TxtPairs DecodeTxt(const void *record, uint16_t length) {
|
|
273
|
+
TxtPairs txt;
|
|
274
|
+
if (record == nullptr || length == 0) {
|
|
275
|
+
return txt;
|
|
276
|
+
}
|
|
277
|
+
uint16_t count = TXTRecordGetCount(length, record);
|
|
278
|
+
for (uint16_t i = 0; i < count; i++) {
|
|
279
|
+
char key[256] = {};
|
|
280
|
+
uint8_t valueLength = 0;
|
|
281
|
+
const void *value = nullptr;
|
|
282
|
+
if (TXTRecordGetItemAtIndex(length, record, i, sizeof(key), key, &valueLength, &value) != kDNSServiceErr_NoError || key[0] == '\0') {
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
std::string string = value != nullptr && valueLength > 0 ? std::string(static_cast<const char *>(value), valueLength) : "";
|
|
286
|
+
txt.emplace_back(key, IsUtf8(string) ? string : Latin1ToUtf8(string));
|
|
287
|
+
}
|
|
288
|
+
return txt;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
bool InterfaceIndex(const std::string &name, uint32_t &index) {
|
|
292
|
+
index = kDNSServiceInterfaceIndexAny;
|
|
293
|
+
if (name.empty()) {
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
unsigned int found = if_nametoindex(name.c_str());
|
|
297
|
+
if (found == 0) {
|
|
298
|
+
return false;
|
|
299
|
+
}
|
|
300
|
+
index = found;
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
std::shared_ptr<DnssdBackend> DnssdBackend::Create(std::shared_ptr<Executor> executor, Events events) {
|
|
305
|
+
return std::shared_ptr<DnssdBackend>(new DnssdBackend(std::move(executor), std::move(events)));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
DnssdBackend::DnssdBackend(std::shared_ptr<Executor> executor, Events events)
|
|
309
|
+
: executor_(std::move(executor)), events_(std::move(events)) {}
|
|
310
|
+
|
|
311
|
+
void DnssdBackend::Post(Executor::Task task) {
|
|
312
|
+
executor_->Post(std::move(task));
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
void DnssdBackend::Run(std::function<void(DnssdBackend &)> task) {
|
|
316
|
+
std::weak_ptr<DnssdBackend> weak = weak_from_this();
|
|
317
|
+
executor_->Post([weak, task = std::move(task)] {
|
|
318
|
+
if (auto self = weak.lock()) {
|
|
319
|
+
task(*self);
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
void DnssdBackend::SendError(const std::string &scanId, const Error &error) {
|
|
325
|
+
if (events_.error) {
|
|
326
|
+
events_.error(scanId, error);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
void DnssdBackend::DeallocateLater(DNSServiceRef &ref, std::shared_ptr<void> keepAlive) {
|
|
331
|
+
if (ref == nullptr) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
DNSServiceRef deallocated = ref;
|
|
335
|
+
ref = nullptr;
|
|
336
|
+
executor_->Post([deallocated, keepAlive = std::move(keepAlive)] { DNSServiceRefDeallocate(deallocated); });
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Scan
|
|
340
|
+
|
|
341
|
+
void DnssdBackend::Scan(
|
|
342
|
+
const std::string &scanId,
|
|
343
|
+
const std::string &type,
|
|
344
|
+
const std::string &protocol,
|
|
345
|
+
const std::string &domain,
|
|
346
|
+
const ScanOptions &options) {
|
|
347
|
+
Run([=](DnssdBackend &self) {
|
|
348
|
+
self.StopScan(scanId);
|
|
349
|
+
|
|
350
|
+
auto scan = std::make_shared<DnssdScan>();
|
|
351
|
+
scan->owner = self.weak_from_this();
|
|
352
|
+
scan->self = scan;
|
|
353
|
+
scan->scanId = scanId;
|
|
354
|
+
scan->resolveTimeoutSeconds = options.resolveTimeoutSeconds > 0 ? options.resolveTimeoutSeconds : 5;
|
|
355
|
+
if (!InterfaceIndex(options.networkInterface, scan->interfaceIndex)) {
|
|
356
|
+
self.SendError(scanId, UnknownInterface(options.networkInterface));
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
std::string regtype = "_" + type + "._" + protocol;
|
|
361
|
+
scan->typesOnly = regtype == "_services._dns-sd._udp";
|
|
362
|
+
// "_ipp._tcp,_printer" browses the _printer subtype
|
|
363
|
+
std::string browseType = regtype;
|
|
364
|
+
std::string subtype = SubtypeLabel(options.subtype);
|
|
365
|
+
if (!subtype.empty()) {
|
|
366
|
+
browseType += "," + subtype;
|
|
367
|
+
}
|
|
368
|
+
DNSServiceErrorType error = DNSServiceBrowse(&scan->browseRef, 0, scan->interfaceIndex, browseType.c_str(),
|
|
369
|
+
domain.empty() ? nullptr : domain.c_str(), BrowseReply, scan.get());
|
|
370
|
+
if (error != kDNSServiceErr_NoError) {
|
|
371
|
+
scan->browseRef = nullptr;
|
|
372
|
+
self.SendError(scanId, BrowseError(error, scan->typesOnly));
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
self.executor_->Attach(scan->browseRef);
|
|
376
|
+
self.scans_[scanId] = scan;
|
|
377
|
+
if (self.events_.start) {
|
|
378
|
+
self.events_.start(scanId);
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
void DnssdBackend::Stop(const std::string &scanId) {
|
|
384
|
+
Run([scanId](DnssdBackend &self) {
|
|
385
|
+
if (!scanId.empty()) {
|
|
386
|
+
self.StopScan(scanId);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
std::vector<std::string> ids;
|
|
390
|
+
for (const auto &entry : self.scans_) {
|
|
391
|
+
ids.push_back(entry.first);
|
|
392
|
+
}
|
|
393
|
+
for (const auto &id : ids) {
|
|
394
|
+
self.StopScan(id);
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
void DnssdBackend::StopScan(const std::string &scanId) {
|
|
400
|
+
auto found = scans_.find(scanId);
|
|
401
|
+
if (found == scans_.end()) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
auto scan = found->second;
|
|
405
|
+
scans_.erase(found);
|
|
406
|
+
|
|
407
|
+
for (auto &entry : scan->resolving) {
|
|
408
|
+
CancelResolve(entry.second);
|
|
409
|
+
}
|
|
410
|
+
scan->resolving.clear();
|
|
411
|
+
scan->foundInterfaces.clear();
|
|
412
|
+
if (scan->browseRef) {
|
|
413
|
+
DNSServiceRefDeallocate(scan->browseRef);
|
|
414
|
+
scan->browseRef = nullptr;
|
|
415
|
+
}
|
|
416
|
+
if (events_.stop) {
|
|
417
|
+
events_.stop(scanId);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
void DnssdBackend::OnBrowse(const std::shared_ptr<DnssdScan> &scan, DNSServiceFlags flags, DNSServiceErrorType error, const std::optional<std::string> &name, const std::string ®type, const std::string &domain) {
|
|
422
|
+
if (scan->browseRef == nullptr) {
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
auto found = scans_.find(scan->scanId);
|
|
426
|
+
if (found == scans_.end() || found->second != scan) {
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (error != kDNSServiceErr_NoError) {
|
|
431
|
+
SendError(scan->scanId, BrowseError(error, scan->typesOnly));
|
|
432
|
+
// The browse can't continue after an error
|
|
433
|
+
DeallocateLater(scan->browseRef, scan);
|
|
434
|
+
StopScan(scan->scanId);
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
if (!name) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
std::string serviceName = *name;
|
|
441
|
+
if (scan->typesOnly) {
|
|
442
|
+
// name is "_http" and regtype "_tcp.local.", the service type is "_http._tcp"
|
|
443
|
+
std::string protocol = regtype.substr(0, regtype.find('.'));
|
|
444
|
+
// Subtypes are announced the same way ("_printer._sub"), only types are listed
|
|
445
|
+
if (protocol != "_tcp" && protocol != "_udp") {
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
serviceName += "." + protocol;
|
|
449
|
+
}
|
|
450
|
+
int interfaces = scan->foundInterfaces.count(serviceName) ? scan->foundInterfaces[serviceName] : 0;
|
|
451
|
+
|
|
452
|
+
if (flags & kDNSServiceFlagsAdd) {
|
|
453
|
+
scan->foundInterfaces[serviceName] = interfaces + 1;
|
|
454
|
+
if (interfaces == 0) {
|
|
455
|
+
if (events_.found) {
|
|
456
|
+
events_.found(scan->scanId, serviceName);
|
|
457
|
+
}
|
|
458
|
+
if (!scan->typesOnly) {
|
|
459
|
+
StartResolve(scan, serviceName, regtype, domain);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (interfaces > 1) {
|
|
466
|
+
scan->foundInterfaces[serviceName] = interfaces - 1;
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
scan->foundInterfaces.erase(serviceName);
|
|
470
|
+
|
|
471
|
+
// Stop any pending resolve for the removed service
|
|
472
|
+
auto resolving = scan->resolving.find(serviceName);
|
|
473
|
+
if (resolving != scan->resolving.end()) {
|
|
474
|
+
CancelResolve(resolving->second);
|
|
475
|
+
scan->resolving.erase(resolving);
|
|
476
|
+
}
|
|
477
|
+
if (events_.remove) {
|
|
478
|
+
events_.remove(scan->scanId, serviceName);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Resolve
|
|
483
|
+
|
|
484
|
+
void DnssdBackend::StartResolve(const std::shared_ptr<DnssdScan> &scan, const std::string &name, const std::string ®type, const std::string &domain) {
|
|
485
|
+
auto resolve = std::make_shared<DnssdResolve>();
|
|
486
|
+
resolve->owner = weak_from_this();
|
|
487
|
+
resolve->self = resolve;
|
|
488
|
+
resolve->scan = scan;
|
|
489
|
+
resolve->name = name;
|
|
490
|
+
resolve->regtype = regtype;
|
|
491
|
+
resolve->domain = domain;
|
|
492
|
+
resolve->interfaceIndex = scan->interfaceIndex;
|
|
493
|
+
resolve->timeoutSeconds = scan->resolveTimeoutSeconds;
|
|
494
|
+
scan->resolving[name] = resolve;
|
|
495
|
+
BeginResolve(resolve);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
void DnssdBackend::BeginResolve(const std::shared_ptr<DnssdResolve> &resolve) {
|
|
499
|
+
resolve->finished = false;
|
|
500
|
+
resolve->addresses.clear();
|
|
501
|
+
|
|
502
|
+
DNSServiceErrorType error = DNSServiceResolve(&resolve->resolveRef, 0, resolve->interfaceIndex, resolve->name.c_str(),
|
|
503
|
+
resolve->regtype.c_str(), resolve->domain.c_str(), ResolveReply, resolve.get());
|
|
504
|
+
if (error != kDNSServiceErr_NoError) {
|
|
505
|
+
resolve->resolveRef = nullptr;
|
|
506
|
+
FailResolve(resolve, DnssdError(error, "Resolving service", resolve->name));
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
executor_->Attach(resolve->resolveRef);
|
|
510
|
+
|
|
511
|
+
std::weak_ptr<DnssdBackend> weakSelf = weak_from_this();
|
|
512
|
+
std::weak_ptr<DnssdResolve> weakResolve = resolve;
|
|
513
|
+
resolve->timeoutTimer = executor_->PostDelayed(resolve->timeoutSeconds, [weakSelf, weakResolve] {
|
|
514
|
+
auto self = weakSelf.lock();
|
|
515
|
+
auto timedOut = weakResolve.lock();
|
|
516
|
+
if (self && timedOut) {
|
|
517
|
+
timedOut->timeoutTimer = 0;
|
|
518
|
+
self->ResolveTimedOut(timedOut);
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
void DnssdBackend::ResolveTimedOut(const std::shared_ptr<DnssdResolve> &resolve) {
|
|
524
|
+
if (resolve->finished || resolve->emitted || !IsActive(resolve)) {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
CancelResolve(resolve);
|
|
528
|
+
|
|
529
|
+
// Slow devices can time out, retry once before reporting the error
|
|
530
|
+
if (!resolve->retried) {
|
|
531
|
+
resolve->retried = true;
|
|
532
|
+
BeginResolve(resolve);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
FailResolve(resolve, LibraryError("TIMEOUT", "Resolving service " + resolve->name + " failed: timed out", resolve->name));
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
void DnssdBackend::OnResolve(const std::shared_ptr<DnssdResolve> &resolve, DNSServiceErrorType error, const std::string &host, uint16_t port, const std::string &txt) {
|
|
539
|
+
if (resolve->finished || resolve->resolveRef == nullptr) {
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
if (error != kDNSServiceErr_NoError) {
|
|
543
|
+
FailResolve(resolve, DnssdError(error, "Resolving service", resolve->name));
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Only the first answer is needed
|
|
548
|
+
DeallocateLater(resolve->resolveRef, resolve);
|
|
549
|
+
|
|
550
|
+
resolve->host = host;
|
|
551
|
+
resolve->port = port;
|
|
552
|
+
resolve->txt = DecodeTxt(txt.data(), static_cast<uint16_t>(txt.size()));
|
|
553
|
+
|
|
554
|
+
DNSServiceErrorType addressError = DNSServiceGetAddrInfo(&resolve->addressRef, 0, resolve->interfaceIndex,
|
|
555
|
+
kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6,
|
|
556
|
+
resolve->host.c_str(), AddressReply, resolve.get());
|
|
557
|
+
if (addressError != kDNSServiceErr_NoError) {
|
|
558
|
+
resolve->addressRef = nullptr;
|
|
559
|
+
FailResolve(resolve, DnssdError(addressError, "Resolving service", resolve->name));
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
executor_->Attach(resolve->addressRef);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
void DnssdBackend::OnAddress(const std::shared_ptr<DnssdResolve> &resolve, DNSServiceFlags flags, DNSServiceErrorType error, const std::string &string) {
|
|
566
|
+
if (resolve->finished || resolve->addressRef == nullptr) {
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
if (error != kDNSServiceErr_NoError) {
|
|
570
|
+
// No record for one of the address families is not a failure
|
|
571
|
+
if (error == kDNSServiceErr_NoSuchRecord) {
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
FailResolve(resolve, DnssdError(error, "Resolving service", resolve->name));
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
auto existing = std::find(resolve->addresses.begin(), resolve->addresses.end(), string);
|
|
579
|
+
if (!string.empty() && (flags & kDNSServiceFlagsAdd)) {
|
|
580
|
+
if (existing == resolve->addresses.end()) {
|
|
581
|
+
resolve->addresses.push_back(string);
|
|
582
|
+
}
|
|
583
|
+
} else if (!string.empty() && existing != resolve->addresses.end()) {
|
|
584
|
+
resolve->addresses.erase(existing);
|
|
585
|
+
}
|
|
586
|
+
if (flags & kDNSServiceFlagsMoreComing) {
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
ScheduleEmit(resolve);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Follows the TXT record of a resolved service
|
|
593
|
+
void DnssdBackend::WatchTxt(const std::shared_ptr<DnssdResolve> &resolve) {
|
|
594
|
+
char fullName[kDNSServiceMaxDomainName];
|
|
595
|
+
if (DNSServiceConstructFullName(fullName, resolve->name.c_str(), resolve->regtype.c_str(), resolve->domain.c_str()) != kDNSServiceErr_NoError) {
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
if (DNSServiceQueryRecord(&resolve->txtRef, 0, resolve->interfaceIndex, fullName, kDNSServiceType_TXT, kDNSServiceClass_IN,
|
|
599
|
+
TxtReply, resolve.get()) != kDNSServiceErr_NoError) {
|
|
600
|
+
resolve->txtRef = nullptr;
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
executor_->Attach(resolve->txtRef);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
void DnssdBackend::OnTxt(const std::shared_ptr<DnssdResolve> &resolve, DNSServiceFlags flags, DNSServiceErrorType error, const std::string &data) {
|
|
607
|
+
if (resolve->finished || resolve->txtRef == nullptr || error != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd)) {
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
resolve->txt = DecodeTxt(data.data(), static_cast<uint16_t>(data.size()));
|
|
611
|
+
ScheduleEmit(resolve);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// IPv4 and IPv6 answers can arrive in separate batches, wait briefly before emitting
|
|
615
|
+
void DnssdBackend::ScheduleEmit(const std::shared_ptr<DnssdResolve> &resolve) {
|
|
616
|
+
if (resolve->emitTimer) {
|
|
617
|
+
executor_->Cancel(resolve->emitTimer);
|
|
618
|
+
}
|
|
619
|
+
std::weak_ptr<DnssdBackend> weakSelf = weak_from_this();
|
|
620
|
+
std::weak_ptr<DnssdResolve> weakResolve = resolve;
|
|
621
|
+
resolve->emitTimer = executor_->PostDelayed(0.25, [weakSelf, weakResolve] {
|
|
622
|
+
auto self = weakSelf.lock();
|
|
623
|
+
auto emitting = weakResolve.lock();
|
|
624
|
+
if (self && emitting) {
|
|
625
|
+
emitting->emitTimer = 0;
|
|
626
|
+
self->EmitResolve(emitting);
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
void DnssdBackend::EmitResolve(const std::shared_ptr<DnssdResolve> &resolve) {
|
|
632
|
+
if (resolve->finished || resolve->addresses.empty() || !IsActive(resolve)) {
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
Service service;
|
|
636
|
+
service.name = resolve->name;
|
|
637
|
+
service.fullName = resolve->host + resolve->regtype + ".";
|
|
638
|
+
service.host = resolve->host;
|
|
639
|
+
service.port = resolve->port;
|
|
640
|
+
service.addresses = resolve->addresses;
|
|
641
|
+
service.txt = resolve->txt;
|
|
642
|
+
|
|
643
|
+
if (resolve->single) {
|
|
644
|
+
ServiceCallback promiseResolve = resolve->promiseResolve;
|
|
645
|
+
EndSingleResolve(resolve);
|
|
646
|
+
if (promiseResolve) {
|
|
647
|
+
promiseResolve(service);
|
|
648
|
+
}
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
if (!resolve->emitted) {
|
|
653
|
+
resolve->emitted = true;
|
|
654
|
+
if (resolve->timeoutTimer) {
|
|
655
|
+
executor_->Cancel(resolve->timeoutTimer);
|
|
656
|
+
resolve->timeoutTimer = 0;
|
|
657
|
+
}
|
|
658
|
+
WatchTxt(resolve);
|
|
659
|
+
}
|
|
660
|
+
// Emitted again only when something changed
|
|
661
|
+
if (resolve->lastEmitted && *resolve->lastEmitted == service) {
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
resolve->lastEmitted = service;
|
|
665
|
+
auto scan = resolve->scan.lock();
|
|
666
|
+
if (scan && events_.resolved) {
|
|
667
|
+
events_.resolved(scan->scanId, service);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
void DnssdBackend::FailResolve(const std::shared_ptr<DnssdResolve> &resolve, const Error &error) {
|
|
672
|
+
if (resolve->single) {
|
|
673
|
+
ErrorCallback promiseReject = resolve->promiseReject;
|
|
674
|
+
EndSingleResolve(resolve);
|
|
675
|
+
if (promiseReject) {
|
|
676
|
+
promiseReject(error);
|
|
677
|
+
}
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
CancelResolve(resolve);
|
|
681
|
+
auto scan = resolve->scan.lock();
|
|
682
|
+
if (!scan) {
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
auto entry = scan->resolving.find(resolve->name);
|
|
686
|
+
if (entry != scan->resolving.end() && entry->second == resolve) {
|
|
687
|
+
scan->resolving.erase(entry);
|
|
688
|
+
}
|
|
689
|
+
SendError(scan->scanId, error);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
bool DnssdBackend::IsActive(const std::shared_ptr<DnssdResolve> &resolve) const {
|
|
693
|
+
if (resolve->single) {
|
|
694
|
+
return singleResolves_.count(resolve) > 0;
|
|
695
|
+
}
|
|
696
|
+
auto scan = resolve->scan.lock();
|
|
697
|
+
if (!scan) {
|
|
698
|
+
return false;
|
|
699
|
+
}
|
|
700
|
+
auto entry = scan->resolving.find(resolve->name);
|
|
701
|
+
return entry != scan->resolving.end() && entry->second == resolve;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
void DnssdBackend::EndSingleResolve(const std::shared_ptr<DnssdResolve> &resolve) {
|
|
705
|
+
CancelResolve(resolve);
|
|
706
|
+
resolve->promiseResolve = nullptr;
|
|
707
|
+
resolve->promiseReject = nullptr;
|
|
708
|
+
singleResolves_.erase(resolve);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// Stops everything in flight for a resolve, safe to call from its own callbacks
|
|
712
|
+
void DnssdBackend::CancelResolve(const std::shared_ptr<DnssdResolve> &resolve) {
|
|
713
|
+
resolve->finished = true;
|
|
714
|
+
if (resolve->timeoutTimer) {
|
|
715
|
+
executor_->Cancel(resolve->timeoutTimer);
|
|
716
|
+
resolve->timeoutTimer = 0;
|
|
717
|
+
}
|
|
718
|
+
if (resolve->emitTimer) {
|
|
719
|
+
executor_->Cancel(resolve->emitTimer);
|
|
720
|
+
resolve->emitTimer = 0;
|
|
721
|
+
}
|
|
722
|
+
DeallocateLater(resolve->resolveRef, resolve);
|
|
723
|
+
DeallocateLater(resolve->addressRef, resolve);
|
|
724
|
+
DeallocateLater(resolve->txtRef, resolve);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
void DnssdBackend::ResolveService(
|
|
728
|
+
const std::string &name,
|
|
729
|
+
const std::string &type,
|
|
730
|
+
const std::string &protocol,
|
|
731
|
+
const std::string &domain,
|
|
732
|
+
const ResolveOptions &options,
|
|
733
|
+
ServiceCallback resolve,
|
|
734
|
+
ErrorCallback reject) {
|
|
735
|
+
Run([=](DnssdBackend &self) {
|
|
736
|
+
auto single = std::make_shared<DnssdResolve>();
|
|
737
|
+
if (!InterfaceIndex(options.networkInterface, single->interfaceIndex)) {
|
|
738
|
+
reject(UnknownInterface(options.networkInterface));
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
single->owner = self.weak_from_this();
|
|
742
|
+
single->self = single;
|
|
743
|
+
single->single = true;
|
|
744
|
+
single->name = name;
|
|
745
|
+
single->regtype = "_" + type + "._" + protocol;
|
|
746
|
+
single->domain = domain.empty() ? "local." : domain;
|
|
747
|
+
single->timeoutSeconds = options.timeoutSeconds > 0 ? options.timeoutSeconds : 5;
|
|
748
|
+
single->promiseResolve = resolve;
|
|
749
|
+
single->promiseReject = reject;
|
|
750
|
+
// The timeout is the caller's, no retry as for scans
|
|
751
|
+
single->retried = true;
|
|
752
|
+
self.singleResolves_.insert(single);
|
|
753
|
+
self.BeginResolve(single);
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// Publish
|
|
758
|
+
|
|
759
|
+
void DnssdBackend::Publish(
|
|
760
|
+
const std::string &type,
|
|
761
|
+
const std::string &protocol,
|
|
762
|
+
const std::string &domain,
|
|
763
|
+
const std::string &requestedName,
|
|
764
|
+
uint16_t port,
|
|
765
|
+
const TxtPairs &txt,
|
|
766
|
+
const PublishOptions &options,
|
|
767
|
+
ServiceCallback resolve,
|
|
768
|
+
ErrorCallback reject) {
|
|
769
|
+
Run([=](DnssdBackend &self) {
|
|
770
|
+
uint32_t interfaceIndex = kDNSServiceInterfaceIndexAny;
|
|
771
|
+
if (!InterfaceIndex(options.networkInterface, interfaceIndex)) {
|
|
772
|
+
Error error = UnknownInterface(options.networkInterface);
|
|
773
|
+
self.SendError("", error);
|
|
774
|
+
reject(error);
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
std::vector<std::string> tooLong;
|
|
779
|
+
std::string record = EncodeTxt(txt, &tooLong);
|
|
780
|
+
for (const auto &key : tooLong) {
|
|
781
|
+
self.SendError("", LibraryError("TXT_ENTRY_TOO_LONG", "TXT record entry " + key + " is longer than 255 bytes", requestedName));
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// dns_sd lets the same app register a name twice, rename like Bonjour does on conflicts
|
|
785
|
+
std::string name = requestedName;
|
|
786
|
+
for (int suffix = 2; self.IsNameInUse(name); suffix++) {
|
|
787
|
+
name = requestedName + " (" + std::to_string(suffix) + ")";
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
auto publication = std::make_shared<DnssdPublication>();
|
|
791
|
+
publication->owner = self.weak_from_this();
|
|
792
|
+
publication->self = publication;
|
|
793
|
+
publication->name = name;
|
|
794
|
+
publication->regtype = "_" + type + "._" + protocol;
|
|
795
|
+
publication->domain = domain.empty() ? "local." : domain;
|
|
796
|
+
publication->port = port;
|
|
797
|
+
publication->txt = DecodeTxt(record.data(), static_cast<uint16_t>(record.size()));
|
|
798
|
+
publication->resolve = resolve;
|
|
799
|
+
publication->reject = reject;
|
|
800
|
+
|
|
801
|
+
// "_ipp._tcp,_printer,_color" registers the subtypes too
|
|
802
|
+
std::string registerType = publication->regtype;
|
|
803
|
+
for (const auto &subtype : options.subtypes) {
|
|
804
|
+
std::string label = SubtypeLabel(subtype);
|
|
805
|
+
if (!label.empty()) {
|
|
806
|
+
registerType += "," + label;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
DNSServiceErrorType error = DNSServiceRegister(&publication->ref, 0, interfaceIndex, name.c_str(), registerType.c_str(),
|
|
811
|
+
domain.empty() ? nullptr : domain.c_str(), nullptr, htons(port),
|
|
812
|
+
static_cast<uint16_t>(record.size()), record.empty() ? nullptr : record.data(),
|
|
813
|
+
RegisterReply, publication.get());
|
|
814
|
+
if (error != kDNSServiceErr_NoError) {
|
|
815
|
+
publication->ref = nullptr;
|
|
816
|
+
Error errorInfo = DnssdError(error, "Publishing service", name);
|
|
817
|
+
self.SendError("", errorInfo);
|
|
818
|
+
reject(errorInfo);
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
self.executor_->Attach(publication->ref);
|
|
822
|
+
self.pending_.insert(publication);
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
void DnssdBackend::OnRegister(const std::shared_ptr<DnssdPublication> &publication, DNSServiceFlags flags, DNSServiceErrorType error, const std::optional<std::string> &name) {
|
|
827
|
+
if (publication->ref == nullptr) {
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
if (error != kDNSServiceErr_NoError) {
|
|
831
|
+
Error errorInfo = DnssdError(error, "Publishing service", publication->name);
|
|
832
|
+
SendError("", errorInfo);
|
|
833
|
+
pending_.erase(publication);
|
|
834
|
+
auto published = published_.find(publication->name);
|
|
835
|
+
if (published != published_.end() && published->second == publication) {
|
|
836
|
+
published_.erase(published);
|
|
837
|
+
}
|
|
838
|
+
DeallocateLater(publication->ref, publication);
|
|
839
|
+
if (publication->reject) {
|
|
840
|
+
publication->reject(errorInfo);
|
|
841
|
+
}
|
|
842
|
+
publication->resolve = nullptr;
|
|
843
|
+
publication->reject = nullptr;
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
if (!(flags & kDNSServiceFlagsAdd)) {
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// The name can differ from the requested one when it was already taken
|
|
851
|
+
std::string registeredName = name ? *name : publication->name;
|
|
852
|
+
auto previous = published_.find(publication->name);
|
|
853
|
+
if (previous != published_.end() && previous->second == publication) {
|
|
854
|
+
published_.erase(previous);
|
|
855
|
+
}
|
|
856
|
+
publication->name = registeredName;
|
|
857
|
+
published_[registeredName] = publication;
|
|
858
|
+
pending_.erase(publication);
|
|
859
|
+
|
|
860
|
+
Service service = PublishedService(*publication);
|
|
861
|
+
if (events_.published) {
|
|
862
|
+
events_.published(service);
|
|
863
|
+
}
|
|
864
|
+
if (publication->resolve) {
|
|
865
|
+
publication->resolve(service);
|
|
866
|
+
}
|
|
867
|
+
publication->resolve = nullptr;
|
|
868
|
+
publication->reject = nullptr;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
void DnssdBackend::Unpublish(const std::string &name, ServiceCallback resolve, ErrorCallback reject) {
|
|
872
|
+
Run([=](DnssdBackend &self) {
|
|
873
|
+
auto found = self.published_.find(name);
|
|
874
|
+
if (found == self.published_.end()) {
|
|
875
|
+
reject(NotPublished(name));
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
auto publication = found->second;
|
|
879
|
+
self.published_.erase(found);
|
|
880
|
+
// Deallocating the ref unregisters the service
|
|
881
|
+
DNSServiceRefDeallocate(publication->ref);
|
|
882
|
+
publication->ref = nullptr;
|
|
883
|
+
|
|
884
|
+
Service service = self.PublishedService(*publication);
|
|
885
|
+
if (self.events_.unpublished) {
|
|
886
|
+
self.events_.unpublished(service);
|
|
887
|
+
}
|
|
888
|
+
resolve(service);
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// Replaces the TXT record of a published service
|
|
893
|
+
void DnssdBackend::Update(const std::string &name, const TxtPairs &txt, ServiceCallback resolve, ErrorCallback reject) {
|
|
894
|
+
Run([=](DnssdBackend &self) {
|
|
895
|
+
auto found = self.published_.find(name);
|
|
896
|
+
if (found == self.published_.end()) {
|
|
897
|
+
reject(NotPublished(name));
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
auto publication = found->second;
|
|
901
|
+
|
|
902
|
+
std::vector<std::string> tooLong;
|
|
903
|
+
std::string record = EncodeTxt(txt, &tooLong);
|
|
904
|
+
for (const auto &key : tooLong) {
|
|
905
|
+
self.SendError("", LibraryError("TXT_ENTRY_TOO_LONG", "TXT record entry " + key + " is longer than 255 bytes", name));
|
|
906
|
+
}
|
|
907
|
+
// An empty TXT record is a single empty string
|
|
908
|
+
static const char emptyTxt = 0;
|
|
909
|
+
DNSServiceErrorType error = DNSServiceUpdateRecord(publication->ref, nullptr, 0,
|
|
910
|
+
record.empty() ? 1 : static_cast<uint16_t>(record.size()),
|
|
911
|
+
record.empty() ? &emptyTxt : record.data(), 0);
|
|
912
|
+
if (error != kDNSServiceErr_NoError) {
|
|
913
|
+
reject(DnssdError(error, "Updating service", name));
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
publication->txt = DecodeTxt(record.data(), static_cast<uint16_t>(record.size()));
|
|
917
|
+
resolve(self.PublishedService(*publication));
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
bool DnssdBackend::IsNameInUse(const std::string &name) const {
|
|
922
|
+
if (published_.count(name)) {
|
|
923
|
+
return true;
|
|
924
|
+
}
|
|
925
|
+
for (const auto &publication : pending_) {
|
|
926
|
+
if (publication->name == name) {
|
|
927
|
+
return true;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
return false;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
Service DnssdBackend::PublishedService(const DnssdPublication &publication) const {
|
|
934
|
+
Service service;
|
|
935
|
+
service.name = publication.name;
|
|
936
|
+
service.fullName = publication.name + "." + publication.regtype + "." + publication.domain;
|
|
937
|
+
service.port = publication.port;
|
|
938
|
+
service.txt = publication.txt;
|
|
939
|
+
return service;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// Teardown
|
|
943
|
+
|
|
944
|
+
void DnssdBackend::Shutdown() {
|
|
945
|
+
// Keeps the backend alive until the cleanup ran
|
|
946
|
+
auto self = shared_from_this();
|
|
947
|
+
executor_->Post([self] {
|
|
948
|
+
std::vector<std::string> ids;
|
|
949
|
+
for (const auto &entry : self->scans_) {
|
|
950
|
+
ids.push_back(entry.first);
|
|
951
|
+
}
|
|
952
|
+
for (const auto &id : ids) {
|
|
953
|
+
self->StopScan(id);
|
|
954
|
+
}
|
|
955
|
+
for (auto &entry : self->published_) {
|
|
956
|
+
if (entry.second->ref) {
|
|
957
|
+
DNSServiceRefDeallocate(entry.second->ref);
|
|
958
|
+
entry.second->ref = nullptr;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
self->published_.clear();
|
|
962
|
+
for (const auto &publication : self->pending_) {
|
|
963
|
+
if (publication->ref) {
|
|
964
|
+
DNSServiceRefDeallocate(publication->ref);
|
|
965
|
+
publication->ref = nullptr;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
self->pending_.clear();
|
|
969
|
+
for (const auto &single : self->singleResolves_) {
|
|
970
|
+
self->CancelResolve(single);
|
|
971
|
+
}
|
|
972
|
+
self->singleResolves_.clear();
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
} // namespace rnzeroconf
|