Merge "Add APIs for discover/resolve on specific networks"

This commit is contained in:
Remi NGUYEN VAN
2022-02-04 12:07:38 +00:00
committed by Gerrit Code Review
4 changed files with 190 additions and 4 deletions

View File

@@ -3,6 +3,7 @@ package android.net.nsd {
public final class NsdManager { public final class NsdManager {
method public void discoverServices(String, int, android.net.nsd.NsdManager.DiscoveryListener); method public void discoverServices(String, int, android.net.nsd.NsdManager.DiscoveryListener);
method public void discoverServices(@NonNull String, int, @Nullable android.net.Network, @NonNull android.net.nsd.NsdManager.DiscoveryListener);
method public void registerService(android.net.nsd.NsdServiceInfo, int, android.net.nsd.NsdManager.RegistrationListener); method public void registerService(android.net.nsd.NsdServiceInfo, int, android.net.nsd.NsdManager.RegistrationListener);
method public void resolveService(android.net.nsd.NsdServiceInfo, android.net.nsd.NsdManager.ResolveListener); method public void resolveService(android.net.nsd.NsdServiceInfo, android.net.nsd.NsdManager.ResolveListener);
method public void stopServiceDiscovery(android.net.nsd.NsdManager.DiscoveryListener); method public void stopServiceDiscovery(android.net.nsd.NsdManager.DiscoveryListener);
@@ -43,12 +44,14 @@ package android.net.nsd {
method public int describeContents(); method public int describeContents();
method public java.util.Map<java.lang.String,byte[]> getAttributes(); method public java.util.Map<java.lang.String,byte[]> getAttributes();
method public java.net.InetAddress getHost(); method public java.net.InetAddress getHost();
method @Nullable public android.net.Network getNetwork();
method public int getPort(); method public int getPort();
method public String getServiceName(); method public String getServiceName();
method public String getServiceType(); method public String getServiceType();
method public void removeAttribute(String); method public void removeAttribute(String);
method public void setAttribute(String, String); method public void setAttribute(String, String);
method public void setHost(java.net.InetAddress); method public void setHost(java.net.InetAddress);
method public void setNetwork(@Nullable android.net.Network);
method public void setPort(int); method public void setPort(int);
method public void setServiceName(String); method public void setServiceName(String);
method public void setServiceType(String); method public void setServiceType(String);

View File

@@ -15,6 +15,19 @@
*/ */
package android.net.cts package android.net.cts
import android.Manifest.permission.MANAGE_TEST_NETWORKS
import android.net.ConnectivityManager
import android.net.ConnectivityManager.NetworkCallback
import android.net.LinkProperties
import android.net.Network
import android.net.NetworkAgentConfig
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
import android.net.NetworkCapabilities.TRANSPORT_TEST
import android.net.NetworkRequest
import android.net.TestNetworkInterface
import android.net.TestNetworkManager
import android.net.TestNetworkSpecifier
import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStarted import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStarted
import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStopped import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStopped
import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.ServiceFound import android.net.cts.NsdManagerTest.NsdDiscoveryRecord.DiscoveryEvent.ServiceFound
@@ -32,14 +45,27 @@ import android.net.nsd.NsdManager.DiscoveryListener
import android.net.nsd.NsdManager.RegistrationListener import android.net.nsd.NsdManager.RegistrationListener
import android.net.nsd.NsdManager.ResolveListener import android.net.nsd.NsdManager.ResolveListener
import android.net.nsd.NsdServiceInfo import android.net.nsd.NsdServiceInfo
import android.os.HandlerThread
import android.platform.test.annotations.AppModeFull import android.platform.test.annotations.AppModeFull
import android.util.Log import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4 import androidx.test.runner.AndroidJUnit4
import com.android.net.module.util.ArrayTrackRecord import com.android.net.module.util.ArrayTrackRecord
import com.android.net.module.util.TrackRecord import com.android.net.module.util.TrackRecord
import com.android.networkstack.apishim.ConstantsShim
import com.android.networkstack.apishim.NsdShimImpl
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.SC_V2
import com.android.testutils.TestableNetworkAgent
import com.android.testutils.TestableNetworkCallback
import com.android.testutils.runAsShell
import com.android.testutils.tryTest
import org.junit.After
import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import java.net.ServerSocket import java.net.ServerSocket
@@ -57,12 +83,37 @@ private const val SERVICE_TYPE = "_nmt._tcp"
private const val TIMEOUT_MS = 2000L private const val TIMEOUT_MS = 2000L
private const val DBG = false private const val DBG = false
private val nsdShim = NsdShimImpl.newInstance()
@AppModeFull(reason = "Socket cannot bind in instant app mode") @AppModeFull(reason = "Socket cannot bind in instant app mode")
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class NsdManagerTest { class NsdManagerTest {
// NsdManager is not updatable before S, so tests do not need to be backwards compatible
@get:Rule
val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = SC_V2)
private val context by lazy { InstrumentationRegistry.getInstrumentation().context } private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
private val nsdManager by lazy { context.getSystemService(NsdManager::class.java) } private val nsdManager by lazy { context.getSystemService(NsdManager::class.java) }
private val cm by lazy { context.getSystemService(ConnectivityManager::class.java) }
private val serviceName = "NsdTest%09d".format(Random().nextInt(1_000_000_000)) private val serviceName = "NsdTest%09d".format(Random().nextInt(1_000_000_000))
private val handlerThread = HandlerThread(NsdManagerTest::class.java.simpleName)
private lateinit var testNetwork1: TestTapNetwork
private lateinit var testNetwork2: TestTapNetwork
private class TestTapNetwork(
val iface: TestNetworkInterface,
val requestCb: NetworkCallback,
val agent: TestableNetworkAgent,
val network: Network
) {
fun close(cm: ConnectivityManager) {
cm.unregisterNetworkCallback(requestCb)
agent.unregister()
iface.fileDescriptor.close()
}
}
private interface NsdEvent private interface NsdEvent
private open class NsdRecord<T : NsdEvent> private constructor( private open class NsdRecord<T : NsdEvent> private constructor(
@@ -163,9 +214,14 @@ class NsdManagerTest {
add(ServiceLost(si)) add(ServiceLost(si))
} }
fun waitForServiceDiscovered(serviceName: String): NsdServiceInfo { fun waitForServiceDiscovered(
serviceName: String,
expectedNetwork: Network? = null
): NsdServiceInfo {
return expectCallbackEventually<ServiceFound> { return expectCallbackEventually<ServiceFound> {
it.serviceInfo.serviceName == serviceName it.serviceInfo.serviceName == serviceName &&
(expectedNetwork == null ||
expectedNetwork == nsdShim.getNetwork(it.serviceInfo))
}.serviceInfo }.serviceInfo
} }
} }
@@ -188,6 +244,52 @@ class NsdManagerTest {
} }
} }
@Before
fun setUp() {
handlerThread.start()
runAsShell(MANAGE_TEST_NETWORKS) {
testNetwork1 = createTestNetwork()
testNetwork2 = createTestNetwork()
}
}
private fun createTestNetwork(): TestTapNetwork {
val tnm = context.getSystemService(TestNetworkManager::class.java)
val iface = tnm.createTapInterface()
val cb = TestableNetworkCallback()
val testNetworkSpecifier = TestNetworkSpecifier(iface.interfaceName)
cm.requestNetwork(NetworkRequest.Builder()
.removeCapability(NET_CAPABILITY_TRUSTED)
.addTransportType(TRANSPORT_TEST)
.setNetworkSpecifier(testNetworkSpecifier)
.build(), cb)
val agent = TestableNetworkAgent(context, handlerThread.looper,
NetworkCapabilities().apply {
removeCapability(NET_CAPABILITY_TRUSTED)
addTransportType(TRANSPORT_TEST)
setNetworkSpecifier(testNetworkSpecifier)
},
LinkProperties().apply {
interfaceName = iface.interfaceName
},
NetworkAgentConfig.Builder().build())
val network = agent.register()
agent.markConnected()
// The network has no INTERNET capability, so will be marked validated immediately
cb.expectAvailableThenValidatedCallbacks(network)
return TestTapNetwork(iface, cb, agent, network)
}
@After
fun tearDown() {
runAsShell(MANAGE_TEST_NETWORKS) {
testNetwork1.close(cm)
testNetwork2.close(cm)
}
handlerThread.quitSafely()
}
@Test @Test
fun testNsdManager() { fun testNsdManager() {
val si = NsdServiceInfo() val si = NsdServiceInfo()
@@ -298,6 +400,84 @@ class NsdManagerTest {
registrationRecord2.expectCallback<ServiceUnregistered>() registrationRecord2.expectCallback<ServiceUnregistered>()
} }
@Test
fun testNsdManager_DiscoverOnNetwork() {
// This tests requires shims supporting T+ APIs (discovering on specific network)
assumeTrue(ConstantsShim.VERSION > SC_V2)
val si = NsdServiceInfo()
si.serviceType = SERVICE_TYPE
si.serviceName = this.serviceName
si.port = 12345 // Test won't try to connect so port does not matter
val registrationRecord = NsdRegistrationRecord()
val registeredInfo = registerService(registrationRecord, si)
tryTest {
val discoveryRecord = NsdDiscoveryRecord()
nsdShim.discoverServices(nsdManager, SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD,
testNetwork1.network, discoveryRecord)
val foundInfo = discoveryRecord.waitForServiceDiscovered(
serviceName, testNetwork1.network)
assertEquals(testNetwork1.network, nsdShim.getNetwork(foundInfo))
// Rewind to ensure the service is not found on the other interface
discoveryRecord.nextEvents.rewind(0)
assertNull(discoveryRecord.nextEvents.poll(timeoutMs = 100L) {
it is ServiceFound &&
it.serviceInfo.serviceName == registeredInfo.serviceName &&
nsdShim.getNetwork(it.serviceInfo) != testNetwork1.network
}, "The service should not be found on this network")
} cleanup {
nsdManager.unregisterService(registrationRecord)
}
}
@Test
fun testNsdManager_ResolveOnNetwork() {
// This tests requires shims supporting T+ APIs (NsdServiceInfo.network)
assumeTrue(ConstantsShim.VERSION > SC_V2)
val si = NsdServiceInfo()
si.serviceType = SERVICE_TYPE
si.serviceName = this.serviceName
si.port = 12345 // Test won't try to connect so port does not matter
val registrationRecord = NsdRegistrationRecord()
val registeredInfo = registerService(registrationRecord, si)
tryTest {
val resolveRecord = NsdResolveRecord()
val discoveryRecord = NsdDiscoveryRecord()
nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryRecord)
val foundInfo1 = discoveryRecord.waitForServiceDiscovered(
serviceName, testNetwork1.network)
assertEquals(testNetwork1.network, nsdShim.getNetwork(foundInfo1))
// Rewind as the service could be found on each interface in any order
discoveryRecord.nextEvents.rewind(0)
val foundInfo2 = discoveryRecord.waitForServiceDiscovered(
serviceName, testNetwork2.network)
assertEquals(testNetwork2.network, nsdShim.getNetwork(foundInfo2))
nsdManager.resolveService(foundInfo1, resolveRecord)
val cb = resolveRecord.expectCallback<ServiceResolved>()
cb.serviceInfo.let {
// Resolved service type has leading dot
assertEquals(".$SERVICE_TYPE", it.serviceType)
assertEquals(registeredInfo.serviceName, it.serviceName)
assertEquals(si.port, it.port)
assertEquals(testNetwork1.network, nsdShim.getNetwork(it))
}
// TODO: check that MDNS packets are sent only on testNetwork1.
} cleanupStep {
nsdManager.unregisterService(registrationRecord)
} cleanup {
registrationRecord.expectCallback<ServiceUnregistered>()
}
}
/** /**
* Register a service and return its registration record. * Register a service and return its registration record.
*/ */

View File

@@ -21,6 +21,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import android.net.Network;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcel; import android.os.Parcel;
@@ -123,6 +124,7 @@ public class NsdServiceInfoTest {
fullInfo.setServiceType("_kitten._tcp"); fullInfo.setServiceType("_kitten._tcp");
fullInfo.setPort(4242); fullInfo.setPort(4242);
fullInfo.setHost(LOCALHOST); fullInfo.setHost(LOCALHOST);
fullInfo.setNetwork(new Network(123));
checkParcelable(fullInfo); checkParcelable(fullInfo);
NsdServiceInfo noHostInfo = new NsdServiceInfo(); NsdServiceInfo noHostInfo = new NsdServiceInfo();
@@ -172,6 +174,7 @@ public class NsdServiceInfoTest {
assertEquals(original.getServiceType(), result.getServiceType()); assertEquals(original.getServiceType(), result.getServiceType());
assertEquals(original.getHost(), result.getHost()); assertEquals(original.getHost(), result.getHost());
assertTrue(original.getPort() == result.getPort()); assertTrue(original.getPort() == result.getPort());
assertEquals(original.getNetwork(), result.getNetwork());
// Assert equality of attribute map. // Assert equality of attribute map.
Map<String, byte[]> originalMap = original.getAttributes(); Map<String, byte[]> originalMap = original.getAttributes();

View File

@@ -218,14 +218,14 @@ public class NsdServiceTest {
client.discoverServices("a_type", PROTOCOL, listener2); client.discoverServices("a_type", PROTOCOL, listener2);
waitForIdle(); waitForIdle();
verify(mDaemon, times(1)).maybeStart(); verify(mDaemon, times(1)).maybeStart();
verifyDaemonCommand("discover 3 a_type"); verifyDaemonCommand("discover 3 a_type 0");
// Client resolve request // Client resolve request
NsdManager.ResolveListener listener3 = mock(NsdManager.ResolveListener.class); NsdManager.ResolveListener listener3 = mock(NsdManager.ResolveListener.class);
client.resolveService(request, listener3); client.resolveService(request, listener3);
waitForIdle(); waitForIdle();
verify(mDaemon, times(1)).maybeStart(); verify(mDaemon, times(1)).maybeStart();
verifyDaemonCommand("resolve 4 a_name a_type local."); verifyDaemonCommand("resolve 4 a_name a_type local. 0");
// Client disconnects, stop the daemon after CLEANUP_DELAY_MS. // Client disconnects, stop the daemon after CLEANUP_DELAY_MS.
deathRecipient.binderDied(); deathRecipient.binderDied();