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
@@ -3,6 +3,7 @@ package com.balthazargronon.RCTZeroconf.nsd;
3
3
  import android.annotation.TargetApi;
4
4
  import android.net.nsd.NsdManager;
5
5
  import android.net.nsd.NsdServiceInfo;
6
+ import android.os.Handler;
6
7
  import android.util.Log;
7
8
 
8
9
  import java.util.ArrayList;
@@ -13,6 +14,7 @@ import java.util.concurrent.Executor;
13
14
  /**
14
15
  * Follows found services with NsdManager.registerServiceInfoCallback (Android 14+) instead of one-shot resolves:
15
16
  * services can be followed concurrently, updates (addresses, TXT records) are delivered as they change.
17
+ * A service without addresses after the resolve timeout is followed again once, then reported with onTimeout.
16
18
  * Only loaded on API 34+, so older devices never touch these APIs.
17
19
  */
18
20
  @TargetApi(34)
@@ -23,34 +25,55 @@ class ServiceInfoCallbacks {
23
25
  void onServiceUpdated(NsdServiceInfo serviceInfo);
24
26
 
25
27
  void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode);
28
+
29
+ void onTimeout(NsdServiceInfo serviceInfo);
30
+ }
31
+
32
+ private static class Followed {
33
+ NsdManager.ServiceInfoCallback callback;
34
+ Runnable timeout;
35
+ volatile boolean resolved;
26
36
  }
27
37
 
28
38
  private final NsdManager nsdManager;
29
39
  private final Executor executor;
40
+ private final Handler handler;
41
+ private final long timeoutMs;
30
42
  private final Listener listener;
31
- private final Map<String, NsdManager.ServiceInfoCallback> callbacks = new ConcurrentHashMap<>();
43
+ private final Map<String, Followed> followed = new ConcurrentHashMap<>();
32
44
 
