From 763f3a0fb319daa417ee860c5af3a24dbdf4c508 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Wed, 23 Jun 2021 05:50:54 +0000 Subject: [PATCH] Added tests for QosCallback Tests for the newly added QosCallback functionality Bug: 155176305 Test: Added to cts/NetworkAgentTest Original-Change: https://android-review.googlesource.com/1558648 Merged-In: I29769fc8be074a5105bfe4ac34e30980c5f8744a Change-Id: I29769fc8be074a5105bfe4ac34e30980c5f8744a --- .../src/android/net/cts/NetworkAgentTest.kt | 294 +++++++++++++++++- 1 file changed, 290 insertions(+), 4 deletions(-) diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt index c505cefef9..ccc941625c 100644 --- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt +++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt @@ -29,9 +29,9 @@ import android.net.LinkProperties import android.net.NattKeepalivePacketData import android.net.Network import android.net.NetworkAgent +import android.net.NetworkAgentConfig import android.net.NetworkAgent.INVALID_NETWORK import android.net.NetworkAgent.VALID_NETWORK -import android.net.NetworkAgentConfig import android.net.NetworkCapabilities import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED @@ -46,9 +46,17 @@ import android.net.NetworkCapabilities.TRANSPORT_TEST import android.net.NetworkCapabilities.TRANSPORT_VPN import android.net.NetworkInfo import android.net.NetworkProvider +import android.net.NetworkReleasedException import android.net.NetworkRequest import android.net.NetworkScore import android.net.RouteInfo +import android.net.QosCallback +import android.net.QosCallbackException +import android.net.QosCallback.QosCallbackRegistrationException +import android.net.QosFilter +import android.net.QosSession +import android.net.QosSessionAttributes +import android.net.QosSocketInfo import android.net.SocketKeepalive import android.net.Uri import android.net.VpnManager @@ -59,12 +67,17 @@ import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnBan import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnNetworkCreated import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnNetworkDestroyed import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnNetworkUnwanted +import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnRegisterQosCallback import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnRemoveKeepalivePacketFilter import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnSaveAcceptUnvalidated import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnSignalStrengthThresholdsUpdated import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnStartSocketKeepalive import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnStopSocketKeepalive +import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnUnregisterQosCallback import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnValidationStatus +import android.net.cts.NetworkAgentTest.TestableQosCallback.CallbackEntry.OnError +import android.net.cts.NetworkAgentTest.TestableQosCallback.CallbackEntry.OnQosSessionAvailable +import android.net.cts.NetworkAgentTest.TestableQosCallback.CallbackEntry.OnQosSessionLost import android.os.Build import android.os.Handler import android.os.HandlerThread @@ -72,6 +85,7 @@ import android.os.Looper import android.os.Message import android.os.SystemClock import android.telephony.TelephonyManager +import android.telephony.data.EpsBearerQosSessionAttributes import android.util.DebugUtils.valueToString import androidx.test.InstrumentationRegistry import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity @@ -97,9 +111,13 @@ import org.mockito.Mockito.doReturn import org.mockito.Mockito.mock import org.mockito.Mockito.timeout import org.mockito.Mockito.verify +import java.net.InetAddress +import java.net.InetSocketAddress +import java.net.Socket import java.time.Duration import java.util.Arrays import java.util.UUID +import java.util.concurrent.Executors import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertFalse @@ -143,7 +161,7 @@ class NetworkAgentTest { private val LOCAL_IPV4_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.1") private val REMOTE_IPV4_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.2") - private val mCM = realContext.getSystemService(ConnectivityManager::class.java) + private val mCM = realContext.getSystemService(ConnectivityManager::class.java)!! private val mHandlerThread = HandlerThread("${javaClass.simpleName} handler thread") private val mFakeConnectivityService = FakeConnectivityService() @@ -152,6 +170,7 @@ class NetworkAgentTest { private val agentsToCleanUp = mutableListOf() private val callbacksToCleanUp = mutableListOf() + private var qosTestSocket: Socket? = null @Before fun setUp() { @@ -163,6 +182,7 @@ class NetworkAgentTest { fun tearDown() { agentsToCleanUp.forEach { it.unregister() } callbacksToCleanUp.forEach { mCM.unregisterNetworkCallback(it) } + qosTestSocket?.close() mHandlerThread.quitSafely() instrumentation.getUiAutomation().dropShellPermissionIdentity() } @@ -228,6 +248,11 @@ class NetworkAgentTest { data class OnSignalStrengthThresholdsUpdated(val thresholds: IntArray) : CallbackEntry() object OnNetworkCreated : CallbackEntry() object OnNetworkDestroyed : CallbackEntry() + data class OnRegisterQosCallback( + val callbackId: Int, + val filter: QosFilter + ) : CallbackEntry() + data class OnUnregisterQosCallback(val callbackId: Int) : CallbackEntry() } override fun onBandwidthUpdateRequested() { @@ -276,6 +301,14 @@ class NetworkAgentTest { } } + override fun onQosCallbackRegistered(qosCallbackId: Int, filter: QosFilter) { + history.add(OnRegisterQosCallback(qosCallbackId, filter)) + } + + override fun onQosCallbackUnregistered(qosCallbackId: Int) { + history.add(OnUnregisterQosCallback(qosCallbackId)) + } + override fun onValidationStatus(status: Int, uri: Uri?) { history.add(OnValidationStatus(status, uri)) } @@ -307,6 +340,12 @@ class NetworkAgentTest { return foundCallback } + inline fun expectCallback(valid: (T) -> Boolean) { + val foundCallback = history.poll(DEFAULT_TIMEOUT_MS) + assertTrue(foundCallback is T, "Expected ${T::class} but found $foundCallback") + assertTrue(valid(foundCallback), "Unexpected callback : $foundCallback") + } + inline fun eventuallyExpect() = history.poll(DEFAULT_TIMEOUT_MS) { it is T }.also { assertNotNull(it, "Callback ${T::class} not received") @@ -390,7 +429,7 @@ class NetworkAgentTest { initialConfig: NetworkAgentConfig? = null, expectedInitSignalStrengthThresholds: IntArray? = intArrayOf() ): Pair { - val callback = TestableNetworkCallback(timeoutMs = DEFAULT_TIMEOUT_MS) + val callback = TestableNetworkCallback() // Ensure this NetworkAgent is never unneeded by filing a request with its specifier. requestNetwork(makeTestNetworkRequest(specifier = specifier), callback) val agent = createNetworkAgent(context, specifier, initialConfig = initialConfig) @@ -651,7 +690,7 @@ class NetworkAgentTest { assertFalse(vpnNc.hasCapability(NET_CAPABILITY_NOT_VPN)) assertTrue(hasAllTransports(vpnNc, defaultNetworkTransports), "VPN transports ${Arrays.toString(vpnNc.transportTypes)}" + - " lacking transports from ${Arrays.toString(defaultNetworkTransports)}") + " lacking transports from ${Arrays.toString(defaultNetworkTransports)}") // Check that when no underlying networks are announced the underlying transport disappears. agent.setUnderlyingNetworks(listOf()) @@ -934,4 +973,251 @@ class NetworkAgentTest { // tearDown() will unregister the requests and agents } + + private class TestableQosCallback : QosCallback() { + val history = ArrayTrackRecord().newReadHead() + + sealed class CallbackEntry { + data class OnQosSessionAvailable(val sess: QosSession, val attr: QosSessionAttributes) + : CallbackEntry() + data class OnQosSessionLost(val sess: QosSession) + : CallbackEntry() + data class OnError(val ex: QosCallbackException) + : CallbackEntry() + } + + override fun onQosSessionAvailable(sess: QosSession, attr: QosSessionAttributes) { + history.add(OnQosSessionAvailable(sess, attr)) + } + + override fun onQosSessionLost(sess: QosSession) { + history.add(OnQosSessionLost(sess)) + } + + override fun onError(ex: QosCallbackException) { + history.add(OnError(ex)) + } + + inline fun expectCallback(): T { + val foundCallback = history.poll(DEFAULT_TIMEOUT_MS) + assertTrue(foundCallback is T, "Expected ${T::class} but found $foundCallback") + return foundCallback + } + + inline fun expectCallback(valid: (T) -> Boolean) { + val foundCallback = history.poll(DEFAULT_TIMEOUT_MS) + assertTrue(foundCallback is T, "Expected ${T::class} but found $foundCallback") + assertTrue(valid(foundCallback), "Unexpected callback : $foundCallback") + } + + fun assertNoCallback() { + assertNull(history.poll(NO_CALLBACK_TIMEOUT), + "Callback received") + } + } + + private fun setupForQosCallbackTesting(): Pair { + val request = NetworkRequest.Builder() + .clearCapabilities() + .addTransportType(TRANSPORT_TEST) + .build() + + val callback = TestableNetworkCallback(timeoutMs = DEFAULT_TIMEOUT_MS) + requestNetwork(request, callback) + val (agent, _) = createConnectedNetworkAgent() + + qosTestSocket = assertNotNull(agent.network?.socketFactory?.createSocket()).also { + it.bind(InetSocketAddress(InetAddress.getLoopbackAddress(), 0)) + } + return Pair(agent, qosTestSocket!!) + } + + @Test + fun testQosCallbackRegisterWithUnregister() { + val (agent, socket) = setupForQosCallbackTesting() + + val qosCallback = TestableQosCallback() + var callbackId = -1 + Executors.newSingleThreadExecutor().let { executor -> + try { + val info = QosSocketInfo(agent.network!!, socket) + mCM.registerQosCallback(info, executor, qosCallback) + callbackId = agent.expectCallback().callbackId + + assertFailsWith( + "The same callback cannot be " + + "registered more than once without first being unregistered") { + mCM.registerQosCallback(info, executor, qosCallback) + } + } finally { + socket.close() + mCM.unregisterQosCallback(qosCallback) + agent.expectCallback { it.callbackId == callbackId } + executor.shutdown() + } + } + } + + @Test + fun testQosCallbackOnQosSession() { + val (agent, socket) = setupForQosCallbackTesting() + val qosCallback = TestableQosCallback() + Executors.newSingleThreadExecutor().let { executor -> + try { + val info = QosSocketInfo(agent.network!!, socket) + mCM.registerQosCallback(info, executor, qosCallback) + val callbackId = agent.expectCallback().callbackId + + val uniqueSessionId = 4294967397 + val sessId = 101 + + val attributes = createEpsAttributes(5) + assertEquals(attributes.qosIdentifier, 5) + agent.sendQosSessionAvailable(callbackId, sessId, attributes) + qosCallback.expectCallback { + it.sess.sessionId == sessId && it.sess.uniqueId == uniqueSessionId && + it.sess.sessionType == QosSession.TYPE_EPS_BEARER + } + + agent.sendQosSessionLost(callbackId, sessId, QosSession.TYPE_EPS_BEARER) + qosCallback.expectCallback { + it.sess.sessionId == sessId && it.sess.uniqueId == uniqueSessionId && + it.sess.sessionType == QosSession.TYPE_EPS_BEARER + } + + // Make sure that we don't get more qos callbacks + mCM.unregisterQosCallback(qosCallback) + agent.expectCallback() + + agent.sendQosSessionLost(callbackId, sessId, QosSession.TYPE_EPS_BEARER) + qosCallback.assertNoCallback() + } finally { + socket.close() + + // safety precaution + mCM.unregisterQosCallback(qosCallback) + + executor.shutdown() + } + } + } + + @Test + fun testQosCallbackOnError() { + val (agent, socket) = setupForQosCallbackTesting() + val qosCallback = TestableQosCallback() + Executors.newSingleThreadExecutor().let { executor -> + try { + val info = QosSocketInfo(agent.network!!, socket) + mCM.registerQosCallback(info, executor, qosCallback) + val callbackId = agent.expectCallback().callbackId + + val sessId = 101 + val attributes = createEpsAttributes() + + // Double check that this is wired up and ready to go + agent.sendQosSessionAvailable(callbackId, sessId, attributes) + qosCallback.expectCallback() + + // Check that onError is coming through correctly + agent.sendQosCallbackError(callbackId, + QosCallbackException.EX_TYPE_FILTER_NOT_SUPPORTED) + qosCallback.expectCallback { + it.ex.cause is UnsupportedOperationException + } + + // Ensure that when an error occurs the callback was also unregistered + agent.sendQosSessionLost(callbackId, sessId, QosSession.TYPE_EPS_BEARER) + qosCallback.assertNoCallback() + } finally { + socket.close() + + // Make sure that the callback is fully unregistered + mCM.unregisterQosCallback(qosCallback) + + executor.shutdown() + } + } + } + + @Test + fun testQosCallbackIdsAreMappedCorrectly() { + val (agent, socket) = setupForQosCallbackTesting() + val qosCallback1 = TestableQosCallback() + val qosCallback2 = TestableQosCallback() + Executors.newSingleThreadExecutor().let { executor -> + try { + val info = QosSocketInfo(agent.network!!, socket) + mCM.registerQosCallback(info, executor, qosCallback1) + val callbackId1 = agent.expectCallback().callbackId + + mCM.registerQosCallback(info, executor, qosCallback2) + val callbackId2 = agent.expectCallback().callbackId + + val sessId1 = 101 + val attributes1 = createEpsAttributes(1) + + // Check #1 + agent.sendQosSessionAvailable(callbackId1, sessId1, attributes1) + qosCallback1.expectCallback() + qosCallback2.assertNoCallback() + + // Check #2 + val sessId2 = 102 + val attributes2 = createEpsAttributes(2) + agent.sendQosSessionAvailable(callbackId2, sessId2, attributes2) + qosCallback1.assertNoCallback() + qosCallback2.expectCallback { sessId2 == it.sess.sessionId } + } finally { + socket.close() + + // Make sure that the callback is fully unregistered + mCM.unregisterQosCallback(qosCallback1) + mCM.unregisterQosCallback(qosCallback2) + + executor.shutdown() + } + } + } + + @Test + fun testQosCallbackWhenNetworkReleased() { + val (agent, socket) = setupForQosCallbackTesting() + Executors.newSingleThreadExecutor().let { executor -> + try { + val qosCallback1 = TestableQosCallback() + val qosCallback2 = TestableQosCallback() + try { + val info = QosSocketInfo(agent.network!!, socket) + mCM.registerQosCallback(info, executor, qosCallback1) + mCM.registerQosCallback(info, executor, qosCallback2) + agent.unregister() + + qosCallback1.expectCallback { + it.ex.cause is NetworkReleasedException + } + + qosCallback2.expectCallback { + it.ex.cause is NetworkReleasedException + } + } finally { + socket.close() + mCM.unregisterQosCallback(qosCallback1) + mCM.unregisterQosCallback(qosCallback2) + } + } finally { + socket.close() + executor.shutdown() + } + } + } + + private fun createEpsAttributes(qci: Int = 1): EpsBearerQosSessionAttributes { + val remoteAddresses = ArrayList() + remoteAddresses.add(InetSocketAddress("2001:db8::123", 80)) + return EpsBearerQosSessionAttributes( + qci, 2, 3, 4, 5, + remoteAddresses + ) + } }