Allow resolving previously undiscovered services
Previously resolve queries for SRV/TXT/A/AAAA were only sent when there
was a known but incomplete MdnsResponse for the service.
Allow sending the resolve queries when there is no known MdnsResponse,
so no known PTR record, but the client specified the resolveInstanceName
in the MdnsSearchOptions. This is achieved by adding the serviceName as
an additional field in MdnsResponse, as it is always known when
constructing the MdnsResponse. The serviceName can either be set from a
discovered PTR record, or from the client-specified resolveInstanceName.
Bug: 267570781
Test: atest NsdManagerTest#testResolveWhenServerSendsNoAdditionalRecord
(see separate change).
Change-Id: Icb9ed7ae44179a5df5b69a1e6a0486ffc2d8e42c
diff --git a/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java b/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
index c472a8f..9a67007 100644
--- a/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
+++ b/service-t/src/com/android/server/connectivity/mdns/EnqueueMdnsQueryCallable.java
@@ -120,21 +120,17 @@
// List of (name, type) to query
final ArrayList<Pair<String[], Integer>> missingKnownAnswerRecords = new ArrayList<>();
for (MdnsResponse response : servicesToResolve) {
- // In practice responses should always have at least one pointer record, since the
- // record is added after creation in MdnsResponseDecoder. All PTR records point to
- // the same instance name, since addPointerRecord is only called on instances
- // obtained through MdnsResponseDecoder.findResponseWithPointer.
// TODO: also send queries to renew record TTL (as per RFC6762 7.1 no need to query
// if remaining TTL is more than half the original one, so send the queries if half
// the TTL has passed).
- if (!response.hasPointerRecords() || response.isComplete()) continue;
- final String[] instanceName = response.getPointerRecords().get(0).getPointer();
- if (instanceName == null) continue;
+ if (response.isComplete()) continue;
+ final String[] serviceName = response.getServiceName();
+ if (serviceName == null) continue;
if (!response.hasTextRecord()) {
- missingKnownAnswerRecords.add(new Pair<>(instanceName, MdnsRecord.TYPE_TXT));
+ missingKnownAnswerRecords.add(new Pair<>(serviceName, MdnsRecord.TYPE_TXT));
}
if (!response.hasServiceRecord()) {
- missingKnownAnswerRecords.add(new Pair<>(instanceName, MdnsRecord.TYPE_SRV));
+ missingKnownAnswerRecords.add(new Pair<>(serviceName, MdnsRecord.TYPE_SRV));
// The hostname is not yet known, so queries for address records will be sent
// the next time the EnqueueMdnsQueryCallable is enqueued if the reply does not
// contain them. In practice, advertisers should include the address records
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsResponse.java b/service-t/src/com/android/server/connectivity/mdns/MdnsResponse.java
index 3fcd1aa..a6e3762 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsResponse.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsResponse.java
@@ -41,14 +41,17 @@
private long lastUpdateTime;
private final int interfaceIndex;
@Nullable private final Network network;
+ @NonNull private final String[] serviceName;
/** Constructs a new, empty response. */
- public MdnsResponse(long now, int interfaceIndex, @Nullable Network network) {
+ public MdnsResponse(long now, @NonNull String[] serviceName, int interfaceIndex,
+ @Nullable Network network) {
lastUpdateTime = now;
records = new LinkedList<>();
pointerRecords = new LinkedList<>();
this.interfaceIndex = interfaceIndex;
this.network = network;
+ this.serviceName = serviceName;
}
public MdnsResponse(@NonNull MdnsResponse base) {
@@ -59,6 +62,7 @@
inet4AddressRecord = base.inet4AddressRecord;
inet6AddressRecord = base.inet6AddressRecord;
lastUpdateTime = base.lastUpdateTime;
+ serviceName = base.serviceName;
interfaceIndex = base.interfaceIndex;
network = base.network;
}
@@ -81,6 +85,10 @@
* pointer record is already present in the response with the same TTL.
*/
public synchronized boolean addPointerRecord(MdnsPointerRecord pointerRecord) {
+ if (!Arrays.equals(serviceName, pointerRecord.getPointer())) {
+ throw new IllegalArgumentException(
+ "Pointer records for different service names cannot be added");
+ }
final int existing = pointerRecords.indexOf(pointerRecord);
if (existing >= 0) {
if (recordsAreSame(pointerRecord, pointerRecords.get(existing))) {
@@ -297,12 +305,12 @@
}
/**
- * Tests if the response is complete. A response is considered complete if it contains PTR, SRV,
- * TXT, and A (for IPv4) or AAAA (for IPv6) records.
+ * Tests if the response is complete. A response is considered complete if it contains SRV,
+ * TXT, and A (for IPv4) or AAAA (for IPv6) records. The service type->name mapping is always
+ * known when constructing a MdnsResponse, so this may return true when there is no PTR record.
*/
public synchronized boolean isComplete() {
- return !pointerRecords.isEmpty()
- && (serviceRecord != null)
+ return (serviceRecord != null)
&& (textRecord != null)
&& (inet4AddressRecord != null || inet6AddressRecord != null);
}
@@ -311,12 +319,14 @@
* Returns the key for this response. The key uniquely identifies the response by its service
* name.
*/
- public synchronized String getServiceInstanceName() {
- if (pointerRecords.isEmpty()) {
- return null;
- }
- String[] pointers = pointerRecords.get(0).getPointer();
- return ((pointers != null) && (pointers.length > 0)) ? pointers[0] : null;
+ @Nullable
+ public String getServiceInstanceName() {
+ return serviceName.length > 0 ? serviceName[0] : null;
+ }
+
+ @NonNull
+ public String[] getServiceName() {
+ return serviceName;
}
/**
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java b/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
index 8da818a..2115454 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsResponseDecoder.java
@@ -32,7 +32,6 @@
/** A class that decodes mDNS responses from UDP packets. */
public class MdnsResponseDecoder {
-
public static final int SUCCESS = 0;
private static final String TAG = "MdnsResponseDecoder";
private static final MdnsLogger LOGGER = new MdnsLogger(TAG);
@@ -51,14 +50,8 @@
List<MdnsResponse> responses, String[] pointer) {
if (responses != null) {
for (MdnsResponse response : responses) {
- List<MdnsPointerRecord> pointerRecords = response.getPointerRecords();
- if (pointerRecords == null) {
- continue;
- }
- for (MdnsPointerRecord pointerRecord : pointerRecords) {
- if (Arrays.equals(pointerRecord.getPointer(), pointer)) {
- return response;
- }
+ if (Arrays.equals(response.getServiceName(), pointer)) {
+ return response;
}
}
}
@@ -182,7 +175,8 @@
MdnsResponse response = findResponseWithPointer(responses,
pointerRecord.getPointer());
if (response == null) {
- response = new MdnsResponse(now, interfaceIndex, network);
+ response = new MdnsResponse(now, pointerRecord.getPointer(), interfaceIndex,
+ network);
responses.add(response);
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
index c092a58..f886948 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceTypeClient.java
@@ -219,8 +219,12 @@
public synchronized void processResponse(@NonNull MdnsPacket packet, int interfaceIndex,
Network network) {
synchronized (lock) {
+ // Augment the list of current known responses, and generated responses for resolve
+ // requests if there is no known response
+ final List<MdnsResponse> currentList = new ArrayList<>(instanceNameToResponse.values());
+ currentList.addAll(makeResponsesForResolveIfUnknown(interfaceIndex, network));
final ArraySet<MdnsResponse> modifiedResponses = responseDecoder.augmentResponses(
- packet, instanceNameToResponse.values(), interfaceIndex, network);
+ packet, currentList, interfaceIndex, network);
for (MdnsResponse modified : modifiedResponses) {
if (modified.isGoodbye()) {
@@ -400,6 +404,29 @@
}
}
+ private List<MdnsResponse> makeResponsesForResolveIfUnknown(int interfaceIndex,
+ @NonNull Network network) {
+ final List<MdnsResponse> resolveResponses = new ArrayList<>();
+ for (int i = 0; i < listeners.size(); i++) {
+ final String resolveName = listeners.valueAt(i).getResolveInstanceName();
+ if (resolveName == null) {
+ continue;
+ }
+ MdnsResponse knownResponse = instanceNameToResponse.get(resolveName);
+ if (knownResponse == null) {
+ final ArrayList<String> instanceFullName = new ArrayList<>(
+ serviceTypeLabels.length + 1);
+ instanceFullName.add(resolveName);
+ instanceFullName.addAll(Arrays.asList(serviceTypeLabels));
+ knownResponse = new MdnsResponse(
+ 0L /* lastUpdateTime */, instanceFullName.toArray(new String[0]),
+ interfaceIndex, network);
+ }
+ resolveResponses.add(knownResponse);
+ }
+ return resolveResponses;
+ }
+
// A FutureTask that enqueues a single query, and schedule a new FutureTask for the next task.
private class QueryTask implements Runnable {
@@ -411,35 +438,17 @@
@Override
public void run() {
- final List<MdnsResponse> servicesToResolve = new ArrayList<>();
- boolean sendDiscoveryQueries = false;
+ final List<MdnsResponse> servicesToResolve;
+ final boolean sendDiscoveryQueries;
synchronized (lock) {
- for (int i = 0; i < listeners.size(); i++) {
- final String resolveName = listeners.valueAt(i).getResolveInstanceName();
- if (resolveName == null) {
- sendDiscoveryQueries = true;
- continue;
- }
- MdnsResponse knownResponse = instanceNameToResponse.get(resolveName);
- if (knownResponse == null) {
- // The listener is requesting to resolve a service that has no info in
- // cache. Use the provided name to generate a minimal response with just a
- // PTR record, so other records are queried to complete it.
- // Only the names are used to know which queries to send, other
- // parameters do not matter.
- knownResponse = new MdnsResponse(
- 0L /* now */, 0 /* interfaceIndex */, config.network);
- final ArrayList<String> instanceFullName = new ArrayList<>(
- serviceTypeLabels.length + 1);
- instanceFullName.add(resolveName);
- instanceFullName.addAll(Arrays.asList(serviceTypeLabels));
- knownResponse.addPointerRecord(new MdnsPointerRecord(
- serviceTypeLabels, 0L /* receiptTimeMillis */,
- false /* cacheFlush */, 0L /* ttlMillis */,
- instanceFullName.toArray(new String[0])));
- }
- servicesToResolve.add(knownResponse);
- }
+ // The listener is requesting to resolve a service that has no info in
+ // cache. Use the provided name to generate a minimal response, so other records are
+ // queried to complete it.
+ // Only the names are used to know which queries to send, other parameters like
+ // interfaceIndex do not matter.
+ servicesToResolve = makeResponsesForResolveIfUnknown(
+ 0 /* interfaceIndex */, config.network);
+ sendDiscoveryQueries = servicesToResolve.size() < listeners.size();
}
Pair<Integer, List<String>> result;
try {
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java
index 70900b7..d83c0dd 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseDecoderTests.java
@@ -192,6 +192,8 @@
"03666f6f03626172047175787800001000010000007800240d613d68656c6c6f2074686572650c623d3132"
+ "33343536373839300878797a3d21402324");
+ private static final String[] DATAIN_SERVICE_NAME_1 = new String[] { "foo", "bar", "quxx" };
+
private static final String CAST_SERVICE_NAME = "_googlecast";
private static final String[] CAST_SERVICE_TYPE =
new String[] {CAST_SERVICE_NAME, "_tcp", "local"};
@@ -289,7 +291,8 @@
assertTrue(response.isComplete());
response.clearPointerRecords();
- assertFalse(response.isComplete());
+ // The service name is still known in MdnsResponse#getServiceName
+ assertTrue(response.isComplete());
response = new MdnsResponse(responses.valueAt(0));
response.setInet4AddressRecord(null);
@@ -379,7 +382,7 @@
@Test
public void testDecodeWithIpv4AddressChange() throws IOException {
- MdnsResponse response = makeMdnsResponse(0, List.of(
+ MdnsResponse response = makeMdnsResponse(0, DATAIN_SERVICE_NAME_1, List.of(
new PacketAndRecordClass(DATAIN_PTR_1,
MdnsPointerRecord.class),
new PacketAndRecordClass(DATAIN_SERVICE_1,
@@ -399,7 +402,7 @@
@Test
public void testDecodeWithIpv6AddressChange() throws IOException {
- MdnsResponse response = makeMdnsResponse(0, List.of(
+ MdnsResponse response = makeMdnsResponse(0, DATAIN_SERVICE_NAME_1, List.of(
new PacketAndRecordClass(DATAIN_PTR_1,
MdnsPointerRecord.class),
new PacketAndRecordClass(DATAIN_SERVICE_1,
@@ -419,7 +422,7 @@
@Test
public void testDecodeWithChangeOnText() throws IOException {
- MdnsResponse response = makeMdnsResponse(0, List.of(
+ MdnsResponse response = makeMdnsResponse(0, DATAIN_SERVICE_NAME_1, List.of(
new PacketAndRecordClass(DATAIN_PTR_1,
MdnsPointerRecord.class),
new PacketAndRecordClass(DATAIN_SERVICE_1,
@@ -440,7 +443,7 @@
@Test
public void testDecodeWithChangeOnService() throws IOException {
- MdnsResponse response = makeMdnsResponse(0, List.of(
+ MdnsResponse response = makeMdnsResponse(0, DATAIN_SERVICE_NAME_1, List.of(
new PacketAndRecordClass(DATAIN_PTR_1,
MdnsPointerRecord.class),
new PacketAndRecordClass(DATAIN_SERVICE_1,
@@ -463,7 +466,7 @@
@Test
public void testDecodeWithChangeOnPtr() throws IOException {
- MdnsResponse response = makeMdnsResponse(0, List.of(
+ MdnsResponse response = makeMdnsResponse(0, DATAIN_SERVICE_NAME_1, List.of(
new PacketAndRecordClass(DATAIN_PTR_1,
MdnsPointerRecord.class),
new PacketAndRecordClass(DATAIN_SERVICE_1,
@@ -487,7 +490,7 @@
new PacketAndRecordClass(DATAIN_SERVICE_2, MdnsServiceRecord.class),
new PacketAndRecordClass(DATAIN_TEXT_1, MdnsTextRecord.class));
// Create a two identical responses.
- MdnsResponse response = makeMdnsResponse(0, recordList);
+ MdnsResponse response = makeMdnsResponse(0, DATAIN_SERVICE_NAME_1, recordList);
final MdnsResponseDecoder decoder = new MdnsResponseDecoder(mClock, null);
final byte[] identicalResponse = makeResponsePacket(
@@ -499,10 +502,10 @@
assertEquals(0, changes.size());
}
- private static MdnsResponse makeMdnsResponse(long time, List<PacketAndRecordClass> responseList)
- throws IOException {
+ private static MdnsResponse makeMdnsResponse(long time, String[] serviceName,
+ List<PacketAndRecordClass> responseList) throws IOException {
final MdnsResponse response = new MdnsResponse(
- time, 999 /* interfaceIndex */, mock(Network.class));
+ time, serviceName, 999 /* interfaceIndex */, mock(Network.class));
for (PacketAndRecordClass responseData : responseList) {
DatagramPacket packet =
new DatagramPacket(responseData.packetData, responseData.packetData.length);
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseTests.java
index 252bd26..132a822 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsResponseTests.java
@@ -74,6 +74,8 @@
+ "3D31323334353637"
+ "3839300878797A3D"
+ "21242424");
+ private static final String[] TEST_SERVICE_NAME =
+ new String[] { "test", "_type", "_tcp", "local" };
private static final int INTERFACE_INDEX = 999;
private static final int TEST_TTL_MS = 120_000;
@@ -94,10 +96,11 @@
}
private MdnsResponse makeCompleteResponse(int recordsTtlMillis) {
- final MdnsResponse response = new MdnsResponse(/* now= */ 0, INTERFACE_INDEX, mNetwork);
final String[] hostname = new String[] { "MyHostname" };
final String[] serviceName = new String[] { "MyService", "_type", "_tcp", "local" };
final String[] serviceType = new String[] { "_type", "_tcp", "local" };
+ final MdnsResponse response = new MdnsResponse(/* now= */ 0, serviceName, INTERFACE_INDEX,
+ mNetwork);
response.addPointerRecord(new MdnsPointerRecord(serviceType, 0L /* receiptTimeMillis */,
false /* cacheFlush */, recordsTtlMillis, serviceName));
response.setServiceRecord(new MdnsServiceRecord(serviceName, 0L /* receiptTimeMillis */,
@@ -121,7 +124,7 @@
String[] name = reader.readLabels();
reader.skip(2); // skip record type indication.
MdnsInetAddressRecord record = new MdnsInetAddressRecord(name, MdnsRecord.TYPE_A, reader);
- MdnsResponse response = new MdnsResponse(0, INTERFACE_INDEX, mNetwork);
+ MdnsResponse response = new MdnsResponse(0, TEST_SERVICE_NAME, INTERFACE_INDEX, mNetwork);
assertFalse(response.hasInet4AddressRecord());
assertTrue(response.setInet4AddressRecord(record));
assertEquals(response.getInet4AddressRecord(), record);
@@ -135,7 +138,7 @@
reader.skip(2); // skip record type indication.
MdnsInetAddressRecord record =
new MdnsInetAddressRecord(name, MdnsRecord.TYPE_AAAA, reader);
- MdnsResponse response = new MdnsResponse(0, INTERFACE_INDEX, mNetwork);
+ MdnsResponse response = new MdnsResponse(0, TEST_SERVICE_NAME, INTERFACE_INDEX, mNetwork);
assertFalse(response.hasInet6AddressRecord());
assertTrue(response.setInet6AddressRecord(record));
assertEquals(response.getInet6AddressRecord(), record);
@@ -148,7 +151,7 @@
String[] name = reader.readLabels();
reader.skip(2); // skip record type indication.
MdnsPointerRecord record = new MdnsPointerRecord(name, reader);
- MdnsResponse response = new MdnsResponse(0, INTERFACE_INDEX, mNetwork);
+ MdnsResponse response = new MdnsResponse(0, record.getPointer(), INTERFACE_INDEX, mNetwork);
assertFalse(response.hasPointerRecords());
assertTrue(response.addPointerRecord(record));
List<MdnsPointerRecord> recordList = response.getPointerRecords();
@@ -164,7 +167,7 @@
String[] name = reader.readLabels();
reader.skip(2); // skip record type indication.
MdnsServiceRecord record = new MdnsServiceRecord(name, reader);
- MdnsResponse response = new MdnsResponse(0, INTERFACE_INDEX, mNetwork);
+ MdnsResponse response = new MdnsResponse(0, name, INTERFACE_INDEX, mNetwork);
assertFalse(response.hasServiceRecord());
assertTrue(response.setServiceRecord(record));
assertEquals(response.getServiceRecord(), record);
@@ -177,7 +180,7 @@
String[] name = reader.readLabels();
reader.skip(2); // skip record type indication.
MdnsTextRecord record = new MdnsTextRecord(name, reader);
- MdnsResponse response = new MdnsResponse(0, INTERFACE_INDEX, mNetwork);
+ MdnsResponse response = new MdnsResponse(0, name, INTERFACE_INDEX, mNetwork);
assertFalse(response.hasTextRecord());
assertTrue(response.setTextRecord(record));
assertEquals(response.getTextRecord(), record);
@@ -185,22 +188,23 @@
@Test
public void getInterfaceIndex() {
- final MdnsResponse response1 = new MdnsResponse(/* now= */ 0, INTERFACE_INDEX, mNetwork);
+ final MdnsResponse response1 = new MdnsResponse(/* now= */ 0, TEST_SERVICE_NAME,
+ INTERFACE_INDEX, mNetwork);
assertEquals(INTERFACE_INDEX, response1.getInterfaceIndex());
- final MdnsResponse response2 =
- new MdnsResponse(/* now= */ 0, 1234 /* interfaceIndex */, mNetwork);
+ final MdnsResponse response2 = new MdnsResponse(/* now= */ 0, TEST_SERVICE_NAME,
+ 1234 /* interfaceIndex */, mNetwork);
assertEquals(1234, response2.getInterfaceIndex());
}
@Test
public void testGetNetwork() {
- final MdnsResponse response1 =
- new MdnsResponse(/* now= */ 0, INTERFACE_INDEX, null /* network */);
+ final MdnsResponse response1 = new MdnsResponse(/* now= */ 0, TEST_SERVICE_NAME,
+ INTERFACE_INDEX, null /* network */);
assertNull(response1.getNetwork());
- final MdnsResponse response2 =
- new MdnsResponse(/* now= */ 0, 1234 /* interfaceIndex */, mNetwork);
+ final MdnsResponse response2 = new MdnsResponse(/* now= */ 0, TEST_SERVICE_NAME,
+ 1234 /* interfaceIndex */, mNetwork);
assertEquals(mNetwork, response2.getNetwork());
}
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
index d266b3d..a5c6123 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceTypeClientTests.java
@@ -25,9 +25,11 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -68,6 +70,7 @@
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
/** Tests for {@link MdnsServiceTypeClient}. */
@RunWith(DevSdkIgnoreRunner.class)
@@ -892,6 +895,102 @@
mockNetwork);
}
+ @Test
+ public void testProcessResponse_Resolve() throws Exception {
+ client = new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor);
+
+ final String instanceName = "service-instance";
+ final String[] hostname = new String[] { "testhost "};
+ final String ipV4Address = "192.0.2.0";
+ final String ipV6Address = "2001:db8::";
+
+ final MdnsSearchOptions resolveOptions = MdnsSearchOptions.newBuilder()
+ .setResolveInstanceName(instanceName).build();
+
+ client.startSendAndReceive(mockListenerOne, resolveOptions);
+ InOrder inOrder = inOrder(mockListenerOne, mockSocketClient);
+
+ // Verify a query for SRV/TXT was sent, but no PTR query
+ final ArgumentCaptor<DatagramPacket> srvTxtQueryCaptor =
+ ArgumentCaptor.forClass(DatagramPacket.class);
+ currentThreadExecutor.getAndClearLastScheduledRunnable().run();
+ // Send twice for IPv4 and IPv6
+ inOrder.verify(mockSocketClient, times(2)).sendUnicastPacket(srvTxtQueryCaptor.capture(),
+ eq(null) /* network */);
+
+ final MdnsPacket srvTxtQueryPacket = MdnsPacket.parse(
+ new MdnsPacketReader(srvTxtQueryCaptor.getValue()));
+ final List<MdnsRecord> srvTxtQuestions = srvTxtQueryPacket.questions;
+
+ final String[] serviceName = Stream.concat(Stream.of(instanceName),
+ Arrays.stream(SERVICE_TYPE_LABELS)).toArray(String[]::new);
+ assertFalse(srvTxtQuestions.stream().anyMatch(q -> q.getType() == MdnsRecord.TYPE_PTR));
+ assertTrue(srvTxtQuestions.stream().anyMatch(q ->
+ q.getType() == MdnsRecord.TYPE_SRV && Arrays.equals(q.name, serviceName)));
+ assertTrue(srvTxtQuestions.stream().anyMatch(q ->
+ q.getType() == MdnsRecord.TYPE_TXT && Arrays.equals(q.name, serviceName)));
+
+ // Process a response with SRV+TXT
+ final MdnsPacket srvTxtResponse = new MdnsPacket(
+ 0 /* flags */,
+ Collections.emptyList() /* questions */,
+ List.of(
+ new MdnsServiceRecord(serviceName, 0L /* receiptTimeMillis */,
+ true /* cacheFlush */, TEST_TTL, 0 /* servicePriority */,
+ 0 /* serviceWeight */, 1234 /* servicePort */, hostname),
+ new MdnsTextRecord(serviceName, 0L /* receiptTimeMillis */,
+ true /* cacheFlush */, TEST_TTL,
+ Collections.emptyList() /* entries */)),
+ Collections.emptyList() /* authorityRecords */,
+ Collections.emptyList() /* additionalRecords */);
+
+ client.processResponse(srvTxtResponse, INTERFACE_INDEX, mockNetwork);
+
+ // Expect a query for A/AAAA
+ final ArgumentCaptor<DatagramPacket> addressQueryCaptor =
+ ArgumentCaptor.forClass(DatagramPacket.class);
+ currentThreadExecutor.getAndClearLastScheduledRunnable().run();
+ inOrder.verify(mockSocketClient, times(2)).sendMulticastPacket(addressQueryCaptor.capture(),
+ eq(null) /* network */);
+
+ final MdnsPacket addressQueryPacket = MdnsPacket.parse(
+ new MdnsPacketReader(addressQueryCaptor.getValue()));
+ final List<MdnsRecord> addressQueryQuestions = addressQueryPacket.questions;
+ assertTrue(addressQueryQuestions.stream().anyMatch(q ->
+ q.getType() == MdnsRecord.TYPE_A && Arrays.equals(q.name, hostname)));
+ assertTrue(addressQueryQuestions.stream().anyMatch(q ->
+ q.getType() == MdnsRecord.TYPE_AAAA && Arrays.equals(q.name, hostname)));
+
+ // Process a response with address records
+ final MdnsPacket addressResponse = new MdnsPacket(
+ 0 /* flags */,
+ Collections.emptyList() /* questions */,
+ List.of(
+ new MdnsInetAddressRecord(hostname, 0L /* receiptTimeMillis */,
+ true /* cacheFlush */, TEST_TTL,
+ InetAddresses.parseNumericAddress(ipV4Address)),
+ new MdnsInetAddressRecord(hostname, 0L /* receiptTimeMillis */,
+ true /* cacheFlush */, TEST_TTL,
+ InetAddresses.parseNumericAddress(ipV6Address))),
+ Collections.emptyList() /* authorityRecords */,
+ Collections.emptyList() /* additionalRecords */);
+
+ inOrder.verify(mockListenerOne, never()).onServiceNameDiscovered(any());
+ client.processResponse(addressResponse, INTERFACE_INDEX, mockNetwork);
+
+ inOrder.verify(mockListenerOne).onServiceFound(serviceInfoCaptor.capture());
+ verifyServiceInfo(serviceInfoCaptor.getValue(),
+ instanceName,
+ SERVICE_TYPE_LABELS,
+ ipV4Address,
+ ipV6Address,
+ 1234 /* port */,
+ Collections.emptyList() /* subTypes */,
+ Collections.emptyMap() /* attributes */,
+ INTERFACE_INDEX,
+ mockNetwork);
+ }
+
// verifies that the right query was enqueued with the right delay, and send query by executing
// the runnable.
private void verifyAndSendQuery(int index, long timeInMs, boolean expectsUnicastResponse) {