diff options
34 files changed, 2043 insertions, 131 deletions
diff --git a/Android.bp b/Android.bp index 48e68ae08f26..ccf02ae977ac 100644 --- a/Android.bp +++ b/Android.bp @@ -882,17 +882,23 @@ aidl_interface { name: "networkstack-aidl-interfaces", local_include_dir: "core/java", srcs: [ + "core/java/android/net/ApfCapabilitiesParcelable.aidl", + "core/java/android/net/DhcpResultsParcelable.aidl", "core/java/android/net/INetworkMonitor.aidl", "core/java/android/net/INetworkMonitorCallbacks.aidl", "core/java/android/net/IIpMemoryStore.aidl", "core/java/android/net/INetworkStackConnector.aidl", "core/java/android/net/INetworkStackStatusCallback.aidl", + "core/java/android/net/InitialConfigurationParcelable.aidl", "core/java/android/net/IpPrefixParcelable.aidl", "core/java/android/net/LinkAddressParcelable.aidl", "core/java/android/net/LinkPropertiesParcelable.aidl", + "core/java/android/net/NetworkParcelable.aidl", "core/java/android/net/PrivateDnsConfigParcel.aidl", + "core/java/android/net/ProvisioningConfigurationParcelable.aidl", "core/java/android/net/ProxyInfoParcelable.aidl", "core/java/android/net/RouteInfoParcelable.aidl", + "core/java/android/net/StaticIpConfigurationParcelable.aidl", "core/java/android/net/dhcp/DhcpServingParamsParcel.aidl", "core/java/android/net/dhcp/IDhcpServer.aidl", "core/java/android/net/dhcp/IDhcpServerCallbacks.aidl", diff --git a/Android.mk b/Android.mk index 9f7bf9905c09..e4053452a9c5 100644 --- a/Android.mk +++ b/Android.mk @@ -87,14 +87,11 @@ $(INTERNAL_PLATFORM_HIDDENAPI_FLAGS): \ frameworks/base/config/hiddenapi-greylist-max-p.txt \ frameworks/base/config/hiddenapi-greylist-max-o.txt \ frameworks/base/config/hiddenapi-force-blacklist.txt \ - $(INTERNAL_PLATFORM_HIDDENAPI_PUBLIC_LIST) \ - $(INTERNAL_PLATFORM_HIDDENAPI_PRIVATE_LIST) \ + $(INTERNAL_PLATFORM_HIDDENAPI_STUB_FLAGS) \ $(INTERNAL_PLATFORM_REMOVED_DEX_API_FILE) \ $(SOONG_HIDDENAPI_FLAGS) frameworks/base/tools/hiddenapi/generate_hiddenapi_lists.py \ - --public $(INTERNAL_PLATFORM_HIDDENAPI_PUBLIC_LIST) \ - --private $(INTERNAL_PLATFORM_HIDDENAPI_PRIVATE_LIST) \ - --csv $(PRIVATE_FLAGS_INPUTS) \ + --csv $(INTERNAL_PLATFORM_HIDDENAPI_STUB_FLAGS) $(PRIVATE_FLAGS_INPUTS) \ --greylist frameworks/base/config/hiddenapi-greylist.txt \ --greylist-ignore-conflicts $(INTERNAL_PLATFORM_REMOVED_DEX_API_FILE) \ --greylist-max-p frameworks/base/config/hiddenapi-greylist-max-p.txt \ diff --git a/api/current.txt b/api/current.txt index 6122e5208985..07eec2e8eaa2 100644 --- a/api/current.txt +++ b/api/current.txt @@ -42273,6 +42273,8 @@ package android.system { method public static String gaiName(int); field public static final int AF_INET; field public static final int AF_INET6; + field public static final int AF_NETLINK; + field public static final int AF_PACKET; field public static final int AF_UNIX; field public static final int AF_UNSPEC; field public static final int AI_ADDRCONFIG; @@ -42282,6 +42284,7 @@ package android.system { field public static final int AI_NUMERICSERV; field public static final int AI_PASSIVE; field public static final int AI_V4MAPPED; + field public static final int ARPHRD_ETHER; field public static final int CAP_AUDIT_CONTROL; field public static final int CAP_AUDIT_WRITE; field public static final int CAP_BLOCK_SUSPEND; @@ -42405,6 +42408,10 @@ package android.system { field public static final int ESPIPE; field public static final int ESRCH; field public static final int ESTALE; + field public static final int ETH_P_ALL; + field public static final int ETH_P_ARP; + field public static final int ETH_P_IP; + field public static final int ETH_P_IPV6; field public static final int ETIME; field public static final int ETIMEDOUT; field public static final int ETXTBSY; @@ -42502,6 +42509,8 @@ package android.system { field public static final int MS_ASYNC; field public static final int MS_INVALIDATE; field public static final int MS_SYNC; + field public static final int NETLINK_INET_DIAG; + field public static final int NETLINK_ROUTE; field public static final int NI_DGRAM; field public static final int NI_NAMEREQD; field public static final int NI_NOFQDN; @@ -42538,6 +42547,7 @@ package android.system { field public static final int PR_GET_DUMPABLE; field public static final int PR_SET_DUMPABLE; field public static final int PR_SET_NO_NEW_PRIVS; + field public static final int RTMGRP_NEIGH; field public static final int RT_SCOPE_HOST; field public static final int RT_SCOPE_LINK; field public static final int RT_SCOPE_NOWHERE; diff --git a/core/java/android/net/ApfCapabilitiesParcelable.aidl b/core/java/android/net/ApfCapabilitiesParcelable.aidl new file mode 100644 index 000000000000..f0645d2782d2 --- /dev/null +++ b/core/java/android/net/ApfCapabilitiesParcelable.aidl @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +parcelable ApfCapabilitiesParcelable { + int apfVersionSupported; + int maximumApfProgramSize; + int apfPacketFormat; +}
\ No newline at end of file diff --git a/core/java/android/net/DhcpResultsParcelable.aidl b/core/java/android/net/DhcpResultsParcelable.aidl new file mode 100644 index 000000000000..cf5629b6f792 --- /dev/null +++ b/core/java/android/net/DhcpResultsParcelable.aidl @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing perNmissions and + * limitations under the License. + */ + +package android.net; + +import android.net.StaticIpConfigurationParcelable; + +parcelable DhcpResultsParcelable { + StaticIpConfigurationParcelable baseConfiguration; + int leaseDuration; + int mtu; + String serverAddress; + String vendorInfo; +}
\ No newline at end of file diff --git a/core/java/android/net/InitialConfigurationParcelable.aidl b/core/java/android/net/InitialConfigurationParcelable.aidl new file mode 100644 index 000000000000..bdda355955a5 --- /dev/null +++ b/core/java/android/net/InitialConfigurationParcelable.aidl @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import android.net.IpPrefixParcelable; +import android.net.LinkAddressParcelable; + +parcelable InitialConfigurationParcelable { + LinkAddressParcelable[] ipAddresses; + IpPrefixParcelable[] directlyConnectedRoutes; + String[] dnsServers; + String gateway; +}
\ No newline at end of file diff --git a/core/java/android/net/NetworkParcelable.aidl b/core/java/android/net/NetworkParcelable.aidl new file mode 100644 index 000000000000..c26352efb078 --- /dev/null +++ b/core/java/android/net/NetworkParcelable.aidl @@ -0,0 +1,22 @@ +/* +** +** Copyright (C) 2019 The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package android.net; + +parcelable NetworkParcelable { + long networkHandle; +} diff --git a/core/java/android/net/ProvisioningConfigurationParcelable.aidl b/core/java/android/net/ProvisioningConfigurationParcelable.aidl new file mode 100644 index 000000000000..2a144f2aae3b --- /dev/null +++ b/core/java/android/net/ProvisioningConfigurationParcelable.aidl @@ -0,0 +1,38 @@ +/* +** +** Copyright (C) 2019 The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package android.net; + +import android.net.ApfCapabilitiesParcelable; +import android.net.InitialConfigurationParcelable; +import android.net.NetworkParcelable; +import android.net.StaticIpConfigurationParcelable; + +parcelable ProvisioningConfigurationParcelable { + boolean enableIPv4; + boolean enableIPv6; + boolean usingMultinetworkPolicyTracker; + boolean usingIpReachabilityMonitor; + int requestedPreDhcpActionMs; + InitialConfigurationParcelable initialConfig; + StaticIpConfigurationParcelable staticIpConfig; + ApfCapabilitiesParcelable apfCapabilities; + int provisioningTimeoutMs; + int ipv6AddrGenMode; + NetworkParcelable network; + String displayName; +} diff --git a/core/java/android/net/StaticIpConfigurationParcelable.aidl b/core/java/android/net/StaticIpConfigurationParcelable.aidl new file mode 100644 index 000000000000..45dc0210dfba --- /dev/null +++ b/core/java/android/net/StaticIpConfigurationParcelable.aidl @@ -0,0 +1,27 @@ +/* +** +** Copyright (C) 2019 The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package android.net; + +import android.net.LinkAddressParcelable; + +parcelable StaticIpConfigurationParcelable { + LinkAddressParcelable ipAddress; + String gateway; + String[] dnsServers; + String domains; +} diff --git a/services/net/java/android/net/apf/ApfCapabilities.java b/core/java/android/net/apf/ApfCapabilities.java index dec8ca207343..f28cdc902848 100644 --- a/services/net/java/android/net/apf/ApfCapabilities.java +++ b/core/java/android/net/apf/ApfCapabilities.java @@ -38,18 +38,28 @@ public class ApfCapabilities { */ public final int apfPacketFormat; - public ApfCapabilities(int apfVersionSupported, int maximumApfProgramSize, int apfPacketFormat) - { + public ApfCapabilities( + int apfVersionSupported, int maximumApfProgramSize, int apfPacketFormat) { this.apfVersionSupported = apfVersionSupported; this.maximumApfProgramSize = maximumApfProgramSize; this.apfPacketFormat = apfPacketFormat; } + @Override public String toString() { return String.format("%s{version: %d, maxSize: %d, format: %d}", getClass().getSimpleName(), apfVersionSupported, maximumApfProgramSize, apfPacketFormat); } + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ApfCapabilities)) return false; + final ApfCapabilities other = (ApfCapabilities) obj; + return apfVersionSupported == other.apfVersionSupported + && maximumApfProgramSize == other.maximumApfProgramSize + && apfPacketFormat == other.apfPacketFormat; + } + /** * Returns true if the APF interpreter advertises support for the data buffer access opcodes * LDDW and STDW. diff --git a/core/java/android/net/ipmemorystore/Status.java b/core/java/android/net/ipmemorystore/Status.java index 95e504224ac7..cacd42d713c2 100644 --- a/core/java/android/net/ipmemorystore/Status.java +++ b/core/java/android/net/ipmemorystore/Status.java @@ -18,6 +18,8 @@ package android.net.ipmemorystore; import android.annotation.NonNull; +import com.android.internal.annotations.VisibleForTesting; + /** * A parcelable status representing the result of an operation. * Parcels as StatusParceled. @@ -26,7 +28,10 @@ import android.annotation.NonNull; public class Status { public static final int SUCCESS = 0; - public static final int ERROR_DATABASE_CANNOT_BE_OPENED = -1; + public static final int ERROR_GENERIC = -1; + public static final int ERROR_ILLEGAL_ARGUMENT = -2; + public static final int ERROR_DATABASE_CANNOT_BE_OPENED = -3; + public static final int ERROR_STORAGE = -4; public final int resultCode; @@ -34,7 +39,8 @@ public class Status { this.resultCode = resultCode; } - Status(@NonNull final StatusParcelable parcelable) { + @VisibleForTesting + public Status(@NonNull final StatusParcelable parcelable) { this(parcelable.resultCode); } @@ -55,7 +61,12 @@ public class Status { public String toString() { switch (resultCode) { case SUCCESS: return "SUCCESS"; + case ERROR_GENERIC: return "GENERIC ERROR"; + case ERROR_ILLEGAL_ARGUMENT: return "ILLEGAL ARGUMENT"; case ERROR_DATABASE_CANNOT_BE_OPENED: return "DATABASE CANNOT BE OPENED"; + // "DB storage error" is not very helpful but SQLite does not provide specific error + // codes upon store failure. Thus this indicates SQLite returned some error upon store + case ERROR_STORAGE: return "DATABASE STORAGE ERROR"; default: return "Unknown value ?!"; } } diff --git a/core/java/android/net/ipmemorystore/Utils.java b/core/java/android/net/ipmemorystore/Utils.java index 73d8c83acdd9..b361aca5a6f7 100644 --- a/core/java/android/net/ipmemorystore/Utils.java +++ b/core/java/android/net/ipmemorystore/Utils.java @@ -17,18 +17,25 @@ package android.net.ipmemorystore; import android.annotation.NonNull; +import android.annotation.Nullable; /** {@hide} */ public class Utils { /** Pretty print */ - public static String blobToString(final Blob blob) { - final StringBuilder sb = new StringBuilder("Blob : ["); - if (blob.data.length <= 24) { - appendByteArray(sb, blob.data, 0, blob.data.length); + public static String blobToString(@Nullable final Blob blob) { + return "Blob : " + byteArrayToString(null == blob ? null : blob.data); + } + + /** Pretty print */ + public static String byteArrayToString(@Nullable final byte[] data) { + if (null == data) return "null"; + final StringBuilder sb = new StringBuilder("["); + if (data.length <= 24) { + appendByteArray(sb, data, 0, data.length); } else { - appendByteArray(sb, blob.data, 0, 16); + appendByteArray(sb, data, 0, 16); sb.append("..."); - appendByteArray(sb, blob.data, blob.data.length - 8, blob.data.length); + appendByteArray(sb, data, data.length - 8, data.length); } sb.append("]"); return sb.toString(); diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 9e32206aae90..0d8ede794b73 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -488,13 +488,14 @@ android_media_AudioSystem_recording_callback(int event, static jint android_media_AudioSystem_setDeviceConnectionState(JNIEnv *env, jobject thiz, jint device, jint state, jstring device_address, jstring device_name, - jint codec __unused) + jint codec) { const char *c_address = env->GetStringUTFChars(device_address, NULL); const char *c_name = env->GetStringUTFChars(device_name, NULL); int status = check_AudioSystem_Command(AudioSystem::setDeviceConnectionState(static_cast <audio_devices_t>(device), static_cast <audio_policy_dev_state_t>(state), - c_address, c_name)); + c_address, c_name, + static_cast <audio_format_t>(codec))); env->ReleaseStringUTFChars(device_address, c_address); env->ReleaseStringUTFChars(device_name, c_name); return (jint) status; @@ -512,12 +513,12 @@ android_media_AudioSystem_getDeviceConnectionState(JNIEnv *env, jobject thiz, ji static jint android_media_AudioSystem_handleDeviceConfigChange(JNIEnv *env, jobject thiz, jint device, jstring device_address, jstring device_name, - jint codec __unused) + jint codec) { const char *c_address = env->GetStringUTFChars(device_address, NULL); const char *c_name = env->GetStringUTFChars(device_name, NULL); int status = check_AudioSystem_Command(AudioSystem::handleDeviceConfigChange(static_cast <audio_devices_t>(device), - c_address, c_name)); + c_address, c_name, static_cast <audio_format_t>(codec))); env->ReleaseStringUTFChars(device_address, c_address); env->ReleaseStringUTFChars(device_name, c_name); return (jint) status; @@ -2038,10 +2039,8 @@ android_media_AudioSystem_getHwOffloadEncodingFormatsSupportedForA2DP( return (jint)AUDIO_JAVA_BAD_VALUE; } std::vector<audio_format_t> encodingFormats; - //FIXME: enable when native implementaiton is merged - //status_t status = AudioSystem::getHwOffloadEncodingFormatsSupportedForA2DP( - // &encodingFormats); - status_t status = NO_ERROR; + status_t status = AudioSystem::getHwOffloadEncodingFormatsSupportedForA2DP( + &encodingFormats); if (status != NO_ERROR) { ALOGE("%s: error %d", __FUNCTION__, status); jStatus = nativeToJavaStatus(status); diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java index eaab6507e8d4..238f07715ce3 100644 --- a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java +++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreDatabase.java @@ -17,9 +17,24 @@ package com.android.server.net.ipmemorystore; import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ContentValues; import android.content.Context; +import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteOpenHelper; +import android.net.NetworkUtils; +import android.net.ipmemorystore.NetworkAttributes; +import android.net.ipmemorystore.Status; +import android.util.Log; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; /** * Encapsulating class for using the SQLite database backing the memory store. @@ -30,6 +45,8 @@ import android.database.sqlite.SQLiteOpenHelper; * @hide */ public class IpMemoryStoreDatabase { + private static final String TAG = IpMemoryStoreDatabase.class.getSimpleName(); + /** * Contract class for the Network Attributes table. */ @@ -57,7 +74,7 @@ public class IpMemoryStoreDatabase { public static final String COLTYPE_DNSADDRESSES = "BLOB"; public static final String COLNAME_MTU = "mtu"; - public static final String COLTYPE_MTU = "INTEGER"; + public static final String COLTYPE_MTU = "INTEGER DEFAULT -1"; public static final String CREATE_TABLE = "CREATE TABLE IF NOT EXISTS " + TABLENAME + " (" @@ -108,7 +125,7 @@ public class IpMemoryStoreDatabase { /** The SQLite DB helper */ public static class DbHelper extends SQLiteOpenHelper { // Update this whenever changing the schema. - private static final int SCHEMA_VERSION = 1; + private static final int SCHEMA_VERSION = 2; private static final String DATABASE_FILENAME = "IpMemoryStore.db"; public DbHelper(@NonNull final Context context) { @@ -140,4 +157,216 @@ public class IpMemoryStoreDatabase { onCreate(db); } } + + @NonNull + private static byte[] encodeAddressList(@NonNull final List<InetAddress> addresses) { + final ByteArrayOutputStream os = new ByteArrayOutputStream(); + for (final InetAddress address : addresses) { + final byte[] b = address.getAddress(); + os.write(b.length); + os.write(b, 0, b.length); + } + return os.toByteArray(); + } + + @NonNull + private static ArrayList<InetAddress> decodeAddressList(@NonNull final byte[] encoded) { + final ByteArrayInputStream is = new ByteArrayInputStream(encoded); + final ArrayList<InetAddress> addresses = new ArrayList<>(); + int d = -1; + while ((d = is.read()) != -1) { + final byte[] bytes = new byte[d]; + is.read(bytes, 0, d); + try { + addresses.add(InetAddress.getByAddress(bytes)); + } catch (UnknownHostException e) { /* Hopefully impossible */ } + } + return addresses; + } + + // Convert a NetworkAttributes object to content values to store them in a table compliant + // with the contract defined in NetworkAttributesContract. + @NonNull + private static ContentValues toContentValues(@NonNull final String key, + @Nullable final NetworkAttributes attributes, final long expiry) { + final ContentValues values = new ContentValues(); + values.put(NetworkAttributesContract.COLNAME_L2KEY, key); + values.put(NetworkAttributesContract.COLNAME_EXPIRYDATE, expiry); + if (null != attributes) { + if (null != attributes.assignedV4Address) { + values.put(NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, + NetworkUtils.inet4AddressToIntHTH(attributes.assignedV4Address)); + } + if (null != attributes.groupHint) { + values.put(NetworkAttributesContract.COLNAME_GROUPHINT, attributes.groupHint); + } + if (null != attributes.dnsAddresses) { + values.put(NetworkAttributesContract.COLNAME_DNSADDRESSES, + encodeAddressList(attributes.dnsAddresses)); + } + if (null != attributes.mtu) { + values.put(NetworkAttributesContract.COLNAME_MTU, attributes.mtu); + } + } + return values; + } + + // Convert a byte array into content values to store it in a table compliant with the + // contract defined in PrivateDataContract. + @NonNull + private static ContentValues toContentValues(@NonNull final String key, + @NonNull final String clientId, @NonNull final String name, + @NonNull final byte[] data) { + final ContentValues values = new ContentValues(); + values.put(PrivateDataContract.COLNAME_L2KEY, key); + values.put(PrivateDataContract.COLNAME_CLIENT, clientId); + values.put(PrivateDataContract.COLNAME_DATANAME, name); + values.put(PrivateDataContract.COLNAME_DATA, data); + return values; + } + + private static final String[] EXPIRY_COLUMN = new String[] { + NetworkAttributesContract.COLNAME_EXPIRYDATE + }; + static final int EXPIRY_ERROR = -1; // Legal values for expiry are positive + + static final String SELECT_L2KEY = NetworkAttributesContract.COLNAME_L2KEY + " = ?"; + + // Returns the expiry date of the specified row, or one of the error codes above if the + // row is not found or some other error + static long getExpiry(@NonNull final SQLiteDatabase db, @NonNull final String key) { + final Cursor cursor = db.query(NetworkAttributesContract.TABLENAME, + EXPIRY_COLUMN, // columns + SELECT_L2KEY, // selection + new String[] { key }, // selectionArgs + null, // groupBy + null, // having + null // orderBy + ); + // L2KEY is the primary key ; it should not be possible to get more than one + // result here. 0 results means the key was not found. + if (cursor.getCount() != 1) return EXPIRY_ERROR; + cursor.moveToFirst(); + return cursor.getLong(0); // index in the EXPIRY_COLUMN array + } + + static final int RELEVANCE_ERROR = -1; // Legal values for relevance are positive + + // Returns the relevance of the specified row, or one of the error codes above if the + // row is not found or some other error + static int getRelevance(@NonNull final SQLiteDatabase db, @NonNull final String key) { + final long expiry = getExpiry(db, key); + return expiry < 0 ? (int) expiry : RelevanceUtils.computeRelevanceForNow(expiry); + } + + // If the attributes are null, this will only write the expiry. + // Returns an int out of Status.{SUCCESS,ERROR_*} + static int storeNetworkAttributes(@NonNull final SQLiteDatabase db, @NonNull final String key, + final long expiry, @Nullable final NetworkAttributes attributes) { + final ContentValues cv = toContentValues(key, attributes, expiry); + db.beginTransaction(); + try { + // Unfortunately SQLite does not have any way to do INSERT OR UPDATE. Options are + // to either insert with on conflict ignore then update (like done here), or to + // construct a custom SQL INSERT statement with nested select. + final long resultId = db.insertWithOnConflict(NetworkAttributesContract.TABLENAME, + null, cv, SQLiteDatabase.CONFLICT_IGNORE); + if (resultId < 0) { + db.update(NetworkAttributesContract.TABLENAME, cv, SELECT_L2KEY, new String[]{key}); + } + db.setTransactionSuccessful(); + return Status.SUCCESS; + } catch (SQLiteException e) { + // No space left on disk or something + Log.e(TAG, "Could not write to the memory store", e); + } finally { + db.endTransaction(); + } + return Status.ERROR_STORAGE; + } + + // Returns an int out of Status.{SUCCESS,ERROR_*} + static int storeBlob(@NonNull final SQLiteDatabase db, @NonNull final String key, + @NonNull final String clientId, @NonNull final String name, + @NonNull final byte[] data) { + final long res = db.insertWithOnConflict(PrivateDataContract.TABLENAME, null, + toContentValues(key, clientId, name, data), SQLiteDatabase.CONFLICT_REPLACE); + return (res == -1) ? Status.ERROR_STORAGE : Status.SUCCESS; + } + + @Nullable + static NetworkAttributes retrieveNetworkAttributes(@NonNull final SQLiteDatabase db, + @NonNull final String key) { + final Cursor cursor = db.query(NetworkAttributesContract.TABLENAME, + null, // columns, null means everything + NetworkAttributesContract.COLNAME_L2KEY + " = ?", // selection + new String[] { key }, // selectionArgs + null, // groupBy + null, // having + null); // orderBy + // L2KEY is the primary key ; it should not be possible to get more than one + // result here. 0 results means the key was not found. + if (cursor.getCount() != 1) return null; + cursor.moveToFirst(); + + // Make sure the data hasn't expired + final long expiry = cursor.getLong( + cursor.getColumnIndexOrThrow(NetworkAttributesContract.COLNAME_EXPIRYDATE)); + if (expiry < System.currentTimeMillis()) return null; + + final NetworkAttributes.Builder builder = new NetworkAttributes.Builder(); + final int assignedV4AddressInt = getInt(cursor, + NetworkAttributesContract.COLNAME_ASSIGNEDV4ADDRESS, 0); + final String groupHint = getString(cursor, NetworkAttributesContract.COLNAME_GROUPHINT); + final byte[] dnsAddressesBlob = + getBlob(cursor, NetworkAttributesContract.COLNAME_DNSADDRESSES); + final int mtu = getInt(cursor, NetworkAttributesContract.COLNAME_MTU, -1); + if (0 != assignedV4AddressInt) { + builder.setAssignedV4Address(NetworkUtils.intToInet4AddressHTH(assignedV4AddressInt)); + } + builder.setGroupHint(groupHint); + if (null != dnsAddressesBlob) { + builder.setDnsAddresses(decodeAddressList(dnsAddressesBlob)); + } + if (mtu >= 0) { + builder.setMtu(mtu); + } + return builder.build(); + } + + private static final String[] DATA_COLUMN = new String[] { + PrivateDataContract.COLNAME_DATA + }; + @Nullable + static byte[] retrieveBlob(@NonNull final SQLiteDatabase db, @NonNull final String key, + @NonNull final String clientId, @NonNull final String name) { + final Cursor cursor = db.query(PrivateDataContract.TABLENAME, + DATA_COLUMN, // columns + PrivateDataContract.COLNAME_L2KEY + " = ? AND " // selection + + PrivateDataContract.COLNAME_CLIENT + " = ? AND " + + PrivateDataContract.COLNAME_DATANAME + " = ?", + new String[] { key, clientId, name }, // selectionArgs + null, // groupBy + null, // having + null); // orderBy + // The query above is querying by (composite) primary key, so it should not be possible to + // get more than one result here. 0 results means the key was not found. + if (cursor.getCount() != 1) return null; + cursor.moveToFirst(); + return cursor.getBlob(0); // index in the DATA_COLUMN array + } + + // Helper methods + static String getString(final Cursor cursor, final String columnName) { + final int columnIndex = cursor.getColumnIndex(columnName); + return (columnIndex >= 0) ? cursor.getString(columnIndex) : null; + } + static byte[] getBlob(final Cursor cursor, final String columnName) { + final int columnIndex = cursor.getColumnIndex(columnName); + return (columnIndex >= 0) ? cursor.getBlob(columnIndex) : null; + } + static int getInt(final Cursor cursor, final String columnName, final int defaultValue) { + final int columnIndex = cursor.getColumnIndex(columnName); + return (columnIndex >= 0) ? cursor.getInt(columnIndex) : defaultValue; + } } diff --git a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java index 55a72190eff4..444b299d49e6 100644 --- a/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java +++ b/services/ipmemorystore/java/com/android/server/net/ipmemorystore/IpMemoryStoreService.java @@ -16,6 +16,13 @@ package com.android.server.net.ipmemorystore; +import static android.net.ipmemorystore.Status.ERROR_DATABASE_CANNOT_BE_OPENED; +import static android.net.ipmemorystore.Status.ERROR_GENERIC; +import static android.net.ipmemorystore.Status.ERROR_ILLEGAL_ARGUMENT; +import static android.net.ipmemorystore.Status.SUCCESS; + +import static com.android.server.net.ipmemorystore.IpMemoryStoreDatabase.EXPIRY_ERROR; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; @@ -28,7 +35,12 @@ import android.net.ipmemorystore.IOnL2KeyResponseListener; import android.net.ipmemorystore.IOnNetworkAttributesRetrieved; import android.net.ipmemorystore.IOnSameNetworkResponseListener; import android.net.ipmemorystore.IOnStatusListener; +import android.net.ipmemorystore.NetworkAttributes; import android.net.ipmemorystore.NetworkAttributesParcelable; +import android.net.ipmemorystore.Status; +import android.net.ipmemorystore.StatusParcelable; +import android.net.ipmemorystore.Utils; +import android.os.RemoteException; import android.util.Log; import java.util.concurrent.ExecutorService; @@ -45,6 +57,7 @@ import java.util.concurrent.Executors; public class IpMemoryStoreService extends IIpMemoryStore.Stub { private static final String TAG = IpMemoryStoreService.class.getSimpleName(); private static final int MAX_CONCURRENT_THREADS = 4; + private static final boolean DBG = true; @NonNull final Context mContext; @@ -114,6 +127,11 @@ public class IpMemoryStoreService extends IIpMemoryStore.Stub { if (mDb != null) mDb.close(); } + /** Helper function to make a status object */ + private StatusParcelable makeStatus(final int code) { + return new Status(code).toParcelable(); + } + /** * Store network attributes for a given L2 key. * @@ -128,11 +146,27 @@ public class IpMemoryStoreService extends IIpMemoryStore.Stub { * Through the listener, returns the L2 key. This is useful if the L2 key was not specified. * If the call failed, the L2 key will be null. */ + // Note that while l2Key and attributes are non-null in spirit, they are received from + // another process. If the remote process decides to ignore everything and send null, this + // process should still not crash. @Override - public void storeNetworkAttributes(@NonNull final String l2Key, - @NonNull final NetworkAttributesParcelable attributes, + public void storeNetworkAttributes(@Nullable final String l2Key, + @Nullable final NetworkAttributesParcelable attributes, @Nullable final IOnStatusListener listener) { - // TODO : implement this. + // Because the parcelable is 100% mutable, the thread may not see its members initialized. + // Therefore either an immutable object is created on this same thread before it's passed + // to the executor, or there need to be a write barrier here and a read barrier in the + // remote thread. + final NetworkAttributes na = null == attributes ? null : new NetworkAttributes(attributes); + mExecutor.execute(() -> { + try { + final int code = storeNetworkAttributesAndBlobSync(l2Key, na, + null /* clientId */, null /* name */, null /* data */); + if (null != listener) listener.onComplete(makeStatus(code)); + } catch (final RemoteException e) { + // Client at the other end died + } + }); } /** @@ -141,16 +175,63 @@ public class IpMemoryStoreService extends IIpMemoryStore.Stub { * @param l2Key The L2 key for this network. * @param clientId The ID of the client. * @param name The name of this data. - * @param data The data to store. + * @param blob The data to store. * @param listener The listener that will be invoked to return the answer, or null if the * is not interested in learning about success/failure. * Through the listener, returns a status to indicate success or failure. */ @Override - public void storeBlob(@NonNull final String l2Key, @NonNull final String clientId, - @NonNull final String name, @NonNull final Blob data, + public void storeBlob(@Nullable final String l2Key, @Nullable final String clientId, + @Nullable final String name, @Nullable final Blob blob, @Nullable final IOnStatusListener listener) { - // TODO : implement this. + final byte[] data = null == blob ? null : blob.data; + mExecutor.execute(() -> { + try { + final int code = storeNetworkAttributesAndBlobSync(l2Key, + null /* NetworkAttributes */, clientId, name, data); + if (null != listener) listener.onComplete(makeStatus(code)); + } catch (final RemoteException e) { + // Client at the other end died + } + }); + } + + /** + * Helper method for storeNetworkAttributes and storeBlob. + * + * Either attributes or none of clientId, name and data may be null. This will write the + * passed data if non-null, and will write attributes if non-null, but in any case it will + * bump the relevance up. + * Returns a success code from Status. + */ + private int storeNetworkAttributesAndBlobSync(@Nullable final String l2Key, + @Nullable final NetworkAttributes attributes, + @Nullable final String clientId, + @Nullable final String name, @Nullable final byte[] data) { + if (null == l2Key) return ERROR_ILLEGAL_ARGUMENT; + if (null == attributes && null == data) return ERROR_ILLEGAL_ARGUMENT; + if (null != data && (null == clientId || null == name)) return ERROR_ILLEGAL_ARGUMENT; + if (null == mDb) return ERROR_DATABASE_CANNOT_BE_OPENED; + try { + final long oldExpiry = IpMemoryStoreDatabase.getExpiry(mDb, l2Key); + final long newExpiry = RelevanceUtils.bumpExpiryDate( + oldExpiry == EXPIRY_ERROR ? System.currentTimeMillis() : oldExpiry); + final int errorCode = + IpMemoryStoreDatabase.storeNetworkAttributes(mDb, l2Key, newExpiry, attributes); + // If no blob to store, the client is interested in the result of storing the attributes + if (null == data) return errorCode; + // Otherwise it's interested in the result of storing the blob + return IpMemoryStoreDatabase.storeBlob(mDb, l2Key, clientId, name, data); + } catch (Exception e) { + if (DBG) { + Log.e(TAG, "Exception while storing for key {" + l2Key + + "} ; NetworkAttributes {" + (null == attributes ? "null" : attributes) + + "} ; clientId {" + (null == clientId ? "null" : clientId) + + "} ; name {" + (null == name ? "null" : name) + + "} ; data {" + Utils.byteArrayToString(data) + "}", e); + } + } + return ERROR_GENERIC; } /** @@ -198,9 +279,32 @@ public class IpMemoryStoreService extends IIpMemoryStore.Stub { * the query. */ @Override - public void retrieveNetworkAttributes(@NonNull final String l2Key, - @NonNull final IOnNetworkAttributesRetrieved listener) { - // TODO : implement this. + public void retrieveNetworkAttributes(@Nullable final String l2Key, + @Nullable final IOnNetworkAttributesRetrieved listener) { + if (null == listener) return; + mExecutor.execute(() -> { + try { + if (null == l2Key) { + listener.onL2KeyResponse(makeStatus(ERROR_ILLEGAL_ARGUMENT), l2Key, null); + return; + } + if (null == mDb) { + listener.onL2KeyResponse(makeStatus(ERROR_DATABASE_CANNOT_BE_OPENED), l2Key, + null); + return; + } + try { + final NetworkAttributes attributes = + IpMemoryStoreDatabase.retrieveNetworkAttributes(mDb, l2Key); + listener.onL2KeyResponse(makeStatus(SUCCESS), l2Key, + null == attributes ? null : attributes.toParcelable()); + } catch (final Exception e) { + listener.onL2KeyResponse(makeStatus(ERROR_GENERIC), l2Key, null); + } + } catch (final RemoteException e) { + // Client at the other end died + } + }); } /** @@ -217,6 +321,28 @@ public class IpMemoryStoreService extends IIpMemoryStore.Stub { @Override public void retrieveBlob(@NonNull final String l2Key, @NonNull final String clientId, @NonNull final String name, @NonNull final IOnBlobRetrievedListener listener) { - // TODO : implement this. + if (null == listener) return; + mExecutor.execute(() -> { + try { + if (null == l2Key) { + listener.onBlobRetrieved(makeStatus(ERROR_ILLEGAL_ARGUMENT), l2Key, name, null); + return; + } + if (null == mDb) { + listener.onBlobRetrieved(makeStatus(ERROR_DATABASE_CANNOT_BE_OPENED), l2Key, + name, null); + return; + } + try { + final Blob b = new Blob(); + b.data = IpMemoryStoreDatabase.retrieveBlob(mDb, l2Key, clientId, name); + listener.onBlobRetrieved(makeStatus(SUCCESS), l2Key, name, b); + } catch (final Exception e) { + listener.onBlobRetrieved(makeStatus(ERROR_GENERIC), l2Key, name, null); + } + } catch (final RemoteException e) { + // Client at the other end died + } + }); } } diff --git a/services/net/java/android/net/netlink/ConntrackMessage.java b/services/net/java/android/net/netlink/ConntrackMessage.java index 605c46b3b4e0..4ee64321f262 100644 --- a/services/net/java/android/net/netlink/ConntrackMessage.java +++ b/services/net/java/android/net/netlink/ConntrackMessage.java @@ -28,7 +28,6 @@ import static java.nio.ByteOrder.BIG_ENDIAN; import android.system.OsConstants; import android.util.Log; -import libcore.io.SizeOf; import java.net.Inet4Address; import java.net.Inet6Address; diff --git a/services/net/java/android/net/netlink/StructNfGenMsg.java b/services/net/java/android/net/netlink/StructNfGenMsg.java index 99695e23b248..8155977b6e6a 100644 --- a/services/net/java/android/net/netlink/StructNfGenMsg.java +++ b/services/net/java/android/net/netlink/StructNfGenMsg.java @@ -16,8 +16,6 @@ package android.net.netlink; -import libcore.io.SizeOf; - import java.nio.ByteBuffer; @@ -29,7 +27,7 @@ import java.nio.ByteBuffer; * @hide */ public class StructNfGenMsg { - public static final int STRUCT_SIZE = 2 + SizeOf.SHORT; + public static final int STRUCT_SIZE = 2 + Short.BYTES; public static final int NFNETLINK_V0 = 0; diff --git a/services/net/java/android/net/netlink/StructNlAttr.java b/services/net/java/android/net/netlink/StructNlAttr.java index 811bdbbe821a..28a4e883a316 100644 --- a/services/net/java/android/net/netlink/StructNlAttr.java +++ b/services/net/java/android/net/netlink/StructNlAttr.java @@ -17,7 +17,6 @@ package android.net.netlink; import android.net.netlink.NetlinkConstants; -import libcore.io.SizeOf; import java.net.InetAddress; import java.net.UnknownHostException; @@ -117,7 +116,7 @@ public class StructNlAttr { public StructNlAttr(short type, short value, ByteOrder order) { this(order); nla_type = type; - setValue(new byte[SizeOf.SHORT]); + setValue(new byte[Short.BYTES]); getValueAsByteBuffer().putShort(value); } @@ -128,7 +127,7 @@ public class StructNlAttr { public StructNlAttr(short type, int value, ByteOrder order) { this(order); nla_type = type; - setValue(new byte[SizeOf.INT]); + setValue(new byte[Integer.BYTES]); getValueAsByteBuffer().putInt(value); } @@ -164,7 +163,7 @@ public class StructNlAttr { public int getValueAsInt(int defaultValue) { final ByteBuffer byteBuffer = getValueAsByteBuffer(); - if (byteBuffer == null || byteBuffer.remaining() != SizeOf.INT) { + if (byteBuffer == null || byteBuffer.remaining() != Integer.BYTES) { return defaultValue; } return getValueAsByteBuffer().getInt(); diff --git a/services/net/java/android/net/netlink/StructNlMsgErr.java b/services/net/java/android/net/netlink/StructNlMsgErr.java index f095af43c743..6fcc6e69da5b 100644 --- a/services/net/java/android/net/netlink/StructNlMsgErr.java +++ b/services/net/java/android/net/netlink/StructNlMsgErr.java @@ -18,7 +18,6 @@ package android.net.netlink; import android.net.netlink.NetlinkConstants; import android.net.netlink.StructNlMsgHdr; -import libcore.io.SizeOf; import java.nio.ByteBuffer; @@ -31,7 +30,7 @@ import java.nio.ByteBuffer; * @hide */ public class StructNlMsgErr { - public static final int STRUCT_SIZE = SizeOf.INT + StructNlMsgHdr.STRUCT_SIZE; + public static final int STRUCT_SIZE = Integer.BYTES + StructNlMsgHdr.STRUCT_SIZE; public static boolean hasAvailableSpace(ByteBuffer byteBuffer) { return byteBuffer != null && byteBuffer.remaining() >= STRUCT_SIZE; diff --git a/services/net/java/android/net/shared/InitialConfiguration.java b/services/net/java/android/net/shared/InitialConfiguration.java new file mode 100644 index 000000000000..bc2373f4aabc --- /dev/null +++ b/services/net/java/android/net/shared/InitialConfiguration.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.shared; + +import static android.net.shared.ParcelableUtil.fromParcelableArray; +import static android.net.shared.ParcelableUtil.toParcelableArray; +import static android.text.TextUtils.join; + +import android.net.InitialConfigurationParcelable; +import android.net.IpPrefix; +import android.net.IpPrefixParcelable; +import android.net.LinkAddress; +import android.net.LinkAddressParcelable; +import android.net.RouteInfo; + +import java.net.Inet6Address; +import java.net.InetAddress; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; + +/** @hide */ +public class InitialConfiguration { + public final Set<LinkAddress> ipAddresses = new HashSet<>(); + public final Set<IpPrefix> directlyConnectedRoutes = new HashSet<>(); + public final Set<InetAddress> dnsServers = new HashSet<>(); + + private static final int RFC6177_MIN_PREFIX_LENGTH = 48; + private static final int RFC7421_PREFIX_LENGTH = 64; + + /** + * Create a InitialConfiguration that is a copy of the specified configuration. + */ + public static InitialConfiguration copy(InitialConfiguration config) { + if (config == null) { + return null; + } + InitialConfiguration configCopy = new InitialConfiguration(); + configCopy.ipAddresses.addAll(config.ipAddresses); + configCopy.directlyConnectedRoutes.addAll(config.directlyConnectedRoutes); + configCopy.dnsServers.addAll(config.dnsServers); + return configCopy; + } + + @Override + public String toString() { + return String.format( + "InitialConfiguration(IPs: {%s}, prefixes: {%s}, DNS: {%s})", + join(", ", ipAddresses), join(", ", directlyConnectedRoutes), + join(", ", dnsServers)); + } + + /** + * Tests whether the contents of this IpConfiguration represent a valid configuration. + */ + public boolean isValid() { + if (ipAddresses.isEmpty()) { + return false; + } + + // For every IP address, there must be at least one prefix containing that address. + for (LinkAddress addr : ipAddresses) { + if (!any(directlyConnectedRoutes, (p) -> p.contains(addr.getAddress()))) { + return false; + } + } + // For every dns server, there must be at least one prefix containing that address. + for (InetAddress addr : dnsServers) { + if (!any(directlyConnectedRoutes, (p) -> p.contains(addr))) { + return false; + } + } + // All IPv6 LinkAddresses have an RFC7421-suitable prefix length + // (read: compliant with RFC4291#section2.5.4). + if (any(ipAddresses, not(InitialConfiguration::isPrefixLengthCompliant))) { + return false; + } + // If directlyConnectedRoutes contains an IPv6 default route + // then ipAddresses MUST contain at least one non-ULA GUA. + if (any(directlyConnectedRoutes, InitialConfiguration::isIPv6DefaultRoute) + && all(ipAddresses, not(InitialConfiguration::isIPv6GUA))) { + return false; + } + // The prefix length of routes in directlyConnectedRoutes be within reasonable + // bounds for IPv6: /48-/64 just as we’d accept in RIOs. + if (any(directlyConnectedRoutes, not(InitialConfiguration::isPrefixLengthCompliant))) { + return false; + } + // There no more than one IPv4 address + if (ipAddresses.stream().filter(LinkAddress::isIPv4).count() > 1) { + return false; + } + + return true; + } + + /** + * @return true if the given list of addressess and routes satisfies provisioning for this + * InitialConfiguration. LinkAddresses and RouteInfo objects are not compared with equality + * because addresses and routes seen by Netlink will contain additional fields like flags, + * interfaces, and so on. If this InitialConfiguration has no IP address specified, the + * provisioning check always fails. + * + * If the given list of routes is null, only addresses are taken into considerations. + */ + public boolean isProvisionedBy(List<LinkAddress> addresses, List<RouteInfo> routes) { + if (ipAddresses.isEmpty()) { + return false; + } + + for (LinkAddress addr : ipAddresses) { + if (!any(addresses, (addrSeen) -> addr.isSameAddressAs(addrSeen))) { + return false; + } + } + + if (routes != null) { + for (IpPrefix prefix : directlyConnectedRoutes) { + if (!any(routes, (routeSeen) -> isDirectlyConnectedRoute(routeSeen, prefix))) { + return false; + } + } + } + + return true; + } + + /** + * Convert this configuration to a {@link InitialConfigurationParcelable}. + */ + public InitialConfigurationParcelable toStableParcelable() { + final InitialConfigurationParcelable p = new InitialConfigurationParcelable(); + p.ipAddresses = toParcelableArray(ipAddresses, + LinkPropertiesParcelableUtil::toStableParcelable, LinkAddressParcelable.class); + p.directlyConnectedRoutes = toParcelableArray(directlyConnectedRoutes, + LinkPropertiesParcelableUtil::toStableParcelable, IpPrefixParcelable.class); + p.dnsServers = toParcelableArray( + dnsServers, IpConfigurationParcelableUtil::parcelAddress, String.class); + return p; + } + + /** + * Create an instance of {@link InitialConfiguration} based on the contents of the specified + * {@link InitialConfigurationParcelable}. + */ + public static InitialConfiguration fromStableParcelable(InitialConfigurationParcelable p) { + if (p == null) return null; + final InitialConfiguration config = new InitialConfiguration(); + config.ipAddresses.addAll(fromParcelableArray( + p.ipAddresses, LinkPropertiesParcelableUtil::fromStableParcelable)); + config.directlyConnectedRoutes.addAll(fromParcelableArray( + p.directlyConnectedRoutes, LinkPropertiesParcelableUtil::fromStableParcelable)); + config.dnsServers.addAll( + fromParcelableArray(p.dnsServers, IpConfigurationParcelableUtil::unparcelAddress)); + return config; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof InitialConfiguration)) return false; + final InitialConfiguration other = (InitialConfiguration) obj; + return ipAddresses.equals(other.ipAddresses) + && directlyConnectedRoutes.equals(other.directlyConnectedRoutes) + && dnsServers.equals(other.dnsServers); + } + + private static boolean isDirectlyConnectedRoute(RouteInfo route, IpPrefix prefix) { + return !route.hasGateway() && prefix.equals(route.getDestination()); + } + + private static boolean isPrefixLengthCompliant(LinkAddress addr) { + return addr.isIPv4() || isCompliantIPv6PrefixLength(addr.getPrefixLength()); + } + + private static boolean isPrefixLengthCompliant(IpPrefix prefix) { + return prefix.isIPv4() || isCompliantIPv6PrefixLength(prefix.getPrefixLength()); + } + + private static boolean isCompliantIPv6PrefixLength(int prefixLength) { + return (RFC6177_MIN_PREFIX_LENGTH <= prefixLength) + && (prefixLength <= RFC7421_PREFIX_LENGTH); + } + + private static boolean isIPv6DefaultRoute(IpPrefix prefix) { + return prefix.getAddress().equals(Inet6Address.ANY); + } + + private static boolean isIPv6GUA(LinkAddress addr) { + return addr.isIPv6() && addr.isGlobalPreferred(); + } + + // TODO: extract out into CollectionUtils. + + /** + * Indicate whether any element of the specified iterable verifies the specified predicate. + */ + public static <T> boolean any(Iterable<T> coll, Predicate<T> fn) { + for (T t : coll) { + if (fn.test(t)) { + return true; + } + } + return false; + } + + /** + * Indicate whether all elements of the specified iterable verifies the specified predicate. + */ + public static <T> boolean all(Iterable<T> coll, Predicate<T> fn) { + return !any(coll, not(fn)); + } + + /** + * Create a predicate that returns the opposite value of the specified predicate. + */ + public static <T> Predicate<T> not(Predicate<T> fn) { + return (t) -> !fn.test(t); + } +} diff --git a/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java b/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java new file mode 100644 index 000000000000..2c368c81523e --- /dev/null +++ b/services/net/java/android/net/shared/IpConfigurationParcelableUtil.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.shared; + +import static android.net.shared.ParcelableUtil.fromParcelableArray; +import static android.net.shared.ParcelableUtil.toParcelableArray; + +import android.annotation.Nullable; +import android.net.ApfCapabilitiesParcelable; +import android.net.DhcpResults; +import android.net.DhcpResultsParcelable; +import android.net.InetAddresses; +import android.net.StaticIpConfiguration; +import android.net.StaticIpConfigurationParcelable; +import android.net.apf.ApfCapabilities; + +import java.net.Inet4Address; +import java.net.InetAddress; + +/** + * Collection of utility methods to convert to and from stable AIDL parcelables for IpClient + * configuration classes. + * @hide + */ +public final class IpConfigurationParcelableUtil { + /** + * Convert a StaticIpConfiguration to a StaticIpConfigurationParcelable. + */ + public static StaticIpConfigurationParcelable toStableParcelable( + @Nullable StaticIpConfiguration config) { + if (config == null) return null; + final StaticIpConfigurationParcelable p = new StaticIpConfigurationParcelable(); + p.ipAddress = LinkPropertiesParcelableUtil.toStableParcelable(config.ipAddress); + p.gateway = parcelAddress(config.gateway); + p.dnsServers = toParcelableArray( + config.dnsServers, IpConfigurationParcelableUtil::parcelAddress, String.class); + p.domains = config.domains; + return p; + } + + /** + * Convert a StaticIpConfigurationParcelable to a StaticIpConfiguration. + */ + public static StaticIpConfiguration fromStableParcelable( + @Nullable StaticIpConfigurationParcelable p) { + if (p == null) return null; + final StaticIpConfiguration config = new StaticIpConfiguration(); + config.ipAddress = LinkPropertiesParcelableUtil.fromStableParcelable(p.ipAddress); + config.gateway = unparcelAddress(p.gateway); + config.dnsServers.addAll(fromParcelableArray( + p.dnsServers, IpConfigurationParcelableUtil::unparcelAddress)); + config.domains = p.domains; + return config; + } + + /** + * Convert DhcpResults to a DhcpResultsParcelable. + */ + public static DhcpResultsParcelable toStableParcelable(@Nullable DhcpResults results) { + if (results == null) return null; + final DhcpResultsParcelable p = new DhcpResultsParcelable(); + p.baseConfiguration = toStableParcelable((StaticIpConfiguration) results); + p.leaseDuration = results.leaseDuration; + p.mtu = results.mtu; + p.serverAddress = parcelAddress(results.serverAddress); + p.vendorInfo = results.vendorInfo; + return p; + } + + /** + * Convert a DhcpResultsParcelable to DhcpResults. + */ + public static DhcpResults fromStableParcelable(@Nullable DhcpResultsParcelable p) { + if (p == null) return null; + final DhcpResults results = new DhcpResults(fromStableParcelable(p.baseConfiguration)); + results.leaseDuration = p.leaseDuration; + results.mtu = p.mtu; + results.serverAddress = (Inet4Address) unparcelAddress(p.serverAddress); + results.vendorInfo = p.vendorInfo; + return results; + } + + /** + * Convert ApfCapabilities to ApfCapabilitiesParcelable. + */ + public static ApfCapabilitiesParcelable toStableParcelable(@Nullable ApfCapabilities caps) { + if (caps == null) return null; + final ApfCapabilitiesParcelable p = new ApfCapabilitiesParcelable(); + p.apfVersionSupported = caps.apfVersionSupported; + p.maximumApfProgramSize = caps.maximumApfProgramSize; + p.apfPacketFormat = caps.apfPacketFormat; + return p; + } + + /** + * Convert ApfCapabilitiesParcelable toApfCapabilities. + */ + public static ApfCapabilities fromStableParcelable(@Nullable ApfCapabilitiesParcelable p) { + if (p == null) return null; + return new ApfCapabilities( + p.apfVersionSupported, p.maximumApfProgramSize, p.apfPacketFormat); + } + + /** + * Convert InetAddress to String. + * TODO: have an InetAddressParcelable + */ + public static String parcelAddress(@Nullable InetAddress addr) { + if (addr == null) return null; + return addr.getHostAddress(); + } + + /** + * Convert String to InetAddress. + * TODO: have an InetAddressParcelable + */ + public static InetAddress unparcelAddress(@Nullable String addr) { + if (addr == null) return null; + return InetAddresses.parseNumericAddress(addr); + } +} diff --git a/services/net/java/android/net/shared/LinkPropertiesParcelableUtil.java b/services/net/java/android/net/shared/LinkPropertiesParcelableUtil.java index 5b77f543c62b..d5213dfcebf8 100644 --- a/services/net/java/android/net/shared/LinkPropertiesParcelableUtil.java +++ b/services/net/java/android/net/shared/LinkPropertiesParcelableUtil.java @@ -16,11 +16,12 @@ package android.net.shared; +import static android.net.shared.IpConfigurationParcelableUtil.parcelAddress; +import static android.net.shared.IpConfigurationParcelableUtil.unparcelAddress; import static android.net.shared.ParcelableUtil.fromParcelableArray; import static android.net.shared.ParcelableUtil.toParcelableArray; import android.annotation.Nullable; -import android.net.InetAddresses; import android.net.IpPrefix; import android.net.IpPrefixParcelable; import android.net.LinkAddress; @@ -33,7 +34,6 @@ import android.net.RouteInfo; import android.net.RouteInfoParcelable; import android.net.Uri; -import java.net.InetAddress; import java.util.Arrays; /** @@ -81,7 +81,7 @@ public final class LinkPropertiesParcelableUtil { return null; } final IpPrefixParcelable parcel = new IpPrefixParcelable(); - parcel.address = ipPrefix.getAddress().getHostAddress(); + parcel.address = parcelAddress(ipPrefix.getAddress()); parcel.prefixLength = ipPrefix.getPrefixLength(); return parcel; } @@ -93,7 +93,7 @@ public final class LinkPropertiesParcelableUtil { if (parcel == null) { return null; } - return new IpPrefix(InetAddresses.parseNumericAddress(parcel.address), parcel.prefixLength); + return new IpPrefix(unparcelAddress(parcel.address), parcel.prefixLength); } /** @@ -105,7 +105,7 @@ public final class LinkPropertiesParcelableUtil { } final RouteInfoParcelable parcel = new RouteInfoParcelable(); parcel.destination = toStableParcelable(routeInfo.getDestination()); - parcel.gatewayAddr = routeInfo.getGateway().getHostAddress(); + parcel.gatewayAddr = parcelAddress(routeInfo.getGateway()); parcel.ifaceName = routeInfo.getInterface(); parcel.type = routeInfo.getType(); return parcel; @@ -120,7 +120,7 @@ public final class LinkPropertiesParcelableUtil { } final IpPrefix destination = fromStableParcelable(parcel.destination); return new RouteInfo( - destination, InetAddresses.parseNumericAddress(parcel.gatewayAddr), + destination, unparcelAddress(parcel.gatewayAddr), parcel.ifaceName, parcel.type); } @@ -132,7 +132,7 @@ public final class LinkPropertiesParcelableUtil { return null; } final LinkAddressParcelable parcel = new LinkAddressParcelable(); - parcel.address = la.getAddress().getHostAddress(); + parcel.address = parcelAddress(la.getAddress()); parcel.prefixLength = la.getPrefixLength(); parcel.flags = la.getFlags(); parcel.scope = la.getScope(); @@ -147,7 +147,7 @@ public final class LinkPropertiesParcelableUtil { return null; } return new LinkAddress( - InetAddresses.parseNumericAddress(parcel.address), + unparcelAddress(parcel.address), parcel.prefixLength, parcel.flags, parcel.scope); @@ -167,11 +167,11 @@ public final class LinkPropertiesParcelableUtil { LinkPropertiesParcelableUtil::toStableParcelable, LinkAddressParcelable.class); parcel.dnses = toParcelableArray( - lp.getDnsServers(), InetAddress::getHostAddress, String.class); + lp.getDnsServers(), IpConfigurationParcelableUtil::parcelAddress, String.class); parcel.pcscfs = toParcelableArray( - lp.getPcscfServers(), InetAddress::getHostAddress, String.class); - parcel.validatedPrivateDnses = toParcelableArray( - lp.getValidatedPrivateDnsServers(), InetAddress::getHostAddress, String.class); + lp.getPcscfServers(), IpConfigurationParcelableUtil::parcelAddress, String.class); + parcel.validatedPrivateDnses = toParcelableArray(lp.getValidatedPrivateDnsServers(), + IpConfigurationParcelableUtil::parcelAddress, String.class); parcel.usePrivateDns = lp.isPrivateDnsActive(); parcel.privateDnsServerName = lp.getPrivateDnsServerName(); parcel.domains = lp.getDomains(); @@ -199,11 +199,13 @@ public final class LinkPropertiesParcelableUtil { lp.setInterfaceName(parcel.ifaceName); lp.setLinkAddresses(fromParcelableArray(parcel.linkAddresses, LinkPropertiesParcelableUtil::fromStableParcelable)); - lp.setDnsServers(fromParcelableArray(parcel.dnses, InetAddresses::parseNumericAddress)); - lp.setPcscfServers(fromParcelableArray(parcel.pcscfs, InetAddresses::parseNumericAddress)); + lp.setDnsServers(fromParcelableArray( + parcel.dnses, IpConfigurationParcelableUtil::unparcelAddress)); + lp.setPcscfServers(fromParcelableArray( + parcel.pcscfs, IpConfigurationParcelableUtil::unparcelAddress)); lp.setValidatedPrivateDnsServers( fromParcelableArray(parcel.validatedPrivateDnses, - InetAddresses::parseNumericAddress)); + IpConfigurationParcelableUtil::unparcelAddress)); lp.setUsePrivateDns(parcel.usePrivateDns); lp.setPrivateDnsServerName(parcel.privateDnsServerName); lp.setDomains(parcel.domains); diff --git a/services/net/java/android/net/shared/NetworkParcelableUtil.java b/services/net/java/android/net/shared/NetworkParcelableUtil.java new file mode 100644 index 000000000000..d0b54b8b81d0 --- /dev/null +++ b/services/net/java/android/net/shared/NetworkParcelableUtil.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.shared; + +import android.annotation.Nullable; +import android.net.Network; +import android.net.NetworkParcelable; + +/** + * Utility methods to convert to/from stable AIDL parcelables for network attribute classes. + * @hide + */ +public final class NetworkParcelableUtil { + /** + * Convert from a Network to a NetworkParcelable. + */ + public static NetworkParcelable toStableParcelable(@Nullable Network network) { + if (network == null) { + return null; + } + final NetworkParcelable p = new NetworkParcelable(); + p.networkHandle = network.getNetworkHandle(); + + return p; + } + + /** + * Convert from a NetworkParcelable to a Network. + */ + public static Network fromStableParcelable(@Nullable NetworkParcelable p) { + if (p == null) { + return null; + } + return Network.fromNetworkHandle(p.networkHandle); + } +} diff --git a/services/net/java/android/net/shared/ParcelableUtil.java b/services/net/java/android/net/shared/ParcelableUtil.java index a18976c9eee6..3f4030047938 100644 --- a/services/net/java/android/net/shared/ParcelableUtil.java +++ b/services/net/java/android/net/shared/ParcelableUtil.java @@ -20,7 +20,7 @@ import android.annotation.NonNull; import java.lang.reflect.Array; import java.util.ArrayList; -import java.util.List; +import java.util.Collection; import java.util.function.Function; /** @@ -36,7 +36,7 @@ public final class ParcelableUtil { * converter function. */ public static <ParcelableType, BaseType> ParcelableType[] toParcelableArray( - @NonNull List<BaseType> base, + @NonNull Collection<BaseType> base, @NonNull Function<BaseType, ParcelableType> conv, @NonNull Class<ParcelableType> parcelClass) { final ParcelableType[] out = (ParcelableType[]) Array.newInstance(parcelClass, base.size()); diff --git a/services/net/java/android/net/shared/PrivateDnsConfig.java b/services/net/java/android/net/shared/PrivateDnsConfig.java index 41e0bad7d746..c7dc5306b51b 100644 --- a/services/net/java/android/net/shared/PrivateDnsConfig.java +++ b/services/net/java/android/net/shared/PrivateDnsConfig.java @@ -16,7 +16,9 @@ package android.net.shared; -import android.net.InetAddresses; +import static android.net.shared.ParcelableUtil.fromParcelableArray; +import static android.net.shared.ParcelableUtil.toParcelableArray; + import android.net.PrivateDnsConfigParcel; import android.text.TextUtils; @@ -70,12 +72,8 @@ public class PrivateDnsConfig { public PrivateDnsConfigParcel toParcel() { final PrivateDnsConfigParcel parcel = new PrivateDnsConfigParcel(); parcel.hostname = hostname; - - final String[] parceledIps = new String[ips.length]; - for (int i = 0; i < ips.length; i++) { - parceledIps[i] = ips[i].getHostAddress(); - } - parcel.ips = parceledIps; + parcel.ips = toParcelableArray( + Arrays.asList(ips), IpConfigurationParcelableUtil::parcelAddress, String.class); return parcel; } @@ -84,11 +82,9 @@ public class PrivateDnsConfig { * Build a configuration from a stable AIDL-compatible parcel. */ public static PrivateDnsConfig fromParcel(PrivateDnsConfigParcel parcel) { - final InetAddress[] ips = new InetAddress[parcel.ips.length]; - for (int i = 0; i < ips.length; i++) { - ips[i] = InetAddresses.parseNumericAddress(parcel.ips[i]); - } - + InetAddress[] ips = new InetAddress[parcel.ips.length]; + ips = fromParcelableArray(parcel.ips, IpConfigurationParcelableUtil::unparcelAddress) + .toArray(ips); return new PrivateDnsConfig(parcel.hostname, ips); } } diff --git a/services/net/java/android/net/shared/ProvisioningConfiguration.java b/services/net/java/android/net/shared/ProvisioningConfiguration.java new file mode 100644 index 000000000000..d995d1b1e622 --- /dev/null +++ b/services/net/java/android/net/shared/ProvisioningConfiguration.java @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.shared; + +import android.annotation.Nullable; +import android.net.INetd; +import android.net.Network; +import android.net.ProvisioningConfigurationParcelable; +import android.net.StaticIpConfiguration; +import android.net.apf.ApfCapabilities; + +import java.util.Objects; +import java.util.StringJoiner; + +/** + * This class encapsulates parameters to be passed to + * IpClient#startProvisioning(). A defensive copy is made by IpClient + * and the values specified herein are in force until IpClient#stop() + * is called. + * + * Example use: + * + * final ProvisioningConfiguration config = + * new ProvisioningConfiguration.Builder() + * .withPreDhcpAction() + * .withProvisioningTimeoutMs(36 * 1000) + * .build(); + * mIpClient.startProvisioning(config.toStableParcelable()); + * ... + * mIpClient.stop(); + * + * The specified provisioning configuration will only be active until + * IIpClient#stop() is called. Future calls to IIpClient#startProvisioning() + * must specify the configuration again. + * @hide + */ +public class ProvisioningConfiguration { + // TODO: Delete this default timeout once those callers that care are + // fixed to pass in their preferred timeout. + // + // We pick 36 seconds so we can send DHCP requests at + // + // t=0, t=2, t=6, t=14, t=30 + // + // allowing for 10% jitter. + private static final int DEFAULT_TIMEOUT_MS = 36 * 1000; + + /** + * Builder to create a {@link ProvisioningConfiguration}. + */ + public static class Builder { + protected ProvisioningConfiguration mConfig = new ProvisioningConfiguration(); + + /** + * Specify that the configuration should not enable IPv4. It is enabled by default. + */ + public Builder withoutIPv4() { + mConfig.mEnableIPv4 = false; + return this; + } + + /** + * Specify that the configuration should not enable IPv6. It is enabled by default. + */ + public Builder withoutIPv6() { + mConfig.mEnableIPv6 = false; + return this; + } + + /** + * Specify that the configuration should not use a MultinetworkPolicyTracker. It is used + * by default. + */ + public Builder withoutMultinetworkPolicyTracker() { + mConfig.mUsingMultinetworkPolicyTracker = false; + return this; + } + + /** + * Specify that the configuration should not use a IpReachabilityMonitor. It is used by + * default. + */ + public Builder withoutIpReachabilityMonitor() { + mConfig.mUsingIpReachabilityMonitor = false; + return this; + } + + /** + * Identical to {@link #withPreDhcpAction(int)}, using a default timeout. + * @see #withPreDhcpAction(int) + */ + public Builder withPreDhcpAction() { + mConfig.mRequestedPreDhcpActionMs = DEFAULT_TIMEOUT_MS; + return this; + } + + /** + * Specify that {@link IpClientCallbacks#onPreDhcpAction()} should be called. Clients must + * call {@link IIpClient#completedPreDhcpAction()} when the callback called. This behavior + * is disabled by default. + * @param dhcpActionTimeoutMs Timeout for clients to call completedPreDhcpAction(). + */ + public Builder withPreDhcpAction(int dhcpActionTimeoutMs) { + mConfig.mRequestedPreDhcpActionMs = dhcpActionTimeoutMs; + return this; + } + + /** + * Specify the initial provisioning configuration. + */ + public Builder withInitialConfiguration(InitialConfiguration initialConfig) { + mConfig.mInitialConfig = initialConfig; + return this; + } + + /** + * Specify a static configuration for provisioning. + */ + public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) { + mConfig.mStaticIpConfig = staticConfig; + return this; + } + + /** + * Specify ApfCapabilities. + */ + public Builder withApfCapabilities(ApfCapabilities apfCapabilities) { + mConfig.mApfCapabilities = apfCapabilities; + return this; + } + + /** + * Specify the timeout to use for provisioning. + */ + public Builder withProvisioningTimeoutMs(int timeoutMs) { + mConfig.mProvisioningTimeoutMs = timeoutMs; + return this; + } + + /** + * Specify that IPv6 address generation should use a random MAC address. + */ + public Builder withRandomMacAddress() { + mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_EUI64; + return this; + } + + /** + * Specify that IPv6 address generation should use a stable MAC address. + */ + public Builder withStableMacAddress() { + mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY; + return this; + } + + /** + * Specify the network to use for provisioning. + */ + public Builder withNetwork(Network network) { + mConfig.mNetwork = network; + return this; + } + + /** + * Specify the display name that the IpClient should use. + */ + public Builder withDisplayName(String displayName) { + mConfig.mDisplayName = displayName; + return this; + } + + /** + * Build the configuration using previously specified parameters. + */ + public ProvisioningConfiguration build() { + return new ProvisioningConfiguration(mConfig); + } + } + + public boolean mEnableIPv4 = true; + public boolean mEnableIPv6 = true; + public boolean mUsingMultinetworkPolicyTracker = true; + public boolean mUsingIpReachabilityMonitor = true; + public int mRequestedPreDhcpActionMs; + public InitialConfiguration mInitialConfig; + public StaticIpConfiguration mStaticIpConfig; + public ApfCapabilities mApfCapabilities; + public int mProvisioningTimeoutMs = DEFAULT_TIMEOUT_MS; + public int mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY; + public Network mNetwork = null; + public String mDisplayName = null; + + public ProvisioningConfiguration() {} // used by Builder + + public ProvisioningConfiguration(ProvisioningConfiguration other) { + mEnableIPv4 = other.mEnableIPv4; + mEnableIPv6 = other.mEnableIPv6; + mUsingMultinetworkPolicyTracker = other.mUsingMultinetworkPolicyTracker; + mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor; + mRequestedPreDhcpActionMs = other.mRequestedPreDhcpActionMs; + mInitialConfig = InitialConfiguration.copy(other.mInitialConfig); + mStaticIpConfig = other.mStaticIpConfig == null + ? null + : new StaticIpConfiguration(other.mStaticIpConfig); + mApfCapabilities = other.mApfCapabilities; + mProvisioningTimeoutMs = other.mProvisioningTimeoutMs; + mIPv6AddrGenMode = other.mIPv6AddrGenMode; + mNetwork = other.mNetwork; + mDisplayName = other.mDisplayName; + } + + /** + * Create a ProvisioningConfigurationParcelable from this ProvisioningConfiguration. + */ + public ProvisioningConfigurationParcelable toStableParcelable() { + final ProvisioningConfigurationParcelable p = new ProvisioningConfigurationParcelable(); + p.enableIPv4 = mEnableIPv4; + p.enableIPv6 = mEnableIPv6; + p.usingMultinetworkPolicyTracker = mUsingMultinetworkPolicyTracker; + p.usingIpReachabilityMonitor = mUsingIpReachabilityMonitor; + p.requestedPreDhcpActionMs = mRequestedPreDhcpActionMs; + p.initialConfig = mInitialConfig == null ? null : mInitialConfig.toStableParcelable(); + p.staticIpConfig = IpConfigurationParcelableUtil.toStableParcelable(mStaticIpConfig); + p.apfCapabilities = IpConfigurationParcelableUtil.toStableParcelable(mApfCapabilities); + p.provisioningTimeoutMs = mProvisioningTimeoutMs; + p.ipv6AddrGenMode = mIPv6AddrGenMode; + p.network = NetworkParcelableUtil.toStableParcelable(mNetwork); + p.displayName = mDisplayName; + return p; + } + + /** + * Create a ProvisioningConfiguration from a ProvisioningConfigurationParcelable. + */ + public static ProvisioningConfiguration fromStableParcelable( + @Nullable ProvisioningConfigurationParcelable p) { + if (p == null) return null; + final ProvisioningConfiguration config = new ProvisioningConfiguration(); + config.mEnableIPv4 = p.enableIPv4; + config.mEnableIPv6 = p.enableIPv6; + config.mUsingMultinetworkPolicyTracker = p.usingMultinetworkPolicyTracker; + config.mUsingIpReachabilityMonitor = p.usingIpReachabilityMonitor; + config.mRequestedPreDhcpActionMs = p.requestedPreDhcpActionMs; + config.mInitialConfig = InitialConfiguration.fromStableParcelable(p.initialConfig); + config.mStaticIpConfig = IpConfigurationParcelableUtil.fromStableParcelable( + p.staticIpConfig); + config.mApfCapabilities = IpConfigurationParcelableUtil.fromStableParcelable( + p.apfCapabilities); + config.mProvisioningTimeoutMs = p.provisioningTimeoutMs; + config.mIPv6AddrGenMode = p.ipv6AddrGenMode; + config.mNetwork = NetworkParcelableUtil.fromStableParcelable(p.network); + config.mDisplayName = p.displayName; + return config; + } + + @Override + public String toString() { + return new StringJoiner(", ", getClass().getSimpleName() + "{", "}") + .add("mEnableIPv4: " + mEnableIPv4) + .add("mEnableIPv6: " + mEnableIPv6) + .add("mUsingMultinetworkPolicyTracker: " + mUsingMultinetworkPolicyTracker) + .add("mUsingIpReachabilityMonitor: " + mUsingIpReachabilityMonitor) + .add("mRequestedPreDhcpActionMs: " + mRequestedPreDhcpActionMs) + .add("mInitialConfig: " + mInitialConfig) + .add("mStaticIpConfig: " + mStaticIpConfig) + .add("mApfCapabilities: " + mApfCapabilities) + .add("mProvisioningTimeoutMs: " + mProvisioningTimeoutMs) + .add("mIPv6AddrGenMode: " + mIPv6AddrGenMode) + .add("mNetwork: " + mNetwork) + .add("mDisplayName: " + mDisplayName) + .toString(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ProvisioningConfiguration)) return false; + final ProvisioningConfiguration other = (ProvisioningConfiguration) obj; + return mEnableIPv4 == other.mEnableIPv4 + && mEnableIPv6 == other.mEnableIPv6 + && mUsingMultinetworkPolicyTracker == other.mUsingMultinetworkPolicyTracker + && mUsingIpReachabilityMonitor == other.mUsingIpReachabilityMonitor + && mRequestedPreDhcpActionMs == other.mRequestedPreDhcpActionMs + && Objects.equals(mInitialConfig, other.mInitialConfig) + && Objects.equals(mStaticIpConfig, other.mStaticIpConfig) + && Objects.equals(mApfCapabilities, other.mApfCapabilities) + && mProvisioningTimeoutMs == other.mProvisioningTimeoutMs + && mIPv6AddrGenMode == other.mIPv6AddrGenMode + && Objects.equals(mNetwork, other.mNetwork) + && Objects.equals(mDisplayName, other.mDisplayName); + } + + public boolean isValid() { + return (mInitialConfig == null) || mInitialConfig.isValid(); + } +} diff --git a/tests/net/java/android/net/ipmemorystore/ParcelableTests.java b/tests/net/java/android/net/ipmemorystore/ParcelableTests.java index a9f9758bb1f8..1fc67a8212ae 100644 --- a/tests/net/java/android/net/ipmemorystore/ParcelableTests.java +++ b/tests/net/java/android/net/ipmemorystore/ParcelableTests.java @@ -27,6 +27,7 @@ import android.support.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; +import java.lang.reflect.Modifier; import java.net.Inet4Address; import java.net.InetAddress; import java.util.Arrays; @@ -60,6 +61,12 @@ public class ParcelableTests { builder.setMtu(null); in = builder.build(); assertEquals(in, new NetworkAttributes(parcelingRoundTrip(in.toParcelable()))); + + // Verify that this test does not miss any new field added later. + // If any field is added to NetworkAttributes it must be tested here for parceling + // roundtrip. + assertEquals(4, Arrays.stream(NetworkAttributes.class.getDeclaredFields()) + .filter(f -> !Modifier.isStatic(f.getModifiers())).count()); } @Test diff --git a/tests/net/java/android/net/shared/InitialConfigurationTest.java b/tests/net/java/android/net/shared/InitialConfigurationTest.java new file mode 100644 index 000000000000..78792bdfe4f9 --- /dev/null +++ b/tests/net/java/android/net/shared/InitialConfigurationTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.shared; + +import static android.net.InetAddresses.parseNumericAddress; +import static android.net.shared.ParcelableTestUtil.assertFieldCountEquals; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import android.net.IpPrefix; +import android.net.LinkAddress; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Arrays; +import java.util.function.Consumer; + +/** + * Tests for {@link InitialConfiguration} + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class InitialConfigurationTest { + private InitialConfiguration mConfig; + + @Before + public void setUp() { + mConfig = new InitialConfiguration(); + mConfig.ipAddresses.addAll(Arrays.asList( + new LinkAddress(parseNumericAddress("192.168.45.45"), 16), + new LinkAddress(parseNumericAddress("2001:db8::45"), 33))); + mConfig.directlyConnectedRoutes.addAll(Arrays.asList( + new IpPrefix(parseNumericAddress("192.168.46.46"), 17), + new IpPrefix(parseNumericAddress("2001:db8::46"), 34))); + mConfig.dnsServers.addAll(Arrays.asList( + parseNumericAddress("192.168.47.47"), + parseNumericAddress("2001:db8::47"))); + // Any added InitialConfiguration field must be included in equals() to be tested properly + assertFieldCountEquals(3, InitialConfiguration.class); + } + + @Test + public void testParcelUnparcelInitialConfiguration() { + final InitialConfiguration unparceled = + InitialConfiguration.fromStableParcelable(mConfig.toStableParcelable()); + assertEquals(mConfig, unparceled); + } + + @Test + public void testEquals() { + assertEquals(mConfig, InitialConfiguration.copy(mConfig)); + + assertNotEqualsAfterChange(c -> c.ipAddresses.add( + new LinkAddress(parseNumericAddress("192.168.47.47"), 24))); + assertNotEqualsAfterChange(c -> c.directlyConnectedRoutes.add( + new IpPrefix(parseNumericAddress("192.168.46.46"), 32))); + assertNotEqualsAfterChange(c -> c.dnsServers.add(parseNumericAddress("2001:db8::49"))); + assertFieldCountEquals(3, InitialConfiguration.class); + } + + private void assertNotEqualsAfterChange(Consumer<InitialConfiguration> mutator) { + final InitialConfiguration newConfig = InitialConfiguration.copy(mConfig); + mutator.accept(newConfig); + assertNotEquals(mConfig, newConfig); + } +} diff --git a/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java b/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java new file mode 100644 index 000000000000..14df392cbe07 --- /dev/null +++ b/tests/net/java/android/net/shared/IpConfigurationParcelableUtilTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.shared; + +import static android.net.InetAddresses.parseNumericAddress; +import static android.net.shared.IpConfigurationParcelableUtil.fromStableParcelable; +import static android.net.shared.IpConfigurationParcelableUtil.toStableParcelable; +import static android.net.shared.ParcelableTestUtil.assertFieldCountEquals; + +import static org.junit.Assert.assertEquals; + +import android.net.DhcpResults; +import android.net.LinkAddress; +import android.net.StaticIpConfiguration; +import android.net.apf.ApfCapabilities; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.Inet4Address; + +/** + * Tests for {@link IpConfigurationParcelableUtil}. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class IpConfigurationParcelableUtilTest { + private StaticIpConfiguration mStaticIpConfiguration; + private DhcpResults mDhcpResults; + + @Before + public void setUp() { + mStaticIpConfiguration = new StaticIpConfiguration(); + mStaticIpConfiguration.ipAddress = new LinkAddress(parseNumericAddress("2001:db8::42"), 64); + mStaticIpConfiguration.gateway = parseNumericAddress("192.168.42.42"); + mStaticIpConfiguration.dnsServers.add(parseNumericAddress("2001:db8::43")); + mStaticIpConfiguration.dnsServers.add(parseNumericAddress("192.168.43.43")); + mStaticIpConfiguration.domains = "example.com"; + // Any added StaticIpConfiguration field must be included in equals() to be tested properly + assertFieldCountEquals(4, StaticIpConfiguration.class); + + mDhcpResults = new DhcpResults(mStaticIpConfiguration); + mDhcpResults.serverAddress = (Inet4Address) parseNumericAddress("192.168.44.44"); + mDhcpResults.vendorInfo = "TEST_VENDOR_INFO"; + mDhcpResults.leaseDuration = 3600; + mDhcpResults.mtu = 1450; + // Any added DhcpResults field must be included in equals() to be tested properly + assertFieldCountEquals(4, DhcpResults.class); + } + + @Test + public void testParcelUnparcelStaticConfiguration() { + doStaticConfigurationParcelUnparcelTest(); + } + + @Test + public void testParcelUnparcelStaticConfiguration_NullIpAddress() { + mStaticIpConfiguration.ipAddress = null; + doStaticConfigurationParcelUnparcelTest(); + } + + @Test + public void testParcelUnparcelStaticConfiguration_NullGateway() { + mStaticIpConfiguration.gateway = null; + doStaticConfigurationParcelUnparcelTest(); + } + + @Test + public void testParcelUnparcelStaticConfiguration_NullDomains() { + mStaticIpConfiguration.domains = null; + doStaticConfigurationParcelUnparcelTest(); + } + + @Test + public void testParcelUnparcelStaticConfiguration_EmptyDomains() { + mStaticIpConfiguration.domains = ""; + doStaticConfigurationParcelUnparcelTest(); + } + + private void doStaticConfigurationParcelUnparcelTest() { + final StaticIpConfiguration unparceled = + fromStableParcelable(toStableParcelable(mStaticIpConfiguration)); + assertEquals(mStaticIpConfiguration, unparceled); + } + + @Test + public void testParcelUnparcelDhcpResults() { + doDhcpResultsParcelUnparcelTest(); + } + + @Test + public void testParcelUnparcelDhcpResults_NullServerAddress() { + mDhcpResults.serverAddress = null; + doDhcpResultsParcelUnparcelTest(); + } + + @Test + public void testParcelUnparcelDhcpResults_NullVendorInfo() { + mDhcpResults.vendorInfo = null; + doDhcpResultsParcelUnparcelTest(); + } + + private void doDhcpResultsParcelUnparcelTest() { + final DhcpResults unparceled = fromStableParcelable(toStableParcelable(mDhcpResults)); + assertEquals(mDhcpResults, unparceled); + } + + @Test + public void testParcelUnparcelApfCapabilities() { + final ApfCapabilities caps = new ApfCapabilities(123, 456, 789); + assertEquals(caps, fromStableParcelable(toStableParcelable(caps))); + } +} diff --git a/tests/net/java/android/net/shared/LinkPropertiesParcelableUtilTest.java b/tests/net/java/android/net/shared/LinkPropertiesParcelableUtilTest.java index 4cabfc95b49d..6f711c0b5743 100644 --- a/tests/net/java/android/net/shared/LinkPropertiesParcelableUtilTest.java +++ b/tests/net/java/android/net/shared/LinkPropertiesParcelableUtilTest.java @@ -18,6 +18,7 @@ package android.net.shared; import static android.net.shared.LinkPropertiesParcelableUtil.fromStableParcelable; import static android.net.shared.LinkPropertiesParcelableUtil.toStableParcelable; +import static android.net.shared.ParcelableTestUtil.assertFieldCountEquals; import static org.junit.Assert.assertEquals; @@ -35,7 +36,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.Collections; @@ -100,8 +100,7 @@ public class LinkPropertiesParcelableUtilTest { // Verify that this test does not miss any new field added later. // If any added field is not included in LinkProperties#equals, assertLinkPropertiesEquals // must also be updated. - assertEquals(14, Arrays.stream(LinkProperties.class.getDeclaredFields()) - .filter(f -> !Modifier.isStatic(f.getModifiers())).count()); + assertFieldCountEquals(14, LinkProperties.class); return lp; } diff --git a/tests/net/java/android/net/shared/ParcelableTestUtil.java b/tests/net/java/android/net/shared/ParcelableTestUtil.java new file mode 100644 index 000000000000..088ea3c1d1ed --- /dev/null +++ b/tests/net/java/android/net/shared/ParcelableTestUtil.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.shared; + +import static org.junit.Assert.assertEquals; + +import java.lang.reflect.Modifier; +import java.util.Arrays; + +/** + * Utility classes to write tests for stable AIDL parceling/unparceling + */ +public final class ParcelableTestUtil { + + /** + * Verifies that the number of nonstatic fields in a class equals a given count. + * + * <p>This assertion serves as a reminder to update test code around it if fields are added + * after the test is written. + * @param count Expected number of nonstatic fields in the class. + * @param clazz Class to test. + */ + public static <T> void assertFieldCountEquals(int count, Class<T> clazz) { + assertEquals(count, Arrays.stream(clazz.getDeclaredFields()) + .filter(f -> !Modifier.isStatic(f.getModifiers())).count()); + } +} diff --git a/tests/net/java/android/net/shared/ProvisioningConfigurationTest.java b/tests/net/java/android/net/shared/ProvisioningConfigurationTest.java new file mode 100644 index 000000000000..6ea47d2160a7 --- /dev/null +++ b/tests/net/java/android/net/shared/ProvisioningConfigurationTest.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.shared; + +import static android.net.InetAddresses.parseNumericAddress; +import static android.net.shared.ParcelableTestUtil.assertFieldCountEquals; +import static android.net.shared.ProvisioningConfiguration.fromStableParcelable; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import android.net.LinkAddress; +import android.net.Network; +import android.net.StaticIpConfiguration; +import android.net.apf.ApfCapabilities; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.function.Consumer; + +/** + * Tests for {@link ProvisioningConfiguration}. + */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ProvisioningConfigurationTest { + private ProvisioningConfiguration mConfig; + + @Before + public void setUp() { + mConfig = new ProvisioningConfiguration(); + mConfig.mEnableIPv4 = true; + mConfig.mEnableIPv6 = true; + mConfig.mUsingMultinetworkPolicyTracker = true; + mConfig.mUsingIpReachabilityMonitor = true; + mConfig.mRequestedPreDhcpActionMs = 42; + mConfig.mInitialConfig = new InitialConfiguration(); + mConfig.mInitialConfig.ipAddresses.add( + new LinkAddress(parseNumericAddress("192.168.42.42"), 24)); + mConfig.mStaticIpConfig = new StaticIpConfiguration(); + mConfig.mStaticIpConfig.ipAddress = + new LinkAddress(parseNumericAddress("2001:db8::42"), 90); + // Not testing other InitialConfig or StaticIpConfig members: they have their own unit tests + mConfig.mApfCapabilities = new ApfCapabilities(1, 2, 3); + mConfig.mProvisioningTimeoutMs = 4200; + mConfig.mIPv6AddrGenMode = 123; + mConfig.mNetwork = new Network(321); + mConfig.mDisplayName = "test_config"; + // Any added field must be included in equals() to be tested properly + assertFieldCountEquals(12, ProvisioningConfiguration.class); + } + + @Test + public void testParcelUnparcel() { + doParcelUnparcelTest(); + } + + @Test + public void testParcelUnparcel_NullInitialConfiguration() { + mConfig.mInitialConfig = null; + doParcelUnparcelTest(); + } + + @Test + public void testParcelUnparcel_NullStaticConfiguration() { + mConfig.mStaticIpConfig = null; + doParcelUnparcelTest(); + } + + @Test + public void testParcelUnparcel_NullApfCapabilities() { + mConfig.mApfCapabilities = null; + doParcelUnparcelTest(); + } + + @Test + public void testParcelUnparcel_NullNetwork() { + mConfig.mNetwork = null; + doParcelUnparcelTest(); + } + + private void doParcelUnparcelTest() { + final ProvisioningConfiguration unparceled = + fromStableParcelable(mConfig.toStableParcelable()); + assertEquals(mConfig, unparceled); + } + + @Test + public void testEquals() { + assertEquals(mConfig, new ProvisioningConfiguration(mConfig)); + + assertNotEqualsAfterChange(c -> c.mEnableIPv4 = false); + assertNotEqualsAfterChange(c -> c.mEnableIPv6 = false); + assertNotEqualsAfterChange(c -> c.mUsingMultinetworkPolicyTracker = false); + assertNotEqualsAfterChange(c -> c.mUsingIpReachabilityMonitor = false); + assertNotEqualsAfterChange(c -> c.mRequestedPreDhcpActionMs++); + assertNotEqualsAfterChange(c -> c.mInitialConfig.ipAddresses.add( + new LinkAddress(parseNumericAddress("192.168.47.47"), 16))); + assertNotEqualsAfterChange(c -> c.mInitialConfig = null); + assertNotEqualsAfterChange(c -> c.mStaticIpConfig.ipAddress = + new LinkAddress(parseNumericAddress("2001:db8::47"), 64)); + assertNotEqualsAfterChange(c -> c.mStaticIpConfig = null); + assertNotEqualsAfterChange(c -> c.mApfCapabilities = new ApfCapabilities(4, 5, 6)); + assertNotEqualsAfterChange(c -> c.mApfCapabilities = null); + assertNotEqualsAfterChange(c -> c.mProvisioningTimeoutMs++); + assertNotEqualsAfterChange(c -> c.mIPv6AddrGenMode++); + assertNotEqualsAfterChange(c -> c.mNetwork = new Network(123)); + assertNotEqualsAfterChange(c -> c.mNetwork = null); + assertNotEqualsAfterChange(c -> c.mDisplayName = "other_test"); + assertNotEqualsAfterChange(c -> c.mDisplayName = null); + assertFieldCountEquals(12, ProvisioningConfiguration.class); + } + + private void assertNotEqualsAfterChange(Consumer<ProvisioningConfiguration> mutator) { + final ProvisioningConfiguration newConfig = new ProvisioningConfiguration(mConfig); + mutator.accept(newConfig); + assertNotEquals(mConfig, newConfig); + } +} diff --git a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java index e63c3b02d1c3..94bcd28bf009 100644 --- a/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java +++ b/tests/net/java/com/android/server/net/ipmemorystore/IpMemoryStoreServiceTest.java @@ -16,13 +16,30 @@ package com.android.server.net.ipmemorystore; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; import android.content.Context; +import android.net.ipmemorystore.Blob; +import android.net.ipmemorystore.IOnBlobRetrievedListener; +import android.net.ipmemorystore.IOnNetworkAttributesRetrieved; +import android.net.ipmemorystore.IOnStatusListener; +import android.net.ipmemorystore.NetworkAttributes; +import android.net.ipmemorystore.NetworkAttributesParcelable; +import android.net.ipmemorystore.Status; +import android.net.ipmemorystore.StatusParcelable; +import android.os.IBinder; +import android.os.RemoteException; +import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -30,41 +47,267 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.io.File; +import java.lang.reflect.Modifier; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; -/** Unit tests for {@link IpMemoryStoreServiceTest}. */ +/** Unit tests for {@link IpMemoryStoreService}. */ @SmallTest @RunWith(AndroidJUnit4.class) public class IpMemoryStoreServiceTest { + private static final String TEST_CLIENT_ID = "testClientId"; + private static final String TEST_DATA_NAME = "testData"; + @Mock - Context mMockContext; + private Context mMockContext; + private File mDbFile; + + private IpMemoryStoreService mService; @Before public void setUp() { MockitoAnnotations.initMocks(this); - doReturn(new File("/tmp/test.db")).when(mMockContext).getDatabasePath(anyString()); + final Context context = InstrumentationRegistry.getContext(); + final File dir = context.getFilesDir(); + mDbFile = new File(dir, "test.db"); + doReturn(mDbFile).when(mMockContext).getDatabasePath(anyString()); + mService = new IpMemoryStoreService(mMockContext); + } + + @After + public void tearDown() { + mService.shutdown(); + mDbFile.delete(); + } + + /** Helper method to make a vanilla IOnStatusListener */ + private IOnStatusListener onStatus(Consumer<Status> functor) { + return new IOnStatusListener() { + @Override + public void onComplete(final StatusParcelable statusParcelable) throws RemoteException { + functor.accept(new Status(statusParcelable)); + } + + @Override + public IBinder asBinder() { + return null; + } + }; + } + + /** Helper method to make an IOnBlobRetrievedListener */ + private interface OnBlobRetrievedListener { + void onBlobRetrieved(Status status, String l2Key, String name, byte[] data); + } + private IOnBlobRetrievedListener onBlobRetrieved(final OnBlobRetrievedListener functor) { + return new IOnBlobRetrievedListener() { + @Override + public void onBlobRetrieved(final StatusParcelable statusParcelable, + final String l2Key, final String name, final Blob blob) throws RemoteException { + functor.onBlobRetrieved(new Status(statusParcelable), l2Key, name, + null == blob ? null : blob.data); + } + + @Override + public IBinder asBinder() { + return null; + } + }; + } + + /** Helper method to make an IOnNetworkAttributesRetrievedListener */ + private interface OnNetworkAttributesRetrievedListener { + void onNetworkAttributesRetrieved(Status status, String l2Key, NetworkAttributes attr); + } + private IOnNetworkAttributesRetrieved onNetworkAttributesRetrieved( + final OnNetworkAttributesRetrievedListener functor) { + return new IOnNetworkAttributesRetrieved() { + @Override + public void onL2KeyResponse(final StatusParcelable status, final String l2Key, + final NetworkAttributesParcelable attributes) + throws RemoteException { + functor.onNetworkAttributesRetrieved(new Status(status), l2Key, + null == attributes ? null : new NetworkAttributes(attributes)); + } + + @Override + public IBinder asBinder() { + return null; + } + }; + } + + // Helper method to factorize some boilerplate + private void doLatched(final String timeoutMessage, final Consumer<CountDownLatch> functor) { + final CountDownLatch latch = new CountDownLatch(1); + functor.accept(latch); + try { + latch.await(5000, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + fail(timeoutMessage); + } } @Test public void testNetworkAttributes() { - final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext); - // TODO : implement this + final NetworkAttributes.Builder na = new NetworkAttributes.Builder(); + try { + na.setAssignedV4Address( + (Inet4Address) Inet4Address.getByAddress(new byte[]{1, 2, 3, 4})); + } catch (UnknownHostException e) { /* Can't happen */ } + na.setGroupHint("hint1"); + na.setMtu(219); + final String l2Key = UUID.randomUUID().toString(); + NetworkAttributes attributes = na.build(); + doLatched("Did not complete storing attributes", latch -> + mService.storeNetworkAttributes(l2Key, attributes.toParcelable(), + onStatus(status -> { + assertTrue("Store status not successful : " + status.resultCode, + status.isSuccess()); + latch.countDown(); + }))); + + doLatched("Did not complete retrieving attributes", latch -> + mService.retrieveNetworkAttributes(l2Key, onNetworkAttributesRetrieved( + (status, key, attr) -> { + assertTrue("Retrieve network attributes not successful : " + + status.resultCode, status.isSuccess()); + assertEquals(l2Key, key); + assertEquals(attributes, attr); + latch.countDown(); + }))); + + final NetworkAttributes.Builder na2 = new NetworkAttributes.Builder(); + try { + na.setDnsAddresses(Arrays.asList( + new InetAddress[] {Inet6Address.getByName("0A1C:2E40:480A::1CA6")})); + } catch (UnknownHostException e) { /* Still can't happen */ } + final NetworkAttributes attributes2 = na2.build(); + doLatched("Did not complete storing attributes 2", latch -> + mService.storeNetworkAttributes(l2Key, attributes2.toParcelable(), + onStatus(status -> latch.countDown()))); + + doLatched("Did not complete retrieving attributes 2", latch -> + mService.retrieveNetworkAttributes(l2Key, onNetworkAttributesRetrieved( + (status, key, attr) -> { + assertTrue("Retrieve network attributes not successful : " + + status.resultCode, status.isSuccess()); + assertEquals(l2Key, key); + assertEquals(attributes.assignedV4Address, attr.assignedV4Address); + assertEquals(attributes.groupHint, attr.groupHint); + assertEquals(attributes.mtu, attr.mtu); + assertEquals(attributes2.dnsAddresses, attr.dnsAddresses); + latch.countDown(); + }))); + + doLatched("Did not complete retrieving attributes 3", latch -> + mService.retrieveNetworkAttributes(l2Key + "nonexistent", + onNetworkAttributesRetrieved( + (status, key, attr) -> { + assertTrue("Retrieve network attributes not successful : " + + status.resultCode, status.isSuccess()); + assertEquals(l2Key + "nonexistent", key); + assertNull("Retrieved data not stored", attr); + latch.countDown(); + } + ))); + + // Verify that this test does not miss any new field added later. + // If any field is added to NetworkAttributes it must be tested here for storing + // and retrieving. + assertEquals(4, Arrays.stream(NetworkAttributes.class.getDeclaredFields()) + .filter(f -> !Modifier.isStatic(f.getModifiers())).count()); + } + + @Test + public void testInvalidAttributes() { + doLatched("Did not complete storing bad attributes", latch -> + mService.storeNetworkAttributes("key", null, onStatus(status -> { + assertFalse("Success storing on a null key", + status.isSuccess()); + assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode); + latch.countDown(); + }))); + + final NetworkAttributes na = new NetworkAttributes.Builder().setMtu(2).build(); + doLatched("Did not complete storing bad attributes", latch -> + mService.storeNetworkAttributes(null, na.toParcelable(), onStatus(status -> { + assertFalse("Success storing null attributes on a null key", + status.isSuccess()); + assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode); + latch.countDown(); + }))); + + doLatched("Did not complete storing bad attributes", latch -> + mService.storeNetworkAttributes(null, null, onStatus(status -> { + assertFalse("Success storing null attributes on a null key", + status.isSuccess()); + assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode); + latch.countDown(); + }))); + + doLatched("Did not complete retrieving bad attributes", latch -> + mService.retrieveNetworkAttributes(null, onNetworkAttributesRetrieved( + (status, key, attr) -> { + assertFalse("Success retrieving attributes for a null key", + status.isSuccess()); + assertEquals(Status.ERROR_ILLEGAL_ARGUMENT, status.resultCode); + assertNull(key); + assertNull(attr); + }))); } @Test public void testPrivateData() { - final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext); - // TODO : implement this + final Blob b = new Blob(); + b.data = new byte[] { -3, 6, 8, -9, 12, -128, 0, 89, 112, 91, -34 }; + final String l2Key = UUID.randomUUID().toString(); + doLatched("Did not complete storing private data", latch -> + mService.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b, + onStatus(status -> { + assertTrue("Store status not successful : " + status.resultCode, + status.isSuccess()); + latch.countDown(); + }))); + + doLatched("Did not complete retrieving private data", latch -> + mService.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, onBlobRetrieved( + (status, key, name, data) -> { + assertTrue("Retrieve blob status not successful : " + status.resultCode, + status.isSuccess()); + assertEquals(l2Key, key); + assertEquals(name, TEST_DATA_NAME); + Arrays.equals(b.data, data); + latch.countDown(); + }))); + + // Most puzzling error message ever + doLatched("Did not complete retrieving nothing", latch -> + mService.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME + "2", onBlobRetrieved( + (status, key, name, data) -> { + assertTrue("Retrieve blob status not successful : " + status.resultCode, + status.isSuccess()); + assertEquals(l2Key, key); + assertEquals(name, TEST_DATA_NAME + "2"); + assertNull(data); + latch.countDown(); + }))); } @Test public void testFindL2Key() { - final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext); // TODO : implement this } @Test public void testIsSameNetwork() { - final IpMemoryStoreService service = new IpMemoryStoreService(mMockContext); // TODO : implement this } } diff --git a/tools/hiddenapi/generate_hiddenapi_lists.py b/tools/hiddenapi/generate_hiddenapi_lists.py index 01728fa1a0db..2a8f69525eb8 100755 --- a/tools/hiddenapi/generate_hiddenapi_lists.py +++ b/tools/hiddenapi/generate_hiddenapi_lists.py @@ -17,6 +17,7 @@ Generate API lists for non-SDK API enforcement. """ import argparse +from collections import defaultdict import os import sys import re @@ -27,16 +28,20 @@ FLAG_GREYLIST = "greylist" FLAG_BLACKLIST = "blacklist" FLAG_GREYLIST_MAX_O = "greylist-max-o" FLAG_GREYLIST_MAX_P = "greylist-max-p" +FLAG_CORE_PLATFORM_API = "core-platform-api" # List of all known flags. -FLAGS = [ +FLAGS_API_LIST = [ FLAG_WHITELIST, FLAG_GREYLIST, FLAG_BLACKLIST, FLAG_GREYLIST_MAX_O, FLAG_GREYLIST_MAX_P, ] -FLAGS_SET = set(FLAGS) +ALL_FLAGS = FLAGS_API_LIST + [ FLAG_CORE_PLATFORM_API ] + +FLAGS_API_LIST_SET = set(FLAGS_API_LIST) +ALL_FLAGS_SET = set(ALL_FLAGS) # Suffix used in command line args to express that only known and # otherwise unassigned entries should be assign the given flag. @@ -62,7 +67,7 @@ SERIALIZATION_PATTERNS = [ SERIALIZATION_REGEX = re.compile(r'.*->(' + '|'.join(SERIALIZATION_PATTERNS) + r')$') # Predicates to be used with filter_apis. -IS_UNASSIGNED = lambda api, flags: not flags +HAS_NO_API_LIST_ASSIGNED = lambda api, flags: not FLAGS_API_LIST_SET.intersection(flags) IS_SERIALIZATION = lambda api, flags: SERIALIZATION_REGEX.match(api) def get_args(): @@ -73,12 +78,10 @@ def get_args(): """ parser = argparse.ArgumentParser() parser.add_argument('--output', required=True) - parser.add_argument('--public', required=True, help='list of all public entries') - parser.add_argument('--private', required=True, help='list of all private entries') parser.add_argument('--csv', nargs='*', default=[], metavar='CSV_FILE', help='CSV files to be merged into output') - for flag in FLAGS: + for flag in ALL_FLAGS: ignore_conflicts_flag = flag + FLAG_IGNORE_CONFLICTS_SUFFIX parser.add_argument('--' + flag, dest=flag, nargs='*', default=[], metavar='TXT_FILE', help='lists of entries with flag "' + flag + '"') @@ -118,26 +121,9 @@ def write_lines(filename, lines): f.writelines(lines) class FlagsDict: - def __init__(self, public_api, private_api): - # Bootstrap the entries dictionary. - - # Check that the two sets do not overlap. - public_api_set = set(public_api) - private_api_set = set(private_api) - assert public_api_set.isdisjoint(private_api_set), ( - "Lists of public and private API overlap. " + - "This suggests an issue with the `hiddenapi` build tool.") - - # Compute the whole key set - self._dict_keyset = public_api_set.union(private_api_set) - - # Create a dict that creates entries for both public and private API, - # and assigns public API to the whitelist. - self._dict = {} - for api in public_api: - self._dict[api] = set([ FLAG_WHITELIST ]) - for api in private_api: - self._dict[api] = set() + def __init__(self): + self._dict_keyset = set() + self._dict = defaultdict(set) def _check_entries_set(self, keys_subset, source): assert isinstance(keys_subset, set) @@ -150,12 +136,12 @@ class FlagsDict: def _check_flags_set(self, flags_subset, source): assert isinstance(flags_subset, set) - assert flags_subset.issubset(FLAGS_SET), ( + assert flags_subset.issubset(ALL_FLAGS_SET), ( "Error processing: {}\n" "The following flags were not recognized: \n" "{}\n" "Please visit go/hiddenapi for more information.").format( - source, "\n".join(flags_subset - FLAGS_SET)) + source, "\n".join(flags_subset - ALL_FLAGS_SET)) def filter_apis(self, filter_fn): """Returns APIs which match a given predicate. @@ -173,7 +159,7 @@ class FlagsDict: def get_valid_subset_of_unassigned_apis(self, api_subset): """Sanitizes a key set input to only include keys which exist in the dictionary - and have not been assigned any flags. + and have not been assigned any API list flags. Args: entries_subset (set/list): Key set to be sanitized. @@ -182,7 +168,7 @@ class FlagsDict: Sanitized key set. """ assert isinstance(api_subset, set) - return api_subset.intersection(self.filter_apis(IS_UNASSIGNED)) + return api_subset.intersection(self.filter_apis(HAS_NO_API_LIST_ASSIGNED)) def generate_csv(self): """Constructs CSV entries from a dictionary. @@ -203,14 +189,13 @@ class FlagsDict: source (string): Origin of `csv_lines`. Will be printed in error messages. Throws: - AssertionError if parsed API signatures of flags are invalid. + AssertionError if parsed flags are invalid. """ # Split CSV lines into arrays of values. csv_values = [ line.split(',') for line in csv_lines ] - # Check that all entries exist in the dict. - csv_keys = set([ csv[0] for csv in csv_values ]) - self._check_entries_set(csv_keys, source) + # Update the full set of API signatures. + self._dict_keyset.update([ csv[0] for csv in csv_values ]) # Check that all flags are known. csv_flags = set(reduce(lambda x, y: set(x).union(y), [ csv[1:] for csv in csv_values ], [])) @@ -224,7 +209,7 @@ class FlagsDict: """Assigns a flag to given subset of entries. Args: - flag (string): One of FLAGS. + flag (string): One of ALL_FLAGS. apis (set): Subset of APIs to recieve the flag. source (string): Origin of `entries_subset`. Will be printed in error messages. @@ -245,18 +230,23 @@ def main(argv): # Parse arguments. args = vars(get_args()) - flags = FlagsDict(read_lines(args["public"]), read_lines(args["private"])) + # Initialize API->flags dictionary. + flags = FlagsDict() + + # Merge input CSV files into the dictionary. + # Do this first because CSV files produced by parsing API stubs will + # contain the full set of APIs. Subsequent additions from text files + # will be able to detect invalid entries, and/or filter all as-yet + # unassigned entries. + for filename in args["csv"]: + flags.parse_and_merge_csv(read_lines(filename), filename) # Combine inputs which do not require any particular order. # (1) Assign serialization API to whitelist. flags.assign_flag(FLAG_WHITELIST, flags.filter_apis(IS_SERIALIZATION)) - # (2) Merge input CSV files into the dictionary. - for filename in args["csv"]: - flags.parse_and_merge_csv(read_lines(filename), filename) - - # (3) Merge text files with a known flag into the dictionary. - for flag in FLAGS: + # (2) Merge text files with a known flag into the dictionary. + for flag in ALL_FLAGS: for filename in args[flag]: flags.assign_flag(flag, read_lines(filename), filename) @@ -265,13 +255,13 @@ def main(argv): # (a) the entry exists, and # (b) it has not been assigned any other flag. # Because of (b), this must run after all strict assignments have been performed. - for flag in FLAGS: + for flag in ALL_FLAGS: for filename in args[flag + FLAG_IGNORE_CONFLICTS_SUFFIX]: valid_entries = flags.get_valid_subset_of_unassigned_apis(read_lines(filename)) flags.assign_flag(flag, valid_entries, filename) # Assign all remaining entries to the blacklist. - flags.assign_flag(FLAG_BLACKLIST, flags.filter_apis(IS_UNASSIGNED)) + flags.assign_flag(FLAG_BLACKLIST, flags.filter_apis(HAS_NO_API_LIST_ASSIGNED)) # Write output. write_lines(args["output"], flags.generate_csv()) |