The default implementation uses JmDNS and binds UDP port 5353. On platforms where a system + * mDNS responder already owns that port (notably macOS {@code mDNSResponder}), use {@link + * io.github.hapjava.server.impl.dnssd.DnssdHomekitAdvertiser} instead, which registers through the + * native Bonjour / DNS-SD API without binding 5353. + * + * @see io.github.hapjava.server.impl.jmdns.JmdnsHomekitAdvertiser + * @see io.github.hapjava.server.impl.dnssd.DnssdHomekitAdvertiser + * @see HomekitAdvertisers + * @author Karel Goderis + */ +public interface HomekitAdvertiser { + + /** + * Start advertising the accessory. + * + * @param label instance name shown in Home / Discovery + * @param category HomeKit accessory category ({@code ci} TXT key) + * @param mac accessory id ({@code id} TXT key), typically a MAC-like string + * @param port HAP TCP listen port + * @param configurationIndex configuration number ({@code c#} TXT key) + * @param setupId setup id used for the {@code sh} TXT hash + */ + void advertise( + String label, int category, String mac, int port, int configurationIndex, String setupId) + throws Exception; + + /** Stop advertising and release native resources. */ + void stop(); + + /** Update discoverability ({@code sf} TXT). Re-registers when already advertising. */ + void setDiscoverable(boolean discoverable) throws IOException; + + /** Update accessory id ({@code id} TXT). Re-registers when already advertising. */ + void setMac(String mac) throws IOException; + + /** Update configuration index ({@code c#} TXT). Re-registers when already advertising. */ + void setConfigurationIndex(int revision) throws IOException; +} diff --git a/src/main/java/io/github/hapjava/server/HomekitAdvertisers.java b/src/main/java/io/github/hapjava/server/HomekitAdvertisers.java new file mode 100644 index 000000000..28c3792e7 --- /dev/null +++ b/src/main/java/io/github/hapjava/server/HomekitAdvertisers.java @@ -0,0 +1,122 @@ +package io.github.hapjava.server; + +import io.github.hapjava.server.impl.dnssd.DnssdHomekitAdvertiser; +import io.github.hapjava.server.impl.jmdns.JmdnsHomekitAdvertiser; +import java.io.IOException; +import java.net.InetAddress; +import javax.jmdns.JmDNS; + +/** + * Factories for {@link HomekitAdvertiser} implementations. + * + *
Callers (e.g. openHAB) should choose the advertiser explicitly: use {@link #jmdns(JmDNS)} to + * share an existing JmDNS instance, {@link #jmdns(InetAddress)} for a dedicated JmDNS socket, or + * {@link #nativeDnssd()} / {@link #nativeDnssd(InetAddress)} for Bonjour without binding UDP 5353. + * Do not rely on HAP-Java to pick a policy automatically. + * + *
{@code
+ * // Force JmDNS (e.g. share openHAB's instance):
+ * HomekitAdvertiser adv = HomekitAdvertisers.jmdns(sharedJmDNS);
+ *
+ * // Native DNS-SD on the interface that owns the HAP bind address (SRV host = system default):
+ * HomekitAdvertiser adv = HomekitAdvertisers.nativeDnssd(InetAddress.getByName("192.168.0.6"));
+ * }
+ *
+ * @author Karel Goderis
+ */
+public final class HomekitAdvertisers {
+
+ private HomekitAdvertisers() {}
+
+ /**
+ * Classic JmDNS advertiser (binds UDP 5353 unless sharing an existing {@link JmDNS}).
+ *
+ * @param jmdns existing JmDNS instance to register with
+ * @return a JmDNS-backed advertiser
+ */
+ public static HomekitAdvertiser jmdns(JmDNS jmdns) {
+ return new JmdnsHomekitAdvertiser(jmdns);
+ }
+
+ /**
+ * Classic JmDNS advertiser that creates its own multicast socket on {@code localAddress}.
+ *
+ * @param localAddress address used to create the JmDNS instance
+ * @return a JmDNS-backed advertiser
+ * @throws IOException if JmDNS cannot be created on {@code localAddress}
+ */
+ public static HomekitAdvertiser jmdns(InetAddress localAddress) throws IOException {
+ return new JmdnsHomekitAdvertiser(localAddress);
+ }
+
+ /**
+ * Native DNS-SD / Bonjour advertiser on all interfaces ({@code interfaceIndex =
+ * 0}, default host). Prefer {@link #nativeDnssd(InetAddress)} when the HAP TCP listener is bound
+ * to a single LAN address.
+ *
+ * Requires the optional JNA dependency on the classpath. + * + * @return a native DNS-SD advertiser + * @throws UnsupportedOperationException if JNA or the native library cannot be loaded + */ + public static HomekitAdvertiser nativeDnssd() { + if (!DnssdHomekitAdvertiser.isSupported()) { + throw new UnsupportedOperationException( + "Native DNS-SD (dns_sd / Bonjour) is not available on this platform/JVM"); + } + return new DnssdHomekitAdvertiser(); + } + + /** + * Native DNS-SD / Bonjour advertiser scoped to the interface that owns {@code localAddress}. + * Registration uses that interface index when resolvable; SRV host stays {@code null} (system + * hostname). Passing an IP literal as DNS-SD host is incorrect — it yields an SRV target with no + * A record and breaks Home pairing. + * + * @param localAddress LAN address the HAP TCP server listens on + * @return a native DNS-SD advertiser + * @throws UnsupportedOperationException if JNA or the native library cannot be loaded + */ + public static HomekitAdvertiser nativeDnssd(InetAddress localAddress) { + if (!DnssdHomekitAdvertiser.isSupported()) { + throw new UnsupportedOperationException( + "Native DNS-SD (dns_sd / Bonjour) is not available on this platform/JVM"); + } + return new DnssdHomekitAdvertiser(localAddress); + } + + /** + * Convenience: prefer native DNS-SD when the library loads, otherwise JmDNS on {@code + * localAddress}. + * + *
Applications should prefer an explicit {@link #jmdns(JmDNS)} / {@link + * #nativeDnssd(InetAddress)} choice (policy belongs in openHAB / the integrator). This + * helper only probes whether the native library loads; it does not verify that + * registration will succeed on the network. + * + * @param localAddress address used for native scoping or JmDNS fallback + * @return a native DNS-SD advertiser when the library loads, otherwise a JmDNS-backed advertiser + * @throws IOException if native DNS-SD is unavailable and JmDNS cannot be created + * @deprecated Prefer {@link #jmdns(JmDNS)}, {@link #jmdns(InetAddress)}, or {@link + * #nativeDnssd(InetAddress)} so the application chooses the advertiser. + */ + @Deprecated + public static HomekitAdvertiser auto(InetAddress localAddress) throws IOException { + if (DnssdHomekitAdvertiser.isSupported()) { + return new DnssdHomekitAdvertiser(localAddress); + } + return new JmdnsHomekitAdvertiser(localAddress); + } + + /** + * Whether {@link #nativeDnssd()} can be constructed on this JVM/OS. + * + *
Only checks that JNA and the platform DNS-SD library load. It does not register a + * service or prove mDNS is reachable on the LAN. + * + * @return {@code true} if the platform DNS-SD library loaded successfully + */ + public static boolean isNativeDnssdSupported() { + return DnssdHomekitAdvertiser.isSupported(); + } +} diff --git a/src/main/java/io/github/hapjava/server/impl/HomekitRoot.java b/src/main/java/io/github/hapjava/server/impl/HomekitRoot.java index 36bfeefa2..1721be95f 100644 --- a/src/main/java/io/github/hapjava/server/impl/HomekitRoot.java +++ b/src/main/java/io/github/hapjava/server/impl/HomekitRoot.java @@ -3,6 +3,7 @@ import io.github.hapjava.accessories.Bridge; import io.github.hapjava.accessories.HomekitAccessory; import io.github.hapjava.server.HomekitAccessoryCategories; +import io.github.hapjava.server.HomekitAdvertiser; import io.github.hapjava.server.HomekitAuthInfo; import io.github.hapjava.server.HomekitWebHandler; import io.github.hapjava.server.impl.connections.HomekitClientConnectionFactoryImpl; @@ -27,7 +28,7 @@ public class HomekitRoot { private static final Logger logger = LoggerFactory.getLogger(HomekitRoot.class); private static final int DEFAULT_ACCESSORY_CATEGORY = HomekitAccessoryCategories.OTHER; - private final JmdnsHomekitAdvertiser advertiser; + private final HomekitAdvertiser advertiser; private final HomekitWebHandler webHandler; private final HomekitAuthInfo authInfo; private final String label; @@ -60,7 +61,7 @@ public class HomekitRoot { int category, HomekitWebHandler webHandler, HomekitAuthInfo authInfo, - JmdnsHomekitAdvertiser advertiser) + HomekitAdvertiser advertiser) throws IOException { this.advertiser = advertiser; this.webHandler = webHandler; diff --git a/src/main/java/io/github/hapjava/server/impl/HomekitServer.java b/src/main/java/io/github/hapjava/server/impl/HomekitServer.java index 7728896a2..e16c3518b 100644 --- a/src/main/java/io/github/hapjava/server/impl/HomekitServer.java +++ b/src/main/java/io/github/hapjava/server/impl/HomekitServer.java @@ -1,6 +1,7 @@ package io.github.hapjava.server.impl; import io.github.hapjava.accessories.HomekitAccessory; +import io.github.hapjava.server.HomekitAdvertiser; import io.github.hapjava.server.HomekitAuthInfo; import io.github.hapjava.server.impl.http.impl.HomekitHttpServer; import java.io.IOException; @@ -23,6 +24,9 @@ * same values on each start of your application or HomeKit will fail to recognize your device as * being the same. * + *
On macOS, prefer constructing with an explicit {@link HomekitAdvertiser} from {@link + * io.github.hapjava.server.HomekitAdvertisers#nativeDnssd()} so advertising does not bind UDP 5353. + * * @author Andy Lintner */ public class HomekitServer { @@ -30,12 +34,15 @@ public class HomekitServer { private final HomekitHttpServer http; private final InetAddress localAddress; private final JmDNS jmdns; + private final HomekitAdvertiser advertiser; /** * Constructor. Contains an argument indicating the number of threads to use in the http server. * The other constructors default this to the number of available processors, however you may * increase this in an environment with many users and/or blocking accessory implementations. * + *
Advertising uses a dedicated JmDNS instance (binds UDP 5353).
+ *
* @param localAddress local address to bind to.
* @param port local port to bind to.
* @param nThreads number of threads to use in the http server
@@ -44,6 +51,28 @@ public class HomekitServer {
public HomekitServer(InetAddress localAddress, int port, int nThreads) throws IOException {
this.localAddress = localAddress;
this.jmdns = null;
+ this.advertiser = null;
+ http = new HomekitHttpServer(localAddress, port, nThreads);
+ }
+
+ /**
+ * Constructor that uses a caller-supplied advertiser (e.g. native DNS-SD) and does not create
+ * JmDNS.
+ *
+ * @param localAddress local address for the HAP HTTP server
+ * @param port local TCP port
+ * @param nThreads HTTP worker threads
+ * @param advertiser mDNS / DNS-SD advertiser (must not be null)
+ */
+ public HomekitServer(
+ InetAddress localAddress, int port, int nThreads, HomekitAdvertiser advertiser)
+ throws IOException {
+ if (advertiser == null) {
+ throw new IllegalArgumentException("advertiser must not be null");
+ }
+ this.localAddress = localAddress;
+ this.jmdns = null;
+ this.advertiser = advertiser;
http = new HomekitHttpServer(localAddress, port, nThreads);
}
@@ -58,6 +87,7 @@ public HomekitServer(InetAddress localAddress, int port, int nThreads) throws IO
public HomekitServer(JmDNS jmdns, int port, int nThreads) throws IOException {
this.jmdns = jmdns;
this.localAddress = null;
+ this.advertiser = null;
http = new HomekitHttpServer(jmdns.getInetAddress(), port, nThreads);
}
@@ -72,6 +102,15 @@ public HomekitServer(InetAddress localAddress, int port) throws IOException {
this(localAddress, port, Runtime.getRuntime().availableProcessors());
}
+ /**
+ * HAP HTTP on {@code localAddress}:{@code port} with an explicit advertiser (native DNSSD or
+ * shared JmDNS wrapper).
+ */
+ public HomekitServer(InetAddress localAddress, int port, HomekitAdvertiser advertiser)
+ throws IOException {
+ this(localAddress, port, Runtime.getRuntime().availableProcessors(), advertiser);
+ }
+
/**
* Constructor
*
@@ -111,7 +150,10 @@ public void stop() {
public HomekitStandaloneAccessoryServer createStandaloneAccessory(
HomekitAuthInfo authInfo, HomekitAccessory accessory)
throws IOException, ExecutionException, InterruptedException {
- if (jmdns != null) {
+ if (advertiser != null) {
+ return new HomekitStandaloneAccessoryServer(
+ accessory, http, localAddress, authInfo, advertiser);
+ } else if (jmdns != null) {
return new HomekitStandaloneAccessoryServer(accessory, http, jmdns, authInfo);
} else {
return new HomekitStandaloneAccessoryServer(accessory, http, localAddress, authInfo);
@@ -121,7 +163,10 @@ public HomekitStandaloneAccessoryServer createStandaloneAccessory(
public HomekitStandaloneAccessoryServer createStandaloneAccessory(
HomekitAuthInfo authInfo, HomekitAccessory accessory, int category)
throws IOException, ExecutionException, InterruptedException {
- if (jmdns != null) {
+ if (advertiser != null) {
+ return new HomekitStandaloneAccessoryServer(
+ accessory, http, localAddress, authInfo, category, advertiser);
+ } else if (jmdns != null) {
return new HomekitStandaloneAccessoryServer(accessory, http, jmdns, authInfo, category);
} else {
return new HomekitStandaloneAccessoryServer(
@@ -158,7 +203,9 @@ public HomekitRoot createBridge(
String hardwareRevision)
throws IOException {
HomekitRoot root;
- if (jmdns != null) {
+ if (advertiser != null) {
+ root = new HomekitRoot(label, category, http, authInfo, advertiser);
+ } else if (jmdns != null) {
root = new HomekitRoot(label, category, http, jmdns, authInfo);
} else {
root = new HomekitRoot(label, category, http, localAddress, authInfo);
diff --git a/src/main/java/io/github/hapjava/server/impl/HomekitStandaloneAccessoryServer.java b/src/main/java/io/github/hapjava/server/impl/HomekitStandaloneAccessoryServer.java
index 4a5dbabee..c0fc23b95 100644
--- a/src/main/java/io/github/hapjava/server/impl/HomekitStandaloneAccessoryServer.java
+++ b/src/main/java/io/github/hapjava/server/impl/HomekitStandaloneAccessoryServer.java
@@ -1,6 +1,8 @@
package io.github.hapjava.server.impl;
import io.github.hapjava.accessories.HomekitAccessory;
+import io.github.hapjava.server.HomekitAccessoryCategories;
+import io.github.hapjava.server.HomekitAdvertiser;
import io.github.hapjava.server.HomekitAuthInfo;
import io.github.hapjava.server.HomekitWebHandler;
import java.io.IOException;
@@ -63,6 +65,35 @@ public class HomekitStandaloneAccessoryServer {
root.addAccessory(accessory);
}
+ HomekitStandaloneAccessoryServer(
+ HomekitAccessory accessory,
+ HomekitWebHandler webHandler,
+ InetAddress localhost,
+ HomekitAuthInfo authInfo,
+ HomekitAdvertiser advertiser)
+ throws UnknownHostException, IOException, ExecutionException, InterruptedException {
+ root =
+ new HomekitRoot(
+ accessory.getName().get(),
+ HomekitAccessoryCategories.OTHER,
+ webHandler,
+ authInfo,
+ advertiser);
+ root.addAccessory(accessory);
+ }
+
+ HomekitStandaloneAccessoryServer(
+ HomekitAccessory accessory,
+ HomekitWebHandler webHandler,
+ InetAddress localhost,
+ HomekitAuthInfo authInfo,
+ int category,
+ HomekitAdvertiser advertiser)
+ throws UnknownHostException, IOException, ExecutionException, InterruptedException {
+ root = new HomekitRoot(accessory.getName().get(), category, webHandler, authInfo, advertiser);
+ root.addAccessory(accessory);
+ }
+
/** Begins advertising and handling requests for this accessory. */
public void start() {
root.start();
diff --git a/src/main/java/io/github/hapjava/server/impl/advertiser/HapDnsTxt.java b/src/main/java/io/github/hapjava/server/impl/advertiser/HapDnsTxt.java
new file mode 100644
index 000000000..632f23498
--- /dev/null
+++ b/src/main/java/io/github/hapjava/server/impl/advertiser/HapDnsTxt.java
@@ -0,0 +1,81 @@
+package io.github.hapjava.server.impl.advertiser;
+
+import static io.github.hapjava.server.impl.crypto.HAPSetupCodeUtils.generateSHA512Hash;
+
+import java.nio.charset.StandardCharsets;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Shared HAP DNS-SD TXT record helpers for JmDNS and native DNSSD advertisers.
+ *
+ * @author Karel Goderis
+ */
+public final class HapDnsTxt {
+
+ public static final String SERVICE_TYPE = "_hap._tcp";
+ public static final String SERVICE_TYPE_LOCAL = "_hap._tcp.local.";
+ public static final String DOMAIN_LOCAL = "local.";
+
+ private HapDnsTxt() {}
+
+ /**
+ * Build the standard HAP TXT key/value map.
+ *
+ * @param discoverable whether the accessory is unpaired / discoverable ({@code sf})
+ * @param mac accessory id ({@code id})
+ * @param label model / display name ({@code md})
+ * @param setupId setup id used to compute {@code sh}
+ * @param configurationIndex configuration number ({@code c#})
+ * @param category accessory category ({@code ci})
+ * @return ordered TXT properties suitable for JmDNS or {@link #encodeTxt(Map)}
+ */
+ public static Map On macOS the symbols live in {@code libSystem}; on Linux with Avahi they typically live in
+ * {@code libdns_sd}.
+ *
+ * @author Karel Goderis
+ */
+public interface DnsSdLibrary extends Library {
+
+ int kDNSServiceErr_NoError = 0;
+ int kDNSServiceFlagsNoAutoRename = 0; // default flags = 0
+
+ int DNSServiceRegister(
+ PointerByReference sdRef,
+ int flags,
+ int interfaceIndex,
+ String name,
+ String regtype,
+ String domain,
+ String host,
+ short port, // network byte order
+ short txtLen,
+ byte[] txtRecord,
+ Pointer callBack, // may be null
+ Pointer context);
+
+ void DNSServiceRefDeallocate(Pointer sdRef);
+
+ int DNSServiceRefSockFD(Pointer sdRef);
+
+ int DNSServiceProcessResult(Pointer sdRef);
+
+ static DnsSdLibrary load() {
+ String os = System.getProperty("os.name", "").toLowerCase();
+ if (os.contains("mac") || os.contains("darwin")) {
+ return Native.load("System", DnsSdLibrary.class);
+ }
+ // Avahi Bonjour compatibility library
+ return Native.load("dns_sd", DnsSdLibrary.class);
+ }
+}
diff --git a/src/main/java/io/github/hapjava/server/impl/dnssd/DnssdHomekitAdvertiser.java b/src/main/java/io/github/hapjava/server/impl/dnssd/DnssdHomekitAdvertiser.java
new file mode 100644
index 000000000..fddb7d025
--- /dev/null
+++ b/src/main/java/io/github/hapjava/server/impl/dnssd/DnssdHomekitAdvertiser.java
@@ -0,0 +1,322 @@
+package io.github.hapjava.server.impl.dnssd;
+
+import com.sun.jna.Pointer;
+import com.sun.jna.ptr.PointerByReference;
+import io.github.hapjava.server.HomekitAdvertiser;
+import io.github.hapjava.server.impl.advertiser.HapDnsTxt;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * HomeKit advertiser that registers {@code _hap._tcp} through the platform DNS-SD API (macOS
+ * mDNSResponder / Avahi dns_sd compat) without binding UDP 5353.
+ *
+ * Uses JNA to call {@code DNSServiceRegister} / {@code DNSServiceRefDeallocate}.
+ *
+ * Pass an {@link InetAddress} to scope registration to one LAN interface; SRV host
+ * stays {@code null} so mDNSResponder advertises the system hostname (with A/AAAA). Passing a raw
+ * IP as DNS-SD {@code host} breaks pairing — the daemon does not synthesize an A record for that
+ * name. The no-arg constructor registers on all interfaces ({@code interfaceIndex = 0}).
+ *
+ * @author Karel Goderis
+ */
+public class DnssdHomekitAdvertiser implements HomekitAdvertiser {
+
+ private static final Logger logger = LoggerFactory.getLogger(DnssdHomekitAdvertiser.class);
+
+ /** DNS-SD: register on all interfaces. */
+ public static final int INTERFACE_INDEX_ANY = 0;
+
+ private static final boolean SUPPORTED = detectSupport();
+
+ private final DnsSdLibrary dnsSd;
+ private final int interfaceIndex;
+ private final String host;
+ private final Object lock = new Object();
+
+ private boolean discoverable = true;
+ private boolean isAdvertising = false;
+
+ private String label;
+ private String mac;
+ private String setupId;
+ private int port;
+ private int configurationIndex;
+ private int category;
+
+ private Pointer serviceRef;
+ private Thread processThread;
+ private final AtomicBoolean runProcessLoop = new AtomicBoolean(false);
+
+ /** Register on all interfaces using the system default hostname. */
+ public DnssdHomekitAdvertiser() {
+ this(INTERFACE_INDEX_ANY, null);
+ }
+
+ /**
+ * Scope registration to the network interface that owns {@code localAddress}. SRV {@code host} is
+ * left {@code null} (system default hostname + A/AAAA via mDNSResponder).
+ *
+ * Do not pass {@code localAddress.getHostAddress()} as DNS-SD host: a literal IP becomes an
+ * SRV target name with no A record, and Home pairing hangs after accepting the QR code.
+ *
+ * @param localAddress LAN address the HAP TCP listener uses
+ */
+ public DnssdHomekitAdvertiser(InetAddress localAddress) {
+ this(resolveInterfaceIndex(localAddress), null);
+ }
+
+ /**
+ * Full control over DNS-SD {@code interfaceIndex} and {@code host}.
+ *
+ * @param interfaceIndex {@link #INTERFACE_INDEX_ANY} (0) for all interfaces, or a platform
+ * interface index from {@link NetworkInterface#getIndex()}
+ * @param host SRV target DNS hostname, or {@code null} for the system default.
+ * Must not be a raw IP literal — DNS-SD will not register an A/AAAA for it.
+ */
+ public DnssdHomekitAdvertiser(int interfaceIndex, String host) {
+ if (!SUPPORTED) {
+ throw new UnsupportedOperationException("Native DNS-SD is not available");
+ }
+ this.dnsSd = DnsSdLibrary.load();
+ this.interfaceIndex = interfaceIndex;
+ this.host = host;
+ }
+
+ /** Visible for tests that inject a mock library. */
+ DnssdHomekitAdvertiser(DnsSdLibrary dnsSd) {
+ this(dnsSd, INTERFACE_INDEX_ANY, null);
+ }
+
+ /** Visible for tests that inject a mock library with interface/host. */
+ DnssdHomekitAdvertiser(DnsSdLibrary dnsSd, int interfaceIndex, String host) {
+ this.dnsSd = dnsSd;
+ this.interfaceIndex = interfaceIndex;
+ this.host = host;
+ }
+
+ public static boolean isSupported() {
+ return SUPPORTED;
+ }
+
+ /** DNS-SD interface index used for registration (0 = all interfaces). */
+ public int getInterfaceIndex() {
+ return interfaceIndex;
+ }
+
+ /** SRV host passed to DNS-SD, or {@code null} for the system default. */
+ public String getHost() {
+ return host;
+ }
+
+ private static boolean detectSupport() {
+ try {
+ DnsSdLibrary.load();
+ return true;
+ } catch (LinkageError | Exception e) {
+ // LinkageError covers NoClassDefFoundError when optional JNA is absent, and
+ // UnsatisfiedLinkError when libSystem / libdns_sd cannot be loaded.
+ logger.debug("Native DNS-SD not available: {}", e.toString());
+ return false;
+ }
+ }
+
+ private static int resolveInterfaceIndex(InetAddress localAddress) {
+ Objects.requireNonNull(localAddress, "localAddress");
+ try {
+ NetworkInterface nif = NetworkInterface.getByInetAddress(localAddress);
+ if (nif != null) {
+ int index = nif.getIndex();
+ if (index > 0) {
+ return index;
+ }
+ }
+ } catch (SocketException e) {
+ logger.debug("Could not resolve NetworkInterface for {}: {}", localAddress, e.toString());
+ }
+ logger.debug(
+ "No network interface index for {}; registering on all interfaces (index 0)", localAddress);
+ return INTERFACE_INDEX_ANY;
+ }
+
+ @Override
+ public void advertise(
+ String label, int category, String mac, int port, int configurationIndex, String setupId)
+ throws Exception {
+ synchronized (lock) {
+ if (isAdvertising) {
+ throw new IllegalStateException("HomeKit advertiser is already running");
+ }
+ this.label = label;
+ this.mac = mac;
+ this.port = port;
+ this.setupId = setupId;
+ this.category = category;
+ this.configurationIndex = configurationIndex;
+ logger.trace(
+ "Advertising accessory {} via native DNS-SD (interfaceIndex={}, host={})",
+ label,
+ interfaceIndex,
+ host);
+ registerService();
+ isAdvertising = true;
+ }
+ }
+
+ @Override
+ public void stop() {
+ synchronized (lock) {
+ unregisterService();
+ isAdvertising = false;
+ }
+ }
+
+ @Override
+ public void setDiscoverable(boolean discoverable) throws IOException {
+ synchronized (lock) {
+ if (this.discoverable != discoverable) {
+ this.discoverable = discoverable;
+ if (isAdvertising) {
+ logger.trace("Re-creating DNSSD service due to discoverability={}", discoverable);
+ unregisterService();
+ registerService();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void setMac(String mac) throws IOException {
+ synchronized (lock) {
+ if (!Objects.equals(this.mac, mac)) {
+ this.mac = mac;
+ if (isAdvertising) {
+ logger.trace("Re-creating DNSSD service due to mac change");
+ unregisterService();
+ registerService();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void setConfigurationIndex(int revision) throws IOException {
+ synchronized (lock) {
+ if (this.configurationIndex != revision) {
+ this.configurationIndex = revision;
+ if (isAdvertising) {
+ logger.trace("Re-creating DNSSD service due to c#={}", revision);
+ unregisterService();
+ registerService();
+ }
+ }
+ }
+ }
+
+ private void registerService() throws IOException {
+ if (serviceRef != null) {
+ throw new AssertionError("DNSSD service already registered");
+ }
+ Map