diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index 4a503970e2..c6b62eec07 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -359,6 +359,7 @@ public final class NetworkCapabilities implements Parcelable { mSubIds = new ArraySet<>(); mUnderlyingNetworks = null; mEnterpriseId = 0; + mReservationId = RES_ID_UNSET; } /** @@ -393,6 +394,7 @@ public final class NetworkCapabilities implements Parcelable { // necessary. mUnderlyingNetworks = nc.mUnderlyingNetworks; mEnterpriseId = nc.mEnterpriseId; + mReservationId = nc.mReservationId; } /** @@ -2233,7 +2235,8 @@ public final class NetworkCapabilities implements Parcelable { && (onlyImmutable || satisfiedByUids(nc)) && (onlyImmutable || satisfiedBySSID(nc)) && (onlyImmutable || satisfiedByRequestor(nc)) - && (onlyImmutable || satisfiedBySubscriptionIds(nc))); + && (onlyImmutable || satisfiedBySubscriptionIds(nc))) + && satisfiedByReservationId(nc); } /** @@ -2347,7 +2350,8 @@ public final class NetworkCapabilities implements Parcelable { && equalsAdministratorUids(that) && equalsSubscriptionIds(that) && equalsUnderlyingNetworks(that) - && equalsEnterpriseCapabilitiesId(that); + && equalsEnterpriseCapabilitiesId(that) + && equalsReservationId(that); } @Override @@ -2373,7 +2377,9 @@ public final class NetworkCapabilities implements Parcelable { + Arrays.hashCode(mAdministratorUids) * 67 + Objects.hashCode(mSubIds) * 71 + Objects.hashCode(mUnderlyingNetworks) * 73 - + mEnterpriseId * 79; + + mEnterpriseId * 79 + + mReservationId * 83; + } @Override @@ -2411,6 +2417,7 @@ public final class NetworkCapabilities implements Parcelable { dest.writeIntArray(CollectionUtils.toIntArray(mSubIds)); dest.writeTypedList(mUnderlyingNetworks); dest.writeInt(mEnterpriseId & ALL_VALID_ENTERPRISE_IDS); + dest.writeInt(mReservationId); } public static final @android.annotation.NonNull Creator CREATOR = @@ -2446,6 +2453,7 @@ public final class NetworkCapabilities implements Parcelable { } netCap.setUnderlyingNetworks(in.createTypedArrayList(Network.CREATOR)); netCap.mEnterpriseId = in.readInt() & ALL_VALID_ENTERPRISE_IDS; + netCap.mReservationId = in.readInt(); return netCap; } @Override @@ -2548,6 +2556,11 @@ public final class NetworkCapabilities implements Parcelable { NetworkCapabilities::enterpriseIdNameOf, "&"); } + if (mReservationId != RES_ID_UNSET) { + final boolean isReservationOffer = (mReservationId == RES_ID_MATCH_ALL_RESERVATIONS); + sb.append(" ReservationId: ").append(isReservationOffer ? "*" : mReservationId); + } + sb.append(" UnderlyingNetworks: "); if (mUnderlyingNetworks != null) { sb.append("["); @@ -2875,6 +2888,65 @@ public final class NetworkCapabilities implements Parcelable { return false; } + /** + * The reservation ID used by non-reservable Networks and "regular" NetworkOffers. + * + * Note that {@code NetworkRequest#FIRST_REQUEST_ID} is 1; + * @hide + */ + public static final int RES_ID_UNSET = 0; + + /** + * The reservation ID used by special NetworkOffers that handle RESERVATION requests. + * + * NetworkOffers with {@code RES_ID_MATCH_ALL_RESERVATIONS} *only* receive onNetworkNeeded() + * callbacks for {@code NetworkRequest.Type.RESERVATION}. + * @hide + */ + public static final int RES_ID_MATCH_ALL_RESERVATIONS = -1; + + /** + * Unique ID that identifies the network reservation. + */ + private int mReservationId; + + /** + * Returns the reservation ID + * @hide + */ + public int getReservationId() { + return mReservationId; + } + + /** + * Set the reservation ID + * @hide + */ + public void setReservationId(int resId) { + mReservationId = resId; + } + + private boolean equalsReservationId(@NonNull NetworkCapabilities nc) { + return mReservationId == nc.mReservationId; + } + + private boolean satisfiedByReservationId(@NonNull NetworkCapabilities nc) { + if (mReservationId == RES_ID_UNSET) { + // To maintain regular NetworkRequest semantics, a request with a zero reservationId + // matches an offer or network with any reservationId except MATCH_ALL_RESERVATIONS. + return nc.mReservationId != RES_ID_MATCH_ALL_RESERVATIONS; + } + // A request with a non-zero reservationId matches only an offer or network with that exact + // reservationId (required to match the network that will eventually come up) or + // MATCH_ALL_RESERVATIONS (required to match the blanket reservation offer). + if (nc.mReservationId == RES_ID_MATCH_ALL_RESERVATIONS) { + return true; + } + return mReservationId == nc.mReservationId; + } + + + /** * Returns a bitmask of all the applicable redactions (based on the permissions held by the * receiving app) to be performed on this object. diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java index 4cc3c613cb..5ae25abf80 100644 --- a/framework/src/android/net/NetworkRequest.java +++ b/framework/src/android/net/NetworkRequest.java @@ -258,6 +258,12 @@ public class NetworkRequest implements Parcelable { } requestId = rId; networkCapabilities = nc; + if (type == Type.RESERVATION) { + // Conceptually, the reservationId is not related to the requestId; however, the + // requestId fulfills the same uniqueness requirements that are needed for the + // reservationId, so it can be reused for this purpose. + networkCapabilities.setReservationId(rId); + } this.legacyType = legacyType; this.type = type; } diff --git a/tests/common/java/android/net/NetworkCapabilitiesTest.java b/tests/common/java/android/net/NetworkCapabilitiesTest.java index 0f0e2f124b..d69463743d 100644 --- a/tests/common/java/android/net/NetworkCapabilitiesTest.java +++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java @@ -382,6 +382,7 @@ public class NetworkCapabilitiesTest { netCap.setAllowedUids(allowedUids); netCap.setSubscriptionIds(Set.of(TEST_SUBID1, TEST_SUBID2)); netCap.setUids(uids); + netCap.setReservationId(42); } netCap.setOwnerUid(123); @@ -1493,4 +1494,42 @@ public class NetworkCapabilitiesTest { // nc1 and nc2 are the same since invalid capability is ignored assertEquals(nc1, nc2); } + + @Test + public void testReservationIdMatching() { + final NetworkCapabilities requestNc = new NetworkCapabilities(); + final NetworkCapabilities reservationNc = new NetworkCapabilities(); + reservationNc.setReservationId(42); + + final NetworkCapabilities reservedNetworkNc = new NetworkCapabilities(reservationNc); + final NetworkCapabilities otherNetworkNc = new NetworkCapabilities(); + final NetworkCapabilities otherReservedNetworkNc = new NetworkCapabilities(); + otherReservedNetworkNc.setReservationId(99); + final NetworkCapabilities offerNc = new NetworkCapabilities(); + offerNc.setReservationId(NetworkCapabilities.RES_ID_MATCH_ALL_RESERVATIONS); + + // A regular request can match any network or offer except one with MATCH_ALL_RESERVATIONS + assertTrue(requestNc.satisfiedByNetworkCapabilities(reservedNetworkNc)); + assertTrue(requestNc.satisfiedByNetworkCapabilities(otherNetworkNc)); + assertTrue(requestNc.satisfiedByNetworkCapabilities(otherReservedNetworkNc)); + assertFalse(requestNc.satisfiedByNetworkCapabilities(offerNc)); + + // A reservation request can only match the reservedNetwork and the blanket offer. + assertTrue(reservationNc.satisfiedByNetworkCapabilities(reservedNetworkNc)); + assertFalse(reservationNc.satisfiedByNetworkCapabilities(otherNetworkNc)); + assertFalse(reservationNc.satisfiedByNetworkCapabilities(otherReservedNetworkNc)); + assertTrue(reservationNc.satisfiedByNetworkCapabilities(offerNc)); + } + + @Test @IgnoreUpTo(SC_V2) + public void testReservationIdEquals() { + final NetworkCapabilities nc = new NetworkCapabilities(); + nc.setReservationId(42); + final NetworkCapabilities other = new NetworkCapabilities(nc); + + assertEquals(nc, other); + + nc.setReservationId(43); + assertNotEquals(nc, other); + } } diff --git a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java index cdca8dc584..2226f4cb1a 100644 --- a/tests/cts/net/src/android/net/cts/NetworkRequestTest.java +++ b/tests/cts/net/src/android/net/cts/NetworkRequestTest.java @@ -16,6 +16,7 @@ package android.net.cts; +import static android.net.ConnectivityManager.TYPE_NONE; import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; import static android.net.NetworkCapabilities.NET_CAPABILITY_FOTA; import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; @@ -554,4 +555,32 @@ public class NetworkRequestTest { .setBssidPattern(ARBITRARY_ADDRESS, ARBITRARY_ADDRESS) .build(); } + + @Test + @IgnoreUpTo(Build.VERSION_CODES.R) + public void testNetworkReservation() { + final NetworkCapabilities nc = new NetworkCapabilities(); + final NetworkCapabilities blanketOffer = new NetworkCapabilities(nc); + blanketOffer.setReservationId(NetworkCapabilities.RES_ID_MATCH_ALL_RESERVATIONS); + final NetworkCapabilities specificOffer = new NetworkCapabilities(nc); + specificOffer.setReservationId(42); + final NetworkCapabilities otherSpecificOffer = new NetworkCapabilities(nc); + otherSpecificOffer.setReservationId(43); + final NetworkCapabilities regularOffer = new NetworkCapabilities(nc); + + final NetworkRequest reservationNR = new NetworkRequest(new NetworkCapabilities(nc), + TYPE_NONE, 42 /* rId */, NetworkRequest.Type.RESERVATION); + final NetworkRequest requestNR = new NetworkRequest(new NetworkCapabilities(nc), + TYPE_NONE, 42 /* rId */, NetworkRequest.Type.REQUEST); + + assertTrue(reservationNR.canBeSatisfiedBy(blanketOffer)); + assertTrue(reservationNR.canBeSatisfiedBy(specificOffer)); + assertFalse(reservationNR.canBeSatisfiedBy(otherSpecificOffer)); + assertFalse(reservationNR.canBeSatisfiedBy(regularOffer)); + + assertFalse(requestNR.canBeSatisfiedBy(blanketOffer)); + assertTrue(requestNR.canBeSatisfiedBy(specificOffer)); + assertTrue(requestNR.canBeSatisfiedBy(otherSpecificOffer)); + assertTrue(requestNR.canBeSatisfiedBy(regularOffer)); + } } diff --git a/tests/unit/java/com/android/server/connectivityservice/CSNetworkReservationTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSNetworkReservationTest.kt index db3bbb7e5e..a15969788d 100644 --- a/tests/unit/java/com/android/server/connectivityservice/CSNetworkReservationTest.kt +++ b/tests/unit/java/com/android/server/connectivityservice/CSNetworkReservationTest.kt @@ -22,6 +22,7 @@ import android.net.NetworkCapabilities import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET +import android.net.NetworkCapabilities.RES_ID_MATCH_ALL_RESERVATIONS import android.net.NetworkCapabilities.TRANSPORT_ETHERNET import android.net.NetworkProvider import android.net.NetworkRequest @@ -60,22 +61,25 @@ class CSNetworkReservationTest : CSTest() { context.packageName, context.attributionTag, NetworkCallback.DECLARED_METHODS_ALL) } + fun NetworkCapabilities.copyWithReservationId(resId: Int) = NetworkCapabilities(this).also { + it.reservationId = resId + } + @Test fun testReservationTriggersOnNetworkNeeded() { val provider = NetworkProvider(context, csHandlerThread.looper, "Ethernet provider") - val offerCb = TestableNetworkOfferCallback(TIMEOUT_MS, NO_CB_TIMEOUT_MS) + val blanketOfferCb = TestableNetworkOfferCallback(TIMEOUT_MS, NO_CB_TIMEOUT_MS) cm.registerNetworkProvider(provider) - provider.registerNetworkOffer(ETHERNET_SCORE, ETHERNET_CAPS, {r -> r.run()}, offerCb) - // TODO: add reservationId to offer, so it doesn't match the default request. - offerCb.expectOnNetworkNeeded(ETHERNET_CAPS) + val blanketCaps = ETHERNET_CAPS.copyWithReservationId(RES_ID_MATCH_ALL_RESERVATIONS) + provider.registerNetworkOffer(ETHERNET_SCORE, blanketCaps, {r -> r.run()}, blanketOfferCb) val req = NetworkRequest.Builder().addTransportType(TRANSPORT_ETHERNET).build() val cb = NetworkCallback() cm.reserveNetwork(req, cb) - offerCb.expectOnNetworkNeeded(req.networkCapabilities) + blanketOfferCb.expectOnNetworkNeeded(blanketCaps) // TODO: also test onNetworkUnneeded is called once ConnectivityManager supports the // reserveNetwork API.