From 74a8f475b2d9cf98e226c482f7fff5488d8b312e Mon Sep 17 00:00:00 2001 From: Patrick Rohr Date: Fri, 10 Jan 2025 10:32:30 -0800 Subject: [PATCH] Implement matching semantics for L2capNetworkSpecifier A server network is created by using ConnectivityManger#reserveNetwork, in which the app passes a specifier that sets: reservationRequest = { role = server headerCompression = 6lowpan remoteAddr = any (null) psm = any } This specifier must subsequently match the blanket reservation offer, and the reserved offer that is generated as a response: blanketOffer = { role = server headerCompression = any remoteAddr = any (null) psm = any } reservedOffer = { role = server headerCompression = 6lowpan remoteAddr = any (null) psm = 0x81 } And not the client network offer or a client network: clientOffer = { role = client headerCompression = any remoteAddr = any (null) psm = any } clientNetwork = { role = client headerCompression = 6lowpan remoteAddr = 01:02:03:04:05:06 psm = 0x81 } Similarly the client request must match both the clientOffer and clientNetweork, but not the reservedOffer (and technically blanketOffer): clientRequest = { role = client headerCompression = 6lowpan remoteAddr = 01:02:03:04:05:06 psm = 0x81 } Note, a request for TRANSPORT_BLUETOOTH with no specifier matches *any* specifier per specifier matching semantics in NetworkCapabilities. Test: L2capNetworkSpecifierTest Change-Id: Ia16926b463a54530ae6e88278466e92ed82bf852 --- .../android/net/L2capNetworkSpecifier.java | 25 ++++++++- .../net/cts/L2capNetworkSpecifierTest.kt | 54 +++++++++++++++++++ 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/framework/src/android/net/L2capNetworkSpecifier.java b/framework/src/android/net/L2capNetworkSpecifier.java index 3331123f96..3c95dd0ec2 100644 --- a/framework/src/android/net/L2capNetworkSpecifier.java +++ b/framework/src/android/net/L2capNetworkSpecifier.java @@ -251,8 +251,29 @@ public final class L2capNetworkSpecifier extends NetworkSpecifier implements Par /** @hide */ @Override public boolean canBeSatisfiedBy(NetworkSpecifier other) { - // TODO: implement matching semantics. - return false; + if (!(other instanceof L2capNetworkSpecifier)) return false; + final L2capNetworkSpecifier rhs = (L2capNetworkSpecifier) other; + + // A network / offer cannot be ROLE_ANY, but it is added for consistency. + if (mRole != rhs.mRole && mRole != ROLE_ANY && rhs.mRole != ROLE_ANY) { + return false; + } + + if (mHeaderCompression != rhs.mHeaderCompression + && mHeaderCompression != HEADER_COMPRESSION_ANY + && rhs.mHeaderCompression != HEADER_COMPRESSION_ANY) { + return false; + } + + if (!Objects.equals(mRemoteAddress, rhs.mRemoteAddress) + && mRemoteAddress != null && rhs.mRemoteAddress != null) { + return false; + } + + if (mPsm != rhs.mPsm && mPsm != PSM_ANY && rhs.mPsm != PSM_ANY) { + return false; + } + return true; } /** @hide */ diff --git a/tests/cts/net/src/android/net/cts/L2capNetworkSpecifierTest.kt b/tests/cts/net/src/android/net/cts/L2capNetworkSpecifierTest.kt index 51ccdd5b24..484cce8fea 100644 --- a/tests/cts/net/src/android/net/cts/L2capNetworkSpecifierTest.kt +++ b/tests/cts/net/src/android/net/cts/L2capNetworkSpecifierTest.kt @@ -18,8 +18,11 @@ package android.net.cts import android.net.L2capNetworkSpecifier import android.net.L2capNetworkSpecifier.HEADER_COMPRESSION_6LOWPAN +import android.net.L2capNetworkSpecifier.HEADER_COMPRESSION_ANY import android.net.L2capNetworkSpecifier.HEADER_COMPRESSION_NONE +import android.net.L2capNetworkSpecifier.PSM_ANY import android.net.L2capNetworkSpecifier.ROLE_CLIENT +import android.net.L2capNetworkSpecifier.ROLE_SERVER import android.net.MacAddress import android.os.Build import com.android.testutils.ConnectivityModuleTest @@ -27,6 +30,8 @@ import com.android.testutils.DevSdkIgnoreRule import com.android.testutils.DevSdkIgnoreRunner import com.android.testutils.assertParcelingIsLossless import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue import org.junit.Test import org.junit.runner.RunWith @@ -60,4 +65,53 @@ class L2capNetworkSpecifierTest { assertEquals(123, specifier.getPsm()) assertEquals(remoteMac, specifier.getRemoteAddress()) } + + @Test + fun testCanBeSatisfiedBy() { + val blanketOffer = L2capNetworkSpecifier.Builder() + .setRole(ROLE_SERVER) + .setHeaderCompression(HEADER_COMPRESSION_ANY) + .setPsm(PSM_ANY) + .build() + + val reservedOffer = L2capNetworkSpecifier.Builder() + .setRole(ROLE_SERVER) + .setHeaderCompression(HEADER_COMPRESSION_6LOWPAN) + .setPsm(42) + .build() + + val clientOffer = L2capNetworkSpecifier.Builder() + .setRole(ROLE_CLIENT) + .setHeaderCompression(HEADER_COMPRESSION_ANY) + .build() + + val serverReservation = L2capNetworkSpecifier.Builder() + .setRole(ROLE_SERVER) + .setHeaderCompression(HEADER_COMPRESSION_6LOWPAN) + .build() + + assertTrue(serverReservation.canBeSatisfiedBy(blanketOffer)) + assertTrue(serverReservation.canBeSatisfiedBy(reservedOffer)) + // Note: serverReservation can be filed using reserveNetwork, or it could be a regular + // request filed using requestNetwork. + assertFalse(serverReservation.canBeSatisfiedBy(clientOffer)) + + val clientRequest = L2capNetworkSpecifier.Builder() + .setRole(ROLE_CLIENT) + .setHeaderCompression(HEADER_COMPRESSION_6LOWPAN) + .setRemoteAddress(MacAddress.fromString("00:01:02:03:04:05")) + .setPsm(42) + .build() + + assertTrue(clientRequest.canBeSatisfiedBy(clientOffer)) + // Note: the BlanketOffer also includes a RES_ID_MATCH_ALL_RESERVATIONS. Since the + // clientRequest is not a reservation, it won't match that request to begin with. + assertFalse(clientRequest.canBeSatisfiedBy(blanketOffer)) + assertFalse(clientRequest.canBeSatisfiedBy(reservedOffer)) + + val matchAny = L2capNetworkSpecifier.Builder().build() + assertTrue(matchAny.canBeSatisfiedBy(blanketOffer)) + assertTrue(matchAny.canBeSatisfiedBy(reservedOffer)) + assertTrue(matchAny.canBeSatisfiedBy(clientOffer)) + } }