From f20aa0c48524a890f2fcf08f249bafe62bb6db45 Mon Sep 17 00:00:00 2001 From: Jeff Tinker Date: Fri, 31 Mar 2017 15:32:12 -0700 Subject: [PATCH] Add additional drm hal tests Test: VTS tests passing bug:34178477 Change-Id: Ie8fa5f2ad193b717b0564bb3046de8a64ccd8d85 --- drm/1.0/default/DrmPlugin.cpp | 12 +- drm/1.0/vts/functional/Android.bp | 2 + .../vts/functional/drm_hal_clearkey_test.cpp | 308 +++++++- .../functional/drm_hal_vendor_module_api.h | 18 +- .../vts/functional/drm_hal_vendor_test.cpp | 659 +++++++++++++++--- drm/1.0/vts/functional/vendor_modules.cpp | 43 +- drm/1.0/vts/functional/vendor_modules.h | 26 +- 7 files changed, 896 insertions(+), 172 deletions(-) diff --git a/drm/1.0/default/DrmPlugin.cpp b/drm/1.0/default/DrmPlugin.cpp index c7428a5c36..e9133acbb6 100644 --- a/drm/1.0/default/DrmPlugin.cpp +++ b/drm/1.0/default/DrmPlugin.cpp @@ -327,19 +327,25 @@ namespace implementation { Return DrmPlugin::sendEvent(EventType eventType, const hidl_vec& sessionId, const hidl_vec& data) { - mListener->sendEvent(eventType, sessionId, data); + if (mListener != nullptr) { + mListener->sendEvent(eventType, sessionId, data); + } return Void(); } Return DrmPlugin::sendExpirationUpdate( const hidl_vec& sessionId, int64_t expiryTimeInMS) { - mListener->sendExpirationUpdate(sessionId, expiryTimeInMS); + if (mListener != nullptr) { + mListener->sendExpirationUpdate(sessionId, expiryTimeInMS); + } return Void(); } Return DrmPlugin::sendKeysChange(const hidl_vec& sessionId, const hidl_vec& keyStatusList, bool hasNewUsableKey) { - mListener->sendKeysChange(sessionId, keyStatusList, hasNewUsableKey); + if (mListener != nullptr) { + mListener->sendKeysChange(sessionId, keyStatusList, hasNewUsableKey); + } return Void(); } diff --git a/drm/1.0/vts/functional/Android.bp b/drm/1.0/vts/functional/Android.bp index 36d7d1c03c..43ea3727e5 100644 --- a/drm/1.0/vts/functional/Android.bp +++ b/drm/1.0/vts/functional/Android.bp @@ -34,6 +34,8 @@ cc_test { "libhwbinder", "liblog", "libnativehelper", + "libssl", + "libcrypto", "libutils", ], static_libs: [ diff --git a/drm/1.0/vts/functional/drm_hal_clearkey_test.cpp b/drm/1.0/vts/functional/drm_hal_clearkey_test.cpp index 2296d2d26f..691085571b 100644 --- a/drm/1.0/vts/functional/drm_hal_clearkey_test.cpp +++ b/drm/1.0/vts/functional/drm_hal_clearkey_test.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include "VtsHalHidlTargetTestBase.h" @@ -124,6 +125,39 @@ TEST_F(DrmHalClearkeyFactoryTest, InvalidPluginNotSupported) { EXPECT_FALSE(cryptoFactory->isCryptoSchemeSupported(kInvalidUUID)); } +/** + * Ensure the factory doesn't support an empty UUID + */ +TEST_F(DrmHalClearkeyFactoryTest, EmptyPluginUUIDNotSupported) { + hidl_array emptyUUID; + EXPECT_FALSE(drmFactory->isCryptoSchemeSupported(emptyUUID)); + EXPECT_FALSE(cryptoFactory->isCryptoSchemeSupported(emptyUUID)); +} + +/** + * Ensure empty content type is not supported + */ +TEST_F(DrmHalClearkeyFactoryTest, EmptyContentTypeNotSupported) { + hidl_string empty; + EXPECT_FALSE(drmFactory->isContentTypeSupported(empty)); +} + +/** + * Ensure invalid content type is not supported + */ +TEST_F(DrmHalClearkeyFactoryTest, InvalidContentTypeNotSupported) { + hidl_string invalid("abcdabcd"); + EXPECT_FALSE(drmFactory->isContentTypeSupported(invalid)); +} + +/** + * Ensure valid content type is supported + */ +TEST_F(DrmHalClearkeyFactoryTest, ValidContentTypeSupported) { + hidl_string cencType("cenc"); + EXPECT_TRUE(drmFactory->isContentTypeSupported(cencType)); +} + /** * Ensure clearkey drm plugin can be created */ @@ -417,6 +451,26 @@ TEST_F(DrmHalClearkeyPluginTest, ProvideKeyResponseEmptyResponse) { closeSession(session); } +/** + * Test that a removeKeys on an empty sessionID returns BAD_VALUE + */ +TEST_F(DrmHalClearkeyPluginTest, RemoveKeysEmptySessionId) { + SessionId sessionId; + Status status = drmPlugin->removeKeys(sessionId); + EXPECT_TRUE(status == Status::BAD_VALUE); +} + +/** + * Remove keys is not supported for clearkey. + */ +TEST_F(DrmHalClearkeyPluginTest, RemoveKeysNewSession) { + SessionId sessionId = openSession(); + Status status = drmPlugin->removeKeys(sessionId); + // Clearkey plugin doesn't support remove keys + EXPECT_EQ(Status::ERROR_DRM_CANNOT_HANDLE, status); + closeSession(sessionId); +} + /** * Test that the clearkey plugin doesn't support getting * secure stops. @@ -617,7 +671,7 @@ TEST_F(DrmHalClearkeyPluginTest, GenericEncryptNotSupported) { ; hidl_vec keyId = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; hidl_vec input = {1, 2, 3, 4, 5}; - hidl_vec iv = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + hidl_vec iv = std::vector(AES_BLOCK_SIZE, 0); auto res = drmPlugin->encrypt(session, keyId, input, iv, [&](Status status, const hidl_vec&) { EXPECT_EQ(Status::ERROR_DRM_CANNOT_HANDLE, @@ -629,10 +683,9 @@ TEST_F(DrmHalClearkeyPluginTest, GenericEncryptNotSupported) { TEST_F(DrmHalClearkeyPluginTest, GenericDecryptNotSupported) { SessionId session = openSession(); - ; hidl_vec keyId = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; hidl_vec input = {1, 2, 3, 4, 5}; - hidl_vec iv = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + hidl_vec iv = std::vector(AES_BLOCK_SIZE, 0); auto res = drmPlugin->decrypt(session, keyId, input, iv, [&](Status status, const hidl_vec&) { EXPECT_EQ(Status::ERROR_DRM_CANNOT_HANDLE, @@ -762,6 +815,17 @@ TEST_F(DrmHalClearkeyPluginTest, SetMediaDrmSessionClosedSession) { EXPECT_EQ(Status::ERROR_DRM_SESSION_NOT_OPENED, status); } +/** + * setMediaDrmSession with an empty session id: BAD_VALUE. An + * empty session clears the previously set session and should + * return OK. + */ +TEST_F(DrmHalClearkeyPluginTest, SetMediaDrmSessionEmptySession) { + SessionId sessionId; + Status status = cryptoPlugin->setMediaDrmSession(sessionId); + EXPECT_EQ(Status::OK, status); +} + /** * Decrypt tests */ @@ -771,9 +835,15 @@ class DrmHalClearkeyDecryptTest : public DrmHalClearkeyPluginTest { void loadKeys(const SessionId& sessionId); void fillRandom(const sp& memory); hidl_array toHidlArray(const vector& vec) { - EXPECT_EQ(vec.size(), 16u); + EXPECT_EQ(16u, vec.size()); return hidl_array(&vec[0]); } + uint32_t decrypt(Mode mode, uint8_t* iv, const hidl_vec& subSamples, + const Pattern& pattern, Status status); + void aes_ctr_decrypt(uint8_t* dest, uint8_t* src, uint8_t* iv, + const hidl_vec& subSamples, const vector& key); + void aes_cbc_decrypt(uint8_t* dest, uint8_t* src, uint8_t* iv, + const hidl_vec& subSamples, const vector& key); }; /** @@ -847,36 +917,162 @@ void DrmHalClearkeyDecryptTest::fillRandom(const sp& memory) { } } -/** - * Positive decrypt test. "Decrypt" a single clear - * segment. Verify data matches. - */ -TEST_F(DrmHalClearkeyDecryptTest, ClearSegmentTest) { - const size_t kSegmentSize = 1024; +uint32_t DrmHalClearkeyDecryptTest::decrypt(Mode mode, + uint8_t* iv, const hidl_vec& subSamples, + const Pattern& pattern, Status expectedStatus) { const size_t kSegmentIndex = 0; const vector keyId = {0x60, 0x06, 0x1e, 0x01, 0x7e, 0x47, 0x7e, 0x87, 0x7e, 0x57, 0xd0, 0x0d, 0x1e, 0xd0, 0x0d, 0x1e}; - uint8_t iv[16] = {0}; + const vector contentKey = {0x1a, 0x8a, 0x20, 0x95, 0xe4, + 0xde, 0xb2, 0xd2, 0x9e, 0xc8, + 0x16, 0xac, 0x7b, 0xae, 0x20, 0x82}; + uint8_t localIv[AES_BLOCK_SIZE]; + memcpy(localIv, iv, AES_BLOCK_SIZE); + size_t totalSize = 0; + for (size_t i = 0; i < subSamples.size(); i++) { + totalSize += subSamples[i].numBytesOfClearData; + totalSize += subSamples[i].numBytesOfEncryptedData; + } + + // The first totalSize bytes of shared memory is the encrypted + // input, the second totalSize bytes is the decrypted output. sp sharedMemory = - getDecryptMemory(kSegmentSize * 2, kSegmentIndex); + getDecryptMemory(totalSize * 2, kSegmentIndex); - SharedBuffer sourceBuffer = { - .bufferId = kSegmentIndex, .offset = 0, .size = kSegmentSize}; + const SharedBuffer sourceBuffer = { + .bufferId = kSegmentIndex, .offset = 0, .size = totalSize}; fillRandom(sharedMemory); - DestinationBuffer destBuffer = {.type = BufferType::SHARED_MEMORY, - {.bufferId = kSegmentIndex, - .offset = kSegmentSize, - .size = kSegmentSize}, - .secureMemory = nullptr}; + const DestinationBuffer destBuffer = {.type = BufferType::SHARED_MEMORY, + {.bufferId = kSegmentIndex, + .offset = totalSize, + .size = totalSize}, + .secureMemory = nullptr}; + const uint64_t offset = 0; + const bool kNotSecure = false; + uint32_t bytesWritten = 0; + auto res = cryptoPlugin->decrypt(kNotSecure, toHidlArray(keyId), localIv, mode, + pattern, subSamples, sourceBuffer, offset, destBuffer, + [&](Status status, uint32_t count, string detailedError) { + EXPECT_EQ(expectedStatus, status) << "Unexpected decrypt status " << + detailedError; + bytesWritten = count; + }); + EXPECT_OK(res); - Pattern noPattern = {0, 0}; - vector subSamples = {{.numBytesOfClearData = kSegmentSize, - .numBytesOfEncryptedData = 0}}; - uint64_t offset = 0; + if (bytesWritten != totalSize) { + return bytesWritten; + } + uint8_t* base = static_cast( + static_cast(sharedMemory->getPointer())); + // generate reference vector + vector reference(totalSize); + + memcpy(localIv, iv, AES_BLOCK_SIZE); + switch (mode) { + case Mode::UNENCRYPTED: + memcpy(&reference[0], base, totalSize); + break; + case Mode::AES_CTR: + aes_ctr_decrypt(&reference[0], base, localIv, subSamples, contentKey); + break; + case Mode::AES_CBC: + aes_cbc_decrypt(&reference[0], base, localIv, subSamples, contentKey); + break; + case Mode::AES_CBC_CTS: + EXPECT_TRUE(false) << "AES_CBC_CTS mode not supported"; + break; + } + + // compare reference to decrypted data which is at base + total size + EXPECT_EQ(0, memcmp(static_cast(&reference[0]), + static_cast(base + totalSize), totalSize)) + << "decrypt data mismatch"; + return totalSize; +} + +/** + * Decrypt a list of clear+encrypted subsamples using the specified key + * in AES-CTR mode + */ +void DrmHalClearkeyDecryptTest::aes_ctr_decrypt(uint8_t* dest, uint8_t* src, + uint8_t* iv, const hidl_vec& subSamples, + const vector& key) { + AES_KEY decryptionKey; + AES_set_encrypt_key(&key[0], 128, &decryptionKey); + + size_t offset = 0; + unsigned int blockOffset = 0; + uint8_t previousEncryptedCounter[AES_BLOCK_SIZE]; + memset(previousEncryptedCounter, 0, AES_BLOCK_SIZE); + + for (size_t i = 0; i < subSamples.size(); i++) { + const SubSample& subSample = subSamples[i]; + + if (subSample.numBytesOfClearData > 0) { + memcpy(dest + offset, src + offset, subSample.numBytesOfClearData); + offset += subSample.numBytesOfClearData; + } + + if (subSample.numBytesOfEncryptedData > 0) { + AES_ctr128_encrypt(src + offset, dest + offset, + subSample.numBytesOfEncryptedData, &decryptionKey, + iv, previousEncryptedCounter, &blockOffset); + offset += subSample.numBytesOfEncryptedData; + } + } +} + +/** + * Decrypt a list of clear+encrypted subsamples using the specified key + * in AES-CBC mode + */ +void DrmHalClearkeyDecryptTest::aes_cbc_decrypt(uint8_t* dest, uint8_t* src, + uint8_t* iv, const hidl_vec& subSamples, + const vector& key) { + AES_KEY decryptionKey; + AES_set_encrypt_key(&key[0], 128, &decryptionKey); + + size_t offset = 0; + size_t num = 0; + size_t ecount_buf = 0; + for (size_t i = 0; i < subSamples.size(); i++) { + memcpy(dest + offset, src + offset, subSamples[i].numBytesOfClearData); + offset += subSamples[i].numBytesOfClearData; + + AES_cbc_encrypt(src + offset, dest + offset, subSamples[i].numBytesOfEncryptedData, + &decryptionKey, iv, 0 /* decrypt */); + offset += subSamples[i].numBytesOfEncryptedData; + } +} + +/** + * Test query key status + */ +TEST_F(DrmHalClearkeyDecryptTest, TestQueryKeyStatus) { + auto sessionId = openSession(); + auto res = drmPlugin->queryKeyStatus(sessionId, + [&](Status status, KeyedVector /* info */) { + // clearkey doesn't support this method + EXPECT_EQ(Status::ERROR_DRM_CANNOT_HANDLE, status); + }); + EXPECT_OK(res); +} + + +/** + * Positive decrypt test. "Decrypt" a single clear segment + */ +TEST_F(DrmHalClearkeyDecryptTest, ClearSegmentTest) { + vector iv(AES_BLOCK_SIZE, 0); + const Pattern noPattern = {0, 0}; + const uint32_t kByteCount = 256; + const vector subSamples = { + {.numBytesOfClearData = kByteCount, + .numBytesOfEncryptedData = 0}}; auto sessionId = openSession(); loadKeys(sessionId); @@ -884,21 +1080,57 @@ TEST_F(DrmHalClearkeyDecryptTest, ClearSegmentTest) { EXPECT_EQ(Status::OK, status); const bool kNotSecure = false; - auto res = cryptoPlugin->decrypt( - kNotSecure, toHidlArray(keyId), iv, Mode::UNENCRYPTED, noPattern, - subSamples, sourceBuffer, offset, destBuffer, - [&](Status status, uint32_t bytesWritten, string detailedError) { - EXPECT_EQ(Status::OK, status) << "Failure in decryption:" - << detailedError; - EXPECT_EQ(bytesWritten, kSegmentSize); - }); - EXPECT_OK(res); + uint32_t byteCount = decrypt(Mode::UNENCRYPTED, &iv[0], subSamples, + noPattern, Status::OK); + EXPECT_EQ(kByteCount, byteCount); + + closeSession(sessionId); +} + +/** + * Positive decrypt test. Decrypt a single segment using AES_CTR. + * Verify data matches. + */ +TEST_F(DrmHalClearkeyDecryptTest, EncryptedAesCtrSegmentTest) { + vector iv(AES_BLOCK_SIZE, 0); + const Pattern noPattern = {0, 0}; + const uint32_t kClearBytes = 512; + const uint32_t kEncryptedBytes = 512; + const vector subSamples = { + {.numBytesOfClearData = kClearBytes, + .numBytesOfEncryptedData = kEncryptedBytes + }}; + auto sessionId = openSession(); + loadKeys(sessionId); + + Status status = cryptoPlugin->setMediaDrmSession(sessionId); + EXPECT_EQ(Status::OK, status); + + const bool kNotSecure = false; + uint32_t byteCount = decrypt(Mode::AES_CTR, &iv[0], subSamples, + noPattern, Status::OK); + EXPECT_EQ(kClearBytes + kEncryptedBytes, byteCount); + + closeSession(sessionId); +} +/** + * Negative decrypt test. Decrypt without loading keys. + */ +TEST_F(DrmHalClearkeyDecryptTest, EncryptedAesCtrSegmentTestNoKeys) { + vector iv(AES_BLOCK_SIZE, 0); + const Pattern noPattern = {0, 0}; + const vector subSamples = { + {.numBytesOfClearData = 256, + .numBytesOfEncryptedData = 256}}; + auto sessionId = openSession(); + + Status status = cryptoPlugin->setMediaDrmSession(sessionId); + EXPECT_EQ(Status::OK, status); + + const bool kNotSecure = false; + uint32_t byteCount = decrypt(Mode::AES_CTR, &iv[0], subSamples, + noPattern, Status::ERROR_DRM_NO_LICENSE); + EXPECT_EQ(0u, byteCount); - uint8_t* base = static_cast( - static_cast(sharedMemory->getPointer())); - - EXPECT_EQ(0, memcmp(static_cast(base), - static_cast(base + kSegmentSize), kSegmentSize)) - << "decrypt data mismatch"; closeSession(sessionId); } diff --git a/drm/1.0/vts/functional/drm_hal_vendor_module_api.h b/drm/1.0/vts/functional/drm_hal_vendor_module_api.h index db19719523..73e0cfe933 100644 --- a/drm/1.0/vts/functional/drm_hal_vendor_module_api.h +++ b/drm/1.0/vts/functional/drm_hal_vendor_module_api.h @@ -73,21 +73,21 @@ class DrmHalVTSVendorModule { * value with initial version 1. The API version indicates which subclass * version DrmHalVTSVendorModule this instance is. */ - virtual uint32_t getAPIVersion() = 0; + virtual uint32_t getAPIVersion() const = 0; /** * Return the UUID for the DRM HAL implementation. Protection System * Specific * UUID (see http://dashif.org/identifiers/protection/) */ - virtual std::vector getUUID() = 0; + virtual std::vector getUUID() const = 0; /** * Return the service name for the DRM HAL implementation. If the hal is a * legacy * drm plugin, i.e. not running as a HIDL service, return the empty string. */ - virtual std::string getServiceName() = 0; + virtual std::string getServiceName() const = 0; private: DrmHalVTSVendorModule(const DrmHalVTSVendorModule&) = delete; @@ -103,7 +103,7 @@ class DrmHalVTSVendorModule_V1 : public DrmHalVTSVendorModule { DrmHalVTSVendorModule_V1() {} virtual ~DrmHalVTSVendorModule_V1() {} - virtual uint32_t getAPIVersion() { return 1; } + virtual uint32_t getAPIVersion() const { return 1; } /** * Handle a provisioning request. This function will be called if the HAL @@ -178,11 +178,10 @@ class DrmHalVTSVendorModule_V1 : public DrmHalVTSVendorModule { const std::vector keyId; /** - * The key value is provided to generate expected values for - * validating - * decryption. If isSecure is false, no key value is required. + * The clear content key is provided to generate expected values for + * validating decryption. */ - const std::vector keyValue; + const std::vector clearContentKey; }; std::vector keys; }; @@ -191,7 +190,8 @@ class DrmHalVTSVendorModule_V1 : public DrmHalVTSVendorModule { * Return a list of content configurations that can be exercised by the * VTS test. */ - virtual std::vector getContentConfigurations() = 0; + virtual std::vector + getContentConfigurations() const = 0; /** * Handle a key request. This function will be called if the HAL diff --git a/drm/1.0/vts/functional/drm_hal_vendor_test.cpp b/drm/1.0/vts/functional/drm_hal_vendor_test.cpp index bd78442cfc..7448c4253a 100644 --- a/drm/1.0/vts/functional/drm_hal_vendor_test.cpp +++ b/drm/1.0/vts/functional/drm_hal_vendor_test.cpp @@ -21,27 +21,33 @@ #include #include #include +#include #include #include #include #include #include +#include #include -#include "VtsHalHidlTargetTestBase.h" #include "drm_hal_vendor_module_api.h" #include "vendor_modules.h" +#include "VtsHalHidlTargetTestBase.h" using ::android::hardware::drm::V1_0::BufferType; using ::android::hardware::drm::V1_0::DestinationBuffer; +using ::android::hardware::drm::V1_0::EventType; using ::android::hardware::drm::V1_0::ICryptoFactory; using ::android::hardware::drm::V1_0::ICryptoPlugin; using ::android::hardware::drm::V1_0::IDrmFactory; using ::android::hardware::drm::V1_0::IDrmPlugin; +using ::android::hardware::drm::V1_0::IDrmPluginListener; using ::android::hardware::drm::V1_0::KeyedVector; -using ::android::hardware::drm::V1_0::KeyValue; using ::android::hardware::drm::V1_0::KeyRequestType; +using ::android::hardware::drm::V1_0::KeyStatus; +using ::android::hardware::drm::V1_0::KeyStatusType; using ::android::hardware::drm::V1_0::KeyType; +using ::android::hardware::drm::V1_0::KeyValue; using ::android::hardware::drm::V1_0::Mode; using ::android::hardware::drm::V1_0::Pattern; using ::android::hardware::drm::V1_0::SecureStop; @@ -56,6 +62,7 @@ using ::android::hardware::hidl_memory; using ::android::hardware::hidl_string; using ::android::hardware::hidl_vec; using ::android::hardware::Return; +using ::android::hardware::Void; using ::android::hidl::allocator::V1_0::IAllocator; using ::android::hidl::memory::V1_0::IMemory; using ::android::sp; @@ -67,6 +74,9 @@ using std::map; using std::mt19937; using std::vector; +using ContentConfiguration = ::DrmHalVTSVendorModule_V1::ContentConfiguration; +using Key = ::DrmHalVTSVendorModule_V1::ContentConfiguration::Key; + #define ASSERT_OK(ret) ASSERT_TRUE(ret.isOk()) #define EXPECT_OK(ret) EXPECT_TRUE(ret.isOk()) @@ -80,10 +90,9 @@ static drm_vts::VendorModules* gVendorModules = nullptr; class DrmHalVendorFactoryTest : public testing::TestWithParam { public: DrmHalVendorFactoryTest() - : vendorModule(gVendorModules ? static_cast( - gVendorModules->getVendorModule( - GetParam())) - : nullptr) {} + : vendorModule(static_cast( + gVendorModules->getModule(GetParam()))), + contentConfigurations(vendorModule->getContentConfigurations()) {} virtual ~DrmHalVendorFactoryTest() {} @@ -117,14 +126,27 @@ class DrmHalVendorFactoryTest : public testing::TestWithParam { sp drmFactory; sp cryptoFactory; unique_ptr vendorModule; + const vector contentConfigurations; }; -/** - * Ensure the factory supports its scheme UUID - */ -TEST_P(DrmHalVendorFactoryTest, VendorPluginSupported) { - EXPECT_TRUE(drmFactory->isCryptoSchemeSupported(getVendorUUID())); - EXPECT_TRUE(cryptoFactory->isCryptoSchemeSupported(getVendorUUID())); +TEST_P(DrmHalVendorFactoryTest, ValidateConfigurations) { + const char* kVendorStr = "Vendor module "; + for (auto config : contentConfigurations) { + ASSERT_TRUE(config.name.size() > 0) << kVendorStr << "has no name"; + ASSERT_TRUE(config.serverUrl.size() > 0) << kVendorStr + << "has no serverUrl"; + ASSERT_TRUE(config.initData.size() > 0) << kVendorStr + << "has no init data"; + ASSERT_TRUE(config.mimeType.size() > 0) << kVendorStr + << "has no mime type"; + ASSERT_TRUE(config.keys.size() >= 1) << kVendorStr << "has no keys"; + for (auto key : config.keys) { + ASSERT_TRUE(key.keyId.size() > 0) << kVendorStr + << " has zero length keyId"; + ASSERT_TRUE(key.keyId.size() > 0) << kVendorStr + << " has zero length key value"; + } + } } /** @@ -135,6 +157,48 @@ TEST_P(DrmHalVendorFactoryTest, InvalidPluginNotSupported) { EXPECT_FALSE(cryptoFactory->isCryptoSchemeSupported(kInvalidUUID)); } +/** + * Ensure the factory doesn't support an empty UUID + */ +TEST_P(DrmHalVendorFactoryTest, EmptyPluginUUIDNotSupported) { + hidl_array emptyUUID; + EXPECT_FALSE(drmFactory->isCryptoSchemeSupported(emptyUUID)); + EXPECT_FALSE(cryptoFactory->isCryptoSchemeSupported(emptyUUID)); +} + +/** + * Ensure the factory supports the scheme uuid in the config + */ +TEST_P(DrmHalVendorFactoryTest, EmptyPluginConfigUUIDSupported) { + EXPECT_TRUE(drmFactory->isCryptoSchemeSupported(getVendorUUID())); + EXPECT_TRUE(cryptoFactory->isCryptoSchemeSupported(getVendorUUID())); +} + +/** + * Ensure empty content type is not supported + */ +TEST_P(DrmHalVendorFactoryTest, EmptyContentTypeNotSupported) { + hidl_string empty; + EXPECT_FALSE(drmFactory->isContentTypeSupported(empty)); +} + +/** + * Ensure invalid content type is not supported + */ +TEST_P(DrmHalVendorFactoryTest, InvalidContentTypeNotSupported) { + hidl_string invalid("abcdabcd"); + EXPECT_FALSE(drmFactory->isContentTypeSupported(invalid)); +} + +/** + * Ensure valid content types in the configs are supported + */ +TEST_P(DrmHalVendorFactoryTest, ValidContentTypeSupported) { + for (auto config : contentConfigurations) { + EXPECT_TRUE(drmFactory->isContentTypeSupported(config.mimeType)); + } +} + /** * Ensure vendor drm plugin can be created */ @@ -392,6 +456,26 @@ TEST_P(DrmHalVendorPluginTest, ProvideKeyResponseEmptyResponse) { closeSession(session); } +/** + * Test that a removeKeys on an empty sessionID returns BAD_VALUE + */ +TEST_P(DrmHalVendorPluginTest, RemoveKeysEmptySessionId) { + SessionId sessionId; + Status status = drmPlugin->removeKeys(sessionId); + EXPECT_TRUE(status == Status::BAD_VALUE); +} + +/** + * Test that remove keys returns okay on an initialized session + * that has no keys. + */ +TEST_P(DrmHalVendorPluginTest, RemoveKeysNewSession) { + SessionId sessionId = openSession(); + Status status = drmPlugin->removeKeys(sessionId); + EXPECT_TRUE(status == Status::OK); + closeSession(sessionId); +} + /** * Test that the plugin either doesn't support getting * secure stops, or has no secure stops available after @@ -721,6 +805,175 @@ TEST_P(DrmHalVendorPluginTest, RequiresSecureDecoderInvalidMimeType) { EXPECT_FALSE(cryptoPlugin->requiresSecureDecoderComponent("bad")); } +/** + * Verify that requiresSecureDecoderComponent returns true for secure + * configurations + */ +TEST_P(DrmHalVendorPluginTest, RequiresSecureDecoderConfig) { + const char* kVendorStr = "Vendor module "; + for (auto config : contentConfigurations) { + for (auto key : config.keys) { + if (key.isSecure) { + EXPECT_TRUE(cryptoPlugin->requiresSecureDecoderComponent(config.mimeType)); + break; + } + } + } +} + +/** + * Event Handling tests + */ + +class TestDrmPluginListener : public IDrmPluginListener { +public: + TestDrmPluginListener() {reset();} + virtual ~TestDrmPluginListener() {} + + virtual Return sendEvent(EventType eventType, const hidl_vec& sessionId, + const hidl_vec& data) override { + eventType_ = eventType; + sessionId_ = sessionId; + data_ = data; + gotEvent_ = true; + return Void(); + } + + virtual Return sendExpirationUpdate(const hidl_vec& sessionId, + int64_t expiryTimeInMS) override { + sessionId_ = sessionId; + expiryTimeInMS_ = expiryTimeInMS; + gotExpirationUpdate_ = true; + return Void(); + } + + virtual Return sendKeysChange(const hidl_vec& sessionId, + const hidl_vec& keyStatusList, bool hasNewUsableKey) override { + sessionId_ = sessionId; + keyStatusList_ = keyStatusList; + hasNewUsableKey_ = hasNewUsableKey; + gotKeysChange_ = true; + return Void(); + } + + EventType getEventType() const {return eventType_;} + SessionId getSessionId() const {return sessionId_;} + vector getData() const {return data_;} + int64_t getExpiryTimeInMS() const {return expiryTimeInMS_;} + hidl_vec getKeyStatusList() const {return keyStatusList_;} + bool hasNewUsableKey() {return hasNewUsableKey_;} + bool gotEvent() {return gotEvent_;} + bool gotExpirationUpdate() {return gotExpirationUpdate_;} + bool gotKeysChange() {return gotKeysChange_;} + + void reset() { + gotEvent_ = gotExpirationUpdate_ = gotKeysChange_ = false; + eventType_ = EventType::PROVISION_REQUIRED; + sessionId_ = SessionId(); + data_ = hidl_vec(); + expiryTimeInMS_ = 0; + keyStatusList_ = hidl_vec(); + hasNewUsableKey_ = false; + } + +private: + bool gotEvent_; + bool gotExpirationUpdate_; + bool gotKeysChange_; + + EventType eventType_; + SessionId sessionId_; + hidl_vec data_; + int64_t expiryTimeInMS_; + hidl_vec keyStatusList_; + bool hasNewUsableKey_; +}; + +/** + * Simulate the plugin sending events. Make sure the listener + * gets them. + */ +TEST_P(DrmHalVendorPluginTest, ListenerEvents) { + sp listener = new TestDrmPluginListener(); + drmPlugin->setListener(listener); + auto sessionId = openSession(); + vector data = {0, 1, 2}; + EventType eventTypes[] = {EventType::PROVISION_REQUIRED, + EventType::KEY_NEEDED, + EventType::KEY_EXPIRED, + EventType::VENDOR_DEFINED, + EventType::SESSION_RECLAIMED}; + for (auto eventType : eventTypes) { + listener->reset(); + drmPlugin->sendEvent(eventType, sessionId, data); + while (!listener->gotEvent()) {usleep(100);} + EXPECT_EQ(eventType, listener->getEventType()); + EXPECT_EQ(sessionId, listener->getSessionId()); + EXPECT_EQ(data, listener->getData()); + } + closeSession(sessionId); +} + +/** + * Simulate the plugin sending expiration updates and make sure + * the listener gets them. + */ +TEST_P(DrmHalVendorPluginTest, ListenerExpirationUpdate) { + sp listener = new TestDrmPluginListener(); + drmPlugin->setListener(listener); + auto sessionId = openSession(); + drmPlugin->sendExpirationUpdate(sessionId, 100); + while (!listener->gotExpirationUpdate()) {usleep(100);} + EXPECT_EQ(sessionId, listener->getSessionId()); + EXPECT_EQ(100, listener->getExpiryTimeInMS()); + closeSession(sessionId); +} + +/** + * Simulate the plugin sending keys change and make sure + * the listener gets them. + */ +TEST_P(DrmHalVendorPluginTest, ListenerKeysChange) { + sp listener = new TestDrmPluginListener(); + drmPlugin->setListener(listener); + auto sessionId = openSession(); + const hidl_vec keyStatusList = { + {{1}, KeyStatusType::USABLE}, + {{2}, KeyStatusType::EXPIRED}, + {{3}, KeyStatusType::OUTPUTNOTALLOWED}, + {{4}, KeyStatusType::STATUSPENDING}, + {{5}, KeyStatusType::INTERNALERROR}, + }; + + drmPlugin->sendKeysChange(sessionId, keyStatusList, true); + while (!listener->gotKeysChange()) {usleep(100);} + EXPECT_EQ(sessionId, listener->getSessionId()); + EXPECT_EQ(keyStatusList, listener->getKeyStatusList()); + EXPECT_EQ(true, listener->hasNewUsableKey()); +} + +/** + * Negative listener tests. Call send methods with no + * listener set. + */ +TEST_P(DrmHalVendorPluginTest, NotListening) { + sp listener = new TestDrmPluginListener(); + drmPlugin->setListener(listener); + drmPlugin->setListener(nullptr); + + SessionId sessionId; + vector data; + hidl_vec keyStatusList; + drmPlugin->sendEvent(EventType::PROVISION_REQUIRED, sessionId, data); + drmPlugin->sendExpirationUpdate(sessionId, 100); + drmPlugin->sendKeysChange(sessionId, keyStatusList, true); + usleep(1000); // can't wait for the event to be recieved, just wait a long time + EXPECT_EQ(false, listener->gotEvent()); + EXPECT_EQ(false, listener->gotExpirationUpdate()); + EXPECT_EQ(false, listener->gotKeysChange()); +} + + /** * CryptoPlugin tests */ @@ -785,6 +1038,15 @@ TEST_P(DrmHalVendorPluginTest, SetMediaDrmSessionClosedSession) { EXPECT_EQ(Status::ERROR_DRM_SESSION_NOT_OPENED, status); } +/** + * setMediaDrmSession with a empty session id: BAD_VALUE + */ +TEST_P(DrmHalVendorPluginTest, SetMediaDrmSessionEmptySession) { + SessionId sessionId; + Status status = cryptoPlugin->setMediaDrmSession(sessionId); + EXPECT_EQ(Status::BAD_VALUE, status); +} + /** * Decrypt tests */ @@ -796,14 +1058,23 @@ class DrmHalVendorDecryptTest : public DrmHalVendorPluginTest { protected: void loadKeys(const SessionId& sessionId, - const DrmHalVTSVendorModule_V1::ContentConfiguration& - configuration); + const ContentConfiguration& configuration); void fillRandom(const sp& memory); KeyedVector toHidlKeyedVector(const map& params); hidl_array toHidlArray(const vector& vec) { EXPECT_EQ(vec.size(), 16u); return hidl_array(&vec[0]); } + hidl_vec queryKeyStatus(SessionId sessionId); + void removeKeys(SessionId sessionId); + uint32_t decrypt(Mode mode, bool isSecure, + const hidl_array& keyId, uint8_t* iv, + const hidl_vec& subSamples, const Pattern& pattern, + const vector& key, Status expectedStatus); + void aes_ctr_decrypt(uint8_t* dest, uint8_t* src, uint8_t* iv, + const hidl_vec& subSamples, const vector& key); + void aes_cbc_decrypt(uint8_t* dest, uint8_t* src, uint8_t* iv, + const hidl_vec& subSamples, const vector& key); }; KeyedVector DrmHalVendorDecryptTest::toHidlKeyedVector( @@ -823,9 +1094,8 @@ KeyedVector DrmHalVendorDecryptTest::toHidlKeyedVector( * These tests use predetermined key request/response to * avoid requiring a round trip to a license server. */ -void DrmHalVendorDecryptTest::loadKeys( - const SessionId& sessionId, - const DrmHalVTSVendorModule_V1::ContentConfiguration& configuration) { +void DrmHalVendorDecryptTest::loadKeys(const SessionId& sessionId, + const ContentConfiguration& configuration) { hidl_vec keyRequest; auto res = drmPlugin->getKeyRequest( sessionId, configuration.initData, configuration.mimeType, @@ -874,111 +1144,326 @@ void DrmHalVendorDecryptTest::fillRandom(const sp& memory) { } } -TEST_P(DrmHalVendorDecryptTest, ValidateConfigurations) { - vector configurations = - vendorModule->getContentConfigurations(); - const char* kVendorStr = "Vendor module "; - for (auto config : configurations) { - ASSERT_TRUE(config.name.size() > 0) << kVendorStr << "has no name"; - ASSERT_TRUE(config.serverUrl.size() > 0) << kVendorStr - << "has no serverUrl"; - ASSERT_TRUE(config.initData.size() > 0) << kVendorStr - << "has no init data"; - ASSERT_TRUE(config.mimeType.size() > 0) << kVendorStr - << "has no mime type"; - ASSERT_TRUE(config.keys.size() >= 1) << kVendorStr << "has no keys"; - for (auto key : config.keys) { - ASSERT_TRUE(key.keyId.size() > 0) << kVendorStr - << " has zero length keyId"; - ASSERT_TRUE(key.keyId.size() > 0) << kVendorStr - << " has zero length key value"; +hidl_vec DrmHalVendorDecryptTest::queryKeyStatus(SessionId sessionId) { + hidl_vec keyStatus; + auto res = drmPlugin->queryKeyStatus(sessionId, + [&](Status status, KeyedVector info) { + EXPECT_EQ(Status::OK, status); + keyStatus = info; + }); + EXPECT_OK(res); + return keyStatus; +} + +void DrmHalVendorDecryptTest::removeKeys(SessionId sessionId) { + auto res = drmPlugin->removeKeys(sessionId); + EXPECT_OK(res); +} + +uint32_t DrmHalVendorDecryptTest::decrypt(Mode mode, bool isSecure, + const hidl_array& keyId, uint8_t* iv, + const hidl_vec& subSamples, const Pattern& pattern, + const vector& key, Status expectedStatus) { + const size_t kSegmentIndex = 0; + + uint8_t localIv[AES_BLOCK_SIZE]; + memcpy(localIv, iv, AES_BLOCK_SIZE); + + size_t totalSize = 0; + for (size_t i = 0; i < subSamples.size(); i++) { + totalSize += subSamples[i].numBytesOfClearData; + totalSize += subSamples[i].numBytesOfEncryptedData; + } + + // The first totalSize bytes of shared memory is the encrypted + // input, the second totalSize bytes is the decrypted output. + sp sharedMemory = + getDecryptMemory(totalSize * 2, kSegmentIndex); + + SharedBuffer sourceBuffer = { + .bufferId = kSegmentIndex, .offset = 0, .size = totalSize}; + fillRandom(sharedMemory); + + DestinationBuffer destBuffer = {.type = BufferType::SHARED_MEMORY, + {.bufferId = kSegmentIndex, + .offset = totalSize, + .size = totalSize}, + .secureMemory = nullptr}; + uint64_t offset = 0; + uint32_t bytesWritten = 0; + auto res = cryptoPlugin->decrypt(isSecure, keyId, localIv, mode, pattern, + subSamples, sourceBuffer, offset, destBuffer, + [&](Status status, uint32_t count, string detailedError) { + EXPECT_EQ(expectedStatus, status) << "Unexpected decrypt status " << + detailedError; + bytesWritten = count; + }); + EXPECT_OK(res); + + if (bytesWritten != totalSize) { + return bytesWritten; + } + uint8_t* base = static_cast( + static_cast(sharedMemory->getPointer())); + + // generate reference vector + vector reference(totalSize); + + memcpy(localIv, iv, AES_BLOCK_SIZE); + switch (mode) { + case Mode::UNENCRYPTED: + memcpy(&reference[0], base, totalSize); + break; + case Mode::AES_CTR: + aes_ctr_decrypt(&reference[0], base, localIv, subSamples, key); + break; + case Mode::AES_CBC: + aes_cbc_decrypt(&reference[0], base, localIv, subSamples, key); + break; + case Mode::AES_CBC_CTS: + EXPECT_TRUE(false) << "AES_CBC_CTS mode not supported"; + break; + } + + // compare reference to decrypted data which is at base + total size + EXPECT_EQ(0, memcmp(static_cast(&reference[0]), + static_cast(base + totalSize), totalSize)) + << "decrypt data mismatch"; + return totalSize; +} + +/** + * Decrypt a list of clear+encrypted subsamples using the specified key + * in AES-CTR mode + */ +void DrmHalVendorDecryptTest::aes_ctr_decrypt(uint8_t* dest, uint8_t* src, + uint8_t* iv, const hidl_vec& subSamples, + const vector& key) { + + AES_KEY decryptionKey; + AES_set_encrypt_key(&key[0], 128, &decryptionKey); + + size_t offset = 0; + unsigned blockOffset = 0; + uint8_t previousEncryptedCounter[AES_BLOCK_SIZE]; + memset(previousEncryptedCounter, 0, AES_BLOCK_SIZE); + + for (size_t i = 0; i < subSamples.size(); i++) { + const SubSample& subSample = subSamples[i]; + + if (subSample.numBytesOfClearData > 0) { + memcpy(dest + offset, src + offset, subSample.numBytesOfClearData); + offset += subSample.numBytesOfClearData; + } + + if (subSample.numBytesOfEncryptedData > 0) { + AES_ctr128_encrypt(src + offset, dest + offset, + subSample.numBytesOfEncryptedData, &decryptionKey, + iv, previousEncryptedCounter, &blockOffset); + offset += subSample.numBytesOfEncryptedData; } } } /** - * Positive decrypt test. "Decrypt" a single clear - * segment. Verify data matches. + * Decrypt a list of clear+encrypted subsamples using the specified key + * in AES-CBC mode */ -TEST_P(DrmHalVendorDecryptTest, ClearSegmentTest) { - vector configurations = - vendorModule->getContentConfigurations(); - for (auto config : configurations) { - const size_t kSegmentSize = 1024; - const size_t kSegmentIndex = 0; - uint8_t iv[16] = {0}; +void DrmHalVendorDecryptTest::aes_cbc_decrypt(uint8_t* dest, uint8_t* src, + uint8_t* iv, const hidl_vec& subSamples, + const vector& key) { + AES_KEY decryptionKey; + AES_set_encrypt_key(&key[0], 128, &decryptionKey); - sp sharedMemory = - getDecryptMemory(kSegmentSize * 2, kSegmentIndex); + size_t offset = 0; + size_t num = 0; + size_t ecount_buf = 0; + for (size_t i = 0; i < subSamples.size(); i++) { + const SubSample& subSample = subSamples[i]; - SharedBuffer sourceBuffer = { - .bufferId = kSegmentIndex, .offset = 0, .size = kSegmentSize}; - fillRandom(sharedMemory); + memcpy(dest + offset, src + offset, subSample.numBytesOfClearData); + offset += subSample.numBytesOfClearData; - DestinationBuffer destBuffer = {.type = BufferType::SHARED_MEMORY, - {.bufferId = kSegmentIndex, - .offset = kSegmentSize, - .size = kSegmentSize}, - .secureMemory = nullptr}; + AES_cbc_encrypt(src + offset, dest + offset, subSample.numBytesOfEncryptedData, + &decryptionKey, iv, 0 /* decrypt */); + offset += subSample.numBytesOfEncryptedData; + } +} - Pattern noPattern = {0, 0}; - vector subSamples = {{.numBytesOfClearData = kSegmentSize, - .numBytesOfEncryptedData = 0}}; - uint64_t offset = 0; +/** + * Test key status with empty session id, should return BAD_VALUE + */ +TEST_P(DrmHalVendorDecryptTest, QueryKeyStatusInvalidSession) { + SessionId sessionId; + auto res = drmPlugin->queryKeyStatus(sessionId, + [&](Status status, KeyedVector /* info */) { + EXPECT_EQ(Status::BAD_VALUE, status); + }); + EXPECT_OK(res); +} + + +/** + * Test key status. There should be no key status prior to loading keys + */ +TEST_P(DrmHalVendorDecryptTest, QueryKeyStatusWithNoKeys) { + auto sessionId = openSession(); + auto keyStatus = queryKeyStatus(sessionId); + EXPECT_EQ(0u, keyStatus.size()); + closeSession(sessionId); +} + + +/** + * Test key status. There should be key status after loading keys. + */ +TEST_P(DrmHalVendorDecryptTest, QueryKeyStatus) { + for (auto config : contentConfigurations) { auto sessionId = openSession(); loadKeys(sessionId, config); - - Status status = cryptoPlugin->setMediaDrmSession(sessionId); - EXPECT_EQ(Status::OK, status); - - const bool kNotSecure = false; - auto res = cryptoPlugin->decrypt( - kNotSecure, toHidlArray(config.keys[0].keyId), iv, - Mode::UNENCRYPTED, noPattern, subSamples, sourceBuffer, offset, - destBuffer, [&](Status status, uint32_t bytesWritten, - string detailedError) { - EXPECT_EQ(Status::OK, status) << "Failure in decryption " - "for configuration " - << config.name << ": " - << detailedError; - EXPECT_EQ(bytesWritten, kSegmentSize); - }); - EXPECT_OK(res); - uint8_t* base = static_cast( - static_cast(sharedMemory->getPointer())); - - EXPECT_EQ(0, - memcmp(static_cast(base), - static_cast(base + kSegmentSize), kSegmentSize)) - << "decrypt data mismatch"; + auto keyStatus = queryKeyStatus(sessionId); + EXPECT_NE(0u, keyStatus.size()); closeSession(sessionId); } } +/** + * Positive decrypt test. "Decrypt" a single clear segment and verify. + */ +TEST_P(DrmHalVendorDecryptTest, ClearSegmentTest) { + for (auto config : contentConfigurations) { + for (auto key : config.keys) { + const size_t kSegmentSize = 1024; + vector iv(AES_BLOCK_SIZE, 0); + const Pattern noPattern = {0, 0}; + const vector subSamples = {{.numBytesOfClearData = kSegmentSize, + .numBytesOfEncryptedData = 0}}; + auto sessionId = openSession(); + loadKeys(sessionId, config); + + Status status = cryptoPlugin->setMediaDrmSession(sessionId); + EXPECT_EQ(Status::OK, status); + + uint32_t byteCount = decrypt(Mode::UNENCRYPTED, key.isSecure, toHidlArray(key.keyId), + &iv[0], subSamples, noPattern, key.clearContentKey, Status::OK); + EXPECT_EQ(kSegmentSize, byteCount); + + closeSession(sessionId); + } + } +} + +/** + * Positive decrypt test. Decrypt a single segment using aes_ctr. + * Verify data matches. + */ +TEST_P(DrmHalVendorDecryptTest, EncryptedAesCtrSegmentTest) { + for (auto config : contentConfigurations) { + for (auto key : config.keys) { + const size_t kSegmentSize = 1024; + vector iv(AES_BLOCK_SIZE, 0); + const Pattern noPattern = {0, 0}; + const vector subSamples = {{.numBytesOfClearData = kSegmentSize, + .numBytesOfEncryptedData = 0}}; + auto sessionId = openSession(); + loadKeys(sessionId, config); + + Status status = cryptoPlugin->setMediaDrmSession(sessionId); + EXPECT_EQ(Status::OK, status); + + uint32_t byteCount = decrypt(Mode::AES_CTR, key.isSecure, toHidlArray(key.keyId), + &iv[0], subSamples, noPattern, key.clearContentKey, Status::OK); + EXPECT_EQ(kSegmentSize, byteCount); + + closeSession(sessionId); + } + } +} + +/** + * Negative decrypt test. Decrypt without loading keys. + */ +TEST_P(DrmHalVendorDecryptTest, EncryptedAesCtrSegmentTestNoKeys) { + for (auto config : contentConfigurations) { + for (auto key : config.keys) { + vector iv(AES_BLOCK_SIZE, 0); + const Pattern noPattern = {0, 0}; + const vector subSamples = {{.numBytesOfClearData = 256, + .numBytesOfEncryptedData = 256}}; + auto sessionId = openSession(); + + Status status = cryptoPlugin->setMediaDrmSession(sessionId); + EXPECT_EQ(Status::OK, status); + + uint32_t byteCount = decrypt(Mode::AES_CTR, key.isSecure, + toHidlArray(key.keyId), &iv[0], subSamples, noPattern, + key.clearContentKey, Status::ERROR_DRM_NO_LICENSE); + EXPECT_EQ(0u, byteCount); + + closeSession(sessionId); + } + } +} + +/** + * Test key removal. Load keys then remove them and verify that + * decryption can't be performed. + */ +TEST_P(DrmHalVendorDecryptTest, AttemptDecryptWithKeysRemoved) { + for (auto config : contentConfigurations) { + for (auto key : config.keys) { + vector iv(AES_BLOCK_SIZE, 0); + const Pattern noPattern = {0, 0}; + const vector subSamples = {{.numBytesOfClearData = 256, + .numBytesOfEncryptedData = 256}}; + auto sessionId = openSession(); + + Status status = cryptoPlugin->setMediaDrmSession(sessionId); + EXPECT_EQ(Status::OK, status); + + loadKeys(sessionId, config); + removeKeys(sessionId); + + uint32_t byteCount = decrypt(Mode::AES_CTR, key.isSecure, + toHidlArray(key.keyId), &iv[0], subSamples, noPattern, + key.clearContentKey, Status::ERROR_DRM_DECRYPT); + EXPECT_EQ(0u, byteCount); + + closeSession(sessionId); + } + } +} + + /** * Instantiate the set of test cases for each vendor module */ INSTANTIATE_TEST_CASE_P( DrmHalVendorFactoryTestCases, DrmHalVendorFactoryTest, - testing::ValuesIn(gVendorModules->getVendorModulePaths())); + testing::ValuesIn(gVendorModules->getPathList())); INSTANTIATE_TEST_CASE_P( DrmHalVendorPluginTestCases, DrmHalVendorPluginTest, - testing::ValuesIn(gVendorModules->getVendorModulePaths())); + testing::ValuesIn(gVendorModules->getPathList())); INSTANTIATE_TEST_CASE_P( DrmHalVendorDecryptTestCases, DrmHalVendorDecryptTest, - testing::ValuesIn(gVendorModules->getVendorModulePaths())); + testing::ValuesIn(gVendorModules->getPathList())); int main(int argc, char** argv) { #if defined(__LP64__) - const char *kModulePath = "/data/local/tmp/64/lib"; + const char* kModulePath = "/data/local/tmp/64/lib"; #else - const char *kModulePath = "/data/local/tmp/32/lib"; + const char* kModulePath = "/data/local/tmp/32/lib"; #endif gVendorModules = new drm_vts::VendorModules(kModulePath); + if (gVendorModules->getPathList().size() == 0) { + std::cerr << "No vendor modules found in " << kModulePath << + ", exiting" << std::endl; + exit(-1); + } ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } diff --git a/drm/1.0/vts/functional/vendor_modules.cpp b/drm/1.0/vts/functional/vendor_modules.cpp index 34af6f86c6..bb232aeafc 100644 --- a/drm/1.0/vts/functional/vendor_modules.cpp +++ b/drm/1.0/vts/functional/vendor_modules.cpp @@ -29,44 +29,37 @@ using std::vector; using std::unique_ptr; namespace drm_vts { -vector VendorModules::getVendorModulePaths() { - if (mModuleList.size() > 0) { - return mModuleList; - } - - DIR* dir = opendir(mModulesPath.c_str()); +void VendorModules::scanModules(const std::string &directory) { + DIR* dir = opendir(directory.c_str()); if (dir == NULL) { - ALOGE("Unable to open drm VTS vendor directory %s", - mModulesPath.c_str()); - return mModuleList; - } - - struct dirent* entry; - while ((entry = readdir(dir))) { - string fullpath = mModulesPath + "/" + entry->d_name; - if (endsWith(fullpath, ".so")) { - mModuleList.push_back(fullpath); + ALOGE("Unable to open drm VTS vendor directory %s", directory.c_str()); + } else { + struct dirent* entry; + while ((entry = readdir(dir))) { + ALOGD("checking file %s", entry->d_name); + string fullpath = directory + "/" + entry->d_name; + if (endsWith(fullpath, ".so")) { + mPathList.push_back(fullpath); + } } + closedir(dir); } - - closedir(dir); - return mModuleList; } -DrmHalVTSVendorModule* VendorModules::getVendorModule(const string& path) { - unique_ptr& library = mOpenLibraries[path]; - if (!library) { - library = unique_ptr(new SharedLibrary(path)); +DrmHalVTSVendorModule* VendorModules::getModule(const string& path) { + if (mOpenLibraries.find(path) == mOpenLibraries.end()) { + auto library = std::make_unique(path); if (!library) { ALOGE("failed to map shared library %s", path.c_str()); return NULL; } + mOpenLibraries[path] = std::move(library); } + const unique_ptr& library = mOpenLibraries[path]; void* symbol = library->lookup("vendorModuleFactory"); if (symbol == NULL) { ALOGE("getVendorModule failed to lookup 'vendorModuleFactory' in %s: " - "%s", - path.c_str(), library->lastError()); + "%s", path.c_str(), library->lastError()); return NULL; } typedef DrmHalVTSVendorModule* (*ModuleFactory)(); diff --git a/drm/1.0/vts/functional/vendor_modules.h b/drm/1.0/vts/functional/vendor_modules.h index 5371a0dbf7..ca538f6bac 100644 --- a/drm/1.0/vts/functional/vendor_modules.h +++ b/drm/1.0/vts/functional/vendor_modules.h @@ -30,27 +30,33 @@ class VendorModules { * Initialize with a file system path where the shared libraries * are to be found. */ - explicit VendorModules(const std::string& path) : mModulesPath(path) {} + explicit VendorModules(const std::string& dir) { + scanModules(dir); + } ~VendorModules() {} - /** - * Return a list of paths to available vendor modules. - */ - std::vector getVendorModulePaths(); - /** * Retrieve a DrmHalVTSVendorModule given its full path. The * getAPIVersion method can be used to determine the versioned * subclass type. */ - DrmHalVTSVendorModule* getVendorModule(const std::string& path); + DrmHalVTSVendorModule* getModule(const std::string& path); + + /** + * Return the list of paths to available vendor modules. + */ + std::vector getPathList() const {return mPathList;} private: - std::string mModulesPath; - std::vector mModuleList; + std::vector mPathList; std::map> mOpenLibraries; - inline bool endsWith(const std::string& str, const std::string& suffix) { + /** + * Scan the list of paths to available vendor modules. + */ + void scanModules(const std::string& dir); + + inline bool endsWith(const std::string& str, const std::string& suffix) const { if (suffix.size() > str.size()) return false; return std::equal(suffix.rbegin(), suffix.rend(), str.rbegin()); }