Merge changes I09e780af,I21367c66

* changes:
  Implement proper subtype advertising
  Implement proper discovery with subtypes
This commit is contained in:
Remi NGUYEN VAN
2023-05-12 13:31:19 +00:00
committed by Gerrit Code Review
10 changed files with 395 additions and 107 deletions

View File

@@ -23,7 +23,7 @@ import static android.net.nsd.NsdManager.FAILURE_BAD_PARAMETERS;
import static android.net.nsd.NsdManager.FAILURE_INTERNAL_ERROR;
import static android.net.nsd.NsdManager.FAILURE_OPERATION_NOT_RUNNING;
import static com.android.server.NsdService.constructServiceType;
import static com.android.server.NsdService.parseTypeAndSubtype;
import static com.android.testutils.ContextUtils.mockService;
import static libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
@@ -77,6 +77,7 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
@@ -84,6 +85,7 @@ import androidx.test.filters.SmallTest;
import com.android.server.NsdService.Dependencies;
import com.android.server.connectivity.mdns.MdnsAdvertiser;
import com.android.server.connectivity.mdns.MdnsDiscoveryManager;
import com.android.server.connectivity.mdns.MdnsSearchOptions;
import com.android.server.connectivity.mdns.MdnsServiceBrowserListener;
import com.android.server.connectivity.mdns.MdnsServiceInfo;
import com.android.server.connectivity.mdns.MdnsSocketProvider;
@@ -104,6 +106,7 @@ import org.mockito.MockitoAnnotations;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
@@ -968,6 +971,34 @@ public class NsdServiceTest {
.onStartDiscoveryFailed(serviceTypeWithoutTcpOrUdpEnding, FAILURE_INTERNAL_ERROR);
}
@Test
@EnableCompatChanges(ENABLE_PLATFORM_MDNS_BACKEND)
public void testDiscoveryWithMdnsDiscoveryManager_UsesSubtypes() {
final String typeWithSubtype = SERVICE_TYPE + ",_subtype";
final NsdManager client = connectClient(mService);
final NsdServiceInfo regInfo = new NsdServiceInfo("Instance", typeWithSubtype);
final Network network = new Network(999);
regInfo.setHostAddresses(List.of(parseNumericAddress("192.0.2.123")));
regInfo.setPort(12345);
regInfo.setNetwork(network);
final RegistrationListener regListener = mock(RegistrationListener.class);
client.registerService(regInfo, NsdManager.PROTOCOL_DNS_SD, Runnable::run, regListener);
waitForIdle();
verify(mAdvertiser).addService(anyInt(), argThat(s ->
"Instance".equals(s.getServiceName())
&& SERVICE_TYPE.equals(s.getServiceType())), eq("_subtype"));
final DiscoveryListener discListener = mock(DiscoveryListener.class);
client.discoverServices(typeWithSubtype, PROTOCOL, network, Runnable::run, discListener);
waitForIdle();
final ArgumentCaptor<MdnsSearchOptions> optionsCaptor =
ArgumentCaptor.forClass(MdnsSearchOptions.class);
verify(mDiscoveryManager).registerListener(eq(SERVICE_TYPE + ".local"), any(),
optionsCaptor.capture());
assertEquals(Collections.singletonList("subtype"), optionsCaptor.getValue().getSubtypes());
}
@Test
public void testResolutionWithMdnsDiscoveryManager() throws UnknownHostException {
setMdnsDiscoveryManagerEnabled();
@@ -976,7 +1007,7 @@ public class NsdServiceTest {
final ResolveListener resolveListener = mock(ResolveListener.class);
final Network network = new Network(999);
final String serviceType = "_nsd._service._tcp";
final String constructedServiceType = "_nsd._sub._service._tcp.local";
final String constructedServiceType = "_service._tcp.local";
final ArgumentCaptor<MdnsServiceBrowserListener> listenerCaptor =
ArgumentCaptor.forClass(MdnsServiceBrowserListener.class);
final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, serviceType);
@@ -984,12 +1015,14 @@ public class NsdServiceTest {
client.resolveService(request, resolveListener);
waitForIdle();
verify(mSocketProvider).startMonitoringSockets();
// TODO(b/266167702): this is a bug, as registerListener should be done _service._tcp, and
// _sub should be in the list of subtypes in the options.
final ArgumentCaptor<MdnsSearchOptions> optionsCaptor =
ArgumentCaptor.forClass(MdnsSearchOptions.class);
verify(mDiscoveryManager).registerListener(eq(constructedServiceType),
listenerCaptor.capture(), argThat(options ->
network.equals(options.getNetwork())
&& SERVICE_NAME.equals(options.getResolveInstanceName())));
listenerCaptor.capture(),
optionsCaptor.capture());
assertEquals(network, optionsCaptor.getValue().getNetwork());
// Subtypes are not used for resolution, only for discovery
assertEquals(Collections.emptyList(), optionsCaptor.getValue().getSubtypes());
final MdnsServiceBrowserListener listener = listenerCaptor.getValue();
final MdnsServiceInfo mdnsServiceInfo = new MdnsServiceInfo(
@@ -1013,8 +1046,7 @@ public class NsdServiceTest {
verify(resolveListener, timeout(TIMEOUT_MS)).onServiceResolved(infoCaptor.capture());
final NsdServiceInfo info = infoCaptor.getValue();
assertEquals(SERVICE_NAME, info.getServiceName());
// TODO(b/266167702): this should be ._service._tcp (as per legacy behavior)
assertEquals("._nsd._sub._service._tcp", info.getServiceType());
assertEquals("._service._tcp", info.getServiceType());
assertEquals(PORT, info.getPort());
assertTrue(info.getAttributes().containsKey("key"));
assertEquals(1, info.getAttributes().size());
@@ -1057,7 +1089,7 @@ public class NsdServiceTest {
final ArgumentCaptor<Integer> serviceIdCaptor = ArgumentCaptor.forClass(Integer.class);
verify(mAdvertiser).addService(serviceIdCaptor.capture(),
argThat(info -> matches(info, regInfo)));
argThat(info -> matches(info, regInfo)), eq(null) /* subtype */);
client.unregisterService(regListenerWithoutFeature);
waitForIdle();
@@ -1114,8 +1146,10 @@ public class NsdServiceTest {
waitForIdle();
// The advertiser is enabled for _type2 but not _type1
verify(mAdvertiser, never()).addService(anyInt(), argThat(info -> matches(info, service1)));
verify(mAdvertiser).addService(anyInt(), argThat(info -> matches(info, service2)));
verify(mAdvertiser, never()).addService(
anyInt(), argThat(info -> matches(info, service1)), eq(null) /* subtype */);
verify(mAdvertiser).addService(
anyInt(), argThat(info -> matches(info, service2)), eq(null) /* subtype */);
}
@Test
@@ -1140,7 +1174,7 @@ public class NsdServiceTest {
verify(mSocketProvider).startMonitoringSockets();
final ArgumentCaptor<Integer> idCaptor = ArgumentCaptor.forClass(Integer.class);
verify(mAdvertiser).addService(idCaptor.capture(), argThat(info ->
matches(info, regInfo)));
matches(info, regInfo)), eq(null) /* subtype */);
// Verify onServiceRegistered callback
final MdnsAdvertiser.AdvertiserCallback cb = cbCaptor.getValue();
@@ -1176,7 +1210,7 @@ public class NsdServiceTest {
client.registerService(regInfo, NsdManager.PROTOCOL_DNS_SD, Runnable::run, regListener);
waitForIdle();
verify(mAdvertiser, never()).addService(anyInt(), any());
verify(mAdvertiser, never()).addService(anyInt(), any(), any());
verify(regListener, timeout(TIMEOUT_MS)).onRegistrationFailed(
argThat(info -> matches(info, regInfo)), eq(FAILURE_INTERNAL_ERROR));
@@ -1204,7 +1238,8 @@ public class NsdServiceTest {
final ArgumentCaptor<Integer> idCaptor = ArgumentCaptor.forClass(Integer.class);
// Service name is truncated to 63 characters
verify(mAdvertiser).addService(idCaptor.capture(),
argThat(info -> info.getServiceName().equals("a".repeat(63))));
argThat(info -> info.getServiceName().equals("a".repeat(63))),
eq(null) /* subtype */);
// Verify onServiceRegistered callback
final MdnsAdvertiser.AdvertiserCallback cb = cbCaptor.getValue();
@@ -1222,7 +1257,7 @@ public class NsdServiceTest {
final ResolveListener resolveListener = mock(ResolveListener.class);
final Network network = new Network(999);
final String serviceType = "_nsd._service._tcp";
final String constructedServiceType = "_nsd._sub._service._tcp.local";
final String constructedServiceType = "_service._tcp.local";
final ArgumentCaptor<MdnsServiceBrowserListener> listenerCaptor =
ArgumentCaptor.forClass(MdnsServiceBrowserListener.class);
final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, serviceType);
@@ -1230,8 +1265,14 @@ public class NsdServiceTest {
client.resolveService(request, resolveListener);
waitForIdle();
verify(mSocketProvider).startMonitoringSockets();
final ArgumentCaptor<MdnsSearchOptions> optionsCaptor =
ArgumentCaptor.forClass(MdnsSearchOptions.class);
verify(mDiscoveryManager).registerListener(eq(constructedServiceType),
listenerCaptor.capture(), argThat(options -> network.equals(options.getNetwork())));
listenerCaptor.capture(),
optionsCaptor.capture());
assertEquals(network, optionsCaptor.getValue().getNetwork());
// Subtypes are not used for resolution, only for discovery
assertEquals(Collections.emptyList(), optionsCaptor.getValue().getSubtypes());
client.stopServiceResolution(resolveListener);
waitForIdle();
@@ -1246,16 +1287,22 @@ public class NsdServiceTest {
}
@Test
public void testConstructServiceType() {
public void testParseTypeAndSubtype() {
final String serviceType1 = "test._tcp";
final String serviceType2 = "_test._quic";
final String serviceType3 = "_123._udp.";
final String serviceType4 = "_TEST._999._tcp.";
final String serviceType3 = "_test._quic,_test1,_test2";
final String serviceType4 = "_123._udp.";
final String serviceType5 = "_TEST._999._tcp.";
final String serviceType6 = "_998._tcp.,_TEST";
final String serviceType7 = "_997._tcp,_TEST";
assertEquals(null, constructServiceType(serviceType1));
assertEquals(null, constructServiceType(serviceType2));
assertEquals("_123._udp", constructServiceType(serviceType3));
assertEquals("_TEST._sub._999._tcp", constructServiceType(serviceType4));
assertNull(parseTypeAndSubtype(serviceType1));
assertNull(parseTypeAndSubtype(serviceType2));
assertNull(parseTypeAndSubtype(serviceType3));
assertEquals(new Pair<>("_123._udp", null), parseTypeAndSubtype(serviceType4));
assertEquals(new Pair<>("_999._tcp", "_TEST"), parseTypeAndSubtype(serviceType5));
assertEquals(new Pair<>("_998._tcp", "_TEST"), parseTypeAndSubtype(serviceType6));
assertEquals(new Pair<>("_997._tcp", "_TEST"), parseTypeAndSubtype(serviceType7));
}
@Test
@@ -1274,7 +1321,7 @@ public class NsdServiceTest {
client.registerService(regInfo, NsdManager.PROTOCOL_DNS_SD, Runnable::run, regListener);
waitForIdle();
verify(mSocketProvider).startMonitoringSockets();
verify(mAdvertiser).addService(anyInt(), any());
verify(mAdvertiser).addService(anyInt(), any(), any());
// Verify the discovery uses MdnsDiscoveryManager
final DiscoveryListener discListener = mock(DiscoveryListener.class);

View File

@@ -57,6 +57,7 @@ private val TEST_LINKADDR = LinkAddress(TEST_ADDR, 64 /* prefixLength */)
private val TEST_NETWORK_1 = mock(Network::class.java)
private val TEST_NETWORK_2 = mock(Network::class.java)
private val TEST_HOSTNAME = arrayOf("Android_test", "local")
private const val TEST_SUBTYPE = "_subtype"
private val SERVICE_1 = NsdServiceInfo("TestServiceName", "_advertisertest._tcp").apply {
port = 12345
@@ -130,7 +131,7 @@ class MdnsAdvertiserTest {
@Test
fun testAddService_OneNetwork() {
val advertiser = MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog)
postSync { advertiser.addService(SERVICE_ID_1, SERVICE_1) }
postSync { advertiser.addService(SERVICE_ID_1, SERVICE_1, null /* subtype */) }
val socketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
verify(socketProvider).requestSocket(eq(TEST_NETWORK_1), socketCbCaptor.capture())
@@ -161,7 +162,7 @@ class MdnsAdvertiserTest {
@Test
fun testAddService_AllNetworks() {
val advertiser = MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog)
postSync { advertiser.addService(SERVICE_ID_1, ALL_NETWORKS_SERVICE) }
postSync { advertiser.addService(SERVICE_ID_1, ALL_NETWORKS_SERVICE, TEST_SUBTYPE) }
val socketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
verify(socketProvider).requestSocket(eq(ALL_NETWORKS_SERVICE.network),
@@ -179,6 +180,10 @@ class MdnsAdvertiserTest {
verify(mockDeps).makeAdvertiser(eq(mockSocket2), eq(listOf(TEST_LINKADDR)),
eq(thread.looper), any(), intAdvCbCaptor2.capture(), eq(TEST_HOSTNAME), any()
)
verify(mockInterfaceAdvertiser1).addService(
anyInt(), eq(ALL_NETWORKS_SERVICE), eq(TEST_SUBTYPE))
verify(mockInterfaceAdvertiser2).addService(
anyInt(), eq(ALL_NETWORKS_SERVICE), eq(TEST_SUBTYPE))
doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1)
postSync { intAdvCbCaptor1.value.onRegisterServiceSucceeded(
@@ -207,20 +212,21 @@ class MdnsAdvertiserTest {
@Test
fun testAddService_Conflicts() {
val advertiser = MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog)
postSync { advertiser.addService(SERVICE_ID_1, SERVICE_1) }
postSync { advertiser.addService(SERVICE_ID_1, SERVICE_1, null /* subtype */) }
val oneNetSocketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
verify(socketProvider).requestSocket(eq(TEST_NETWORK_1), oneNetSocketCbCaptor.capture())
val oneNetSocketCb = oneNetSocketCbCaptor.value
// Register a service with the same name on all networks (name conflict)
postSync { advertiser.addService(SERVICE_ID_2, ALL_NETWORKS_SERVICE) }
postSync { advertiser.addService(SERVICE_ID_2, ALL_NETWORKS_SERVICE, null /* subtype */) }
val allNetSocketCbCaptor = ArgumentCaptor.forClass(SocketCallback::class.java)
verify(socketProvider).requestSocket(eq(null), allNetSocketCbCaptor.capture())
val allNetSocketCb = allNetSocketCbCaptor.value
postSync { advertiser.addService(LONG_SERVICE_ID_1, LONG_SERVICE_1) }
postSync { advertiser.addService(LONG_SERVICE_ID_2, LONG_ALL_NETWORKS_SERVICE) }
postSync { advertiser.addService(LONG_SERVICE_ID_1, LONG_SERVICE_1, null /* subtype */) }
postSync { advertiser.addService(LONG_SERVICE_ID_2, LONG_ALL_NETWORKS_SERVICE,
null /* subtype */) }
// Callbacks for matching network and all networks both get the socket
postSync {
@@ -248,13 +254,13 @@ class MdnsAdvertiserTest {
eq(thread.looper), any(), intAdvCbCaptor.capture(), eq(TEST_HOSTNAME), any()
)
verify(mockInterfaceAdvertiser1).addService(eq(SERVICE_ID_1),
argThat { it.matches(SERVICE_1) })
argThat { it.matches(SERVICE_1) }, eq(null))
verify(mockInterfaceAdvertiser1).addService(eq(SERVICE_ID_2),
argThat { it.matches(expectedRenamed) })
argThat { it.matches(expectedRenamed) }, eq(null))
verify(mockInterfaceAdvertiser1).addService(eq(LONG_SERVICE_ID_1),
argThat { it.matches(LONG_SERVICE_1) })
argThat { it.matches(LONG_SERVICE_1) }, eq(null))
verify(mockInterfaceAdvertiser1).addService(eq(LONG_SERVICE_ID_2),
argThat { it.matches(expectedLongRenamed) })
argThat { it.matches(expectedLongRenamed) }, eq(null))
doReturn(false).`when`(mockInterfaceAdvertiser1).isProbing(SERVICE_ID_1)
postSync { intAdvCbCaptor.value.onRegisterServiceSucceeded(
@@ -278,7 +284,7 @@ class MdnsAdvertiserTest {
fun testRemoveService_whenAllServiceRemoved_thenUpdateHostName() {
val advertiser = MdnsAdvertiser(thread.looper, socketProvider, cb, mockDeps, sharedlog)
verify(mockDeps, times(1)).generateHostname()
postSync { advertiser.addService(SERVICE_ID_1, SERVICE_1) }
postSync { advertiser.addService(SERVICE_ID_1, SERVICE_1, null /* subtype */) }
postSync { advertiser.removeService(SERVICE_ID_1) }
verify(mockDeps, times(2)).generateHostname()
}

View File

@@ -117,7 +117,7 @@ class MdnsInterfaceAdvertiserTest {
knownServices.add(inv.getArgument(0))
-1
}.`when`(repository).addService(anyInt(), any())
}.`when`(repository).addService(anyInt(), any(), any())
doAnswer { inv ->
knownServices.remove(inv.getArgument(0))
null
@@ -278,8 +278,8 @@ class MdnsInterfaceAdvertiserTest {
doReturn(serviceId).`when`(testProbingInfo).serviceId
doReturn(testProbingInfo).`when`(repository).setServiceProbing(serviceId)
advertiser.addService(serviceId, serviceInfo)
verify(repository).addService(serviceId, serviceInfo)
advertiser.addService(serviceId, serviceInfo, null /* subtype */)
verify(repository).addService(serviceId, serviceInfo, null /* subtype */)
verify(prober).startProbing(testProbingInfo)
// Simulate probing success: continues to announcing

View File

@@ -44,6 +44,7 @@ import org.junit.runner.RunWith
private const val TEST_SERVICE_ID_1 = 42
private const val TEST_SERVICE_ID_2 = 43
private const val TEST_PORT = 12345
private const val TEST_SUBTYPE = "_subtype"
private val TEST_HOSTNAME = arrayOf("Android_000102030405060708090A0B0C0D0E0F", "local")
private val TEST_ADDRESSES = listOf(
LinkAddress(parseNumericAddress("192.0.2.111"), 24),
@@ -86,7 +87,8 @@ class MdnsRecordRepositoryTest {
fun testAddServiceAndProbe() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
assertEquals(0, repository.servicesCount)
assertEquals(-1, repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1))
assertEquals(-1, repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1,
null /* subtype */))
assertEquals(1, repository.servicesCount)
val probingInfo = repository.setServiceProbing(TEST_SERVICE_ID_1)
@@ -118,18 +120,18 @@ class MdnsRecordRepositoryTest {
@Test
fun testAddAndConflicts() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* subtype */)
assertFailsWith(NameConflictException::class) {
repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_1)
repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_1, null /* subtype */)
}
}
@Test
fun testInvalidReuseOfServiceId() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* subtype */)
assertFailsWith(IllegalArgumentException::class) {
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_2)
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_2, null /* subtype */)
}
}
@@ -138,7 +140,7 @@ class MdnsRecordRepositoryTest {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
assertFalse(repository.hasActiveService(TEST_SERVICE_ID_1))
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* subtype */)
assertTrue(repository.hasActiveService(TEST_SERVICE_ID_1))
val probingInfo = repository.setServiceProbing(TEST_SERVICE_ID_1)
@@ -179,6 +181,41 @@ class MdnsRecordRepositoryTest {
assertEquals(0, repository.servicesCount)
}
@Test
fun testExitAnnouncements_WithSubtype() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1, TEST_SUBTYPE)
repository.onAdvertisementSent(TEST_SERVICE_ID_1)
val exitAnnouncement = repository.exitService(TEST_SERVICE_ID_1)
assertNotNull(exitAnnouncement)
assertEquals(1, repository.servicesCount)
val packet = exitAnnouncement.getPacket(0)
assertEquals(0x8400 /* response, authoritative */, packet.flags)
assertEquals(0, packet.questions.size)
assertEquals(0, packet.authorityRecords.size)
assertEquals(0, packet.additionalRecords.size)
assertContentEquals(listOf(
MdnsPointerRecord(
arrayOf("_testservice", "_tcp", "local"),
0L /* receiptTimeMillis */,
true /* cacheFlush */,
0L /* ttlMillis */,
arrayOf("MyTestService", "_testservice", "_tcp", "local")),
MdnsPointerRecord(
arrayOf("_subtype", "_sub", "_testservice", "_tcp", "local"),
0L /* receiptTimeMillis */,
true /* cacheFlush */,
0L /* ttlMillis */,
arrayOf("MyTestService", "_testservice", "_tcp", "local")),
), packet.answers)
repository.removeService(TEST_SERVICE_ID_1)
assertEquals(0, repository.servicesCount)
}
@Test
fun testExitingServiceReAdded() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
@@ -186,7 +223,8 @@ class MdnsRecordRepositoryTest {
repository.onAdvertisementSent(TEST_SERVICE_ID_1)
repository.exitService(TEST_SERVICE_ID_1)
assertEquals(TEST_SERVICE_ID_1, repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_1))
assertEquals(TEST_SERVICE_ID_1,
repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_1, null /* subtype */))
assertEquals(1, repository.servicesCount)
repository.removeService(TEST_SERVICE_ID_2)
@@ -196,7 +234,8 @@ class MdnsRecordRepositoryTest {
@Test
fun testOnProbingSucceeded() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
val announcementInfo = repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
val announcementInfo = repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1,
TEST_SUBTYPE)
repository.onAdvertisementSent(TEST_SERVICE_ID_1)
val packet = announcementInfo.getPacket(0)
@@ -205,6 +244,7 @@ class MdnsRecordRepositoryTest {
assertEquals(0, packet.authorityRecords.size)
val serviceType = arrayOf("_testservice", "_tcp", "local")
val serviceSubtype = arrayOf(TEST_SUBTYPE, "_sub", "_testservice", "_tcp", "local")
val serviceName = arrayOf("MyTestService", "_testservice", "_tcp", "local")
val v4AddrRev = getReverseDnsAddress(TEST_ADDRESSES[0].address)
val v6Addr1Rev = getReverseDnsAddress(TEST_ADDRESSES[1].address)
@@ -250,6 +290,13 @@ class MdnsRecordRepositoryTest {
false /* cacheFlush */,
4500000L /* ttlMillis */,
serviceName),
MdnsPointerRecord(
serviceSubtype,
0L /* receiptTimeMillis */,
// Not a unique name owned by the announcer, so cacheFlush=false
false /* cacheFlush */,
4500000L /* ttlMillis */,
serviceName),
MdnsServiceRecord(
serviceName,
0L /* receiptTimeMillis */,
@@ -319,9 +366,21 @@ class MdnsRecordRepositoryTest {
@Test
fun testGetReply() {
doGetReplyTest(subtype = null)
}
@Test
fun testGetReply_WithSubtype() {
doGetReplyTest(TEST_SUBTYPE)
}
private fun doGetReplyTest(subtype: String?) {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
val questions = listOf(MdnsPointerRecord(arrayOf("_testservice", "_tcp", "local"),
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1, subtype)
val queriedName = if (subtype == null) arrayOf("_testservice", "_tcp", "local")
else arrayOf(subtype, "_sub", "_testservice", "_tcp", "local")
val questions = listOf(MdnsPointerRecord(queriedName,
0L /* receiptTimeMillis */,
false /* cacheFlush */,
// TTL and data is empty for a question
@@ -344,7 +403,7 @@ class MdnsRecordRepositoryTest {
assertEquals(listOf(
MdnsPointerRecord(
arrayOf("_testservice", "_tcp", "local"),
queriedName,
0L /* receiptTimeMillis */,
false /* cacheFlush */,
longTtl,
@@ -405,8 +464,8 @@ class MdnsRecordRepositoryTest {
@Test
fun testGetConflictingServices() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2)
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* subtype */)
repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2, null /* subtype */)
val packet = MdnsPacket(
0 /* flags */,
@@ -433,8 +492,8 @@ class MdnsRecordRepositoryTest {
@Test
fun testGetConflictingServices_IdenticalService() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME)
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2)
repository.addService(TEST_SERVICE_ID_1, TEST_SERVICE_1, null /* subtype */)
repository.addService(TEST_SERVICE_ID_2, TEST_SERVICE_2, null /* subtype */)
val otherTtlMillis = 1234L
val packet = MdnsPacket(
@@ -460,10 +519,13 @@ class MdnsRecordRepositoryTest {
}
}
private fun MdnsRecordRepository.initWithService(serviceId: Int, serviceInfo: NsdServiceInfo):
AnnouncementInfo {
private fun MdnsRecordRepository.initWithService(
serviceId: Int,
serviceInfo: NsdServiceInfo,
subtype: String? = null
): AnnouncementInfo {
updateAddresses(TEST_ADDRESSES)
addService(serviceId, serviceInfo)
addService(serviceId, serviceInfo, subtype)
val probingInfo = setServiceProbing(serviceId)
assertNotNull(probingInfo)
return onProbingSucceeded(probingInfo)

View File

@@ -41,6 +41,7 @@ import android.net.InetAddresses;
import android.net.Network;
import android.text.TextUtils;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.SharedLog;
import com.android.server.connectivity.mdns.MdnsServiceInfo.TextEntry;
import com.android.server.connectivity.mdns.MdnsServiceTypeClient.QueryTaskConfig;
@@ -52,6 +53,7 @@ import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.Captor;
import org.mockito.InOrder;
import org.mockito.Mock;
@@ -1056,6 +1058,87 @@ public class MdnsServiceTypeClientTests {
inOrder.verify(mockListenerTwo).onServiceRemoved(matchServiceName(otherInstance));
}
@Test
public void testProcessResponse_SubtypeDiscoveryLimitedToSubtype() {
client = new MdnsServiceTypeClient(
SERVICE_TYPE, mockSocketClient, currentThreadExecutor, mockNetwork, mockSharedLog);
final String matchingInstance = "instance1";
final String subtype = "_subtype";
final String otherInstance = "instance2";
final String ipV4Address = "192.0.2.0";
final String ipV6Address = "2001:db8::";
final MdnsSearchOptions options = MdnsSearchOptions.newBuilder()
// Search with different case. Note MdnsSearchOptions subtype doesn't start with "_"
.addSubtype("Subtype").build();
client.startSendAndReceive(mockListenerOne, options);
client.startSendAndReceive(mockListenerTwo, MdnsSearchOptions.getDefaultOptions());
// Complete response from instanceName
final MdnsPacket packetWithoutSubtype = createResponse(
matchingInstance, ipV4Address, 5353, SERVICE_TYPE_LABELS,
Collections.emptyMap() /* textAttributes */, TEST_TTL);
final MdnsPointerRecord originalPtr = (MdnsPointerRecord) CollectionUtils.findFirst(
packetWithoutSubtype.answers, r -> r instanceof MdnsPointerRecord);
// Add a subtype PTR record
final ArrayList<MdnsRecord> newAnswers = new ArrayList<>(packetWithoutSubtype.answers);
newAnswers.add(new MdnsPointerRecord(
// PTR should be _subtype._sub._type._tcp.local -> instance1._type._tcp.local
Stream.concat(Stream.of(subtype, "_sub"), Arrays.stream(SERVICE_TYPE_LABELS))
.toArray(String[]::new),
originalPtr.getReceiptTime(), originalPtr.getCacheFlush(), originalPtr.getTtl(),
originalPtr.getPointer()));
final MdnsPacket packetWithSubtype = new MdnsPacket(
packetWithoutSubtype.flags,
packetWithoutSubtype.questions,
newAnswers,
packetWithoutSubtype.authorityRecords,
packetWithoutSubtype.additionalRecords);
client.processResponse(packetWithSubtype, INTERFACE_INDEX, mockNetwork);
// Complete response from otherInstanceName, without subtype
client.processResponse(createResponse(
otherInstance, ipV4Address, 5353, SERVICE_TYPE_LABELS,
Collections.emptyMap() /* textAttributes */, TEST_TTL),
INTERFACE_INDEX, mockNetwork);
// Address update from otherInstanceName
client.processResponse(createResponse(
otherInstance, ipV6Address, 5353, SERVICE_TYPE_LABELS,
Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
// Goodbye from otherInstanceName
client.processResponse(createResponse(
otherInstance, ipV6Address, 5353, SERVICE_TYPE_LABELS,
Collections.emptyMap(), 0L /* ttl */), INTERFACE_INDEX, mockNetwork);
// mockListenerOne gets notified for the requested instance
final ArgumentMatcher<MdnsServiceInfo> subtypeInstanceMatcher = info ->
info.getServiceInstanceName().equals(matchingInstance)
&& info.getSubtypes().equals(Collections.singletonList(subtype));
verify(mockListenerOne).onServiceNameDiscovered(argThat(subtypeInstanceMatcher));
verify(mockListenerOne).onServiceFound(argThat(subtypeInstanceMatcher));
// ...but does not get any callback for the other instance
verify(mockListenerOne, never()).onServiceFound(matchServiceName(otherInstance));
verify(mockListenerOne, never()).onServiceNameDiscovered(matchServiceName(otherInstance));
verify(mockListenerOne, never()).onServiceUpdated(matchServiceName(otherInstance));
verify(mockListenerOne, never()).onServiceRemoved(matchServiceName(otherInstance));
// mockListenerTwo gets notified for both though
final InOrder inOrder = inOrder(mockListenerTwo);
inOrder.verify(mockListenerTwo).onServiceNameDiscovered(argThat(subtypeInstanceMatcher));
inOrder.verify(mockListenerTwo).onServiceFound(argThat(subtypeInstanceMatcher));
inOrder.verify(mockListenerTwo).onServiceNameDiscovered(matchServiceName(otherInstance));
inOrder.verify(mockListenerTwo).onServiceFound(matchServiceName(otherInstance));
inOrder.verify(mockListenerTwo).onServiceUpdated(matchServiceName(otherInstance));
inOrder.verify(mockListenerTwo).onServiceRemoved(matchServiceName(otherInstance));
}
@Test
public void testNotifyAllServicesRemoved() {
client = new MdnsServiceTypeClient(