react-native-zeroconf 0.17.1 → 0.17.3

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/README.md CHANGED
@@ -2,6 +2,14 @@
2
2
  <a href="https://zeroconf.balthazar.dev"><img src=".github/banner.png" alt="react-native-zeroconf: Bonjour / mDNS discovery and publishing for React Native, on iOS, Android, Windows, macOS, tvOS and Expo" width="100%"></a>
3
3
  </p>
4
4
 
5
+ <p align="center">
6
+ <a href="https://www.npmjs.com/package/react-native-zeroconf"><img src="https://img.shields.io/npm/v/react-native-zeroconf?style=flat" alt="npm version"></a>
7
+ <a href="https://www.npmjs.com/package/react-native-zeroconf"><img src="https://img.shields.io/npm/dm/react-native-zeroconf?style=flat" alt="npm downloads"></a>
8
+ <a href="https://github.com/balthazar/react-native-zeroconf/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/balthazar/react-native-zeroconf/ci.yml?branch=master&label=tests&style=flat" alt="tests"></a>
9
+ <a href="https://github.com/balthazar/react-native-zeroconf/actions/workflows/windows.yml"><img src="https://img.shields.io/github/actions/workflow/status/balthazar/react-native-zeroconf/windows.yml?branch=master&label=windows&style=flat" alt="windows"></a>
10
+ <a href="LICENSE"><img src="https://img.shields.io/npm/l/react-native-zeroconf?style=flat" alt="license"></a>
11
+ </p>
12
+
5
13
  Find the services other devices advertise on the local network (printers, speakers, cameras, your own servers) and advertise your own, on iOS, Android, macOS, tvOS and Windows.
6
14
 