33
- ServiceInfoCallbacks(NsdManager nsdManager, Executor executor, Listener listener) {
45
+ ServiceInfoCallbacks(NsdManager nsdManager, Executor executor, Handler handler, long timeoutMs, Listener listener) {
34
46
  this.nsdManager = nsdManager;
35
47
  this.executor = executor;
48
+ this.handler = handler;
49
+ this.timeoutMs = timeoutMs;
36
50
  this.listener = listener;
37
51
  }
38
52
 
39
53
  void register(final NsdServiceInfo serviceInfo) {
40
- final String name = serviceInfo.getServiceName();
41
- if (callbacks.containsKey(name)) {
54
+ if (followed.containsKey(serviceInfo.getServiceName())) {
42
55
  return;
43
56
  }
57
+ follow(serviceInfo, false);
58
+ }
44
59
 
45
- NsdManager.ServiceInfoCallback callback = new NsdManager.ServiceInfoCallback() {
60
+ private void follow(final NsdServiceInfo serviceInfo, final boolean retried) {
61
+ final String name = serviceInfo.getServiceName();
62
+ final Followed entry = new Followed();
63
+ entry.callback = new NsdManager.ServiceInfoCallback() {
46
64
  @Override
47
65
  public void onServiceInfoCallbackRegistrationFailed(int errorCode) {
48
- callbacks.remove(name);
49
- listener.onRegistrationFailed(serviceInfo, errorCode);
66
+ if (followed.remove(name, entry)) {
67
+ handler.removeCallbacks(entry.timeout);
68
+ listener.onRegistrationFailed(serviceInfo, errorCode);
69
+ }
50
70
  }
51
71
 
52
72
  @Override
53
73
  public void onServiceUpdated(NsdServiceInfo updated) {
74
+ if (!updated.getHostAddresses().isEmpty()) {
75
+ entry.resolved = true;
76
+ }
54
77
  listener.onServiceUpdated(updated);
55
78
  }
56
79
 
@@ -63,18 +86,32 @@ class ServiceInfoCallbacks {
63
86
  public void onServiceInfoCallbackUnregistered() {
64
87
  }
65
88
  };
66
- callbacks.put(name, callback);
89
+ entry.timeout = () -> {
90
+ if (followed.get(name) != entry || entry.resolved) {
91
+ return;
92
+ }
93
+ unregister(name);
94
+ // Slow devices can time out, follow again once before reporting the error
95
+ if (!retried) {
96
+ follow(serviceInfo, true);
97
+ } else {
98
+ listener.onTimeout(serviceInfo);
99
+ }
100
+ };
101
+ followed.put(name, entry);
67
102
  Log.d(TAG, "Following " + name + " with registerServiceInfoCallback");
68
- nsdManager.registerServiceInfoCallback(serviceInfo, executor, callback);
103
+ nsdManager.registerServiceInfoCallback(serviceInfo, executor, entry.callback);
104
+ handler.postDelayed(entry.timeout, timeoutMs);
69
105
  }
70
106
 
71
107
  void unregister(String name) {
72
- NsdManager.ServiceInfoCallback callback = callbacks.remove(name);
73
- if (callback == null) {
108
+ Followed entry = followed.remove(name);
109
+ if (entry == null) {
74
110
  return;
75
111
  }
112
+ handler.removeCallbacks(entry.timeout);
76
113
  try {
77
- nsdManager.unregisterServiceInfoCallback(callback);
114
+ nsdManager.unregisterServiceInfoCallback(entry.callback);
78
115
  } catch (IllegalArgumentException e) {
79
116
  // Registration failed or was already unregistered
80
117
  Log.w(TAG, "unregisterServiceInfoCallback failed", e);
@@ -82,7 +119,7 @@ class ServiceInfoCallbacks {
82
119
  }
83
120
 
84
121
  void unregisterAll() {
85
- for (String name : new ArrayList<>(callbacks.keySet())) {
122
+ for (String name : new ArrayList<>(followed.keySet())) {
86
123
  unregister(name);
87
124
  }
88
125
  }
@@ -0,0 +1,296 @@
1
+ package com.balthazargronon.RCTZeroconf.nsd;
2
+
3
+ import android.text.TextUtils;
4
+ import android.util.Log;
5
+
6
+ import java.io.ByteArrayOutputStream;
7
+ import java.io.IOException;
8
+ import java.net.DatagramPacket;
9
+ import java.net.Inet4Address;
10
+ import java.net.InetAddress;
11
+ import java.net.InterfaceAddress;
12
+ import java.net.MulticastSocket;
13
+ import java.net.NetworkInterface;
14
+ import java.net.SocketException;
15
+ import java.net.SocketTimeoutException;
16
+ import java.util.ArrayList;
17
+ import java.util.Collections;
18
+ import java.util.HashMap;
19
+ import java.util.List;
20
+ import java.util.Locale;
21
+ import java.util.Map;
22
+ import java.util.Random;
23
+
24
+ import javax.annotation.Nullable;
25
+
26
+ /**
27
+ * Lists the service types on the network by querying the PTR records of _services._dns-sd._udp.local.
28
+ * NsdManager can't: on Android 14 and later it reads that name as a subtype of _dns-sd._udp.
29
+ *
30
+ * Queries are sent from an ephemeral port (RFC 6762 legacy unicast), so responders answer directly to
31
+ * this socket and the system's mDNS port 5353 is left alone. Types are queried again periodically, and
32
+ * reported lost when they stop being answered.
33
+ */
34
+ class ServiceTypesQuery {
35
+ private static final String TAG = "ServiceTypesQuery";
36
+ private static final String QUERY_NAME = "_services._dns-sd._udp.local";
37
+ private static final int TYPE_PTR = 12;
38
+ private static final int CLASS_IN = 1;
39
+ private static final int MDNS_PORT = 5353;
40
+ // Query again after 1, 2 and 4 seconds, then every 10 seconds
41
+ private static final long[] QUERY_DELAYS_MS = { 1000, 1000, 2000 };
42
+ private static final long QUERY_INTERVAL_MS = 10000;
43
+ // Lost after three unanswered queries
44
+ private static final long EXPIRY_MS = 3 * QUERY_INTERVAL_MS + 5000;
45
+
46
+ interface Listener {
47
+ void onTypeFound(String type);
48
+
49
+ void onTypeLost(String type);
50
+
51
+ void onError(String message);
52
+ }
53
+
54
+ private final Listener listener;
55
+ private final Map<String, Long> lastSeen = new HashMap<>();
56
+ private final int queryId = 1 + new Random().nextInt(0xfffe);
57
+ private volatile boolean running;
58
+ @Nullable private MulticastSocket socket;
59
+ @Nullable private Thread thread;
60
+
61
+ // Network interface name ("wlan0"), or null for the Wi-Fi / Ethernet one
62
+ @Nullable private final String interfaceName;
63
+
64
+ ServiceTypesQuery(@Nullable String interfaceName, Listener listener) {
65
+ this.interfaceName = interfaceName;
66
+ this.listener = listener;
67
+ }
68
+
69
+ void start() {
70
+ running = true;
71
+ thread = new Thread(this::run, "ServiceTypesQuery");
72
+ thread.start();
73
+ }
74
+
75
+ void stop() {
76
+ running = false;
77
+ MulticastSocket current = socket;
78
+ if (current != null) {
79
+ current.close();
80
+ }
81
+ if (thread != null) {
82
+ thread.interrupt();
83
+ }
84
+ }
85
+
86
+ private void run() {
87
+ try {
88
+ NetworkInterface networkInterface = interfaceName != null ? NetworkInterface.getByName(interfaceName) : findInterface();
89
+ if (networkInterface == null) {
90
+ listener.onError(interfaceName != null ? "unknown network interface " + interfaceName : "no network interface with IPv4 multicast");
91
+ return;
92
+ }
93
+ InetAddress group = InetAddress.getByName("224.0.0.251");
94
+ MulticastSocket multicastSocket = new MulticastSocket(0);
95
+ multicastSocket.setNetworkInterface(networkInterface);
96
+ multicastSocket.setTimeToLive(255);
97
+ multicastSocket.setSoTimeout(500);
98
+ socket = multicastSocket;
99
+
100
+ byte[] query = buildQuery();
101
+ byte[] buffer = new byte[9000];
102
+ int queries = 0;
103
+ long nextQuery = 0;
104
+ while (running) {
105
+ long now = System.currentTimeMillis();
106
+ if (now >= nextQuery) {
107
+ multicastSocket.send(new DatagramPacket(query, query.length, group, MDNS_PORT));
108
+ nextQuery = now + (queries < QUERY_DELAYS_MS.length ? QUERY_DELAYS_MS[queries] : QUERY_INTERVAL_MS);
109
+ queries++;
110
+ expire(now);
111
+ }
112
+ DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
113
+ try {
114
+ multicastSocket.receive(packet);
115
+ } catch (SocketTimeoutException e) {
116
+ continue;
117
+ }
118
+ for (Map.Entry<String, Long> answer : parseAnswers(packet.getData(), packet.getLength()).entrySet()) {
119
+ onAnswer(answer.getKey(), answer.getValue(), System.currentTimeMillis());
120
+ }
121
+ }
122
+ } catch (IOException e) {
123
+ if (running) {
124
+ Log.e(TAG, "Service types query failed", e);
125
+ listener.onError(e.getMessage());
126
+ }
127
+ } finally {
128
+ MulticastSocket current = socket;
129
+ if (current != null) {
130
+ current.close();
131
+ }
132
+ }
133
+ }
134
+
135
+ private void onAnswer(String type, long ttlSeconds, long now) {
136
+ if (ttlSeconds == 0) {
137
+ // Goodbye record
138
+ if (lastSeen.remove(type) != null) {
139
+ listener.onTypeLost(type);
140
+ }
141
+ return;
142
+ }
143
+ if (lastSeen.put(type, now) == null) {
144
+ listener.onTypeFound(type);
145
+ }
146
+ }
147
+
148
+ private void expire(long now) {
149
+ for (String type : new ArrayList<>(lastSeen.keySet())) {
150
+ if (now - lastSeen.get(type) > EXPIRY_MS) {
151
+ lastSeen.remove(type);
152
+ listener.onTypeLost(type);
153
+ }
154
+ }
155
+ }
156
+
157
+ // The Wi-Fi (or Ethernet) interface: up, multicast, not loopback, with an IPv4 address
158
+ @Nullable
159
+ private static NetworkInterface findInterface() throws SocketException {
160
+ NetworkInterface fallback = null;
161
+ for (NetworkInterface candidate : Collections.list(NetworkInterface.getNetworkInterfaces())) {
162
+ if (!candidate.isUp() || candidate.isLoopback() || !candidate.supportsMulticast() || candidate.isPointToPoint()) {
163
+ continue;
164
+ }
165
+ boolean hasIPv4 = false;
166
+ for (InterfaceAddress address : candidate.getInterfaceAddresses()) {
167
+ hasIPv4 |= address.getAddress() instanceof Inet4Address;
168
+ }
169
+ if (!hasIPv4) {
170
+ continue;
171
+ }
172
+ if (candidate.getName().startsWith("wlan") || candidate.getName().startsWith("eth")) {
173
+ return candidate;
174
+ }
175
+ if (fallback == null) {
176
+ fallback = candidate;
177
+ }
178
+ }
179
+ return fallback;
180
+ }
181
+
182
+ private byte[] buildQuery() {
183
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
184
+ // Header: id, flags, 1 question, no records. Legacy unicast queries use a non-zero id
185
+ writeShort(out, queryId);
186
+ writeShort(out, 0);
187
+ writeShort(out, 1);
188
+ writeShort(out, 0);
189
+ writeShort(out, 0);
190
+ writeShort(out, 0);
191
+ for (String label : QUERY_NAME.split("\\.")) {
192
+ out.write(label.length());
193
+ out.write(label.getBytes(), 0, label.length());
194
+ }
195
+ out.write(0);
196
+ writeShort(out, TYPE_PTR);
197
+ writeShort(out, CLASS_IN);
198
+ return out.toByteArray();
199
+ }
200
+
201
+ private static void writeShort(ByteArrayOutputStream out, int value) {
202
+ out.write((value >> 8) & 0xff);
203
+ out.write(value & 0xff);
204
+ }
205
+
206
+ /**
207
+ * Service types ("_http._tcp") and their TTL from the PTR records of _services._dns-sd._udp.local in a response
208
+ */
209
+ static Map<String, Long> parseAnswers(byte[] data, int length) {
210
+ Map<String, Long> types = new HashMap<>();
211
+ try {
212
+ if (length < 12 || (data[2] & 0x80) == 0) {
213
+ // Not a response
214
+ return types;
215
+ }
216
+ int questions = readShort(data, 4);
217
+ int records = readShort(data, 6) + readShort(data, 8) + readShort(data, 10);
218
+ int offset = 12;
219
+ for (int i = 0; i < questions; i++) {
220
+ offset = skipName(data, offset, length) + 4;
221
+ }
222
+ for (int i = 0; i < records && offset < length; i++) {
223
+ List<String> name = new ArrayList<>();
224
+ offset = readName(data, offset, length, name);
225
+ int type = readShort(data, offset);
226
+ long ttl = ((long) readShort(data, offset + 4) << 16) | readShort(data, offset + 6);
227
+ int dataLength = readShort(data, offset + 8);
228
+ int dataOffset = offset + 10;
229
+ offset = dataOffset + dataLength;
230
+ if (type != TYPE_PTR || !QUERY_NAME.equals(TextUtils.join(".", name).toLowerCase(Locale.ROOT))) {
231
+ continue;
232
+ }
233
+ List<String> target = new ArrayList<>();
234
+ readName(data, dataOffset, length, target);
235
+ if (target.size() >= 2 && target.get(0).startsWith("_")
236
+ && (target.get(1).equalsIgnoreCase("_tcp") || target.get(1).equalsIgnoreCase("_udp"))) {
237
+ types.put(target.get(0) + "." + target.get(1).toLowerCase(Locale.ROOT), ttl);
238
+ }
239
+ }
240
+ } catch (IndexOutOfBoundsException e) {
241
+ // Truncated or malformed packet, keep what was parsed
242
+ }
243
+ return types;
244
+ }
245
+
246
+ private static int readShort(byte[] data, int offset) {
247
+ if (offset + 1 >= data.length) {
248
+ throw new IndexOutOfBoundsException();
249
+ }
250
+ return ((data[offset] & 0xff) << 8) | (data[offset + 1] & 0xff);
251
+ }
252
+
253
+ private static int skipName(byte[] data, int offset, int length) {
254
+ while (offset < length) {
255
+ int labelLength = data[offset] & 0xff;
256
+ if (labelLength == 0) {
257
+ return offset + 1;
258
+ }
259
+ if ((labelLength & 0xc0) == 0xc0) {
260
+ return offset + 2;
261
+ }
262
+ offset += 1 + labelLength;
263
+ }
264
+ throw new IndexOutOfBoundsException();
265
+ }
266
+
267
+ // Reads a name with compression pointers into labels, returns the offset after the name
268
+ private static int readName(byte[] data, int offset, int length, List<String> labels) {
269
+ int end = -1;
270
+ int jumps = 0;
271
+ while (true) {
272
+ if (offset >= length) {
273
+ throw new IndexOutOfBoundsException();
274
+ }
275
+ int labelLength = data[offset] & 0xff;
276
+ if (labelLength == 0) {
277
+ return end >= 0 ? end : offset + 1;
278
+ }
279
+ if ((labelLength & 0xc0) == 0xc0) {
280
+ if (end < 0) {
281
+ end = offset + 2;
282
+ }
283
+ if (++jumps > 20) {
284
+ throw new IndexOutOfBoundsException();
285
+ }
286
+ offset = ((labelLength & 0x3f) << 8) | (data[offset + 1] & 0xff);
287
+ continue;
288
+ }
289
+ if (offset + 1 + labelLength > length) {
290
+ throw new IndexOutOfBoundsException();
291
+ }
292
+ labels.add(new String(data, offset + 1, labelLength, java.nio.charset.StandardCharsets.UTF_8));
293
+ offset += 1 + labelLength;
294
+ }
295
+ }
296
+ }
@@ -22,7 +22,7 @@ import android.os.Looper;
22
22
  import android.util.Log;
23
23
 
24
24
  /**
25
- * RxDnssd is implementation of RxDnssd with embedded DNS-SD {@link InternalDNSSD}
25
+ * DNSSD implementation running the embedded mDNSResponder {@link InternalDNSSD}
26
26
  */
27
27
  public class DNSSDEmbedded extends DNSSD {
28
28
 
@@ -27,6 +27,11 @@
27
27
  #include "mDNSEmbeddedAPI.h" // The interface we're building on top of
28
28
  #include <sys/socket.h>
29
29
  #include <netinet/in.h>
30
+ #include <string.h>
31
+
32
+ // react-native-zeroconf: subtype parsing from uds_daemon.c, built into the same library
33
+ extern mDNSs32 ChopSubTypes(char *regtype);
34
+ extern AuthRecord *AllocateSubTypes(mDNSs32 NumSubTypes, char *p);
30
35
 
31
36
  extern mDNS mDNSStorage; // We need to pass the address of this storage to the lower-layer functions
32
37
 
@@ -242,16 +247,20 @@ DNSServiceErrorType DNSServiceRegister
242
247
  mDNSIPPort port;
243
248
  unsigned int size = sizeof(RDataBody);
244
249
  AuthRecord *SubTypes = mDNSNULL;
245
- mDNSu32 NumSubTypes = 0;
250
+ mDNSs32 NumSubTypes = 0;
251
+ // react-native-zeroconf: "_type._tcp,_subtype1,_subtype2" registers subtypes, as with the daemon
252
+ char typeAndSubtypes[MAX_ESCAPED_DOMAIN_NAME];
246
253
  mDNS_DirectOP_Register *x;
247
254
  (void)flags; // Unused
248
- (void)interfaceIndex; // Unused
249
255
 
250
256
  // Check parameters
251
257
  if (!name) name = "";
252
258
  if (!name[0]) n = mDNSStorage.nicelabel;
253
259
  else if (!MakeDomainLabelFromLiteralString(&n, name)) { errormsg = "Bad Instance Name"; goto badparam; }
254
- if (!regtype || !*regtype || !MakeDomainNameFromDNSNameString(&t, regtype)) { errormsg = "Bad Service Type"; goto badparam; }
260
+ if (!regtype || !*regtype || strlen(regtype) >= sizeof(typeAndSubtypes)) { errormsg = "Bad Service Type"; goto badparam; }
261
+ strcpy(typeAndSubtypes, regtype);
262
+ NumSubTypes = ChopSubTypes(typeAndSubtypes); // Cuts the subtypes off the type
263
+ if (NumSubTypes < 0 || !MakeDomainNameFromDNSNameString(&t, typeAndSubtypes)) { errormsg = "Bad Service Type"; goto badparam; }
255
264
  if (!MakeDomainNameFromDNSNameString(&d, (domain && *domain) ? domain : "local.")) { errormsg = "Bad Domain"; goto badparam; }
256
265
  if (!MakeDomainNameFromDNSNameString(&h, (host && *host ) ? host : "")) { errormsg = "Bad Target Host"; goto badparam; }
257
266
  if (!ConstructServiceName(&srv, &n, &t, &d)) { errormsg = "Bad Name"; goto badparam; }
@@ -271,16 +280,22 @@ DNSServiceErrorType DNSServiceRegister
271
280
  x->autorename = !(flags & kDNSServiceFlagsNoAutoRename);
272
281
  x->name = n;
273
282
  x->host = h;
283
+ if (NumSubTypes > 0)
284
+ {
285
+ SubTypes = AllocateSubTypes(NumSubTypes, typeAndSubtypes);
286
+ if (!SubTypes) { mDNSPlatformMemFree(x); err = mStatus_BadParamErr; errormsg = "Bad Subtype"; goto fail; }
287
+ }
274
288
 
275
289
  // Do the operation
290
+ // react-native-zeroconf: registers on the requested interface instead of always any
276
291
  err = mDNS_RegisterService(&mDNSStorage, &x->s,
277
292
  &x->name, &t, &d, // Name, type, domain
278
293
  &x->host, port, // Host and port
279
294
  txtRecord, txtLen, // TXT data, length
280
- SubTypes, NumSubTypes, // Subtypes
281
- mDNSInterface_Any, // Interface ID
295
+ SubTypes, (mDNSu32)NumSubTypes, // Subtypes
296
+ mDNSPlatformInterfaceIDfromInterfaceIndex(&mDNSStorage, interfaceIndex), // Interface ID
282
297
  RegCallback, x, 0); // Callback, context, flags
283
- if (err) { mDNSPlatformMemFree(x); errormsg = "mDNS_RegisterService"; goto fail; }
298
+ if (err) { if (SubTypes) mDNSPlatformMemFree(SubTypes); mDNSPlatformMemFree(x); errormsg = "mDNS_RegisterService"; goto fail; }
284
299
 
285
300
  // Succeeded: Wrap up and return
286
301
  *sdRef = (DNSServiceRef)x;
@@ -301,6 +316,14 @@ fail:
301
316
  // is run against this Extension, it will get a reasonable error code instead of just
302
317
  // failing to launch (Strong Link) or calling an unresolved symbol and crashing (Weak Link)
303
318
  #if !MDNS_BUILDINGSTUBLIBRARY
319
+ // react-native-zeroconf: frees the TXT data replaced by DNSServiceUpdateRecord
320
+ static void UpdateCallback(mDNS *const m, AuthRecord *const rr, RData *oldrd, mDNSu16 oldrdlen)
321
+ {
322
+ (void)m; // Unused
323
+ (void)oldrdlen; // Unused
324
+ if (oldrd != &rr->rdatastorage) mDNSPlatformMemFree(oldrd);
325
+ }
326
+
304
327
  DNSServiceErrorType DNSServiceAddRecord
305
328
  (
306
329
  DNSServiceRef sdRef,
@@ -332,13 +355,23 @@ DNSServiceErrorType DNSServiceUpdateRecord
332
355
  uint32_t ttl
333
356
  )
334
357
  {
335
- (void)sdRef; // Unused
336
- (void)RecordRef; // Unused
358
+ // react-native-zeroconf: updates the primary TXT record of a registration, as uds_daemon.c update_record does
359
+ mDNS_DirectOP_Register *x = (mDNS_DirectOP_Register*)sdRef;
360
+ unsigned int size;
361
+ RData *newrd;
362
+ mStatus err;
337
363
  (void)flags; // Unused
338
- (void)rdlen; // Unused
339
- (void)rdata; // Unused
340
- (void)ttl; // Unused
341
- return(kDNSServiceErr_Unsupported);
364
+ if (!x || RecordRef || x->disposefn != DNSServiceRegisterDispose) return(kDNSServiceErr_Unsupported);
365
+ size = rdlen > sizeof(RDataBody) ? rdlen : sizeof(RDataBody);
366
+ newrd = (RData *)mDNSPlatformMemAllocate(sizeof(RData) - sizeof(RDataBody) + size);
367
+ if (!newrd) return(kDNSServiceErr_NoMemory);
368
+ newrd->MaxRDLength = (mDNSu16)size;
369
+ mDNSPlatformMemCopy(&newrd->u, rdata, rdlen);
370
+ // A TXT record can't be empty, use a single empty string
371
+ if (rdlen == 0) { rdlen = 1; newrd->u.txt.c[0] = 0; }
372
+ err = mDNS_Update(&mDNSStorage, &x->s.RR_TXT, ttl, rdlen, newrd, UpdateCallback);
373
+ if (err) mDNSPlatformMemFree(newrd);
374
+ return(err);
342
375
  }
343
376
 
344
377
  DNSServiceErrorType DNSServiceRemoveRecord
@@ -409,11 +442,19 @@ DNSServiceErrorType DNSServiceBrowse
409
442
  const char *errormsg = "Unknown";
410
443
  domainname t, d;
411
444
  mDNS_DirectOP_Browse *x;
445
+ // react-native-zeroconf: "_type._tcp,_subtype" browses a subtype, as uds_daemon.c handle_browse_request does
446
+ char typeAndSubtype[MAX_ESCAPED_DOMAIN_NAME];
447
+ mDNSs32 NumSubTypes;
412
448
  (void)flags; // Unused
413
- (void)interfaceIndex; // Unused
414
449
 
415
450
  // Check parameters
416
- if (!regtype[0] || !MakeDomainNameFromDNSNameString(&t, regtype)) { errormsg = "Illegal regtype"; goto badparam; }
451
+ if (!regtype[0] || strlen(regtype) >= sizeof(typeAndSubtype)) { errormsg = "Illegal regtype"; goto badparam; }
452
+ strcpy(typeAndSubtype, regtype);
453
+ t.c[0] = 0;
454
+ NumSubTypes = ChopSubTypes(typeAndSubtype);
455
+ if (NumSubTypes < 0 || NumSubTypes > 1) { errormsg = "Illegal regtype"; goto badparam; }
456
+ if (NumSubTypes == 1 && !AppendDNSNameString(&t, typeAndSubtype + strlen(typeAndSubtype) + 1)) { errormsg = "Illegal regtype"; goto badparam; }
457
+ if (!typeAndSubtype[0] || !AppendDNSNameString(&t, typeAndSubtype)) { errormsg = "Illegal regtype"; goto badparam; }
417
458
  if (!MakeDomainNameFromDNSNameString(&d, *domain ? domain : "local.")) { errormsg = "Illegal domain"; goto badparam; }
418
459
 
419
460
  // Allocate memory, and handle failure
@@ -427,7 +468,7 @@ DNSServiceErrorType DNSServiceBrowse
427
468
  x->q.QuestionContext = x;
428
469
 
429
470
  // Do the operation
430
- err = mDNS_StartBrowse(&mDNSStorage, &x->q, &t, &d, mDNSInterface_Any, (flags & kDNSServiceFlagsForceMulticast) != 0, FoundInstance, x);
471
+ err = mDNS_StartBrowse(&mDNSStorage, &x->q, &t, &d, mDNSPlatformInterfaceIDfromInterfaceIndex(&mDNSStorage, interfaceIndex), (flags & kDNSServiceFlagsForceMulticast) != 0, FoundInstance, x);
431
472
  if (err) { mDNSPlatformMemFree(x); errormsg = "mDNS_StartBrowse"; goto fail; }
432
473
 
433
474
  // Succeeded: Wrap up and return
@@ -495,8 +536,6 @@ DNSServiceErrorType DNSServiceResolve
495
536
  mDNS_DirectOP_Resolve *x;
496
537
 
497
538
  (void)flags; // Unused
498
- (void)interfaceIndex; // Unused
499
-
500
539
  // Check parameters
501
540
  if (!name[0] || !MakeDomainLabelFromLiteralString(&n, name )) { errormsg = "Bad Instance Name"; goto badparam; }
502
541
  if (!regtype[0] || !MakeDomainNameFromDNSNameString(&t, regtype)) { errormsg = "Bad Service Type"; goto badparam; }
@@ -515,7 +554,7 @@ DNSServiceErrorType DNSServiceResolve
515
554
  x->TXT = mDNSNULL;
516
555
 
517
556
  x->qSRV.ThisQInterval = -1; // So that DNSServiceResolveDispose() knows whether to cancel this question
518
- x->qSRV.InterfaceID = mDNSInterface_Any;
557
+ x->qSRV.InterfaceID = mDNSPlatformInterfaceIDfromInterfaceIndex(&mDNSStorage, interfaceIndex);
519
558
  x->qSRV.Target = zeroAddr;
520
559
  AssignDomainName(&x->qSRV.qname, &srv);
521
560
  x->qSRV.qtype = kDNSType_SRV;
@@ -535,7 +574,7 @@ DNSServiceErrorType DNSServiceResolve
535
574
  x->qSRV.QuestionContext = x;
536
575
 
537
576
  x->qTXT.ThisQInterval = -1; // So that DNSServiceResolveDispose() knows whether to cancel this question
538
- x->qTXT.InterfaceID = mDNSInterface_Any;
577
+ x->qTXT.InterfaceID = mDNSPlatformInterfaceIDfromInterfaceIndex(&mDNSStorage, interfaceIndex);
539
578
  x->qTXT.Target = zeroAddr;
540
579
  AssignDomainName(&x->qTXT.qname, &srv);
541
580
  x->qTXT.qtype = kDNSType_TXT;
@@ -653,8 +692,6 @@ DNSServiceErrorType DNSServiceQueryRecord
653
692
  mDNS_DirectOP_QueryRecord *x;
654
693
 
655
694
  (void)flags; // Unused
656
- (void)interfaceIndex; // Unused
657
-
658
695
  // Allocate memory, and handle failure
659
696
  x = (mDNS_DirectOP_QueryRecord *)mDNSPlatformMemAllocate(sizeof(*x));
660
697
  if (!x) { err = mStatus_NoMemoryErr; errormsg = "No memory"; goto fail; }
@@ -665,7 +702,7 @@ DNSServiceErrorType DNSServiceQueryRecord
665
702
  x->context = context;
666
703
 
667
704
  x->q.ThisQInterval = -1; // So that DNSServiceResolveDispose() knows whether to cancel this question
668
- x->q.InterfaceID = mDNSInterface_Any;
705
+ x->q.InterfaceID = mDNSPlatformInterfaceIDfromInterfaceIndex(&mDNSStorage, interfaceIndex);
669
706
  x->q.Target = zeroAddr;
670
707
  MakeDomainNameFromDNSNameString(&x->q.qname, fullname);
671
708
  x->q.qtype = rrtype;