Merge "Add current compatibility matrix for R."
diff --git a/atrace/1.0/default/AtraceDevice.cpp b/atrace/1.0/default/AtraceDevice.cpp
index 35d11e9..4e82b0a 100644
--- a/atrace/1.0/default/AtraceDevice.cpp
+++ b/atrace/1.0/default/AtraceDevice.cpp
@@ -94,7 +94,7 @@
for (auto& c : kTracingMap) {
for (auto& p : c.second.paths) {
if (!android::base::WriteStringToFile("0", p.first)) {
- LOG(ERROR) << "Failed to enable tracing on: " << p.first;
+ LOG(ERROR) << "Failed to disable tracing on: " << p.first;
if (p.second) {
ret = Status::ERROR_TRACING_POINT;
}
diff --git a/compatibility_matrices/compatibility_matrix.4.xml b/compatibility_matrices/compatibility_matrix.4.xml
index af62cb6..3a2fd48 100644
--- a/compatibility_matrices/compatibility_matrix.4.xml
+++ b/compatibility_matrices/compatibility_matrix.4.xml
@@ -282,7 +282,7 @@
</hal>
<hal format="hidl" optional="true">
<name>android.hardware.nfc</name>
- <version>1.1</version>
+ <version>1.2</version>
<interface>
<name>INfc</name>
<instance>default</instance>
diff --git a/current.txt b/current.txt
index 44ed2ca..7a9e47d 100644
--- a/current.txt
+++ b/current.txt
@@ -399,8 +399,8 @@
65a021fa89085b62fc96b2b6d3bef2f9103cf4d63379c68bc154fd9eef672852 android.hardware.health@1.0::types
b7ecf29927055ec422ec44bf776223f07d79ad9f92ccf9becf167e62c2607e7a android.hardware.keymaster@4.0::IKeymasterDevice
574e8f1499436fb4075894dcae0b36682427956ecb114f17f1fe22d116a83c6b android.hardware.neuralnetworks@1.0::IPreparedModel
-417ab60fe1ef786778047e4486f3d868ebce570d91addd8fe4251515213072de android.hardware.neuralnetworks@1.0::types
-ec8aa14fe9b03f2b3fb9845346a4005b6d098ebe2277b2564f73a548a0fd14a7 android.hardware.neuralnetworks@1.1::types
+1e3576c07006d82ba5bc6ddbf87c101414d361c41afe7a82713750844c488725 android.hardware.neuralnetworks@1.0::types
+eb754b58c93e5591613208b4c972811288b0fa16a82430d602f107c91a908b22 android.hardware.neuralnetworks@1.1::types
1d4a5776614c08b5d794a5ec5ab04697260cbd4b3441d5935cd53ee71d19da02 android.hardware.radio@1.0::IRadioResponse
ed9da80ec0c96991fd03f0a46107815d0e50f764656e49dba4980fa5c31d5bc3 android.hardware.radio@1.0::types
1d19720d4fd38b1095f0f555a4bd92b3b12c9b1d0f560b0e9a474cd6dcc20db6 android.hardware.radio@1.2::IRadio
@@ -451,7 +451,7 @@
92714960d1a53fc2ec557302b41c7cc93d2636d8364a44bd0f85be0c92927ff8 android.hardware.neuralnetworks@1.2::IExecutionCallback
36e1064c869965dee533c537cefbe87e54db8bd8cd45be7e0e93e00e8a43863a android.hardware.neuralnetworks@1.2::IPreparedModel
e1c734d1545e1a4ae749ff1dd9704a8e594c59aea7c8363159dc258e93e0df3b android.hardware.neuralnetworks@1.2::IPreparedModelCallback
-b75126d572c1788682a679ab53c2dbdf3fcd75f754b1370dbf09aaad0e19d397 android.hardware.neuralnetworks@1.2::types
+d18bba0b6c8d2d1da3cfb52b14f556d2e04eb91551d97ee60a3524cf993a3e0e android.hardware.neuralnetworks@1.2::types
cf7a4ba516a638f9b82a249c91fb603042c2d9ca43fd5aad9cf6c0401ed2a5d7 android.hardware.nfc@1.2::INfc
abf98c2ae08bf765db54edc8068e36d52eb558cff6706b6fd7c18c65a1f3fc18 android.hardware.nfc@1.2::types
4cb252dc6372a874aef666b92a6e9529915aa187521a700f0789065c3c702ead android.hardware.power.stats@1.0::IPowerStats
@@ -459,6 +459,7 @@
a1c6b0761bcb89d6bf15a156f9306b8090b3a916a15fea1689b4b0c1738e382f android.hardware.radio@1.3::IRadio
e9d0f11a52715f5a29d89e2d8e2e21db1e16a43174af6b9d51a62d705cda1455 android.hardware.radio@1.3::IRadioIndication
d233f0da44f55fdef0a95db5229231412787bb67695cd1ea197ce89a3c2908b9 android.hardware.radio@1.3::IRadioResponse
+f5fbe4f28a9e346be36063eca4e6c864114a1a6fb64884db03fdd825791ad9b8 android.hardware.radio@1.3::IRadioResponse # b/132818184 for Android Q
750a363c8cec70baa1aac19e275c15233c5898e93c6bb5155fa2ca7f365490dc android.hardware.radio@1.3::types
ef4ab741f7e7762fb45e2e24ca83871f72006ce05f57aa9addc574893dd29872 android.hardware.radio@1.4::IRadio
33d9e6895cca98aa56296bb01720d18b8acd0e4de4960beb712e63ad147438a5 android.hardware.radio@1.4::IRadioIndication
@@ -476,3 +477,4 @@
# ABI preserving changes to HALs during Android R
b69a7615c508acf5c5201efd1bfa3262167874fc3594e2db5a3ff93addd8ac75 android.hardware.keymaster@4.0::IKeymasterDevice
+1a6e2bd289f22931c526b21916910f1d4c436b7acb9556e4243de4ce8e6cc2e4 android.hardware.soundtrigger@2.0::ISoundTriggerHwCallback
diff --git a/keymaster/4.0/support/Keymaster.cpp b/keymaster/4.0/support/Keymaster.cpp
index 9325cc0..1eb9a68 100644
--- a/keymaster/4.0/support/Keymaster.cpp
+++ b/keymaster/4.0/support/Keymaster.cpp
@@ -19,7 +19,7 @@
#include <iomanip>
#include <android-base/logging.h>
-#include <android/hidl/manager/1.0/IServiceManager.h>
+#include <android/hidl/manager/1.2/IServiceManager.h>
#include <keymasterV4_0/Keymaster3.h>
#include <keymasterV4_0/Keymaster4.h>
#include <keymasterV4_0/key_param_output.h>
@@ -69,7 +69,7 @@
namespace support {
using ::android::sp;
-using ::android::hidl::manager::V1_0::IServiceManager;
+using ::android::hidl::manager::V1_2::IServiceManager;
std::ostream& operator<<(std::ostream& os, const Keymaster& keymaster) {
auto& version = keymaster.halVersion();
@@ -86,7 +86,7 @@
bool foundDefault = false;
auto& descriptor = Wrapper::WrappedIKeymasterDevice::descriptor;
- serviceManager->listByInterface(descriptor, [&](const hidl_vec<hidl_string>& names) {
+ serviceManager->listManifestByInterface(descriptor, [&](const hidl_vec<hidl_string>& names) {
for (auto& name : names) {
if (name == "default") foundDefault = true;
auto device = Wrapper::WrappedIKeymasterDevice::getService(name);
@@ -97,7 +97,7 @@
});
if (!foundDefault) {
- // "default" wasn't provided by listByInterface. Maybe there's a passthrough
+ // "default" wasn't provided by listManifestByInterface. Maybe there's a passthrough
// implementation.
auto device = Wrapper::WrappedIKeymasterDevice::getService("default");
if (device) result.push_back(std::unique_ptr<Keymaster>(new Wrapper(device, "default")));
@@ -106,6 +106,19 @@
return result;
}
+void Keymaster::logIfKeymasterVendorError(ErrorCode ec) const {
+ static constexpr int32_t k_keymaster_vendor_error_code_range_max = -10000;
+ if (static_cast<int32_t>(ec) <= k_keymaster_vendor_error_code_range_max) {
+ const auto& versionInfo = halVersion();
+ LOG(ERROR) << "Keymaster reported error: " << static_cast<int32_t>(ec) << "\n"
+ << "NOTE: This is an error in the vendor specific error range.\n"
+ << " Refer to the vendor of the implementation for details.\n"
+ << " Implementation name: " << versionInfo.keymasterName << "\n"
+ << " Vendor name: " << versionInfo.authorName << "\n"
+ << " MajorVersion: " << versionInfo.majorVersion;
+ }
+}
+
Keymaster::KeymasterSet Keymaster::enumerateAvailableDevices() {
auto serviceManager = IServiceManager::getService();
CHECK(serviceManager) << "Could not retrieve ServiceManager";
diff --git a/keymaster/4.0/support/include/keymasterV4_0/Keymaster.h b/keymaster/4.0/support/include/keymasterV4_0/Keymaster.h
index 458053a..43a34b0 100644
--- a/keymaster/4.0/support/include/keymasterV4_0/Keymaster.h
+++ b/keymaster/4.0/support/include/keymasterV4_0/Keymaster.h
@@ -65,6 +65,12 @@
const hidl_string& instanceName() const { return instanceName_; }
/**
+ * If ec is in the vendor error code range (<-10000), logs the fact to logcat.
+ * There are no side effects otherwise.
+ */
+ void logIfKeymasterVendorError(ErrorCode ec) const;
+
+ /**
* Returns all available Keymaster3 and Keymaster4 instances, in order of most secure to least
* secure (as defined by VersionResult::operator<).
*/
diff --git a/keymaster/4.0/vts/functional/VerificationTokenTest.cpp b/keymaster/4.0/vts/functional/VerificationTokenTest.cpp
index 3876b16..de28683 100644
--- a/keymaster/4.0/vts/functional/VerificationTokenTest.cpp
+++ b/keymaster/4.0/vts/functional/VerificationTokenTest.cpp
@@ -124,6 +124,65 @@
// report if times aren't nearly always <1ms apart.
EXPECT_LE(host_time_delta, km_time_delta + 2);
EXPECT_LE(km_time_delta, host_time_delta + 2);
+ ASSERT_EQ(result1.token.mac.size(), result2.token.mac.size());
+ ASSERT_NE(0,
+ memcmp(result1.token.mac.data(), result2.token.mac.data(), result1.token.mac.size()));
+}
+
+/*
+ * Test that the mac changes when the time stamp changes. This is does not guarantee that the time
+ * stamp is included in the mac but on failure we know that it is not. Other than in the test
+ * case above we call verifyAuthorization with the exact same set of parameters.
+ */
+TEST_F(VerificationTokenTest, MacChangesOnChangingTimestamp) {
+ auto result1 =
+ verifyAuthorization(0 /* operation handle */,
+ AuthorizationSet() /* paramtersToVerify */, HardwareAuthToken());
+ ASSERT_TRUE(result1.callSuccessful);
+ auto result1_time = getTime();
+
+ if (SecLevel() == SecurityLevel::STRONGBOX) {
+ // StrongBox should not implement verifyAuthorization.
+ EXPECT_EQ(ErrorCode::UNIMPLEMENTED, result1.error);
+ return;
+ }
+
+ EXPECT_EQ(ErrorCode::OK, result1.error);
+ EXPECT_EQ(0U, result1.token.challenge);
+ EXPECT_EQ(SecLevel(), result1.token.securityLevel);
+ EXPECT_EQ(0U, result1.token.parametersVerified.size())
+ << "We didn't supply any parameters to verify";
+ EXPECT_GT(result1.token.timestamp, 0U);
+
+ constexpr uint32_t time_to_sleep = 200;
+ sleep_ms(time_to_sleep);
+
+ auto result2 =
+ verifyAuthorization(0 /* operation handle */,
+ AuthorizationSet() /* paramtersToVerify */, HardwareAuthToken());
+ ASSERT_TRUE(result2.callSuccessful);
+ auto result2_time = getTime();
+ EXPECT_EQ(ErrorCode::OK, result2.error);
+ EXPECT_EQ(0U, result2.token.challenge);
+ EXPECT_EQ(SecLevel(), result2.token.securityLevel);
+ EXPECT_EQ(0U, result2.token.parametersVerified.size())
+ << "We didn't supply any parameters to verify";
+
+ auto host_time_delta = result2_time - result1_time;
+
+ EXPECT_GE(host_time_delta, time_to_sleep)
+ << "We slept for " << time_to_sleep << " ms, the clock must have advanced by that much";
+ EXPECT_LE(host_time_delta, time_to_sleep + 20)
+ << "The verifyAuthorization call took " << (host_time_delta - time_to_sleep)
+ << " ms? That's awful!";
+
+ auto km_time_delta = result2.token.timestamp - result1.token.timestamp;
+
+ EXPECT_LE(host_time_delta, km_time_delta + 2);
+ EXPECT_LE(km_time_delta, host_time_delta + 2);
+ ASSERT_EQ(result1.token.mac.size(), result2.token.mac.size());
+ ASSERT_NE(0,
+ memcmp(result1.token.mac.data(), result2.token.mac.data(), result1.token.mac.size()));
}
} // namespace test
diff --git a/keymaster/4.0/vts/functional/keymaster_hidl_hal_test.cpp b/keymaster/4.0/vts/functional/keymaster_hidl_hal_test.cpp
index d069d5d..a6bd29a 100644
--- a/keymaster/4.0/vts/functional/keymaster_hidl_hal_test.cpp
+++ b/keymaster/4.0/vts/functional/keymaster_hidl_hal_test.cpp
@@ -1918,26 +1918,26 @@
}
auto wrapped_key = hex2str(
- "3082017902010004820100A0E69B1395D382354FC0E7F74AC068C5818279D76D46745C7274997D045BAA8B9763B3F3"
- "09E5E59ECA99273AAAE0A37449DA9B1E67B66EC4E42BB62C25346683A43A9F2ACBCA6D350B25551CC53CE0721D29BE"
- "90F60686877478F82B3BB111C5EAC0BAE9310D7AD11F5A82948B31C322820F24E20DDB0FBD07D1566DAEAA058D4645"
- "2607352699E1F631D2ABAF60B13E41ED5EDBB90D252331BDB9CDB1B672E871F37CAC009FE9028B3B1E0ACE8F6F0678"
- "3F581B860620BDD478969EDE3101AAEFF65C6DB03E143E586167DC87D0CCE39E9119782F7B60A7A1CF2B7EE234E013"
- "E3DE6C56F0D51F30C389D31FA37C5F2875ACB44434E82EF40B316C93DE129BA0040CD796B02C370F1FA4CC0124F130"
- "2E0201033029A1083106020100020101A203020120A30402020100A4053103020101A6053103020140BF8377020500"
- "0420CCD540855F833A5E1480BFD2D36FAF3AEEE15DF5BEABE2691BC82DDE2A7AA910041064C9F689C60FF6223AB6E6"
- "999E0EB6E5");
+ "3082017902010004820100934bf94e2aa28a3f83c9f79297250262fbe3276b5a1c91159bbfa3ef8957aac84b59b30b"
+ "455a79c2973480823d8b3863c3deef4a8e243590268d80e18751a0e130f67ce6a1ace9f79b95e097474febc981195b"
+ "1d13a69086c0863f66a7b7fdb48792227b1ac5e2489febdf087ab5486483033a6f001ca5d1ec1e27f5c30f4cec2642"
+ "074a39ae68aee552e196627a8e3d867e67a8c01b11e75f13cca0a97ab668b50cda07a8ecb7cd8e3dd7009c9636534f"
+ "6f239cffe1fc8daa466f78b676c7119efb96bce4e69ca2a25d0b34ed9c3ff999b801597d5220e307eaa5bee507fb94"
+ "d1fa69f9e519b2de315bac92c36f2ea1fa1df4478c0ddedeae8c70e0233cd098040cd796b02c370f1fa4cc0124f130"
+ "2e0201033029a1083106020100020101a203020120a30402020100a4053103020101a6053103020140bf8377020500"
+ "0420ccd540855f833a5e1480bfd2d36faf3aeee15df5beabe2691bc82dde2a7aa910041064c9f689c60ff6223ab6e6"
+ "999e0eb6e5");
auto wrapped_key_masked = hex2str(
- "30820179020100048201001EF5320D3C920D7614688A439409ACE4318C48395ABB7247A68671BD4B7156A7773B31A4"
- "4459B73858625988A312E4D8855138F555678F525E4C52D91444FDC936BE6AEB63FD73FD84201EF46F88A0B622F528"
- "956C92C9C731EB65BCBC6A03BEAB45959B54A768E2842D2CE174EE542EF2A15DCAA7542F3574BEEB1A991F95439466"
- "E1960A9CE9E4CBC77DB23765191E4758C850908BCC74E158B77AB774141F171262C1AC771FDFA2E942F2F7633E97E8"
- "0BD492C3E821361AC6B4F568DE351C816C8C997212C707F728FB3BCAAA796EA6B8E7A80BE010970B380122940277E9"
- "4C5E9288F7CB6878A4C4CC1E83AB85A81FD68E43B14F1F81AD21E0D3545D70EE040C6D9721D08589581AB49204A330"
- "2E0201033029A1083106020100020101A203020120A30402020100A4053103020101A6053103020140BF8377020500"
- "0420A61C6E247E25B3E6E69AA78EB03C2D4AC20D1F99A9A024A76F35C8E2CAB9B68D04102560C70109AE67C030F00B"
- "98B512A670");
+ "3082017902010004820100aad93ed5924f283b4bb5526fbe7a1412f9d9749ec30db9062b29e574a8546f33c8873245"
+ "2f5b8e6a391ee76c39ed1712c61d8df6213dec1cffbc17a8c6d04c7b30893d8daa9b2015213e21946821553207f8f9"
+ "931c4caba23ed3bee28b36947e47f10e0a5c3dc51c988a628daad3e5e1f4005e79c2d5a96c284b4b8d7e4948f331e5"
+ "b85dd5a236f85579f3ea1d1b848487470bdb0ab4f81a12bee42c99fe0df4bee3759453e69ad1d68a809ce06b949f76"
+ "94a990429b2fe81e066ff43e56a21602db70757922a4bcc23ab89f1e35da77586775f423e519c2ea394caf48a28d0c"
+ "8020f1dcf6b3a68ec246f615ae96dae9a079b1f6eb959033c1af5c125fd94168040c6d9721d08589581ab49204a330"
+ "2e0201033029a1083106020100020101a203020120a30402020100a4053103020101a6053103020140bf8377020500"
+ "0420a61c6e247e25b3e6e69aa78eb03c2d4ac20d1f99a9a024a76f35c8e2cab9b68d04102560c70109ae67c030f00b"
+ "98b512a670");
auto wrapping_key = hex2str(
"308204be020100300d06092a864886f70d0101010500048204a8308204a40201000282010100aec367931d8900ce56"
@@ -1976,14 +1976,16 @@
TEST_F(ImportWrappedKeyTest, Success) {
auto wrapping_key_desc = AuthorizationSetBuilder()
.RsaEncryptionKey(2048, 65537)
- .Digest(Digest::SHA1)
+ .Digest(Digest::SHA_2_256)
.Padding(PaddingMode::RSA_OAEP)
.Authorization(TAG_PURPOSE, KeyPurpose::WRAP_KEY);
ASSERT_EQ(ErrorCode::OK,
ImportWrappedKey(
wrapped_key, wrapping_key, wrapping_key_desc, zero_masking_key,
- AuthorizationSetBuilder().Digest(Digest::SHA1).Padding(PaddingMode::RSA_OAEP)));
+ AuthorizationSetBuilder()
+ .Digest(Digest::SHA_2_256)
+ .Padding(PaddingMode::RSA_OAEP)));
string message = "Hello World!";
auto params = AuthorizationSetBuilder().BlockMode(BlockMode::ECB).Padding(PaddingMode::PKCS7);
@@ -1995,39 +1997,45 @@
TEST_F(ImportWrappedKeyTest, SuccessMasked) {
auto wrapping_key_desc = AuthorizationSetBuilder()
.RsaEncryptionKey(2048, 65537)
- .Digest(Digest::SHA1)
+ .Digest(Digest::SHA_2_256)
.Padding(PaddingMode::RSA_OAEP)
.Authorization(TAG_PURPOSE, KeyPurpose::WRAP_KEY);
ASSERT_EQ(ErrorCode::OK,
ImportWrappedKey(
wrapped_key_masked, wrapping_key, wrapping_key_desc, masking_key,
- AuthorizationSetBuilder().Digest(Digest::SHA1).Padding(PaddingMode::RSA_OAEP)));
+ AuthorizationSetBuilder()
+ .Digest(Digest::SHA_2_256)
+ .Padding(PaddingMode::RSA_OAEP)));
}
TEST_F(ImportWrappedKeyTest, WrongMask) {
auto wrapping_key_desc = AuthorizationSetBuilder()
.RsaEncryptionKey(2048, 65537)
- .Digest(Digest::SHA1)
+ .Digest(Digest::SHA_2_256)
.Padding(PaddingMode::RSA_OAEP)
.Authorization(TAG_PURPOSE, KeyPurpose::WRAP_KEY);
ASSERT_EQ(ErrorCode::VERIFICATION_FAILED,
ImportWrappedKey(
wrapped_key_masked, wrapping_key, wrapping_key_desc, zero_masking_key,
- AuthorizationSetBuilder().Digest(Digest::SHA1).Padding(PaddingMode::RSA_OAEP)));
+ AuthorizationSetBuilder()
+ .Digest(Digest::SHA_2_256)
+ .Padding(PaddingMode::RSA_OAEP)));
}
TEST_F(ImportWrappedKeyTest, WrongPurpose) {
auto wrapping_key_desc = AuthorizationSetBuilder()
.RsaEncryptionKey(2048, 65537)
- .Digest(Digest::SHA1)
+ .Digest(Digest::SHA_2_256)
.Padding(PaddingMode::RSA_OAEP);
ASSERT_EQ(ErrorCode::INCOMPATIBLE_PURPOSE,
ImportWrappedKey(
wrapped_key_masked, wrapping_key, wrapping_key_desc, zero_masking_key,
- AuthorizationSetBuilder().Digest(Digest::SHA1).Padding(PaddingMode::RSA_OAEP)));
+ AuthorizationSetBuilder()
+ .Digest(Digest::SHA_2_256)
+ .Padding(PaddingMode::RSA_OAEP)));
}
typedef KeymasterHidlTest EncryptionOperationsTest;
@@ -2261,7 +2269,8 @@
Begin(KeyPurpose::ENCRYPT,
AuthorizationSetBuilder().Padding(PaddingMode::RSA_OAEP).Digest(Digest::SHA_2_256)));
string result;
- EXPECT_EQ(ErrorCode::INVALID_ARGUMENT, Finish(message, &result));
+ auto error = Finish(message, &result);
+ EXPECT_TRUE(error == ErrorCode::INVALID_INPUT_LENGTH || error == ErrorCode::INVALID_ARGUMENT);
EXPECT_EQ(0U, result.size());
}
@@ -2319,7 +2328,8 @@
auto params = AuthorizationSetBuilder().Padding(PaddingMode::RSA_PKCS1_1_5_ENCRYPT);
EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::ENCRYPT, params));
string result;
- EXPECT_EQ(ErrorCode::INVALID_ARGUMENT, Finish(message, &result));
+ auto error = Finish(message, &result);
+ EXPECT_TRUE(error == ErrorCode::INVALID_INPUT_LENGTH || error == ErrorCode::INVALID_ARGUMENT);
EXPECT_EQ(0U, result.size());
}
@@ -3963,6 +3973,33 @@
}
/*
+ * AttestationTest.EcAttestationByKeySize
+ *
+ * Verifies that attesting to EC keys works and generates the expected output.
+ */
+TEST_F(AttestationTest, EcAttestationByKeySize) {
+ ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder()
+ .Authorization(TAG_NO_AUTH_REQUIRED)
+ .EcdsaSigningKey(256)
+ .Digest(Digest::SHA_2_256)
+ .Authorization(TAG_INCLUDE_UNIQUE_ID)));
+
+ hidl_vec<hidl_vec<uint8_t>> cert_chain;
+ ASSERT_EQ(ErrorCode::OK,
+ AttestKey(AuthorizationSetBuilder()
+ .Authorization(TAG_ATTESTATION_CHALLENGE, HidlBuf("challenge"))
+ .Authorization(TAG_ATTESTATION_APPLICATION_ID, HidlBuf("foo")),
+ &cert_chain));
+ EXPECT_GE(cert_chain.size(), 2U);
+ EXPECT_TRUE(verify_chain(cert_chain));
+
+ EXPECT_TRUE(verify_attestation_record("challenge", "foo", //
+ key_characteristics_.softwareEnforced, //
+ key_characteristics_.hardwareEnforced, //
+ SecLevel(), cert_chain[0]));
+}
+
+/*
* AttestationTest.EcAttestationRequiresAttestationAppId
*
* Verifies that attesting to EC keys requires app ID
diff --git a/neuralnetworks/1.0/types.hal b/neuralnetworks/1.0/types.hal
index 89af35a..02db063 100644
--- a/neuralnetworks/1.0/types.hal
+++ b/neuralnetworks/1.0/types.hal
@@ -858,20 +858,21 @@
* elements of the input matrices.
*
* The operation has the following independently optional inputs:
+ * * The cell-to-input weights (\f$W_{ci}\f$), cell-to-forget weights
+ * (\f$W_{cf}\f$) and cell-to-output weights (\f$W_{co}\f$) either all
+ * have values or neither of them have values (i.e., all set to null). If
+ * they have values, the peephole optimization is used.
* * The input-to-input weights (\f$W_{xi}\f$), recurrent-to-input weights
- * (\f$W_{hi}\f$), cell-to-input (\f$W_{ci}\f$) weights, and input gate
- * bias (\f$b_i\f$) either all have values, or none of them have values
- * (i.e., all set to null). If they have no values, coupling of input and
- * forget gates (CIFG) is used, in which case the input gate (\f$i_t\f$)
- * is calculated using the following equation instead.
+ * (\f$W_{hi}\f$) and input gate bias (\f$b_i\f$) either all have values,
+ * or none of them have values. If they have no values, coupling of input
+ * and forget gates (CIFG) is used, in which case the input gate
+ * (\f$i_t\f$) is calculated using the following equation instead.
* \f{eqnarray*}{
* i_t = 1 - f_t
* \f}
- * * The cell-to-forget weights (\f$W_{cf}\f$) and cell-to-output weights
- * (\f$W_{co}\f$) either both have values or neither of them have values.
- * If they have values, the peephole optimization is used. Additionally,
- * if CIFG is not used, cell-to-input weights (\f$W_{ci}\f$) is also
- * required to have values for peephole optimization.
+ * In case peephole optimization is used and CIFG is not used
+ * cell-to-input (\f$W_{ci}\f$) weights must be present. Otherwise, the
+ * cell-to-input weights must have no value.
* * The projection weights (\f$W_{proj}\f$) is required only for the
* recurrent projection layer, and should otherwise have no value.
* * The projection bias (\f$b_{proj}\f$) may (but not required to) have a
@@ -984,8 +985,8 @@
* Outputs:
* * 0: The scratch buffer.
* A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape
- * [batch_size, num_units * 4] with CIFG, or
- * [batch_size, num_units * 3] without CIFG.
+ * [batch_size, num_units * 3] with CIFG, or
+ * [batch_size, num_units * 4] without CIFG.
* * 1: The output state (out) (\f$h_t\f$).
* A 2-D tensor of {@link OperandType::TENSOR_FLOAT32}, of shape
* [batch_size, output_size].
@@ -1197,6 +1198,11 @@
* shape of the output tensor. The number of elements implied by shape
* must be the same as the number of elements in the input tensor.
*
+ * If one component of shape is the special value -1, the size of that
+ * dimension is computed so that the total size remains constant. In
+ * particular, a shape of [-1] flattens into 1-D. At most one component
+ * of shape can be -1.
+ *
* Outputs:
* * 0: The output tensor, of shape specified by the input shape.
*
@@ -1220,9 +1226,9 @@
* * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
* the input.
* * 1: An {@link OperandType::INT32} scalar, specifying the output
- * height of the output tensor.
- * * 2: An {@link OperandType::INT32} scalar, specifying the output
* width of the output tensor.
+ * * 2: An {@link OperandType::INT32} scalar, specifying the output
+ * height of the output tensor.
*
* Outputs:
* * 0: The output 4-D tensor, of shape
diff --git a/neuralnetworks/1.0/vts/functional/ValidateRequest.cpp b/neuralnetworks/1.0/vts/functional/ValidateRequest.cpp
index 72a5007..f0c93b7 100644
--- a/neuralnetworks/1.0/vts/functional/ValidateRequest.cpp
+++ b/neuralnetworks/1.0/vts/functional/ValidateRequest.cpp
@@ -34,7 +34,6 @@
namespace functional {
using ::android::hardware::neuralnetworks::V1_2::implementation::ExecutionCallback;
-using ::android::hardware::neuralnetworks::V1_2::implementation::PreparedModelCallback;
using ::android::hidl::memory::V1_0::IMemory;
using test_helper::for_all;
using test_helper::MixedTyped;
@@ -42,53 +41,6 @@
///////////////////////// UTILITY FUNCTIONS /////////////////////////
-static void createPreparedModel(const sp<IDevice>& device, const V1_0::Model& model,
- sp<IPreparedModel>* preparedModel) {
- ASSERT_NE(nullptr, preparedModel);
-
- // see if service can handle model
- bool fullySupportsModel = false;
- Return<void> supportedOpsLaunchStatus = device->getSupportedOperations(
- model, [&fullySupportsModel](ErrorStatus status, const hidl_vec<bool>& supported) {
- ASSERT_EQ(ErrorStatus::NONE, status);
- ASSERT_NE(0ul, supported.size());
- fullySupportsModel =
- std::all_of(supported.begin(), supported.end(), [](bool valid) { return valid; });
- });
- ASSERT_TRUE(supportedOpsLaunchStatus.isOk());
-
- // launch prepare model
- sp<PreparedModelCallback> preparedModelCallback = new PreparedModelCallback();
- ASSERT_NE(nullptr, preparedModelCallback.get());
- Return<ErrorStatus> prepareLaunchStatus = device->prepareModel(model, preparedModelCallback);
- ASSERT_TRUE(prepareLaunchStatus.isOk());
- ASSERT_EQ(ErrorStatus::NONE, static_cast<ErrorStatus>(prepareLaunchStatus));
-
- // retrieve prepared model
- preparedModelCallback->wait();
- ErrorStatus prepareReturnStatus = preparedModelCallback->getStatus();
- *preparedModel = preparedModelCallback->getPreparedModel();
-
- // The getSupportedOperations call returns a list of operations that are
- // guaranteed not to fail if prepareModel is called, and
- // 'fullySupportsModel' is true i.f.f. the entire model is guaranteed.
- // If a driver has any doubt that it can prepare an operation, it must
- // return false. So here, if a driver isn't sure if it can support an
- // operation, but reports that it successfully prepared the model, the test
- // can continue.
- if (!fullySupportsModel && prepareReturnStatus != ErrorStatus::NONE) {
- ASSERT_EQ(nullptr, preparedModel->get());
- LOG(INFO) << "NN VTS: Unable to test Request validation because vendor service cannot "
- "prepare model that it does not support.";
- std::cout << "[ ] Unable to test Request validation because vendor service "
- "cannot prepare model that it does not support."
- << std::endl;
- return;
- }
- ASSERT_EQ(ErrorStatus::NONE, prepareReturnStatus);
- ASSERT_NE(nullptr, preparedModel->get());
-}
-
// Primary validation function. This function will take a valid request, apply a
// mutation to it to invalidate the request, then pass it to interface calls
// that use the request. Note that the request here is passed by value, and any
@@ -237,15 +189,8 @@
return requests;
}
-void ValidationTest::validateRequests(const V1_0::Model& model,
+void ValidationTest::validateRequests(const sp<IPreparedModel>& preparedModel,
const std::vector<Request>& requests) {
- // create IPreparedModel
- sp<IPreparedModel> preparedModel;
- ASSERT_NO_FATAL_FAILURE(createPreparedModel(device, model, &preparedModel));
- if (preparedModel == nullptr) {
- return;
- }
-
// validate each request
for (const Request& request : requests) {
removeInputTest(preparedModel, request);
diff --git a/neuralnetworks/1.0/vts/functional/VtsHalNeuralnetworks.cpp b/neuralnetworks/1.0/vts/functional/VtsHalNeuralnetworks.cpp
index 8883057..aee2f85 100644
--- a/neuralnetworks/1.0/vts/functional/VtsHalNeuralnetworks.cpp
+++ b/neuralnetworks/1.0/vts/functional/VtsHalNeuralnetworks.cpp
@@ -18,6 +18,10 @@
#include "VtsHalNeuralnetworks.h"
+#include <android-base/logging.h>
+
+#include "Callbacks.h"
+
namespace android {
namespace hardware {
namespace neuralnetworks {
@@ -25,6 +29,55 @@
namespace vts {
namespace functional {
+using ::android::hardware::neuralnetworks::V1_2::implementation::PreparedModelCallback;
+
+static void createPreparedModel(const sp<IDevice>& device, const V1_0::Model& model,
+ sp<IPreparedModel>* preparedModel) {
+ ASSERT_NE(nullptr, preparedModel);
+
+ // see if service can handle model
+ bool fullySupportsModel = false;
+ Return<void> supportedOpsLaunchStatus = device->getSupportedOperations(
+ model, [&fullySupportsModel](ErrorStatus status, const hidl_vec<bool>& supported) {
+ ASSERT_EQ(ErrorStatus::NONE, status);
+ ASSERT_NE(0ul, supported.size());
+ fullySupportsModel = std::all_of(supported.begin(), supported.end(),
+ [](bool valid) { return valid; });
+ });
+ ASSERT_TRUE(supportedOpsLaunchStatus.isOk());
+
+ // launch prepare model
+ sp<PreparedModelCallback> preparedModelCallback = new PreparedModelCallback();
+ ASSERT_NE(nullptr, preparedModelCallback.get());
+ Return<ErrorStatus> prepareLaunchStatus = device->prepareModel(model, preparedModelCallback);
+ ASSERT_TRUE(prepareLaunchStatus.isOk());
+ ASSERT_EQ(ErrorStatus::NONE, static_cast<ErrorStatus>(prepareLaunchStatus));
+
+ // retrieve prepared model
+ preparedModelCallback->wait();
+ ErrorStatus prepareReturnStatus = preparedModelCallback->getStatus();
+ *preparedModel = preparedModelCallback->getPreparedModel();
+
+ // The getSupportedOperations call returns a list of operations that are
+ // guaranteed not to fail if prepareModel is called, and
+ // 'fullySupportsModel' is true i.f.f. the entire model is guaranteed.
+ // If a driver has any doubt that it can prepare an operation, it must
+ // return false. So here, if a driver isn't sure if it can support an
+ // operation, but reports that it successfully prepared the model, the test
+ // can continue.
+ if (!fullySupportsModel && prepareReturnStatus != ErrorStatus::NONE) {
+ ASSERT_EQ(nullptr, preparedModel->get());
+ LOG(INFO) << "NN VTS: Unable to test Request validation because vendor service cannot "
+ "prepare model that it does not support.";
+ std::cout << "[ ] Unable to test Request validation because vendor service "
+ "cannot prepare model that it does not support."
+ << std::endl;
+ return;
+ }
+ ASSERT_EQ(ErrorStatus::NONE, prepareReturnStatus);
+ ASSERT_NE(nullptr, preparedModel->get());
+}
+
// A class for test environment setup
NeuralnetworksHidlEnvironment::NeuralnetworksHidlEnvironment() {}
@@ -68,6 +121,19 @@
::testing::VtsHalHidlTargetTestBase::TearDown();
}
+void ValidationTest::validateEverything(const Model& model, const std::vector<Request>& requests) {
+ validateModel(model);
+
+ // create IPreparedModel
+ sp<IPreparedModel> preparedModel;
+ ASSERT_NO_FATAL_FAILURE(createPreparedModel(device, model, &preparedModel));
+ if (preparedModel == nullptr) {
+ return;
+ }
+
+ validateRequests(preparedModel, requests);
+}
+
} // namespace functional
} // namespace vts
} // namespace V1_0
diff --git a/neuralnetworks/1.0/vts/functional/VtsHalNeuralnetworks.h b/neuralnetworks/1.0/vts/functional/VtsHalNeuralnetworks.h
index d4c114d..22285be 100644
--- a/neuralnetworks/1.0/vts/functional/VtsHalNeuralnetworks.h
+++ b/neuralnetworks/1.0/vts/functional/VtsHalNeuralnetworks.h
@@ -63,8 +63,12 @@
// Tag for the validation tests
class ValidationTest : public NeuralnetworksHidlTest {
protected:
- void validateModel(const Model& model);
- void validateRequests(const Model& model, const std::vector<Request>& request);
+ void validateEverything(const Model& model, const std::vector<Request>& request);
+
+ private:
+ void validateModel(const Model& model);
+ void validateRequests(const sp<IPreparedModel>& preparedModel,
+ const std::vector<Request>& requests);
};
// Tag for the generated tests
diff --git a/neuralnetworks/1.1/types.hal b/neuralnetworks/1.1/types.hal
index 99f873a..73705bb 100644
--- a/neuralnetworks/1.1/types.hal
+++ b/neuralnetworks/1.1/types.hal
@@ -104,9 +104,6 @@
* in axis. If keep_dims is true, the reduced dimensions are retained with
* length 1.
*
- * If dimensions to reduce have no entries, all dimensions are reduced, and
- * a tensor with a single element is returned.
- *
* Supported tensor {@link OperandType}:
* * {@link OperandType::TENSOR_FLOAT32}
* * {@link OperandType::TENSOR_QUANT8_ASYMM}
@@ -116,8 +113,14 @@
* Inputs:
* * 0: A tensor, specifying the input.
* * 1: A 1-D Tensor of {@link OperandType::TENSOR_INT32}. The dimensions
- * to reduce. If None (the default), reduces all dimensions. Must be in
- * the range [-rank(input_tensor), rank(input_tensor)).
+ * to reduce. Must be in the range
+ * [-rank(input_tensor), rank(input_tensor)).
+ *
+ * NOTE: When the operation was introduced, the documentation
+ * incorrectly stated that if dimensions were empty, the operation
+ * would reduce across all dimensions. This behavior was never
+ * implemented.
+ *
* * 2: An {@link OperandType::INT32} scalar, keep_dims. If positive,
* retains reduced dimensions with length 1.
*
@@ -135,7 +138,7 @@
*
* Supported tensor {@link OperandType}:
* * {@link OperandType::TENSOR_FLOAT32}
- * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM} (the pad value is undefined)
*
* Supported tensor rank: up to 4
*
@@ -158,6 +161,9 @@
* output0.dimension[i] =
* padding[i, 0] + input0.dimension[i] + padding[i, 1]
*
+ * NOTE: The pad value for {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM}
+ * is undefined.
+ *
* Available since API level 28.
*/
PAD = 32,
diff --git a/neuralnetworks/1.1/vts/functional/ValidateRequest.cpp b/neuralnetworks/1.1/vts/functional/ValidateRequest.cpp
index 5225bf7..f4adbab 100644
--- a/neuralnetworks/1.1/vts/functional/ValidateRequest.cpp
+++ b/neuralnetworks/1.1/vts/functional/ValidateRequest.cpp
@@ -34,7 +34,6 @@
namespace functional {
using ::android::hardware::neuralnetworks::V1_2::implementation::ExecutionCallback;
-using ::android::hardware::neuralnetworks::V1_2::implementation::PreparedModelCallback;
using ::android::hidl::memory::V1_0::IMemory;
using test_helper::for_all;
using test_helper::MixedTyped;
@@ -42,54 +41,6 @@
///////////////////////// UTILITY FUNCTIONS /////////////////////////
-static void createPreparedModel(const sp<IDevice>& device, const V1_1::Model& model,
- sp<IPreparedModel>* preparedModel) {
- ASSERT_NE(nullptr, preparedModel);
-
- // see if service can handle model
- bool fullySupportsModel = false;
- Return<void> supportedOpsLaunchStatus = device->getSupportedOperations_1_1(
- model, [&fullySupportsModel](ErrorStatus status, const hidl_vec<bool>& supported) {
- ASSERT_EQ(ErrorStatus::NONE, status);
- ASSERT_NE(0ul, supported.size());
- fullySupportsModel =
- std::all_of(supported.begin(), supported.end(), [](bool valid) { return valid; });
- });
- ASSERT_TRUE(supportedOpsLaunchStatus.isOk());
-
- // launch prepare model
- sp<PreparedModelCallback> preparedModelCallback = new PreparedModelCallback();
- ASSERT_NE(nullptr, preparedModelCallback.get());
- Return<ErrorStatus> prepareLaunchStatus = device->prepareModel_1_1(
- model, ExecutionPreference::FAST_SINGLE_ANSWER, preparedModelCallback);
- ASSERT_TRUE(prepareLaunchStatus.isOk());
- ASSERT_EQ(ErrorStatus::NONE, static_cast<ErrorStatus>(prepareLaunchStatus));
-
- // retrieve prepared model
- preparedModelCallback->wait();
- ErrorStatus prepareReturnStatus = preparedModelCallback->getStatus();
- *preparedModel = preparedModelCallback->getPreparedModel();
-
- // The getSupportedOperations_1_1 call returns a list of operations that are
- // guaranteed not to fail if prepareModel_1_1 is called, and
- // 'fullySupportsModel' is true i.f.f. the entire model is guaranteed.
- // If a driver has any doubt that it can prepare an operation, it must
- // return false. So here, if a driver isn't sure if it can support an
- // operation, but reports that it successfully prepared the model, the test
- // can continue.
- if (!fullySupportsModel && prepareReturnStatus != ErrorStatus::NONE) {
- ASSERT_EQ(nullptr, preparedModel->get());
- LOG(INFO) << "NN VTS: Unable to test Request validation because vendor service cannot "
- "prepare model that it does not support.";
- std::cout << "[ ] Unable to test Request validation because vendor service "
- "cannot prepare model that it does not support."
- << std::endl;
- return;
- }
- ASSERT_EQ(ErrorStatus::NONE, prepareReturnStatus);
- ASSERT_NE(nullptr, preparedModel->get());
-}
-
// Primary validation function. This function will take a valid request, apply a
// mutation to it to invalidate the request, then pass it to interface calls
// that use the request. Note that the request here is passed by value, and any
@@ -238,15 +189,8 @@
return requests;
}
-void ValidationTest::validateRequests(const V1_1::Model& model,
+void ValidationTest::validateRequests(const sp<IPreparedModel>& preparedModel,
const std::vector<Request>& requests) {
- // create IPreparedModel
- sp<IPreparedModel> preparedModel;
- ASSERT_NO_FATAL_FAILURE(createPreparedModel(device, model, &preparedModel));
- if (preparedModel == nullptr) {
- return;
- }
-
// validate each request
for (const Request& request : requests) {
removeInputTest(preparedModel, request);
diff --git a/neuralnetworks/1.1/vts/functional/VtsHalNeuralnetworks.cpp b/neuralnetworks/1.1/vts/functional/VtsHalNeuralnetworks.cpp
index 224a51d..08069f2 100644
--- a/neuralnetworks/1.1/vts/functional/VtsHalNeuralnetworks.cpp
+++ b/neuralnetworks/1.1/vts/functional/VtsHalNeuralnetworks.cpp
@@ -18,6 +18,10 @@
#include "VtsHalNeuralnetworks.h"
+#include <android-base/logging.h>
+
+#include "Callbacks.h"
+
namespace android {
namespace hardware {
namespace neuralnetworks {
@@ -25,6 +29,56 @@
namespace vts {
namespace functional {
+using ::android::hardware::neuralnetworks::V1_2::implementation::PreparedModelCallback;
+
+static void createPreparedModel(const sp<IDevice>& device, const V1_1::Model& model,
+ sp<IPreparedModel>* preparedModel) {
+ ASSERT_NE(nullptr, preparedModel);
+
+ // see if service can handle model
+ bool fullySupportsModel = false;
+ Return<void> supportedOpsLaunchStatus = device->getSupportedOperations_1_1(
+ model, [&fullySupportsModel](ErrorStatus status, const hidl_vec<bool>& supported) {
+ ASSERT_EQ(ErrorStatus::NONE, status);
+ ASSERT_NE(0ul, supported.size());
+ fullySupportsModel = std::all_of(supported.begin(), supported.end(),
+ [](bool valid) { return valid; });
+ });
+ ASSERT_TRUE(supportedOpsLaunchStatus.isOk());
+
+ // launch prepare model
+ sp<PreparedModelCallback> preparedModelCallback = new PreparedModelCallback();
+ ASSERT_NE(nullptr, preparedModelCallback.get());
+ Return<ErrorStatus> prepareLaunchStatus = device->prepareModel_1_1(
+ model, ExecutionPreference::FAST_SINGLE_ANSWER, preparedModelCallback);
+ ASSERT_TRUE(prepareLaunchStatus.isOk());
+ ASSERT_EQ(ErrorStatus::NONE, static_cast<ErrorStatus>(prepareLaunchStatus));
+
+ // retrieve prepared model
+ preparedModelCallback->wait();
+ ErrorStatus prepareReturnStatus = preparedModelCallback->getStatus();
+ *preparedModel = preparedModelCallback->getPreparedModel();
+
+ // The getSupportedOperations_1_1 call returns a list of operations that are
+ // guaranteed not to fail if prepareModel_1_1 is called, and
+ // 'fullySupportsModel' is true i.f.f. the entire model is guaranteed.
+ // If a driver has any doubt that it can prepare an operation, it must
+ // return false. So here, if a driver isn't sure if it can support an
+ // operation, but reports that it successfully prepared the model, the test
+ // can continue.
+ if (!fullySupportsModel && prepareReturnStatus != ErrorStatus::NONE) {
+ ASSERT_EQ(nullptr, preparedModel->get());
+ LOG(INFO) << "NN VTS: Unable to test Request validation because vendor service cannot "
+ "prepare model that it does not support.";
+ std::cout << "[ ] Unable to test Request validation because vendor service "
+ "cannot prepare model that it does not support."
+ << std::endl;
+ return;
+ }
+ ASSERT_EQ(ErrorStatus::NONE, prepareReturnStatus);
+ ASSERT_NE(nullptr, preparedModel->get());
+}
+
// A class for test environment setup
NeuralnetworksHidlEnvironment::NeuralnetworksHidlEnvironment() {}
@@ -68,6 +122,19 @@
::testing::VtsHalHidlTargetTestBase::TearDown();
}
+void ValidationTest::validateEverything(const Model& model, const std::vector<Request>& requests) {
+ validateModel(model);
+
+ // create IPreparedModel
+ sp<IPreparedModel> preparedModel;
+ ASSERT_NO_FATAL_FAILURE(createPreparedModel(device, model, &preparedModel));
+ if (preparedModel == nullptr) {
+ return;
+ }
+
+ validateRequests(preparedModel, requests);
+}
+
} // namespace functional
} // namespace vts
} // namespace V1_1
diff --git a/neuralnetworks/1.1/vts/functional/VtsHalNeuralnetworks.h b/neuralnetworks/1.1/vts/functional/VtsHalNeuralnetworks.h
index 1c8c0e1..f3f587b 100644
--- a/neuralnetworks/1.1/vts/functional/VtsHalNeuralnetworks.h
+++ b/neuralnetworks/1.1/vts/functional/VtsHalNeuralnetworks.h
@@ -72,8 +72,12 @@
// Tag for the validation tests
class ValidationTest : public NeuralnetworksHidlTest {
protected:
- void validateModel(const Model& model);
- void validateRequests(const Model& model, const std::vector<Request>& request);
+ void validateEverything(const Model& model, const std::vector<Request>& request);
+
+ private:
+ void validateModel(const Model& model);
+ void validateRequests(const sp<IPreparedModel>& preparedModel,
+ const std::vector<Request>& requests);
};
// Tag for the generated tests
diff --git a/neuralnetworks/1.2/types.hal b/neuralnetworks/1.2/types.hal
index 839114f..f368ce2 100644
--- a/neuralnetworks/1.2/types.hal
+++ b/neuralnetworks/1.2/types.hal
@@ -1005,12 +1005,21 @@
* the input.
* * 1: An {@link OperandType::INT32} scalar, specifying the radius of
* the normalization window.
- * * 2: An {@link OperandType::FLOAT32} scalar, specifying the bias, must
- * not be zero.
- * * 3: An {@link OperandType::FLOAT32} scalar, specifying the scale
- * factor, alpha.
- * * 4: An {@link OperandType::FLOAT32} scalar, specifying the exponent,
- * beta.
+ * * 2: A scalar, specifying the bias, must not be zero.
+ * For input tensor of {@link OperandType::TENSOR_FLOAT16}, the bias
+ * value must be of {@link OperandType::FLOAT16}.
+ * For input tensor of {@link OperandType::TENSOR_FLOAT32}, the bias
+ * value must be of {@link OperandType::FLOAT32}.
+ * * 3: A scalar, specifying the scale factor, alpha.
+ * For input tensor of {@link OperandType::TENSOR_FLOAT16}, the alpha
+ * value must be of {@link OperandType::FLOAT16}.
+ * For input tensor of {@link OperandType::TENSOR_FLOAT32}, the alpha
+ * value must be of {@link OperandType::FLOAT32}.
+ * * 4: A scalar, specifying the exponent, beta.
+ * For input tensor of {@link OperandType::TENSOR_FLOAT16}, the beta
+ * value must be of {@link OperandType::FLOAT16}.
+ * For input tensor of {@link OperandType::TENSOR_FLOAT32}, the beta
+ * value must be of {@link OperandType::FLOAT32}.
* * 5: An optional {@link OperandType::INT32} scalar, default to -1,
* specifying the dimension normalization would be performed on.
* Negative index is used to specify axis from the end (e.g. -1 for
@@ -1168,20 +1177,21 @@
* https://arxiv.org/pdf/1607.06450.pdf
*
* The operation has the following independently optional inputs:
+ * * The cell-to-input weights (\f$W_{ci}\f$), cell-to-forget weights
+ * (\f$W_{cf}\f$) and cell-to-output weights (\f$W_{co}\f$) either all
+ * have values or neither of them have values (i.e., all set to null). If
+ * they have values, the peephole optimization is used.
* * The input-to-input weights (\f$W_{xi}\f$), recurrent-to-input weights
- * (\f$W_{hi}\f$), cell-to-input (\f$W_{ci}\f$) weights, and input gate
- * bias (\f$b_i\f$) either all have values, or none of them have values
- * (i.e., all set to null). If they have no values, coupling of input and
- * forget gates (CIFG) is used, in which case the input gate (\f$i_t\f$)
- * is calculated using the following equation instead.
+ * (\f$W_{hi}\f$) and input gate bias (\f$b_i\f$) either all have values,
+ * or none of them have values. If they have no values, coupling of input
+ * and forget gates (CIFG) is used, in which case the input gate
+ * (\f$i_t\f$) is calculated using the following equation instead.
* \f{eqnarray*}{
* i_t = 1 - f_t
* \f}
- * * The cell-to-forget weights (\f$W_{cf}\f$) and cell-to-output weights
- * (\f$W_{co}\f$) either both have values or neither of them have values.
- * If they have values, the peephole optimization is used. Additionally,
- * if CIFG is not used, cell-to-input weights (\f$W_{ci}\f$) is also
- * required to have values for peephole optimization.
+ * In case peephole optimization is used and CIFG is not used
+ * cell-to-input (\f$W_{ci}\f$) weights must be present. Otherwise, the
+ * cell-to-input weights must have no value.
* * The projection weights (\f$W_{proj}\f$) is required only for the
* recurrent projection layer, and should otherwise have no value.
* * The projection bias (\f$b_{proj}\f$) may (but not required to) have a
@@ -1548,6 +1558,11 @@
* shape of the output tensor. The number of elements implied by shape
* must be the same as the number of elements in the input tensor.
*
+ * If one component of shape is the special value -1, the size of that
+ * dimension is computed so that the total size remains constant. In
+ * particular, a shape of [-1] flattens into 1-D. At most one component
+ * of shape can be -1.
+ *
* Outputs:
* * 0: The output tensor, of shape specified by the input shape.
*
@@ -1579,9 +1594,9 @@
* the input. Since API level 29, zero batches is supported for this
* tensor.
* * 1: An {@link OperandType::INT32} scalar, specifying the output
- * height of the output tensor.
- * * 2: An {@link OperandType::INT32} scalar, specifying the output
* width of the output tensor.
+ * * 2: An {@link OperandType::INT32} scalar, specifying the output
+ * height of the output tensor.
* * 3: An optional {@link OperandType::BOOL} scalar, default to false.
* Set to true to specify NCHW data layout for input0 and output0.
* Available since API level 29.
@@ -1589,15 +1604,15 @@
* Inputs (resizing by scale, since API level 29):
* * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
* the input. Zero batches is supported for this tensor.
- * * 1: A scalar, specifying height_scale, the scaling factor of the height
+ * * 1: A scalar, specifying width_scale, the scaling factor of the width
* dimension from the input tensor to the output tensor. The output
- * height is calculated as new_height = floor(height * height_scale).
+ * width is calculated as new_width = floor(width * width_scale).
* The scalar must be of {@link OperandType::FLOAT16} if input0 is
* of {@link OperandType::TENSOR_FLOAT16} and of
* {@link OperandType::FLOAT32} otherwise.
- * * 2: A scalar, specifying width_scale, the scaling factor of the width
+ * * 2: A scalar, specifying height_scale, the scaling factor of the height
* dimension from the input tensor to the output tensor. The output
- * width is calculated as new_width = floor(width * width_scale).
+ * height is calculated as new_height = floor(height * height_scale).
* The scalar must be of {@link OperandType::FLOAT16} if input0 is
* of {@link OperandType::TENSOR_FLOAT16} and of
* {@link OperandType::FLOAT32} otherwise.
@@ -1949,9 +1964,6 @@
* in axis. If keep_dims is true, the reduced dimensions are retained with
* length 1.
*
- * If dimensions to reduce have no entries, all dimensions are reduced, and
- * a tensor with a single element is returned.
- *
* Supported tensor {@link OperandType}:
* * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
* * {@link OperandType::TENSOR_FLOAT32}
@@ -1962,8 +1974,14 @@
* Inputs:
* * 0: A tensor, specifying the input.
* * 1: A 1-D Tensor of {@link OperandType::TENSOR_INT32}. The dimensions
- * to reduce. If None (the default), reduces all dimensions. Must be in
- * the range [-rank(input_tensor), rank(input_tensor)).
+ * to reduce. Must be in the range
+ * [-rank(input_tensor), rank(input_tensor)).
+ *
+ * NOTE: When the operation was introduced, the documentation
+ * incorrectly stated that if dimensions were empty, the operation
+ * would reduce across all dimensions. This behavior was never
+ * implemented.
+ *
* * 2: An {@link OperandType::INT32} scalar, keep_dims. If positive,
* retains reduced dimensions with length 1.
*
@@ -1982,7 +2000,8 @@
* Supported tensor {@link OperandType}:
* * {@link OperandType::TENSOR_FLOAT16} (since API level 29)
* * {@link OperandType::TENSOR_FLOAT32}
- * * {@link OperandType::TENSOR_QUANT8_ASYMM}
+ * * {@link OperandType::TENSOR_QUANT8_ASYMM} (full support since API
+ * level 29, see the output section)
*
* Supported tensor rank: up to 4
*
@@ -2005,6 +2024,10 @@
* output0.dimension[i] =
* padding[i, 0] + input0.dimension[i] + padding[i, 1]
*
+ * NOTE: Before API level 29, the pad value for
+ * {@link ANEURALNETWORKS_TENSOR_QUANT8_ASYMM} is undefined.
+ * Since API level 29, the pad value is always the logical zero.
+ *
* Available since API level 28.
*/
PAD = @1.1::OperationType:PAD,
@@ -3487,8 +3510,12 @@
*
* Inputs:
* * 0: A tensor specifying the input logits.
- * * 1: An {@link OperandType::FLOAT32} scalar, specifying the positive
- * scaling factor for the exponent, beta.
+ * * 1: A scalar, specifying the positive scaling factor for the exponent,
+ * beta.
+ * For input tensor of {@link OperandType::TENSOR_FLOAT16}, the beta
+ * value must be of {@link OperandType::FLOAT16}.
+ * For input tensor of {@link OperandType::TENSOR_FLOAT32}, the beta
+ * value must be of {@link OperandType::FLOAT32}.
* * 2: An {@link OperandType::INT32} scalar specifying the axis to
* reduce across. Negative index is used to specify axis from the
* end (e.g. -1 for the last axis). Must be in the range [-n, n).
@@ -3510,6 +3537,8 @@
* * {@link OperandType::TENSOR_INT32}
* * {@link OperandType::TENSOR_QUANT8_ASYMM}
*
+ * Supported tensor rank: from 1.
+ *
* Inputs:
* * 0: A tensor.
* * 1: A tensor of the same {@link OperandType} and compatible dimensions
@@ -3531,6 +3560,8 @@
* * {@link OperandType::TENSOR_INT32}
* * {@link OperandType::TENSOR_QUANT8_ASYMM}
*
+ * Supported tensor rank: from 1.
+ *
* Inputs:
* * 0: A tensor.
* * 1: A tensor of the same {@link OperandType} and compatible dimensions
@@ -4638,24 +4669,24 @@
* * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
* the input. Zero batches is supported for this tensor.
* * 1: An {@link OperandType::INT32} scalar, specifying the output
- * height of the output tensor.
- * * 2: An {@link OperandType::INT32} scalar, specifying the output
* width of the output tensor.
+ * * 2: An {@link OperandType::INT32} scalar, specifying the output
+ * height of the output tensor.
* * 3: An {@link OperandType::BOOL} scalar, default to false.
* Set to true to specify NCHW data layout for input0 and output0.
*
* Inputs (resizing by scale):
* * 0: A 4-D tensor, of shape [batches, height, width, depth], specifying
* the input. Zero batches is supported for this tensor.
- * * 1: A scalar, specifying height_scale, the scaling factor of the height
+ * * 1: A scalar, specifying width_scale, the scaling factor of the width
* dimension from the input tensor to the output tensor. The output
- * height is calculated as new_height = floor(height * height_scale).
+ * width is calculated as new_width = floor(width * width_scale).
* The scalar must be of {@link OperandType::FLOAT16} if input0 is
* of {@link OperandType::TENSOR_FLOAT16} and of
* {@link OperandType::FLOAT32} otherwise.
- * * 2: A scalar, specifying width_scale, the scaling factor of the width
+ * * 2: A scalar, specifying height_scale, the scaling factor of the height
* dimension from the input tensor to the output tensor. The output
- * width is calculated as new_width = floor(width * width_scale).
+ * height is calculated as new_height = floor(height * height_scale).
* The scalar must be of {@link OperandType::FLOAT16} if input0 is
* of {@link OperandType::TENSOR_FLOAT16} and of
* {@link OperandType::FLOAT32} otherwise.
diff --git a/neuralnetworks/1.2/vts/functional/Android.bp b/neuralnetworks/1.2/vts/functional/Android.bp
index 891b414..6c26820 100644
--- a/neuralnetworks/1.2/vts/functional/Android.bp
+++ b/neuralnetworks/1.2/vts/functional/Android.bp
@@ -20,6 +20,7 @@
defaults: ["VtsHalNeuralNetworksTargetTestDefaults"],
srcs: [
"GeneratedTestsV1_0.cpp",
+ "ValidateBurst.cpp",
],
cflags: [
"-DNN_TEST_DYNAMIC_OUTPUT_SHAPE"
@@ -32,6 +33,7 @@
defaults: ["VtsHalNeuralNetworksTargetTestDefaults"],
srcs: [
"GeneratedTestsV1_1.cpp",
+ "ValidateBurst.cpp",
],
cflags: [
"-DNN_TEST_DYNAMIC_OUTPUT_SHAPE"
@@ -46,6 +48,7 @@
"BasicTests.cpp",
"CompilationCachingTests.cpp",
"GeneratedTests.cpp",
+ "ValidateBurst.cpp",
],
cflags: [
"-DNN_TEST_DYNAMIC_OUTPUT_SHAPE"
@@ -58,6 +61,7 @@
srcs: [
"BasicTests.cpp",
"GeneratedTests.cpp",
+ "ValidateBurst.cpp",
],
cflags: [
"-DNN_TEST_DYNAMIC_OUTPUT_SHAPE",
diff --git a/neuralnetworks/1.2/vts/functional/CompilationCachingTests.cpp b/neuralnetworks/1.2/vts/functional/CompilationCachingTests.cpp
index 167fc09..4411b90 100644
--- a/neuralnetworks/1.2/vts/functional/CompilationCachingTests.cpp
+++ b/neuralnetworks/1.2/vts/functional/CompilationCachingTests.cpp
@@ -16,21 +16,22 @@
#define LOG_TAG "neuralnetworks_hidl_hal_test"
-#include "VtsHalNeuralnetworks.h"
+#include <android-base/logging.h>
+#include <android/hidl/memory/1.0/IMemory.h>
+#include <ftw.h>
+#include <gtest/gtest.h>
+#include <hidlmemory/mapping.h>
+#include <unistd.h>
+
+#include <cstdio>
+#include <cstdlib>
+#include <random>
#include "Callbacks.h"
#include "GeneratedTestHarness.h"
#include "TestHarness.h"
#include "Utils.h"
-
-#include <android-base/logging.h>
-#include <android/hidl/memory/1.0/IMemory.h>
-#include <hidlmemory/mapping.h>
-#include <cstdio>
-#include <cstdlib>
-#include <random>
-
-#include <gtest/gtest.h>
+#include "VtsHalNeuralnetworks.h"
namespace android {
namespace hardware {
@@ -44,9 +45,9 @@
using ::android::nn::allocateSharedMemory;
using ::test_helper::MixedTypedExample;
-namespace {
+namespace float32_model {
-// In frameworks/ml/nn/runtime/tests/generated/, creates a hidl model of mobilenet.
+// In frameworks/ml/nn/runtime/test/generated/, creates a hidl model of float32 mobilenet.
#include "examples/mobilenet_224_gender_basic_fixed.example.cpp"
#include "vts_models/mobilenet_224_gender_basic_fixed.model.cpp"
@@ -54,6 +55,44 @@
[[maybe_unused]] auto dummy_createTestModel = createTestModel_dynamic_output_shape;
[[maybe_unused]] auto dummy_get_examples = get_examples_dynamic_output_shape;
+// MixedTypedExample is defined in frameworks/ml/nn/tools/test_generator/include/TestHarness.h.
+// This function assumes the operation is always ADD.
+std::vector<MixedTypedExample> getLargeModelExamples(uint32_t len) {
+ float outputValue = 1.0f + static_cast<float>(len);
+ return {{.operands = {
+ // Input
+ {.operandDimensions = {{0, {1}}}, .float32Operands = {{0, {1.0f}}}},
+ // Output
+ {.operandDimensions = {{0, {1}}}, .float32Operands = {{0, {outputValue}}}}}}};
+}
+
+} // namespace float32_model
+
+namespace quant8_model {
+
+// In frameworks/ml/nn/runtime/test/generated/, creates a hidl model of quant8 mobilenet.
+#include "examples/mobilenet_quantized.example.cpp"
+#include "vts_models/mobilenet_quantized.model.cpp"
+
+// Prevent the compiler from complaining about an otherwise unused function.
+[[maybe_unused]] auto dummy_createTestModel = createTestModel_dynamic_output_shape;
+[[maybe_unused]] auto dummy_get_examples = get_examples_dynamic_output_shape;
+
+// MixedTypedExample is defined in frameworks/ml/nn/tools/test_generator/include/TestHarness.h.
+// This function assumes the operation is always ADD.
+std::vector<MixedTypedExample> getLargeModelExamples(uint32_t len) {
+ uint8_t outputValue = 1 + static_cast<uint8_t>(len);
+ return {{.operands = {// Input
+ {.operandDimensions = {{0, {1}}}, .quant8AsymmOperands = {{0, {1}}}},
+ // Output
+ {.operandDimensions = {{0, {1}}},
+ .quant8AsymmOperands = {{0, {outputValue}}}}}}};
+}
+
+} // namespace quant8_model
+
+namespace {
+
enum class AccessMode { READ_WRITE, READ_ONLY, WRITE_ONLY };
// Creates cache handles based on provided file groups.
@@ -89,11 +128,137 @@
createCacheHandles(fileGroups, std::vector<AccessMode>(fileGroups.size(), mode), handles);
}
+// Create a chain of broadcast operations. The second operand is always constant tensor [1].
+// For simplicity, activation scalar is shared. The second operand is not shared
+// in the model to let driver maintain a non-trivial size of constant data and the corresponding
+// data locations in cache.
+//
+// --------- activation --------
+// ↓ ↓ ↓ ↓
+// E.g. input -> ADD -> ADD -> ADD -> ... -> ADD -> output
+// ↑ ↑ ↑ ↑
+// [1] [1] [1] [1]
+//
+// This function assumes the operation is either ADD or MUL.
+template <typename CppType, OperandType operandType>
+Model createLargeTestModelImpl(OperationType op, uint32_t len) {
+ EXPECT_TRUE(op == OperationType::ADD || op == OperationType::MUL);
+
+ // Model operations and operands.
+ std::vector<Operation> operations(len);
+ std::vector<Operand> operands(len * 2 + 2);
+
+ // The constant buffer pool. This contains the activation scalar, followed by the
+ // per-operation constant operands.
+ std::vector<uint8_t> operandValues(sizeof(int32_t) + len * sizeof(CppType));
+
+ // The activation scalar, value = 0.
+ operands[0] = {
+ .type = OperandType::INT32,
+ .dimensions = {},
+ .numberOfConsumers = len,
+ .scale = 0.0f,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::CONSTANT_COPY,
+ .location = {.poolIndex = 0, .offset = 0, .length = sizeof(int32_t)},
+ };
+ memset(operandValues.data(), 0, sizeof(int32_t));
+
+ // The buffer value of the constant second operand. The logical value is always 1.0f.
+ CppType bufferValue;
+ // The scale of the first and second operand.
+ float scale1, scale2;
+ if (operandType == OperandType::TENSOR_FLOAT32) {
+ bufferValue = 1.0f;
+ scale1 = 0.0f;
+ scale2 = 0.0f;
+ } else if (op == OperationType::ADD) {
+ bufferValue = 1;
+ scale1 = 1.0f;
+ scale2 = 1.0f;
+ } else {
+ // To satisfy the constraint on quant8 MUL: input0.scale * input1.scale < output.scale,
+ // set input1 to have scale = 0.5f and bufferValue = 2, i.e. 1.0f in floating point.
+ bufferValue = 2;
+ scale1 = 1.0f;
+ scale2 = 0.5f;
+ }
+
+ for (uint32_t i = 0; i < len; i++) {
+ const uint32_t firstInputIndex = i * 2 + 1;
+ const uint32_t secondInputIndex = firstInputIndex + 1;
+ const uint32_t outputIndex = secondInputIndex + 1;
+
+ // The first operation input.
+ operands[firstInputIndex] = {
+ .type = operandType,
+ .dimensions = {1},
+ .numberOfConsumers = 1,
+ .scale = scale1,
+ .zeroPoint = 0,
+ .lifetime = (i == 0 ? OperandLifeTime::MODEL_INPUT
+ : OperandLifeTime::TEMPORARY_VARIABLE),
+ .location = {},
+ };
+
+ // The second operation input, value = 1.
+ operands[secondInputIndex] = {
+ .type = operandType,
+ .dimensions = {1},
+ .numberOfConsumers = 1,
+ .scale = scale2,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::CONSTANT_COPY,
+ .location = {.poolIndex = 0,
+ .offset = static_cast<uint32_t>(i * sizeof(CppType) + sizeof(int32_t)),
+ .length = sizeof(CppType)},
+ };
+ memcpy(operandValues.data() + sizeof(int32_t) + i * sizeof(CppType), &bufferValue,
+ sizeof(CppType));
+
+ // The operation. All operations share the same activation scalar.
+ // The output operand is created as an input in the next iteration of the loop, in the case
+ // of all but the last member of the chain; and after the loop as a model output, in the
+ // case of the last member of the chain.
+ operations[i] = {
+ .type = op,
+ .inputs = {firstInputIndex, secondInputIndex, /*activation scalar*/ 0},
+ .outputs = {outputIndex},
+ };
+ }
+
+ // The model output.
+ operands.back() = {
+ .type = operandType,
+ .dimensions = {1},
+ .numberOfConsumers = 0,
+ .scale = scale1,
+ .zeroPoint = 0,
+ .lifetime = OperandLifeTime::MODEL_OUTPUT,
+ .location = {},
+ };
+
+ const std::vector<uint32_t> inputIndexes = {1};
+ const std::vector<uint32_t> outputIndexes = {len * 2 + 1};
+ const std::vector<hidl_memory> pools = {};
+
+ return {
+ .operands = operands,
+ .operations = operations,
+ .inputIndexes = inputIndexes,
+ .outputIndexes = outputIndexes,
+ .operandValues = operandValues,
+ .pools = pools,
+ };
+}
+
} // namespace
// Tag for the compilation caching tests.
-class CompilationCachingTest : public NeuralnetworksHidlTest {
+class CompilationCachingTestBase : public NeuralnetworksHidlTest {
protected:
+ CompilationCachingTestBase(OperandType type) : kOperandType(type) {}
+
void SetUp() override {
NeuralnetworksHidlTest::SetUp();
ASSERT_NE(device.get(), nullptr);
@@ -139,21 +304,53 @@
}
void TearDown() override {
- // The tmp directory is only removed when the driver reports caching not supported,
- // otherwise it is kept for debugging purpose.
- if (!mIsCachingSupported) {
- remove(mTmpCache.c_str());
- rmdir(mCacheDir.c_str());
+ // If the test passes, remove the tmp directory. Otherwise, keep it for debugging purposes.
+ if (!::testing::Test::HasFailure()) {
+ // Recursively remove the cache directory specified by mCacheDir.
+ auto callback = [](const char* entry, const struct stat*, int, struct FTW*) {
+ return remove(entry);
+ };
+ nftw(mCacheDir.c_str(), callback, 128, FTW_DEPTH | FTW_MOUNT | FTW_PHYS);
}
NeuralnetworksHidlTest::TearDown();
}
- void saveModelToCache(const V1_2::Model& model, const hidl_vec<hidl_handle>& modelCache,
- const hidl_vec<hidl_handle>& dataCache, bool* supported,
- sp<IPreparedModel>* preparedModel = nullptr) {
- if (preparedModel != nullptr) *preparedModel = nullptr;
+ // Model and examples creators. According to kOperandType, the following methods will return
+ // either float32 model/examples or the quant8 variant.
+ Model createTestModel() {
+ if (kOperandType == OperandType::TENSOR_FLOAT32) {
+ return float32_model::createTestModel();
+ } else {
+ return quant8_model::createTestModel();
+ }
+ }
- // See if service can handle model.
+ std::vector<MixedTypedExample> get_examples() {
+ if (kOperandType == OperandType::TENSOR_FLOAT32) {
+ return float32_model::get_examples();
+ } else {
+ return quant8_model::get_examples();
+ }
+ }
+
+ Model createLargeTestModel(OperationType op, uint32_t len) {
+ if (kOperandType == OperandType::TENSOR_FLOAT32) {
+ return createLargeTestModelImpl<float, OperandType::TENSOR_FLOAT32>(op, len);
+ } else {
+ return createLargeTestModelImpl<uint8_t, OperandType::TENSOR_QUANT8_ASYMM>(op, len);
+ }
+ }
+
+ std::vector<MixedTypedExample> getLargeModelExamples(uint32_t len) {
+ if (kOperandType == OperandType::TENSOR_FLOAT32) {
+ return float32_model::getLargeModelExamples(len);
+ } else {
+ return quant8_model::getLargeModelExamples(len);
+ }
+ }
+
+ // See if the service can handle the model.
+ bool isModelFullySupported(const V1_2::Model& model) {
bool fullySupportsModel = false;
Return<void> supportedCall = device->getSupportedOperations_1_2(
model,
@@ -163,9 +360,14 @@
fullySupportsModel = std::all_of(supported.begin(), supported.end(),
[](bool valid) { return valid; });
});
- ASSERT_TRUE(supportedCall.isOk());
- *supported = fullySupportsModel;
- if (!fullySupportsModel) return;
+ EXPECT_TRUE(supportedCall.isOk());
+ return fullySupportsModel;
+ }
+
+ void saveModelToCache(const V1_2::Model& model, const hidl_vec<hidl_handle>& modelCache,
+ const hidl_vec<hidl_handle>& dataCache,
+ sp<IPreparedModel>* preparedModel = nullptr) {
+ if (preparedModel != nullptr) *preparedModel = nullptr;
// Launch prepare model.
sp<PreparedModelCallback> preparedModelCallback = new PreparedModelCallback();
@@ -199,8 +401,8 @@
return false;
}
- bool checkEarlyTermination(bool supported) {
- if (!supported) {
+ bool checkEarlyTermination(const V1_2::Model& model) {
+ if (!isModelFullySupported(model)) {
LOG(INFO) << "NN VTS: Early termination of test because vendor service cannot "
"prepare model that it does not support.";
std::cout << "[ ] Early termination of test because vendor service cannot "
@@ -250,21 +452,31 @@
uint32_t mNumModelCache;
uint32_t mNumDataCache;
uint32_t mIsCachingSupported;
+
+ // The primary data type of the testModel.
+ const OperandType kOperandType;
};
-TEST_F(CompilationCachingTest, CacheSavingAndRetrieval) {
+// A parameterized fixture of CompilationCachingTestBase. Every test will run twice, with the first
+// pass running with float32 models and the second pass running with quant8 models.
+class CompilationCachingTest : public CompilationCachingTestBase,
+ public ::testing::WithParamInterface<OperandType> {
+ protected:
+ CompilationCachingTest() : CompilationCachingTestBase(GetParam()) {}
+};
+
+TEST_P(CompilationCachingTest, CacheSavingAndRetrieval) {
// Create test HIDL model and compile.
- Model testModel = createTestModel();
+ const Model testModel = createTestModel();
+ if (checkEarlyTermination(testModel)) return;
sp<IPreparedModel> preparedModel = nullptr;
// Save the compilation to cache.
{
- bool supported;
hidl_vec<hidl_handle> modelCache, dataCache;
createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
- saveModelToCache(testModel, modelCache, dataCache, &supported);
- if (checkEarlyTermination(supported)) return;
+ saveModelToCache(testModel, modelCache, dataCache);
}
// Retrieve preparedModel from cache.
@@ -294,14 +506,14 @@
/*testDynamicOutputShape=*/false);
}
-TEST_F(CompilationCachingTest, CacheSavingAndRetrievalNonZeroOffset) {
+TEST_P(CompilationCachingTest, CacheSavingAndRetrievalNonZeroOffset) {
// Create test HIDL model and compile.
- Model testModel = createTestModel();
+ const Model testModel = createTestModel();
+ if (checkEarlyTermination(testModel)) return;
sp<IPreparedModel> preparedModel = nullptr;
// Save the compilation to cache.
{
- bool supported;
hidl_vec<hidl_handle> modelCache, dataCache;
createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
@@ -318,8 +530,7 @@
write(dataCache[i].getNativeHandle()->data[0], &dummyBytes, sizeof(dummyBytes)),
sizeof(dummyBytes));
}
- saveModelToCache(testModel, modelCache, dataCache, &supported);
- if (checkEarlyTermination(supported)) return;
+ saveModelToCache(testModel, modelCache, dataCache);
}
// Retrieve preparedModel from cache.
@@ -358,13 +569,13 @@
/*testDynamicOutputShape=*/false);
}
-TEST_F(CompilationCachingTest, SaveToCacheInvalidNumCache) {
+TEST_P(CompilationCachingTest, SaveToCacheInvalidNumCache) {
// Create test HIDL model and compile.
- Model testModel = createTestModel();
+ const Model testModel = createTestModel();
+ if (checkEarlyTermination(testModel)) return;
// Test with number of model cache files greater than mNumModelCache.
{
- bool supported;
hidl_vec<hidl_handle> modelCache, dataCache;
// Pass an additional cache file for model cache.
mModelCache.push_back({mTmpCache});
@@ -372,8 +583,7 @@
createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
mModelCache.pop_back();
sp<IPreparedModel> preparedModel = nullptr;
- saveModelToCache(testModel, modelCache, dataCache, &supported, &preparedModel);
- if (checkEarlyTermination(supported)) return;
+ saveModelToCache(testModel, modelCache, dataCache, &preparedModel);
ASSERT_NE(preparedModel, nullptr);
// Execute and verify results.
generated_tests::EvaluatePreparedModel(preparedModel, [](int) { return false; },
@@ -392,7 +602,6 @@
// Test with number of model cache files smaller than mNumModelCache.
if (mModelCache.size() > 0) {
- bool supported;
hidl_vec<hidl_handle> modelCache, dataCache;
// Pop out the last cache file.
auto tmp = mModelCache.back();
@@ -401,8 +610,7 @@
createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
mModelCache.push_back(tmp);
sp<IPreparedModel> preparedModel = nullptr;
- saveModelToCache(testModel, modelCache, dataCache, &supported, &preparedModel);
- if (checkEarlyTermination(supported)) return;
+ saveModelToCache(testModel, modelCache, dataCache, &preparedModel);
ASSERT_NE(preparedModel, nullptr);
// Execute and verify results.
generated_tests::EvaluatePreparedModel(preparedModel, [](int) { return false; },
@@ -421,7 +629,6 @@
// Test with number of data cache files greater than mNumDataCache.
{
- bool supported;
hidl_vec<hidl_handle> modelCache, dataCache;
// Pass an additional cache file for data cache.
mDataCache.push_back({mTmpCache});
@@ -429,8 +636,7 @@
createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
mDataCache.pop_back();
sp<IPreparedModel> preparedModel = nullptr;
- saveModelToCache(testModel, modelCache, dataCache, &supported, &preparedModel);
- if (checkEarlyTermination(supported)) return;
+ saveModelToCache(testModel, modelCache, dataCache, &preparedModel);
ASSERT_NE(preparedModel, nullptr);
// Execute and verify results.
generated_tests::EvaluatePreparedModel(preparedModel, [](int) { return false; },
@@ -449,7 +655,6 @@
// Test with number of data cache files smaller than mNumDataCache.
if (mDataCache.size() > 0) {
- bool supported;
hidl_vec<hidl_handle> modelCache, dataCache;
// Pop out the last cache file.
auto tmp = mDataCache.back();
@@ -458,8 +663,7 @@
createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
mDataCache.push_back(tmp);
sp<IPreparedModel> preparedModel = nullptr;
- saveModelToCache(testModel, modelCache, dataCache, &supported, &preparedModel);
- if (checkEarlyTermination(supported)) return;
+ saveModelToCache(testModel, modelCache, dataCache, &preparedModel);
ASSERT_NE(preparedModel, nullptr);
// Execute and verify results.
generated_tests::EvaluatePreparedModel(preparedModel, [](int) { return false; },
@@ -477,18 +681,17 @@
}
}
-TEST_F(CompilationCachingTest, PrepareModelFromCacheInvalidNumCache) {
+TEST_P(CompilationCachingTest, PrepareModelFromCacheInvalidNumCache) {
// Create test HIDL model and compile.
- Model testModel = createTestModel();
+ const Model testModel = createTestModel();
+ if (checkEarlyTermination(testModel)) return;
// Save the compilation to cache.
{
- bool supported;
hidl_vec<hidl_handle> modelCache, dataCache;
createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
- saveModelToCache(testModel, modelCache, dataCache, &supported);
- if (checkEarlyTermination(supported)) return;
+ saveModelToCache(testModel, modelCache, dataCache);
}
// Test with number of model cache files greater than mNumModelCache.
@@ -558,13 +761,13 @@
}
}
-TEST_F(CompilationCachingTest, SaveToCacheInvalidNumFd) {
+TEST_P(CompilationCachingTest, SaveToCacheInvalidNumFd) {
// Create test HIDL model and compile.
- Model testModel = createTestModel();
+ const Model testModel = createTestModel();
+ if (checkEarlyTermination(testModel)) return;
// Go through each handle in model cache, test with NumFd greater than 1.
for (uint32_t i = 0; i < mNumModelCache; i++) {
- bool supported;
hidl_vec<hidl_handle> modelCache, dataCache;
// Pass an invalid number of fds for handle i.
mModelCache[i].push_back(mTmpCache);
@@ -572,8 +775,7 @@
createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
mModelCache[i].pop_back();
sp<IPreparedModel> preparedModel = nullptr;
- saveModelToCache(testModel, modelCache, dataCache, &supported, &preparedModel);
- if (checkEarlyTermination(supported)) return;
+ saveModelToCache(testModel, modelCache, dataCache, &preparedModel);
ASSERT_NE(preparedModel, nullptr);
// Execute and verify results.
generated_tests::EvaluatePreparedModel(preparedModel, [](int) { return false; },
@@ -592,7 +794,6 @@
// Go through each handle in model cache, test with NumFd equal to 0.
for (uint32_t i = 0; i < mNumModelCache; i++) {
- bool supported;
hidl_vec<hidl_handle> modelCache, dataCache;
// Pass an invalid number of fds for handle i.
auto tmp = mModelCache[i].back();
@@ -601,8 +802,7 @@
createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
mModelCache[i].push_back(tmp);
sp<IPreparedModel> preparedModel = nullptr;
- saveModelToCache(testModel, modelCache, dataCache, &supported, &preparedModel);
- if (checkEarlyTermination(supported)) return;
+ saveModelToCache(testModel, modelCache, dataCache, &preparedModel);
ASSERT_NE(preparedModel, nullptr);
// Execute and verify results.
generated_tests::EvaluatePreparedModel(preparedModel, [](int) { return false; },
@@ -621,7 +821,6 @@
// Go through each handle in data cache, test with NumFd greater than 1.
for (uint32_t i = 0; i < mNumDataCache; i++) {
- bool supported;
hidl_vec<hidl_handle> modelCache, dataCache;
// Pass an invalid number of fds for handle i.
mDataCache[i].push_back(mTmpCache);
@@ -629,8 +828,7 @@
createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
mDataCache[i].pop_back();
sp<IPreparedModel> preparedModel = nullptr;
- saveModelToCache(testModel, modelCache, dataCache, &supported, &preparedModel);
- if (checkEarlyTermination(supported)) return;
+ saveModelToCache(testModel, modelCache, dataCache, &preparedModel);
ASSERT_NE(preparedModel, nullptr);
// Execute and verify results.
generated_tests::EvaluatePreparedModel(preparedModel, [](int) { return false; },
@@ -649,7 +847,6 @@
// Go through each handle in data cache, test with NumFd equal to 0.
for (uint32_t i = 0; i < mNumDataCache; i++) {
- bool supported;
hidl_vec<hidl_handle> modelCache, dataCache;
// Pass an invalid number of fds for handle i.
auto tmp = mDataCache[i].back();
@@ -658,8 +855,7 @@
createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
mDataCache[i].push_back(tmp);
sp<IPreparedModel> preparedModel = nullptr;
- saveModelToCache(testModel, modelCache, dataCache, &supported, &preparedModel);
- if (checkEarlyTermination(supported)) return;
+ saveModelToCache(testModel, modelCache, dataCache, &preparedModel);
ASSERT_NE(preparedModel, nullptr);
// Execute and verify results.
generated_tests::EvaluatePreparedModel(preparedModel, [](int) { return false; },
@@ -677,18 +873,17 @@
}
}
-TEST_F(CompilationCachingTest, PrepareModelFromCacheInvalidNumFd) {
+TEST_P(CompilationCachingTest, PrepareModelFromCacheInvalidNumFd) {
// Create test HIDL model and compile.
- Model testModel = createTestModel();
+ const Model testModel = createTestModel();
+ if (checkEarlyTermination(testModel)) return;
// Save the compilation to cache.
{
- bool supported;
hidl_vec<hidl_handle> modelCache, dataCache;
createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
- saveModelToCache(testModel, modelCache, dataCache, &supported);
- if (checkEarlyTermination(supported)) return;
+ saveModelToCache(testModel, modelCache, dataCache);
}
// Go through each handle in model cache, test with NumFd greater than 1.
@@ -758,23 +953,22 @@
}
}
-TEST_F(CompilationCachingTest, SaveToCacheInvalidAccessMode) {
+TEST_P(CompilationCachingTest, SaveToCacheInvalidAccessMode) {
// Create test HIDL model and compile.
- Model testModel = createTestModel();
+ const Model testModel = createTestModel();
+ if (checkEarlyTermination(testModel)) return;
std::vector<AccessMode> modelCacheMode(mNumModelCache, AccessMode::READ_WRITE);
std::vector<AccessMode> dataCacheMode(mNumDataCache, AccessMode::READ_WRITE);
// Go through each handle in model cache, test with invalid access mode.
for (uint32_t i = 0; i < mNumModelCache; i++) {
- bool supported;
hidl_vec<hidl_handle> modelCache, dataCache;
modelCacheMode[i] = AccessMode::READ_ONLY;
createCacheHandles(mModelCache, modelCacheMode, &modelCache);
createCacheHandles(mDataCache, dataCacheMode, &dataCache);
modelCacheMode[i] = AccessMode::READ_WRITE;
sp<IPreparedModel> preparedModel = nullptr;
- saveModelToCache(testModel, modelCache, dataCache, &supported, &preparedModel);
- if (checkEarlyTermination(supported)) return;
+ saveModelToCache(testModel, modelCache, dataCache, &preparedModel);
ASSERT_NE(preparedModel, nullptr);
// Execute and verify results.
generated_tests::EvaluatePreparedModel(preparedModel, [](int) { return false; },
@@ -793,15 +987,13 @@
// Go through each handle in data cache, test with invalid access mode.
for (uint32_t i = 0; i < mNumDataCache; i++) {
- bool supported;
hidl_vec<hidl_handle> modelCache, dataCache;
dataCacheMode[i] = AccessMode::READ_ONLY;
createCacheHandles(mModelCache, modelCacheMode, &modelCache);
createCacheHandles(mDataCache, dataCacheMode, &dataCache);
dataCacheMode[i] = AccessMode::READ_WRITE;
sp<IPreparedModel> preparedModel = nullptr;
- saveModelToCache(testModel, modelCache, dataCache, &supported, &preparedModel);
- if (checkEarlyTermination(supported)) return;
+ saveModelToCache(testModel, modelCache, dataCache, &preparedModel);
ASSERT_NE(preparedModel, nullptr);
// Execute and verify results.
generated_tests::EvaluatePreparedModel(preparedModel, [](int) { return false; },
@@ -819,20 +1011,19 @@
}
}
-TEST_F(CompilationCachingTest, PrepareModelFromCacheInvalidAccessMode) {
+TEST_P(CompilationCachingTest, PrepareModelFromCacheInvalidAccessMode) {
// Create test HIDL model and compile.
- Model testModel = createTestModel();
+ const Model testModel = createTestModel();
+ if (checkEarlyTermination(testModel)) return;
std::vector<AccessMode> modelCacheMode(mNumModelCache, AccessMode::READ_WRITE);
std::vector<AccessMode> dataCacheMode(mNumDataCache, AccessMode::READ_WRITE);
// Save the compilation to cache.
{
- bool supported;
hidl_vec<hidl_handle> modelCache, dataCache;
createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
- saveModelToCache(testModel, modelCache, dataCache, &supported);
- if (checkEarlyTermination(supported)) return;
+ saveModelToCache(testModel, modelCache, dataCache);
}
// Go through each handle in model cache, test with invalid access mode.
@@ -864,129 +1055,198 @@
}
}
-class CompilationCachingSecurityTest : public CompilationCachingTest,
- public ::testing::WithParamInterface<uint32_t> {
- protected:
- void SetUp() {
- CompilationCachingTest::SetUp();
- generator.seed(kSeed);
- }
+// Copy file contents between file groups.
+// The outer vector corresponds to handles and the inner vector is for fds held by each handle.
+// The outer vector sizes must match and the inner vectors must have size = 1.
+static void copyCacheFiles(const std::vector<std::vector<std::string>>& from,
+ const std::vector<std::vector<std::string>>& to) {
+ constexpr size_t kBufferSize = 1000000;
+ uint8_t buffer[kBufferSize];
- // Get a random integer within a closed range [lower, upper].
- template <typename T>
- T getRandomInt(T lower, T upper) {
- std::uniform_int_distribution<T> dis(lower, upper);
- return dis(generator);
- }
+ ASSERT_EQ(from.size(), to.size());
+ for (uint32_t i = 0; i < from.size(); i++) {
+ ASSERT_EQ(from[i].size(), 1u);
+ ASSERT_EQ(to[i].size(), 1u);
+ int fromFd = open(from[i][0].c_str(), O_RDONLY);
+ int toFd = open(to[i][0].c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
+ ASSERT_GE(fromFd, 0);
+ ASSERT_GE(toFd, 0);
- const uint32_t kSeed = GetParam();
- std::mt19937 generator;
-};
-
-TEST_P(CompilationCachingSecurityTest, CorruptedSecuritySensitiveCache) {
- if (!mIsCachingSupported) return;
-
- // Create test HIDL model and compile.
- Model testModel = createTestModel();
-
- for (uint32_t i = 0; i < mNumModelCache; i++) {
- // Save the compilation to cache.
- {
- bool supported;
- hidl_vec<hidl_handle> modelCache, dataCache;
- createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
- createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
- saveModelToCache(testModel, modelCache, dataCache, &supported);
- if (checkEarlyTermination(supported)) return;
+ ssize_t readBytes;
+ while ((readBytes = read(fromFd, &buffer, kBufferSize)) > 0) {
+ ASSERT_EQ(write(toFd, &buffer, readBytes), readBytes);
}
+ ASSERT_GE(readBytes, 0);
- // Randomly flip one single bit of the cache entry.
- FILE* pFile = fopen(mModelCache[i][0].c_str(), "r+");
- ASSERT_EQ(fseek(pFile, 0, SEEK_END), 0);
- long int fileSize = ftell(pFile);
- if (fileSize == 0) {
- fclose(pFile);
- continue;
- }
- ASSERT_EQ(fseek(pFile, getRandomInt(0l, fileSize - 1), SEEK_SET), 0);
- int readByte = fgetc(pFile);
- ASSERT_NE(readByte, EOF);
- ASSERT_EQ(fseek(pFile, -1, SEEK_CUR), 0);
- ASSERT_NE(fputc(static_cast<uint8_t>(readByte) ^ (1U << getRandomInt(0, 7)), pFile), EOF);
- fclose(pFile);
-
- // Retrieve preparedModel from cache, expect failure.
- {
- sp<IPreparedModel> preparedModel = nullptr;
- ErrorStatus status;
- hidl_vec<hidl_handle> modelCache, dataCache;
- createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
- createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
- prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
- ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE);
- ASSERT_EQ(preparedModel, nullptr);
- }
+ close(fromFd);
+ close(toFd);
}
}
-TEST_P(CompilationCachingSecurityTest, WrongLengthSecuritySensitiveCache) {
+// Number of operations in the large test model.
+constexpr uint32_t kLargeModelSize = 100;
+constexpr uint32_t kNumIterationsTOCTOU = 100;
+
+TEST_P(CompilationCachingTest, SaveToCache_TOCTOU) {
if (!mIsCachingSupported) return;
- // Create test HIDL model and compile.
- Model testModel = createTestModel();
+ // Create test models and check if fully supported by the service.
+ const Model testModelMul = createLargeTestModel(OperationType::MUL, kLargeModelSize);
+ if (checkEarlyTermination(testModelMul)) return;
+ const Model testModelAdd = createLargeTestModel(OperationType::ADD, kLargeModelSize);
+ if (checkEarlyTermination(testModelAdd)) return;
- for (uint32_t i = 0; i < mNumModelCache; i++) {
- // Save the compilation to cache.
- {
- bool supported;
- hidl_vec<hidl_handle> modelCache, dataCache;
- createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
- createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
- saveModelToCache(testModel, modelCache, dataCache, &supported);
- if (checkEarlyTermination(supported)) return;
- }
-
- // Randomly append bytes to the cache entry.
- FILE* pFile = fopen(mModelCache[i][0].c_str(), "a");
- uint32_t appendLength = getRandomInt(1, 256);
- for (uint32_t i = 0; i < appendLength; i++) {
- ASSERT_NE(fputc(getRandomInt<uint8_t>(0, 255), pFile), EOF);
- }
- fclose(pFile);
-
- // Retrieve preparedModel from cache, expect failure.
- {
- sp<IPreparedModel> preparedModel = nullptr;
- ErrorStatus status;
- hidl_vec<hidl_handle> modelCache, dataCache;
- createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
- createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
- prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
- ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE);
- ASSERT_EQ(preparedModel, nullptr);
- }
+ // Save the testModelMul compilation to cache.
+ auto modelCacheMul = mModelCache;
+ for (auto& cache : modelCacheMul) {
+ cache[0].append("_mul");
}
-}
-
-TEST_P(CompilationCachingSecurityTest, WrongToken) {
- if (!mIsCachingSupported) return;
-
- // Create test HIDL model and compile.
- Model testModel = createTestModel();
-
- // Save the compilation to cache.
{
- bool supported;
+ hidl_vec<hidl_handle> modelCache, dataCache;
+ createCacheHandles(modelCacheMul, AccessMode::READ_WRITE, &modelCache);
+ createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ saveModelToCache(testModelMul, modelCache, dataCache);
+ }
+
+ // Use a different token for testModelAdd.
+ mToken[0]++;
+
+ // This test is probabilistic, so we run it multiple times.
+ for (uint32_t i = 0; i < kNumIterationsTOCTOU; i++) {
+ // Save the testModelAdd compilation to cache.
+ {
+ hidl_vec<hidl_handle> modelCache, dataCache;
+ createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+
+ // Spawn a thread to copy the cache content concurrently while saving to cache.
+ std::thread thread(copyCacheFiles, std::cref(modelCacheMul), std::cref(mModelCache));
+ saveModelToCache(testModelAdd, modelCache, dataCache);
+ thread.join();
+ }
+
+ // Retrieve preparedModel from cache.
+ {
+ sp<IPreparedModel> preparedModel = nullptr;
+ ErrorStatus status;
+ hidl_vec<hidl_handle> modelCache, dataCache;
+ createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+
+ // The preparation may fail or succeed, but must not crash. If the preparation succeeds,
+ // the prepared model must be executed with the correct result and not crash.
+ if (status != ErrorStatus::NONE) {
+ ASSERT_EQ(preparedModel, nullptr);
+ } else {
+ ASSERT_NE(preparedModel, nullptr);
+ generated_tests::EvaluatePreparedModel(
+ preparedModel, [](int) { return false; },
+ getLargeModelExamples(kLargeModelSize),
+ testModelAdd.relaxComputationFloat32toFloat16,
+ /*testDynamicOutputShape=*/false);
+ }
+ }
+ }
+}
+
+TEST_P(CompilationCachingTest, PrepareFromCache_TOCTOU) {
+ if (!mIsCachingSupported) return;
+
+ // Create test models and check if fully supported by the service.
+ const Model testModelMul = createLargeTestModel(OperationType::MUL, kLargeModelSize);
+ if (checkEarlyTermination(testModelMul)) return;
+ const Model testModelAdd = createLargeTestModel(OperationType::ADD, kLargeModelSize);
+ if (checkEarlyTermination(testModelAdd)) return;
+
+ // Save the testModelMul compilation to cache.
+ auto modelCacheMul = mModelCache;
+ for (auto& cache : modelCacheMul) {
+ cache[0].append("_mul");
+ }
+ {
+ hidl_vec<hidl_handle> modelCache, dataCache;
+ createCacheHandles(modelCacheMul, AccessMode::READ_WRITE, &modelCache);
+ createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ saveModelToCache(testModelMul, modelCache, dataCache);
+ }
+
+ // Use a different token for testModelAdd.
+ mToken[0]++;
+
+ // This test is probabilistic, so we run it multiple times.
+ for (uint32_t i = 0; i < kNumIterationsTOCTOU; i++) {
+ // Save the testModelAdd compilation to cache.
+ {
+ hidl_vec<hidl_handle> modelCache, dataCache;
+ createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ saveModelToCache(testModelAdd, modelCache, dataCache);
+ }
+
+ // Retrieve preparedModel from cache.
+ {
+ sp<IPreparedModel> preparedModel = nullptr;
+ ErrorStatus status;
+ hidl_vec<hidl_handle> modelCache, dataCache;
+ createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+
+ // Spawn a thread to copy the cache content concurrently while preparing from cache.
+ std::thread thread(copyCacheFiles, std::cref(modelCacheMul), std::cref(mModelCache));
+ prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+ thread.join();
+
+ // The preparation may fail or succeed, but must not crash. If the preparation succeeds,
+ // the prepared model must be executed with the correct result and not crash.
+ if (status != ErrorStatus::NONE) {
+ ASSERT_EQ(preparedModel, nullptr);
+ } else {
+ ASSERT_NE(preparedModel, nullptr);
+ generated_tests::EvaluatePreparedModel(
+ preparedModel, [](int) { return false; },
+ getLargeModelExamples(kLargeModelSize),
+ testModelAdd.relaxComputationFloat32toFloat16,
+ /*testDynamicOutputShape=*/false);
+ }
+ }
+ }
+}
+
+TEST_P(CompilationCachingTest, ReplaceSecuritySensitiveCache) {
+ if (!mIsCachingSupported) return;
+
+ // Create test models and check if fully supported by the service.
+ const Model testModelMul = createLargeTestModel(OperationType::MUL, kLargeModelSize);
+ if (checkEarlyTermination(testModelMul)) return;
+ const Model testModelAdd = createLargeTestModel(OperationType::ADD, kLargeModelSize);
+ if (checkEarlyTermination(testModelAdd)) return;
+
+ // Save the testModelMul compilation to cache.
+ auto modelCacheMul = mModelCache;
+ for (auto& cache : modelCacheMul) {
+ cache[0].append("_mul");
+ }
+ {
+ hidl_vec<hidl_handle> modelCache, dataCache;
+ createCacheHandles(modelCacheMul, AccessMode::READ_WRITE, &modelCache);
+ createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ saveModelToCache(testModelMul, modelCache, dataCache);
+ }
+
+ // Use a different token for testModelAdd.
+ mToken[0]++;
+
+ // Save the testModelAdd compilation to cache.
+ {
hidl_vec<hidl_handle> modelCache, dataCache;
createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
- saveModelToCache(testModel, modelCache, dataCache, &supported);
- if (checkEarlyTermination(supported)) return;
+ saveModelToCache(testModelAdd, modelCache, dataCache);
}
- // Randomly flip one single bit in mToken.
- uint32_t ind = getRandomInt(0u, static_cast<uint32_t>(Constant::BYTE_SIZE_OF_CACHE_TOKEN) - 1);
- mToken[ind] ^= (1U << getRandomInt(0, 7));
+ // Replace the model cache of testModelAdd with testModelMul.
+ copyCacheFiles(modelCacheMul, mModelCache);
// Retrieve the preparedModel from cache, expect failure.
{
@@ -1001,8 +1261,153 @@
}
}
+static const auto kOperandTypeChoices =
+ ::testing::Values(OperandType::TENSOR_FLOAT32, OperandType::TENSOR_QUANT8_ASYMM);
+
+INSTANTIATE_TEST_CASE_P(TestCompilationCaching, CompilationCachingTest, kOperandTypeChoices);
+
+class CompilationCachingSecurityTest
+ : public CompilationCachingTestBase,
+ public ::testing::WithParamInterface<std::tuple<OperandType, uint32_t>> {
+ protected:
+ CompilationCachingSecurityTest() : CompilationCachingTestBase(std::get<0>(GetParam())) {}
+
+ void SetUp() {
+ CompilationCachingTestBase::SetUp();
+ generator.seed(kSeed);
+ }
+
+ // Get a random integer within a closed range [lower, upper].
+ template <typename T>
+ T getRandomInt(T lower, T upper) {
+ std::uniform_int_distribution<T> dis(lower, upper);
+ return dis(generator);
+ }
+
+ // Randomly flip one single bit of the cache entry.
+ void flipOneBitOfCache(const std::string& filename, bool* skip) {
+ FILE* pFile = fopen(filename.c_str(), "r+");
+ ASSERT_EQ(fseek(pFile, 0, SEEK_END), 0);
+ long int fileSize = ftell(pFile);
+ if (fileSize == 0) {
+ fclose(pFile);
+ *skip = true;
+ return;
+ }
+ ASSERT_EQ(fseek(pFile, getRandomInt(0l, fileSize - 1), SEEK_SET), 0);
+ int readByte = fgetc(pFile);
+ ASSERT_NE(readByte, EOF);
+ ASSERT_EQ(fseek(pFile, -1, SEEK_CUR), 0);
+ ASSERT_NE(fputc(static_cast<uint8_t>(readByte) ^ (1U << getRandomInt(0, 7)), pFile), EOF);
+ fclose(pFile);
+ *skip = false;
+ }
+
+ // Randomly append bytes to the cache entry.
+ void appendBytesToCache(const std::string& filename, bool* skip) {
+ FILE* pFile = fopen(filename.c_str(), "a");
+ uint32_t appendLength = getRandomInt(1, 256);
+ for (uint32_t i = 0; i < appendLength; i++) {
+ ASSERT_NE(fputc(getRandomInt<uint8_t>(0, 255), pFile), EOF);
+ }
+ fclose(pFile);
+ *skip = false;
+ }
+
+ enum class ExpectedResult { GENERAL_FAILURE, NOT_CRASH };
+
+ // Test if the driver behaves as expected when given corrupted cache or token.
+ // The modifier will be invoked after save to cache but before prepare from cache.
+ // The modifier accepts one pointer argument "skip" as the returning value, indicating
+ // whether the test should be skipped or not.
+ void testCorruptedCache(ExpectedResult expected, std::function<void(bool*)> modifier) {
+ const Model testModel = createTestModel();
+ if (checkEarlyTermination(testModel)) return;
+
+ // Save the compilation to cache.
+ {
+ hidl_vec<hidl_handle> modelCache, dataCache;
+ createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ saveModelToCache(testModel, modelCache, dataCache);
+ }
+
+ bool skip = false;
+ modifier(&skip);
+ if (skip) return;
+
+ // Retrieve preparedModel from cache.
+ {
+ sp<IPreparedModel> preparedModel = nullptr;
+ ErrorStatus status;
+ hidl_vec<hidl_handle> modelCache, dataCache;
+ createCacheHandles(mModelCache, AccessMode::READ_WRITE, &modelCache);
+ createCacheHandles(mDataCache, AccessMode::READ_WRITE, &dataCache);
+ prepareModelFromCache(modelCache, dataCache, &preparedModel, &status);
+
+ switch (expected) {
+ case ExpectedResult::GENERAL_FAILURE:
+ ASSERT_EQ(status, ErrorStatus::GENERAL_FAILURE);
+ ASSERT_EQ(preparedModel, nullptr);
+ break;
+ case ExpectedResult::NOT_CRASH:
+ ASSERT_EQ(preparedModel == nullptr, status != ErrorStatus::NONE);
+ break;
+ default:
+ FAIL();
+ }
+ }
+ }
+
+ const uint32_t kSeed = std::get<1>(GetParam());
+ std::mt19937 generator;
+};
+
+TEST_P(CompilationCachingSecurityTest, CorruptedModelCache) {
+ if (!mIsCachingSupported) return;
+ for (uint32_t i = 0; i < mNumModelCache; i++) {
+ testCorruptedCache(ExpectedResult::GENERAL_FAILURE,
+ [this, i](bool* skip) { flipOneBitOfCache(mModelCache[i][0], skip); });
+ }
+}
+
+TEST_P(CompilationCachingSecurityTest, WrongLengthModelCache) {
+ if (!mIsCachingSupported) return;
+ for (uint32_t i = 0; i < mNumModelCache; i++) {
+ testCorruptedCache(ExpectedResult::GENERAL_FAILURE,
+ [this, i](bool* skip) { appendBytesToCache(mModelCache[i][0], skip); });
+ }
+}
+
+TEST_P(CompilationCachingSecurityTest, CorruptedDataCache) {
+ if (!mIsCachingSupported) return;
+ for (uint32_t i = 0; i < mNumDataCache; i++) {
+ testCorruptedCache(ExpectedResult::NOT_CRASH,
+ [this, i](bool* skip) { flipOneBitOfCache(mDataCache[i][0], skip); });
+ }
+}
+
+TEST_P(CompilationCachingSecurityTest, WrongLengthDataCache) {
+ if (!mIsCachingSupported) return;
+ for (uint32_t i = 0; i < mNumDataCache; i++) {
+ testCorruptedCache(ExpectedResult::NOT_CRASH,
+ [this, i](bool* skip) { appendBytesToCache(mDataCache[i][0], skip); });
+ }
+}
+
+TEST_P(CompilationCachingSecurityTest, WrongToken) {
+ if (!mIsCachingSupported) return;
+ testCorruptedCache(ExpectedResult::GENERAL_FAILURE, [this](bool* skip) {
+ // Randomly flip one single bit in mToken.
+ uint32_t ind =
+ getRandomInt(0u, static_cast<uint32_t>(Constant::BYTE_SIZE_OF_CACHE_TOKEN) - 1);
+ mToken[ind] ^= (1U << getRandomInt(0, 7));
+ *skip = false;
+ });
+}
+
INSTANTIATE_TEST_CASE_P(TestCompilationCaching, CompilationCachingSecurityTest,
- ::testing::Range(0U, 10U));
+ ::testing::Combine(kOperandTypeChoices, ::testing::Range(0U, 10U)));
} // namespace functional
} // namespace vts
diff --git a/neuralnetworks/1.2/vts/functional/ValidateBurst.cpp b/neuralnetworks/1.2/vts/functional/ValidateBurst.cpp
new file mode 100644
index 0000000..43bd400
--- /dev/null
+++ b/neuralnetworks/1.2/vts/functional/ValidateBurst.cpp
@@ -0,0 +1,337 @@
+/*
+ * 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 "neuralnetworks_hidl_hal_test"
+
+#include "VtsHalNeuralnetworks.h"
+
+#include "Callbacks.h"
+#include "ExecutionBurstController.h"
+#include "ExecutionBurstServer.h"
+#include "TestHarness.h"
+#include "Utils.h"
+
+#include <android-base/logging.h>
+
+namespace android {
+namespace hardware {
+namespace neuralnetworks {
+namespace V1_2 {
+namespace vts {
+namespace functional {
+
+using ::android::nn::ExecutionBurstController;
+using ::android::nn::RequestChannelSender;
+using ::android::nn::ResultChannelReceiver;
+using ExecutionBurstCallback = ::android::nn::ExecutionBurstController::ExecutionBurstCallback;
+
+// This constant value represents the length of an FMQ that is large enough to
+// return a result from a burst execution for all of the generated test cases.
+constexpr size_t kExecutionBurstChannelLength = 1024;
+
+// This constant value represents a length of an FMQ that is not large enough
+// to return a result from a burst execution for some of the generated test
+// cases.
+constexpr size_t kExecutionBurstChannelSmallLength = 8;
+
+///////////////////////// UTILITY FUNCTIONS /////////////////////////
+
+static bool badTiming(Timing timing) {
+ return timing.timeOnDevice == UINT64_MAX && timing.timeInDriver == UINT64_MAX;
+}
+
+static void createBurst(const sp<IPreparedModel>& preparedModel, const sp<IBurstCallback>& callback,
+ std::unique_ptr<RequestChannelSender>* sender,
+ std::unique_ptr<ResultChannelReceiver>* receiver,
+ sp<IBurstContext>* context,
+ size_t resultChannelLength = kExecutionBurstChannelLength) {
+ ASSERT_NE(nullptr, preparedModel.get());
+ ASSERT_NE(nullptr, sender);
+ ASSERT_NE(nullptr, receiver);
+ ASSERT_NE(nullptr, context);
+
+ // create FMQ objects
+ auto [fmqRequestChannel, fmqRequestDescriptor] =
+ RequestChannelSender::create(kExecutionBurstChannelLength, /*blocking=*/true);
+ auto [fmqResultChannel, fmqResultDescriptor] =
+ ResultChannelReceiver::create(resultChannelLength, /*blocking=*/true);
+ ASSERT_NE(nullptr, fmqRequestChannel.get());
+ ASSERT_NE(nullptr, fmqResultChannel.get());
+ ASSERT_NE(nullptr, fmqRequestDescriptor);
+ ASSERT_NE(nullptr, fmqResultDescriptor);
+
+ // configure burst
+ ErrorStatus errorStatus;
+ sp<IBurstContext> burstContext;
+ const Return<void> ret = preparedModel->configureExecutionBurst(
+ callback, *fmqRequestDescriptor, *fmqResultDescriptor,
+ [&errorStatus, &burstContext](ErrorStatus status, const sp<IBurstContext>& context) {
+ errorStatus = status;
+ burstContext = context;
+ });
+ ASSERT_TRUE(ret.isOk());
+ ASSERT_EQ(ErrorStatus::NONE, errorStatus);
+ ASSERT_NE(nullptr, burstContext.get());
+
+ // return values
+ *sender = std::move(fmqRequestChannel);
+ *receiver = std::move(fmqResultChannel);
+ *context = burstContext;
+}
+
+static void createBurstWithResultChannelLength(
+ const sp<IPreparedModel>& preparedModel, size_t resultChannelLength,
+ std::shared_ptr<ExecutionBurstController>* controller) {
+ ASSERT_NE(nullptr, preparedModel.get());
+ ASSERT_NE(nullptr, controller);
+
+ // create FMQ objects
+ std::unique_ptr<RequestChannelSender> sender;
+ std::unique_ptr<ResultChannelReceiver> receiver;
+ sp<ExecutionBurstCallback> callback = new ExecutionBurstCallback();
+ sp<IBurstContext> context;
+ ASSERT_NO_FATAL_FAILURE(createBurst(preparedModel, callback, &sender, &receiver, &context,
+ resultChannelLength));
+ ASSERT_NE(nullptr, sender.get());
+ ASSERT_NE(nullptr, receiver.get());
+ ASSERT_NE(nullptr, context.get());
+
+ // return values
+ *controller = std::make_shared<ExecutionBurstController>(std::move(sender), std::move(receiver),
+ context, callback);
+}
+
+// Primary validation function. This function will take a valid serialized
+// request, apply a mutation to it to invalidate the serialized request, then
+// pass it to interface calls that use the serialized request. Note that the
+// serialized request here is passed by value, and any mutation to the
+// serialized request does not leave this function.
+static void validate(RequestChannelSender* sender, ResultChannelReceiver* receiver,
+ const std::string& message, std::vector<FmqRequestDatum> serialized,
+ const std::function<void(std::vector<FmqRequestDatum>*)>& mutation) {
+ mutation(&serialized);
+
+ // skip if packet is too large to send
+ if (serialized.size() > kExecutionBurstChannelLength) {
+ return;
+ }
+
+ SCOPED_TRACE(message);
+
+ // send invalid packet
+ ASSERT_TRUE(sender->sendPacket(serialized));
+
+ // receive error
+ auto results = receiver->getBlocking();
+ ASSERT_TRUE(results.has_value());
+ const auto [status, outputShapes, timing] = std::move(*results);
+ EXPECT_NE(ErrorStatus::NONE, status);
+ EXPECT_EQ(0u, outputShapes.size());
+ EXPECT_TRUE(badTiming(timing));
+}
+
+// For validation, valid packet entries are mutated to invalid packet entries,
+// or invalid packet entries are inserted into valid packets. This function
+// creates pre-set invalid packet entries for convenience.
+static std::vector<FmqRequestDatum> createBadRequestPacketEntries() {
+ const FmqRequestDatum::PacketInformation packetInformation = {
+ /*.packetSize=*/10, /*.numberOfInputOperands=*/10, /*.numberOfOutputOperands=*/10,
+ /*.numberOfPools=*/10};
+ const FmqRequestDatum::OperandInformation operandInformation = {
+ /*.hasNoValue=*/false, /*.location=*/{}, /*.numberOfDimensions=*/10};
+ const int32_t invalidPoolIdentifier = std::numeric_limits<int32_t>::max();
+ std::vector<FmqRequestDatum> bad(7);
+ bad[0].packetInformation(packetInformation);
+ bad[1].inputOperandInformation(operandInformation);
+ bad[2].inputOperandDimensionValue(0);
+ bad[3].outputOperandInformation(operandInformation);
+ bad[4].outputOperandDimensionValue(0);
+ bad[5].poolIdentifier(invalidPoolIdentifier);
+ bad[6].measureTiming(MeasureTiming::YES);
+ return bad;
+}
+
+// For validation, valid packet entries are mutated to invalid packet entries,
+// or invalid packet entries are inserted into valid packets. This function
+// retrieves pre-set invalid packet entries for convenience. This function
+// caches these data so they can be reused on subsequent validation checks.
+static const std::vector<FmqRequestDatum>& getBadRequestPacketEntries() {
+ static const std::vector<FmqRequestDatum> bad = createBadRequestPacketEntries();
+ return bad;
+}
+
+///////////////////////// REMOVE DATUM ////////////////////////////////////
+
+static void removeDatumTest(RequestChannelSender* sender, ResultChannelReceiver* receiver,
+ const std::vector<FmqRequestDatum>& serialized) {
+ for (size_t index = 0; index < serialized.size(); ++index) {
+ const std::string message = "removeDatum: removed datum at index " + std::to_string(index);
+ validate(sender, receiver, message, serialized,
+ [index](std::vector<FmqRequestDatum>* serialized) {
+ serialized->erase(serialized->begin() + index);
+ });
+ }
+}
+
+///////////////////////// ADD DATUM ////////////////////////////////////
+
+static void addDatumTest(RequestChannelSender* sender, ResultChannelReceiver* receiver,
+ const std::vector<FmqRequestDatum>& serialized) {
+ const std::vector<FmqRequestDatum>& extra = getBadRequestPacketEntries();
+ for (size_t index = 0; index <= serialized.size(); ++index) {
+ for (size_t type = 0; type < extra.size(); ++type) {
+ const std::string message = "addDatum: added datum type " + std::to_string(type) +
+ " at index " + std::to_string(index);
+ validate(sender, receiver, message, serialized,
+ [index, type, &extra](std::vector<FmqRequestDatum>* serialized) {
+ serialized->insert(serialized->begin() + index, extra[type]);
+ });
+ }
+ }
+}
+
+///////////////////////// MUTATE DATUM ////////////////////////////////////
+
+static bool interestingCase(const FmqRequestDatum& lhs, const FmqRequestDatum& rhs) {
+ using Discriminator = FmqRequestDatum::hidl_discriminator;
+
+ const bool differentValues = (lhs != rhs);
+ const bool sameDiscriminator = (lhs.getDiscriminator() == rhs.getDiscriminator());
+ const auto discriminator = rhs.getDiscriminator();
+ const bool isDimensionValue = (discriminator == Discriminator::inputOperandDimensionValue ||
+ discriminator == Discriminator::outputOperandDimensionValue);
+
+ return differentValues && !(sameDiscriminator && isDimensionValue);
+}
+
+static void mutateDatumTest(RequestChannelSender* sender, ResultChannelReceiver* receiver,
+ const std::vector<FmqRequestDatum>& serialized) {
+ const std::vector<FmqRequestDatum>& change = getBadRequestPacketEntries();
+ for (size_t index = 0; index < serialized.size(); ++index) {
+ for (size_t type = 0; type < change.size(); ++type) {
+ if (interestingCase(serialized[index], change[type])) {
+ const std::string message = "mutateDatum: changed datum at index " +
+ std::to_string(index) + " to datum type " +
+ std::to_string(type);
+ validate(sender, receiver, message, serialized,
+ [index, type, &change](std::vector<FmqRequestDatum>* serialized) {
+ (*serialized)[index] = change[type];
+ });
+ }
+ }
+ }
+}
+
+///////////////////////// BURST VALIATION TESTS ////////////////////////////////////
+
+static void validateBurstSerialization(const sp<IPreparedModel>& preparedModel,
+ const std::vector<Request>& requests) {
+ // create burst
+ std::unique_ptr<RequestChannelSender> sender;
+ std::unique_ptr<ResultChannelReceiver> receiver;
+ sp<ExecutionBurstCallback> callback = new ExecutionBurstCallback();
+ sp<IBurstContext> context;
+ ASSERT_NO_FATAL_FAILURE(createBurst(preparedModel, callback, &sender, &receiver, &context));
+ ASSERT_NE(nullptr, sender.get());
+ ASSERT_NE(nullptr, receiver.get());
+ ASSERT_NE(nullptr, context.get());
+
+ // validate each request
+ for (const Request& request : requests) {
+ // load memory into callback slots
+ std::vector<intptr_t> keys;
+ keys.reserve(request.pools.size());
+ std::transform(request.pools.begin(), request.pools.end(), std::back_inserter(keys),
+ [](const auto& pool) { return reinterpret_cast<intptr_t>(&pool); });
+ const std::vector<int32_t> slots = callback->getSlots(request.pools, keys);
+
+ // ensure slot std::numeric_limits<int32_t>::max() doesn't exist (for
+ // subsequent slot validation testing)
+ ASSERT_TRUE(std::all_of(slots.begin(), slots.end(), [](int32_t slot) {
+ return slot != std::numeric_limits<int32_t>::max();
+ }));
+
+ // serialize the request
+ const auto serialized = ::android::nn::serialize(request, MeasureTiming::YES, slots);
+
+ // validations
+ removeDatumTest(sender.get(), receiver.get(), serialized);
+ addDatumTest(sender.get(), receiver.get(), serialized);
+ mutateDatumTest(sender.get(), receiver.get(), serialized);
+ }
+}
+
+// This test validates that when the Result message size exceeds length of the
+// result FMQ, the service instance gracefully fails and returns an error.
+static void validateBurstFmqLength(const sp<IPreparedModel>& preparedModel,
+ const std::vector<Request>& requests) {
+ // create regular burst
+ std::shared_ptr<ExecutionBurstController> controllerRegular;
+ ASSERT_NO_FATAL_FAILURE(createBurstWithResultChannelLength(
+ preparedModel, kExecutionBurstChannelLength, &controllerRegular));
+ ASSERT_NE(nullptr, controllerRegular.get());
+
+ // create burst with small output channel
+ std::shared_ptr<ExecutionBurstController> controllerSmall;
+ ASSERT_NO_FATAL_FAILURE(createBurstWithResultChannelLength(
+ preparedModel, kExecutionBurstChannelSmallLength, &controllerSmall));
+ ASSERT_NE(nullptr, controllerSmall.get());
+
+ // validate each request
+ for (const Request& request : requests) {
+ // load memory into callback slots
+ std::vector<intptr_t> keys(request.pools.size());
+ for (size_t i = 0; i < keys.size(); ++i) {
+ keys[i] = reinterpret_cast<intptr_t>(&request.pools[i]);
+ }
+
+ // collect serialized result by running regular burst
+ const auto [statusRegular, outputShapesRegular, timingRegular] =
+ controllerRegular->compute(request, MeasureTiming::NO, keys);
+
+ // skip test if regular burst output isn't useful for testing a failure
+ // caused by having too small of a length for the result FMQ
+ const std::vector<FmqResultDatum> serialized =
+ ::android::nn::serialize(statusRegular, outputShapesRegular, timingRegular);
+ if (statusRegular != ErrorStatus::NONE ||
+ serialized.size() <= kExecutionBurstChannelSmallLength) {
+ continue;
+ }
+
+ // by this point, execution should fail because the result channel isn't
+ // large enough to return the serialized result
+ const auto [statusSmall, outputShapesSmall, timingSmall] =
+ controllerSmall->compute(request, MeasureTiming::NO, keys);
+ EXPECT_NE(ErrorStatus::NONE, statusSmall);
+ EXPECT_EQ(0u, outputShapesSmall.size());
+ EXPECT_TRUE(badTiming(timingSmall));
+ }
+}
+
+///////////////////////////// ENTRY POINT //////////////////////////////////
+
+void ValidationTest::validateBurst(const sp<IPreparedModel>& preparedModel,
+ const std::vector<Request>& requests) {
+ ASSERT_NO_FATAL_FAILURE(validateBurstSerialization(preparedModel, requests));
+ ASSERT_NO_FATAL_FAILURE(validateBurstFmqLength(preparedModel, requests));
+}
+
+} // namespace functional
+} // namespace vts
+} // namespace V1_2
+} // namespace neuralnetworks
+} // namespace hardware
+} // namespace android
diff --git a/neuralnetworks/1.2/vts/functional/ValidateRequest.cpp b/neuralnetworks/1.2/vts/functional/ValidateRequest.cpp
index 870d017..9703c2d 100644
--- a/neuralnetworks/1.2/vts/functional/ValidateRequest.cpp
+++ b/neuralnetworks/1.2/vts/functional/ValidateRequest.cpp
@@ -35,9 +35,7 @@
namespace functional {
using ::android::hardware::neuralnetworks::V1_2::implementation::ExecutionCallback;
-using ::android::hardware::neuralnetworks::V1_2::implementation::PreparedModelCallback;
using ::android::hidl::memory::V1_0::IMemory;
-using HidlToken = hidl_array<uint8_t, static_cast<uint32_t>(Constant::BYTE_SIZE_OF_CACHE_TOKEN)>;
using test_helper::for_all;
using test_helper::MixedTyped;
using test_helper::MixedTypedExample;
@@ -48,55 +46,6 @@
return timing.timeOnDevice == UINT64_MAX && timing.timeInDriver == UINT64_MAX;
}
-static void createPreparedModel(const sp<IDevice>& device, const Model& model,
- sp<IPreparedModel>* preparedModel) {
- ASSERT_NE(nullptr, preparedModel);
-
- // see if service can handle model
- bool fullySupportsModel = false;
- Return<void> supportedOpsLaunchStatus = device->getSupportedOperations_1_2(
- model, [&fullySupportsModel](ErrorStatus status, const hidl_vec<bool>& supported) {
- ASSERT_EQ(ErrorStatus::NONE, status);
- ASSERT_NE(0ul, supported.size());
- fullySupportsModel =
- std::all_of(supported.begin(), supported.end(), [](bool valid) { return valid; });
- });
- ASSERT_TRUE(supportedOpsLaunchStatus.isOk());
-
- // launch prepare model
- sp<PreparedModelCallback> preparedModelCallback = new PreparedModelCallback();
- ASSERT_NE(nullptr, preparedModelCallback.get());
- Return<ErrorStatus> prepareLaunchStatus = device->prepareModel_1_2(
- model, ExecutionPreference::FAST_SINGLE_ANSWER, hidl_vec<hidl_handle>(),
- hidl_vec<hidl_handle>(), HidlToken(), preparedModelCallback);
- ASSERT_TRUE(prepareLaunchStatus.isOk());
- ASSERT_EQ(ErrorStatus::NONE, static_cast<ErrorStatus>(prepareLaunchStatus));
-
- // retrieve prepared model
- preparedModelCallback->wait();
- ErrorStatus prepareReturnStatus = preparedModelCallback->getStatus();
- *preparedModel = getPreparedModel_1_2(preparedModelCallback);
-
- // The getSupportedOperations_1_2 call returns a list of operations that are
- // guaranteed not to fail if prepareModel_1_2 is called, and
- // 'fullySupportsModel' is true i.f.f. the entire model is guaranteed.
- // If a driver has any doubt that it can prepare an operation, it must
- // return false. So here, if a driver isn't sure if it can support an
- // operation, but reports that it successfully prepared the model, the test
- // can continue.
- if (!fullySupportsModel && prepareReturnStatus != ErrorStatus::NONE) {
- ASSERT_EQ(nullptr, preparedModel->get());
- LOG(INFO) << "NN VTS: Unable to test Request validation because vendor service cannot "
- "prepare model that it does not support.";
- std::cout << "[ ] Unable to test Request validation because vendor service "
- "cannot prepare model that it does not support."
- << std::endl;
- return;
- }
- ASSERT_EQ(ErrorStatus::NONE, prepareReturnStatus);
- ASSERT_NE(nullptr, preparedModel->get());
-}
-
// Primary validation function. This function will take a valid request, apply a
// mutation to it to invalidate the request, then pass it to interface calls
// that use the request. Note that the request here is passed by value, and any
@@ -316,14 +265,8 @@
return requests;
}
-void ValidationTest::validateRequests(const Model& model, const std::vector<Request>& requests) {
- // create IPreparedModel
- sp<IPreparedModel> preparedModel;
- ASSERT_NO_FATAL_FAILURE(createPreparedModel(device, model, &preparedModel));
- if (preparedModel == nullptr) {
- return;
- }
-
+void ValidationTest::validateRequests(const sp<IPreparedModel>& preparedModel,
+ const std::vector<Request>& requests) {
// validate each request
for (const Request& request : requests) {
removeInputTest(preparedModel, request);
diff --git a/neuralnetworks/1.2/vts/functional/VtsHalNeuralnetworks.cpp b/neuralnetworks/1.2/vts/functional/VtsHalNeuralnetworks.cpp
index 4728c28..4ddefe8 100644
--- a/neuralnetworks/1.2/vts/functional/VtsHalNeuralnetworks.cpp
+++ b/neuralnetworks/1.2/vts/functional/VtsHalNeuralnetworks.cpp
@@ -18,6 +18,10 @@
#include "VtsHalNeuralnetworks.h"
+#include <android-base/logging.h>
+
+#include "Callbacks.h"
+
namespace android {
namespace hardware {
namespace neuralnetworks {
@@ -25,6 +29,60 @@
namespace vts {
namespace functional {
+using ::android::hardware::neuralnetworks::V1_2::implementation::PreparedModelCallback;
+using HidlToken = hidl_array<uint8_t, static_cast<uint32_t>(Constant::BYTE_SIZE_OF_CACHE_TOKEN)>;
+using V1_1::ExecutionPreference;
+
+// internal helper function
+static void createPreparedModel(const sp<IDevice>& device, const Model& model,
+ sp<IPreparedModel>* preparedModel) {
+ ASSERT_NE(nullptr, preparedModel);
+
+ // see if service can handle model
+ bool fullySupportsModel = false;
+ Return<void> supportedOpsLaunchStatus = device->getSupportedOperations_1_2(
+ model, [&fullySupportsModel](ErrorStatus status, const hidl_vec<bool>& supported) {
+ ASSERT_EQ(ErrorStatus::NONE, status);
+ ASSERT_NE(0ul, supported.size());
+ fullySupportsModel = std::all_of(supported.begin(), supported.end(),
+ [](bool valid) { return valid; });
+ });
+ ASSERT_TRUE(supportedOpsLaunchStatus.isOk());
+
+ // launch prepare model
+ sp<PreparedModelCallback> preparedModelCallback = new PreparedModelCallback();
+ ASSERT_NE(nullptr, preparedModelCallback.get());
+ Return<ErrorStatus> prepareLaunchStatus = device->prepareModel_1_2(
+ model, ExecutionPreference::FAST_SINGLE_ANSWER, hidl_vec<hidl_handle>(),
+ hidl_vec<hidl_handle>(), HidlToken(), preparedModelCallback);
+ ASSERT_TRUE(prepareLaunchStatus.isOk());
+ ASSERT_EQ(ErrorStatus::NONE, static_cast<ErrorStatus>(prepareLaunchStatus));
+
+ // retrieve prepared model
+ preparedModelCallback->wait();
+ ErrorStatus prepareReturnStatus = preparedModelCallback->getStatus();
+ *preparedModel = getPreparedModel_1_2(preparedModelCallback);
+
+ // The getSupportedOperations_1_2 call returns a list of operations that are
+ // guaranteed not to fail if prepareModel_1_2 is called, and
+ // 'fullySupportsModel' is true i.f.f. the entire model is guaranteed.
+ // If a driver has any doubt that it can prepare an operation, it must
+ // return false. So here, if a driver isn't sure if it can support an
+ // operation, but reports that it successfully prepared the model, the test
+ // can continue.
+ if (!fullySupportsModel && prepareReturnStatus != ErrorStatus::NONE) {
+ ASSERT_EQ(nullptr, preparedModel->get());
+ LOG(INFO) << "NN VTS: Unable to test Request validation because vendor service cannot "
+ "prepare model that it does not support.";
+ std::cout << "[ ] Unable to test Request validation because vendor service "
+ "cannot prepare model that it does not support."
+ << std::endl;
+ return;
+ }
+ ASSERT_EQ(ErrorStatus::NONE, prepareReturnStatus);
+ ASSERT_NE(nullptr, preparedModel->get());
+}
+
// A class for test environment setup
NeuralnetworksHidlEnvironment::NeuralnetworksHidlEnvironment() {}
@@ -68,6 +126,20 @@
::testing::VtsHalHidlTargetTestBase::TearDown();
}
+void ValidationTest::validateEverything(const Model& model, const std::vector<Request>& requests) {
+ validateModel(model);
+
+ // create IPreparedModel
+ sp<IPreparedModel> preparedModel;
+ ASSERT_NO_FATAL_FAILURE(createPreparedModel(device, model, &preparedModel));
+ if (preparedModel == nullptr) {
+ return;
+ }
+
+ validateRequests(preparedModel, requests);
+ validateBurst(preparedModel, requests);
+}
+
sp<IPreparedModel> getPreparedModel_1_2(
const sp<V1_2::implementation::PreparedModelCallback>& callback) {
sp<V1_0::IPreparedModel> preparedModelV1_0 = callback->getPreparedModel();
diff --git a/neuralnetworks/1.2/vts/functional/VtsHalNeuralnetworks.h b/neuralnetworks/1.2/vts/functional/VtsHalNeuralnetworks.h
index 404eec0..8d1acbe 100644
--- a/neuralnetworks/1.2/vts/functional/VtsHalNeuralnetworks.h
+++ b/neuralnetworks/1.2/vts/functional/VtsHalNeuralnetworks.h
@@ -72,8 +72,14 @@
// Tag for the validation tests
class ValidationTest : public NeuralnetworksHidlTest {
protected:
- void validateModel(const Model& model);
- void validateRequests(const Model& model, const std::vector<Request>& request);
+ void validateEverything(const Model& model, const std::vector<Request>& requests);
+
+ private:
+ void validateModel(const Model& model);
+ void validateRequests(const sp<IPreparedModel>& preparedModel,
+ const std::vector<Request>& requests);
+ void validateBurst(const sp<IPreparedModel>& preparedModel,
+ const std::vector<Request>& requests);
};
// Tag for the generated tests
diff --git a/prebuilt_hashes/28.txt b/prebuilt_hashes/28.txt
index cc15322..8a45ca5 100644
--- a/prebuilt_hashes/28.txt
+++ b/prebuilt_hashes/28.txt
@@ -348,6 +348,8 @@
5e278fcaa3287d397d8eebe1c22aaa28150f5caae1cf9381cd6dc32cb37899c5 android.hardware.nfc@1.1::types
163e115e833fc1d77cdd4a8cf0c833bb8b8d74fe35c880fe693101d17774926f android.hardware.power@1.2::IPower
7899b9305587b2d5cd74a3cc87e9090f58bf4ae74256ce3ee36e7ec011822840 android.hardware.power@1.2::types
+5a464e6db53fad223986d655028a18185b73db8e2bfa9663f9042c9623eb0aa0 android.hardware.power@1.3::IPower
+a54a28d39b892d27a3cb06829181c038edcdd9e8eef359543b01e4313ae59aa0 android.hardware.power@1.3::types
ab132c990a62f0aca35871c092c22fb9c85d478e22124ef6a4d0a2302da76a9f android.hardware.radio@1.2::IRadio
cda752aeabaabc20486a82ac57a3dd107785c006094a349bc5e224e8aa22a17c android.hardware.radio@1.2::IRadioIndication
da8c6ae991c6a4b284cc6e445332e064e28ee8a09482ed5afff9d159ec6694b7 android.hardware.radio@1.2::IRadioResponse
diff --git a/radio/1.2/vts/functional/radio_hidl_hal_api.cpp b/radio/1.2/vts/functional/radio_hidl_hal_api.cpp
index 730d969..a3073ac 100644
--- a/radio/1.2/vts/functional/radio_hidl_hal_api.cpp
+++ b/radio/1.2/vts/functional/radio_hidl_hal_api.cpp
@@ -19,19 +19,25 @@
#define ASSERT_OK(ret) ASSERT_TRUE(ret.isOk())
+namespace {
+const RadioAccessSpecifier GERAN_SPECIFIER_P900 = {.radioAccessNetwork = RadioAccessNetworks::GERAN,
+ .geranBands = {GeranBands::BAND_P900},
+ .channels = {1, 2}};
+const RadioAccessSpecifier GERAN_SPECIFIER_850 = {.radioAccessNetwork = RadioAccessNetworks::GERAN,
+ .geranBands = {GeranBands::BAND_850},
+ .channels = {128, 129}};
+} // namespace
+
/*
* Test IRadio.startNetworkScan() for the response returned.
*/
TEST_F(RadioHidlTest_v1_2, startNetworkScan) {
serial = GetRandomSerialNumber();
- RadioAccessSpecifier specifier = {
- .radioAccessNetwork = RadioAccessNetworks::GERAN,
- .geranBands = {GeranBands::BAND_450, GeranBands::BAND_480},
- .channels = {1,2}};
-
::android::hardware::radio::V1_2::NetworkScanRequest request = {
- .type = ScanType::ONE_SHOT, .interval = 60, .specifiers = {specifier}};
+ .type = ScanType::ONE_SHOT,
+ .interval = 60,
+ .specifiers = {::GERAN_SPECIFIER_P900, ::GERAN_SPECIFIER_850}};
Return<void> res = radio_v1_2->startNetworkScan_1_2(serial, request);
ASSERT_OK(res);
@@ -89,18 +95,13 @@
TEST_F(RadioHidlTest_v1_2, startNetworkScan_InvalidInterval1) {
serial = GetRandomSerialNumber();
- RadioAccessSpecifier specifier = {
- .radioAccessNetwork = RadioAccessNetworks::GERAN,
- .geranBands = {GeranBands::BAND_450, GeranBands::BAND_480},
- .channels = {1,2}};
-
::android::hardware::radio::V1_2::NetworkScanRequest request = {
- .type = ScanType::ONE_SHOT,
- .interval = 4,
- .specifiers = {specifier},
- .maxSearchTime = 60,
- .incrementalResults = false,
- .incrementalResultsPeriodicity = 1};
+ .type = ScanType::ONE_SHOT,
+ .interval = 4,
+ .specifiers = {::GERAN_SPECIFIER_P900, ::GERAN_SPECIFIER_850},
+ .maxSearchTime = 60,
+ .incrementalResults = false,
+ .incrementalResultsPeriodicity = 1};
Return<void> res = radio_v1_2->startNetworkScan_1_2(serial, request);
ASSERT_OK(res);
@@ -126,18 +127,13 @@
TEST_F(RadioHidlTest_v1_2, startNetworkScan_InvalidInterval2) {
serial = GetRandomSerialNumber();
- RadioAccessSpecifier specifier = {
- .radioAccessNetwork = RadioAccessNetworks::GERAN,
- .geranBands = {GeranBands::BAND_450, GeranBands::BAND_480},
- .channels = {1,2}};
-
::android::hardware::radio::V1_2::NetworkScanRequest request = {
- .type = ScanType::ONE_SHOT,
- .interval = 301,
- .specifiers = {specifier},
- .maxSearchTime = 60,
- .incrementalResults = false,
- .incrementalResultsPeriodicity = 1};
+ .type = ScanType::ONE_SHOT,
+ .interval = 301,
+ .specifiers = {::GERAN_SPECIFIER_P900, ::GERAN_SPECIFIER_850},
+ .maxSearchTime = 60,
+ .incrementalResults = false,
+ .incrementalResultsPeriodicity = 1};
Return<void> res = radio_v1_2->startNetworkScan_1_2(serial, request);
ASSERT_OK(res);
@@ -163,18 +159,13 @@
TEST_F(RadioHidlTest_v1_2, startNetworkScan_InvalidMaxSearchTime1) {
serial = GetRandomSerialNumber();
- RadioAccessSpecifier specifier = {
- .radioAccessNetwork = RadioAccessNetworks::GERAN,
- .geranBands = {GeranBands::BAND_450, GeranBands::BAND_480},
- .channels = {1,2}};
-
::android::hardware::radio::V1_2::NetworkScanRequest request = {
- .type = ScanType::ONE_SHOT,
- .interval = 60,
- .specifiers = {specifier},
- .maxSearchTime = 59,
- .incrementalResults = false,
- .incrementalResultsPeriodicity = 1};
+ .type = ScanType::ONE_SHOT,
+ .interval = 60,
+ .specifiers = {::GERAN_SPECIFIER_P900, ::GERAN_SPECIFIER_850},
+ .maxSearchTime = 59,
+ .incrementalResults = false,
+ .incrementalResultsPeriodicity = 1};
Return<void> res = radio_v1_2->startNetworkScan_1_2(serial, request);
ASSERT_OK(res);
@@ -200,18 +191,13 @@
TEST_F(RadioHidlTest_v1_2, startNetworkScan_InvalidMaxSearchTime2) {
serial = GetRandomSerialNumber();
- RadioAccessSpecifier specifier = {
- .radioAccessNetwork = RadioAccessNetworks::GERAN,
- .geranBands = {GeranBands::BAND_450, GeranBands::BAND_480},
- .channels = {1,2}};
-
::android::hardware::radio::V1_2::NetworkScanRequest request = {
- .type = ScanType::ONE_SHOT,
- .interval = 60,
- .specifiers = {specifier},
- .maxSearchTime = 3601,
- .incrementalResults = false,
- .incrementalResultsPeriodicity = 1};
+ .type = ScanType::ONE_SHOT,
+ .interval = 60,
+ .specifiers = {::GERAN_SPECIFIER_P900, ::GERAN_SPECIFIER_850},
+ .maxSearchTime = 3601,
+ .incrementalResults = false,
+ .incrementalResultsPeriodicity = 1};
Return<void> res = radio_v1_2->startNetworkScan_1_2(serial, request);
ASSERT_OK(res);
@@ -237,18 +223,13 @@
TEST_F(RadioHidlTest_v1_2, startNetworkScan_InvalidPeriodicity1) {
serial = GetRandomSerialNumber();
- RadioAccessSpecifier specifier = {
- .radioAccessNetwork = RadioAccessNetworks::GERAN,
- .geranBands = {GeranBands::BAND_450, GeranBands::BAND_480},
- .channels = {1,2}};
-
::android::hardware::radio::V1_2::NetworkScanRequest request = {
- .type = ScanType::ONE_SHOT,
- .interval = 60,
- .specifiers = {specifier},
- .maxSearchTime = 600,
- .incrementalResults = false,
- .incrementalResultsPeriodicity = 0};
+ .type = ScanType::ONE_SHOT,
+ .interval = 60,
+ .specifiers = {::GERAN_SPECIFIER_P900, ::GERAN_SPECIFIER_850},
+ .maxSearchTime = 600,
+ .incrementalResults = false,
+ .incrementalResultsPeriodicity = 0};
Return<void> res = radio_v1_2->startNetworkScan_1_2(serial, request);
ASSERT_OK(res);
@@ -274,18 +255,13 @@
TEST_F(RadioHidlTest_v1_2, startNetworkScan_InvalidPeriodicity2) {
serial = GetRandomSerialNumber();
- RadioAccessSpecifier specifier = {
- .radioAccessNetwork = RadioAccessNetworks::GERAN,
- .geranBands = {GeranBands::BAND_450, GeranBands::BAND_480},
- .channels = {1,2}};
-
::android::hardware::radio::V1_2::NetworkScanRequest request = {
- .type = ScanType::ONE_SHOT,
- .interval = 60,
- .specifiers = {specifier},
- .maxSearchTime = 600,
- .incrementalResults = false,
- .incrementalResultsPeriodicity = 11};
+ .type = ScanType::ONE_SHOT,
+ .interval = 60,
+ .specifiers = {::GERAN_SPECIFIER_P900, ::GERAN_SPECIFIER_850},
+ .maxSearchTime = 600,
+ .incrementalResults = false,
+ .incrementalResultsPeriodicity = 11};
Return<void> res = radio_v1_2->startNetworkScan_1_2(serial, request);
ASSERT_OK(res);
@@ -311,20 +287,15 @@
TEST_F(RadioHidlTest_v1_2, startNetworkScan_GoodRequest1) {
serial = GetRandomSerialNumber();
- RadioAccessSpecifier specifier = {
- .radioAccessNetwork = RadioAccessNetworks::GERAN,
- .geranBands = {GeranBands::BAND_450, GeranBands::BAND_480},
- .channels = {1,2}};
-
::android::hardware::radio::V1_2::NetworkScanRequest request = {
- .type = ScanType::ONE_SHOT,
- .interval = 60,
- .specifiers = {specifier},
- // Some vendor may not support max search time of 360s.
- // This issue is tracked in b/112205669.
- .maxSearchTime = 300,
- .incrementalResults = false,
- .incrementalResultsPeriodicity = 10};
+ .type = ScanType::ONE_SHOT,
+ .interval = 60,
+ .specifiers = {::GERAN_SPECIFIER_P900, ::GERAN_SPECIFIER_850},
+ // Some vendor may not support max search time of 360s.
+ // This issue is tracked in b/112205669.
+ .maxSearchTime = 300,
+ .incrementalResults = false,
+ .incrementalResultsPeriodicity = 10};
Return<void> res = radio_v1_2->startNetworkScan_1_2(serial, request);
ASSERT_OK(res);
@@ -350,21 +321,16 @@
TEST_F(RadioHidlTest_v1_2, startNetworkScan_GoodRequest2) {
serial = GetRandomSerialNumber();
- RadioAccessSpecifier specifier = {
- .radioAccessNetwork = RadioAccessNetworks::GERAN,
- .geranBands = {GeranBands::BAND_450, GeranBands::BAND_480},
- .channels = {1,2}};
-
::android::hardware::radio::V1_2::NetworkScanRequest request = {
- .type = ScanType::ONE_SHOT,
- .interval = 60,
- .specifiers = {specifier},
- // Some vendor may not support max search time of 360s.
- // This issue is tracked in b/112205669.
- .maxSearchTime = 300,
- .incrementalResults = false,
- .incrementalResultsPeriodicity = 10,
- .mccMncs = {"310410"}};
+ .type = ScanType::ONE_SHOT,
+ .interval = 60,
+ .specifiers = {::GERAN_SPECIFIER_P900, ::GERAN_SPECIFIER_850},
+ // Some vendor may not support max search time of 360s.
+ // This issue is tracked in b/112205669.
+ .maxSearchTime = 300,
+ .incrementalResults = false,
+ .incrementalResultsPeriodicity = 10,
+ .mccMncs = {"310410"}};
Return<void> res = radio_v1_2->startNetworkScan_1_2(serial, request);
ASSERT_OK(res);
@@ -757,6 +723,7 @@
// Check the mcc [0, 999] and mnc [0, 999].
string hidl_mcc;
string hidl_mnc;
+ bool checkMccMnc = true;
int totalIdentitySizeExpected = 1;
::android::hardware::radio::V1_2::CellIdentity cellIdentities =
radioRsp_v1_2->dataRegResp.cellIdentity;
@@ -765,6 +732,7 @@
if (cellInfoType == CellInfoType::NONE) {
// All the fields are 0
totalIdentitySizeExpected = 0;
+ checkMccMnc = false;
} else if (cellInfoType == CellInfoType::GSM) {
EXPECT_EQ(1, cellIdentities.cellIdentityGsm.size());
::android::hardware::radio::V1_2::CellIdentityGsm cig = cellIdentities.cellIdentityGsm[0];
@@ -791,6 +759,7 @@
// CellIndentityCdma has no mcc and mnc.
EXPECT_EQ(CellInfoType::CDMA, cellInfoType);
EXPECT_EQ(1, cellIdentities.cellIdentityCdma.size());
+ checkMccMnc = false;
}
// Check only one CellIdentity is size 1, and others must be 0.
@@ -799,10 +768,13 @@
cellIdentities.cellIdentityLte.size() + cellIdentities.cellIdentityWcdma.size() +
cellIdentities.cellIdentityTdscdma.size());
- int mcc = stoi(hidl_mcc);
- int mnc = stoi(hidl_mnc);
- EXPECT_TRUE(mcc >= 0 && mcc <= 999);
- EXPECT_TRUE(mnc >= 0 && mnc <= 999);
+ // 32 bit system might return invalid mcc and mnc hidl string "\xff\xff..."
+ if (checkMccMnc && hidl_mcc.size() < 4 && hidl_mnc.size() < 4) {
+ int mcc = stoi(hidl_mcc);
+ int mnc = stoi(hidl_mnc);
+ EXPECT_TRUE(mcc >= 0 && mcc <= 999);
+ EXPECT_TRUE(mnc >= 0 && mnc <= 999);
+ }
}
/*
diff --git a/radio/1.3/IRadioResponse.hal b/radio/1.3/IRadioResponse.hal
index c3bbe65..85085e2 100644
--- a/radio/1.3/IRadioResponse.hal
+++ b/radio/1.3/IRadioResponse.hal
@@ -44,6 +44,9 @@
* RadioError:NONE
* RadioError:RADIO_NOT_AVAILABLE
* RadioError:MODEM_ERR
+ * RadioError:INVALID_STATE: this is for the case that the API is called in a single-sim
+ * mode, or when there is only one modem available, as this API should only
+ * be called in multi sim status.
*/
oneway enableModemResponse(RadioResponseInfo info);
diff --git a/radio/1.3/vts/functional/radio_hidl_hal_api.cpp b/radio/1.3/vts/functional/radio_hidl_hal_api.cpp
index 1bebae7..7fb8995 100644
--- a/radio/1.3/vts/functional/radio_hidl_hal_api.cpp
+++ b/radio/1.3/vts/functional/radio_hidl_hal_api.cpp
@@ -33,9 +33,9 @@
EXPECT_EQ(serial, radioRsp_v1_3->rspInfo.serial);
ALOGI("getModemStackStatus, rspInfo.error = %s\n",
toString(radioRsp_v1_3->rspInfo.error).c_str());
- ASSERT_TRUE(CheckAnyOfErrors(
- radioRsp_v1_3->rspInfo.error,
- {RadioError::NONE, RadioError::RADIO_NOT_AVAILABLE, RadioError::MODEM_ERR}));
+ ASSERT_TRUE(CheckAnyOfErrors(radioRsp_v1_3->rspInfo.error,
+ {RadioError::NONE, RadioError::RADIO_NOT_AVAILABLE,
+ RadioError::MODEM_ERR, RadioError::INVALID_STATE}));
// checking if getModemStackStatus returns true, as modem was enabled above
if (RadioError::NONE == radioRsp_v1_3->rspInfo.error) {
@@ -50,9 +50,9 @@
EXPECT_EQ(serial, radioRsp_v1_3->rspInfo.serial);
ALOGI("getModemStackStatus, rspInfo.error = %s\n",
toString(radioRsp_v1_3->rspInfo.error).c_str());
- ASSERT_TRUE(CheckAnyOfErrors(
- radioRsp_v1_3->rspInfo.error,
- {RadioError::NONE, RadioError::RADIO_NOT_AVAILABLE, RadioError::MODEM_ERR}));
+ ASSERT_TRUE(CheckAnyOfErrors(radioRsp_v1_3->rspInfo.error,
+ {RadioError::NONE, RadioError::RADIO_NOT_AVAILABLE,
+ RadioError::MODEM_ERR, RadioError::INVALID_STATE}));
// verify that enableModem did set isEnabled correctly
EXPECT_EQ(true, radioRsp_v1_3->isModemEnabled);
}
diff --git a/soundtrigger/2.0/ISoundTriggerHwCallback.hal b/soundtrigger/2.0/ISoundTriggerHwCallback.hal
index 90132d9..84b11c8 100644
--- a/soundtrigger/2.0/ISoundTriggerHwCallback.hal
+++ b/soundtrigger/2.0/ISoundTriggerHwCallback.hal
@@ -46,7 +46,6 @@
int32_t captureSession;
/**
* Delay in ms between end of model detection and start of audio
- /**
* available for capture. A negative value is possible
* (e.g. if key phrase is also available for capture */
int32_t captureDelayMs;
diff --git a/wifi/1.2/default/wifi_legacy_hal.cpp b/wifi/1.2/default/wifi_legacy_hal.cpp
index 55ec96d..375204c 100644
--- a/wifi/1.2/default/wifi_legacy_hal.cpp
+++ b/wifi/1.2/default/wifi_legacy_hal.cpp
@@ -550,7 +550,6 @@
}
// Fall through if failed. Failure to retrieve cached scan
// results should trigger a background scan failure.
- [[fallthrough]];
case WIFI_SCAN_FAILED:
on_failure_user_callback(id);
on_gscan_event_internal_callback = nullptr;