7
15
  **[Read the documentation](https://zeroconf.balthazar.dev)**
@@ -13,6 +13,8 @@ android {
13
13
  targetSdkVersion safeExtGet('targetSdkVersion', 34)
14
14
  versionCode 1
15
15
  versionName "1.0"
16
+ // Instrumented tests (android/src/androidTest), see docs/Contributing.md
17
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
16
18
 
17
19
  ndk {
18
20
  abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
@@ -42,4 +44,6 @@ repositories {
42
44
 
43
45
  dependencies {
44
46
  implementation "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}"
47
+ androidTestImplementation 'androidx.test:runner:1.6.2'
48
+ androidTestImplementation 'androidx.test.ext:junit:1.2.1'
45
49
  }
@@ -532,6 +532,21 @@ public class NsdServiceImpl implements Zeroconf {
532
532
  return mNsdManager;
533
533
  }
534
534
 
535
+ // Before Android 14 each NsdManager client resolves one service at a time and a resolve can't be cancelled:
536
+ // a service that never answers would block every later resolve. After a timeout the queue moves on to a
537
+ // client of its own, an NsdManager from a new Context (system services are cached per Context)
538
+ @Nullable private volatile NsdManager mResolveManager;
539
+
540
+ private NsdManager getResolveManager() {
541
+ return mResolveManager != null ? mResolveManager : getNsdManager();
542
+ }
543
+
544
+ private void replaceResolveManager() {
545
+ Context context = getReactApplicationContext();
546
+ Context fresh = context.createConfigurationContext(context.getResources().getConfiguration());
547
+ mResolveManager = (NsdManager) fresh.getSystemService(Context.NSD_SERVICE);
548
+ }
549
+
535
550
  private ReactApplicationContext getReactApplicationContext() {
536
551
  return reactApplicationContext;
537
552
  }
@@ -560,8 +575,8 @@ public class NsdServiceImpl implements Zeroconf {
560
575
  }
561
576
  final ZeroResolveListener listener = new ZeroResolveListener(next);
562
577
  try {
563
- getNsdManager().resolveService(next.serviceInfo, listener);
564
- // The system keeps the resolve until it answers or fails, the queue waits for it
578
+ getResolveManager().resolveService(next.serviceInfo, listener);
579
+ // The queue waits for the answer, or moves on to another client after the timeout
565
580
  mHandler.postDelayed(listener::timedOut, (long) (next.timeoutSeconds * 1000));
566
581
  } catch (Throwable e) {
567
582
  Log.e(TAG, "resolveService failed", e);
@@ -582,7 +597,7 @@ public class NsdServiceImpl implements Zeroconf {
582
597
  private final PendingResolve pending;
583
598
  @Nullable private final String scanId;
584
599
  @Nullable private final Promise promise;
585
- // Settled: answered, failed or timed out. A late answer after the timeout only frees the queue
600
+ // Settled: answered, failed or timed out. A late answer after the timeout is ignored
586
601
  private boolean settled = false;
587
602
 
588
603
  ZeroResolveListener(PendingResolve pending) {
@@ -609,6 +624,8 @@ public class NsdServiceImpl implements Zeroconf {
609
624
  } else if (mScans.containsKey(scanId)) {
610
625
  sendResolveTimeout(serviceName, scanId);
611
626
  }
627
+ replaceResolveManager();
628
+ onResolveDone();
612
629
  }
613
630
 
614
631
  @Override
@@ -616,7 +633,6 @@ public class NsdServiceImpl implements Zeroconf {
616
633
  if (errorCode == NsdManager.FAILURE_ALREADY_ACTIVE) {
617
634
  // Another resolve (possibly from another app or listener) is in flight: retry shortly
618
635
  if (!settle()) {
619
- onResolveDone();
620
636
  return;
621
637
  }
622
638
  synchronized (mResolveQueue) {
@@ -627,7 +643,7 @@ public class NsdServiceImpl implements Zeroconf {
627
643
  return;
628
644
  }
629
645
  if (!settle()) {
630
- onResolveDone();
646
+ // Timed out: the queue already moved on to another client
631
647
  return;
632
648
  }
633
649
  String message = "Resolving service " + serviceInfo.getServiceName() + " failed: " + ZeroconfModule.describeNsdError(errorCode);
@@ -642,7 +658,7 @@ public class NsdServiceImpl implements Zeroconf {
642
658
  @Override
643
659
  public void onServiceResolved(NsdServiceInfo serviceInfo) {
644
660
  if (!settle()) {
645
- onResolveDone();
661
+ // Timed out: the queue already moved on to another client
646
662
  return;
647
663
  }
648
664
  if (promise != null) {
@@ -106,6 +106,37 @@ public abstract class DNSSD implements InternalDNSSDService.DnssdServiceListener
106
106
  this.serviceTimeout = serviceTimeout;
107
107
  }
108
108
 
109
+ // The embedded responder can answer before the call that starts an operation has returned, from its
110
+ // own thread or from inside the call. The operation is assigned under the monitor of its array while
111
+ // that call runs, and the callbacks posted to the main thread take the monitor first, so they wait for
112
+ // the call to return. If it threw, there is no operation and the callback is dropped.
113
+
114
+ private interface Start<T> {
115
+ T start() throws DNSSDException;
116
+ }
117
+
118
+ private interface OperationCallback<T> {
119
+ void run(T operation);
120
+ }
121
+
122
+ private static <T> void start(T[] operation, Start<T> start) throws DNSSDException {
123
+ synchronized (operation) {
124
+ operation[0] = start.start();
125
+ }
126
+ }
127
+
128
+ private <T> void postWith(final T[] operation, final OperationCallback<T> callback) {
129
+ handler.post(() -> {
130
+ T started;
131
+ synchronized (operation) {
132
+ started = operation[0];
133
+ }
134
+ if (started != null) {
135
+ callback.run(started);
136
+ }
137
+ });
138
+ }
139
+
109
140
  /** Browse for instances of a service.<P>
110
141
 
111
142
  Note: browsing consumes network bandwidth. Call {@link InternalDNSSDService#stop} when you have finished browsing.<P>
@@ -139,13 +170,13 @@ public abstract class DNSSD implements InternalDNSSDService.DnssdServiceListener
139
170
  public DNSSDService browse(int flags, int ifIndex, String regType, String domain, final BrowseListener listener) throws DNSSDException {
140
171
  onServiceStarting();
141
172
  final InternalDNSSDService[] services = new InternalDNSSDService[1];
142
- services[0] = new InternalDNSSDService(this, InternalDNSSD.browse(flags, ifIndex, regType, domain, new InternalBrowseListener() {
173
+ start(services, () -> new InternalDNSSDService(this, InternalDNSSD.browse(flags, ifIndex, regType, domain, new InternalBrowseListener() {
143
174
  @Override
144
175
  public void serviceFound(final DNSSDService browser, final int flags, final int ifIndex, final byte[] serviceName, final byte[] regType, final byte[] domain) {
145
176
  final String serviceNameStr = new String(serviceName, UTF_8);
146
177
  final String regTypeStr = new String(regType, UTF_8);
147
178
  final String domainStr = new String(domain, UTF_8);
148
- handler.post(() -> listener.serviceFound(services[0], flags, ifIndex, serviceNameStr, regTypeStr, domainStr));
179
+ postWith(services, operation -> listener.serviceFound(operation, flags, ifIndex, serviceNameStr, regTypeStr, domainStr));
149
180
  }
150
181
 
151
182
  @Override
@@ -153,14 +184,14 @@ public abstract class DNSSD implements InternalDNSSDService.DnssdServiceListener
153
184
  final String serviceNameStr = new String(serviceName, UTF_8);
154
185
  final String regTypeStr = new String(regType, UTF_8);
155
186
  final String domainStr = new String(domain, UTF_8);
156
- handler.post(() -> listener.serviceLost(services[0], flags, ifIndex, serviceNameStr, regTypeStr, domainStr));
187
+ postWith(services, operation -> listener.serviceLost(operation, flags, ifIndex, serviceNameStr, regTypeStr, domainStr));
157
188
  }
158
189
 
159
190
  @Override
160
191
  public void operationFailed(final DNSSDService service, final int errorCode) {
161
- handler.post(() -> listener.operationFailed(services[0], errorCode));
192
+ postWith(services, operation -> listener.operationFailed(operation, errorCode));
162
193
  }
163
- }));
194
+ })));
164
195
  return services[0];
165
196
 
166
197
  }
@@ -229,28 +260,28 @@ public abstract class DNSSD implements InternalDNSSDService.DnssdServiceListener
229
260
 
230
261
  final Runnable timeoutRunnable = () -> services[0].stop();
231
262
 
232
- services[0] = new InternalDNSSDService(this, InternalDNSSD.resolve(flags, ifIndex, serviceName, regType, domain, new InternalResolveListener() {
263
+ start(services, () -> new InternalDNSSDService(this, InternalDNSSD.resolve(flags, ifIndex, serviceName, regType, domain, new InternalResolveListener() {
233
264
  @Override
234
265
  public void serviceResolved(final DNSSDService resolver, final int flags, final int ifIndex, byte[] fullName, byte[] hostName, final int port, TXTRecord txtRecord) {
235
266
  final String fullNameStr = new String(fullName, UTF_8);
236
267
  final String hostNameStr = new String(hostName, UTF_8);
237
268
  final Map<String, String> record = parseTXTRecords(txtRecord);
238
269
  handler.removeCallbacks(timeoutRunnable);
239
- handler.post(() -> {
240
- listener.serviceResolved(services[0], flags, ifIndex, fullNameStr, hostNameStr, port, record);
241
- services[0].stop();
270
+ postWith(services, operation -> {
271
+ listener.serviceResolved(operation, flags, ifIndex, fullNameStr, hostNameStr, port, record);
272
+ operation.stop();
242
273
  });
243
274
  }
244
275
 
245
276
  @Override
246
277
  public void operationFailed(final DNSSDService service, final int errorCode) {
247
278
  handler.removeCallbacks(timeoutRunnable);
248
- handler.post(() -> {
249
- listener.operationFailed(services[0], errorCode);
250
- services[0].stop();
279
+ postWith(services, operation -> {
280
+ listener.operationFailed(operation, errorCode);
281
+ operation.stop();
251
282
  });
252
283
  }
253
- }));
284
+ })));
254
285
 
255
286
  handler.postDelayed(timeoutRunnable, serviceTimeout);
256
287
  return services[0];
@@ -313,7 +344,7 @@ public abstract class DNSSD implements InternalDNSSDService.DnssdServiceListener
313
344
  final RegisterListener listener) throws DNSSDException {
314
345
  onServiceStarting();
315
346
  final DNSSDRegistration[] services = new DNSSDRegistration[1];
316
- services[0] = new InternalDNSSDRegistration(this, InternalDNSSD.register(flags, ifIndex, serviceName, regType, domain, host, port, txtRecord,
347
+ start(services, () -> new InternalDNSSDRegistration(this, InternalDNSSD.register(flags, ifIndex, serviceName, regType, domain, host, port, txtRecord,
317
348
  new InternalRegisterListener() {
318
349
 
319
350
  @Override
@@ -321,14 +352,14 @@ public abstract class DNSSD implements InternalDNSSDService.DnssdServiceListener
321
352
  final String serviceNameStr = new String(serviceName, UTF_8);
322
353
  final String regTypeStr = new String(regType, UTF_8);
323
354
  final String domainStr = new String(domain, UTF_8);
324
- handler.post(() -> listener.serviceRegistered(services[0], flags, serviceNameStr, regTypeStr, domainStr));
355
+ postWith(services, operation -> listener.serviceRegistered(operation, flags, serviceNameStr, regTypeStr, domainStr));
325
356
  }
326
357
 
327
358
  @Override
328
359
  public void operationFailed(DNSSDService service, final int errorCode) {
329
- handler.post(() -> listener.operationFailed(services[0], errorCode));
360
+ postWith(services, operation -> listener.operationFailed(operation, errorCode));
330
361
  }
331
- }));
362
+ })));
332
363
  return services[0];
333
364
  }
334
365
 
@@ -447,15 +478,15 @@ public abstract class DNSSD implements InternalDNSSDService.DnssdServiceListener
447
478
 
448
479
  final Runnable timeoutRunnable = () -> services[0].stop();
449
480
 
450
- services[0] = new InternalDNSSDService(this, InternalDNSSD.queryRecord(flags, ifIndex, serviceName, rrtype, rrclass, new InternalQueryListener() {
481
+ start(services, () -> new InternalDNSSDService(this, InternalDNSSD.queryRecord(flags, ifIndex, serviceName, rrtype, rrclass, new InternalQueryListener() {
451
482
  @Override
452
483
  public void queryAnswered(DNSSDService query, final int flags, final int ifIndex, byte[] fullName, final int rrtype, final int rrclass, byte[] rdata, final int ttl) {
453
484
  final String fullNameStr = new String(fullName, UTF_8);
454
485
  handler.removeCallbacks(timeoutRunnable);
455
- handler.post(() -> {
456
- listener.queryAnswered(services[0], flags, ifIndex, fullNameStr, rrtype, rrclass, rdata, ttl);
486
+ postWith(services, operation -> {
487
+ listener.queryAnswered(operation, flags, ifIndex, fullNameStr, rrtype, rrclass, rdata, ttl);
457
488
  if (autoStop) {
458
- services[0].stop();
489
+ operation.stop();
459
490
  }
460
491
  });
461
492
  }
@@ -463,12 +494,12 @@ public abstract class DNSSD implements InternalDNSSDService.DnssdServiceListener
463
494
  @Override
464
495
  public void operationFailed(DNSSDService service, final int errorCode) {
465
496
  handler.removeCallbacks(timeoutRunnable);
466
- handler.post(() -> {
467
- listener.operationFailed(services[0], errorCode);
468
- services[0].stop();
497
+ postWith(services, operation -> {
498
+ listener.operationFailed(operation, errorCode);
499
+ operation.stop();
469
500
  });
470
501
  }
471
- }));
502
+ })));
472
503
 
473
504
  if (autoStop) {
474
505
  handler.postDelayed(timeoutRunnable, serviceTimeout);
@@ -503,24 +534,24 @@ public abstract class DNSSD implements InternalDNSSDService.DnssdServiceListener
503
534
  public DNSSDService enumerateDomains(int flags, int ifIndex, final DomainListener listener) throws DNSSDException {
504
535
  onServiceStarting();
505
536
  final DNSSDService[] services = new DNSSDService[1];
506
- services[0] = new InternalDNSSDService(this, InternalDNSSD.enumerateDomains(flags, ifIndex, new InternalDomainListener() {
537
+ start(services, () -> new InternalDNSSDService(this, InternalDNSSD.enumerateDomains(flags, ifIndex, new InternalDomainListener() {
507
538
  @Override
508
539
  public void domainFound(DNSSDService domainEnum, final int flags, final int ifIndex, byte[] domain) {
509
540
  final String domainStr = new String(domain, UTF_8);
510
- handler.post(() -> listener.domainFound(services[0], flags, ifIndex, domainStr));
541
+ postWith(services, operation -> listener.domainFound(operation, flags, ifIndex, domainStr));
511
542
  }
512
543
 
513
544
  @Override
514
545
  public void domainLost(DNSSDService domainEnum, final int flags, final int ifIndex, byte[] domain) {
515
546
  final String domainStr = new String(domain, UTF_8);
516
- handler.post(() -> listener.domainLost(services[0], flags, ifIndex, domainStr));
547
+ postWith(services, operation -> listener.domainLost(operation, flags, ifIndex, domainStr));
517
548
  }
518
549
 
519
550
  @Override
520
551
  public void operationFailed(final DNSSDService service, final int errorCode) {
521
- handler.post(() -> listener.operationFailed(services[0], errorCode));
552
+ postWith(services, operation -> listener.operationFailed(operation, errorCode));
522
553
  }
523
- }));
554
+ })));
524
555
  return services[0];
525
556
  }
526
557
 
@@ -23,6 +23,10 @@ import android.util.Log;
23
23
 
24
24
  /**
25
25
  * DNSSD implementation running the embedded mDNSResponder {@link InternalDNSSD}
26
+ *
27
+ * The responder's event loop runs on its own thread, started by the first operation and stopped
28
+ * a while after the last one ended. Operations start and stop on arbitrary threads, so the
29
+ * lifecycle state below is guarded by the class monitor.
26
30
  */
27
31
  public class DNSSDEmbedded extends DNSSD {
28
32
 
@@ -31,9 +35,11 @@ public class DNSSDEmbedded extends DNSSD {
31
35
  private static final String TAG = "DNSSDEmbedded";
32
36
  private final long mStopTimerDelay;
33
37
  private final Handler handler = new Handler(Looper.getMainLooper());
34
- private Thread mThread;
35
- private volatile boolean isStarted = false;
36
- private int serviceCount = 0;
38
+ private final ExitTimer exitTimer;
39
+ private Thread mThread; // guarded by DNSSDEmbedded.class
40
+ private volatile boolean isStarted = false; // the loop is running (set by the loop thread)
41
+ private volatile boolean isExiting = false; // nativeExit() was called, the thread is on its way out
42
+ private int serviceCount = 0; // guarded by DNSSDEmbedded.class
37
43
 
38
44
  public DNSSDEmbedded(Context context) {
39
45
  this(context, DEFAULT_STOP_TIMER_DELAY);
@@ -42,6 +48,35 @@ public class DNSSDEmbedded extends DNSSD {
42
48
  public DNSSDEmbedded(Context context, long stopTimerDelay) {
43
49
  super(context, "jdns_sd_embedded");
44
50
  mStopTimerDelay = stopTimerDelay;
51
+ exitTimer = new ExitTimer(handler, stopTimerDelay, this::exitIfIdle);
52
+ }
53
+
54
+ /**
55
+ * Stops the responder a while after the last operation ended, unless another one starts first.
56
+ * The same Runnable is posted and removed: evaluating a method reference at two call sites
57
+ * gives two objects, so posting one and removing the other would never cancel anything.
58
+ */
59
+ static final class ExitTimer {
60
+ private final Handler handler;
61
+ private final long delayMillis;
62
+ private final Runnable exit;
63
+
64
+ ExitTimer(Handler handler, long delayMillis, Runnable exit) {
65
+ this.handler = handler;
66
+ this.delayMillis = delayMillis;
67
+ this.exit = exit;
68
+ }
69
+
70
+ /** The last operation stopped: exit after the delay. */
71
+ void schedule() {
72
+ handler.removeCallbacks(exit);
73
+ handler.postDelayed(exit, delayMillis);
74
+ }
75
+
76
+ /** An operation is starting: the responder stays up. */
77
+ void cancel() {
78
+ handler.removeCallbacks(exit);
79
+ }
45
80
  }
46
81
 
47
82
  static native int nativeInit();
@@ -57,40 +92,52 @@ public class DNSSDEmbedded extends DNSSD {
57
92
  * Note: This method will block thread until DNS-SD initialization finish.
58
93
  */
59
94
  public void init() {
60
- handler.removeCallbacks(DNSSDEmbedded::nativeExit);
61
-
62
- if (mThread != null && mThread.isAlive()) {
63
- Log.i(TAG, "already started");
64
- waitUntilStarted();
65
- return;
66
- }
67
-
68
- isStarted = false;
95
+ synchronized (DNSSDEmbedded.class) {
96
+ exitTimer.cancel();
69
97
 
70
- InternalDNSSD.getInstance();
71
- mThread = new Thread() {
72
- public void run() {
73
- Log.i(TAG, "init");
74
- int err = nativeInit();
75
- synchronized (DNSSDEmbedded.class) {
76
- isStarted = true;
77
- DNSSDEmbedded.class.notifyAll();
78
- }
79
- if (err != 0) {
80
- Log.e(TAG, "error: " + err);
98
+ if (mThread != null && mThread.isAlive()) {
99
+ if (!isExiting) {
100
+ Log.i(TAG, "already started");
101
+ waitUntilStarted();
81
102
  return;
82
103
  }
83
- Log.i(TAG, "start");
84
- int ret = nativeLoop();
85
- isStarted = false;
86
- Log.i(TAG, "finish with code: " + ret);
104
+ // The exit timer fired just before this operation: let the thread finish, then start afresh
105
+ Log.i(TAG, "restarting");
106
+ try {
107
+ mThread.join();
108
+ } catch (InterruptedException e) {
109
+ Log.e(TAG, "init interrupted while waiting for the previous loop: ", e);
110
+ }
87
111
  }
88
- };
89
- mThread.setPriority(Thread.MAX_PRIORITY);
90
- mThread.setName("DNS-SDEmbedded");
91
- mThread.start();
92
112
 
93
- waitUntilStarted();
113
+ isStarted = false;
114
+ isExiting = false;
115
+
116
+ InternalDNSSD.getInstance();
117
+ mThread = new Thread() {
118
+ public void run() {
119
+ Log.i(TAG, "init");
120
+ int err = nativeInit();
121
+ synchronized (DNSSDEmbedded.class) {
122
+ isStarted = true;
123
+ DNSSDEmbedded.class.notifyAll();
124
+ }
125
+ if (err != 0) {
126
+ Log.e(TAG, "error: " + err);
127
+ return;
128
+ }
129
+ Log.i(TAG, "start");
130
+ int ret = nativeLoop();
131
+ isStarted = false;
132
+ Log.i(TAG, "finish with code: " + ret);
133
+ }
134
+ };
135
+ mThread.setPriority(Thread.MAX_PRIORITY);
136
+ mThread.setName("DNS-SDEmbedded");
137
+ mThread.start();
138
+
139
+ waitUntilStarted();
140
+ }
94
141
  }
95
142
 
96
143
  /**
@@ -101,7 +148,19 @@ public class DNSSDEmbedded extends DNSSD {
101
148
  public void exit() {
102
149
  synchronized (DNSSDEmbedded.class) {
103
150
  Log.i(TAG, "post exit");
104
- handler.postDelayed(DNSSDEmbedded::nativeExit, mStopTimerDelay);
151
+ exitTimer.schedule();
152
+ }
153
+ }
154
+
155
+ // Runs on the main thread when the timer fires; an operation may have started since it was scheduled
156
+ private void exitIfIdle() {
157
+ synchronized (DNSSDEmbedded.class) {
158
+ if (serviceCount != 0 || isExiting || mThread == null || !mThread.isAlive()) {
159
+ return;
160
+ }
161
+ Log.i(TAG, "exit");
162
+ isExiting = true;
163
+ nativeExit();
105
164
  }
106
165
  }
107
166
 
@@ -119,17 +178,21 @@ public class DNSSDEmbedded extends DNSSD {
119
178
 
120
179
  @Override
121
180
  public void onServiceStarting() {
122
- super.onServiceStarting();
123
- this.init();
124
- serviceCount++;
181
+ synchronized (DNSSDEmbedded.class) {
182
+ super.onServiceStarting();
183
+ this.init();
184
+ serviceCount++;
185
+ }
125
186
  }
126
187
 
127
188
  @Override
128
189
  public void onServiceStopped() {
129
- super.onServiceStopped();
130
- serviceCount--;
131
- if (serviceCount == 0) {
132
- this.exit();
190
+ synchronized (DNSSDEmbedded.class) {
191
+ super.onServiceStopped();
192
+ serviceCount--;
193
+ if (serviceCount == 0) {
194
+ this.exit();
195
+ }
133
196
  }
134
197
  }
135
198
  }
@@ -107,14 +107,26 @@ struct OpContext
107
107
 
108
108
  //For AUTO_CALLBACKS, we must attach the callback thread to the Java VM prior to upcall.
109
109
  #if AUTO_CALLBACKS
110
- JNIEnv *pLoopEnv = NULL;
110
+ JNIEnv *pLoopEnv = NULL; // The event loop thread's env, the fallback when the current thread's is unavailable
111
+ static JavaVM *gJavaVM = NULL;
111
112
  #endif
112
113
 
113
114
  #ifdef EMBEDDED
114
115
  int init();
115
116
  int loop();
116
117
  void stopLoop();
118
+ void lockCore();
119
+ void unlockCore();
120
+ // The embedded shim reads core state (the interface list, the host name) before mDNS core
121
+ // takes its own lock, so every call into it is made under the core lock. It is recursive.
122
+ #define LOCK_CORE() lockCore()
123
+ #define UNLOCK_CORE() unlockCore()
124
+ #else
125
+ #define LOCK_CORE() do { } while (0)
126
+ #define UNLOCK_CORE() do { } while (0)
127
+ #endif
117
128
 
129
+ #ifdef EMBEDDED
118
130
  JNIEXPORT jint JNICALL Java_com_github_druk_dnssd_DNSSDEmbedded_nativeInit( JNIEnv *pEnv, jclass cls)
119
131
  {
120
132
  pLoopEnv = pEnv;
@@ -140,12 +152,8 @@ JNIEXPORT jint JNICALL Java_com_github_druk_dnssd_AppleDNSSD_InitLibrary( JNIEnv
140
152
  return kDNSServiceErr_Incompatible;
141
153
 
142
154
  #if AUTO_CALLBACKS
143
- // {
144
- // jsize numVMs;
145
-
146
- // if ( 0 != JNI_GetCreatedJavaVMs( &gJavaVM, 1, &numVMs))
147
- // return kDNSServiceErr_BadState;
148
- // }
155
+ if ( gJavaVM == NULL && 0 != (*pEnv)->GetJavaVM( pEnv, &gJavaVM))
156
+ return kDNSServiceErr_BadState;
149
157
  #endif
150
158
 
151
159
  // Set AppleDNSSD.hasAutoCallbacks
@@ -180,13 +188,18 @@ static void SafeReleaseUTFChars( JNIEnv *pEnv, jstring str, const char *buff)
180
188
  #if AUTO_CALLBACKS
181
189
  static void SetupCallbackState( JNIEnv **ppEnv)
182
190
  {
183
- (*ppEnv) = pLoopEnv;
184
- //(*gJavaVM)->AttachCurrentThread( gJavaVM, (void**) ppEnv, NULL);
191
+ // Callbacks usually come from the event loop thread, but mDNS core also calls back from inside
192
+ // an API call, on the client's thread: a local-only registration is acknowledged inside
193
+ // DNSServiceRegister(). A JNIEnv belongs to one thread, so use the current thread's.
194
+ JNIEnv *pEnv = NULL;
195
+ if ( gJavaVM != NULL && JNI_OK == (*gJavaVM)->GetEnv( gJavaVM, (void**) &pEnv, JNI_VERSION_1_6) && pEnv != NULL)
196
+ (*ppEnv) = pEnv;
197
+ else
198
+ (*ppEnv) = pLoopEnv;
185
199
  }
186
200
 
187
201
  static void TeardownCallbackState( void )
188
202
  {
189
- //(*gJavaVM)->DetachCurrentThread( gJavaVM);
190
203
  }
191
204
 
192
205
  #else // AUTO_CALLBACKS
@@ -248,6 +261,13 @@ JNIEXPORT void JNICALL Java_com_github_druk_dnssd_AppleService_HaltOperation( JN
248
261
  OpContext *pContext = (OpContext*) (long) (*pEnv)->GetLongField(pEnv, pThis, contextField);
249
262
  if ( pContext != NULL)
250
263
  {
264
+ // The embedded responder delivers callbacks from its own thread and may be in
265
+ // the middle of one for this very operation (an answer with several records
266
+ // invokes the callback once per record). DNSServiceRefDeallocate() takes the
267
+ // core lock itself; holding it here too keeps the weak refs and the free() in
268
+ // the same critical section. Consequently a Java callback running on the loop
269
+ // thread must never block on a thread that may be in here (the main thread).
270
+ LOCK_CORE();
251
271
  // MUST clear fNativeContext first, BEFORE calling DNSServiceRefDeallocate()
252
272
  (*pEnv)->SetLongField(pEnv, pThis, contextField, 0);
253
273
  if ( pContext->ServiceRef != NULL)
@@ -256,6 +276,7 @@ JNIEXPORT void JNICALL Java_com_github_druk_dnssd_AppleService_HaltOperation( JN
256
276
  (*pEnv)->DeleteWeakGlobalRef( pEnv, pContext->JavaObj);
257
277
  (*pEnv)->DeleteWeakGlobalRef( pEnv, pContext->ClientObj);
258
278
  free( pContext);
279
+ UNLOCK_CORE();
259
280
  }
260
281
  }
261
282
  }
@@ -355,29 +376,36 @@ static void DNSSD_API ServiceBrowseReply( DNSServiceRef sdRef _UNUSED, DNSServic
355
376
 
356
377
  SetupCallbackState( &pContext->Env);
357
378
 
358
- jobject clientObj = (*pContext->Env)->NewLocalRef(pContext->Env, pContext->ClientObj);
379
+ // Copies: the Java callback may stop this operation, after which pContext is freed.
380
+ JNIEnv *env = pContext->Env;
381
+ jobject javaObj = pContext->JavaObj;
382
+ jobject clientRef = pContext->ClientObj;
383
+ jmethodID callback = pContext->Callback;
384
+ jmethodID callback2 = pContext->Callback2;
385
+
386
+ jobject clientObj = (*env)->NewLocalRef(env, clientRef);
359
387
 
360
- if ( clientObj != NULL && pContext->Callback != NULL)
388
+ if ( clientObj != NULL && callback != NULL)
361
389
  {
362
390
  if ( errorCode == kDNSServiceErr_NoError)
363
391
  {
364
- jbyteArray jServiceName = (*pContext->Env)->NewByteArray(pContext->Env, (jsize)strlen(serviceName));
365
- (*pContext->Env)->SetByteArrayRegion (pContext->Env, jServiceName, 0, (jsize)strlen(serviceName), (const jbyte *) serviceName);
366
- jbyteArray jRegType = (*pContext->Env)->NewByteArray(pContext->Env, (jsize)strlen(regtype));
367
- (*pContext->Env)->SetByteArrayRegion (pContext->Env, jRegType, 0, (jsize)strlen(regtype), (const jbyte *) regtype);
368
- jbyteArray jReplyDomain = (*pContext->Env)->NewByteArray(pContext->Env, (jsize)strlen(replyDomain));
369
- (*pContext->Env)->SetByteArrayRegion (pContext->Env, jReplyDomain, 0, (jsize)strlen(replyDomain), (const jbyte *) replyDomain);
370
- (*pContext->Env)->CallVoidMethod( pContext->Env, clientObj,
371
- ( flags & kDNSServiceFlagsAdd) != 0 ? pContext->Callback : pContext->Callback2,
372
- pContext->JavaObj, flags, interfaceIndex, jServiceName, jRegType, jReplyDomain);
373
- (*pContext->Env)->DeleteLocalRef( pContext->Env, jServiceName);
374
- (*pContext->Env)->DeleteLocalRef( pContext->Env, jRegType);
375
- (*pContext->Env)->DeleteLocalRef( pContext->Env, jReplyDomain);
392
+ jbyteArray jServiceName = (*env)->NewByteArray(env, (jsize)strlen(serviceName));
393
+ (*env)->SetByteArrayRegion (env, jServiceName, 0, (jsize)strlen(serviceName), (const jbyte *) serviceName);
394
+ jbyteArray jRegType = (*env)->NewByteArray(env, (jsize)strlen(regtype));
395
+ (*env)->SetByteArrayRegion (env, jRegType, 0, (jsize)strlen(regtype), (const jbyte *) regtype);
396
+ jbyteArray jReplyDomain = (*env)->NewByteArray(env, (jsize)strlen(replyDomain));
397
+ (*env)->SetByteArrayRegion (env, jReplyDomain, 0, (jsize)strlen(replyDomain), (const jbyte *) replyDomain);
398
+ (*env)->CallVoidMethod( env, clientObj,
399
+ ( flags & kDNSServiceFlagsAdd) != 0 ? callback : callback2,
400
+ javaObj, flags, interfaceIndex, jServiceName, jRegType, jReplyDomain);
401
+ (*env)->DeleteLocalRef( env, jServiceName);
402
+ (*env)->DeleteLocalRef( env, jRegType);
403
+ (*env)->DeleteLocalRef( env, jReplyDomain);
376
404
  }
377
405
  else
378
- ReportError( pContext->Env, clientObj, pContext->JavaObj, errorCode);
406
+ ReportError( env, clientObj, javaObj, errorCode);
379
407
 
380
- (*pContext->Env)->DeleteLocalRef(pContext->Env, clientObj);
408
+ (*env)->DeleteLocalRef(env, clientObj);
381
409
  }
382
410
 
383
411
  TeardownCallbackState();
@@ -406,7 +434,9 @@ JNIEXPORT jint JNICALL Java_com_github_druk_dnssd_AppleBrowser_CreateBrowser( JN
406
434
  (*pEnv)->GetObjectClass( pEnv, pContext->ClientObj),
407
435
  "serviceLost", "(Lcom/github/druk/dnssd/DNSSDService;II[B[B[B)V");
408
436
 
437
+ LOCK_CORE();
409
438
  err = DNSServiceBrowse( &pContext->ServiceRef, flags, ifIndex, regStr, domainStr, ServiceBrowseReply, pContext);
439
+ UNLOCK_CORE();
410
440
  if ( err == kDNSServiceErr_NoError)
411
441
  {
412
442
  (*pEnv)->SetLongField(pEnv, pThis, contextField, (long) pContext);
@@ -435,11 +465,17 @@ static void DNSSD_API ServiceResolveReply( DNSServiceRef sdRef _UNUSED, DNSServi
435
465
 
436
466
  SetupCallbackState( &pContext->Env);
437
467
 
438
- txtCls = (*pContext->Env)->FindClass( pContext->Env, "com/github/druk/dnssd/TXTRecord");
439
- txtCtor = (*pContext->Env)->GetMethodID( pContext->Env, txtCls, "<init>", "([B)V");
468
+ // Copies: the Java callback may stop this operation, after which pContext is freed.
469
+ JNIEnv *env = pContext->Env;
470
+ jobject javaObj = pContext->JavaObj;
471
+ jobject clientRef = pContext->ClientObj;
472
+ jmethodID callback = pContext->Callback;
440
473
 
441
- if ( pContext->ClientObj != NULL && pContext->Callback != NULL && txtCtor != NULL &&
442
- NULL != ( txtBytes = (*pContext->Env)->NewByteArray( pContext->Env, txtLen)))
474
+ txtCls = (*env)->FindClass( env, "com/github/druk/dnssd/TXTRecord");
475
+ txtCtor = (*env)->GetMethodID( env, txtCls, "<init>", "([B)V");
476
+
477
+ if ( clientRef != NULL && callback != NULL && txtCtor != NULL &&
478
+ NULL != ( txtBytes = (*env)->NewByteArray( env, txtLen)))
443
479
  {
444
480
  if ( errorCode == kDNSServiceErr_NoError)
445
481
  {
@@ -448,26 +484,26 @@ static void DNSSD_API ServiceResolveReply( DNSServiceRef sdRef _UNUSED, DNSServi
448
484
  port = ( ((unsigned char*) &port)[0] << 8) | ((unsigned char*) &port)[1];
449
485
 
450
486
  // Initialize txtBytes with contents of txtRecord
451
- pBytes = (*pContext->Env)->GetByteArrayElements( pContext->Env, txtBytes, NULL);
487
+ pBytes = (*env)->GetByteArrayElements( env, txtBytes, NULL);
452
488
  memcpy( pBytes, txtRecord, txtLen);
453
- (*pContext->Env)->ReleaseByteArrayElements( pContext->Env, txtBytes, pBytes, JNI_COMMIT);
489
+ (*env)->ReleaseByteArrayElements( env, txtBytes, pBytes, JNI_COMMIT);
454
490
 
455
491
  // Construct txtObj with txtBytes
456
- txtObj = (*pContext->Env)->NewObject( pContext->Env, txtCls, txtCtor, txtBytes);
457
- (*pContext->Env)->DeleteLocalRef( pContext->Env, txtBytes);
458
-
459
- jbyteArray jFullName = (*pContext->Env)->NewByteArray(pContext->Env, (jsize)strlen(fullname));
460
- (*pContext->Env)->SetByteArrayRegion (pContext->Env, jFullName, 0, (jsize)strlen(fullname), (const jbyte *) fullname);
461
- jbyteArray jHostTarget = (*pContext->Env)->NewByteArray(pContext->Env, (jsize)strlen(hosttarget));
462
- (*pContext->Env)->SetByteArrayRegion (pContext->Env, jHostTarget, 0, (jsize)strlen(hosttarget), (const jbyte *) hosttarget);
463
- (*pContext->Env)->CallVoidMethod( pContext->Env, pContext->ClientObj, pContext->Callback,
464
- pContext->JavaObj, flags, interfaceIndex, jFullName, jHostTarget, port, txtObj);
465
- (*pContext->Env)->DeleteLocalRef( pContext->Env, jFullName);
466
- (*pContext->Env)->DeleteLocalRef( pContext->Env, jHostTarget);
467
- (*pContext->Env)->DeleteLocalRef( pContext->Env, txtObj);
492
+ txtObj = (*env)->NewObject( env, txtCls, txtCtor, txtBytes);
493
+ (*env)->DeleteLocalRef( env, txtBytes);
494
+
495
+ jbyteArray jFullName = (*env)->NewByteArray(env, (jsize)strlen(fullname));
496
+ (*env)->SetByteArrayRegion (env, jFullName, 0, (jsize)strlen(fullname), (const jbyte *) fullname);
497
+ jbyteArray jHostTarget = (*env)->NewByteArray(env, (jsize)strlen(hosttarget));
498
+ (*env)->SetByteArrayRegion (env, jHostTarget, 0, (jsize)strlen(hosttarget), (const jbyte *) hosttarget);
499
+ (*env)->CallVoidMethod( env, clientRef, callback,
500
+ javaObj, flags, interfaceIndex, jFullName, jHostTarget, port, txtObj);
501
+ (*env)->DeleteLocalRef( env, jFullName);
502
+ (*env)->DeleteLocalRef( env, jHostTarget);
503
+ (*env)->DeleteLocalRef( env, txtObj);
468
504
  }
469
505
  else
470
- ReportError( pContext->Env, pContext->ClientObj, pContext->JavaObj, errorCode);
506
+ ReportError( env, clientRef, javaObj, errorCode);
471
507
  }
472
508
 
473
509
  TeardownCallbackState();
@@ -493,8 +529,10 @@ JNIEXPORT jint JNICALL Java_com_github_druk_dnssd_AppleResolver_CreateResolver(
493
529
  const char *regStr = SafeGetUTFChars( pEnv, regType);
494
530
  const char *domainStr = SafeGetUTFChars( pEnv, domain);
495
531
 
532
+ LOCK_CORE();
496
533
  err = DNSServiceResolve( &pContext->ServiceRef, flags, ifIndex,
497
534
  servStr, regStr, domainStr, ServiceResolveReply, pContext);
535
+ UNLOCK_CORE();
498
536
  if ( err == kDNSServiceErr_NoError)
499
537
  {
500
538
  (*pEnv)->SetLongField(pEnv, pThis, contextField, (long) pContext);
@@ -519,25 +557,31 @@ static void DNSSD_API ServiceRegisterReply( DNSServiceRef sdRef _UNUSED, DNSServ
519
557
 
520
558
  SetupCallbackState( &pContext->Env);
521
559
 
522
- if ( pContext->ClientObj != NULL && pContext->Callback != NULL)
560
+ // Copies: the Java callback may stop this operation, after which pContext is freed.
561
+ JNIEnv *env = pContext->Env;
562
+ jobject javaObj = pContext->JavaObj;
563
+ jobject clientRef = pContext->ClientObj;
564
+ jmethodID callback = pContext->Callback;
565
+
566
+ if ( clientRef != NULL && callback != NULL)
523
567
  {
524
568
  if ( errorCode == kDNSServiceErr_NoError)
525
569
  {
526
- jbyteArray jServiceName = (*pContext->Env)->NewByteArray(pContext->Env, (jsize)strlen(serviceName));
527
- (*pContext->Env)->SetByteArrayRegion (pContext->Env, jServiceName, 0, (jsize)strlen(serviceName), (const jbyte *) serviceName);
528
- jbyteArray jRegType = (*pContext->Env)->NewByteArray(pContext->Env, (jsize)strlen(regType));
529
- (*pContext->Env)->SetByteArrayRegion (pContext->Env, jRegType, 0, (jsize)strlen(regType), (const jbyte *) regType);
530
- jbyteArray jDomain = (*pContext->Env)->NewByteArray(pContext->Env, (jsize)strlen(domain));
531
- (*pContext->Env)->SetByteArrayRegion (pContext->Env, jDomain, 0, (jsize)strlen(domain), (const jbyte *) domain);
532
-
533
- (*pContext->Env)->CallVoidMethod( pContext->Env, pContext->ClientObj, pContext->Callback,
534
- pContext->JavaObj, flags,jServiceName, jRegType, jDomain);
535
- (*pContext->Env)->DeleteLocalRef( pContext->Env, jServiceName);
536
- (*pContext->Env)->DeleteLocalRef( pContext->Env, jRegType);
537
- (*pContext->Env)->DeleteLocalRef( pContext->Env, jDomain);
570
+ jbyteArray jServiceName = (*env)->NewByteArray(env, (jsize)strlen(serviceName));
571
+ (*env)->SetByteArrayRegion (env, jServiceName, 0, (jsize)strlen(serviceName), (const jbyte *) serviceName);
572
+ jbyteArray jRegType = (*env)->NewByteArray(env, (jsize)strlen(regType));
573
+ (*env)->SetByteArrayRegion (env, jRegType, 0, (jsize)strlen(regType), (const jbyte *) regType);
574
+ jbyteArray jDomain = (*env)->NewByteArray(env, (jsize)strlen(domain));
575
+ (*env)->SetByteArrayRegion (env, jDomain, 0, (jsize)strlen(domain), (const jbyte *) domain);
576
+
577
+ (*env)->CallVoidMethod( env, clientRef, callback,
578
+ javaObj, flags,jServiceName, jRegType, jDomain);
579
+ (*env)->DeleteLocalRef( env, jServiceName);
580
+ (*env)->DeleteLocalRef( env, jRegType);
581
+ (*env)->DeleteLocalRef( env, jDomain);
538
582
  }
539
583
  else
540
- ReportError( pContext->Env, pContext->ClientObj, pContext->JavaObj, errorCode);
584
+ ReportError( env, clientRef, javaObj, errorCode);
541
585
  }
542
586
  TeardownCallbackState();
543
587
  }
@@ -579,9 +623,11 @@ JNIEXPORT jint JNICALL Java_com_github_druk_dnssd_AppleRegistration_BeginRegiste
579
623
  pBytes = txtRecord ? (*pEnv)->GetByteArrayElements( pEnv, txtRecord, NULL) : NULL;
580
624
  numBytes = txtRecord ? (*pEnv)->GetArrayLength( pEnv, txtRecord) : 0;
581
625
 
626
+ LOCK_CORE();
582
627
  err = DNSServiceRegister( &pContext->ServiceRef, flags, ifIndex, servStr, regStr,
583
628
  domainStr, hostStr, portBits,
584
629
  numBytes, pBytes, ServiceRegisterReply, pContext);
630
+ UNLOCK_CORE();
585
631
  if ( err == kDNSServiceErr_NoError)
586
632
  {
587
633
  (*pEnv)->SetLongField(pEnv, pThis, contextField, (long) pContext);
@@ -622,7 +668,9 @@ JNIEXPORT jint JNICALL Java_com_github_druk_dnssd_AppleRegistration_AddRecord( J
622
668
  pBytes = (*pEnv)->GetByteArrayElements( pEnv, rData, NULL);
623
669
  numBytes = (*pEnv)->GetArrayLength( pEnv, rData);
624
670
 
671
+ LOCK_CORE();
625
672
  err = DNSServiceAddRecord( pContext->ServiceRef, &recRef, flags, rrType, numBytes, pBytes, ttl);
673
+ UNLOCK_CORE();
626
674
  if ( err == kDNSServiceErr_NoError)
627
675
  {
628
676
  (*pEnv)->SetLongField(pEnv, destObj, recField, (long) recRef);
@@ -662,7 +710,9 @@ JNIEXPORT jint JNICALL Java_com_github_druk_dnssd_AppleDNSRecord_Update( JNIEnv
662
710
  pBytes = (*pEnv)->GetByteArrayElements( pEnv, rData, NULL);
663
711
  numBytes = (*pEnv)->GetArrayLength( pEnv, rData);
664
712
 
713
+ LOCK_CORE();
665
714
  err = DNSServiceUpdateRecord( pContext->ServiceRef, recRef, flags, numBytes, pBytes, ttl);
715
+ UNLOCK_CORE();
666
716
 
667
717
  if ( pBytes != NULL)
668
718
  (*pEnv)->ReleaseByteArrayElements( pEnv, rData, pBytes, 0);
@@ -692,7 +742,9 @@ JNIEXPORT jint JNICALL Java_com_github_druk_dnssd_AppleDNSRecord_Remove( JNIEnv
692
742
  if ( pContext == NULL || pContext->ServiceRef == NULL)
693
743
  return kDNSServiceErr_BadParam;
694
744
 
745
+ LOCK_CORE();
695
746
  err = DNSServiceRemoveRecord( pContext->ServiceRef, recRef, 0);
747
+ UNLOCK_CORE();
696
748
 
697
749
  return err;
698
750
  }
@@ -712,7 +764,9 @@ JNIEXPORT jint JNICALL Java_com_github_druk_dnssd_AppleRecordRegistrar_CreateCon
712
764
 
713
765
  if ( pContext != NULL)
714
766
  {
767
+ LOCK_CORE();
715
768
  err = DNSServiceCreateConnection( &pContext->ServiceRef);
769
+ UNLOCK_CORE();
716
770
  if ( err == kDNSServiceErr_NoError)
717
771
  {
718
772
  (*pEnv)->SetLongField(pEnv, pThis, contextField, (long) pContext);
@@ -740,18 +794,24 @@ static void DNSSD_API RegisterRecordReply( DNSServiceRef sdRef _UNUSED,
740
794
 
741
795
  SetupCallbackState( &pContext->Env);
742
796
 
743
- if ( pContext->ClientObj != NULL && pContext->Callback != NULL)
797
+ // Copies: the Java callback may stop this operation, after which pContext is freed.
798
+ JNIEnv *env = pContext->Env;
799
+ jobject javaObj = pContext->JavaObj;
800
+ jobject clientRef = pContext->ClientObj;
801
+ jmethodID callback = pContext->Callback;
802
+
803
+ if ( clientRef != NULL && callback != NULL)
744
804
  {
745
805
  if ( errorCode == kDNSServiceErr_NoError)
746
806
  {
747
- (*pContext->Env)->CallVoidMethod( pContext->Env, pContext->ClientObj, pContext->Callback,
807
+ (*env)->CallVoidMethod( env, clientRef, callback,
748
808
  regEnvelope->RecordObj, flags);
749
809
  }
750
810
  else
751
- ReportError( pContext->Env, pContext->ClientObj, pContext->JavaObj, errorCode);
811
+ ReportError( env, clientRef, javaObj, errorCode);
752
812
  }
753
813
 
754
- (*pContext->Env)->DeleteWeakGlobalRef( pContext->Env, regEnvelope->RecordObj);
814
+ (*env)->DeleteWeakGlobalRef( env, regEnvelope->RecordObj);
755
815
  free( regEnvelope);
756
816
 
757
817
  TeardownCallbackState();
@@ -787,9 +847,11 @@ JNIEXPORT jint JNICALL Java_com_github_druk_dnssd_AppleRecordRegistrar_RegisterR
787
847
  pBytes = (*pEnv)->GetByteArrayElements( pEnv, rData, NULL);
788
848
  numBytes = (*pEnv)->GetArrayLength( pEnv, rData);
789
849
 
850
+ LOCK_CORE();
790
851
  err = DNSServiceRegisterRecord( pContext->ServiceRef, &recRef, flags, ifIndex,
791
852
  nameStr, rrType, rrClass, numBytes, pBytes, ttl,
792
853
  RegisterRecordReply, regEnvelope);
854
+ UNLOCK_CORE();
793
855
 
794
856
  if ( err == kDNSServiceErr_NoError)
795
857
  {
@@ -822,25 +884,31 @@ static void DNSSD_API ServiceQueryReply( DNSServiceRef sdRef _UNUSED, DNSService
822
884
 
823
885
  SetupCallbackState( &pContext->Env);
824
886
 
825
- if ( pContext->ClientObj != NULL && pContext->Callback != NULL &&
826
- NULL != ( rDataObj = (*pContext->Env)->NewByteArray( pContext->Env, rdlen)))
887
+ // Copies: the Java callback may stop this operation, after which pContext is freed.
888
+ JNIEnv *env = pContext->Env;
889
+ jobject javaObj = pContext->JavaObj;
890
+ jobject clientRef = pContext->ClientObj;
891
+ jmethodID callback = pContext->Callback;
892
+
893
+ if ( clientRef != NULL && callback != NULL &&
894
+ NULL != ( rDataObj = (*env)->NewByteArray( env, rdlen)))
827
895
  {
828
896
  if ( errorCode == kDNSServiceErr_NoError)
829
897
  {
830
898
  // Initialize rDataObj with contents of rdata
831
- pBytes = (*pContext->Env)->GetByteArrayElements( pContext->Env, rDataObj, NULL);
899
+ pBytes = (*env)->GetByteArrayElements( env, rDataObj, NULL);
832
900
  memcpy( pBytes, rdata, rdlen);
833
- (*pContext->Env)->ReleaseByteArrayElements( pContext->Env, rDataObj, pBytes, JNI_COMMIT);
834
-
835
- jbyteArray jServiceName = (*pContext->Env)->NewByteArray(pContext->Env, (jsize)strlen(serviceName));
836
- (*pContext->Env)->SetByteArrayRegion (pContext->Env, jServiceName, 0, (jsize)strlen(serviceName), (const jbyte *) serviceName);
837
- (*pContext->Env)->CallVoidMethod( pContext->Env, pContext->ClientObj, pContext->Callback,
838
- pContext->JavaObj, flags, interfaceIndex, jServiceName, rrtype, rrclass, rDataObj, ttl);
839
- (*pContext->Env)->DeleteLocalRef( pContext->Env, jServiceName);
840
- (*pContext->Env)->DeleteLocalRef( pContext->Env, rDataObj);
901
+ (*env)->ReleaseByteArrayElements( env, rDataObj, pBytes, JNI_COMMIT);
902
+
903
+ jbyteArray jServiceName = (*env)->NewByteArray(env, (jsize)strlen(serviceName));
904
+ (*env)->SetByteArrayRegion (env, jServiceName, 0, (jsize)strlen(serviceName), (const jbyte *) serviceName);
905
+ (*env)->CallVoidMethod( env, clientRef, callback,
906
+ javaObj, flags, interfaceIndex, jServiceName, rrtype, rrclass, rDataObj, ttl);
907
+ (*env)->DeleteLocalRef( env, jServiceName);
908
+ (*env)->DeleteLocalRef( env, rDataObj);
841
909
  }
842
910
  else
843
- ReportError( pContext->Env, pContext->ClientObj, pContext->JavaObj, errorCode);
911
+ ReportError( env, clientRef, javaObj, errorCode);
844
912
  }
845
913
  TeardownCallbackState();
846
914
  }
@@ -863,8 +931,10 @@ JNIEXPORT jint JNICALL Java_com_github_druk_dnssd_AppleQuery_CreateQuery( JNIEnv
863
931
  {
864
932
  const char *servStr = SafeGetUTFChars( pEnv, serviceName);
865
933
 
934
+ LOCK_CORE();
866
935
  err = DNSServiceQueryRecord( &pContext->ServiceRef, flags, ifIndex, servStr,
867
936
  rrtype, rrclass, ServiceQueryReply, pContext);
937
+ UNLOCK_CORE();
868
938
  if ( err == kDNSServiceErr_NoError)
869
939
  {
870
940
  (*pEnv)->SetLongField(pEnv, pThis, contextField, (long) pContext);
@@ -886,19 +956,26 @@ static void DNSSD_API DomainEnumReply( DNSServiceRef sdRef _UNUSED, DNSServiceFl
886
956
 
887
957
  SetupCallbackState( &pContext->Env);
888
958
 
889
- if ( pContext->ClientObj != NULL && pContext->Callback != NULL)
959
+ // Copies: the Java callback may stop this operation, after which pContext is freed.
960
+ JNIEnv *env = pContext->Env;
961
+ jobject javaObj = pContext->JavaObj;
962
+ jobject clientRef = pContext->ClientObj;
963
+ jmethodID callback = pContext->Callback;
964
+ jmethodID callback2 = pContext->Callback2;
965
+
966
+ if ( clientRef != NULL && callback != NULL)
890
967
  {
891
968
  if ( errorCode == kDNSServiceErr_NoError)
892
969
  {
893
- jbyteArray jReplyDomain = (*pContext->Env)->NewByteArray(pContext->Env, (jsize)strlen(replyDomain));
894
- (*pContext->Env)->SetByteArrayRegion (pContext->Env, jReplyDomain, 0, (jsize)strlen(replyDomain), (const jbyte *) replyDomain);
895
- (*pContext->Env)->CallVoidMethod( pContext->Env, pContext->ClientObj,
896
- ( flags & kDNSServiceFlagsAdd) != 0 ? pContext->Callback : pContext->Callback2,
897
- pContext->JavaObj, flags, interfaceIndex, jReplyDomain);
898
- (*pContext->Env)->DeleteLocalRef( pContext->Env, jReplyDomain);
970
+ jbyteArray jReplyDomain = (*env)->NewByteArray(env, (jsize)strlen(replyDomain));
971
+ (*env)->SetByteArrayRegion (env, jReplyDomain, 0, (jsize)strlen(replyDomain), (const jbyte *) replyDomain);
972
+ (*env)->CallVoidMethod( env, clientRef,
973
+ ( flags & kDNSServiceFlagsAdd) != 0 ? callback : callback2,
974
+ javaObj, flags, interfaceIndex, jReplyDomain);
975
+ (*env)->DeleteLocalRef( env, jReplyDomain);
899
976
  }
900
977
  else
901
- ReportError( pContext->Env, pContext->ClientObj, pContext->JavaObj, errorCode);
978
+ ReportError( env, clientRef, javaObj, errorCode);
902
979
  }
903
980
  TeardownCallbackState();
904
981
  }
@@ -923,8 +1000,10 @@ JNIEXPORT jint JNICALL Java_com_github_druk_dnssd_AppleDomainEnum_BeginEnum( JNI
923
1000
  (*pEnv)->GetObjectClass( pEnv, pContext->ClientObj),
924
1001
  "domainLost", "(Lcom/github/druk/dnssd/DNSSDService;II[B)V");
925
1002
 
1003
+ LOCK_CORE();
926
1004
  err = DNSServiceEnumerateDomains( &pContext->ServiceRef, flags, ifIndex,
927
1005
  DomainEnumReply, pContext);
1006
+ UNLOCK_CORE();
928
1007
  if ( err == kDNSServiceErr_NoError)
929
1008
  {
930
1009
  (*pEnv)->SetLongField(pEnv, pThis, contextField, (long) pContext);
@@ -973,7 +1052,9 @@ JNIEXPORT jint JNICALL Java_com_github_druk_dnssd_AppleDNSSD_ReconfirmRecord( JN
973
1052
  pBytes = (*pEnv)->GetByteArrayElements( pEnv, rdata, NULL);
974
1053
  numBytes = (*pEnv)->GetArrayLength( pEnv, rdata);
975
1054
 
1055
+ LOCK_CORE();
976
1056
  err = DNSServiceReconfirmRecord( flags, ifIndex, nameStr, rrtype, rrclass, numBytes, pBytes);
1057
+ UNLOCK_CORE();
977
1058
 
978
1059
  if ( pBytes != NULL)
979
1060
  (*pEnv)->ReleaseByteArrayElements( pEnv, rdata, pBytes, 0);
@@ -51,6 +51,7 @@ extern int daemon(int, int);
51
51
 
52
52
  #include "mDNSEmbeddedAPI.h"
53
53
  #include "mDNSPosix.h"
54
+ #include "DNSCommon.h" // mDNS_Lock / mDNS_Unlock, for Reconfigure()
54
55
  #include "mDNSUNP.h" // For daemon()
55
56
  #include "uds_daemon.h"
56
57
  #include "PlatformCommon.h"
@@ -67,7 +68,7 @@ static domainname DynDNSHostname;
67
68
  static CacheEntity gRRCache[RR_CACHE_SIZE];
68
69
  static mDNS_PlatformSupport PlatformStorage;
69
70
 
70
- int stopNow = 0;
71
+ volatile sig_atomic_t stopNow = 0; // Set from another thread by stopLoop()
71
72
 
72
73
  mDNSlocal void mDNS_StatusCallback(mDNS *const m, mStatus result)
73
74
  {
@@ -101,8 +102,10 @@ static void Reconfigure(mDNS *m)
101
102
  mDNSAddr DynDNSIP;
102
103
  const mDNSAddr dummy = { mDNSAddrType_IPv4, { { { 1, 1, 1, 1 } } } };;
103
104
  mDNS_SetPrimaryInterfaceInfo(m, NULL, NULL, NULL);
105
+ mDNS_Lock(m); // mDNS_AddDNSServer() expects the lock to be held
104
106
  if (ParseDNSServers(m, uDNS_SERVERS_FILE) < 0)
105
107
  LogMsg("Unable to parse DNS server list. Unicast DNS-SD unavailable");
108
+ mDNS_Unlock(m);
106
109
  ReadDDNSSettingsFromConfFile(m, CONFIG_FILE, &DynDNSHostname, &DynDNSZone, NULL);
107
110
  mDNSPlatformSourceAddrForDest(&DynDNSIP, &dummy);
108
111
  if (DynDNSHostname.c[0]) mDNS_AddDynDNSHostName(m, &DynDNSHostname, NULL, NULL);
@@ -263,7 +266,9 @@ int loop(){
263
266
  mStatus err;
264
267
  Reconfigure(&mDNSStorage);
265
268
 
269
+ mDNSPosixEmbeddedLoopStarting();
266
270
  err = MainLoop(&mDNSStorage);
271
+ mDNSPosixEmbeddedLoopStopping();
267
272
 
268
273
  LogMsg("%s stopping", mDNSResponderVersionString);
269
274
 
@@ -279,6 +284,19 @@ int loop(){
279
284
  void stopLoop()
280
285
  {
281
286
  stopNow = 1;
287
+ mDNSPosixEmbeddedWakeLoop(); // It may be sleeping in select() until its next timer
288
+ }
289
+
290
+ // For JNISupport.c: hold the core lock around work on an operation's data structures
291
+ // that must not interleave with the event loop thread (callbacks run on that thread).
292
+ void lockCore()
293
+ {
294
+ mDNSPlatformLock(&mDNSStorage);
295
+ }
296
+
297
+ void unlockCore()
298
+ {
299
+ mDNSPlatformUnlock(&mDNSStorage);
282
300
  }
283
301
  #endif
284
302
 
@@ -505,7 +505,7 @@ mDNSexport int ParseDNSServers(mDNS *m, const char *filePath)
505
505
  {
506
506
  char line[256];
507
507
  char nameserver[16];
508
- char keyword[10];
508
+ char keyword[11]; // "%10s" writes up to 10 characters and the terminator
509
509
  int numOfServers = 0;
510
510
  FILE *fp = fopen(filePath, "r");
511
511
  if (fp == NULL) return -1;
@@ -1333,6 +1333,107 @@ mDNSexport mStatus mDNSPlatformPosixRefreshInterfaceList(mDNS *const m)
1333
1333
  #pragma mark ***** Locking
1334
1334
  #endif
1335
1335
 
1336
+ #ifdef EMBEDDED
1337
+
1338
+ // The embedded build enters mDNS core from several threads: the DNS-SDEmbedded thread
1339
+ // runs mDNS_Execute() and delivers the callbacks, while the DNSService* calls, and in
1340
+ // particular DNSServiceRefDeallocate(), arrive on whatever thread the client uses.
1341
+ // mDNS core relies on mDNSPlatformLock() for mutual exclusion between those, so here it
1342
+ // is a real lock. It is recursive because the core holds it while it calls back into the
1343
+ // client, and the client may call back into the core from there (see mDNS_DropLockBeforeCallback).
1344
+
1345
+ #include <pthread.h>
1346
+
1347
+ static pthread_mutex_t gCoreLock;
1348
+ static pthread_once_t gCoreLockOnce = PTHREAD_ONCE_INIT;
1349
+
1350
+ // The event loop sleeps in select() until the core's next scheduled event. A DNSService* call
1351
+ // from another thread changes that schedule (a new question has to be answered now), so when
1352
+ // such a call releases the lock, the loop is woken through a pipe it listens to. Otherwise the
1353
+ // call would wait for the next timer or the next packet from the network.
1354
+ // The pipe is created together with the lock and lives as long as the process, so that the
1355
+ // write in mDNSPlatformUnlock() can never hit a descriptor another thread has closed and reused.
1356
+ static pthread_t gLoopThread;
1357
+ static int gLoopThreadSet = 0;
1358
+ static int gWakePipe[2] = { -1, -1 };
1359
+
1360
+ static void WakeCallback(int fd, short filter, void *context)
1361
+ {
1362
+ char buffer[64];
1363
+ (void) filter; // Unused
1364
+ (void) context; // Unused
1365
+ while (read(fd, buffer, sizeof(buffer)) > 0) { }
1366
+ }
1367
+
1368
+ static void InitCoreLockAndWakePipe(void)
1369
+ {
1370
+ pthread_mutexattr_t attr;
1371
+ pthread_mutexattr_init(&attr);
1372
+ pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
1373
+ pthread_mutex_init(&gCoreLock, &attr);
1374
+ pthread_mutexattr_destroy(&attr);
1375
+ if (pipe(gWakePipe) == 0)
1376
+ {
1377
+ fcntl(gWakePipe[0], F_SETFL, fcntl(gWakePipe[0], F_GETFL, 0) | O_NONBLOCK);
1378
+ fcntl(gWakePipe[1], F_SETFL, fcntl(gWakePipe[1], F_GETFL, 0) | O_NONBLOCK);
1379
+ }
1380
+ else
1381
+ {
1382
+ gWakePipe[0] = gWakePipe[1] = -1;
1383
+ LogMsg("mDNSPosix: pipe() failed (%d), other threads will not wake the event loop", errno);
1384
+ }
1385
+ }
1386
+
1387
+ mDNSexport void mDNSPosixEmbeddedLoopStarting(void)
1388
+ {
1389
+ mDNSPlatformLock(mDNSNULL);
1390
+ gLoopThread = pthread_self();
1391
+ gLoopThreadSet = 1;
1392
+ if (gWakePipe[0] >= 0)
1393
+ {
1394
+ mStatus err = mDNSPosixAddFDToEventLoop(gWakePipe[0], WakeCallback, mDNSNULL);
1395
+ if (err) LogMsg("mDNSPosix: cannot listen to the wake-up pipe (%d), other threads will not wake the event loop", err);
1396
+ }
1397
+ mDNSPlatformUnlock(mDNSNULL);
1398
+ }
1399
+
1400
+ mDNSexport void mDNSPosixEmbeddedLoopStopping(void)
1401
+ {
1402
+ mDNSPlatformLock(mDNSNULL);
1403
+ if (gWakePipe[0] >= 0)
1404
+ {
1405
+ mDNSPosixRemoveFDFromEventLoop(gWakePipe[0]);
1406
+ WakeCallback(gWakePipe[0], 0, mDNSNULL); // Drain, for the next loop
1407
+ }
1408
+ gLoopThreadSet = 0;
1409
+ mDNSPlatformUnlock(mDNSNULL);
1410
+ }
1411
+
1412
+ mDNSexport void mDNSPosixEmbeddedWakeLoop(void)
1413
+ {
1414
+ if (gWakePipe[1] >= 0)
1415
+ {
1416
+ if (write(gWakePipe[1], "", 1) < 0) { } // A full pipe means the loop is about to wake anyway
1417
+ }
1418
+ }
1419
+
1420
+ mDNSexport void mDNSPlatformLock (const mDNS *const m)
1421
+ {
1422
+ (void) m; // Unused
1423
+ pthread_once(&gCoreLockOnce, InitCoreLockAndWakePipe);
1424
+ pthread_mutex_lock(&gCoreLock);
1425
+ }
1426
+
1427
+ mDNSexport void mDNSPlatformUnlock (const mDNS *const m)
1428
+ {
1429
+ (void) m; // Unused
1430
+ int fromLoopThread = gLoopThreadSet && pthread_equal(pthread_self(), gLoopThread);
1431
+ pthread_mutex_unlock(&gCoreLock);
1432
+ if (!fromLoopThread) mDNSPosixEmbeddedWakeLoop();
1433
+ }
1434
+
1435
+ #else
1436
+
1336
1437
  // On the Posix platform, locking is a no-op because we only ever enter
1337
1438
  // mDNS core on the main thread.
1338
1439
 
@@ -1350,6 +1451,8 @@ mDNSexport void mDNSPlatformUnlock (const mDNS *const m)
1350
1451
  (void) m; // Unused
1351
1452
  }
1352
1453
 
1454
+ #endif // EMBEDDED
1455
+
1353
1456
  #if COMPILER_LIKES_PRAGMA_MARK
1354
1457
  #pragma mark ***** Strings
1355
1458
  #endif
@@ -81,6 +81,13 @@ extern mStatus mDNSPosixListenForSignalInEventLoop( int signum);
81
81
  extern mStatus mDNSPosixIgnoreSignalInEventLoop( int signum);
82
82
  extern mStatus mDNSPosixRunEventLoopOnce( mDNS *m, const struct timeval *pTimeout, sigset_t *pSignalsReceived, mDNSBool *pDataDispatched);
83
83
 
84
+ #ifdef EMBEDDED
85
+ // The embedded build is driven from several threads, see the locking section of mDNSPosix.c.
86
+ extern void mDNSPosixEmbeddedLoopStarting(void); // Call on the event loop thread, before running the loop
87
+ extern void mDNSPosixEmbeddedLoopStopping(void); // Call on the event loop thread, after the loop returned
88
+ extern void mDNSPosixEmbeddedWakeLoop(void); // Make the loop re-read the schedule and stop flags
89
+ #endif
90
+
84
91
  #ifdef __cplusplus
85
92
  }
86
93
  #endif
@@ -701,10 +701,10 @@ DNSServiceErrorType DNSServiceQueryRecord
701
701
  x->callback = callback;
702
702
  x->context = context;
703
703
 
704
- x->q.ThisQInterval = -1; // So that DNSServiceResolveDispose() knows whether to cancel this question
704
+ x->q.ThisQInterval = -1; // So that DNSServiceQueryRecordDispose() knows whether to cancel this question
705
705
  x->q.InterfaceID = mDNSPlatformInterfaceIDfromInterfaceIndex(&mDNSStorage, interfaceIndex);
706
706
  x->q.Target = zeroAddr;
707
- MakeDomainNameFromDNSNameString(&x->q.qname, fullname);
707
+ if (!MakeDomainNameFromDNSNameString(&x->q.qname, fullname)) { mDNSPlatformMemFree(x); err = mStatus_BadParamErr; errormsg = "MakeDomainNameFromDNSNameString"; goto fail; }
708
708
  x->q.qtype = rrtype;
709
709
  x->q.qclass = rrclass;
710
710
  x->q.LongLived = (flags & kDNSServiceFlagsLongLivedQuery) != 0;
@@ -721,7 +721,7 @@ DNSServiceErrorType DNSServiceQueryRecord
721
721
  x->q.QuestionContext = x;
722
722
 
723
723
  err = mDNS_StartQuery(&mDNSStorage, &x->q);
724
- if (err) { DNSServiceResolveDispose((mDNS_DirectOP*)x); errormsg = "mDNS_StartQuery"; goto fail; }
724
+ if (err) { DNSServiceQueryRecordDispose((mDNS_DirectOP*)x); errormsg = "mDNS_StartQuery"; goto fail; }
725
725
 
726
726
  // Succeeded: Wrap up and return
727
727
  *sdRef = (DNSServiceRef)x;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-zeroconf",
3
- "version": "0.17.1",
3
+ "version": "0.17.3",
4
4
  "description": "A Zeroconf discovery utility for react-native",
5
5
  "main": "dist",
6
6
  "types": "index.d.ts",
@@ -57,6 +57,7 @@
57
57
  "!android/src/main/jni/mdnsresponder/mDNSShared/mDNSResponder.8",
58
58
  "!android/build",
59
59
  "!android/.gradle",
60
+ "!android/src/androidTest",
60
61
  "windows",
61
62
  "!windows/**/*.user",
62
63
  "!windows/**/obj",