Added tests for QosCallback
Tests for the newly added QosCallback functionality
Bug: 155176305
Test: Added to cts/NetworkAgentTest
Change-Id: I29769fc8be074a5105bfe4ac34e30980c5f8744a
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index c505cef..ccc9416 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.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_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.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.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.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 @@
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 @@
private val agentsToCleanUp = mutableListOf<NetworkAgent>()
private val callbacksToCleanUp = mutableListOf<TestableNetworkCallback>()
+ private var qosTestSocket: Socket? = null
@Before
fun setUp() {
@@ -163,6 +182,7 @@
fun tearDown() {
agentsToCleanUp.forEach { it.unregister() }
callbacksToCleanUp.forEach { mCM.unregisterNetworkCallback(it) }
+ qosTestSocket?.close()
mHandlerThread.quitSafely()
instrumentation.getUiAutomation().dropShellPermissionIdentity()
}
@@ -228,6 +248,11 @@
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 @@
}
}
+ 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 @@
return foundCallback
}
+ inline fun <reified T : CallbackEntry> 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 <reified T : CallbackEntry> eventuallyExpect() =
history.poll(DEFAULT_TIMEOUT_MS) { it is T }.also {
assertNotNull(it, "Callback ${T::class} not received")
@@ -390,7 +429,7 @@
initialConfig: NetworkAgentConfig? = null,
expectedInitSignalStrengthThresholds: IntArray? = intArrayOf()
): Pair<TestableNetworkAgent, TestableNetworkCallback> {
- 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 @@
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<Network>())
@@ -934,4 +973,251 @@
// tearDown() will unregister the requests and agents
}
+
+ private class TestableQosCallback : QosCallback() {
+ val history = ArrayTrackRecord<CallbackEntry>().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 <reified T : CallbackEntry> expectCallback(): T {
+ val foundCallback = history.poll(DEFAULT_TIMEOUT_MS)
+ assertTrue(foundCallback is T, "Expected ${T::class} but found $foundCallback")
+ return foundCallback
+ }
+
+ inline fun <reified T : CallbackEntry> 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<TestableNetworkAgent, Socket> {
+ 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<OnRegisterQosCallback>().callbackId
+
+ assertFailsWith<QosCallbackRegistrationException>(
+ "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<OnUnregisterQosCallback> { 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<OnRegisterQosCallback>().callbackId
+
+ val uniqueSessionId = 4294967397
+ val sessId = 101
+
+ val attributes = createEpsAttributes(5)
+ assertEquals(attributes.qosIdentifier, 5)
+ agent.sendQosSessionAvailable(callbackId, sessId, attributes)
+ qosCallback.expectCallback<OnQosSessionAvailable> {
+ 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<OnQosSessionLost> {
+ 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<OnUnregisterQosCallback>()
+
+ 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<OnRegisterQosCallback>().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<OnQosSessionAvailable>()
+
+ // Check that onError is coming through correctly
+ agent.sendQosCallbackError(callbackId,
+ QosCallbackException.EX_TYPE_FILTER_NOT_SUPPORTED)
+ qosCallback.expectCallback<OnError> {
+ 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<OnRegisterQosCallback>().callbackId
+
+ mCM.registerQosCallback(info, executor, qosCallback2)
+ val callbackId2 = agent.expectCallback<OnRegisterQosCallback>().callbackId
+
+ val sessId1 = 101
+ val attributes1 = createEpsAttributes(1)
+
+ // Check #1
+ agent.sendQosSessionAvailable(callbackId1, sessId1, attributes1)
+ qosCallback1.expectCallback<OnQosSessionAvailable>()
+ qosCallback2.assertNoCallback()
+
+ // Check #2
+ val sessId2 = 102
+ val attributes2 = createEpsAttributes(2)
+ agent.sendQosSessionAvailable(callbackId2, sessId2, attributes2)
+ qosCallback1.assertNoCallback()
+ qosCallback2.expectCallback<OnQosSessionAvailable> { 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<OnError> {
+ it.ex.cause is NetworkReleasedException
+ }
+
+ qosCallback2.expectCallback<OnError> {
+ 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<InetSocketAddress>()
+ remoteAddresses.add(InetSocketAddress("2001:db8::123", 80))
+ return EpsBearerQosSessionAttributes(
+ qci, 2, 3, 4, 5,
+ remoteAddresses
+ )
+ }
}