Merge "Rename vts-core to vts"
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..d97975c
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,3 @@
+third_party {
+ license_type: NOTICE
+}
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
deleted file mode 100644
index e69de29..0000000
--- a/MODULE_LICENSE_APACHE2
+++ /dev/null
diff --git a/audio/common/all-versions/default/service/android.hardware.audio.service.rc b/audio/common/all-versions/default/service/android.hardware.audio.service.rc
index 63d2542..f7e1e24 100644
--- a/audio/common/all-versions/default/service/android.hardware.audio.service.rc
+++ b/audio/common/all-versions/default/service/android.hardware.audio.service.rc
@@ -5,5 +5,5 @@
group audio camera drmrpc inet media mediadrm net_bt net_bt_admin net_bw_acct wakelock
capabilities BLOCK_SUSPEND
ioprio rt 4
- writepid /dev/cpuset/foreground/tasks /dev/stune/foreground/tasks
+ task_profiles ProcessCapacityHigh HighPerformance
onrestart restart audioserver
diff --git a/bluetooth/1.0/default/android.hardware.bluetooth@1.0-service.rc b/bluetooth/1.0/default/android.hardware.bluetooth@1.0-service.rc
index 9fa128d..def59de 100644
--- a/bluetooth/1.0/default/android.hardware.bluetooth@1.0-service.rc
+++ b/bluetooth/1.0/default/android.hardware.bluetooth@1.0-service.rc
@@ -4,5 +4,5 @@
capabilities BLOCK_SUSPEND NET_ADMIN SYS_NICE
user bluetooth
group bluetooth
- writepid /dev/stune/foreground/tasks
+ task_profiles HighPerformance
diff --git a/bluetooth/1.1/default/android.hardware.bluetooth@1.1-service.rc b/bluetooth/1.1/default/android.hardware.bluetooth@1.1-service.rc
index 49f0be3..5c7cbf4 100644
--- a/bluetooth/1.1/default/android.hardware.bluetooth@1.1-service.rc
+++ b/bluetooth/1.1/default/android.hardware.bluetooth@1.1-service.rc
@@ -5,5 +5,5 @@
capabilities BLOCK_SUSPEND NET_ADMIN SYS_NICE
user bluetooth
group bluetooth
- writepid /dev/stune/foreground/tasks
+ task_profiles HighPerformance
diff --git a/boot/1.1/vts/functional/VtsHalBootV1_1TargetTest.cpp b/boot/1.1/vts/functional/VtsHalBootV1_1TargetTest.cpp
index 7c58ef3..30b965d 100644
--- a/boot/1.1/vts/functional/VtsHalBootV1_1TargetTest.cpp
+++ b/boot/1.1/vts/functional/VtsHalBootV1_1TargetTest.cpp
@@ -76,7 +76,11 @@
for (const auto value : ValidMergeStatusValues()) {
EXPECT_TRUE(boot->setSnapshotMergeStatus(value).withDefault(false));
auto status = boot->getSnapshotMergeStatus();
- EXPECT_EQ(status, value);
+ if (value == MergeStatus::SNAPSHOTTED) {
+ EXPECT_TRUE(status == MergeStatus::SNAPSHOTTED || status == MergeStatus::NONE);
+ } else {
+ EXPECT_EQ(status, value);
+ }
}
}
diff --git a/camera/provider/2.4/default/android.hardware.camera.provider@2.4-external-service.rc b/camera/provider/2.4/default/android.hardware.camera.provider@2.4-external-service.rc
index 64cf321..52ade97 100644
--- a/camera/provider/2.4/default/android.hardware.camera.provider@2.4-external-service.rc
+++ b/camera/provider/2.4/default/android.hardware.camera.provider@2.4-external-service.rc
@@ -5,4 +5,4 @@
group audio camera input drmrpc usb
ioprio rt 4
capabilities SYS_NICE
- writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks
+ task_profiles CameraServiceCapacity MaxPerformance
diff --git a/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service-lazy.rc b/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service-lazy.rc
index e8549ed..63ded90 100644
--- a/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service-lazy.rc
+++ b/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service-lazy.rc
@@ -7,4 +7,4 @@
group audio camera input drmrpc
ioprio rt 4
capabilities SYS_NICE
- writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks
+ task_profiles CameraServiceCapacity MaxPerformance
diff --git a/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service-lazy_64.rc b/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service-lazy_64.rc
index 2dfac76..953d1af 100644
--- a/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service-lazy_64.rc
+++ b/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service-lazy_64.rc
@@ -7,4 +7,4 @@
group audio camera input drmrpc
ioprio rt 4
capabilities SYS_NICE
- writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks
+ task_profiles CameraServiceCapacity MaxPerformance
diff --git a/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service.rc b/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service.rc
index 913561b..f7ac9f8 100644
--- a/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service.rc
+++ b/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service.rc
@@ -5,4 +5,4 @@
group audio camera input drmrpc
ioprio rt 4
capabilities SYS_NICE
- writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks
+ task_profiles CameraServiceCapacity MaxPerformance
diff --git a/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service_64.rc b/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service_64.rc
index fd4826e..a32dd46 100644
--- a/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service_64.rc
+++ b/camera/provider/2.4/default/android.hardware.camera.provider@2.4-service_64.rc
@@ -5,4 +5,4 @@
group audio camera input drmrpc
ioprio rt 4
capabilities SYS_NICE
- writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks
+ task_profiles CameraServiceCapacity MaxPerformance
diff --git a/camera/provider/2.5/default/android.hardware.camera.provider@2.5-external-service.rc b/camera/provider/2.5/default/android.hardware.camera.provider@2.5-external-service.rc
index 107097e..b3b06b2 100644
--- a/camera/provider/2.5/default/android.hardware.camera.provider@2.5-external-service.rc
+++ b/camera/provider/2.5/default/android.hardware.camera.provider@2.5-external-service.rc
@@ -6,4 +6,4 @@
group audio camera input drmrpc usb
ioprio rt 4
capabilities SYS_NICE
- writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks
+ task_profiles CameraServiceCapacity MaxPerformance
diff --git a/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service-lazy.rc b/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service-lazy.rc
index b45158a..7c5e69b 100644
--- a/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service-lazy.rc
+++ b/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service-lazy.rc
@@ -8,4 +8,4 @@
group audio camera input drmrpc
ioprio rt 4
capabilities SYS_NICE
- writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks
+ task_profiles CameraServiceCapacity MaxPerformance
diff --git a/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service-lazy_64.rc b/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service-lazy_64.rc
index 955b28e..49bca8f 100644
--- a/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service-lazy_64.rc
+++ b/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service-lazy_64.rc
@@ -8,4 +8,4 @@
group audio camera input drmrpc
ioprio rt 4
capabilities SYS_NICE
- writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks
+ task_profiles CameraServiceCapacity MaxPerformance
diff --git a/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service.rc b/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service.rc
index c065815..4bd1fb4 100644
--- a/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service.rc
+++ b/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service.rc
@@ -6,4 +6,4 @@
group audio camera input drmrpc
ioprio rt 4
capabilities SYS_NICE
- writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks
+ task_profiles CameraServiceCapacity MaxPerformance
diff --git a/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service_64.rc b/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service_64.rc
index 63dd11d..b444325 100644
--- a/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service_64.rc
+++ b/camera/provider/2.5/default/android.hardware.camera.provider@2.5-service_64.rc
@@ -6,4 +6,4 @@
group audio camera input drmrpc
ioprio rt 4
capabilities SYS_NICE
- writepid /dev/cpuset/camera-daemon/tasks /dev/stune/top-app/tasks
+ task_profiles CameraServiceCapacity MaxPerformance
diff --git a/identity/aidl/Android.bp b/identity/aidl/Android.bp
index 7298c7d..14aef8e 100644
--- a/identity/aidl/Android.bp
+++ b/identity/aidl/Android.bp
@@ -18,5 +18,8 @@
},
},
},
- versions: ["1"],
+ versions: [
+ "1",
+ "2",
+ ],
}
diff --git a/identity/aidl/aidl_api/android.hardware.identity/2/.hash b/identity/aidl/aidl_api/android.hardware.identity/2/.hash
new file mode 100644
index 0000000..1b2c1b6
--- /dev/null
+++ b/identity/aidl/aidl_api/android.hardware.identity/2/.hash
@@ -0,0 +1 @@
+3b0b10b618dbc4bf283aa2bf78833ad3de0a5928
diff --git a/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/Certificate.aidl b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/Certificate.aidl
new file mode 100644
index 0000000..7e3002d
--- /dev/null
+++ b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/Certificate.aidl
@@ -0,0 +1,22 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.identity;
+@VintfStability
+parcelable Certificate {
+ byte[] encodedCertificate;
+}
diff --git a/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/CipherSuite.aidl b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/CipherSuite.aidl
new file mode 100644
index 0000000..447203f
--- /dev/null
+++ b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/CipherSuite.aidl
@@ -0,0 +1,22 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.identity;
+@Backing(type="int") @VintfStability
+enum CipherSuite {
+ CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256 = 1,
+}
diff --git a/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/HardwareInformation.aidl b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/HardwareInformation.aidl
new file mode 100644
index 0000000..e1296e0
--- /dev/null
+++ b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/HardwareInformation.aidl
@@ -0,0 +1,26 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.identity;
+@VintfStability
+parcelable HardwareInformation {
+ @utf8InCpp String credentialStoreName;
+ @utf8InCpp String credentialStoreAuthorName;
+ int dataChunkSize;
+ boolean isDirectAccess;
+ @utf8InCpp String[] supportedDocTypes;
+}
diff --git a/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/IIdentityCredential.aidl b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/IIdentityCredential.aidl
new file mode 100644
index 0000000..e8e93f8
--- /dev/null
+++ b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/IIdentityCredential.aidl
@@ -0,0 +1,31 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.identity;
+@VintfStability
+interface IIdentityCredential {
+ byte[] deleteCredential();
+ byte[] createEphemeralKeyPair();
+ void setReaderEphemeralPublicKey(in byte[] publicKey);
+ long createAuthChallenge();
+ void startRetrieval(in android.hardware.identity.SecureAccessControlProfile[] accessControlProfiles, in android.hardware.keymaster.HardwareAuthToken authToken, in byte[] itemsRequest, in byte[] signingKeyBlob, in byte[] sessionTranscript, in byte[] readerSignature, in int[] requestCounts);
+ void startRetrieveEntryValue(in @utf8InCpp String nameSpace, in @utf8InCpp String name, in int entrySize, in int[] accessControlProfileIds);
+ byte[] retrieveEntryValue(in byte[] encryptedContent);
+ void finishRetrieval(out byte[] mac, out byte[] deviceNameSpaces);
+ android.hardware.identity.Certificate generateSigningKeyPair(out byte[] signingKeyBlob);
+ void setRequestedNamespaces(in android.hardware.identity.RequestNamespace[] requestNamespaces);
+}
diff --git a/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/IIdentityCredentialStore.aidl b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/IIdentityCredentialStore.aidl
new file mode 100644
index 0000000..5dafb76
--- /dev/null
+++ b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/IIdentityCredentialStore.aidl
@@ -0,0 +1,37 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.identity;
+@VintfStability
+interface IIdentityCredentialStore {
+ android.hardware.identity.HardwareInformation getHardwareInformation();
+ android.hardware.identity.IWritableIdentityCredential createCredential(in @utf8InCpp String docType, in boolean testCredential);
+ android.hardware.identity.IIdentityCredential getCredential(in android.hardware.identity.CipherSuite cipherSuite, in byte[] credentialData);
+ const int STATUS_OK = 0;
+ const int STATUS_FAILED = 1;
+ const int STATUS_CIPHER_SUITE_NOT_SUPPORTED = 2;
+ const int STATUS_INVALID_DATA = 3;
+ const int STATUS_INVALID_AUTH_TOKEN = 4;
+ const int STATUS_INVALID_ITEMS_REQUEST_MESSAGE = 5;
+ const int STATUS_READER_SIGNATURE_CHECK_FAILED = 6;
+ const int STATUS_EPHEMERAL_PUBLIC_KEY_NOT_FOUND = 7;
+ const int STATUS_USER_AUTHENTICATION_FAILED = 8;
+ const int STATUS_READER_AUTHENTICATION_FAILED = 9;
+ const int STATUS_NO_ACCESS_CONTROL_PROFILES = 10;
+ const int STATUS_NOT_IN_REQUEST_MESSAGE = 11;
+ const int STATUS_SESSION_TRANSCRIPT_MISMATCH = 12;
+}
diff --git a/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/IWritableIdentityCredential.aidl b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/IWritableIdentityCredential.aidl
new file mode 100644
index 0000000..c5ac9d6
--- /dev/null
+++ b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/IWritableIdentityCredential.aidl
@@ -0,0 +1,28 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.identity;
+@VintfStability
+interface IWritableIdentityCredential {
+ android.hardware.identity.Certificate[] getAttestationCertificate(in byte[] attestationApplicationId, in byte[] attestationChallenge);
+ void startPersonalization(in int accessControlProfileCount, in int[] entryCounts);
+ android.hardware.identity.SecureAccessControlProfile addAccessControlProfile(in int id, in android.hardware.identity.Certificate readerCertificate, in boolean userAuthenticationRequired, in long timeoutMillis, in long secureUserId);
+ void beginAddEntry(in int[] accessControlProfileIds, in @utf8InCpp String nameSpace, in @utf8InCpp String name, in int entrySize);
+ byte[] addEntryValue(in byte[] content);
+ void finishAddingEntries(out byte[] credentialData, out byte[] proofOfProvisioningSignature);
+ void setExpectedProofOfProvisioningSize(in int expectedProofOfProvisioningSize);
+}
diff --git a/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/RequestDataItem.aidl b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/RequestDataItem.aidl
new file mode 100644
index 0000000..24ec26a
--- /dev/null
+++ b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/RequestDataItem.aidl
@@ -0,0 +1,24 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.identity;
+@VintfStability
+parcelable RequestDataItem {
+ @utf8InCpp String name;
+ long size;
+ int[] accessControlProfileIds;
+}
diff --git a/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/RequestNamespace.aidl b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/RequestNamespace.aidl
new file mode 100644
index 0000000..af00f3b
--- /dev/null
+++ b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/RequestNamespace.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.identity;
+@VintfStability
+parcelable RequestNamespace {
+ @utf8InCpp String namespaceName;
+ android.hardware.identity.RequestDataItem[] items;
+}
diff --git a/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/SecureAccessControlProfile.aidl b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/SecureAccessControlProfile.aidl
new file mode 100644
index 0000000..dfc1ad0
--- /dev/null
+++ b/identity/aidl/aidl_api/android.hardware.identity/2/android/hardware/identity/SecureAccessControlProfile.aidl
@@ -0,0 +1,27 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.identity;
+@VintfStability
+parcelable SecureAccessControlProfile {
+ int id;
+ android.hardware.identity.Certificate readerCertificate;
+ boolean userAuthenticationRequired;
+ long timeoutMillis;
+ long secureUserId;
+ byte[] mac;
+}
diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredential.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredential.aidl
index 58b90b5..e8e93f8 100644
--- a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredential.aidl
+++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IIdentityCredential.aidl
@@ -27,4 +27,5 @@
byte[] retrieveEntryValue(in byte[] encryptedContent);
void finishRetrieval(out byte[] mac, out byte[] deviceNameSpaces);
android.hardware.identity.Certificate generateSigningKeyPair(out byte[] signingKeyBlob);
+ void setRequestedNamespaces(in android.hardware.identity.RequestNamespace[] requestNamespaces);
}
diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IWritableIdentityCredential.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IWritableIdentityCredential.aidl
index 32f283c..c5ac9d6 100644
--- a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IWritableIdentityCredential.aidl
+++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/IWritableIdentityCredential.aidl
@@ -24,4 +24,5 @@
void beginAddEntry(in int[] accessControlProfileIds, in @utf8InCpp String nameSpace, in @utf8InCpp String name, in int entrySize);
byte[] addEntryValue(in byte[] content);
void finishAddingEntries(out byte[] credentialData, out byte[] proofOfProvisioningSignature);
+ void setExpectedProofOfProvisioningSize(in int expectedProofOfProvisioningSize);
}
diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestDataItem.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestDataItem.aidl
new file mode 100644
index 0000000..24ec26a
--- /dev/null
+++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestDataItem.aidl
@@ -0,0 +1,24 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.identity;
+@VintfStability
+parcelable RequestDataItem {
+ @utf8InCpp String name;
+ long size;
+ int[] accessControlProfileIds;
+}
diff --git a/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestNamespace.aidl b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestNamespace.aidl
new file mode 100644
index 0000000..af00f3b
--- /dev/null
+++ b/identity/aidl/aidl_api/android.hardware.identity/current/android/hardware/identity/RequestNamespace.aidl
@@ -0,0 +1,23 @@
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
+// edit this file. It looks like you are doing that because you have modified
+// an AIDL interface in a backward-incompatible way, e.g., deleting a function
+// from an interface or a field from a parcelable and it broke the build. That
+// breakage is intended.
+//
+// You must not make a backward incompatible changes to the AIDL files built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.identity;
+@VintfStability
+parcelable RequestNamespace {
+ @utf8InCpp String namespaceName;
+ android.hardware.identity.RequestDataItem[] items;
+}
diff --git a/identity/aidl/android/hardware/identity/IIdentityCredential.aidl b/identity/aidl/android/hardware/identity/IIdentityCredential.aidl
index 7d14f03..7cd25e6 100644
--- a/identity/aidl/android/hardware/identity/IIdentityCredential.aidl
+++ b/identity/aidl/android/hardware/identity/IIdentityCredential.aidl
@@ -17,6 +17,7 @@
package android.hardware.identity;
import android.hardware.identity.Certificate;
+import android.hardware.identity.RequestNamespace;
import android.hardware.identity.SecureAccessControlProfile;
import android.hardware.keymaster.HardwareAuthToken;
@@ -82,6 +83,8 @@
/**
* Start an entry retrieval process.
*
+ * The setRequestedNamespaces() method will be called before this method.
+ *
* This method be called after createEphemeralKeyPair(), setReaderEphemeralPublicKey(),
* createAuthChallenge() and before startRetrieveEntry(). This method call is followed by
* multiple calls of startRetrieveEntryValue(), retrieveEntryValue(), and finally
@@ -274,7 +277,7 @@
* "DeviceAuthentication",
* SessionTranscript,
* DocType,
- * DeviceNameSpaceBytes,
+ * DeviceNameSpacesBytes,
* ]
*
* DocType = tstr
@@ -343,4 +346,16 @@
* @return an X.509 certificate for the new signing key, signed by the credential key.
*/
Certificate generateSigningKeyPair(out byte[] signingKeyBlob);
+
+ /**
+ * Sets the namespaces and data items (including their size and access control profiles)
+ * which will be requested. This method must be called before startRetrieval() is called.
+ *
+ * This information is provided to make it possible for a HAL implementation to
+ * incrementally build up cryptographically authenticated data which includes the
+ * DeviceNameSpaces CBOR.
+ *
+ * @param requestNamespaces Namespaces and data items which will be requested.
+ */
+ void setRequestedNamespaces(in RequestNamespace[] requestNamespaces);
}
diff --git a/identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl b/identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl
index 9673821..b7ad283 100644
--- a/identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl
+++ b/identity/aidl/android/hardware/identity/IWritableIdentityCredential.aidl
@@ -120,6 +120,8 @@
*
* startPersonalization must not be called more than once.
*
+ * The setExpectedProofOfProvisioningSize() method will be called before this method.
+ *
* @param accessControlProfileCount specifies the number of access control profiles that will
* be provisioned with addAccessControlProfile().
*
@@ -140,7 +142,8 @@
* with STATUS_INVALID_DATA.
*
* @param id a numeric identifier that must be unique within the context of a Credential and may
- * be used to reference the profile. If this is not satisfied the call fails with
+ * be used to reference the profile. This id must be non-negative and less than 32 (allowing
+ * for a total of 32 profiles). If this is not satisfied the call fails with
* STATUS_INVALID_DATA.
*
* @param readerCertificate if non-empty, specifies a single X.509 certificate (not a chain of
@@ -287,4 +290,16 @@
*/
void finishAddingEntries(out byte[] credentialData,
out byte[] proofOfProvisioningSignature);
+
+ /**
+ * Sets the expected size of the ProofOfProvisioning returned by finishAddingEntries(). This
+ * method must be called before startPersonalization() is called.
+ *
+ * This information is provided to make it possible for a HAL implementation to
+ * incrementally build up cryptographically authenticated data which includes the
+ * ProofOfProvisioning CBOR.
+ *
+ * @param expectedProofOfProvisioningSize the expected size of ProofOfProvisioning.
+ */
+ void setExpectedProofOfProvisioningSize(in int expectedProofOfProvisioningSize);
}
diff --git a/identity/aidl/android/hardware/identity/RequestDataItem.aidl b/identity/aidl/android/hardware/identity/RequestDataItem.aidl
new file mode 100644
index 0000000..05bc762
--- /dev/null
+++ b/identity/aidl/android/hardware/identity/RequestDataItem.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2020 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.hardware.identity;
+
+@VintfStability
+parcelable RequestDataItem {
+ /**
+ * The data item name being requested, for example "driving_privileges".
+ */
+ @utf8InCpp String name;
+
+ /**
+ * The size of the data item value.
+ *
+ * Data item values are always encoded as CBOR so this is the length of
+ * the CBOR encoding of the value.
+ */
+ long size;
+
+ /**
+ * The access control profile ids this data item is configured with.
+ */
+ int[] accessControlProfileIds;
+}
diff --git a/identity/aidl/android/hardware/identity/RequestNamespace.aidl b/identity/aidl/android/hardware/identity/RequestNamespace.aidl
new file mode 100644
index 0000000..4d61506
--- /dev/null
+++ b/identity/aidl/android/hardware/identity/RequestNamespace.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2020 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.hardware.identity;
+
+import android.hardware.identity.RequestDataItem;
+
+@VintfStability
+parcelable RequestNamespace {
+ /**
+ * The name of the namespace that items are being requested from, for
+ * example "org.iso.18013.5.1".
+ */
+ @utf8InCpp String namespaceName;
+
+ /**
+ * The data items requested.
+ */
+ RequestDataItem[] items;
+}
diff --git a/identity/aidl/default/IdentityCredential.cpp b/identity/aidl/default/IdentityCredential.cpp
index aaae1f6..ff4107a 100644
--- a/identity/aidl/default/IdentityCredential.cpp
+++ b/identity/aidl/default/IdentityCredential.cpp
@@ -25,6 +25,7 @@
#include <string.h>
#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
#include <cppbor.h>
#include <cppbor_parse.h>
@@ -32,6 +33,7 @@
namespace aidl::android::hardware::identity {
using ::aidl::android::hardware::keymaster::Timestamp;
+using ::android::base::StringPrintf;
using ::std::optional;
using namespace ::android::hardware::identity;
@@ -253,6 +255,12 @@
return true;
}
+ndk::ScopedAStatus IdentityCredential::setRequestedNamespaces(
+ const vector<RequestNamespace>& requestNamespaces) {
+ requestNamespaces_ = requestNamespaces;
+ return ndk::ScopedAStatus::ok();
+}
+
ndk::ScopedAStatus IdentityCredential::startRetrieval(
const vector<SecureAccessControlProfile>& accessControlProfiles,
const HardwareAuthToken& authToken, const vector<uint8_t>& itemsRequest,
@@ -447,7 +455,7 @@
"Type mismatch in nameSpaces map"));
}
string requestedNamespace = nsKey->value();
- vector<string> requestedKeys;
+ set<string> requestedKeys;
for (size_t m = 0; m < nsInnerMap->size(); m++) {
const auto& [innerMapKeyItem, innerMapValueItem] = (*nsInnerMap)[m];
const cppbor::Tstr* nameItem = innerMapKeyItem->asTstr();
@@ -459,13 +467,13 @@
IIdentityCredentialStore::STATUS_INVALID_ITEMS_REQUEST_MESSAGE,
"Type mismatch in value in nameSpaces map"));
}
- requestedKeys.push_back(nameItem->value());
+ requestedKeys.insert(nameItem->value());
}
requestedNameSpacesAndNames_[requestedNamespace] = requestedKeys;
}
}
- // Finally, validate all the access control profiles in the requestData.
+ // Validate all the access control profiles in the requestData.
bool haveAuthToken = (authToken.mac.size() > 0);
for (const auto& profile : accessControlProfiles) {
if (!secureAccessControlProfileCheckMac(profile, storageKey_)) {
@@ -496,10 +504,118 @@
itemsRequest_ = itemsRequest;
signingKeyBlob_ = signingKeyBlob;
+ // Finally, calculate the size of DeviceNameSpaces. We need to know it ahead of time.
+ expectedDeviceNameSpacesSize_ = calcDeviceNameSpacesSize();
+
numStartRetrievalCalls_ += 1;
return ndk::ScopedAStatus::ok();
}
+size_t cborNumBytesForLength(size_t length) {
+ if (length < 24) {
+ return 0;
+ } else if (length <= 0xff) {
+ return 1;
+ } else if (length <= 0xffff) {
+ return 2;
+ } else if (length <= 0xffffffff) {
+ return 4;
+ }
+ return 8;
+}
+
+size_t cborNumBytesForTstr(const string& value) {
+ return 1 + cborNumBytesForLength(value.size()) + value.size();
+}
+
+size_t IdentityCredential::calcDeviceNameSpacesSize() {
+ /*
+ * This is how DeviceNameSpaces is defined:
+ *
+ * DeviceNameSpaces = {
+ * * NameSpace => DeviceSignedItems
+ * }
+ * DeviceSignedItems = {
+ * + DataItemName => DataItemValue
+ * }
+ *
+ * Namespace = tstr
+ * DataItemName = tstr
+ * DataItemValue = any
+ *
+ * This function will calculate its length using knowledge of how CBOR is
+ * encoded.
+ */
+ size_t ret = 0;
+ size_t numNamespacesWithValues = 0;
+ for (const RequestNamespace& rns : requestNamespaces_) {
+ vector<RequestDataItem> itemsToInclude;
+
+ for (const RequestDataItem& rdi : rns.items) {
+ // If we have a CBOR request message, skip if item isn't in it
+ if (itemsRequest_.size() > 0) {
+ const auto& it = requestedNameSpacesAndNames_.find(rns.namespaceName);
+ if (it == requestedNameSpacesAndNames_.end()) {
+ continue;
+ }
+ const set<string>& dataItemNames = it->second;
+ if (dataItemNames.find(rdi.name) == dataItemNames.end()) {
+ continue;
+ }
+ }
+
+ // Access is granted if at least one of the profiles grants access.
+ //
+ // If an item is configured without any profiles, access is denied.
+ //
+ bool authorized = false;
+ for (auto id : rdi.accessControlProfileIds) {
+ auto it = profileIdToAccessCheckResult_.find(id);
+ if (it != profileIdToAccessCheckResult_.end()) {
+ int accessControlForProfile = it->second;
+ if (accessControlForProfile == IIdentityCredentialStore::STATUS_OK) {
+ authorized = true;
+ break;
+ }
+ }
+ }
+ if (!authorized) {
+ continue;
+ }
+
+ itemsToInclude.push_back(rdi);
+ }
+
+ // If no entries are to be in the namespace, we don't include it...
+ if (itemsToInclude.size() == 0) {
+ continue;
+ }
+
+ // Key: NameSpace
+ ret += cborNumBytesForTstr(rns.namespaceName);
+
+ // Value: Open the DeviceSignedItems map
+ ret += 1 + cborNumBytesForLength(itemsToInclude.size());
+
+ for (const RequestDataItem& item : itemsToInclude) {
+ // Key: DataItemName
+ ret += cborNumBytesForTstr(item.name);
+
+ // Value: DataItemValue - entryData.size is the length of serialized CBOR so we use
+ // that.
+ ret += item.size;
+ }
+
+ numNamespacesWithValues++;
+ }
+
+ // Now that we now the nunber of namespaces with values, we know how many
+ // bytes the DeviceNamespaces map in the beginning is going to take up.
+ ret += 1 + cborNumBytesForLength(numNamespacesWithValues);
+
+ return ret;
+}
+
ndk::ScopedAStatus IdentityCredential::startRetrieveEntryValue(
const string& nameSpace, const string& name, int32_t entrySize,
const vector<int32_t>& accessControlProfileIds) {
@@ -558,8 +674,8 @@
IIdentityCredentialStore::STATUS_NOT_IN_REQUEST_MESSAGE,
"Name space was not requested in startRetrieval"));
}
- const auto& dataItemNames = it->second;
- if (std::find(dataItemNames.begin(), dataItemNames.end(), name) == dataItemNames.end()) {
+ const set<string>& dataItemNames = it->second;
+ if (dataItemNames.find(name) == dataItemNames.end()) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_NOT_IN_REQUEST_MESSAGE,
"Data item name in name space was not requested in startRetrieval"));
@@ -653,6 +769,17 @@
}
vector<uint8_t> encodedDeviceNameSpaces = deviceNameSpacesMap_.encode();
+ if (encodedDeviceNameSpaces.size() != expectedDeviceNameSpacesSize_) {
+ LOG(ERROR) << "encodedDeviceNameSpaces is " << encodedDeviceNameSpaces.size() << " bytes, "
+ << "was expecting " << expectedDeviceNameSpacesSize_;
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ StringPrintf(
+ "Unexpected CBOR size %zd for encodedDeviceNameSpaces, was expecting %zd",
+ encodedDeviceNameSpaces.size(), expectedDeviceNameSpacesSize_)
+ .c_str()));
+ }
+
// If there's no signing key or no sessionTranscript or no reader ephemeral
// public key, we return the empty MAC.
optional<vector<uint8_t>> mac;
diff --git a/identity/aidl/default/IdentityCredential.h b/identity/aidl/default/IdentityCredential.h
index 6072afe..a8bad88 100644
--- a/identity/aidl/default/IdentityCredential.h
+++ b/identity/aidl/default/IdentityCredential.h
@@ -32,15 +32,17 @@
using ::aidl::android::hardware::keymaster::HardwareAuthToken;
using ::std::map;
+using ::std::set;
using ::std::string;
using ::std::vector;
-using MapStringToVectorOfStrings = map<string, vector<string>>;
-
class IdentityCredential : public BnIdentityCredential {
public:
IdentityCredential(const vector<uint8_t>& credentialData)
- : credentialData_(credentialData), numStartRetrievalCalls_(0), authChallenge_(0) {}
+ : credentialData_(credentialData),
+ numStartRetrievalCalls_(0),
+ authChallenge_(0),
+ expectedDeviceNameSpacesSize_(0) {}
// Parses and decrypts credentialData_, return a status code from
// IIdentityCredentialStore. Must be called right after construction.
@@ -51,6 +53,8 @@
ndk::ScopedAStatus createEphemeralKeyPair(vector<uint8_t>* outKeyPair) override;
ndk::ScopedAStatus setReaderEphemeralPublicKey(const vector<uint8_t>& publicKey) override;
ndk::ScopedAStatus createAuthChallenge(int64_t* outChallenge) override;
+ ndk::ScopedAStatus setRequestedNamespaces(
+ const vector<RequestNamespace>& requestNamespaces) override;
ndk::ScopedAStatus startRetrieval(
const vector<SecureAccessControlProfile>& accessControlProfiles,
const HardwareAuthToken& authToken, const vector<uint8_t>& itemsRequest,
@@ -86,6 +90,9 @@
// Set by createAuthChallenge()
uint64_t authChallenge_;
+ // Set by setRequestedNamespaces()
+ vector<RequestNamespace> requestNamespaces_;
+
// Set at startRetrieval() time.
map<int32_t, int> profileIdToAccessCheckResult_;
vector<uint8_t> signingKeyBlob_;
@@ -93,16 +100,21 @@
std::unique_ptr<cppbor::Item> sessionTranscriptItem_;
vector<uint8_t> itemsRequest_;
vector<int32_t> requestCountsRemaining_;
- MapStringToVectorOfStrings requestedNameSpacesAndNames_;
+ map<string, set<string>> requestedNameSpacesAndNames_;
cppbor::Map deviceNameSpacesMap_;
cppbor::Map currentNameSpaceDeviceNameSpacesMap_;
+ // Calculated at startRetrieval() time.
+ size_t expectedDeviceNameSpacesSize_;
+
// Set at startRetrieveEntryValue() time.
string currentNameSpace_;
string currentName_;
size_t entryRemainingBytes_;
vector<uint8_t> entryValue_;
vector<uint8_t> entryAdditionalData_;
+
+ size_t calcDeviceNameSpacesSize();
};
} // namespace aidl::android::hardware::identity
diff --git a/identity/aidl/default/WritableIdentityCredential.cpp b/identity/aidl/default/WritableIdentityCredential.cpp
index bce913a..8bc4b49 100644
--- a/identity/aidl/default/WritableIdentityCredential.cpp
+++ b/identity/aidl/default/WritableIdentityCredential.cpp
@@ -22,6 +22,7 @@
#include <android/hardware/identity/support/IdentityCredentialSupport.h>
#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
#include <cppbor/cppbor.h>
#include <cppbor/cppbor_parse.h>
@@ -34,6 +35,7 @@
namespace aidl::android::hardware::identity {
+using ::android::base::StringPrintf;
using ::std::optional;
using namespace ::android::hardware::identity;
@@ -44,6 +46,8 @@
return false;
}
storageKey_ = random.value();
+ startPersonalizationCalled_ = false;
+ firstEntry_ = true;
return true;
}
@@ -103,8 +107,20 @@
return ndk::ScopedAStatus::ok();
}
+ndk::ScopedAStatus WritableIdentityCredential::setExpectedProofOfProvisioningSize(
+ int32_t expectedProofOfProvisioningSize) {
+ expectedProofOfProvisioningSize_ = expectedProofOfProvisioningSize;
+ return ndk::ScopedAStatus::ok();
+}
+
ndk::ScopedAStatus WritableIdentityCredential::startPersonalization(
int32_t accessControlProfileCount, const vector<int32_t>& entryCounts) {
+ if (startPersonalizationCalled_) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "startPersonalization called already"));
+ }
+
+ startPersonalizationCalled_ = true;
numAccessControlProfileRemaining_ = accessControlProfileCount;
remainingEntryCounts_ = entryCounts;
entryNameSpace_ = "";
@@ -128,6 +144,19 @@
"numAccessControlProfileRemaining_ is 0 and expected non-zero"));
}
+ if (accessControlProfileIds_.find(id) != accessControlProfileIds_.end()) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "Access Control Profile id must be unique"));
+ }
+ accessControlProfileIds_.insert(id);
+
+ if (id < 0 || id >= 32) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "Access Control Profile id must be non-negative and less than 32"));
+ }
+
// Spec requires if |userAuthenticationRequired| is false, then |timeoutMillis| must also
// be zero.
if (!userAuthenticationRequired && timeoutMillis != 0) {
@@ -183,12 +212,20 @@
}
// Handle initial beginEntry() call.
- if (entryNameSpace_ == "") {
+ if (firstEntry_) {
+ firstEntry_ = false;
entryNameSpace_ = nameSpace;
+ allNameSpaces_.insert(nameSpace);
}
// If the namespace changed...
if (nameSpace != entryNameSpace_) {
+ if (allNameSpaces_.find(nameSpace) != allNameSpaces_.end()) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "Name space cannot be added in interleaving fashion"));
+ }
+
// Then check that all entries in the previous namespace have been added..
if (remainingEntryCounts_[0] != 0) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
@@ -196,6 +233,8 @@
"New namespace but a non-zero number of entries remain to be added"));
}
remainingEntryCounts_.erase(remainingEntryCounts_.begin());
+ remainingEntryCounts_[0] -= 1;
+ allNameSpaces_.insert(nameSpace);
if (signedDataCurrentNamespace_.size() > 0) {
signedDataNamespaces_.add(entryNameSpace_, std::move(signedDataCurrentNamespace_));
@@ -328,6 +367,18 @@
ndk::ScopedAStatus WritableIdentityCredential::finishAddingEntries(
vector<uint8_t>* outCredentialData, vector<uint8_t>* outProofOfProvisioningSignature) {
+ if (numAccessControlProfileRemaining_ != 0) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "numAccessControlProfileRemaining_ is not 0 and expected zero"));
+ }
+
+ if (remainingEntryCounts_.size() > 1 || remainingEntryCounts_[0] != 0) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "More entry spaces remain than startPersonalization configured"));
+ }
+
if (signedDataCurrentNamespace_.size() > 0) {
signedDataNamespaces_.add(entryNameSpace_, std::move(signedDataCurrentNamespace_));
}
@@ -339,6 +390,16 @@
.add(testCredential_);
vector<uint8_t> encodedCbor = popArray.encode();
+ if (encodedCbor.size() != expectedProofOfProvisioningSize_) {
+ LOG(ERROR) << "CBOR for proofOfProvisioning is " << encodedCbor.size() << " bytes, "
+ << "was expecting " << expectedProofOfProvisioningSize_;
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ StringPrintf("Unexpected CBOR size %zd for proofOfProvisioning, was expecting %zd",
+ encodedCbor.size(), expectedProofOfProvisioningSize_)
+ .c_str()));
+ }
+
optional<vector<uint8_t>> signature = support::coseSignEcDsa(credentialPrivKey_,
encodedCbor, // payload
{}, // additionalData
diff --git a/identity/aidl/default/WritableIdentityCredential.h b/identity/aidl/default/WritableIdentityCredential.h
index 4b6fca8..5645852 100644
--- a/identity/aidl/default/WritableIdentityCredential.h
+++ b/identity/aidl/default/WritableIdentityCredential.h
@@ -21,9 +21,11 @@
#include <android/hardware/identity/support/IdentityCredentialSupport.h>
#include <cppbor.h>
+#include <set>
namespace aidl::android::hardware::identity {
+using ::std::set;
using ::std::string;
using ::std::vector;
@@ -41,6 +43,9 @@
const vector<uint8_t>& attestationChallenge,
vector<Certificate>* outCertificateChain) override;
+ ndk::ScopedAStatus setExpectedProofOfProvisioningSize(
+ int32_t expectedProofOfProvisioningSize) override;
+
ndk::ScopedAStatus startPersonalization(int32_t accessControlProfileCount,
const vector<int32_t>& entryCounts) override;
@@ -60,12 +65,14 @@
vector<uint8_t>* outCredentialData,
vector<uint8_t>* outProofOfProvisioningSignature) override;
- // private:
+ private:
string docType_;
bool testCredential_;
// This is set in initialize().
vector<uint8_t> storageKey_;
+ bool startPersonalizationCalled_;
+ bool firstEntry_;
// These are set in getAttestationCertificate().
vector<uint8_t> credentialPrivKey_;
@@ -78,6 +85,10 @@
cppbor::Array signedDataAccessControlProfiles_;
cppbor::Map signedDataNamespaces_;
cppbor::Array signedDataCurrentNamespace_;
+ size_t expectedProofOfProvisioningSize_;
+
+ // This field is initialized in addAccessControlProfile
+ set<int32_t> accessControlProfileIds_;
// These fields are initialized during beginAddEntry()
size_t entryRemainingBytes_;
@@ -86,6 +97,7 @@
string entryName_;
vector<int32_t> entryAccessControlProfileIds_;
vector<uint8_t> entryBytes_;
+ set<string> allNameSpaces_;
};
} // namespace aidl::android::hardware::identity
diff --git a/identity/aidl/vts/Android.bp b/identity/aidl/vts/Android.bp
index ef8beb4..e4780bf 100644
--- a/identity/aidl/vts/Android.bp
+++ b/identity/aidl/vts/Android.bp
@@ -4,7 +4,11 @@
"VtsHalTargetTestDefaults",
"use_libaidlvintf_gtest_helper_static",
],
- srcs: ["VtsHalIdentityTargetTest.cpp"],
+ srcs: [
+ "VtsHalIdentityEndToEndTest.cpp",
+ "VtsIWritableIdentityCredentialTests.cpp",
+ "VtsIdentityTestUtils.cpp",
+ ],
shared_libs: [
"libbinder",
"libcrypto",
diff --git a/identity/aidl/vts/VtsHalIdentityTargetTest.cpp b/identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp
similarity index 69%
rename from identity/aidl/vts/VtsHalIdentityTargetTest.cpp
rename to identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp
index ea37fdc..807feab 100644
--- a/identity/aidl/vts/VtsHalIdentityTargetTest.cpp
+++ b/identity/aidl/vts/VtsHalIdentityEndToEndTest.cpp
@@ -28,8 +28,11 @@
#include <future>
#include <map>
+#include "VtsIdentityTestUtils.h"
+
namespace android::hardware::identity {
+using std::endl;
using std::map;
using std::optional;
using std::string;
@@ -41,51 +44,6 @@
using ::android::hardware::keymaster::HardwareAuthToken;
-// ---------------------------------------------------------------------------
-// Test Data.
-// ---------------------------------------------------------------------------
-
-struct TestEntryData {
- TestEntryData(string nameSpace, string name, vector<int32_t> profileIds)
- : nameSpace(nameSpace), name(name), profileIds(profileIds) {}
-
- TestEntryData(string nameSpace, string name, const string& value, vector<int32_t> profileIds)
- : TestEntryData(nameSpace, name, profileIds) {
- valueCbor = cppbor::Tstr(((const char*)value.data())).encode();
- }
- TestEntryData(string nameSpace, string name, const vector<uint8_t>& value,
- vector<int32_t> profileIds)
- : TestEntryData(nameSpace, name, profileIds) {
- valueCbor = cppbor::Bstr(value).encode();
- }
- TestEntryData(string nameSpace, string name, bool value, vector<int32_t> profileIds)
- : TestEntryData(nameSpace, name, profileIds) {
- valueCbor = cppbor::Bool(value).encode();
- }
- TestEntryData(string nameSpace, string name, int64_t value, vector<int32_t> profileIds)
- : TestEntryData(nameSpace, name, profileIds) {
- if (value >= 0) {
- valueCbor = cppbor::Uint(value).encode();
- } else {
- valueCbor = cppbor::Nint(-value).encode();
- }
- }
-
- string nameSpace;
- string name;
- vector<uint8_t> valueCbor;
- vector<int32_t> profileIds;
-};
-
-struct TestProfile {
- uint16_t id;
- vector<uint8_t> readerCertificate;
- bool userAuthenticationRequired;
- uint64_t timeoutMillis;
-};
-
-// ----------------------------------------------------------------
-
class IdentityAidl : public testing::TestWithParam<std::string> {
public:
virtual void SetUp() override {
@@ -108,39 +66,26 @@
TEST_P(IdentityAidl, createAndRetrieveCredential) {
// First, generate a key-pair for the reader since its public key will be
// part of the request data.
- optional<vector<uint8_t>> readerKeyPKCS8 = support::createEcKeyPair();
- ASSERT_TRUE(readerKeyPKCS8);
- optional<vector<uint8_t>> readerPublicKey =
- support::ecKeyPairGetPublicKey(readerKeyPKCS8.value());
- optional<vector<uint8_t>> readerKey = support::ecKeyPairGetPrivateKey(readerKeyPKCS8.value());
- string serialDecimal = "1234";
- string issuer = "Android Open Source Project";
- string subject = "Android IdentityCredential VTS Test";
- time_t validityNotBefore = time(nullptr);
- time_t validityNotAfter = validityNotBefore + 365 * 24 * 3600;
- optional<vector<uint8_t>> readerCertificate = support::ecPublicKeyGenerateCertificate(
- readerPublicKey.value(), readerKey.value(), serialDecimal, issuer, subject,
- validityNotBefore, validityNotAfter);
+ vector<uint8_t> readerKey;
+ optional<vector<uint8_t>> readerCertificate =
+ test_utils::GenerateReaderCertificate("1234", readerKey);
ASSERT_TRUE(readerCertificate);
// Make the portrait image really big (just shy of 256 KiB) to ensure that
// the chunking code gets exercised.
vector<uint8_t> portraitImage;
- portraitImage.resize(256 * 1024 - 10);
- for (size_t n = 0; n < portraitImage.size(); n++) {
- portraitImage[n] = (uint8_t)n;
- }
+ test_utils::SetImageData(portraitImage);
// Access control profiles:
- const vector<TestProfile> testProfiles = {// Profile 0 (reader authentication)
- {0, readerCertificate.value(), false, 0},
- // Profile 1 (no authentication)
- {1, {}, false, 0}};
+ const vector<test_utils::TestProfile> testProfiles = {// Profile 0 (reader authentication)
+ {0, readerCertificate.value(), false, 0},
+ // Profile 1 (no authentication)
+ {1, {}, false, 0}};
HardwareAuthToken authToken;
// Here's the actual test data:
- const vector<TestEntryData> testEntries = {
+ const vector<test_utils::TestEntryData> testEntries = {
{"PersonalData", "Last name", string("Turing"), vector<int32_t>{0, 1}},
{"PersonalData", "Birth date", string("19120623"), vector<int32_t>{0, 1}},
{"PersonalData", "First name", string("Alan"), vector<int32_t>{0, 1}},
@@ -155,67 +100,38 @@
string cborPretty;
sp<IWritableIdentityCredential> writableCredential;
- string docType = "org.iso.18013-5.2019.mdl";
- bool testCredential = true;
- ASSERT_TRUE(credentialStore_->createCredential(docType, testCredential, &writableCredential)
- .isOk());
- ASSERT_NE(writableCredential, nullptr);
+ ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
string challenge = "attestationChallenge";
- // TODO: set it to something random and check it's in the cert chain
- vector<uint8_t> attestationApplicationId = {};
- vector<uint8_t> attestationChallenge(challenge.begin(), challenge.end());
- vector<Certificate> attestationCertificates;
- ASSERT_TRUE(writableCredential
- ->getAttestationCertificate(attestationApplicationId, attestationChallenge,
- &attestationCertificates)
- .isOk());
- ASSERT_GE(attestationCertificates.size(), 2);
+ test_utils::AttestationData attData(writableCredential, challenge, {});
+ ASSERT_TRUE(attData.result.isOk())
+ << attData.result.exceptionCode() << "; " << attData.result.exceptionMessage() << endl;
+ ASSERT_EQ(binder::Status::EX_NONE, attData.result.exceptionCode());
+ ASSERT_EQ(IIdentityCredentialStore::STATUS_OK, attData.result.serviceSpecificErrorCode());
+ // TODO: set it to something random and check it's in the cert chain
+ ASSERT_GE(attData.attestationCertificate.size(), 2);
+
+ // This is kinda of a hack but we need to give the size of
+ // ProofOfProvisioning that we'll expect to receive.
+ const int32_t expectedProofOfProvisioningSize = 262861 - 326 + readerCertificate.value().size();
+ // OK to fail, not available in v1 HAL
+ writableCredential->setExpectedProofOfProvisioningSize(expectedProofOfProvisioningSize);
ASSERT_TRUE(
writableCredential->startPersonalization(testProfiles.size(), testEntriesEntryCounts)
.isOk());
- vector<SecureAccessControlProfile> returnedSecureProfiles;
- for (const auto& testProfile : testProfiles) {
- SecureAccessControlProfile profile;
- Certificate cert;
- cert.encodedCertificate = testProfile.readerCertificate;
- ASSERT_TRUE(writableCredential
- ->addAccessControlProfile(testProfile.id, cert,
- testProfile.userAuthenticationRequired,
- testProfile.timeoutMillis,
- 0, // secureUserId
- &profile)
- .isOk());
- ASSERT_EQ(testProfile.id, profile.id);
- ASSERT_EQ(testProfile.readerCertificate, profile.readerCertificate.encodedCertificate);
- ASSERT_EQ(testProfile.userAuthenticationRequired, profile.userAuthenticationRequired);
- ASSERT_EQ(testProfile.timeoutMillis, profile.timeoutMillis);
- ASSERT_EQ(support::kAesGcmTagSize + support::kAesGcmIvSize, profile.mac.size());
- returnedSecureProfiles.push_back(profile);
- }
+ optional<vector<SecureAccessControlProfile>> secureProfiles =
+ test_utils::AddAccessControlProfiles(writableCredential, testProfiles);
+ ASSERT_TRUE(secureProfiles);
// Uses TestEntryData* pointer as key and values are the encrypted blobs. This
// is a little hacky but it works well enough.
- map<const TestEntryData*, vector<vector<uint8_t>>> encryptedBlobs;
+ map<const test_utils::TestEntryData*, vector<vector<uint8_t>>> encryptedBlobs;
for (const auto& entry : testEntries) {
- vector<vector<uint8_t>> chunks =
- support::chunkVector(entry.valueCbor, hwInfo.dataChunkSize);
-
- ASSERT_TRUE(writableCredential
- ->beginAddEntry(entry.profileIds, entry.nameSpace, entry.name,
- entry.valueCbor.size())
- .isOk());
-
- vector<vector<uint8_t>> encryptedChunks;
- for (const auto& chunk : chunks) {
- vector<uint8_t> encryptedChunk;
- ASSERT_TRUE(writableCredential->addEntryValue(chunk, &encryptedChunk).isOk());
- encryptedChunks.push_back(encryptedChunk);
- }
- encryptedBlobs[&entry] = encryptedChunks;
+ ASSERT_TRUE(test_utils::AddEntry(writableCredential, entry, hwInfo.dataChunkSize,
+ encryptedBlobs, true));
}
vector<uint8_t> credentialData;
@@ -276,8 +192,8 @@
"]",
cborPretty);
- optional<vector<uint8_t>> credentialPubKey =
- support::certificateChainGetTopMostKey(attestationCertificates[0].encodedCertificate);
+ optional<vector<uint8_t>> credentialPubKey = support::certificateChainGetTopMostKey(
+ attData.attestationCertificate[0].encodedCertificate);
ASSERT_TRUE(credentialPubKey);
EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfProvisioningSignature,
{}, // Additional data
@@ -347,8 +263,8 @@
.add(cppbor::Semantic(24, itemsRequestBytes))
.encode();
optional<vector<uint8_t>> readerSignature =
- support::coseSignEcDsa(readerKey.value(), {}, // content
- dataToSign, // detached content
+ support::coseSignEcDsa(readerKey, {}, // content
+ dataToSign, // detached content
readerCertificate.value());
ASSERT_TRUE(readerSignature);
@@ -357,8 +273,10 @@
Certificate signingKeyCertificate;
ASSERT_TRUE(credential->generateSigningKeyPair(&signingKeyBlob, &signingKeyCertificate).isOk());
+ vector<RequestNamespace> requestedNamespaces = test_utils::buildRequestNamespaces(testEntries);
+ ASSERT_TRUE(credential->setRequestedNamespaces(requestedNamespaces).isOk());
ASSERT_TRUE(credential
- ->startRetrieval(returnedSecureProfiles, authToken, itemsRequestBytes,
+ ->startRetrieval(secureProfiles.value(), authToken, itemsRequestBytes,
signingKeyBlob, sessionTranscriptBytes,
readerSignature.value(), testEntriesEntryCounts)
.isOk());
@@ -405,6 +323,8 @@
cppbor::Array deviceAuthentication;
deviceAuthentication.add("DeviceAuthentication");
deviceAuthentication.add(sessionTranscript.clone());
+
+ string docType = "org.iso.18013-5.2019.mdl";
deviceAuthentication.add(docType);
deviceAuthentication.add(cppbor::Semantic(24, deviceNameSpacesBytes));
vector<uint8_t> encodedDeviceAuthentication = deviceAuthentication.encode();
diff --git a/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp b/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp
new file mode 100644
index 0000000..724aaa1
--- /dev/null
+++ b/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp
@@ -0,0 +1,695 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "VtsIWritableIdentityCredentialTests"
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <android-base/logging.h>
+#include <android/hardware/identity/IIdentityCredentialStore.h>
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <cppbor.h>
+#include <cppbor_parse.h>
+#include <gtest/gtest.h>
+#include <future>
+#include <map>
+
+#include "VtsIdentityTestUtils.h"
+
+namespace android::hardware::identity {
+
+using std::endl;
+using std::map;
+using std::optional;
+using std::string;
+using std::vector;
+
+using ::android::sp;
+using ::android::String16;
+using ::android::binder::Status;
+
+class IdentityCredentialTests : public testing::TestWithParam<string> {
+ public:
+ virtual void SetUp() override {
+ credentialStore_ = android::waitForDeclaredService<IIdentityCredentialStore>(
+ String16(GetParam().c_str()));
+ ASSERT_NE(credentialStore_, nullptr);
+ }
+
+ sp<IIdentityCredentialStore> credentialStore_;
+};
+
+TEST_P(IdentityCredentialTests, verifyAttestationWithEmptyChallenge) {
+ Status result;
+ sp<IWritableIdentityCredential> writableCredential;
+ ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+ vector<uint8_t> attestationChallenge;
+ vector<Certificate> attestationCertificate;
+ vector<uint8_t> attestationApplicationId = {};
+ result = writableCredential->getAttestationCertificate(
+ attestationApplicationId, attestationChallenge, &attestationCertificate);
+
+ EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+
+ EXPECT_TRUE(test_utils::ValidateAttestationCertificate(attestationCertificate));
+}
+
+TEST_P(IdentityCredentialTests, verifyAttestationSuccessWithChallenge) {
+ Status result;
+ sp<IWritableIdentityCredential> writableCredential;
+ ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+ string challenge = "NotSoRandomChallenge1NotSoRandomChallenge1NotSoRandomChallenge1";
+ vector<uint8_t> attestationChallenge(challenge.begin(), challenge.end());
+ vector<Certificate> attestationCertificate;
+ vector<uint8_t> attestationApplicationId = {};
+
+ result = writableCredential->getAttestationCertificate(
+ attestationApplicationId, attestationChallenge, &attestationCertificate);
+
+ EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+
+ EXPECT_TRUE(test_utils::ValidateAttestationCertificate(attestationCertificate));
+}
+
+TEST_P(IdentityCredentialTests, verifyAttestationDoubleCallFails) {
+ Status result;
+ sp<IWritableIdentityCredential> writableCredential;
+ ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+ string challenge = "NotSoRandomChallenge1";
+ test_utils::AttestationData attData(writableCredential, challenge, {});
+ ASSERT_TRUE(test_utils::ValidateAttestationCertificate(attData.attestationCertificate));
+
+ string challenge2 = "NotSoRandomChallenge2";
+ test_utils::AttestationData attData2(writableCredential, challenge2, {});
+ EXPECT_FALSE(attData2.result.isOk()) << attData2.result.exceptionCode() << "; "
+ << attData2.result.exceptionMessage() << endl;
+ EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, attData2.result.exceptionCode());
+ EXPECT_EQ(IIdentityCredentialStore::STATUS_FAILED, attData2.result.serviceSpecificErrorCode());
+}
+
+TEST_P(IdentityCredentialTests, verifyStartPersonalization) {
+ Status result;
+ sp<IWritableIdentityCredential> writableCredential;
+ ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+ // First call should go through
+ const vector<int32_t> entryCounts = {2, 4};
+ result = writableCredential->startPersonalization(5, entryCounts);
+ ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+
+ // Call personalization again to check if repeat call is allowed.
+ result = writableCredential->startPersonalization(7, entryCounts);
+
+ // Second call to startPersonalization should have failed.
+ EXPECT_FALSE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+ EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode());
+ EXPECT_EQ(IIdentityCredentialStore::STATUS_FAILED, result.serviceSpecificErrorCode());
+}
+
+TEST_P(IdentityCredentialTests, verifyStartPersonalizationMin) {
+ Status result;
+ sp<IWritableIdentityCredential> writableCredential;
+ ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+ // Verify minimal number of profile count and entry count
+ const vector<int32_t> entryCounts = {1, 1};
+ writableCredential->startPersonalization(1, entryCounts);
+ EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+}
+
+TEST_P(IdentityCredentialTests, verifyStartPersonalizationZero) {
+ Status result;
+ sp<IWritableIdentityCredential> writableCredential;
+ ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+ const vector<int32_t> entryCounts = {0};
+ writableCredential->startPersonalization(0, entryCounts);
+ EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+}
+
+TEST_P(IdentityCredentialTests, verifyStartPersonalizationOne) {
+ Status result;
+ sp<IWritableIdentityCredential> writableCredential;
+ ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+ // Verify minimal number of profile count and entry count
+ const vector<int32_t> entryCounts = {1};
+ writableCredential->startPersonalization(1, entryCounts);
+ EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+}
+
+TEST_P(IdentityCredentialTests, verifyStartPersonalizationLarge) {
+ Status result;
+ sp<IWritableIdentityCredential> writableCredential;
+ ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+ // Verify set a large number of profile count and entry count is ok
+ const vector<int32_t> entryCounts = {3000};
+ writableCredential->startPersonalization(3500, entryCounts);
+ EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+}
+
+TEST_P(IdentityCredentialTests, verifyProfileNumberMismatchShouldFail) {
+ Status result;
+ sp<IWritableIdentityCredential> writableCredential;
+ ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+ // Enter mismatched entry and profile numbers
+ const vector<int32_t> entryCounts = {5, 6};
+ writableCredential->startPersonalization(5, entryCounts);
+ ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+
+ optional<vector<uint8_t>> readerCertificate = test_utils::GenerateReaderCertificate("12345");
+ ASSERT_TRUE(readerCertificate);
+
+ const vector<test_utils::TestProfile> testProfiles = {// Profile 0 (reader authentication)
+ {1, readerCertificate.value(), false, 0},
+ {2, readerCertificate.value(), true, 1},
+ // Profile 4 (no authentication)
+ {4, {}, false, 0}};
+
+ optional<vector<SecureAccessControlProfile>> secureProfiles =
+ test_utils::AddAccessControlProfiles(writableCredential, testProfiles);
+ ASSERT_TRUE(secureProfiles);
+
+ vector<uint8_t> credentialData;
+ vector<uint8_t> proofOfProvisioningSignature;
+ result =
+ writableCredential->finishAddingEntries(&credentialData, &proofOfProvisioningSignature);
+
+ // finishAddingEntries should fail because the number of addAccessControlProfile mismatched with
+ // startPersonalization, and begintest_utils::AddEntry was not called.
+ EXPECT_FALSE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+ EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode());
+ EXPECT_EQ(IIdentityCredentialStore::STATUS_INVALID_DATA, result.serviceSpecificErrorCode());
+}
+
+TEST_P(IdentityCredentialTests, verifyDuplicateProfileId) {
+ Status result;
+ sp<IWritableIdentityCredential> writableCredential;
+ ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+ const vector<int32_t> entryCounts = {3, 6};
+ writableCredential->startPersonalization(3, entryCounts);
+ ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+
+ const vector<test_utils::TestProfile> testProfiles = {// first profile should go though
+ {1, {}, true, 2},
+ // same id, different
+ // authentication requirement
+ {1, {}, true, 1},
+ // same id, different certificate
+ {1, {}, false, 0}};
+
+ bool expectOk = true;
+ for (const auto& testProfile : testProfiles) {
+ SecureAccessControlProfile profile;
+ Certificate cert;
+ cert.encodedCertificate = testProfile.readerCertificate;
+ result = writableCredential->addAccessControlProfile(
+ testProfile.id, cert, testProfile.userAuthenticationRequired,
+ testProfile.timeoutMillis, 0, &profile);
+
+ if (expectOk) {
+ expectOk = false;
+ // for profile should be allowed though as there are no duplications
+ // yet.
+ ASSERT_TRUE(result.isOk())
+ << result.exceptionCode() << "; " << result.exceptionMessage()
+ << "test profile id = " << testProfile.id << endl;
+
+ ASSERT_EQ(testProfile.id, profile.id);
+ ASSERT_EQ(testProfile.readerCertificate, profile.readerCertificate.encodedCertificate);
+ ASSERT_EQ(testProfile.userAuthenticationRequired, profile.userAuthenticationRequired);
+ ASSERT_EQ(testProfile.timeoutMillis, profile.timeoutMillis);
+ ASSERT_EQ(support::kAesGcmTagSize + support::kAesGcmIvSize, profile.mac.size());
+ } else {
+ // should not allow duplicate id profiles.
+ ASSERT_FALSE(result.isOk())
+ << result.exceptionCode() << "; " << result.exceptionMessage()
+ << ". Test profile id = " << testProfile.id
+ << ", timeout=" << testProfile.timeoutMillis << endl;
+ ASSERT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode());
+ ASSERT_EQ(IIdentityCredentialStore::STATUS_INVALID_DATA,
+ result.serviceSpecificErrorCode());
+ }
+ }
+}
+
+TEST_P(IdentityCredentialTests, verifyOneProfileAndEntryPass) {
+ Status result;
+
+ HardwareInformation hwInfo;
+ ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk());
+
+ sp<IWritableIdentityCredential> writableCredential;
+ ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+ string challenge = "NotSoRandomChallenge1";
+ test_utils::AttestationData attData(writableCredential, challenge, {});
+ EXPECT_TRUE(attData.result.isOk())
+ << attData.result.exceptionCode() << "; " << attData.result.exceptionMessage() << endl;
+
+ optional<vector<uint8_t>> readerCertificate1 = test_utils::GenerateReaderCertificate("123456");
+ ASSERT_TRUE(readerCertificate1);
+
+ const vector<int32_t> entryCounts = {1u};
+ size_t expectedPoPSize = 186 + readerCertificate1.value().size();
+ // OK to fail, not available in v1 HAL
+ writableCredential->setExpectedProofOfProvisioningSize(expectedPoPSize);
+ result = writableCredential->startPersonalization(1, entryCounts);
+ ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+
+ const vector<test_utils::TestProfile> testProfiles = {{1, readerCertificate1.value(), true, 1}};
+
+ optional<vector<SecureAccessControlProfile>> secureProfiles =
+ test_utils::AddAccessControlProfiles(writableCredential, testProfiles);
+ ASSERT_TRUE(secureProfiles);
+
+ const vector<test_utils::TestEntryData> testEntries1 = {
+ {"Name Space", "Last name", string("Turing"), vector<int32_t>{0, 1}},
+ };
+
+ map<const test_utils::TestEntryData*, vector<vector<uint8_t>>> encryptedBlobs;
+ for (const auto& entry : testEntries1) {
+ ASSERT_TRUE(test_utils::AddEntry(writableCredential, entry, hwInfo.dataChunkSize,
+ encryptedBlobs, true));
+ }
+
+ vector<uint8_t> credentialData;
+ vector<uint8_t> proofOfProvisioningSignature;
+ result =
+ writableCredential->finishAddingEntries(&credentialData, &proofOfProvisioningSignature);
+
+ EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+
+ optional<vector<uint8_t>> proofOfProvisioning =
+ support::coseSignGetPayload(proofOfProvisioningSignature);
+ ASSERT_TRUE(proofOfProvisioning);
+ string cborPretty =
+ support::cborPrettyPrint(proofOfProvisioning.value(), 32, {"readerCertificate"});
+ EXPECT_EQ(
+ "[\n"
+ " 'ProofOfProvisioning',\n"
+ " 'org.iso.18013-5.2019.mdl',\n"
+ " [\n"
+ " {\n"
+ " 'id' : 1,\n"
+ " 'readerCertificate' : <not printed>,\n"
+ " 'userAuthenticationRequired' : true,\n"
+ " 'timeoutMillis' : 1,\n"
+ " },\n"
+ " ],\n"
+ " {\n"
+ " 'Name Space' : [\n"
+ " {\n"
+ " 'name' : 'Last name',\n"
+ " 'value' : 'Turing',\n"
+ " 'accessControlProfiles' : [0, 1, ],\n"
+ " },\n"
+ " ],\n"
+ " },\n"
+ " true,\n"
+ "]",
+ cborPretty);
+
+ optional<vector<uint8_t>> credentialPubKey = support::certificateChainGetTopMostKey(
+ attData.attestationCertificate[0].encodedCertificate);
+ ASSERT_TRUE(credentialPubKey);
+ EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfProvisioningSignature,
+ {}, // Additional data
+ credentialPubKey.value()));
+}
+
+TEST_P(IdentityCredentialTests, verifyManyProfilesAndEntriesPass) {
+ Status result;
+
+ HardwareInformation hwInfo;
+ ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk());
+
+ sp<IWritableIdentityCredential> writableCredential;
+ ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+ string challenge = "NotSoRandomChallenge";
+ test_utils::AttestationData attData(writableCredential, challenge, {});
+ EXPECT_TRUE(attData.result.isOk())
+ << attData.result.exceptionCode() << "; " << attData.result.exceptionMessage() << endl;
+
+ optional<vector<uint8_t>> readerCertificate1 = test_utils::GenerateReaderCertificate("123456");
+ ASSERT_TRUE(readerCertificate1);
+
+ optional<vector<uint8_t>> readerCertificate2 = test_utils::GenerateReaderCertificate("1256");
+ ASSERT_TRUE(readerCertificate2);
+
+ const vector<test_utils::TestProfile> testProfiles = {
+ {1, readerCertificate1.value(), true, 1},
+ {2, readerCertificate2.value(), true, 2},
+ };
+ const vector<int32_t> entryCounts = {1u, 3u, 1u, 1u, 2u};
+ size_t expectedPoPSize =
+ 525021 + readerCertificate1.value().size() + readerCertificate2.value().size();
+ // OK to fail, not available in v1 HAL
+ writableCredential->setExpectedProofOfProvisioningSize(expectedPoPSize);
+ result = writableCredential->startPersonalization(testProfiles.size(), entryCounts);
+ ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+
+ optional<vector<SecureAccessControlProfile>> secureProfiles =
+ test_utils::AddAccessControlProfiles(writableCredential, testProfiles);
+ ASSERT_TRUE(secureProfiles);
+
+ vector<uint8_t> portraitImage1;
+ test_utils::SetImageData(portraitImage1);
+
+ vector<uint8_t> portraitImage2;
+ test_utils::SetImageData(portraitImage2);
+
+ const vector<test_utils::TestEntryData> testEntries1 = {
+ {"Name Space 1", "Last name", string("Turing"), vector<int32_t>{1, 2}},
+ {"Name Space2", "Home address", string("Maida Vale, London, England"),
+ vector<int32_t>{1}},
+ {"Name Space2", "Work address", string("Maida Vale2, London, England"),
+ vector<int32_t>{2}},
+ {"Name Space2", "Trailer address", string("Maida, London, England"),
+ vector<int32_t>{1}},
+ {"Image", "Portrait image", portraitImage1, vector<int32_t>{1}},
+ {"Image2", "Work image", portraitImage2, vector<int32_t>{1, 2}},
+ {"Name Space3", "xyzw", string("random stuff"), vector<int32_t>{1, 2}},
+ {"Name Space3", "Something", string("Some string"), vector<int32_t>{2}},
+ };
+
+ map<const test_utils::TestEntryData*, vector<vector<uint8_t>>> encryptedBlobs;
+ for (const auto& entry : testEntries1) {
+ EXPECT_TRUE(test_utils::AddEntry(writableCredential, entry, hwInfo.dataChunkSize,
+ encryptedBlobs, true));
+ }
+
+ vector<uint8_t> credentialData;
+ vector<uint8_t> proofOfProvisioningSignature;
+ result =
+ writableCredential->finishAddingEntries(&credentialData, &proofOfProvisioningSignature);
+
+ EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+
+ optional<vector<uint8_t>> proofOfProvisioning =
+ support::coseSignGetPayload(proofOfProvisioningSignature);
+ ASSERT_TRUE(proofOfProvisioning);
+ string cborPretty = support::cborPrettyPrint(proofOfProvisioning.value(),
+ 32, //
+ {"readerCertificate"});
+ EXPECT_EQ(
+ "[\n"
+ " 'ProofOfProvisioning',\n"
+ " 'org.iso.18013-5.2019.mdl',\n"
+ " [\n"
+ " {\n"
+ " 'id' : 1,\n"
+ " 'readerCertificate' : <not printed>,\n"
+ " 'userAuthenticationRequired' : true,\n"
+ " 'timeoutMillis' : 1,\n"
+ " },\n"
+ " {\n"
+ " 'id' : 2,\n"
+ " 'readerCertificate' : <not printed>,\n"
+ " 'userAuthenticationRequired' : true,\n"
+ " 'timeoutMillis' : 2,\n"
+ " },\n"
+ " ],\n"
+ " {\n"
+ " 'Name Space 1' : [\n"
+ " {\n"
+ " 'name' : 'Last name',\n"
+ " 'value' : 'Turing',\n"
+ " 'accessControlProfiles' : [1, 2, ],\n"
+ " },\n"
+ " ],\n"
+ " 'Name Space2' : [\n"
+ " {\n"
+ " 'name' : 'Home address',\n"
+ " 'value' : 'Maida Vale, London, England',\n"
+ " 'accessControlProfiles' : [1, ],\n"
+ " },\n"
+ " {\n"
+ " 'name' : 'Work address',\n"
+ " 'value' : 'Maida Vale2, London, England',\n"
+ " 'accessControlProfiles' : [2, ],\n"
+ " },\n"
+ " {\n"
+ " 'name' : 'Trailer address',\n"
+ " 'value' : 'Maida, London, England',\n"
+ " 'accessControlProfiles' : [1, ],\n"
+ " },\n"
+ " ],\n"
+ " 'Image' : [\n"
+ " {\n"
+ " 'name' : 'Portrait image',\n"
+ " 'value' : <bstr size=262134 sha1=941e372f654d86c32d88fae9e41b706afbfd02bb>,\n"
+ " 'accessControlProfiles' : [1, ],\n"
+ " },\n"
+ " ],\n"
+ " 'Image2' : [\n"
+ " {\n"
+ " 'name' : 'Work image',\n"
+ " 'value' : <bstr size=262134 sha1=941e372f654d86c32d88fae9e41b706afbfd02bb>,\n"
+ " 'accessControlProfiles' : [1, 2, ],\n"
+ " },\n"
+ " ],\n"
+ " 'Name Space3' : [\n"
+ " {\n"
+ " 'name' : 'xyzw',\n"
+ " 'value' : 'random stuff',\n"
+ " 'accessControlProfiles' : [1, 2, ],\n"
+ " },\n"
+ " {\n"
+ " 'name' : 'Something',\n"
+ " 'value' : 'Some string',\n"
+ " 'accessControlProfiles' : [2, ],\n"
+ " },\n"
+ " ],\n"
+ " },\n"
+ " true,\n"
+ "]",
+ cborPretty);
+
+ optional<vector<uint8_t>> credentialPubKey = support::certificateChainGetTopMostKey(
+ attData.attestationCertificate[0].encodedCertificate);
+ ASSERT_TRUE(credentialPubKey);
+ EXPECT_TRUE(support::coseCheckEcDsaSignature(proofOfProvisioningSignature,
+ {}, // Additional data
+ credentialPubKey.value()));
+}
+
+TEST_P(IdentityCredentialTests, verifyEmptyNameSpaceMixedWithNonEmptyWorks) {
+ Status result;
+
+ HardwareInformation hwInfo;
+ ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk());
+
+ sp<IWritableIdentityCredential> writableCredential;
+ ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+ string challenge = "NotSoRandomChallenge";
+ test_utils::AttestationData attData(writableCredential, challenge, {});
+ ASSERT_TRUE(attData.result.isOk())
+ << attData.result.exceptionCode() << "; " << attData.result.exceptionMessage() << endl;
+
+ optional<vector<uint8_t>> readerCertificate1 = test_utils::GenerateReaderCertificate("123456");
+ ASSERT_TRUE(readerCertificate1);
+
+ optional<vector<uint8_t>> readerCertificate2 =
+ test_utils::GenerateReaderCertificate("123456987987987987987987");
+ ASSERT_TRUE(readerCertificate2);
+
+ const vector<int32_t> entryCounts = {2u, 2u};
+ size_t expectedPoPSize =
+ 377 + readerCertificate1.value().size() + readerCertificate2.value().size();
+ ;
+ // OK to fail, not available in v1 HAL
+ writableCredential->setExpectedProofOfProvisioningSize(expectedPoPSize);
+ writableCredential->startPersonalization(3, entryCounts);
+ ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+
+ const vector<test_utils::TestProfile> testProfiles = {{0, readerCertificate1.value(), false, 0},
+ {1, readerCertificate2.value(), true, 1},
+ {2, {}, false, 0}};
+
+ optional<vector<SecureAccessControlProfile>> secureProfiles =
+ test_utils::AddAccessControlProfiles(writableCredential, testProfiles);
+ ASSERT_TRUE(secureProfiles);
+
+ const vector<test_utils::TestEntryData> testEntries1 = {
+ // test empty name space
+ {"", "t name", string("Turing"), vector<int32_t>{2}},
+ {"", "Birth", string("19120623"), vector<int32_t>{2}},
+ {"Name Space", "Last name", string("Turing"), vector<int32_t>{0, 1}},
+ {"Name Space", "Birth date", string("19120623"), vector<int32_t>{0, 1}},
+ };
+
+ map<const test_utils::TestEntryData*, vector<vector<uint8_t>>> encryptedBlobs;
+ for (const auto& entry : testEntries1) {
+ EXPECT_TRUE(test_utils::AddEntry(writableCredential, entry, hwInfo.dataChunkSize,
+ encryptedBlobs, true));
+ }
+
+ vector<uint8_t> credentialData;
+ vector<uint8_t> proofOfProvisioningSignature;
+ result =
+ writableCredential->finishAddingEntries(&credentialData, &proofOfProvisioningSignature);
+
+ EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+}
+
+TEST_P(IdentityCredentialTests, verifyInterleavingEntryNameSpaceOrderingFails) {
+ Status result;
+
+ HardwareInformation hwInfo;
+ ASSERT_TRUE(credentialStore_->getHardwareInformation(&hwInfo).isOk());
+
+ sp<IWritableIdentityCredential> writableCredential;
+ ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+ string challenge = "NotSoRandomChallenge";
+ test_utils::AttestationData attData(writableCredential, challenge, {});
+ ASSERT_TRUE(attData.result.isOk())
+ << attData.result.exceptionCode() << "; " << attData.result.exceptionMessage() << endl;
+
+ // Enter mismatched entry and profile numbers.
+ // Technically the 2nd name space of "Name Space" occurs intermittently, 2
+ // before "Image" and 2 after image, which is not correct. All of same name
+ // space should occur together. Let's see if this fails.
+ const vector<int32_t> entryCounts = {2u, 1u, 2u};
+ writableCredential->startPersonalization(3, entryCounts);
+ ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+
+ optional<vector<uint8_t>> readerCertificate1 = test_utils::GenerateReaderCertificate("123456");
+ ASSERT_TRUE(readerCertificate1);
+
+ optional<vector<uint8_t>> readerCertificate2 =
+ test_utils::GenerateReaderCertificate("123456987987987987987987");
+ ASSERT_TRUE(readerCertificate2);
+
+ const vector<test_utils::TestProfile> testProfiles = {{0, readerCertificate1.value(), false, 0},
+ {1, readerCertificate2.value(), true, 1},
+ {2, {}, false, 0}};
+
+ optional<vector<SecureAccessControlProfile>> secureProfiles =
+ test_utils::AddAccessControlProfiles(writableCredential, testProfiles);
+ ASSERT_TRUE(secureProfiles);
+
+ const vector<test_utils::TestEntryData> testEntries1 = {
+ // test empty name space
+ {"Name Space", "Last name", string("Turing"), vector<int32_t>{0, 1}},
+ {"Name Space", "Birth date", string("19120623"), vector<int32_t>{0, 1}},
+ };
+
+ map<const test_utils::TestEntryData*, vector<vector<uint8_t>>> encryptedBlobs;
+ for (const auto& entry : testEntries1) {
+ EXPECT_TRUE(test_utils::AddEntry(writableCredential, entry, hwInfo.dataChunkSize,
+ encryptedBlobs, true));
+ }
+ const test_utils::TestEntryData testEntry2 = {"Image", "Portrait image", string("asdfs"),
+ vector<int32_t>{0, 1}};
+
+ EXPECT_TRUE(test_utils::AddEntry(writableCredential, testEntry2, hwInfo.dataChunkSize,
+ encryptedBlobs, true));
+
+ // We expect this to fail because the namespace is out of order, all "Name Space"
+ // should have been called together
+ const vector<test_utils::TestEntryData> testEntries3 = {
+ {"Name Space", "First name", string("Alan"), vector<int32_t>{0, 1}},
+ {"Name Space", "Home address", string("Maida Vale, London, England"),
+ vector<int32_t>{0}},
+ };
+
+ for (const auto& entry : testEntries3) {
+ EXPECT_FALSE(test_utils::AddEntry(writableCredential, entry, hwInfo.dataChunkSize,
+ encryptedBlobs, false));
+ }
+
+ vector<uint8_t> credentialData;
+ vector<uint8_t> proofOfProvisioningSignature;
+ result =
+ writableCredential->finishAddingEntries(&credentialData, &proofOfProvisioningSignature);
+
+ // should fail because test_utils::AddEntry should have failed earlier.
+ EXPECT_FALSE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+ EXPECT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode());
+ EXPECT_EQ(IIdentityCredentialStore::STATUS_INVALID_DATA, result.serviceSpecificErrorCode());
+}
+
+TEST_P(IdentityCredentialTests, verifyAccessControlProfileIdOutOfRange) {
+ sp<IWritableIdentityCredential> writableCredential;
+ ASSERT_TRUE(test_utils::SetupWritableCredential(writableCredential, credentialStore_));
+
+ const vector<int32_t> entryCounts = {1};
+ Status result = writableCredential->startPersonalization(1, entryCounts);
+ ASSERT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << endl;
+
+ SecureAccessControlProfile profile;
+
+ // This should fail because the id is >= 32
+ result = writableCredential->addAccessControlProfile(32, // id
+ {}, // readerCertificate
+ false, // userAuthenticationRequired
+ 0, // timeoutMillis
+ 42, // secureUserId
+ &profile);
+ ASSERT_FALSE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage();
+ ASSERT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode());
+ ASSERT_EQ(IIdentityCredentialStore::STATUS_INVALID_DATA, result.serviceSpecificErrorCode());
+
+ // This should fail because the id is < 0
+ result = writableCredential->addAccessControlProfile(-1, // id
+ {}, // readerCertificate
+ false, // userAuthenticationRequired
+ 0, // timeoutMillis
+ 42, // secureUserId
+ &profile);
+ ASSERT_FALSE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage();
+ ASSERT_EQ(binder::Status::EX_SERVICE_SPECIFIC, result.exceptionCode());
+ ASSERT_EQ(IIdentityCredentialStore::STATUS_INVALID_DATA, result.serviceSpecificErrorCode());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ Identity, IdentityCredentialTests,
+ testing::ValuesIn(android::getAidlHalInstanceNames(IIdentityCredentialStore::descriptor)),
+ android::PrintInstanceNameToString);
+
+} // namespace android::hardware::identity
diff --git a/identity/aidl/vts/VtsIdentityTestUtils.cpp b/identity/aidl/vts/VtsIdentityTestUtils.cpp
new file mode 100644
index 0000000..48e47dc
--- /dev/null
+++ b/identity/aidl/vts/VtsIdentityTestUtils.cpp
@@ -0,0 +1,203 @@
+/*
+ * Copyright 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.
+ */
+
+#include "VtsIdentityTestUtils.h"
+
+#include <aidl/Gtest.h>
+#include <map>
+
+namespace android::hardware::identity::test_utils {
+
+using std::endl;
+using std::map;
+using std::optional;
+using std::string;
+using std::vector;
+
+using ::android::sp;
+using ::android::String16;
+using ::android::binder::Status;
+
+bool SetupWritableCredential(sp<IWritableIdentityCredential>& writableCredential,
+ sp<IIdentityCredentialStore>& credentialStore) {
+ if (credentialStore == nullptr) {
+ return false;
+ }
+
+ string docType = "org.iso.18013-5.2019.mdl";
+ bool testCredential = true;
+ Status result = credentialStore->createCredential(docType, testCredential, &writableCredential);
+
+ if (result.isOk() && writableCredential != nullptr) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+optional<vector<uint8_t>> GenerateReaderCertificate(string serialDecimal) {
+ vector<uint8_t> privKey;
+ return GenerateReaderCertificate(serialDecimal, privKey);
+}
+
+optional<vector<uint8_t>> GenerateReaderCertificate(string serialDecimal,
+ vector<uint8_t>& readerPrivateKey) {
+ optional<vector<uint8_t>> readerKeyPKCS8 = support::createEcKeyPair();
+ if (!readerKeyPKCS8) {
+ return {};
+ }
+
+ optional<vector<uint8_t>> readerPublicKey =
+ support::ecKeyPairGetPublicKey(readerKeyPKCS8.value());
+ optional<vector<uint8_t>> readerKey = support::ecKeyPairGetPrivateKey(readerKeyPKCS8.value());
+ if (!readerPublicKey || !readerKey) {
+ return {};
+ }
+
+ readerPrivateKey = readerKey.value();
+
+ string issuer = "Android Open Source Project";
+ string subject = "Android IdentityCredential VTS Test";
+ time_t validityNotBefore = time(nullptr);
+ time_t validityNotAfter = validityNotBefore + 365 * 24 * 3600;
+
+ return support::ecPublicKeyGenerateCertificate(readerPublicKey.value(), readerKey.value(),
+ serialDecimal, issuer, subject,
+ validityNotBefore, validityNotAfter);
+}
+
+optional<vector<SecureAccessControlProfile>> AddAccessControlProfiles(
+ sp<IWritableIdentityCredential>& writableCredential,
+ const vector<TestProfile>& testProfiles) {
+ Status result;
+
+ vector<SecureAccessControlProfile> secureProfiles;
+
+ for (const auto& testProfile : testProfiles) {
+ SecureAccessControlProfile profile;
+ Certificate cert;
+ cert.encodedCertificate = testProfile.readerCertificate;
+ result = writableCredential->addAccessControlProfile(
+ testProfile.id, cert, testProfile.userAuthenticationRequired,
+ testProfile.timeoutMillis, 0, &profile);
+
+ // Don't use assert so all errors can be outputed. Then return
+ // instead of exit even on errors so caller can decide.
+ EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
+ << "test profile id = " << testProfile.id << endl;
+ EXPECT_EQ(testProfile.id, profile.id);
+ EXPECT_EQ(testProfile.readerCertificate, profile.readerCertificate.encodedCertificate);
+ EXPECT_EQ(testProfile.userAuthenticationRequired, profile.userAuthenticationRequired);
+ EXPECT_EQ(testProfile.timeoutMillis, profile.timeoutMillis);
+ EXPECT_EQ(support::kAesGcmTagSize + support::kAesGcmIvSize, profile.mac.size());
+
+ if (!result.isOk() || testProfile.id != profile.id ||
+ testProfile.readerCertificate != profile.readerCertificate.encodedCertificate ||
+ testProfile.userAuthenticationRequired != profile.userAuthenticationRequired ||
+ testProfile.timeoutMillis != profile.timeoutMillis ||
+ support::kAesGcmTagSize + support::kAesGcmIvSize != profile.mac.size()) {
+ return {};
+ }
+
+ secureProfiles.push_back(profile);
+ }
+
+ return secureProfiles;
+}
+
+// Most test expects this function to pass. So we will print out additional
+// value if failed so more debug data can be provided.
+bool AddEntry(sp<IWritableIdentityCredential>& writableCredential, const TestEntryData& entry,
+ int dataChunkSize, map<const TestEntryData*, vector<vector<uint8_t>>>& encryptedBlobs,
+ bool expectSuccess) {
+ Status result;
+ vector<vector<uint8_t>> chunks = support::chunkVector(entry.valueCbor, dataChunkSize);
+
+ result = writableCredential->beginAddEntry(entry.profileIds, entry.nameSpace, entry.name,
+ entry.valueCbor.size());
+
+ if (expectSuccess) {
+ EXPECT_TRUE(result.isOk())
+ << result.exceptionCode() << "; " << result.exceptionMessage() << endl
+ << "entry name = " << entry.name << ", name space=" << entry.nameSpace << endl;
+ }
+
+ if (!result.isOk()) {
+ return false;
+ }
+
+ vector<vector<uint8_t>> encryptedChunks;
+ for (const auto& chunk : chunks) {
+ vector<uint8_t> encryptedContent;
+ result = writableCredential->addEntryValue(chunk, &encryptedContent);
+ if (expectSuccess) {
+ EXPECT_TRUE(result.isOk())
+ << result.exceptionCode() << "; " << result.exceptionMessage() << endl
+ << "entry name = " << entry.name << ", name space = " << entry.nameSpace
+ << endl;
+
+ EXPECT_GT(encryptedContent.size(), 0u) << "entry name = " << entry.name
+ << ", name space = " << entry.nameSpace << endl;
+ }
+
+ if (!result.isOk() || encryptedContent.size() <= 0u) {
+ return false;
+ }
+
+ encryptedChunks.push_back(encryptedContent);
+ }
+
+ encryptedBlobs[&entry] = encryptedChunks;
+ return true;
+}
+
+bool ValidateAttestationCertificate(vector<Certificate>& inputCertificates) {
+ return (inputCertificates.size() >= 2);
+ // TODO: add parsing of the certificate and make sure it is genuine.
+}
+
+void SetImageData(vector<uint8_t>& image) {
+ image.resize(256 * 1024 - 10);
+ for (size_t n = 0; n < image.size(); n++) {
+ image[n] = (uint8_t)n;
+ }
+}
+
+vector<RequestNamespace> buildRequestNamespaces(const vector<TestEntryData> entries) {
+ vector<RequestNamespace> ret;
+ RequestNamespace curNs;
+ for (const TestEntryData& testEntry : entries) {
+ if (testEntry.nameSpace != curNs.namespaceName) {
+ if (curNs.namespaceName.size() > 0) {
+ ret.push_back(curNs);
+ }
+ curNs.namespaceName = testEntry.nameSpace;
+ curNs.items.clear();
+ }
+
+ RequestDataItem item;
+ item.name = testEntry.name;
+ item.size = testEntry.valueCbor.size();
+ item.accessControlProfileIds = testEntry.profileIds;
+ curNs.items.push_back(item);
+ }
+ if (curNs.namespaceName.size() > 0) {
+ ret.push_back(curNs);
+ }
+ return ret;
+}
+
+} // namespace android::hardware::identity::test_utils
diff --git a/identity/aidl/vts/VtsIdentityTestUtils.h b/identity/aidl/vts/VtsIdentityTestUtils.h
new file mode 100644
index 0000000..9e1f352
--- /dev/null
+++ b/identity/aidl/vts/VtsIdentityTestUtils.h
@@ -0,0 +1,120 @@
+/*
+ * Copyright 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.
+ */
+
+#ifndef VTS_IDENTITY_TEST_UTILS_H
+#define VTS_IDENTITY_TEST_UTILS_H
+
+#include <android/hardware/identity/IIdentityCredentialStore.h>
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+#include <cppbor.h>
+#include <cppbor_parse.h>
+
+namespace android::hardware::identity::test_utils {
+
+using ::std::map;
+using ::std::optional;
+using ::std::string;
+using ::std::vector;
+
+using ::android::sp;
+using ::android::binder::Status;
+
+struct AttestationData {
+ AttestationData(sp<IWritableIdentityCredential>& writableCredential, string challenge,
+ vector<uint8_t> applicationId)
+ : attestationApplicationId(applicationId) {
+ // ASSERT_NE(writableCredential, nullptr);
+
+ if (!challenge.empty()) {
+ attestationChallenge.assign(challenge.begin(), challenge.end());
+ }
+
+ result = writableCredential->getAttestationCertificate(
+ attestationApplicationId, attestationChallenge, &attestationCertificate);
+ }
+
+ AttestationData() {}
+
+ vector<uint8_t> attestationChallenge;
+ vector<uint8_t> attestationApplicationId;
+ vector<Certificate> attestationCertificate;
+ Status result;
+};
+
+struct TestEntryData {
+ TestEntryData(string nameSpace, string name, vector<int32_t> profileIds)
+ : nameSpace(nameSpace), name(name), profileIds(profileIds) {}
+
+ TestEntryData(string nameSpace, string name, const string& value, vector<int32_t> profileIds)
+ : TestEntryData(nameSpace, name, profileIds) {
+ valueCbor = cppbor::Tstr(((const char*)value.data())).encode();
+ }
+ TestEntryData(string nameSpace, string name, const vector<uint8_t>& value,
+ vector<int32_t> profileIds)
+ : TestEntryData(nameSpace, name, profileIds) {
+ valueCbor = cppbor::Bstr(value).encode();
+ }
+ TestEntryData(string nameSpace, string name, bool value, vector<int32_t> profileIds)
+ : TestEntryData(nameSpace, name, profileIds) {
+ valueCbor = cppbor::Bool(value).encode();
+ }
+ TestEntryData(string nameSpace, string name, int64_t value, vector<int32_t> profileIds)
+ : TestEntryData(nameSpace, name, profileIds) {
+ if (value >= 0) {
+ valueCbor = cppbor::Uint(value).encode();
+ } else {
+ valueCbor = cppbor::Nint(-value).encode();
+ }
+ }
+
+ string nameSpace;
+ string name;
+ vector<uint8_t> valueCbor;
+ vector<int32_t> profileIds;
+};
+
+struct TestProfile {
+ uint16_t id;
+ vector<uint8_t> readerCertificate;
+ bool userAuthenticationRequired;
+ uint64_t timeoutMillis;
+};
+
+bool SetupWritableCredential(sp<IWritableIdentityCredential>& writableCredential,
+ sp<IIdentityCredentialStore>& credentialStore);
+
+optional<vector<uint8_t>> GenerateReaderCertificate(string serialDecimal);
+
+optional<vector<uint8_t>> GenerateReaderCertificate(string serialDecimal,
+ vector<uint8_t>& readerPrivateKey);
+
+optional<vector<SecureAccessControlProfile>> AddAccessControlProfiles(
+ sp<IWritableIdentityCredential>& writableCredential,
+ const vector<TestProfile>& testProfiles);
+
+bool AddEntry(sp<IWritableIdentityCredential>& writableCredential, const TestEntryData& entry,
+ int dataChunkSize, map<const TestEntryData*, vector<vector<uint8_t>>>& encryptedBlobs,
+ bool expectSuccess);
+
+bool ValidateAttestationCertificate(vector<Certificate>& inputCertificates);
+
+void SetImageData(vector<uint8_t>& image);
+
+vector<RequestNamespace> buildRequestNamespaces(const vector<TestEntryData> entries);
+
+} // namespace android::hardware::identity::test_utils
+
+#endif // VTS_IDENTITY_TEST_UTILS_H
diff --git a/identity/support/src/IdentityCredentialSupport.cpp b/identity/support/src/IdentityCredentialSupport.cpp
index bf6a5c3..dc49ddc 100644
--- a/identity/support/src/IdentityCredentialSupport.cpp
+++ b/identity/support/src/IdentityCredentialSupport.cpp
@@ -958,12 +958,17 @@
optional<vector<uint8_t>> createEcKeyPair() {
auto ec_key = EC_KEY_Ptr(EC_KEY_new());
auto pkey = EVP_PKEY_Ptr(EVP_PKEY_new());
- auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
if (ec_key.get() == nullptr || pkey.get() == nullptr) {
LOG(ERROR) << "Memory allocation failed";
return {};
}
+ auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
+ if (group.get() == nullptr) {
+ LOG(ERROR) << "Error creating EC group by curve name";
+ return {};
+ }
+
if (EC_KEY_set_group(ec_key.get(), group.get()) != 1 ||
EC_KEY_generate_key(ec_key.get()) != 1 || EC_KEY_check_key(ec_key.get()) < 0) {
LOG(ERROR) << "Error generating key";
diff --git a/keymaster/4.0/support/include/keymasterV4_0/keymaster_tags.h b/keymaster/4.0/support/include/keymasterV4_0/keymaster_tags.h
index bc7f311..8d6e74a 100644
--- a/keymaster/4.0/support/include/keymasterV4_0/keymaster_tags.h
+++ b/keymaster/4.0/support/include/keymasterV4_0/keymaster_tags.h
@@ -112,6 +112,11 @@
DECLARE_TYPED_TAG(ASSOCIATED_DATA);
DECLARE_TYPED_TAG(ATTESTATION_APPLICATION_ID);
DECLARE_TYPED_TAG(ATTESTATION_CHALLENGE);
+DECLARE_TYPED_TAG(ATTESTATION_ID_BRAND);
+DECLARE_TYPED_TAG(ATTESTATION_ID_DEVICE);
+DECLARE_TYPED_TAG(ATTESTATION_ID_PRODUCT);
+DECLARE_TYPED_TAG(ATTESTATION_ID_MANUFACTURER);
+DECLARE_TYPED_TAG(ATTESTATION_ID_MODEL);
DECLARE_TYPED_TAG(AUTH_TIMEOUT);
DECLARE_TYPED_TAG(BLOB_USAGE_REQUIREMENTS);
DECLARE_TYPED_TAG(BLOCK_MODE);
@@ -155,21 +160,22 @@
template <typename... Elems>
struct MetaList {};
-using all_tags_t =
- MetaList<TAG_INVALID_t, TAG_KEY_SIZE_t, TAG_MAC_LENGTH_t, TAG_CALLER_NONCE_t,
- TAG_MIN_MAC_LENGTH_t, TAG_RSA_PUBLIC_EXPONENT_t, TAG_INCLUDE_UNIQUE_ID_t,
- TAG_ACTIVE_DATETIME_t, TAG_ORIGINATION_EXPIRE_DATETIME_t, TAG_USAGE_EXPIRE_DATETIME_t,
- TAG_MIN_SECONDS_BETWEEN_OPS_t, TAG_MAX_USES_PER_BOOT_t, TAG_USER_ID_t,
- TAG_USER_SECURE_ID_t, TAG_NO_AUTH_REQUIRED_t, TAG_AUTH_TIMEOUT_t,
- TAG_ALLOW_WHILE_ON_BODY_t, TAG_UNLOCKED_DEVICE_REQUIRED_t, TAG_APPLICATION_ID_t,
- TAG_APPLICATION_DATA_t, TAG_CREATION_DATETIME_t, TAG_ROLLBACK_RESISTANCE_t,
- TAG_HARDWARE_TYPE_t, TAG_ROOT_OF_TRUST_t, TAG_ASSOCIATED_DATA_t, TAG_NONCE_t,
- TAG_BOOTLOADER_ONLY_t, TAG_OS_VERSION_t, TAG_OS_PATCHLEVEL_t, TAG_UNIQUE_ID_t,
- TAG_ATTESTATION_CHALLENGE_t, TAG_ATTESTATION_APPLICATION_ID_t,
- TAG_RESET_SINCE_ID_ROTATION_t, TAG_PURPOSE_t, TAG_ALGORITHM_t, TAG_BLOCK_MODE_t,
- TAG_DIGEST_t, TAG_PADDING_t, TAG_BLOB_USAGE_REQUIREMENTS_t, TAG_ORIGIN_t,
- TAG_USER_AUTH_TYPE_t, TAG_EC_CURVE_t, TAG_BOOT_PATCHLEVEL_t, TAG_VENDOR_PATCHLEVEL_t,
- TAG_TRUSTED_CONFIRMATION_REQUIRED_t, TAG_TRUSTED_USER_PRESENCE_REQUIRED_t>;
+using all_tags_t = MetaList<
+ TAG_INVALID_t, TAG_KEY_SIZE_t, TAG_MAC_LENGTH_t, TAG_CALLER_NONCE_t, TAG_MIN_MAC_LENGTH_t,
+ TAG_RSA_PUBLIC_EXPONENT_t, TAG_INCLUDE_UNIQUE_ID_t, TAG_ACTIVE_DATETIME_t,
+ TAG_ORIGINATION_EXPIRE_DATETIME_t, TAG_USAGE_EXPIRE_DATETIME_t,
+ TAG_MIN_SECONDS_BETWEEN_OPS_t, TAG_MAX_USES_PER_BOOT_t, TAG_USER_ID_t, TAG_USER_SECURE_ID_t,
+ TAG_NO_AUTH_REQUIRED_t, TAG_AUTH_TIMEOUT_t, TAG_ALLOW_WHILE_ON_BODY_t,
+ TAG_UNLOCKED_DEVICE_REQUIRED_t, TAG_APPLICATION_ID_t, TAG_APPLICATION_DATA_t,
+ TAG_CREATION_DATETIME_t, TAG_ROLLBACK_RESISTANCE_t, TAG_HARDWARE_TYPE_t,
+ TAG_ROOT_OF_TRUST_t, TAG_ASSOCIATED_DATA_t, TAG_NONCE_t, TAG_BOOTLOADER_ONLY_t,
+ TAG_OS_VERSION_t, TAG_OS_PATCHLEVEL_t, TAG_UNIQUE_ID_t, TAG_ATTESTATION_CHALLENGE_t,
+ TAG_ATTESTATION_APPLICATION_ID_t, TAG_ATTESTATION_ID_BRAND_t, TAG_ATTESTATION_ID_DEVICE_t,
+ TAG_ATTESTATION_ID_PRODUCT_t, TAG_ATTESTATION_ID_MANUFACTURER_t, TAG_ATTESTATION_ID_MODEL_t,
+ TAG_RESET_SINCE_ID_ROTATION_t, TAG_PURPOSE_t, TAG_ALGORITHM_t, TAG_BLOCK_MODE_t,
+ TAG_DIGEST_t, TAG_PADDING_t, TAG_BLOB_USAGE_REQUIREMENTS_t, TAG_ORIGIN_t,
+ TAG_USER_AUTH_TYPE_t, TAG_EC_CURVE_t, TAG_BOOT_PATCHLEVEL_t, TAG_VENDOR_PATCHLEVEL_t,
+ TAG_TRUSTED_CONFIRMATION_REQUIRED_t, TAG_TRUSTED_USER_PRESENCE_REQUIRED_t>;
template <typename TypedTagType>
struct TypedTag2ValueType;
diff --git a/media/omx/1.0/vts/functional/common/Android.bp b/media/omx/1.0/vts/functional/common/Android.bp
index 2c024a0..7e7463a 100644
--- a/media/omx/1.0/vts/functional/common/Android.bp
+++ b/media/omx/1.0/vts/functional/common/Android.bp
@@ -68,11 +68,11 @@
"android.hidl.allocator@1.0",
"android.hidl.memory@1.0",
"libhidlmemory",
- "libnativehelper",
],
// TODO(b/64437680): Assume these libs are always available on the device.
shared_libs: [
+ "libnativehelper",
"libstagefright_foundation",
],
}
diff --git a/neuralnetworks/1.0/vts/functional/BasicTests.cpp b/neuralnetworks/1.0/vts/functional/BasicTests.cpp
index cc44c9e..bda43b1 100644
--- a/neuralnetworks/1.0/vts/functional/BasicTests.cpp
+++ b/neuralnetworks/1.0/vts/functional/BasicTests.cpp
@@ -18,8 +18,12 @@
#include "VtsHalNeuralnetworks.h"
+#include "1.0/Callbacks.h"
+
namespace android::hardware::neuralnetworks::V1_0::vts::functional {
+using implementation::PreparedModelCallback;
+
// create device test
TEST_P(NeuralnetworksHidlTest, CreateDevice) {}
@@ -43,4 +47,136 @@
EXPECT_TRUE(ret.isOk());
}
+// detect cycle
+TEST_P(NeuralnetworksHidlTest, CycleTest) {
+ // opnd0 = TENSOR_FLOAT32 // model input
+ // opnd1 = TENSOR_FLOAT32 // model input
+ // opnd2 = INT32 // model input
+ // opnd3 = ADD(opnd0, opnd4, opnd2)
+ // opnd4 = ADD(opnd1, opnd3, opnd2)
+ // opnd5 = ADD(opnd4, opnd0, opnd2) // model output
+ //
+ // +-----+
+ // | |
+ // v |
+ // 3 = ADD(0, 4, 2) |
+ // | |
+ // +----------+ |
+ // | |
+ // v |
+ // 4 = ADD(1, 3, 2) |
+ // | |
+ // +----------------+
+ // |
+ // |
+ // +-------+
+ // |
+ // v
+ // 5 = ADD(4, 0, 2)
+
+ const std::vector<Operand> operands = {
+ {
+ // operands[0]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 2,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::MODEL_INPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[1]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 1,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::MODEL_INPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[2]
+ .type = OperandType::INT32,
+ .dimensions = {},
+ .numberOfConsumers = 3,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::MODEL_INPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[3]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 1,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::TEMPORARY_VARIABLE,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[4]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 2,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::TEMPORARY_VARIABLE,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[5]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 0,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::MODEL_OUTPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ };
+
+ const std::vector<Operation> operations = {
+ {.type = OperationType::ADD, .inputs = {0, 4, 2}, .outputs = {3}},
+ {.type = OperationType::ADD, .inputs = {1, 3, 2}, .outputs = {4}},
+ {.type = OperationType::ADD, .inputs = {4, 0, 2}, .outputs = {5}},
+ };
+
+ const Model model = {
+ .operands = operands,
+ .operations = operations,
+ .inputIndexes = {0, 1, 2},
+ .outputIndexes = {5},
+ .operandValues = {},
+ .pools = {},
+ };
+
+ // ensure that getSupportedOperations() checks model validity
+ ErrorStatus supportedOpsErrorStatus = ErrorStatus::GENERAL_FAILURE;
+ Return<void> supportedOpsReturn = kDevice->getSupportedOperations(
+ model, [&model, &supportedOpsErrorStatus](ErrorStatus status,
+ const hidl_vec<bool>& supported) {
+ supportedOpsErrorStatus = status;
+ if (status == ErrorStatus::NONE) {
+ ASSERT_EQ(supported.size(), model.operations.size());
+ }
+ });
+ ASSERT_TRUE(supportedOpsReturn.isOk());
+ ASSERT_EQ(supportedOpsErrorStatus, ErrorStatus::INVALID_ARGUMENT);
+
+ // ensure that prepareModel() checks model validity
+ sp<PreparedModelCallback> preparedModelCallback = new PreparedModelCallback;
+ Return<ErrorStatus> prepareLaunchReturn = kDevice->prepareModel(model, preparedModelCallback);
+ ASSERT_TRUE(prepareLaunchReturn.isOk());
+ // Note that preparation can fail for reasons other than an
+ // invalid model (invalid model should result in
+ // INVALID_ARGUMENT) -- for example, perhaps not all
+ // operations are supported, or perhaps the device hit some
+ // kind of capacity limit.
+ EXPECT_NE(prepareLaunchReturn, ErrorStatus::NONE);
+ EXPECT_NE(preparedModelCallback->getStatus(), ErrorStatus::NONE);
+ EXPECT_EQ(preparedModelCallback->getPreparedModel(), nullptr);
+}
+
} // namespace android::hardware::neuralnetworks::V1_0::vts::functional
diff --git a/neuralnetworks/1.0/vts/functional/Utils.cpp b/neuralnetworks/1.0/vts/functional/Utils.cpp
index 3613e69..32850b0 100644
--- a/neuralnetworks/1.0/vts/functional/Utils.cpp
+++ b/neuralnetworks/1.0/vts/functional/Utils.cpp
@@ -29,7 +29,11 @@
#include <gtest/gtest.h>
#include <algorithm>
+#include <cstring>
+#include <functional>
#include <iostream>
+#include <map>
+#include <numeric>
#include <vector>
namespace android::hardware::neuralnetworks {
@@ -172,6 +176,45 @@
return outputBuffers;
}
+uint32_t sizeOfData(V1_0::OperandType type) {
+ switch (type) {
+ case V1_0::OperandType::FLOAT32:
+ case V1_0::OperandType::INT32:
+ case V1_0::OperandType::UINT32:
+ case V1_0::OperandType::TENSOR_FLOAT32:
+ case V1_0::OperandType::TENSOR_INT32:
+ return 4;
+ case V1_0::OperandType::TENSOR_QUANT8_ASYMM:
+ return 1;
+ default:
+ CHECK(false) << "Invalid OperandType " << static_cast<uint32_t>(type);
+ return 0;
+ }
+}
+
+static bool isTensor(V1_0::OperandType type) {
+ switch (type) {
+ case V1_0::OperandType::FLOAT32:
+ case V1_0::OperandType::INT32:
+ case V1_0::OperandType::UINT32:
+ return false;
+ case V1_0::OperandType::TENSOR_FLOAT32:
+ case V1_0::OperandType::TENSOR_INT32:
+ case V1_0::OperandType::TENSOR_QUANT8_ASYMM:
+ return true;
+ default:
+ CHECK(false) << "Invalid OperandType " << static_cast<uint32_t>(type);
+ return false;
+ }
+}
+
+uint32_t sizeOfData(const V1_0::Operand& operand) {
+ const uint32_t dataSize = sizeOfData(operand.type);
+ if (isTensor(operand.type) && operand.dimensions.size() == 0) return 0;
+ return std::accumulate(operand.dimensions.begin(), operand.dimensions.end(), dataSize,
+ std::multiplies<>{});
+}
+
std::string gtestCompliantName(std::string name) {
// gtest test names must only contain alphanumeric characters
std::replace_if(
diff --git a/neuralnetworks/1.0/vts/functional/ValidateModel.cpp b/neuralnetworks/1.0/vts/functional/ValidateModel.cpp
index 79d8594..5ffbd43 100644
--- a/neuralnetworks/1.0/vts/functional/ValidateModel.cpp
+++ b/neuralnetworks/1.0/vts/functional/ValidateModel.cpp
@@ -17,9 +17,14 @@
#define LOG_TAG "neuralnetworks_hidl_hal_test"
#include "1.0/Callbacks.h"
+#include "1.0/Utils.h"
#include "GeneratedTestHarness.h"
#include "VtsHalNeuralnetworks.h"
+#include <optional>
+#include <type_traits>
+#include <utility>
+
namespace android::hardware::neuralnetworks::V1_0::vts::functional {
using implementation::PreparedModelCallback;
@@ -67,26 +72,6 @@
validatePrepareModel(device, message, model);
}
-// Delete element from hidl_vec. hidl_vec doesn't support a "remove" operation,
-// so this is efficiently accomplished by moving the element to the end and
-// resizing the hidl_vec to one less.
-template <typename Type>
-static void hidl_vec_removeAt(hidl_vec<Type>* vec, uint32_t index) {
- if (vec) {
- std::rotate(vec->begin() + index, vec->begin() + index + 1, vec->end());
- vec->resize(vec->size() - 1);
- }
-}
-
-template <typename Type>
-static uint32_t hidl_vec_push_back(hidl_vec<Type>* vec, const Type& value) {
- // assume vec is valid
- const uint32_t index = vec->size();
- vec->resize(index + 1);
- (*vec)[index] = value;
- return index;
-}
-
static uint32_t addOperand(Model* model) {
return hidl_vec_push_back(&model->operands,
{
@@ -107,6 +92,211 @@
return index;
}
+// If we introduce a CONSTANT_COPY for an operand of size operandSize,
+// how much will this increase the size of the model? This assumes
+// that we can (re)use all of model.operandValues for the operand
+// value.
+static size_t constantCopyExtraSize(const Model& model, size_t operandSize) {
+ const size_t operandValuesSize = model.operandValues.size();
+ return (operandValuesSize < operandSize) ? (operandSize - operandValuesSize) : 0;
+}
+
+// Highly specialized utility routine for converting an operand to
+// CONSTANT_COPY lifetime.
+//
+// Expects that:
+// - operand has a known size
+// - operand->lifetime has already been set to CONSTANT_COPY
+// - operand->location has been zeroed out
+//
+// Does the following:
+// - initializes operand->location to point to the beginning of model->operandValues
+// - resizes model->operandValues (if necessary) to be large enough for the operand
+// value, padding it with zeroes on the end
+//
+// Potential problem:
+// By changing the operand to CONSTANT_COPY lifetime, this function is effectively initializing the
+// operand with unspecified (but deterministic) data. This means that the model may be invalidated
+// in two ways: not only is the lifetime of CONSTANT_COPY invalid, but the operand's value in the
+// graph may also be invalid (e.g., if the operand is used as an activation code and has an invalid
+// value). For now, this should be fine because it just means we're not testing what we think we're
+// testing in certain cases; but we can handwave this and assume we're probabilistically likely to
+// exercise the validation code over the span of the entire test set and operand space.
+//
+// Aborts if the specified operand type is an extension type or OEM type.
+static void becomeConstantCopy(Model* model, Operand* operand) {
+ // sizeOfData will abort if the specified type is an extension type or OEM type.
+ const size_t sizeOfOperand = sizeOfData(*operand);
+ EXPECT_NE(sizeOfOperand, size_t(0));
+ operand->location.poolIndex = 0;
+ operand->location.offset = 0;
+ operand->location.length = sizeOfOperand;
+ if (model->operandValues.size() < sizeOfOperand) {
+ model->operandValues.resize(sizeOfOperand);
+ }
+}
+
+// The sizeForBinder() functions estimate the size of the
+// representation of a value when sent to binder. It's probably a bit
+// of an under-estimate, because we don't know the size of the
+// metadata in the binder format (e.g., representation of the size of
+// a vector); but at least it adds up "big" things like vector
+// contents. However, it doesn't treat inter-field or end-of-struct
+// padding in a methodical way -- there's no attempt to be consistent
+// in whether or not padding in the native (C++) representation
+// contributes to the estimated size for the binder representation;
+// and there's no attempt to understand what padding (if any) is
+// needed in the binder representation.
+//
+// This assumes that non-metadata uses a fixed length encoding (e.g.,
+// a uint32_t is always encoded in sizeof(uint32_t) bytes, rather than
+// using an encoding whose length is related to the magnitude of the
+// encoded value).
+
+template <typename Type>
+static size_t sizeForBinder(const Type& val) {
+ static_assert(std::is_trivially_copyable_v<std::remove_reference_t<Type>>,
+ "expected a trivially copyable type");
+ return sizeof(val);
+}
+
+template <typename Type>
+static size_t sizeForBinder(const hidl_vec<Type>& vec) {
+ return std::accumulate(vec.begin(), vec.end(), 0,
+ [](size_t acc, const Type& x) { return acc + sizeForBinder(x); });
+}
+
+template <>
+size_t sizeForBinder(const Operand& operand) {
+ size_t size = 0;
+
+ size += sizeForBinder(operand.type);
+ size += sizeForBinder(operand.dimensions);
+ size += sizeForBinder(operand.numberOfConsumers);
+ size += sizeForBinder(operand.scale);
+ size += sizeForBinder(operand.zeroPoint);
+ size += sizeForBinder(operand.lifetime);
+ size += sizeForBinder(operand.location);
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const Operation& operation) {
+ size_t size = 0;
+
+ size += sizeForBinder(operation.type);
+ size += sizeForBinder(operation.inputs);
+ size += sizeForBinder(operation.outputs);
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const hidl_string& name) {
+ return name.size();
+}
+
+template <>
+size_t sizeForBinder(const hidl_memory& memory) {
+ // This is just a guess.
+
+ size_t size = 0;
+
+ if (const native_handle_t* handle = memory.handle()) {
+ size += sizeof(*handle);
+ size += sizeof(handle->data[0] * (handle->numFds + handle->numInts));
+ }
+ size += sizeForBinder(memory.name());
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const Model& model) {
+ size_t size = 0;
+
+ size += sizeForBinder(model.operands);
+ size += sizeForBinder(model.operations);
+ size += sizeForBinder(model.inputIndexes);
+ size += sizeForBinder(model.outputIndexes);
+ size += sizeForBinder(model.operandValues);
+ size += sizeForBinder(model.pools);
+
+ return size;
+}
+
+// https://developer.android.com/reference/android/os/TransactionTooLargeException.html
+//
+// "The Binder transaction buffer has a limited fixed size,
+// currently 1Mb, which is shared by all transactions in progress
+// for the process."
+//
+// Will our representation fit under this limit? There are two complications:
+// - Our representation size is just approximate (see sizeForBinder()).
+// - This object may not be the only occupant of the Binder transaction buffer.
+// So we'll be very conservative: We want the representation size to be no
+// larger than half the transaction buffer size.
+//
+// If our representation grows large enough that it still fits within
+// the transaction buffer but combined with other transactions may
+// exceed the buffer size, then we may see intermittent HAL transport
+// errors.
+static bool exceedsBinderSizeLimit(size_t representationSize) {
+ // Instead of using this fixed buffer size, we might instead be able to use
+ // ProcessState::self()->getMmapSize(). However, this has a potential
+ // problem: The binder/mmap size of the current process does not necessarily
+ // indicate the binder/mmap size of the service (i.e., the other process).
+ // The only way it would be a good indication is if both the current process
+ // and the service use the default size.
+ static const size_t kHalfBufferSize = 1024 * 1024 / 2;
+
+ return representationSize > kHalfBufferSize;
+}
+
+///////////////////////// VALIDATE EXECUTION ORDER ////////////////////////////
+
+static void mutateExecutionOrderTest(const sp<IDevice>& device, const V1_0::Model& model) {
+ for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+ const Operation& operationObj = model.operations[operation];
+ for (uint32_t input : operationObj.inputs) {
+ if (model.operands[input].lifetime == OperandLifeTime::TEMPORARY_VARIABLE ||
+ model.operands[input].lifetime == OperandLifeTime::MODEL_OUTPUT) {
+ // This operation reads an operand written by some
+ // other operation. Move this operation to the
+ // beginning of the sequence, ensuring that it reads
+ // the operand before that operand is written, thereby
+ // violating execution order rules.
+ const std::string message = "mutateExecutionOrderTest: operation " +
+ std::to_string(operation) + " is a reader";
+ validate(device, message, model, [operation](Model* model) {
+ auto& operations = model->operations;
+ std::rotate(operations.begin(), operations.begin() + operation,
+ operations.begin() + operation + 1);
+ });
+ break; // only need to do this once per operation
+ }
+ }
+ for (uint32_t output : operationObj.outputs) {
+ if (model.operands[output].numberOfConsumers > 0) {
+ // This operation writes an operand read by some other
+ // operation. Move this operation to the end of the
+ // sequence, ensuring that it writes the operand after
+ // that operand is read, thereby violating execution
+ // order rules.
+ const std::string message = "mutateExecutionOrderTest: operation " +
+ std::to_string(operation) + " is a writer";
+ validate(device, message, model, [operation](Model* model) {
+ auto& operations = model->operations;
+ std::rotate(operations.begin() + operation, operations.begin() + operation + 1,
+ operations.end());
+ });
+ break; // only need to do this once per operation
+ }
+ }
+ }
+}
+
///////////////////////// VALIDATE MODEL OPERAND TYPE /////////////////////////
static const int32_t invalidOperandTypes[] = {
@@ -218,9 +408,233 @@
}
}
+///////////////////////// VALIDATE OPERAND LIFETIME /////////////////////////////////////////////
+
+static std::vector<OperandLifeTime> getInvalidLifeTimes(const Model& model, size_t modelSize,
+ const Operand& operand) {
+ // TODO: Support OperandLifeTime::CONSTANT_REFERENCE as an invalid lifetime
+ // TODO: Support OperandLifeTime::NO_VALUE as an invalid lifetime
+
+ // Ways to get an invalid lifetime:
+ // - change whether a lifetime means an operand should have a writer
+ std::vector<OperandLifeTime> ret;
+ switch (operand.lifetime) {
+ case OperandLifeTime::MODEL_OUTPUT:
+ case OperandLifeTime::TEMPORARY_VARIABLE:
+ ret = {
+ OperandLifeTime::MODEL_INPUT,
+ OperandLifeTime::CONSTANT_COPY,
+ };
+ break;
+ case OperandLifeTime::CONSTANT_COPY:
+ case OperandLifeTime::CONSTANT_REFERENCE:
+ case OperandLifeTime::MODEL_INPUT:
+ ret = {
+ OperandLifeTime::TEMPORARY_VARIABLE,
+ OperandLifeTime::MODEL_OUTPUT,
+ };
+ break;
+ case OperandLifeTime::NO_VALUE:
+ // Not enough information to know whether
+ // TEMPORARY_VARIABLE or CONSTANT_COPY would be invalid --
+ // is this operand written (then CONSTANT_COPY would be
+ // invalid) or not (then TEMPORARY_VARIABLE would be
+ // invalid)?
+ break;
+ default:
+ ADD_FAILURE();
+ break;
+ }
+
+ const size_t operandSize = sizeOfData(operand); // will be zero if shape is unknown
+ if (!operandSize ||
+ exceedsBinderSizeLimit(modelSize + constantCopyExtraSize(model, operandSize))) {
+ // Unknown size or too-large size
+ ret.erase(std::remove(ret.begin(), ret.end(), OperandLifeTime::CONSTANT_COPY), ret.end());
+ }
+
+ return ret;
+}
+
+static void mutateOperandLifeTimeTest(const sp<IDevice>& device, const V1_0::Model& model) {
+ const size_t modelSize = sizeForBinder(model);
+ for (size_t operand = 0; operand < model.operands.size(); ++operand) {
+ const std::vector<OperandLifeTime> invalidLifeTimes =
+ getInvalidLifeTimes(model, modelSize, model.operands[operand]);
+ for (OperandLifeTime invalidLifeTime : invalidLifeTimes) {
+ const std::string message = "mutateOperandLifetimeTest: operand " +
+ std::to_string(operand) + " has lifetime " +
+ toString(invalidLifeTime) + " instead of lifetime " +
+ toString(model.operands[operand].lifetime);
+ validate(device, message, model, [operand, invalidLifeTime](Model* model) {
+ static const DataLocation kZeroDataLocation = {};
+ Operand& operandObj = model->operands[operand];
+ switch (operandObj.lifetime) {
+ case OperandLifeTime::MODEL_INPUT: {
+ hidl_vec_remove(&model->inputIndexes, uint32_t(operand));
+ break;
+ }
+ case OperandLifeTime::MODEL_OUTPUT: {
+ hidl_vec_remove(&model->outputIndexes, uint32_t(operand));
+ break;
+ }
+ default:
+ break;
+ }
+ operandObj.lifetime = invalidLifeTime;
+ operandObj.location = kZeroDataLocation;
+ switch (invalidLifeTime) {
+ case OperandLifeTime::CONSTANT_COPY: {
+ becomeConstantCopy(model, &operandObj);
+ break;
+ }
+ case OperandLifeTime::MODEL_INPUT:
+ hidl_vec_push_back(&model->inputIndexes, uint32_t(operand));
+ break;
+ case OperandLifeTime::MODEL_OUTPUT:
+ hidl_vec_push_back(&model->outputIndexes, uint32_t(operand));
+ break;
+ default:
+ break;
+ }
+ });
+ }
+ }
+}
+
+///////////////////////// VALIDATE OPERAND INPUT-or-OUTPUT //////////////////////////////////////
+
+static std::optional<OperandLifeTime> getInputOutputLifeTime(const Model& model, size_t modelSize,
+ const Operand& operand) {
+ // Ways to get an invalid lifetime (with respect to model inputIndexes and outputIndexes):
+ // - change whether a lifetime means an operand is a model input, a model output, or neither
+ // - preserve whether or not a lifetime means an operand should have a writer
+ switch (operand.lifetime) {
+ case OperandLifeTime::CONSTANT_COPY:
+ case OperandLifeTime::CONSTANT_REFERENCE:
+ return OperandLifeTime::MODEL_INPUT;
+ case OperandLifeTime::MODEL_INPUT: {
+ const size_t operandSize = sizeOfData(operand); // will be zero if shape is unknown
+ if (!operandSize ||
+ exceedsBinderSizeLimit(modelSize + constantCopyExtraSize(model, operandSize))) {
+ // Unknown size or too-large size
+ break;
+ }
+ return OperandLifeTime::CONSTANT_COPY;
+ }
+ case OperandLifeTime::MODEL_OUTPUT:
+ return OperandLifeTime::TEMPORARY_VARIABLE;
+ case OperandLifeTime::TEMPORARY_VARIABLE:
+ return OperandLifeTime::MODEL_OUTPUT;
+ case OperandLifeTime::NO_VALUE:
+ // Not enough information to know whether
+ // TEMPORARY_VARIABLE or CONSTANT_COPY would be an
+ // appropriate choice -- is this operand written (then
+ // TEMPORARY_VARIABLE would be appropriate) or not (then
+ // CONSTANT_COPY would be appropriate)?
+ break;
+ default:
+ ADD_FAILURE();
+ break;
+ }
+
+ return std::nullopt;
+}
+
+static void mutateOperandInputOutputTest(const sp<IDevice>& device, const V1_0::Model& model) {
+ const size_t modelSize = sizeForBinder(model);
+ for (size_t operand = 0; operand < model.operands.size(); ++operand) {
+ const std::optional<OperandLifeTime> changedLifeTime =
+ getInputOutputLifeTime(model, modelSize, model.operands[operand]);
+ if (changedLifeTime) {
+ const std::string message = "mutateOperandInputOutputTest: operand " +
+ std::to_string(operand) + " has lifetime " +
+ toString(*changedLifeTime) + " instead of lifetime " +
+ toString(model.operands[operand].lifetime);
+ validate(device, message, model, [operand, changedLifeTime](Model* model) {
+ static const DataLocation kZeroDataLocation = {};
+ Operand& operandObj = model->operands[operand];
+ operandObj.lifetime = *changedLifeTime;
+ operandObj.location = kZeroDataLocation;
+ if (*changedLifeTime == OperandLifeTime::CONSTANT_COPY) {
+ becomeConstantCopy(model, &operandObj);
+ }
+ });
+ }
+ }
+}
+
+///////////////////////// VALIDATE OPERAND NUMBER OF CONSUMERS //////////////////////////////////
+
+static std::vector<uint32_t> getInvalidNumberOfConsumers(uint32_t numberOfConsumers) {
+ if (numberOfConsumers == 0) {
+ return {1};
+ } else {
+ return {numberOfConsumers - 1, numberOfConsumers + 1};
+ }
+}
+
+static void mutateOperandNumberOfConsumersTest(const sp<IDevice>& device,
+ const V1_0::Model& model) {
+ for (size_t operand = 0; operand < model.operands.size(); ++operand) {
+ const std::vector<uint32_t> invalidNumberOfConsumersVec =
+ getInvalidNumberOfConsumers(model.operands[operand].numberOfConsumers);
+ for (uint32_t invalidNumberOfConsumers : invalidNumberOfConsumersVec) {
+ const std::string message =
+ "mutateOperandNumberOfConsumersTest: operand " + std::to_string(operand) +
+ " numberOfConsumers = " + std::to_string(invalidNumberOfConsumers);
+ validate(device, message, model, [operand, invalidNumberOfConsumers](Model* model) {
+ model->operands[operand].numberOfConsumers = invalidNumberOfConsumers;
+ });
+ }
+ }
+}
+
+///////////////////////// VALIDATE OPERAND NUMBER OF WRITERS ////////////////////////////////////
+
+static void mutateOperandAddWriterTest(const sp<IDevice>& device, const V1_0::Model& model) {
+ for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+ for (size_t badOutputNum = 0; badOutputNum < model.operations[operation].outputs.size();
+ ++badOutputNum) {
+ const uint32_t outputOperandIndex = model.operations[operation].outputs[badOutputNum];
+ const std::string message = "mutateOperandAddWriterTest: operation " +
+ std::to_string(operation) + " writes to " +
+ std::to_string(outputOperandIndex);
+ // We'll insert a copy of the operation, all of whose
+ // OTHER output operands are newly-created -- i.e.,
+ // there'll only be a duplicate write of ONE of that
+ // operation's output operands.
+ validate(device, message, model, [operation, badOutputNum](Model* model) {
+ Operation newOperation = model->operations[operation];
+ for (uint32_t input : newOperation.inputs) {
+ ++model->operands[input].numberOfConsumers;
+ }
+ for (size_t outputNum = 0; outputNum < newOperation.outputs.size(); ++outputNum) {
+ if (outputNum == badOutputNum) continue;
+
+ Operand operandValue = model->operands[newOperation.outputs[outputNum]];
+ operandValue.numberOfConsumers = 0;
+ if (operandValue.lifetime == OperandLifeTime::MODEL_OUTPUT) {
+ operandValue.lifetime = OperandLifeTime::TEMPORARY_VARIABLE;
+ } else {
+ ASSERT_EQ(operandValue.lifetime, OperandLifeTime::TEMPORARY_VARIABLE);
+ }
+ newOperation.outputs[outputNum] =
+ hidl_vec_push_back(&model->operands, operandValue);
+ }
+ // Where do we insert the extra writer (a new
+ // operation)? It has to be later than all the
+ // writers of its inputs. The easiest thing to do
+ // is to insert it at the end of the operation
+ // sequence.
+ hidl_vec_push_back(&model->operations, newOperation);
+ });
+ }
+ }
+}
+
///////////////////////// VALIDATE EXTRA ??? /////////////////////////
-// TODO: Operand::lifetime
// TODO: Operand::location
///////////////////////// VALIDATE OPERATION OPERAND TYPE /////////////////////////
@@ -351,6 +765,33 @@
}
}
+///////////////////////// VALIDATE MODEL OPERANDS WRITTEN ///////////////////////////////////////
+
+static void mutateOperationRemoveWriteTest(const sp<IDevice>& device, const V1_0::Model& model) {
+ for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+ for (size_t outputNum = 0; outputNum < model.operations[operation].outputs.size();
+ ++outputNum) {
+ const uint32_t outputOperandIndex = model.operations[operation].outputs[outputNum];
+ if (model.operands[outputOperandIndex].numberOfConsumers > 0) {
+ const std::string message = "mutateOperationRemoveWriteTest: operation " +
+ std::to_string(operation) + " writes to " +
+ std::to_string(outputOperandIndex);
+ validate(device, message, model, [operation, outputNum](Model* model) {
+ uint32_t& outputOperandIndex = model->operations[operation].outputs[outputNum];
+ Operand operandValue = model->operands[outputOperandIndex];
+ operandValue.numberOfConsumers = 0;
+ if (operandValue.lifetime == OperandLifeTime::MODEL_OUTPUT) {
+ operandValue.lifetime = OperandLifeTime::TEMPORARY_VARIABLE;
+ } else {
+ ASSERT_EQ(operandValue.lifetime, OperandLifeTime::TEMPORARY_VARIABLE);
+ }
+ outputOperandIndex = hidl_vec_push_back(&model->operands, operandValue);
+ });
+ }
+ }
+ }
+}
+
///////////////////////// REMOVE OPERAND FROM EVERYTHING /////////////////////////
static void removeValueAndDecrementGreaterValues(hidl_vec<uint32_t>* vec, uint32_t value) {
@@ -476,14 +917,20 @@
////////////////////////// ENTRY POINT //////////////////////////////
void validateModel(const sp<IDevice>& device, const Model& model) {
+ mutateExecutionOrderTest(device, model);
mutateOperandTypeTest(device, model);
mutateOperandRankTest(device, model);
mutateOperandScaleTest(device, model);
mutateOperandZeroPointTest(device, model);
+ mutateOperandLifeTimeTest(device, model);
+ mutateOperandInputOutputTest(device, model);
+ mutateOperandNumberOfConsumersTest(device, model);
+ mutateOperandAddWriterTest(device, model);
mutateOperationOperandTypeTest(device, model);
mutateOperationTypeTest(device, model);
mutateOperationInputOperandIndexTest(device, model);
mutateOperationOutputOperandIndexTest(device, model);
+ mutateOperationRemoveWriteTest(device, model);
removeOperandTest(device, model);
removeOperationTest(device, model);
removeOperationInputTest(device, model);
diff --git a/neuralnetworks/1.0/vts/functional/include/1.0/Utils.h b/neuralnetworks/1.0/vts/functional/include/1.0/Utils.h
index 3292f79..7bd0460 100644
--- a/neuralnetworks/1.0/vts/functional/include/1.0/Utils.h
+++ b/neuralnetworks/1.0/vts/functional/include/1.0/Utils.h
@@ -21,6 +21,7 @@
#include <android/hardware/neuralnetworks/1.0/types.h>
#include <android/hardware_buffer.h>
#include <android/hidl/memory/1.0/IMemory.h>
+#include <gtest/gtest.h>
#include <algorithm>
#include <iosfwd>
#include <string>
@@ -108,6 +109,15 @@
vec->resize(vec->size() - 1);
}
+// Assumes there is exactly one instance of the value in the vector.
+template <typename Type>
+inline void hidl_vec_remove(hidl_vec<Type>* vec, const Type& val) {
+ CHECK(vec != nullptr);
+ auto where = std::find(vec->begin(), vec->end(), val);
+ ASSERT_NE(where, vec->end());
+ hidl_vec_removeAt(vec, where - vec->begin());
+}
+
template <typename Type>
inline uint32_t hidl_vec_push_back(hidl_vec<Type>* vec, const Type& value) {
CHECK(vec != nullptr);
@@ -117,6 +127,18 @@
return index;
}
+// Returns the amount of space needed to store a value of the specified type.
+//
+// Aborts if the specified type is an extension type or OEM type.
+uint32_t sizeOfData(V1_0::OperandType type);
+
+// Returns the amount of space needed to store a value of the dimensions and
+// type of this operand. For a non-extension, non-OEM tensor with unspecified
+// rank or at least one unspecified dimension, returns zero.
+//
+// Aborts if the specified type is an extension type or OEM type.
+uint32_t sizeOfData(const V1_0::Operand& operand);
+
template <typename Type>
using Named = std::pair<std::string, Type>;
diff --git a/neuralnetworks/1.1/vts/functional/BasicTests.cpp b/neuralnetworks/1.1/vts/functional/BasicTests.cpp
index 44836f0..baadd1b 100644
--- a/neuralnetworks/1.1/vts/functional/BasicTests.cpp
+++ b/neuralnetworks/1.1/vts/functional/BasicTests.cpp
@@ -18,10 +18,16 @@
#include "VtsHalNeuralnetworks.h"
+#include "1.0/Callbacks.h"
+
namespace android::hardware::neuralnetworks::V1_1::vts::functional {
using V1_0::DeviceStatus;
using V1_0::ErrorStatus;
+using V1_0::Operand;
+using V1_0::OperandLifeTime;
+using V1_0::OperandType;
+using V1_0::implementation::PreparedModelCallback;
// create device test
TEST_P(NeuralnetworksHidlTest, CreateDevice) {}
@@ -48,4 +54,137 @@
EXPECT_TRUE(ret.isOk());
}
+// detect cycle
+TEST_P(NeuralnetworksHidlTest, CycleTest) {
+ // opnd0 = TENSOR_FLOAT32 // model input
+ // opnd1 = TENSOR_FLOAT32 // model input
+ // opnd2 = INT32 // model input
+ // opnd3 = ADD(opnd0, opnd4, opnd2)
+ // opnd4 = ADD(opnd1, opnd3, opnd2)
+ // opnd5 = ADD(opnd4, opnd0, opnd2) // model output
+ //
+ // +-----+
+ // | |
+ // v |
+ // 3 = ADD(0, 4, 2) |
+ // | |
+ // +----------+ |
+ // | |
+ // v |
+ // 4 = ADD(1, 3, 2) |
+ // | |
+ // +----------------+
+ // |
+ // |
+ // +-------+
+ // |
+ // v
+ // 5 = ADD(4, 0, 2)
+
+ const std::vector<Operand> operands = {
+ {
+ // operands[0]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 2,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::MODEL_INPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[1]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 1,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::MODEL_INPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[2]
+ .type = OperandType::INT32,
+ .dimensions = {},
+ .numberOfConsumers = 3,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::MODEL_INPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[3]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 1,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::TEMPORARY_VARIABLE,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[4]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 2,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::TEMPORARY_VARIABLE,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[5]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 0,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::MODEL_OUTPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ };
+
+ const std::vector<Operation> operations = {
+ {.type = OperationType::ADD, .inputs = {0, 4, 2}, .outputs = {3}},
+ {.type = OperationType::ADD, .inputs = {1, 3, 2}, .outputs = {4}},
+ {.type = OperationType::ADD, .inputs = {4, 0, 2}, .outputs = {5}},
+ };
+
+ const Model model = {
+ .operands = operands,
+ .operations = operations,
+ .inputIndexes = {0, 1, 2},
+ .outputIndexes = {5},
+ .operandValues = {},
+ .pools = {},
+ };
+
+ // ensure that getSupportedOperations_1_1() checks model validity
+ ErrorStatus supportedOpsErrorStatus = ErrorStatus::GENERAL_FAILURE;
+ Return<void> supportedOpsReturn = kDevice->getSupportedOperations_1_1(
+ model, [&model, &supportedOpsErrorStatus](ErrorStatus status,
+ const hidl_vec<bool>& supported) {
+ supportedOpsErrorStatus = status;
+ if (status == ErrorStatus::NONE) {
+ ASSERT_EQ(supported.size(), model.operations.size());
+ }
+ });
+ ASSERT_TRUE(supportedOpsReturn.isOk());
+ ASSERT_EQ(supportedOpsErrorStatus, ErrorStatus::INVALID_ARGUMENT);
+
+ // ensure that prepareModel_1_1() checks model validity
+ sp<PreparedModelCallback> preparedModelCallback = new PreparedModelCallback;
+ Return<ErrorStatus> prepareLaunchReturn = kDevice->prepareModel_1_1(
+ model, ExecutionPreference::FAST_SINGLE_ANSWER, preparedModelCallback);
+ ASSERT_TRUE(prepareLaunchReturn.isOk());
+ // Note that preparation can fail for reasons other than an
+ // invalid model (invalid model should result in
+ // INVALID_ARGUMENT) -- for example, perhaps not all
+ // operations are supported, or perhaps the device hit some
+ // kind of capacity limit.
+ EXPECT_NE(prepareLaunchReturn, ErrorStatus::NONE);
+ EXPECT_NE(preparedModelCallback->getStatus(), ErrorStatus::NONE);
+ EXPECT_EQ(preparedModelCallback->getPreparedModel(), nullptr);
+}
+
} // namespace android::hardware::neuralnetworks::V1_1::vts::functional
diff --git a/neuralnetworks/1.1/vts/functional/ValidateModel.cpp b/neuralnetworks/1.1/vts/functional/ValidateModel.cpp
index 3b6f0f8..1f4e4ed 100644
--- a/neuralnetworks/1.1/vts/functional/ValidateModel.cpp
+++ b/neuralnetworks/1.1/vts/functional/ValidateModel.cpp
@@ -16,13 +16,19 @@
#define LOG_TAG "neuralnetworks_hidl_hal_test"
+#include <android/hardware/neuralnetworks/1.1/types.h>
#include "1.0/Callbacks.h"
#include "1.0/Utils.h"
#include "GeneratedTestHarness.h"
#include "VtsHalNeuralnetworks.h"
+#include <optional>
+#include <type_traits>
+#include <utility>
+
namespace android::hardware::neuralnetworks::V1_1::vts::functional {
+using V1_0::DataLocation;
using V1_0::ErrorStatus;
using V1_0::IPreparedModel;
using V1_0::Operand;
@@ -105,6 +111,212 @@
return index;
}
+// If we introduce a CONSTANT_COPY for an operand of size operandSize,
+// how much will this increase the size of the model? This assumes
+// that we can (re)use all of model.operandValues for the operand
+// value.
+static size_t constantCopyExtraSize(const Model& model, size_t operandSize) {
+ const size_t operandValuesSize = model.operandValues.size();
+ return (operandValuesSize < operandSize) ? (operandSize - operandValuesSize) : 0;
+}
+
+// Highly specialized utility routine for converting an operand to
+// CONSTANT_COPY lifetime.
+//
+// Expects that:
+// - operand has a known size
+// - operand->lifetime has already been set to CONSTANT_COPY
+// - operand->location has been zeroed out
+//
+// Does the following:
+// - initializes operand->location to point to the beginning of model->operandValues
+// - resizes model->operandValues (if necessary) to be large enough for the operand
+// value, padding it with zeroes on the end
+//
+// Potential problem:
+// By changing the operand to CONSTANT_COPY lifetime, this function is effectively initializing the
+// operand with unspecified (but deterministic) data. This means that the model may be invalidated
+// in two ways: not only is the lifetime of CONSTANT_COPY invalid, but the operand's value in the
+// graph may also be invalid (e.g., if the operand is used as an activation code and has an invalid
+// value). For now, this should be fine because it just means we're not testing what we think we're
+// testing in certain cases; but we can handwave this and assume we're probabilistically likely to
+// exercise the validation code over the span of the entire test set and operand space.
+//
+// Aborts if the specified operand type is an extension type or OEM type.
+static void becomeConstantCopy(Model* model, Operand* operand) {
+ // sizeOfData will abort if the specified type is an extension type or OEM type.
+ const size_t sizeOfOperand = sizeOfData(*operand);
+ EXPECT_NE(sizeOfOperand, size_t(0));
+ operand->location.poolIndex = 0;
+ operand->location.offset = 0;
+ operand->location.length = sizeOfOperand;
+ if (model->operandValues.size() < sizeOfOperand) {
+ model->operandValues.resize(sizeOfOperand);
+ }
+}
+
+// The sizeForBinder() functions estimate the size of the
+// representation of a value when sent to binder. It's probably a bit
+// of an under-estimate, because we don't know the size of the
+// metadata in the binder format (e.g., representation of the size of
+// a vector); but at least it adds up "big" things like vector
+// contents. However, it doesn't treat inter-field or end-of-struct
+// padding in a methodical way -- there's no attempt to be consistent
+// in whether or not padding in the native (C++) representation
+// contributes to the estimated size for the binder representation;
+// and there's no attempt to understand what padding (if any) is
+// needed in the binder representation.
+//
+// This assumes that non-metadata uses a fixed length encoding (e.g.,
+// a uint32_t is always encoded in sizeof(uint32_t) bytes, rather than
+// using an encoding whose length is related to the magnitude of the
+// encoded value).
+
+template <typename Type>
+static size_t sizeForBinder(const Type& val) {
+ static_assert(std::is_trivially_copyable_v<std::remove_reference_t<Type>>,
+ "expected a trivially copyable type");
+ return sizeof(val);
+}
+
+template <typename Type>
+static size_t sizeForBinder(const hidl_vec<Type>& vec) {
+ return std::accumulate(vec.begin(), vec.end(), 0,
+ [](size_t acc, const Type& x) { return acc + sizeForBinder(x); });
+}
+
+template <>
+size_t sizeForBinder(const Operand& operand) {
+ size_t size = 0;
+
+ size += sizeForBinder(operand.type);
+ size += sizeForBinder(operand.dimensions);
+ size += sizeForBinder(operand.numberOfConsumers);
+ size += sizeForBinder(operand.scale);
+ size += sizeForBinder(operand.zeroPoint);
+ size += sizeForBinder(operand.lifetime);
+ size += sizeForBinder(operand.location);
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const Operation& operation) {
+ size_t size = 0;
+
+ size += sizeForBinder(operation.type);
+ size += sizeForBinder(operation.inputs);
+ size += sizeForBinder(operation.outputs);
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const hidl_string& name) {
+ return name.size();
+}
+
+template <>
+size_t sizeForBinder(const hidl_memory& memory) {
+ // This is just a guess.
+
+ size_t size = 0;
+
+ if (const native_handle_t* handle = memory.handle()) {
+ size += sizeof(*handle);
+ size += sizeof(handle->data[0] * (handle->numFds + handle->numInts));
+ }
+ size += sizeForBinder(memory.name());
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const Model& model) {
+ size_t size = 0;
+
+ size += sizeForBinder(model.operands);
+ size += sizeForBinder(model.operations);
+ size += sizeForBinder(model.inputIndexes);
+ size += sizeForBinder(model.outputIndexes);
+ size += sizeForBinder(model.operandValues);
+ size += sizeForBinder(model.pools);
+ size += sizeForBinder(model.relaxComputationFloat32toFloat16);
+
+ return size;
+}
+
+// https://developer.android.com/reference/android/os/TransactionTooLargeException.html
+//
+// "The Binder transaction buffer has a limited fixed size,
+// currently 1Mb, which is shared by all transactions in progress
+// for the process."
+//
+// Will our representation fit under this limit? There are two complications:
+// - Our representation size is just approximate (see sizeForBinder()).
+// - This object may not be the only occupant of the Binder transaction buffer.
+// So we'll be very conservative: We want the representation size to be no
+// larger than half the transaction buffer size.
+//
+// If our representation grows large enough that it still fits within
+// the transaction buffer but combined with other transactions may
+// exceed the buffer size, then we may see intermittent HAL transport
+// errors.
+static bool exceedsBinderSizeLimit(size_t representationSize) {
+ // Instead of using this fixed buffer size, we might instead be able to use
+ // ProcessState::self()->getMmapSize(). However, this has a potential
+ // problem: The binder/mmap size of the current process does not necessarily
+ // indicate the binder/mmap size of the service (i.e., the other process).
+ // The only way it would be a good indication is if both the current process
+ // and the service use the default size.
+ static const size_t kHalfBufferSize = 1024 * 1024 / 2;
+
+ return representationSize > kHalfBufferSize;
+}
+
+///////////////////////// VALIDATE EXECUTION ORDER ////////////////////////////
+
+static void mutateExecutionOrderTest(const sp<IDevice>& device, const V1_1::Model& model) {
+ for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+ const Operation& operationObj = model.operations[operation];
+ for (uint32_t input : operationObj.inputs) {
+ if (model.operands[input].lifetime == OperandLifeTime::TEMPORARY_VARIABLE ||
+ model.operands[input].lifetime == OperandLifeTime::MODEL_OUTPUT) {
+ // This operation reads an operand written by some
+ // other operation. Move this operation to the
+ // beginning of the sequence, ensuring that it reads
+ // the operand before that operand is written, thereby
+ // violating execution order rules.
+ const std::string message = "mutateExecutionOrderTest: operation " +
+ std::to_string(operation) + " is a reader";
+ validate(device, message, model, [operation](Model* model, ExecutionPreference*) {
+ auto& operations = model->operations;
+ std::rotate(operations.begin(), operations.begin() + operation,
+ operations.begin() + operation + 1);
+ });
+ break; // only need to do this once per operation
+ }
+ }
+ for (uint32_t output : operationObj.outputs) {
+ if (model.operands[output].numberOfConsumers > 0) {
+ // This operation writes an operand read by some other
+ // operation. Move this operation to the end of the
+ // sequence, ensuring that it writes the operand after
+ // that operand is read, thereby violating execution
+ // order rules.
+ const std::string message = "mutateExecutionOrderTest: operation " +
+ std::to_string(operation) + " is a writer";
+ validate(device, message, model, [operation](Model* model, ExecutionPreference*) {
+ auto& operations = model->operations;
+ std::rotate(operations.begin() + operation, operations.begin() + operation + 1,
+ operations.end());
+ });
+ break; // only need to do this once per operation
+ }
+ }
+ }
+}
+
///////////////////////// VALIDATE MODEL OPERAND TYPE /////////////////////////
static const int32_t invalidOperandTypes[] = {
@@ -221,9 +433,240 @@
}
}
+///////////////////////// VALIDATE OPERAND LIFETIME /////////////////////////////////////////////
+
+static std::vector<OperandLifeTime> getInvalidLifeTimes(const Model& model, size_t modelSize,
+ const Operand& operand) {
+ // TODO: Support OperandLifeTime::CONSTANT_REFERENCE as an invalid lifetime
+ // TODO: Support OperandLifeTime::NO_VALUE as an invalid lifetime
+
+ // Ways to get an invalid lifetime:
+ // - change whether a lifetime means an operand should have a writer
+ std::vector<OperandLifeTime> ret;
+ switch (operand.lifetime) {
+ case OperandLifeTime::MODEL_OUTPUT:
+ case OperandLifeTime::TEMPORARY_VARIABLE:
+ ret = {
+ OperandLifeTime::MODEL_INPUT,
+ OperandLifeTime::CONSTANT_COPY,
+ };
+ break;
+ case OperandLifeTime::CONSTANT_COPY:
+ case OperandLifeTime::CONSTANT_REFERENCE:
+ case OperandLifeTime::MODEL_INPUT:
+ ret = {
+ OperandLifeTime::TEMPORARY_VARIABLE,
+ OperandLifeTime::MODEL_OUTPUT,
+ };
+ break;
+ case OperandLifeTime::NO_VALUE:
+ // Not enough information to know whether
+ // TEMPORARY_VARIABLE or CONSTANT_COPY would be invalid --
+ // is this operand written (then CONSTANT_COPY would be
+ // invalid) or not (then TEMPORARY_VARIABLE would be
+ // invalid)?
+ break;
+ default:
+ ADD_FAILURE();
+ break;
+ }
+
+ const size_t operandSize = sizeOfData(operand); // will be zero if shape is unknown
+ if (!operandSize ||
+ exceedsBinderSizeLimit(modelSize + constantCopyExtraSize(model, operandSize))) {
+ // Unknown size or too-large size
+ ret.erase(std::remove(ret.begin(), ret.end(), OperandLifeTime::CONSTANT_COPY), ret.end());
+ }
+
+ return ret;
+}
+
+static void mutateOperandLifeTimeTest(const sp<IDevice>& device, const V1_1::Model& model) {
+ const size_t modelSize = sizeForBinder(model);
+ for (size_t operand = 0; operand < model.operands.size(); ++operand) {
+ const std::vector<OperandLifeTime> invalidLifeTimes =
+ getInvalidLifeTimes(model, modelSize, model.operands[operand]);
+ for (OperandLifeTime invalidLifeTime : invalidLifeTimes) {
+ const std::string message = "mutateOperandLifetimeTest: operand " +
+ std::to_string(operand) + " has lifetime " +
+ toString(invalidLifeTime) + " instead of lifetime " +
+ toString(model.operands[operand].lifetime);
+ validate(device, message, model,
+ [operand, invalidLifeTime](Model* model, ExecutionPreference*) {
+ static const DataLocation kZeroDataLocation = {};
+ Operand& operandObj = model->operands[operand];
+ switch (operandObj.lifetime) {
+ case OperandLifeTime::MODEL_INPUT: {
+ hidl_vec_remove(&model->inputIndexes, uint32_t(operand));
+ break;
+ }
+ case OperandLifeTime::MODEL_OUTPUT: {
+ hidl_vec_remove(&model->outputIndexes, uint32_t(operand));
+ break;
+ }
+ default:
+ break;
+ }
+ operandObj.lifetime = invalidLifeTime;
+ operandObj.location = kZeroDataLocation;
+ switch (invalidLifeTime) {
+ case OperandLifeTime::CONSTANT_COPY: {
+ becomeConstantCopy(model, &operandObj);
+ break;
+ }
+ case OperandLifeTime::MODEL_INPUT:
+ hidl_vec_push_back(&model->inputIndexes, uint32_t(operand));
+ break;
+ case OperandLifeTime::MODEL_OUTPUT:
+ hidl_vec_push_back(&model->outputIndexes, uint32_t(operand));
+ break;
+ default:
+ break;
+ }
+ });
+ }
+ }
+}
+
+///////////////////////// VALIDATE OPERAND INPUT-or-OUTPUT //////////////////////////////////////
+
+static std::optional<OperandLifeTime> getInputOutputLifeTime(const Model& model, size_t modelSize,
+ const Operand& operand) {
+ // Ways to get an invalid lifetime (with respect to model inputIndexes and outputIndexes):
+ // - change whether a lifetime means an operand is a model input, a model output, or neither
+ // - preserve whether or not a lifetime means an operand should have a writer
+ switch (operand.lifetime) {
+ case OperandLifeTime::CONSTANT_COPY:
+ case OperandLifeTime::CONSTANT_REFERENCE:
+ return OperandLifeTime::MODEL_INPUT;
+ case OperandLifeTime::MODEL_INPUT: {
+ const size_t operandSize = sizeOfData(operand); // will be zero if shape is unknown
+ if (!operandSize ||
+ exceedsBinderSizeLimit(modelSize + constantCopyExtraSize(model, operandSize))) {
+ // Unknown size or too-large size
+ break;
+ }
+ return OperandLifeTime::CONSTANT_COPY;
+ }
+ case OperandLifeTime::MODEL_OUTPUT:
+ return OperandLifeTime::TEMPORARY_VARIABLE;
+ case OperandLifeTime::TEMPORARY_VARIABLE:
+ return OperandLifeTime::MODEL_OUTPUT;
+ case OperandLifeTime::NO_VALUE:
+ // Not enough information to know whether
+ // TEMPORARY_VARIABLE or CONSTANT_COPY would be an
+ // appropriate choice -- is this operand written (then
+ // TEMPORARY_VARIABLE would be appropriate) or not (then
+ // CONSTANT_COPY would be appropriate)?
+ break;
+ default:
+ ADD_FAILURE();
+ break;
+ }
+
+ return std::nullopt;
+}
+
+static void mutateOperandInputOutputTest(const sp<IDevice>& device, const V1_1::Model& model) {
+ const size_t modelSize = sizeForBinder(model);
+ for (size_t operand = 0; operand < model.operands.size(); ++operand) {
+ const std::optional<OperandLifeTime> changedLifeTime =
+ getInputOutputLifeTime(model, modelSize, model.operands[operand]);
+ if (changedLifeTime) {
+ const std::string message = "mutateOperandInputOutputTest: operand " +
+ std::to_string(operand) + " has lifetime " +
+ toString(*changedLifeTime) + " instead of lifetime " +
+ toString(model.operands[operand].lifetime);
+ validate(device, message, model,
+ [operand, changedLifeTime](Model* model, ExecutionPreference*) {
+ static const DataLocation kZeroDataLocation = {};
+ Operand& operandObj = model->operands[operand];
+ operandObj.lifetime = *changedLifeTime;
+ operandObj.location = kZeroDataLocation;
+ if (*changedLifeTime == OperandLifeTime::CONSTANT_COPY) {
+ becomeConstantCopy(model, &operandObj);
+ }
+ });
+ }
+ }
+}
+
+///////////////////////// VALIDATE OPERAND NUMBER OF CONSUMERS //////////////////////////////////
+
+static std::vector<uint32_t> getInvalidNumberOfConsumers(uint32_t numberOfConsumers) {
+ if (numberOfConsumers == 0) {
+ return {1};
+ } else {
+ return {numberOfConsumers - 1, numberOfConsumers + 1};
+ }
+}
+
+static void mutateOperandNumberOfConsumersTest(const sp<IDevice>& device,
+ const V1_1::Model& model) {
+ for (size_t operand = 0; operand < model.operands.size(); ++operand) {
+ const std::vector<uint32_t> invalidNumberOfConsumersVec =
+ getInvalidNumberOfConsumers(model.operands[operand].numberOfConsumers);
+ for (uint32_t invalidNumberOfConsumers : invalidNumberOfConsumersVec) {
+ const std::string message =
+ "mutateOperandNumberOfConsumersTest: operand " + std::to_string(operand) +
+ " numberOfConsumers = " + std::to_string(invalidNumberOfConsumers);
+ validate(device, message, model,
+ [operand, invalidNumberOfConsumers](Model* model, ExecutionPreference*) {
+ model->operands[operand].numberOfConsumers = invalidNumberOfConsumers;
+ });
+ }
+ }
+}
+
+///////////////////////// VALIDATE OPERAND NUMBER OF WRITERS ////////////////////////////////////
+
+static void mutateOperandAddWriterTest(const sp<IDevice>& device, const V1_1::Model& model) {
+ for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+ for (size_t badOutputNum = 0; badOutputNum < model.operations[operation].outputs.size();
+ ++badOutputNum) {
+ const uint32_t outputOperandIndex = model.operations[operation].outputs[badOutputNum];
+ const std::string message = "mutateOperandAddWriterTest: operation " +
+ std::to_string(operation) + " writes to " +
+ std::to_string(outputOperandIndex);
+ // We'll insert a copy of the operation, all of whose
+ // OTHER output operands are newly-created -- i.e.,
+ // there'll only be a duplicate write of ONE of that
+ // operation's output operands.
+ validate(device, message, model,
+ [operation, badOutputNum](Model* model, ExecutionPreference*) {
+ Operation newOperation = model->operations[operation];
+ for (uint32_t input : newOperation.inputs) {
+ ++model->operands[input].numberOfConsumers;
+ }
+ for (size_t outputNum = 0; outputNum < newOperation.outputs.size();
+ ++outputNum) {
+ if (outputNum == badOutputNum) continue;
+
+ Operand operandValue =
+ model->operands[newOperation.outputs[outputNum]];
+ operandValue.numberOfConsumers = 0;
+ if (operandValue.lifetime == OperandLifeTime::MODEL_OUTPUT) {
+ operandValue.lifetime = OperandLifeTime::TEMPORARY_VARIABLE;
+ } else {
+ ASSERT_EQ(operandValue.lifetime,
+ OperandLifeTime::TEMPORARY_VARIABLE);
+ }
+ newOperation.outputs[outputNum] =
+ hidl_vec_push_back(&model->operands, operandValue);
+ }
+ // Where do we insert the extra writer (a new
+ // operation)? It has to be later than all the
+ // writers of its inputs. The easiest thing to do
+ // is to insert it at the end of the operation
+ // sequence.
+ hidl_vec_push_back(&model->operations, newOperation);
+ });
+ }
+ }
+}
+
///////////////////////// VALIDATE EXTRA ??? /////////////////////////
-// TODO: Operand::lifetime
// TODO: Operand::location
///////////////////////// VALIDATE OPERATION OPERAND TYPE /////////////////////////
@@ -358,6 +801,37 @@
}
}
+///////////////////////// VALIDATE MODEL OPERANDS WRITTEN ///////////////////////////////////////
+
+static void mutateOperationRemoveWriteTest(const sp<IDevice>& device, const V1_1::Model& model) {
+ for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+ for (size_t outputNum = 0; outputNum < model.operations[operation].outputs.size();
+ ++outputNum) {
+ const uint32_t outputOperandIndex = model.operations[operation].outputs[outputNum];
+ if (model.operands[outputOperandIndex].numberOfConsumers > 0) {
+ const std::string message = "mutateOperationRemoveWriteTest: operation " +
+ std::to_string(operation) + " writes to " +
+ std::to_string(outputOperandIndex);
+ validate(device, message, model,
+ [operation, outputNum](Model* model, ExecutionPreference*) {
+ uint32_t& outputOperandIndex =
+ model->operations[operation].outputs[outputNum];
+ Operand operandValue = model->operands[outputOperandIndex];
+ operandValue.numberOfConsumers = 0;
+ if (operandValue.lifetime == OperandLifeTime::MODEL_OUTPUT) {
+ operandValue.lifetime = OperandLifeTime::TEMPORARY_VARIABLE;
+ } else {
+ ASSERT_EQ(operandValue.lifetime,
+ OperandLifeTime::TEMPORARY_VARIABLE);
+ }
+ outputOperandIndex =
+ hidl_vec_push_back(&model->operands, operandValue);
+ });
+ }
+ }
+ }
+}
+
///////////////////////// REMOVE OPERAND FROM EVERYTHING /////////////////////////
static void removeValueAndDecrementGreaterValues(hidl_vec<uint32_t>* vec, uint32_t value) {
@@ -504,14 +978,20 @@
////////////////////////// ENTRY POINT //////////////////////////////
void validateModel(const sp<IDevice>& device, const Model& model) {
+ mutateExecutionOrderTest(device, model);
mutateOperandTypeTest(device, model);
mutateOperandRankTest(device, model);
mutateOperandScaleTest(device, model);
mutateOperandZeroPointTest(device, model);
+ mutateOperandLifeTimeTest(device, model);
+ mutateOperandInputOutputTest(device, model);
+ mutateOperandNumberOfConsumersTest(device, model);
+ mutateOperandAddWriterTest(device, model);
mutateOperationOperandTypeTest(device, model);
mutateOperationTypeTest(device, model);
mutateOperationInputOperandIndexTest(device, model);
mutateOperationOutputOperandIndexTest(device, model);
+ mutateOperationRemoveWriteTest(device, model);
removeOperandTest(device, model);
removeOperationTest(device, model);
removeOperationInputTest(device, model);
diff --git a/neuralnetworks/1.2/vts/functional/Android.bp b/neuralnetworks/1.2/vts/functional/Android.bp
index 481eb80..182f716 100644
--- a/neuralnetworks/1.2/vts/functional/Android.bp
+++ b/neuralnetworks/1.2/vts/functional/Android.bp
@@ -15,11 +15,12 @@
//
cc_library_static {
- name: "VtsHalNeuralNetworksV1_2Callbacks",
+ name: "VtsHalNeuralNetworksV1_2_utils",
defaults: ["neuralnetworks_vts_functional_defaults"],
export_include_dirs: ["include"],
srcs: [
"Callbacks.cpp",
+ "Utils.cpp",
],
static_libs: [
"android.hardware.neuralnetworks@1.0",
@@ -51,7 +52,7 @@
],
static_libs: [
"VtsHalNeuralNetworksV1_0_utils",
- "VtsHalNeuralNetworksV1_2Callbacks",
+ "VtsHalNeuralNetworksV1_2_utils",
"android.hardware.neuralnetworks@1.0",
"android.hardware.neuralnetworks@1.1",
"android.hardware.neuralnetworks@1.2",
diff --git a/neuralnetworks/1.2/vts/functional/BasicTests.cpp b/neuralnetworks/1.2/vts/functional/BasicTests.cpp
index 58d3c4a..77340e7 100644
--- a/neuralnetworks/1.2/vts/functional/BasicTests.cpp
+++ b/neuralnetworks/1.2/vts/functional/BasicTests.cpp
@@ -20,9 +20,13 @@
namespace android::hardware::neuralnetworks::V1_2::vts::functional {
+using implementation::PreparedModelCallback;
using V1_0::DeviceStatus;
using V1_0::ErrorStatus;
+using V1_0::OperandLifeTime;
using V1_0::PerformanceInfo;
+using V1_1::ExecutionPreference;
+using HidlToken = hidl_array<uint8_t, static_cast<uint32_t>(Constant::BYTE_SIZE_OF_CACHE_TOKEN)>;
// create device test
TEST_P(NeuralnetworksHidlTest, CreateDevice) {}
@@ -123,4 +127,139 @@
});
EXPECT_TRUE(ret.isOk());
}
+
+// detect cycle
+TEST_P(NeuralnetworksHidlTest, CycleTest) {
+ // opnd0 = TENSOR_FLOAT32 // model input
+ // opnd1 = TENSOR_FLOAT32 // model input
+ // opnd2 = INT32 // model input
+ // opnd3 = ADD(opnd0, opnd4, opnd2)
+ // opnd4 = ADD(opnd1, opnd3, opnd2)
+ // opnd5 = ADD(opnd4, opnd0, opnd2) // model output
+ //
+ // +-----+
+ // | |
+ // v |
+ // 3 = ADD(0, 4, 2) |
+ // | |
+ // +----------+ |
+ // | |
+ // v |
+ // 4 = ADD(1, 3, 2) |
+ // | |
+ // +----------------+
+ // |
+ // |
+ // +-------+
+ // |
+ // v
+ // 5 = ADD(4, 0, 2)
+
+ const std::vector<Operand> operands = {
+ {
+ // operands[0]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 2,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::MODEL_INPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[1]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 1,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::MODEL_INPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[2]
+ .type = OperandType::INT32,
+ .dimensions = {},
+ .numberOfConsumers = 3,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::MODEL_INPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[3]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 1,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::TEMPORARY_VARIABLE,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[4]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 2,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::TEMPORARY_VARIABLE,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[5]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 0,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::MODEL_OUTPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ };
+
+ const std::vector<Operation> operations = {
+ {.type = OperationType::ADD, .inputs = {0, 4, 2}, .outputs = {3}},
+ {.type = OperationType::ADD, .inputs = {1, 3, 2}, .outputs = {4}},
+ {.type = OperationType::ADD, .inputs = {4, 0, 2}, .outputs = {5}},
+ };
+
+ const Model model = {
+ .operands = operands,
+ .operations = operations,
+ .inputIndexes = {0, 1, 2},
+ .outputIndexes = {5},
+ .operandValues = {},
+ .pools = {},
+ };
+
+ // ensure that getSupportedOperations_1_2() checks model validity
+ ErrorStatus supportedOpsErrorStatus = ErrorStatus::GENERAL_FAILURE;
+ Return<void> supportedOpsReturn = kDevice->getSupportedOperations_1_2(
+ model, [&model, &supportedOpsErrorStatus](ErrorStatus status,
+ const hidl_vec<bool>& supported) {
+ supportedOpsErrorStatus = status;
+ if (status == ErrorStatus::NONE) {
+ ASSERT_EQ(supported.size(), model.operations.size());
+ }
+ });
+ ASSERT_TRUE(supportedOpsReturn.isOk());
+ ASSERT_EQ(supportedOpsErrorStatus, ErrorStatus::INVALID_ARGUMENT);
+
+ // ensure that prepareModel_1_2() checks model validity
+ sp<PreparedModelCallback> preparedModelCallback = new PreparedModelCallback;
+ Return<ErrorStatus> prepareLaunchReturn = kDevice->prepareModel_1_2(
+ model, ExecutionPreference::FAST_SINGLE_ANSWER, hidl_vec<hidl_handle>(),
+ hidl_vec<hidl_handle>(), HidlToken(), preparedModelCallback);
+ ASSERT_TRUE(prepareLaunchReturn.isOk());
+ // Note that preparation can fail for reasons other than an
+ // invalid model (invalid model should result in
+ // INVALID_ARGUMENT) -- for example, perhaps not all
+ // operations are supported, or perhaps the device hit some
+ // kind of capacity limit.
+ EXPECT_NE(prepareLaunchReturn, ErrorStatus::NONE);
+ EXPECT_NE(preparedModelCallback->getStatus(), ErrorStatus::NONE);
+ EXPECT_EQ(preparedModelCallback->getPreparedModel(), nullptr);
+}
+
} // namespace android::hardware::neuralnetworks::V1_2::vts::functional
diff --git a/neuralnetworks/1.2/vts/functional/Utils.cpp b/neuralnetworks/1.2/vts/functional/Utils.cpp
new file mode 100644
index 0000000..cc654f2
--- /dev/null
+++ b/neuralnetworks/1.2/vts/functional/Utils.cpp
@@ -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.
+ */
+
+#include <android-base/logging.h>
+#include <android/hardware/neuralnetworks/1.2/types.h>
+
+#include <functional>
+#include <numeric>
+
+namespace android {
+namespace hardware {
+namespace neuralnetworks {
+
+uint32_t sizeOfData(V1_2::OperandType type) {
+ switch (type) {
+ case V1_2::OperandType::FLOAT32:
+ case V1_2::OperandType::INT32:
+ case V1_2::OperandType::UINT32:
+ case V1_2::OperandType::TENSOR_FLOAT32:
+ case V1_2::OperandType::TENSOR_INT32:
+ return 4;
+ case V1_2::OperandType::TENSOR_QUANT16_SYMM:
+ case V1_2::OperandType::TENSOR_FLOAT16:
+ case V1_2::OperandType::FLOAT16:
+ case V1_2::OperandType::TENSOR_QUANT16_ASYMM:
+ return 2;
+ case V1_2::OperandType::TENSOR_QUANT8_ASYMM:
+ case V1_2::OperandType::BOOL:
+ case V1_2::OperandType::TENSOR_BOOL8:
+ case V1_2::OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL:
+ case V1_2::OperandType::TENSOR_QUANT8_SYMM:
+ return 1;
+ default:
+ CHECK(false) << "Invalid OperandType " << static_cast<uint32_t>(type);
+ return 0;
+ }
+}
+
+static bool isTensor(V1_2::OperandType type) {
+ switch (type) {
+ case V1_2::OperandType::FLOAT32:
+ case V1_2::OperandType::INT32:
+ case V1_2::OperandType::UINT32:
+ case V1_2::OperandType::FLOAT16:
+ case V1_2::OperandType::BOOL:
+ return false;
+ case V1_2::OperandType::TENSOR_FLOAT32:
+ case V1_2::OperandType::TENSOR_INT32:
+ case V1_2::OperandType::TENSOR_QUANT16_SYMM:
+ case V1_2::OperandType::TENSOR_FLOAT16:
+ case V1_2::OperandType::TENSOR_QUANT16_ASYMM:
+ case V1_2::OperandType::TENSOR_QUANT8_ASYMM:
+ case V1_2::OperandType::TENSOR_BOOL8:
+ case V1_2::OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL:
+ case V1_2::OperandType::TENSOR_QUANT8_SYMM:
+ return true;
+ default:
+ CHECK(false) << "Invalid OperandType " << static_cast<uint32_t>(type);
+ return false;
+ }
+}
+
+uint32_t sizeOfData(const V1_2::Operand& operand) {
+ const uint32_t dataSize = sizeOfData(operand.type);
+ if (isTensor(operand.type) && operand.dimensions.size() == 0) return 0;
+ return std::accumulate(operand.dimensions.begin(), operand.dimensions.end(), dataSize,
+ std::multiplies<>{});
+}
+
+} // namespace neuralnetworks
+} // namespace hardware
+} // namespace android
diff --git a/neuralnetworks/1.2/vts/functional/ValidateModel.cpp b/neuralnetworks/1.2/vts/functional/ValidateModel.cpp
index e9fc6e9..6583dfe 100644
--- a/neuralnetworks/1.2/vts/functional/ValidateModel.cpp
+++ b/neuralnetworks/1.2/vts/functional/ValidateModel.cpp
@@ -16,14 +16,21 @@
#define LOG_TAG "neuralnetworks_hidl_hal_test"
+#include <android/hardware/neuralnetworks/1.1/types.h>
#include "1.0/Utils.h"
#include "1.2/Callbacks.h"
+#include "1.2/Utils.h"
#include "GeneratedTestHarness.h"
#include "VtsHalNeuralnetworks.h"
+#include <optional>
+#include <type_traits>
+#include <utility>
+
namespace android::hardware::neuralnetworks::V1_2::vts::functional {
using implementation::PreparedModelCallback;
+using V1_0::DataLocation;
using V1_0::ErrorStatus;
using V1_0::OperandLifeTime;
using V1_1::ExecutionPreference;
@@ -105,6 +112,250 @@
return index;
}
+// If we introduce a CONSTANT_COPY for an operand of size operandSize,
+// how much will this increase the size of the model? This assumes
+// that we can (re)use all of model.operandValues for the operand
+// value.
+static size_t constantCopyExtraSize(const Model& model, size_t operandSize) {
+ const size_t operandValuesSize = model.operandValues.size();
+ return (operandValuesSize < operandSize) ? (operandSize - operandValuesSize) : 0;
+}
+
+// Highly specialized utility routine for converting an operand to
+// CONSTANT_COPY lifetime.
+//
+// Expects that:
+// - operand has a known size
+// - operand->lifetime has already been set to CONSTANT_COPY
+// - operand->location has been zeroed out
+//
+// Does the following:
+// - initializes operand->location to point to the beginning of model->operandValues
+// - resizes model->operandValues (if necessary) to be large enough for the operand
+// value, padding it with zeroes on the end
+//
+// Potential problem:
+// By changing the operand to CONSTANT_COPY lifetime, this function is effectively initializing the
+// operand with unspecified (but deterministic) data. This means that the model may be invalidated
+// in two ways: not only is the lifetime of CONSTANT_COPY invalid, but the operand's value in the
+// graph may also be invalid (e.g., if the operand is used as an activation code and has an invalid
+// value). For now, this should be fine because it just means we're not testing what we think we're
+// testing in certain cases; but we can handwave this and assume we're probabilistically likely to
+// exercise the validation code over the span of the entire test set and operand space.
+//
+// Aborts if the specified operand type is an extension type or OEM type.
+static void becomeConstantCopy(Model* model, Operand* operand) {
+ // sizeOfData will abort if the specified type is an extension type or OEM type.
+ const size_t sizeOfOperand = sizeOfData(*operand);
+ EXPECT_NE(sizeOfOperand, size_t(0));
+ operand->location.poolIndex = 0;
+ operand->location.offset = 0;
+ operand->location.length = sizeOfOperand;
+ if (model->operandValues.size() < sizeOfOperand) {
+ model->operandValues.resize(sizeOfOperand);
+ }
+}
+
+// The sizeForBinder() functions estimate the size of the
+// representation of a value when sent to binder. It's probably a bit
+// of an under-estimate, because we don't know the size of the
+// metadata in the binder format (e.g., representation of the size of
+// a vector); but at least it adds up "big" things like vector
+// contents. However, it doesn't treat inter-field or end-of-struct
+// padding in a methodical way -- there's no attempt to be consistent
+// in whether or not padding in the native (C++) representation
+// contributes to the estimated size for the binder representation;
+// and there's no attempt to understand what padding (if any) is
+// needed in the binder representation.
+//
+// This assumes that non-metadata uses a fixed length encoding (e.g.,
+// a uint32_t is always encoded in sizeof(uint32_t) bytes, rather than
+// using an encoding whose length is related to the magnitude of the
+// encoded value).
+
+template <typename Type>
+static size_t sizeForBinder(const Type& val) {
+ static_assert(std::is_trivially_copyable_v<std::remove_reference_t<Type>>,
+ "expected a trivially copyable type");
+ return sizeof(val);
+}
+
+template <typename Type>
+static size_t sizeForBinder(const hidl_vec<Type>& vec) {
+ return std::accumulate(vec.begin(), vec.end(), 0,
+ [](size_t acc, const Type& x) { return acc + sizeForBinder(x); });
+}
+
+template <>
+size_t sizeForBinder(const SymmPerChannelQuantParams& symmPerChannelQuantParams) {
+ size_t size = 0;
+
+ size += sizeForBinder(symmPerChannelQuantParams.scales);
+ size += sizeForBinder(symmPerChannelQuantParams.channelDim);
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const Operand::ExtraParams& extraParams) {
+ using Discriminator = Operand::ExtraParams::hidl_discriminator;
+ switch (extraParams.getDiscriminator()) {
+ case Discriminator::none:
+ return 0;
+ case Discriminator::channelQuant:
+ return sizeForBinder(extraParams.channelQuant());
+ case Discriminator::extension:
+ return sizeForBinder(extraParams.extension());
+ }
+ LOG(FATAL) << "Unrecognized extraParams enum: "
+ << static_cast<int>(extraParams.getDiscriminator());
+ return 0;
+}
+
+template <>
+size_t sizeForBinder(const Operand& operand) {
+ size_t size = 0;
+
+ size += sizeForBinder(operand.type);
+ size += sizeForBinder(operand.dimensions);
+ size += sizeForBinder(operand.numberOfConsumers);
+ size += sizeForBinder(operand.scale);
+ size += sizeForBinder(operand.zeroPoint);
+ size += sizeForBinder(operand.lifetime);
+ size += sizeForBinder(operand.location);
+ size += sizeForBinder(operand.extraParams);
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const Operation& operation) {
+ size_t size = 0;
+
+ size += sizeForBinder(operation.type);
+ size += sizeForBinder(operation.inputs);
+ size += sizeForBinder(operation.outputs);
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const hidl_string& name) {
+ return name.size();
+}
+
+template <>
+size_t sizeForBinder(const hidl_memory& memory) {
+ // This is just a guess.
+
+ size_t size = 0;
+
+ if (const native_handle_t* handle = memory.handle()) {
+ size += sizeof(*handle);
+ size += sizeof(handle->data[0] * (handle->numFds + handle->numInts));
+ }
+ size += sizeForBinder(memory.name());
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const Model::ExtensionNameAndPrefix& extensionNameToPrefix) {
+ size_t size = 0;
+
+ size += sizeForBinder(extensionNameToPrefix.name);
+ size += sizeForBinder(extensionNameToPrefix.prefix);
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const Model& model) {
+ size_t size = 0;
+
+ size += sizeForBinder(model.operands);
+ size += sizeForBinder(model.operations);
+ size += sizeForBinder(model.inputIndexes);
+ size += sizeForBinder(model.outputIndexes);
+ size += sizeForBinder(model.operandValues);
+ size += sizeForBinder(model.pools);
+ size += sizeForBinder(model.relaxComputationFloat32toFloat16);
+ size += sizeForBinder(model.extensionNameToPrefix);
+
+ return size;
+}
+
+// https://developer.android.com/reference/android/os/TransactionTooLargeException.html
+//
+// "The Binder transaction buffer has a limited fixed size,
+// currently 1Mb, which is shared by all transactions in progress
+// for the process."
+//
+// Will our representation fit under this limit? There are two complications:
+// - Our representation size is just approximate (see sizeForBinder()).
+// - This object may not be the only occupant of the Binder transaction buffer.
+// So we'll be very conservative: We want the representation size to be no
+// larger than half the transaction buffer size.
+//
+// If our representation grows large enough that it still fits within
+// the transaction buffer but combined with other transactions may
+// exceed the buffer size, then we may see intermittent HAL transport
+// errors.
+static bool exceedsBinderSizeLimit(size_t representationSize) {
+ // Instead of using this fixed buffer size, we might instead be able to use
+ // ProcessState::self()->getMmapSize(). However, this has a potential
+ // problem: The binder/mmap size of the current process does not necessarily
+ // indicate the binder/mmap size of the service (i.e., the other process).
+ // The only way it would be a good indication is if both the current process
+ // and the service use the default size.
+ static const size_t kHalfBufferSize = 1024 * 1024 / 2;
+
+ return representationSize > kHalfBufferSize;
+}
+
+///////////////////////// VALIDATE EXECUTION ORDER ////////////////////////////
+
+static void mutateExecutionOrderTest(const sp<IDevice>& device, const Model& model) {
+ for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+ const Operation& operationObj = model.operations[operation];
+ for (uint32_t input : operationObj.inputs) {
+ if (model.operands[input].lifetime == OperandLifeTime::TEMPORARY_VARIABLE ||
+ model.operands[input].lifetime == OperandLifeTime::MODEL_OUTPUT) {
+ // This operation reads an operand written by some
+ // other operation. Move this operation to the
+ // beginning of the sequence, ensuring that it reads
+ // the operand before that operand is written, thereby
+ // violating execution order rules.
+ const std::string message = "mutateExecutionOrderTest: operation " +
+ std::to_string(operation) + " is a reader";
+ validate(device, message, model, [operation](Model* model, ExecutionPreference*) {
+ auto& operations = model->operations;
+ std::rotate(operations.begin(), operations.begin() + operation,
+ operations.begin() + operation + 1);
+ });
+ break; // only need to do this once per operation
+ }
+ }
+ for (uint32_t output : operationObj.outputs) {
+ if (model.operands[output].numberOfConsumers > 0) {
+ // This operation writes an operand read by some other
+ // operation. Move this operation to the end of the
+ // sequence, ensuring that it writes the operand after
+ // that operand is read, thereby violating execution
+ // order rules.
+ const std::string message = "mutateExecutionOrderTest: operation " +
+ std::to_string(operation) + " is a writer";
+ validate(device, message, model, [operation](Model* model, ExecutionPreference*) {
+ auto& operations = model->operations;
+ std::rotate(operations.begin() + operation, operations.begin() + operation + 1,
+ operations.end());
+ });
+ break; // only need to do this once per operation
+ }
+ }
+ }
+}
+
///////////////////////// VALIDATE MODEL OPERAND TYPE /////////////////////////
static const uint32_t invalidOperandTypes[] = {
@@ -251,9 +502,239 @@
}
}
+///////////////////////// VALIDATE OPERAND LIFETIME /////////////////////////////////////////////
+
+static std::vector<OperandLifeTime> getInvalidLifeTimes(const Model& model, size_t modelSize,
+ const Operand& operand) {
+ // TODO: Support OperandLifeTime::CONSTANT_REFERENCE as an invalid lifetime
+ // TODO: Support OperandLifeTime::NO_VALUE as an invalid lifetime
+
+ // Ways to get an invalid lifetime:
+ // - change whether a lifetime means an operand should have a writer
+ std::vector<OperandLifeTime> ret;
+ switch (operand.lifetime) {
+ case OperandLifeTime::MODEL_OUTPUT:
+ case OperandLifeTime::TEMPORARY_VARIABLE:
+ ret = {
+ OperandLifeTime::MODEL_INPUT,
+ OperandLifeTime::CONSTANT_COPY,
+ };
+ break;
+ case OperandLifeTime::CONSTANT_COPY:
+ case OperandLifeTime::CONSTANT_REFERENCE:
+ case OperandLifeTime::MODEL_INPUT:
+ ret = {
+ OperandLifeTime::TEMPORARY_VARIABLE,
+ OperandLifeTime::MODEL_OUTPUT,
+ };
+ break;
+ case OperandLifeTime::NO_VALUE:
+ // Not enough information to know whether
+ // TEMPORARY_VARIABLE or CONSTANT_COPY would be invalid --
+ // is this operand written (then CONSTANT_COPY would be
+ // invalid) or not (then TEMPORARY_VARIABLE would be
+ // invalid)?
+ break;
+ default:
+ ADD_FAILURE();
+ break;
+ }
+
+ const size_t operandSize = sizeOfData(operand); // will be zero if shape is unknown
+ if (!operandSize ||
+ exceedsBinderSizeLimit(modelSize + constantCopyExtraSize(model, operandSize))) {
+ // Unknown size or too-large size
+ ret.erase(std::remove(ret.begin(), ret.end(), OperandLifeTime::CONSTANT_COPY), ret.end());
+ }
+
+ return ret;
+}
+
+static void mutateOperandLifeTimeTest(const sp<IDevice>& device, const Model& model) {
+ const size_t modelSize = sizeForBinder(model);
+ for (size_t operand = 0; operand < model.operands.size(); ++operand) {
+ const std::vector<OperandLifeTime> invalidLifeTimes =
+ getInvalidLifeTimes(model, modelSize, model.operands[operand]);
+ for (OperandLifeTime invalidLifeTime : invalidLifeTimes) {
+ const std::string message = "mutateOperandLifetimeTest: operand " +
+ std::to_string(operand) + " has lifetime " +
+ toString(invalidLifeTime) + " instead of lifetime " +
+ toString(model.operands[operand].lifetime);
+ validate(device, message, model,
+ [operand, invalidLifeTime](Model* model, ExecutionPreference*) {
+ static const DataLocation kZeroDataLocation = {};
+ Operand& operandObj = model->operands[operand];
+ switch (operandObj.lifetime) {
+ case OperandLifeTime::MODEL_INPUT: {
+ hidl_vec_remove(&model->inputIndexes, uint32_t(operand));
+ break;
+ }
+ case OperandLifeTime::MODEL_OUTPUT: {
+ hidl_vec_remove(&model->outputIndexes, uint32_t(operand));
+ break;
+ }
+ default:
+ break;
+ }
+ operandObj.lifetime = invalidLifeTime;
+ operandObj.location = kZeroDataLocation;
+ switch (invalidLifeTime) {
+ case OperandLifeTime::CONSTANT_COPY: {
+ becomeConstantCopy(model, &operandObj);
+ break;
+ }
+ case OperandLifeTime::MODEL_INPUT:
+ hidl_vec_push_back(&model->inputIndexes, uint32_t(operand));
+ break;
+ case OperandLifeTime::MODEL_OUTPUT:
+ hidl_vec_push_back(&model->outputIndexes, uint32_t(operand));
+ break;
+ default:
+ break;
+ }
+ });
+ }
+ }
+}
+
+///////////////////////// VALIDATE OPERAND INPUT-or-OUTPUT //////////////////////////////////////
+
+static std::optional<OperandLifeTime> getInputOutputLifeTime(const Model& model, size_t modelSize,
+ const Operand& operand) {
+ // Ways to get an invalid lifetime (with respect to model inputIndexes and outputIndexes):
+ // - change whether a lifetime means an operand is a model input, a model output, or neither
+ // - preserve whether or not a lifetime means an operand should have a writer
+ switch (operand.lifetime) {
+ case OperandLifeTime::CONSTANT_COPY:
+ case OperandLifeTime::CONSTANT_REFERENCE:
+ return OperandLifeTime::MODEL_INPUT;
+ case OperandLifeTime::MODEL_INPUT: {
+ const size_t operandSize = sizeOfData(operand); // will be zero if shape is unknown
+ if (!operandSize ||
+ exceedsBinderSizeLimit(modelSize + constantCopyExtraSize(model, operandSize))) {
+ // Unknown size or too-large size
+ break;
+ }
+ return OperandLifeTime::CONSTANT_COPY;
+ }
+ case OperandLifeTime::MODEL_OUTPUT:
+ return OperandLifeTime::TEMPORARY_VARIABLE;
+ case OperandLifeTime::TEMPORARY_VARIABLE:
+ return OperandLifeTime::MODEL_OUTPUT;
+ case OperandLifeTime::NO_VALUE:
+ // Not enough information to know whether
+ // TEMPORARY_VARIABLE or CONSTANT_COPY would be an
+ // appropriate choice -- is this operand written (then
+ // TEMPORARY_VARIABLE would be appropriate) or not (then
+ // CONSTANT_COPY would be appropriate)?
+ break;
+ default:
+ ADD_FAILURE();
+ break;
+ }
+
+ return std::nullopt;
+}
+
+static void mutateOperandInputOutputTest(const sp<IDevice>& device, const Model& model) {
+ const size_t modelSize = sizeForBinder(model);
+ for (size_t operand = 0; operand < model.operands.size(); ++operand) {
+ const std::optional<OperandLifeTime> changedLifeTime =
+ getInputOutputLifeTime(model, modelSize, model.operands[operand]);
+ if (changedLifeTime) {
+ const std::string message = "mutateOperandInputOutputTest: operand " +
+ std::to_string(operand) + " has lifetime " +
+ toString(*changedLifeTime) + " instead of lifetime " +
+ toString(model.operands[operand].lifetime);
+ validate(device, message, model,
+ [operand, changedLifeTime](Model* model, ExecutionPreference*) {
+ static const DataLocation kZeroDataLocation = {};
+ Operand& operandObj = model->operands[operand];
+ operandObj.lifetime = *changedLifeTime;
+ operandObj.location = kZeroDataLocation;
+ if (*changedLifeTime == OperandLifeTime::CONSTANT_COPY) {
+ becomeConstantCopy(model, &operandObj);
+ }
+ });
+ }
+ }
+}
+
+///////////////////////// VALIDATE OPERAND NUMBER OF CONSUMERS //////////////////////////////////
+
+static std::vector<uint32_t> getInvalidNumberOfConsumers(uint32_t numberOfConsumers) {
+ if (numberOfConsumers == 0) {
+ return {1};
+ } else {
+ return {numberOfConsumers - 1, numberOfConsumers + 1};
+ }
+}
+
+static void mutateOperandNumberOfConsumersTest(const sp<IDevice>& device, const Model& model) {
+ for (size_t operand = 0; operand < model.operands.size(); ++operand) {
+ const std::vector<uint32_t> invalidNumberOfConsumersVec =
+ getInvalidNumberOfConsumers(model.operands[operand].numberOfConsumers);
+ for (uint32_t invalidNumberOfConsumers : invalidNumberOfConsumersVec) {
+ const std::string message =
+ "mutateOperandNumberOfConsumersTest: operand " + std::to_string(operand) +
+ " numberOfConsumers = " + std::to_string(invalidNumberOfConsumers);
+ validate(device, message, model,
+ [operand, invalidNumberOfConsumers](Model* model, ExecutionPreference*) {
+ model->operands[operand].numberOfConsumers = invalidNumberOfConsumers;
+ });
+ }
+ }
+}
+
+///////////////////////// VALIDATE OPERAND NUMBER OF WRITERS ////////////////////////////////////
+
+static void mutateOperandAddWriterTest(const sp<IDevice>& device, const Model& model) {
+ for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+ for (size_t badOutputNum = 0; badOutputNum < model.operations[operation].outputs.size();
+ ++badOutputNum) {
+ const uint32_t outputOperandIndex = model.operations[operation].outputs[badOutputNum];
+ const std::string message = "mutateOperandAddWriterTest: operation " +
+ std::to_string(operation) + " writes to " +
+ std::to_string(outputOperandIndex);
+ // We'll insert a copy of the operation, all of whose
+ // OTHER output operands are newly-created -- i.e.,
+ // there'll only be a duplicate write of ONE of that
+ // operation's output operands.
+ validate(device, message, model,
+ [operation, badOutputNum](Model* model, ExecutionPreference*) {
+ Operation newOperation = model->operations[operation];
+ for (uint32_t input : newOperation.inputs) {
+ ++model->operands[input].numberOfConsumers;
+ }
+ for (size_t outputNum = 0; outputNum < newOperation.outputs.size();
+ ++outputNum) {
+ if (outputNum == badOutputNum) continue;
+
+ Operand operandValue =
+ model->operands[newOperation.outputs[outputNum]];
+ operandValue.numberOfConsumers = 0;
+ if (operandValue.lifetime == OperandLifeTime::MODEL_OUTPUT) {
+ operandValue.lifetime = OperandLifeTime::TEMPORARY_VARIABLE;
+ } else {
+ ASSERT_EQ(operandValue.lifetime,
+ OperandLifeTime::TEMPORARY_VARIABLE);
+ }
+ newOperation.outputs[outputNum] =
+ hidl_vec_push_back(&model->operands, operandValue);
+ }
+ // Where do we insert the extra writer (a new
+ // operation)? It has to be later than all the
+ // writers of its inputs. The easiest thing to do
+ // is to insert it at the end of the operation
+ // sequence.
+ hidl_vec_push_back(&model->operations, newOperation);
+ });
+ }
+ }
+}
+
///////////////////////// VALIDATE EXTRA ??? /////////////////////////
-// TODO: Operand::lifetime
// TODO: Operand::location
///////////////////////// VALIDATE OPERATION OPERAND TYPE /////////////////////////
@@ -461,6 +942,37 @@
}
}
+///////////////////////// VALIDATE MODEL OPERANDS WRITTEN ///////////////////////////////////////
+
+static void mutateOperationRemoveWriteTest(const sp<IDevice>& device, const Model& model) {
+ for (size_t operation = 0; operation < model.operations.size(); ++operation) {
+ for (size_t outputNum = 0; outputNum < model.operations[operation].outputs.size();
+ ++outputNum) {
+ const uint32_t outputOperandIndex = model.operations[operation].outputs[outputNum];
+ if (model.operands[outputOperandIndex].numberOfConsumers > 0) {
+ const std::string message = "mutateOperationRemoveWriteTest: operation " +
+ std::to_string(operation) + " writes to " +
+ std::to_string(outputOperandIndex);
+ validate(device, message, model,
+ [operation, outputNum](Model* model, ExecutionPreference*) {
+ uint32_t& outputOperandIndex =
+ model->operations[operation].outputs[outputNum];
+ Operand operandValue = model->operands[outputOperandIndex];
+ operandValue.numberOfConsumers = 0;
+ if (operandValue.lifetime == OperandLifeTime::MODEL_OUTPUT) {
+ operandValue.lifetime = OperandLifeTime::TEMPORARY_VARIABLE;
+ } else {
+ ASSERT_EQ(operandValue.lifetime,
+ OperandLifeTime::TEMPORARY_VARIABLE);
+ }
+ outputOperandIndex =
+ hidl_vec_push_back(&model->operands, operandValue);
+ });
+ }
+ }
+ }
+}
+
///////////////////////// REMOVE OPERAND FROM EVERYTHING /////////////////////////
static void removeValueAndDecrementGreaterValues(hidl_vec<uint32_t>* vec, uint32_t value) {
@@ -711,14 +1223,20 @@
////////////////////////// ENTRY POINT //////////////////////////////
void validateModel(const sp<IDevice>& device, const Model& model) {
+ mutateExecutionOrderTest(device, model);
mutateOperandTypeTest(device, model);
mutateOperandRankTest(device, model);
mutateOperandScaleTest(device, model);
mutateOperandZeroPointTest(device, model);
+ mutateOperandLifeTimeTest(device, model);
+ mutateOperandInputOutputTest(device, model);
+ mutateOperandNumberOfConsumersTest(device, model);
+ mutateOperandAddWriterTest(device, model);
mutateOperationOperandTypeTest(device, model);
mutateOperationTypeTest(device, model);
mutateOperationInputOperandIndexTest(device, model);
mutateOperationOutputOperandIndexTest(device, model);
+ mutateOperationRemoveWriteTest(device, model);
removeOperandTest(device, model);
removeOperationTest(device, model);
removeOperationInputTest(device, model);
diff --git a/neuralnetworks/1.2/vts/functional/include/1.2/Utils.h b/neuralnetworks/1.2/vts/functional/include/1.2/Utils.h
new file mode 100644
index 0000000..61a8d74
--- /dev/null
+++ b/neuralnetworks/1.2/vts/functional/include/1.2/Utils.h
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_HARDWARE_NEURALNETWORKS_V1_2_UTILS_H
+#define ANDROID_HARDWARE_NEURALNETWORKS_V1_2_UTILS_H
+
+#include <android/hardware/neuralnetworks/1.2/types.h>
+
+namespace android {
+namespace hardware {
+namespace neuralnetworks {
+
+// Returns the amount of space needed to store a value of the specified type.
+//
+// Aborts if the specified type is an extension type or OEM type.
+uint32_t sizeOfData(V1_2::OperandType type);
+
+// Returns the amount of space needed to store a value of the dimensions and
+// type of this operand. For a non-extension, non-OEM tensor with unspecified
+// rank or at least one unspecified dimension, returns zero.
+//
+// Aborts if the specified type is an extension type or OEM type.
+uint32_t sizeOfData(const V1_2::Operand& operand);
+
+} // namespace neuralnetworks
+} // namespace hardware
+} // namespace android
+
+#endif // ANDROID_HARDWARE_NEURALNETWORKS_V1_2_UTILS_H
diff --git a/neuralnetworks/1.3/vts/functional/Android.bp b/neuralnetworks/1.3/vts/functional/Android.bp
index 545a5be..457e36e 100644
--- a/neuralnetworks/1.3/vts/functional/Android.bp
+++ b/neuralnetworks/1.3/vts/functional/Android.bp
@@ -54,7 +54,7 @@
],
static_libs: [
"VtsHalNeuralNetworksV1_0_utils",
- "VtsHalNeuralNetworksV1_2Callbacks",
+ "VtsHalNeuralNetworksV1_2_utils",
"VtsHalNeuralNetworksV1_3_utils",
"android.hardware.neuralnetworks@1.0",
"android.hardware.neuralnetworks@1.1",
diff --git a/neuralnetworks/1.3/vts/functional/BasicTests.cpp b/neuralnetworks/1.3/vts/functional/BasicTests.cpp
index 1c25369..6fcfc34 100644
--- a/neuralnetworks/1.3/vts/functional/BasicTests.cpp
+++ b/neuralnetworks/1.3/vts/functional/BasicTests.cpp
@@ -20,11 +20,14 @@
namespace android::hardware::neuralnetworks::V1_3::vts::functional {
+using implementation::PreparedModelCallback;
using V1_0::DeviceStatus;
using V1_0::PerformanceInfo;
+using V1_1::ExecutionPreference;
using V1_2::Constant;
using V1_2::DeviceType;
using V1_2::Extension;
+using HidlToken = hidl_array<uint8_t, static_cast<uint32_t>(Constant::BYTE_SIZE_OF_CACHE_TOKEN)>;
// create device test
TEST_P(NeuralnetworksHidlTest, CreateDevice) {}
@@ -65,4 +68,143 @@
});
EXPECT_TRUE(ret.isOk());
}
+
+// detect cycle
+TEST_P(NeuralnetworksHidlTest, CycleTest) {
+ // opnd0 = TENSOR_FLOAT32 // model input
+ // opnd1 = TENSOR_FLOAT32 // model input
+ // opnd2 = INT32 // model input
+ // opnd3 = ADD(opnd0, opnd4, opnd2)
+ // opnd4 = ADD(opnd1, opnd3, opnd2)
+ // opnd5 = ADD(opnd4, opnd0, opnd2) // model output
+ //
+ // +-----+
+ // | |
+ // v |
+ // 3 = ADD(0, 4, 2) |
+ // | |
+ // +----------+ |
+ // | |
+ // v |
+ // 4 = ADD(1, 3, 2) |
+ // | |
+ // +----------------+
+ // |
+ // |
+ // +-------+
+ // |
+ // v
+ // 5 = ADD(4, 0, 2)
+
+ const std::vector<Operand> operands = {
+ {
+ // operands[0]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 2,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::SUBGRAPH_INPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[1]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 1,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::SUBGRAPH_INPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[2]
+ .type = OperandType::INT32,
+ .dimensions = {},
+ .numberOfConsumers = 3,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::SUBGRAPH_INPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[3]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 1,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::TEMPORARY_VARIABLE,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[4]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 2,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::TEMPORARY_VARIABLE,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ {
+ // operands[5]
+ .type = OperandType::TENSOR_FLOAT32,
+ .dimensions = {1},
+ .numberOfConsumers = 0,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::SUBGRAPH_OUTPUT,
+ .location = {.poolIndex = 0, .offset = 0, .length = 0},
+ },
+ };
+
+ const std::vector<Operation> operations = {
+ {.type = OperationType::ADD, .inputs = {0, 4, 2}, .outputs = {3}},
+ {.type = OperationType::ADD, .inputs = {1, 3, 2}, .outputs = {4}},
+ {.type = OperationType::ADD, .inputs = {4, 0, 2}, .outputs = {5}},
+ };
+
+ Subgraph subgraph = {
+ .operands = operands,
+ .operations = operations,
+ .inputIndexes = {0, 1, 2},
+ .outputIndexes = {5},
+ };
+ const Model model = {
+ .main = std::move(subgraph),
+ .referenced = {},
+ .operandValues = {},
+ .pools = {},
+ };
+
+ // ensure that getSupportedOperations_1_2() checks model validity
+ ErrorStatus supportedOpsErrorStatus = ErrorStatus::GENERAL_FAILURE;
+ Return<void> supportedOpsReturn = kDevice->getSupportedOperations_1_3(
+ model, [&model, &supportedOpsErrorStatus](ErrorStatus status,
+ const hidl_vec<bool>& supported) {
+ supportedOpsErrorStatus = status;
+ if (status == ErrorStatus::NONE) {
+ ASSERT_EQ(supported.size(), model.main.operations.size());
+ }
+ });
+ ASSERT_TRUE(supportedOpsReturn.isOk());
+ ASSERT_EQ(supportedOpsErrorStatus, ErrorStatus::INVALID_ARGUMENT);
+
+ // ensure that prepareModel_1_3() checks model validity
+ sp<PreparedModelCallback> preparedModelCallback = new PreparedModelCallback;
+ Return<ErrorStatus> prepareLaunchReturn = kDevice->prepareModel_1_3(
+ model, ExecutionPreference::FAST_SINGLE_ANSWER, Priority::MEDIUM, {},
+ hidl_vec<hidl_handle>(), hidl_vec<hidl_handle>(), HidlToken(), preparedModelCallback);
+ ASSERT_TRUE(prepareLaunchReturn.isOk());
+ // Note that preparation can fail for reasons other than an
+ // invalid model (invalid model should result in
+ // INVALID_ARGUMENT) -- for example, perhaps not all
+ // operations are supported, or perhaps the device hit some
+ // kind of capacity limit.
+ EXPECT_NE(prepareLaunchReturn, ErrorStatus::NONE);
+ EXPECT_NE(preparedModelCallback->getStatus(), ErrorStatus::NONE);
+ EXPECT_EQ(preparedModelCallback->getPreparedModel(), nullptr);
+}
+
} // namespace android::hardware::neuralnetworks::V1_3::vts::functional
diff --git a/neuralnetworks/1.3/vts/functional/Utils.cpp b/neuralnetworks/1.3/vts/functional/Utils.cpp
index 23e2af8..c460e11 100644
--- a/neuralnetworks/1.3/vts/functional/Utils.cpp
+++ b/neuralnetworks/1.3/vts/functional/Utils.cpp
@@ -17,11 +17,78 @@
#include "1.3/Utils.h"
#include <iostream>
+#include <numeric>
+#include "android-base/logging.h"
+#include "android/hardware/neuralnetworks/1.3/types.h"
-namespace android::hardware::neuralnetworks::V1_3 {
+namespace android::hardware::neuralnetworks {
+
+uint32_t sizeOfData(V1_3::OperandType type) {
+ switch (type) {
+ case V1_3::OperandType::FLOAT32:
+ case V1_3::OperandType::INT32:
+ case V1_3::OperandType::UINT32:
+ case V1_3::OperandType::TENSOR_FLOAT32:
+ case V1_3::OperandType::TENSOR_INT32:
+ return 4;
+ case V1_3::OperandType::TENSOR_QUANT16_SYMM:
+ case V1_3::OperandType::TENSOR_FLOAT16:
+ case V1_3::OperandType::FLOAT16:
+ case V1_3::OperandType::TENSOR_QUANT16_ASYMM:
+ return 2;
+ case V1_3::OperandType::TENSOR_QUANT8_ASYMM:
+ case V1_3::OperandType::BOOL:
+ case V1_3::OperandType::TENSOR_BOOL8:
+ case V1_3::OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL:
+ case V1_3::OperandType::TENSOR_QUANT8_SYMM:
+ case V1_3::OperandType::TENSOR_QUANT8_ASYMM_SIGNED:
+ return 1;
+ case V1_3::OperandType::SUBGRAPH:
+ return 0;
+ default:
+ CHECK(false) << "Invalid OperandType " << static_cast<uint32_t>(type);
+ return 0;
+ }
+}
+
+static bool isTensor(V1_3::OperandType type) {
+ switch (type) {
+ case V1_3::OperandType::FLOAT32:
+ case V1_3::OperandType::INT32:
+ case V1_3::OperandType::UINT32:
+ case V1_3::OperandType::FLOAT16:
+ case V1_3::OperandType::BOOL:
+ case V1_3::OperandType::SUBGRAPH:
+ return false;
+ case V1_3::OperandType::TENSOR_FLOAT32:
+ case V1_3::OperandType::TENSOR_INT32:
+ case V1_3::OperandType::TENSOR_QUANT16_SYMM:
+ case V1_3::OperandType::TENSOR_FLOAT16:
+ case V1_3::OperandType::TENSOR_QUANT16_ASYMM:
+ case V1_3::OperandType::TENSOR_QUANT8_ASYMM:
+ case V1_3::OperandType::TENSOR_BOOL8:
+ case V1_3::OperandType::TENSOR_QUANT8_SYMM_PER_CHANNEL:
+ case V1_3::OperandType::TENSOR_QUANT8_SYMM:
+ case V1_3::OperandType::TENSOR_QUANT8_ASYMM_SIGNED:
+ return true;
+ default:
+ CHECK(false) << "Invalid OperandType " << static_cast<uint32_t>(type);
+ return false;
+ }
+}
+
+uint32_t sizeOfData(const V1_3::Operand& operand) {
+ const uint32_t dataSize = sizeOfData(operand.type);
+ if (isTensor(operand.type) && operand.dimensions.size() == 0) return 0;
+ return std::accumulate(operand.dimensions.begin(), operand.dimensions.end(), dataSize,
+ std::multiplies<>{});
+}
+
+namespace V1_3 {
::std::ostream& operator<<(::std::ostream& os, ErrorStatus errorStatus) {
return os << toString(errorStatus);
}
-} // namespace android::hardware::neuralnetworks::V1_3
+} // namespace V1_3
+} // namespace android::hardware::neuralnetworks
diff --git a/neuralnetworks/1.3/vts/functional/ValidateModel.cpp b/neuralnetworks/1.3/vts/functional/ValidateModel.cpp
index e590fda..849ef7b 100644
--- a/neuralnetworks/1.3/vts/functional/ValidateModel.cpp
+++ b/neuralnetworks/1.3/vts/functional/ValidateModel.cpp
@@ -16,15 +16,22 @@
#define LOG_TAG "neuralnetworks_hidl_hal_test"
+#include <android/hardware/neuralnetworks/1.1/types.h>
+#include <android/hardware/neuralnetworks/1.3/types.h>
#include "1.0/Utils.h"
#include "1.3/Callbacks.h"
#include "1.3/Utils.h"
#include "GeneratedTestHarness.h"
#include "VtsHalNeuralnetworks.h"
+#include <optional>
+#include <type_traits>
+#include <utility>
+
namespace android::hardware::neuralnetworks::V1_3::vts::functional {
using implementation::PreparedModelCallback;
+using V1_0::DataLocation;
using V1_1::ExecutionPreference;
using V1_2::SymmPerChannelQuantParams;
using HidlToken =
@@ -112,6 +119,262 @@
return index;
}
+// If we introduce a CONSTANT_COPY for an operand of size operandSize,
+// how much will this increase the size of the model? This assumes
+// that we can (re)use all of model.operandValues for the operand
+// value.
+static size_t constantCopyExtraSize(const Model& model, size_t operandSize) {
+ const size_t operandValuesSize = model.operandValues.size();
+ return (operandValuesSize < operandSize) ? (operandSize - operandValuesSize) : 0;
+}
+
+// Highly specialized utility routine for converting an operand to
+// CONSTANT_COPY lifetime.
+//
+// Expects that:
+// - operand has a known size
+// - operand->lifetime has already been set to CONSTANT_COPY
+// - operand->location has been zeroed out
+//
+// Does the following:
+// - initializes operand->location to point to the beginning of model->operandValues
+// - resizes model->operandValues (if necessary) to be large enough for the operand
+// value, padding it with zeroes on the end
+//
+// Potential problem:
+// By changing the operand to CONSTANT_COPY lifetime, this function is effectively initializing the
+// operand with unspecified (but deterministic) data. This means that the model may be invalidated
+// in two ways: not only is the lifetime of CONSTANT_COPY invalid, but the operand's value in the
+// graph may also be invalid (e.g., if the operand is used as an activation code and has an invalid
+// value). For now, this should be fine because it just means we're not testing what we think we're
+// testing in certain cases; but we can handwave this and assume we're probabilistically likely to
+// exercise the validation code over the span of the entire test set and operand space.
+//
+// Aborts if the specified operand type is an extension type or OEM type.
+static void becomeConstantCopy(Model* model, Operand* operand) {
+ // sizeOfData will abort if the specified type is an extension type or OEM type.
+ const size_t sizeOfOperand = sizeOfData(*operand);
+ EXPECT_NE(sizeOfOperand, size_t(0));
+ operand->location.poolIndex = 0;
+ operand->location.offset = 0;
+ operand->location.length = sizeOfOperand;
+ if (model->operandValues.size() < sizeOfOperand) {
+ model->operandValues.resize(sizeOfOperand);
+ }
+}
+
+// The sizeForBinder() functions estimate the size of the
+// representation of a value when sent to binder. It's probably a bit
+// of an under-estimate, because we don't know the size of the
+// metadata in the binder format (e.g., representation of the size of
+// a vector); but at least it adds up "big" things like vector
+// contents. However, it doesn't treat inter-field or end-of-struct
+// padding in a methodical way -- there's no attempt to be consistent
+// in whether or not padding in the native (C++) representation
+// contributes to the estimated size for the binder representation;
+// and there's no attempt to understand what padding (if any) is
+// needed in the binder representation.
+//
+// This assumes that non-metadata uses a fixed length encoding (e.g.,
+// a uint32_t is always encoded in sizeof(uint32_t) bytes, rather than
+// using an encoding whose length is related to the magnitude of the
+// encoded value).
+
+template <typename Type>
+static size_t sizeForBinder(const Type& val) {
+ static_assert(std::is_trivially_copyable_v<std::remove_reference_t<Type>>,
+ "expected a trivially copyable type");
+ return sizeof(val);
+}
+
+template <typename Type>
+static size_t sizeForBinder(const hidl_vec<Type>& vec) {
+ return std::accumulate(vec.begin(), vec.end(), 0,
+ [](size_t acc, const Type& x) { return acc + sizeForBinder(x); });
+}
+
+template <>
+size_t sizeForBinder(const SymmPerChannelQuantParams& symmPerChannelQuantParams) {
+ size_t size = 0;
+
+ size += sizeForBinder(symmPerChannelQuantParams.scales);
+ size += sizeForBinder(symmPerChannelQuantParams.channelDim);
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const V1_2::Operand::ExtraParams& extraParams) {
+ using Discriminator = V1_2::Operand::ExtraParams::hidl_discriminator;
+ switch (extraParams.getDiscriminator()) {
+ case Discriminator::none:
+ return 0;
+ case Discriminator::channelQuant:
+ return sizeForBinder(extraParams.channelQuant());
+ case Discriminator::extension:
+ return sizeForBinder(extraParams.extension());
+ }
+ LOG(FATAL) << "Unrecognized extraParams enum: "
+ << static_cast<int>(extraParams.getDiscriminator());
+ return 0;
+}
+
+template <>
+size_t sizeForBinder(const Operand& operand) {
+ size_t size = 0;
+
+ size += sizeForBinder(operand.type);
+ size += sizeForBinder(operand.dimensions);
+ size += sizeForBinder(operand.numberOfConsumers);
+ size += sizeForBinder(operand.scale);
+ size += sizeForBinder(operand.zeroPoint);
+ size += sizeForBinder(operand.lifetime);
+ size += sizeForBinder(operand.location);
+ size += sizeForBinder(operand.extraParams);
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const Operation& operation) {
+ size_t size = 0;
+
+ size += sizeForBinder(operation.type);
+ size += sizeForBinder(operation.inputs);
+ size += sizeForBinder(operation.outputs);
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const hidl_string& name) {
+ return name.size();
+}
+
+template <>
+size_t sizeForBinder(const hidl_memory& memory) {
+ // This is just a guess.
+
+ size_t size = 0;
+
+ if (const native_handle_t* handle = memory.handle()) {
+ size += sizeof(*handle);
+ size += sizeof(handle->data[0] * (handle->numFds + handle->numInts));
+ }
+ size += sizeForBinder(memory.name());
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const Subgraph& subgraph) {
+ size_t size = 0;
+
+ size += sizeForBinder(subgraph.operands);
+ size += sizeForBinder(subgraph.operations);
+ size += sizeForBinder(subgraph.inputIndexes);
+ size += sizeForBinder(subgraph.outputIndexes);
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const V1_2::Model::ExtensionNameAndPrefix& extensionNameToPrefix) {
+ size_t size = 0;
+
+ size += sizeForBinder(extensionNameToPrefix.name);
+ size += sizeForBinder(extensionNameToPrefix.prefix);
+
+ return size;
+}
+
+template <>
+size_t sizeForBinder(const Model& model) {
+ size_t size = 0;
+
+ size += sizeForBinder(model.main);
+ size += sizeForBinder(model.referenced);
+ size += sizeForBinder(model.operandValues);
+ size += sizeForBinder(model.pools);
+ size += sizeForBinder(model.relaxComputationFloat32toFloat16);
+ size += sizeForBinder(model.extensionNameToPrefix);
+
+ return size;
+}
+
+// https://developer.android.com/reference/android/os/TransactionTooLargeException.html
+//
+// "The Binder transaction buffer has a limited fixed size,
+// currently 1Mb, which is shared by all transactions in progress
+// for the process."
+//
+// Will our representation fit under this limit? There are two complications:
+// - Our representation size is just approximate (see sizeForBinder()).
+// - This object may not be the only occupant of the Binder transaction buffer.
+// So we'll be very conservative: We want the representation size to be no
+// larger than half the transaction buffer size.
+//
+// If our representation grows large enough that it still fits within
+// the transaction buffer but combined with other transactions may
+// exceed the buffer size, then we may see intermittent HAL transport
+// errors.
+static bool exceedsBinderSizeLimit(size_t representationSize) {
+ // Instead of using this fixed buffer size, we might instead be able to use
+ // ProcessState::self()->getMmapSize(). However, this has a potential
+ // problem: The binder/mmap size of the current process does not necessarily
+ // indicate the binder/mmap size of the service (i.e., the other process).
+ // The only way it would be a good indication is if both the current process
+ // and the service use the default size.
+ static const size_t kHalfBufferSize = 1024 * 1024 / 2;
+
+ return representationSize > kHalfBufferSize;
+}
+
+///////////////////////// VALIDATE EXECUTION ORDER ////////////////////////////
+
+static void mutateExecutionOrderTest(const sp<IDevice>& device, const Model& model) {
+ for (size_t operation = 0; operation < model.main.operations.size(); ++operation) {
+ const Operation& operationObj = model.main.operations[operation];
+ for (uint32_t input : operationObj.inputs) {
+ if (model.main.operands[input].lifetime == OperandLifeTime::TEMPORARY_VARIABLE ||
+ model.main.operands[input].lifetime == OperandLifeTime::SUBGRAPH_OUTPUT) {
+ // This operation reads an operand written by some
+ // other operation. Move this operation to the
+ // beginning of the sequence, ensuring that it reads
+ // the operand before that operand is written, thereby
+ // violating execution order rules.
+ const std::string message = "mutateExecutionOrderTest: operation " +
+ std::to_string(operation) + " is a reader";
+ validate(device, message, model,
+ [operation](Model* model, ExecutionPreference*, Priority*) {
+ auto& operations = model->main.operations;
+ std::rotate(operations.begin(), operations.begin() + operation,
+ operations.begin() + operation + 1);
+ });
+ break; // only need to do this once per operation
+ }
+ }
+ for (uint32_t output : operationObj.outputs) {
+ if (model.main.operands[output].numberOfConsumers > 0) {
+ // This operation writes an operand read by some other
+ // operation. Move this operation to the end of the
+ // sequence, ensuring that it writes the operand after
+ // that operand is read, thereby violating execution
+ // order rules.
+ const std::string message = "mutateExecutionOrderTest: operation " +
+ std::to_string(operation) + " is a writer";
+ validate(device, message, model,
+ [operation](Model* model, ExecutionPreference*, Priority*) {
+ auto& operations = model->main.operations;
+ std::rotate(operations.begin() + operation,
+ operations.begin() + operation + 1, operations.end());
+ });
+ break; // only need to do this once per operation
+ }
+ }
+ }
+}
+
///////////////////////// VALIDATE MODEL OPERAND TYPE /////////////////////////
static const uint32_t invalidOperandTypes[] = {
@@ -261,9 +524,245 @@
}
}
+///////////////////////// VALIDATE OPERAND LIFETIME /////////////////////////////////////////////
+
+static std::vector<OperandLifeTime> getInvalidLifeTimes(const Model& model, size_t modelSize,
+ const Operand& operand) {
+ // TODO: Support OperandLifeTime::CONSTANT_REFERENCE as an invalid lifetime
+ // TODO: Support OperandLifeTime::NO_VALUE as an invalid lifetime
+
+ // Ways to get an invalid lifetime:
+ // - change whether a lifetime means an operand should have a writer
+ std::vector<OperandLifeTime> ret;
+ switch (operand.lifetime) {
+ case OperandLifeTime::SUBGRAPH_OUTPUT:
+ case OperandLifeTime::TEMPORARY_VARIABLE:
+ ret = {
+ OperandLifeTime::SUBGRAPH_INPUT,
+ OperandLifeTime::CONSTANT_COPY,
+ };
+ break;
+ case OperandLifeTime::CONSTANT_COPY:
+ case OperandLifeTime::CONSTANT_REFERENCE:
+ case OperandLifeTime::SUBGRAPH_INPUT:
+ ret = {
+ OperandLifeTime::TEMPORARY_VARIABLE,
+ OperandLifeTime::SUBGRAPH_OUTPUT,
+ };
+ break;
+ case OperandLifeTime::NO_VALUE:
+ // Not enough information to know whether
+ // TEMPORARY_VARIABLE or CONSTANT_COPY would be invalid --
+ // is this operand written (then CONSTANT_COPY would be
+ // invalid) or not (then TEMPORARY_VARIABLE would be
+ // invalid)?
+ break;
+ case OperandLifeTime::SUBGRAPH:
+ break;
+ default:
+ ADD_FAILURE();
+ break;
+ }
+
+ const size_t operandSize = sizeOfData(operand); // will be zero if shape is unknown
+ if (!operandSize ||
+ exceedsBinderSizeLimit(modelSize + constantCopyExtraSize(model, operandSize))) {
+ // Unknown size or too-large size
+ ret.erase(std::remove(ret.begin(), ret.end(), OperandLifeTime::CONSTANT_COPY), ret.end());
+ }
+
+ return ret;
+}
+
+static void mutateOperandLifeTimeTest(const sp<IDevice>& device, const Model& model) {
+ const size_t modelSize = sizeForBinder(model);
+ for (size_t operand = 0; operand < model.main.operands.size(); ++operand) {
+ const std::vector<OperandLifeTime> invalidLifeTimes =
+ getInvalidLifeTimes(model, modelSize, model.main.operands[operand]);
+ for (OperandLifeTime invalidLifeTime : invalidLifeTimes) {
+ const std::string message = "mutateOperandLifetimeTest: operand " +
+ std::to_string(operand) + " has lifetime " +
+ toString(invalidLifeTime) + " instead of lifetime " +
+ toString(model.main.operands[operand].lifetime);
+ validate(device, message, model,
+ [operand, invalidLifeTime](Model* model, ExecutionPreference*, Priority*) {
+ static const DataLocation kZeroDataLocation = {};
+ Operand& operandObj = model->main.operands[operand];
+ switch (operandObj.lifetime) {
+ case OperandLifeTime::SUBGRAPH_INPUT: {
+ hidl_vec_remove(&model->main.inputIndexes, uint32_t(operand));
+ break;
+ }
+ case OperandLifeTime::SUBGRAPH_OUTPUT: {
+ hidl_vec_remove(&model->main.outputIndexes, uint32_t(operand));
+ break;
+ }
+ default:
+ break;
+ }
+ operandObj.lifetime = invalidLifeTime;
+ operandObj.location = kZeroDataLocation;
+ switch (invalidLifeTime) {
+ case OperandLifeTime::CONSTANT_COPY: {
+ becomeConstantCopy(model, &operandObj);
+ break;
+ }
+ case OperandLifeTime::SUBGRAPH_INPUT:
+ hidl_vec_push_back(&model->main.inputIndexes, uint32_t(operand));
+ break;
+ case OperandLifeTime::SUBGRAPH_OUTPUT:
+ hidl_vec_push_back(&model->main.outputIndexes, uint32_t(operand));
+ break;
+ default:
+ break;
+ }
+ });
+ }
+ }
+}
+
+///////////////////////// VALIDATE OPERAND INPUT-or-OUTPUT //////////////////////////////////////
+
+static std::optional<OperandLifeTime> getInputOutputLifeTime(const Model& model, size_t modelSize,
+ const Operand& operand) {
+ // Ways to get an invalid lifetime (with respect to model inputIndexes and outputIndexes):
+ // - change whether a lifetime means an operand is a model input, a model output, or neither
+ // - preserve whether or not a lifetime means an operand should have a writer
+ switch (operand.lifetime) {
+ case OperandLifeTime::CONSTANT_COPY:
+ case OperandLifeTime::CONSTANT_REFERENCE:
+ return OperandLifeTime::SUBGRAPH_INPUT;
+ case OperandLifeTime::SUBGRAPH_INPUT: {
+ const size_t operandSize = sizeOfData(operand); // will be zero if shape is unknown
+ if (!operandSize ||
+ exceedsBinderSizeLimit(modelSize + constantCopyExtraSize(model, operandSize))) {
+ // Unknown size or too-large size
+ break;
+ }
+ return OperandLifeTime::CONSTANT_COPY;
+ }
+ case OperandLifeTime::SUBGRAPH_OUTPUT:
+ return OperandLifeTime::TEMPORARY_VARIABLE;
+ case OperandLifeTime::TEMPORARY_VARIABLE:
+ return OperandLifeTime::SUBGRAPH_OUTPUT;
+ case OperandLifeTime::NO_VALUE:
+ // Not enough information to know whether
+ // TEMPORARY_VARIABLE or CONSTANT_COPY would be an
+ // appropriate choice -- is this operand written (then
+ // TEMPORARY_VARIABLE would be appropriate) or not (then
+ // CONSTANT_COPY would be appropriate)?
+ break;
+ case OperandLifeTime::SUBGRAPH:
+ break;
+ default:
+ ADD_FAILURE();
+ break;
+ }
+
+ return std::nullopt;
+}
+
+static void mutateOperandInputOutputTest(const sp<IDevice>& device, const Model& model) {
+ const size_t modelSize = sizeForBinder(model);
+ for (size_t operand = 0; operand < model.main.operands.size(); ++operand) {
+ const std::optional<OperandLifeTime> changedLifeTime =
+ getInputOutputLifeTime(model, modelSize, model.main.operands[operand]);
+ if (changedLifeTime) {
+ const std::string message = "mutateOperandInputOutputTest: operand " +
+ std::to_string(operand) + " has lifetime " +
+ toString(*changedLifeTime) + " instead of lifetime " +
+ toString(model.main.operands[operand].lifetime);
+ validate(device, message, model,
+ [operand, changedLifeTime](Model* model, ExecutionPreference*, Priority*) {
+ static const DataLocation kZeroDataLocation = {};
+ Operand& operandObj = model->main.operands[operand];
+ operandObj.lifetime = *changedLifeTime;
+ operandObj.location = kZeroDataLocation;
+ if (*changedLifeTime == OperandLifeTime::CONSTANT_COPY) {
+ becomeConstantCopy(model, &operandObj);
+ }
+ });
+ }
+ }
+}
+
+///////////////////////// VALIDATE OPERAND NUMBER OF CONSUMERS //////////////////////////////////
+
+static std::vector<uint32_t> getInvalidNumberOfConsumers(uint32_t numberOfConsumers) {
+ if (numberOfConsumers == 0) {
+ return {1};
+ } else {
+ return {numberOfConsumers - 1, numberOfConsumers + 1};
+ }
+}
+
+static void mutateOperandNumberOfConsumersTest(const sp<IDevice>& device, const Model& model) {
+ for (size_t operand = 0; operand < model.main.operands.size(); ++operand) {
+ const std::vector<uint32_t> invalidNumberOfConsumersVec =
+ getInvalidNumberOfConsumers(model.main.operands[operand].numberOfConsumers);
+ for (uint32_t invalidNumberOfConsumers : invalidNumberOfConsumersVec) {
+ const std::string message =
+ "mutateOperandNumberOfConsumersTest: operand " + std::to_string(operand) +
+ " numberOfConsumers = " + std::to_string(invalidNumberOfConsumers);
+ validate(device, message, model,
+ [operand, invalidNumberOfConsumers](Model* model, ExecutionPreference*,
+ Priority*) {
+ model->main.operands[operand].numberOfConsumers = invalidNumberOfConsumers;
+ });
+ }
+ }
+}
+
+///////////////////////// VALIDATE OPERAND NUMBER OF WRITERS ////////////////////////////////////
+
+static void mutateOperandAddWriterTest(const sp<IDevice>& device, const Model& model) {
+ for (size_t operation = 0; operation < model.main.operations.size(); ++operation) {
+ for (size_t badOutputNum = 0;
+ badOutputNum < model.main.operations[operation].outputs.size(); ++badOutputNum) {
+ const uint32_t outputOperandIndex =
+ model.main.operations[operation].outputs[badOutputNum];
+ const std::string message = "mutateOperandAddWriterTest: operation " +
+ std::to_string(operation) + " writes to " +
+ std::to_string(outputOperandIndex);
+ // We'll insert a copy of the operation, all of whose
+ // OTHER output operands are newly-created -- i.e.,
+ // there'll only be a duplicate write of ONE of that
+ // operation's output operands.
+ validate(device, message, model,
+ [operation, badOutputNum](Model* model, ExecutionPreference*, Priority*) {
+ Operation newOperation = model->main.operations[operation];
+ for (uint32_t input : newOperation.inputs) {
+ ++model->main.operands[input].numberOfConsumers;
+ }
+ for (size_t outputNum = 0; outputNum < newOperation.outputs.size();
+ ++outputNum) {
+ if (outputNum == badOutputNum) continue;
+
+ Operand operandValue =
+ model->main.operands[newOperation.outputs[outputNum]];
+ operandValue.numberOfConsumers = 0;
+ if (operandValue.lifetime == OperandLifeTime::SUBGRAPH_OUTPUT) {
+ operandValue.lifetime = OperandLifeTime::TEMPORARY_VARIABLE;
+ } else {
+ ASSERT_EQ(operandValue.lifetime,
+ OperandLifeTime::TEMPORARY_VARIABLE);
+ }
+ newOperation.outputs[outputNum] =
+ hidl_vec_push_back(&model->main.operands, operandValue);
+ }
+ // Where do we insert the extra writer (a new
+ // operation)? It has to be later than all the
+ // writers of its inputs. The easiest thing to do
+ // is to insert it at the end of the operation
+ // sequence.
+ hidl_vec_push_back(&model->main.operations, newOperation);
+ });
+ }
+ }
+}
+
///////////////////////// VALIDATE EXTRA ??? /////////////////////////
-// TODO: Operand::lifetime
// TODO: Operand::location
///////////////////////// VALIDATE OPERATION OPERAND TYPE /////////////////////////
@@ -511,6 +1010,37 @@
}
}
+///////////////////////// VALIDATE MODEL OPERANDS WRITTEN ///////////////////////////////////////
+
+static void mutateOperationRemoveWriteTest(const sp<IDevice>& device, const Model& model) {
+ for (size_t operation = 0; operation < model.main.operations.size(); ++operation) {
+ for (size_t outputNum = 0; outputNum < model.main.operations[operation].outputs.size();
+ ++outputNum) {
+ const uint32_t outputOperandIndex = model.main.operations[operation].outputs[outputNum];
+ if (model.main.operands[outputOperandIndex].numberOfConsumers > 0) {
+ const std::string message = "mutateOperationRemoveWriteTest: operation " +
+ std::to_string(operation) + " writes to " +
+ std::to_string(outputOperandIndex);
+ validate(device, message, model,
+ [operation, outputNum](Model* model, ExecutionPreference*, Priority*) {
+ uint32_t& outputOperandIndex =
+ model->main.operations[operation].outputs[outputNum];
+ Operand operandValue = model->main.operands[outputOperandIndex];
+ operandValue.numberOfConsumers = 0;
+ if (operandValue.lifetime == OperandLifeTime::SUBGRAPH_OUTPUT) {
+ operandValue.lifetime = OperandLifeTime::TEMPORARY_VARIABLE;
+ } else {
+ ASSERT_EQ(operandValue.lifetime,
+ OperandLifeTime::TEMPORARY_VARIABLE);
+ }
+ outputOperandIndex =
+ hidl_vec_push_back(&model->main.operands, operandValue);
+ });
+ }
+ }
+ }
+}
+
///////////////////////// REMOVE OPERAND FROM EVERYTHING /////////////////////////
static void removeValueAndDecrementGreaterValues(hidl_vec<uint32_t>* vec, uint32_t value) {
@@ -804,14 +1334,20 @@
////////////////////////// ENTRY POINT //////////////////////////////
void validateModel(const sp<IDevice>& device, const Model& model) {
+ mutateExecutionOrderTest(device, model);
mutateOperandTypeTest(device, model);
mutateOperandRankTest(device, model);
mutateOperandScaleTest(device, model);
mutateOperandZeroPointTest(device, model);
+ mutateOperandLifeTimeTest(device, model);
+ mutateOperandInputOutputTest(device, model);
+ mutateOperandNumberOfConsumersTest(device, model);
+ mutateOperandAddWriterTest(device, model);
mutateOperationOperandTypeTest(device, model);
mutateOperationTypeTest(device, model);
mutateOperationInputOperandIndexTest(device, model);
mutateOperationOutputOperandIndexTest(device, model);
+ mutateOperationRemoveWriteTest(device, model);
removeOperandTest(device, model);
removeOperationTest(device, model);
removeOperationInputTest(device, model);
diff --git a/neuralnetworks/1.3/vts/functional/include/1.3/Utils.h b/neuralnetworks/1.3/vts/functional/include/1.3/Utils.h
index 3661b66..e07e73b 100644
--- a/neuralnetworks/1.3/vts/functional/include/1.3/Utils.h
+++ b/neuralnetworks/1.3/vts/functional/include/1.3/Utils.h
@@ -24,6 +24,18 @@
inline constexpr V1_3::Priority kDefaultPriority = V1_3::Priority::MEDIUM;
+// Returns the amount of space needed to store a value of the specified type.
+//
+// Aborts if the specified type is an extension type or OEM type.
+uint32_t sizeOfData(V1_3::OperandType type);
+
+// Returns the amount of space needed to store a value of the dimensions and
+// type of this operand. For a non-extension, non-OEM tensor with unspecified
+// rank or at least one unspecified dimension, returns zero.
+//
+// Aborts if the specified type is an extension type or OEM type.
+uint32_t sizeOfData(const V1_3::Operand& operand);
+
} // namespace android::hardware::neuralnetworks
namespace android::hardware::neuralnetworks::V1_3 {
diff --git a/sensors/2.0/multihal/HalProxy.cpp b/sensors/2.0/multihal/HalProxy.cpp
index ac6f17a..fbff24c 100644
--- a/sensors/2.0/multihal/HalProxy.cpp
+++ b/sensors/2.0/multihal/HalProxy.cpp
@@ -360,7 +360,7 @@
} else {
std::string subHalLibraryFile;
while (subHalConfigStream >> subHalLibraryFile) {
- void* handle = dlopen(subHalLibraryFile.c_str(), RTLD_NOW);
+ void* handle = getHandleForSubHalSharedObject(subHalLibraryFile);
if (handle == nullptr) {
ALOGE("dlopen failed for library: %s", subHalLibraryFile.c_str());
} else {
@@ -415,6 +415,25 @@
}
}
+void* HalProxy::getHandleForSubHalSharedObject(const std::string& filename) {
+ static const std::string kSubHalShareObjectLocations[] = {
+ "", // Default locations will be searched
+#ifdef __LP64__
+ "/vendor/lib64/hw/", "/odm/lib64/", "/odm/lib64/hw/"
+#else
+ "/vendor/lib/hw/", "/odm/lib/", "/odm/lib/hw/"
+#endif
+ };
+
+ for (const std::string& dir : kSubHalShareObjectLocations) {
+ void* handle = dlopen((dir + filename).c_str(), RTLD_NOW);
+ if (handle != nullptr) {
+ return handle;
+ }
+ }
+ return nullptr;
+}
+
void HalProxy::init() {
initializeSubHalCallbacks();
initializeSensorList();
diff --git a/sensors/2.0/multihal/include/HalProxy.h b/sensors/2.0/multihal/include/HalProxy.h
index 978f7cf..10bba55 100644
--- a/sensors/2.0/multihal/include/HalProxy.h
+++ b/sensors/2.0/multihal/include/HalProxy.h
@@ -262,6 +262,16 @@
void initializeSensorList();
/**
+ * Try using the default include directories as well as the directories defined in
+ * kSubHalShareObjectLocations to get a handle for dlsym for a subhal.
+ *
+ * @param filename The file name to search for.
+ *
+ * @return The handle or nullptr if search failed.
+ */
+ void* getHandleForSubHalSharedObject(const std::string& filename);
+
+ /**
* Calls the helper methods that all ctors use.
*/
void init();
diff --git a/sensors/2.0/vts/functional/Android.bp b/sensors/2.0/vts/functional/Android.bp
index 4765fa2..7755ef5 100644
--- a/sensors/2.0/vts/functional/Android.bp
+++ b/sensors/2.0/vts/functional/Android.bp
@@ -33,5 +33,9 @@
"libfmq",
"VtsHalSensorsTargetTestUtils",
],
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
}
diff --git a/sensors/2.0/vts/functional/VtsHalSensorsV2_0TargetTest.cpp b/sensors/2.0/vts/functional/VtsHalSensorsV2_0TargetTest.cpp
index d0ebe4d..e46f034 100644
--- a/sensors/2.0/vts/functional/VtsHalSensorsV2_0TargetTest.cpp
+++ b/sensors/2.0/vts/functional/VtsHalSensorsV2_0TargetTest.cpp
@@ -320,11 +320,13 @@
<< s.sensorHandle << std::dec << " type=" << static_cast<int>(s.type)
<< " name=" << s.name);
- // Test non-empty type string
- EXPECT_FALSE(s.typeAsString.empty());
-
- // Test defined type matches defined string type
- EXPECT_NO_FATAL_FAILURE(assertTypeMatchStringType(s.type, s.typeAsString));
+ // Test type string non-empty only for private sensor types.
+ if (s.type >= SensorType::DEVICE_PRIVATE_BASE) {
+ EXPECT_FALSE(s.typeAsString.empty());
+ } else if (!s.typeAsString.empty()) {
+ // Test type string matches framework string if specified for non-private types.
+ EXPECT_NO_FATAL_FAILURE(assertTypeMatchStringType(s.type, s.typeAsString));
+ }
// Test if all sensor has name and vendor
EXPECT_FALSE(s.name.empty());