From: Jeff Tinker Date: Thu, 12 Jan 2017 03:42:05 +0000 (-0800) Subject: Add VTS tests for drm+crypto HALs X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=0ea406920af8313875866f13c9104129c08b2d76;p=android-x86%2Fhardware-interfaces.git Add VTS tests for drm+crypto HALs Tests: drm vts tests are passing related-to-bug: 32815560 Change-Id: I2b36f27fbb42eba37f3e5a26acea0e359e60b3af --- diff --git a/drm/1.0/vts/doc/Drm_Vendor_Modules_v1.pdf b/drm/1.0/vts/doc/Drm_Vendor_Modules_v1.pdf new file mode 100644 index 00000000..1b44e4f3 Binary files /dev/null and b/drm/1.0/vts/doc/Drm_Vendor_Modules_v1.pdf differ diff --git a/drm/1.0/vts/functional/Android.bp b/drm/1.0/vts/functional/Android.bp new file mode 100644 index 00000000..546aa124 --- /dev/null +++ b/drm/1.0/vts/functional/Android.bp @@ -0,0 +1,46 @@ +// +// Copyright (C) 2017 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. +// + +cc_test { + name: "drm_hidl_test", + srcs: [ + "drm_hal_clearkey_test.cpp", + "drm_hal_vendor_test.cpp", + "shared_library.cpp", + "vendor_modules.cpp" + ], + shared_libs: [ + "android.hardware.drm@1.0", + "android.hidl.allocator@1.0", + "android.hidl.memory@1.0", + "libbase", + "libcutils", + "libhidlbase", + "libhidlmemory", + "libhidltransport", + "libhwbinder", + "liblog", + "libnativehelper", + "libutils", + ], + static_libs: [ + "VtsHalHidlTargetTestBase" + ], + cflags: [ + "-O0", + "-g", + ], +} diff --git a/drm/1.0/vts/functional/drm_hal_clearkey_test.cpp b/drm/1.0/vts/functional/drm_hal_clearkey_test.cpp new file mode 100644 index 00000000..2296d2d2 --- /dev/null +++ b/drm/1.0/vts/functional/drm_hal_clearkey_test.cpp @@ -0,0 +1,904 @@ +/* + * Copyright (C) 2017 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 "drm_hal_clearkey_test@1.0" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "VtsHalHidlTargetTestBase.h" + +using ::android::hardware::drm::V1_0::BufferType; +using ::android::hardware::drm::V1_0::DestinationBuffer; +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::KeyedVector; +using ::android::hardware::drm::V1_0::KeyValue; +using ::android::hardware::drm::V1_0::KeyRequestType; +using ::android::hardware::drm::V1_0::KeyType; +using ::android::hardware::drm::V1_0::Mode; +using ::android::hardware::drm::V1_0::Pattern; +using ::android::hardware::drm::V1_0::SecureStop; +using ::android::hardware::drm::V1_0::SecureStopId; +using ::android::hardware::drm::V1_0::SessionId; +using ::android::hardware::drm::V1_0::SharedBuffer; +using ::android::hardware::drm::V1_0::Status; +using ::android::hardware::drm::V1_0::SubSample; + +using ::android::hardware::hidl_array; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_memory; +using ::android::hardware::hidl_vec; +using ::android::hardware::Return; +using ::android::hidl::allocator::V1_0::IAllocator; +using ::android::hidl::memory::V1_0::IMemory; +using ::android::sp; + +using std::string; +using std::unique_ptr; +using std::random_device; +using std::map; +using std::mt19937; +using std::vector; + +/** + * These clearkey tests use white box knowledge of the legacy clearkey + * plugin to verify that the HIDL HAL services and interfaces are working. + * It is not intended to verify any vendor's HAL implementation. If you + * are looking for vendor HAL tests, see drm_hal_vendor_test.cpp + */ +#define ASSERT_OK(ret) ASSERT_TRUE(ret.isOk()) +#define EXPECT_OK(ret) EXPECT_TRUE(ret.isOk()) + +static const uint8_t kClearKeyUUID[16] = { + 0x10, 0x77, 0xEF, 0xEC, 0xC0, 0xB2, 0x4D, 0x02, + 0xAC, 0xE3, 0x3C, 0x1E, 0x52, 0xE2, 0xFB, 0x4B}; + +static const uint8_t kInvalidUUID[16] = { + 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, + 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80}; + +class DrmHalClearkeyFactoryTest : public ::testing::VtsHalHidlTargetTestBase { + public: + virtual void SetUp() override { + const ::testing::TestInfo* const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + ALOGD("Running test %s.%s", test_info->test_case_name(), + test_info->name()); + + drmFactory = + ::testing::VtsHalHidlTargetTestBase::getService( + "drm"); + ASSERT_NE(drmFactory, nullptr); + cryptoFactory = + ::testing::VtsHalHidlTargetTestBase::getService( + "crypto"); + ASSERT_NE(cryptoFactory, nullptr); + } + + virtual void TearDown() override {} + + protected: + sp drmFactory; + sp cryptoFactory; +}; + +/** + * Ensure the factory supports the clearkey scheme UUID + */ +TEST_F(DrmHalClearkeyFactoryTest, ClearKeyPluginSupported) { + EXPECT_TRUE(drmFactory->isCryptoSchemeSupported(kClearKeyUUID)); + EXPECT_TRUE(cryptoFactory->isCryptoSchemeSupported(kClearKeyUUID)); +} + +/** + * Ensure the factory doesn't support an invalid scheme UUID + */ +TEST_F(DrmHalClearkeyFactoryTest, InvalidPluginNotSupported) { + EXPECT_FALSE(drmFactory->isCryptoSchemeSupported(kInvalidUUID)); + EXPECT_FALSE(cryptoFactory->isCryptoSchemeSupported(kInvalidUUID)); +} + +/** + * Ensure clearkey drm plugin can be created + */ +TEST_F(DrmHalClearkeyFactoryTest, CreateClearKeyDrmPlugin) { + hidl_string packageName("android.hardware.drm.test"); + auto res = drmFactory->createPlugin( + kClearKeyUUID, packageName, + [&](Status status, const sp& plugin) { + EXPECT_EQ(Status::OK, status); + EXPECT_NE(plugin, nullptr); + }); + EXPECT_OK(res); +} + +/** + * Ensure clearkey crypto plugin can be created + */ +TEST_F(DrmHalClearkeyFactoryTest, CreateClearKeyCryptoPlugin) { + hidl_vec initVec; + auto res = cryptoFactory->createPlugin( + kClearKeyUUID, initVec, + [&](Status status, const sp& plugin) { + EXPECT_EQ(Status::OK, status); + EXPECT_NE(plugin, nullptr); + }); + EXPECT_OK(res); +} + +/** + * Ensure invalid drm plugin can't be created + */ +TEST_F(DrmHalClearkeyFactoryTest, CreateInvalidDrmPlugin) { + hidl_string packageName("android.hardware.drm.test"); + auto res = drmFactory->createPlugin( + kInvalidUUID, packageName, + [&](Status status, const sp& plugin) { + EXPECT_EQ(Status::ERROR_DRM_CANNOT_HANDLE, status); + EXPECT_EQ(plugin, nullptr); + }); + EXPECT_OK(res); +} + +/** + * Ensure invalid crypto plugin can't be created + */ +TEST_F(DrmHalClearkeyFactoryTest, CreateInvalidCryptoPlugin) { + hidl_vec initVec; + auto res = cryptoFactory->createPlugin( + kInvalidUUID, initVec, + [&](Status status, const sp& plugin) { + EXPECT_EQ(Status::ERROR_DRM_CANNOT_HANDLE, status); + EXPECT_EQ(plugin, nullptr); + }); + EXPECT_OK(res); +} + +class DrmHalClearkeyPluginTest : public DrmHalClearkeyFactoryTest { + public: + virtual void SetUp() override { + // Create factories + DrmHalClearkeyFactoryTest::SetUp(); + + ASSERT_NE(drmFactory, nullptr); + hidl_string packageName("android.hardware.drm.test"); + auto res = drmFactory->createPlugin( + kClearKeyUUID, packageName, + [this](Status status, const sp& plugin) { + EXPECT_EQ(Status::OK, status); + ASSERT_NE(plugin, nullptr); + drmPlugin = plugin; + }); + ASSERT_OK(res); + + hidl_vec initVec; + res = cryptoFactory->createPlugin( + kClearKeyUUID, initVec, + [this](Status status, const sp& plugin) { + EXPECT_EQ(Status::OK, status); + ASSERT_NE(plugin, nullptr); + cryptoPlugin = plugin; + }); + ASSERT_OK(res); + } + + virtual void TearDown() override {} + + SessionId openSession(); + void closeSession(const SessionId& sessionId); + sp getDecryptMemory(size_t size, size_t index); + + protected: + sp drmPlugin; + sp cryptoPlugin; +}; + +/** + * DrmPlugin tests + */ + +/** + * Test that the plugin can return a provision request. Since + * the clearkey plugin doesn't support provisioning, it is + * expected to return Status::ERROR_DRM_CANNOT_HANDLE. + */ +TEST_F(DrmHalClearkeyPluginTest, GetProvisionRequest) { + hidl_string certificateType; + hidl_string certificateAuthority; + auto res = drmPlugin->getProvisionRequest( + certificateType, certificateAuthority, + [&](Status status, const hidl_vec&, const hidl_string&) { + // clearkey doesn't require provisioning + EXPECT_EQ(Status::ERROR_DRM_CANNOT_HANDLE, status); + }); + EXPECT_OK(res); +} + +/** + * The DRM HAL should return BAD_VALUE if an empty provisioning + * response is provided. + */ +TEST_F(DrmHalClearkeyPluginTest, ProvideEmptyProvisionResponse) { + hidl_vec response; + auto res = drmPlugin->provideProvisionResponse( + response, [&](Status status, const hidl_vec&, + const hidl_vec&) { + EXPECT_EQ(Status::BAD_VALUE, status); + }); + EXPECT_OK(res); +} + +/** + * Helper method to open a session and verify that a non-empty + * session ID is returned + */ +SessionId DrmHalClearkeyPluginTest::openSession() { + SessionId sessionId; + + auto res = drmPlugin->openSession( + [&sessionId](Status status, const SessionId& id) { + EXPECT_EQ(Status::OK, status); + EXPECT_NE(0u, id.size()); + sessionId = id; + }); + EXPECT_OK(res); + return sessionId; +} + +/** + * Helper method to close a session + */ +void DrmHalClearkeyPluginTest::closeSession(const SessionId& sessionId) { + auto result = drmPlugin->closeSession(sessionId); + EXPECT_EQ(Status::OK, result); +} + +/** + * Test that a session can be opened and closed + */ +TEST_F(DrmHalClearkeyPluginTest, OpenCloseSession) { + auto sessionId = openSession(); + closeSession(sessionId); +} + +/** + * Test that attempting to close an invalid (empty) sessionId + * is prohibited with the documented error code. + */ +TEST_F(DrmHalClearkeyPluginTest, CloseInvalidSession) { + SessionId invalidSessionId; + Status result = drmPlugin->closeSession(invalidSessionId); + EXPECT_EQ(Status::BAD_VALUE, result); +} + +/** + * Test that attempting to close a session that is already closed + * is prohibited with the documented error code. + */ +TEST_F(DrmHalClearkeyPluginTest, CloseClosedSession) { + SessionId sessionId = openSession(); + closeSession(sessionId); + Status result = drmPlugin->closeSession(sessionId); + EXPECT_EQ(Status::ERROR_DRM_SESSION_NOT_OPENED, result); +} + +/** + * A get key request should fail if no sessionId is provided + */ +TEST_F(DrmHalClearkeyPluginTest, GetKeyRequestNoSession) { + SessionId invalidSessionId; + hidl_vec initData; + hidl_string mimeType = "video/mp4"; + KeyedVector optionalParameters; + auto res = drmPlugin->getKeyRequest( + invalidSessionId, initData, mimeType, KeyType::STREAMING, + optionalParameters, + [&](Status status, const hidl_vec&, KeyRequestType, + const hidl_string&) { EXPECT_EQ(Status::BAD_VALUE, status); }); + EXPECT_OK(res); +} + +/** + * The clearkey plugin doesn't support offline key requests. + * Test that the plugin returns the expected error code in + * this case. + */ +TEST_F(DrmHalClearkeyPluginTest, GetKeyRequestOfflineKeyTypeNotSupported) { + auto sessionId = openSession(); + hidl_vec initData; + hidl_string mimeType = "video/mp4"; + KeyedVector optionalParameters; + + auto res = drmPlugin->getKeyRequest( + sessionId, initData, mimeType, KeyType::OFFLINE, optionalParameters, + [&](Status status, const hidl_vec&, KeyRequestType, + const hidl_string&) { + // Clearkey plugin doesn't support offline key type + EXPECT_EQ(Status::ERROR_DRM_CANNOT_HANDLE, status); + }); + EXPECT_OK(res); + closeSession(sessionId); +} + +/** + * Test that the plugin returns the documented error for the + * case of attempting to generate a key request using an + * invalid mime type + */ +TEST_F(DrmHalClearkeyPluginTest, GetKeyRequestBadMime) { + auto sessionId = openSession(); + hidl_vec initData; + hidl_string mimeType = "video/unknown"; + KeyedVector optionalParameters; + auto res = drmPlugin->getKeyRequest( + sessionId, initData, mimeType, KeyType::STREAMING, + optionalParameters, [&](Status status, const hidl_vec&, + KeyRequestType, const hidl_string&) { + EXPECT_EQ(Status::ERROR_DRM_CANNOT_HANDLE, status); + }); + EXPECT_OK(res); + closeSession(sessionId); +} + +/** + * Test that a closed sessionID returns SESSION_NOT_OPENED + */ +TEST_F(DrmHalClearkeyPluginTest, ProvideKeyResponseClosedSession) { + SessionId session = openSession(); + closeSession(session); + + hidl_vec keyResponse = {0x7b, 0x22, 0x6b, 0x65, + 0x79, 0x73, 0x22, 0x3a}; + auto res = drmPlugin->provideKeyResponse( + session, keyResponse, + [&](Status status, const hidl_vec& keySetId) { + EXPECT_EQ(Status::ERROR_DRM_SESSION_NOT_OPENED, status); + EXPECT_EQ(0u, keySetId.size()); + }); + EXPECT_OK(res); +} + +/** + * Test that an empty sessionID returns BAD_VALUE + */ +TEST_F(DrmHalClearkeyPluginTest, ProvideKeyResponseInvalidSessionId) { + SessionId session; + + hidl_vec keyResponse = {0x7b, 0x22, 0x6b, 0x65, + 0x79, 0x73, 0x22, 0x3a}; + auto res = drmPlugin->provideKeyResponse( + session, keyResponse, + [&](Status status, const hidl_vec& keySetId) { + EXPECT_EQ(Status::BAD_VALUE, status); + EXPECT_EQ(0u, keySetId.size()); + }); + EXPECT_OK(res); +} + +/** + * Test that an empty key response returns BAD_VALUE + */ +TEST_F(DrmHalClearkeyPluginTest, ProvideKeyResponseEmptyResponse) { + SessionId session = openSession(); + hidl_vec emptyResponse; + auto res = drmPlugin->provideKeyResponse( + session, emptyResponse, + [&](Status status, const hidl_vec& keySetId) { + EXPECT_EQ(Status::BAD_VALUE, status); + EXPECT_EQ(0u, keySetId.size()); + }); + EXPECT_OK(res); + closeSession(session); +} + +/** + * Test that the clearkey plugin doesn't support getting + * secure stops. + */ +TEST_F(DrmHalClearkeyPluginTest, GetSecureStops) { + auto res = drmPlugin->getSecureStops( + [&](Status status, const hidl_vec&) { + // Clearkey plugin doesn't support secure stops + EXPECT_EQ(Status::ERROR_DRM_CANNOT_HANDLE, status); + }); + EXPECT_OK(res); +} + +/** + * Test that the clearkey plugin returns BAD_VALUE if + * an empty ssid is provided. + */ +TEST_F(DrmHalClearkeyPluginTest, GetSecureStopEmptySSID) { + SecureStopId ssid; + auto res = drmPlugin->getSecureStop( + ssid, [&](Status status, const SecureStop&) { + EXPECT_EQ(Status::BAD_VALUE, status); + }); + EXPECT_OK(res); +} + +/** + * Test that releasing all secure stops isn't handled by + * clearkey. + */ +TEST_F(DrmHalClearkeyPluginTest, ReleaseAllSecureStops) { + EXPECT_EQ(Status::ERROR_DRM_CANNOT_HANDLE, + drmPlugin->releaseAllSecureStops()); +} + +/** + * Test that releasing a specific secure stop with an empty + * SSID returns BAD_VALUE. + */ +TEST_F(DrmHalClearkeyPluginTest, ReleaseSecureStopEmptySSID) { + SecureStopId ssid; + Status status = drmPlugin->releaseSecureStop(ssid); + EXPECT_EQ(Status::BAD_VALUE, status); +} + +/** + * The following four tests verify that the properties + * defined in the MediaDrm API are supported by + * the plugin. + */ +TEST_F(DrmHalClearkeyPluginTest, GetVendorProperty) { + auto res = drmPlugin->getPropertyString( + "vendor", [&](Status status, const hidl_string& value) { + EXPECT_EQ(Status::OK, status); + EXPECT_EQ("Google", value); + }); + EXPECT_OK(res); +} + +TEST_F(DrmHalClearkeyPluginTest, GetVersionProperty) { + auto res = drmPlugin->getPropertyString( + "version", [&](Status status, const hidl_string& value) { + EXPECT_EQ(Status::OK, status); + EXPECT_EQ("1.0", value); + }); + EXPECT_OK(res); +} + +TEST_F(DrmHalClearkeyPluginTest, GetDescriptionProperty) { + auto res = drmPlugin->getPropertyString( + "description", [&](Status status, const hidl_string& value) { + EXPECT_EQ(Status::OK, status); + EXPECT_EQ("ClearKey CDM", value); + }); + EXPECT_OK(res); +} + +TEST_F(DrmHalClearkeyPluginTest, GetAlgorithmsProperty) { + auto res = drmPlugin->getPropertyString( + "algorithms", [&](Status status, const hidl_string& value) { + EXPECT_EQ(Status::OK, status); + EXPECT_EQ("", value); + }); + EXPECT_OK(res); +} + +/** + * Test that attempting to read invalid string and byte array + * properties returns the documented error code. + */ +TEST_F(DrmHalClearkeyPluginTest, GetInvalidStringProperty) { + auto res = drmPlugin->getPropertyString( + "invalid", [&](Status status, const hidl_string&) { + EXPECT_EQ(Status::ERROR_DRM_CANNOT_HANDLE, status); + }); + EXPECT_OK(res); +} + +TEST_F(DrmHalClearkeyPluginTest, GetByteArrayPropertyNotSupported) { + auto res = drmPlugin->getPropertyByteArray( + "deviceUniqueId", [&](Status status, const hidl_vec&) { + EXPECT_EQ(Status::ERROR_DRM_CANNOT_HANDLE, status); + }); + EXPECT_OK(res); +} + +/** + * Clearkey doesn't support setting string or byte array properties, + * particularly an undefined one. + */ +TEST_F(DrmHalClearkeyPluginTest, SetStringPropertyNotSupported) { + Status status = drmPlugin->setPropertyString("property", "value"); + EXPECT_EQ(Status::ERROR_DRM_CANNOT_HANDLE, status); +} + +TEST_F(DrmHalClearkeyPluginTest, SetByteArrayPropertyNotSupported) { + hidl_vec value; + Status status = drmPlugin->setPropertyByteArray("property", value); + EXPECT_EQ(Status::ERROR_DRM_CANNOT_HANDLE, status); +} + +/** + * Clearkey doesn't support setting cipher algorithms, verify it + */ +TEST_F(DrmHalClearkeyPluginTest, SetCipherAlgorithmNotSupported) { + SessionId session = openSession(); + hidl_string algorithm = "AES/CBC/NoPadding"; + Status status = drmPlugin->setCipherAlgorithm(session, algorithm); + EXPECT_EQ(Status::ERROR_DRM_CANNOT_HANDLE, status); + closeSession(session); +} + +/** + * Setting an empty algorithm should return BAD_VALUE + */ +TEST_F(DrmHalClearkeyPluginTest, SetCipherEmptyAlgorithm) { + SessionId session = openSession(); + hidl_string algorithm; + Status status = drmPlugin->setCipherAlgorithm(session, algorithm); + EXPECT_EQ(Status::BAD_VALUE, status); + closeSession(session); +} + +/** + * Setting a cipher algorithm with no session returns BAD_VALUE + */ +TEST_F(DrmHalClearkeyPluginTest, SetCipherAlgorithmNoSession) { + SessionId session; + hidl_string algorithm = "AES/CBC/NoPadding"; + Status status = drmPlugin->setCipherAlgorithm(session, algorithm); + EXPECT_EQ(Status::BAD_VALUE, status); +} + +/** + * Clearkey doesn't support setting mac algorithms, verify it + */ +TEST_F(DrmHalClearkeyPluginTest, SetMacAlgorithmNotSupported) { + SessionId session = openSession(); + hidl_string algorithm = "HmacSHA256"; + Status status = drmPlugin->setMacAlgorithm(session, algorithm); + EXPECT_EQ(Status::ERROR_DRM_CANNOT_HANDLE, status); + closeSession(session); +} + +/** + * Setting an empty algorithm should return BAD_VALUE + */ +TEST_F(DrmHalClearkeyPluginTest, SetMacEmptyAlgorithm) { + SessionId session = openSession(); + hidl_string algorithm; + Status status = drmPlugin->setMacAlgorithm(session, algorithm); + EXPECT_EQ(Status::BAD_VALUE, status); + closeSession(session); +} + +/** + * Setting a mac algorithm with no session should return BAD_VALUE + */ +TEST_F(DrmHalClearkeyPluginTest, SetMacAlgorithmNoSession) { + SessionId session; + hidl_string algorithm = "HmacSHA256"; + Status status = drmPlugin->setMacAlgorithm(session, algorithm); + EXPECT_EQ(Status::BAD_VALUE, status); +} + +/** + * The Generic* methods provide general purpose crypto operations + * that may be used for applications other than DRM. They leverage + * the hardware root of trust and secure key distribution mechanisms + * of a DRM system to enable app-specific crypto functionality where + * the crypto keys are not exposed outside of the trusted execution + * environment. + * + * Clearkey doesn't support generic encrypt/decrypt/sign/verify. + */ +TEST_F(DrmHalClearkeyPluginTest, GenericEncryptNotSupported) { + 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}; + auto res = drmPlugin->encrypt(session, keyId, input, iv, + [&](Status status, const hidl_vec&) { + EXPECT_EQ(Status::ERROR_DRM_CANNOT_HANDLE, + status); + }); + EXPECT_OK(res); + closeSession(session); +} + +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}; + auto res = drmPlugin->decrypt(session, keyId, input, iv, + [&](Status status, const hidl_vec&) { + EXPECT_EQ(Status::ERROR_DRM_CANNOT_HANDLE, + status); + }); + EXPECT_OK(res); + closeSession(session); +} + +TEST_F(DrmHalClearkeyPluginTest, GenericSignNotSupported) { + SessionId session = openSession(); + ; + hidl_vec keyId = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; + hidl_vec message = {1, 2, 3, 4, 5}; + auto res = drmPlugin->sign(session, keyId, message, + [&](Status status, const hidl_vec&) { + EXPECT_EQ(Status::ERROR_DRM_CANNOT_HANDLE, + status); + }); + EXPECT_OK(res); + closeSession(session); +} + +TEST_F(DrmHalClearkeyPluginTest, GenericVerifyNotSupported) { + SessionId session = openSession(); + ; + hidl_vec keyId = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}; + hidl_vec message = {1, 2, 3, 4, 5}; + hidl_vec signature = {0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0}; + auto res = drmPlugin->verify( + session, keyId, message, signature, [&](Status status, bool) { + EXPECT_EQ(Status::ERROR_DRM_CANNOT_HANDLE, status); + }); + EXPECT_OK(res); + closeSession(session); +} + +TEST_F(DrmHalClearkeyPluginTest, GenericSignRSANotSupported) { + SessionId session = openSession(); + hidl_string algorithm = "RSASSA-PSS-SHA1"; + hidl_vec message = {1, 2, 3, 4, 5}; + hidl_vec wrappedKey = {0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0}; + auto res = drmPlugin->signRSA(session, algorithm, message, wrappedKey, + [&](Status status, const hidl_vec&) { + EXPECT_EQ(Status::ERROR_DRM_CANNOT_HANDLE, + status); + }); + EXPECT_OK(res); + closeSession(session); +} + +/** + * CryptoPlugin tests + */ + +/** + * Clearkey doesn't support secure decoder and is expected to + * return false. + */ +TEST_F(DrmHalClearkeyPluginTest, RequiresSecureDecoder) { + EXPECT_FALSE(cryptoPlugin->requiresSecureDecoderComponent("cenc")); +} + +/** + * Verify that requiresSecureDecoderComponent handles empty mimetype + */ +TEST_F(DrmHalClearkeyPluginTest, RequiresSecureDecoderEmptyMimeType) { + EXPECT_FALSE(cryptoPlugin->requiresSecureDecoderComponent("")); +} + +/** + * Exercise the NotifyResolution API. There is no observable result, + * just call the method for coverage. + */ +TEST_F(DrmHalClearkeyPluginTest, NotifyResolution) { + cryptoPlugin->notifyResolution(1920, 1080); +} + +/** + * getDecryptMemory allocates memory for decryption, then sets it + * as a shared buffer base in the crypto hal. The allocated and + * mapped IMemory is returned. + * + * @param size the size of the memory segment to allocate + * @param the index of the memory segment which will be used + * to refer to it for decryption. + */ +sp DrmHalClearkeyPluginTest::getDecryptMemory(size_t size, + size_t index) { + sp ashmemAllocator = IAllocator::getService("ashmem"); + EXPECT_NE(ashmemAllocator, nullptr); + + hidl_memory hidlMemory; + auto res = ashmemAllocator->allocate( + size, [&](bool success, const hidl_memory& memory) { + EXPECT_EQ(true, success); + EXPECT_OK(cryptoPlugin->setSharedBufferBase(memory, index)); + hidlMemory = memory; + }); + EXPECT_OK(res); + + sp mappedMemory = mapMemory(hidlMemory); + EXPECT_OK(cryptoPlugin->setSharedBufferBase(hidlMemory, index)); + return mappedMemory; +} + +/** + * Exercise the setMediaDrmSession method. setMediaDrmSession + * is used to associate a drm session with a crypto session. + */ +TEST_F(DrmHalClearkeyPluginTest, SetMediaDrmSession) { + auto sessionId = openSession(); + Status status = cryptoPlugin->setMediaDrmSession(sessionId); + EXPECT_EQ(Status::OK, status); + closeSession(sessionId); +} + +/** + * setMediaDrmSession with a closed session id + */ +TEST_F(DrmHalClearkeyPluginTest, SetMediaDrmSessionClosedSession) { + auto sessionId = openSession(); + closeSession(sessionId); + Status status = cryptoPlugin->setMediaDrmSession(sessionId); + EXPECT_EQ(Status::ERROR_DRM_SESSION_NOT_OPENED, status); +} + +/** + * Decrypt tests + */ + +class DrmHalClearkeyDecryptTest : public DrmHalClearkeyPluginTest { + public: + void loadKeys(const SessionId& sessionId); + void fillRandom(const sp& memory); + hidl_array toHidlArray(const vector& vec) { + EXPECT_EQ(vec.size(), 16u); + return hidl_array(&vec[0]); + } +}; + +/** + * Helper method to load keys for subsequent decrypt tests. + * These tests use predetermined key request/response to + * avoid requiring a round trip to a license server. + */ +void DrmHalClearkeyDecryptTest::loadKeys(const SessionId& sessionId) { + hidl_vec initData = { + // BMFF box header (4 bytes size + 'pssh') + 0x00, 0x00, 0x00, 0x34, 0x70, 0x73, 0x73, 0x68, + // full box header (version = 1 flags = 0) + 0x01, 0x00, 0x00, 0x00, + // system id + 0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, + 0x1e, 0x52, 0xe2, 0xfb, 0x4b, + // number of key ids + 0x00, 0x00, 0x00, 0x01, + // key id + 0x60, 0x06, 0x1e, 0x01, 0x7e, 0x47, 0x7e, 0x87, 0x7e, 0x57, 0xd0, + 0x0d, 0x1e, 0xd0, 0x0d, 0x1e, + // size of data, must be zero + 0x00, 0x00, 0x00, 0x00}; + + hidl_vec expectedKeyRequest = { + 0x7b, 0x22, 0x6b, 0x69, 0x64, 0x73, 0x22, 0x3a, 0x5b, 0x22, 0x59, + 0x41, 0x59, 0x65, 0x41, 0x58, 0x35, 0x48, 0x66, 0x6f, 0x64, 0x2b, + 0x56, 0x39, 0x41, 0x4e, 0x48, 0x74, 0x41, 0x4e, 0x48, 0x67, 0x22, + 0x5d, 0x2c, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x22, 0x74, + 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x61, 0x72, 0x79, 0x22, 0x7d}; + + hidl_vec knownKeyResponse = { + 0x7b, 0x22, 0x6b, 0x65, 0x79, 0x73, 0x22, 0x3a, 0x5b, 0x7b, 0x22, + 0x6b, 0x74, 0x79, 0x22, 0x3a, 0x22, 0x6f, 0x63, 0x74, 0x22, 0x2c, + 0x22, 0x6b, 0x69, 0x64, 0x22, 0x3a, 0x22, 0x59, 0x41, 0x59, 0x65, + 0x41, 0x58, 0x35, 0x48, 0x66, 0x6f, 0x64, 0x2b, 0x56, 0x39, 0x41, + 0x4e, 0x48, 0x74, 0x41, 0x4e, 0x48, 0x67, 0x22, 0x2c, 0x22, 0x6b, + 0x22, 0x3a, 0x22, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x54, 0x65, + 0x73, 0x74, 0x4b, 0x65, 0x79, 0x42, 0x61, 0x73, 0x65, 0x36, 0x34, + 0x67, 0x67, 0x67, 0x22, 0x7d, 0x5d, 0x7d, 0x0a}; + + hidl_string mimeType = "video/mp4"; + KeyedVector optionalParameters; + auto res = drmPlugin->getKeyRequest( + sessionId, initData, mimeType, KeyType::STREAMING, + optionalParameters, + [&](Status status, const hidl_vec& request, + KeyRequestType requestType, const hidl_string&) { + EXPECT_EQ(Status::OK, status); + EXPECT_EQ(KeyRequestType::INITIAL, requestType); + EXPECT_EQ(request, expectedKeyRequest); + }); + EXPECT_OK(res); + + res = drmPlugin->provideKeyResponse( + sessionId, knownKeyResponse, + [&](Status status, const hidl_vec& keySetId) { + EXPECT_EQ(Status::OK, status); + EXPECT_EQ(0u, keySetId.size()); + }); + EXPECT_OK(res); +} + +void DrmHalClearkeyDecryptTest::fillRandom(const sp& memory) { + random_device rd; + mt19937 rand(rd()); + for (size_t i = 0; i < memory->getSize() / sizeof(uint32_t); i++) { + auto p = static_cast( + static_cast(memory->getPointer())); + p[i] = rand(); + } +} + +/** + * Positive decrypt test. "Decrypt" a single clear + * segment. Verify data matches. + */ +TEST_F(DrmHalClearkeyDecryptTest, ClearSegmentTest) { + const size_t kSegmentSize = 1024; + 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}; + + sp sharedMemory = + getDecryptMemory(kSegmentSize * 2, kSegmentIndex); + + SharedBuffer sourceBuffer = { + .bufferId = kSegmentIndex, .offset = 0, .size = kSegmentSize}; + fillRandom(sharedMemory); + + DestinationBuffer destBuffer = {.type = BufferType::SHARED_MEMORY, + {.bufferId = kSegmentIndex, + .offset = kSegmentSize, + .size = kSegmentSize}, + .secureMemory = nullptr}; + + Pattern noPattern = {0, 0}; + vector subSamples = {{.numBytesOfClearData = kSegmentSize, + .numBytesOfEncryptedData = 0}}; + uint64_t offset = 0; + + auto sessionId = openSession(); + loadKeys(sessionId); + + Status status = cryptoPlugin->setMediaDrmSession(sessionId); + 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); + + 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 new file mode 100644 index 00000000..db197195 --- /dev/null +++ b/drm/1.0/vts/functional/drm_hal_vendor_module_api.h @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DRM_HAL_VENDOR_MODULE_API_H +#define DRM_HAL_VENDOR_MODULE_API_H + +#include +#include +#include +#include + +/** + * The DRM and Crypto HALs interact with vendor-provided HAL implementations + * that have DRM-specific capabilities. Since the VTS tests cannot contain + * DRM-specific functionality, supporting modules are required to enable VTS + * to validate HAL implementations in a generic way. If the vendor-specific + * VTS module is not provided for a given drm HAL implementation, only very + * small subset of functionality can be verified. + * + * As an example, a DRM HAL implementation interacts with a DRM-specific + * license server to obtain licenses for decrypting content. The DRM HAL + * implementation generates a key request message, delivers it to the server + * and receives a key response message which is then loaded into the HAL. Once + * the keys are loaded, the Crypto HAL decryption functionality and performance + * and other associated APIs can be tested by the common VTS test suite. + * + * Vendor-specific VTS modules are shared libraries used by the DRM VTS test. + * They provide a set of functions to support VTS testing of the DRM HAL module. + * + * The modules are placed in a common location on the file system. The VTS test + * scans through all vendor-provided support libraries and runs the VTS test + * suite on each library that is found. + * + * The vendor-specific module exposes an extern “C” vendorModuleFactory() + * function that returns a DrmHalVTSVendorModule instance. DrmHalVTSVendorModule + * instances are versioned, where each version is represented by subclass of + * DrmHalVTSVendorModule that corresponds to the API version. For example, a + * vendor-specific module that implements version 1 of the API would return a + * DrmHalVTSVendorModule_V1 from the vendorModuleFactory() function. + */ + +class DrmHalVTSVendorModule; + +extern "C" { +/** + * The factory method for creating DrmHalVTSVendorModule instances. The returned + * instance will be a subclass of DrmHalVTSVendorModule that corresponds to the + * supported API version. + */ +DrmHalVTSVendorModule* vendorModuleFactory(); +}; + +class DrmHalVTSVendorModule { + public: + DrmHalVTSVendorModule() {} + virtual ~DrmHalVTSVendorModule() {} + + /** + * Return the vendor-specific module API version. The version is an integer + * value with initial version 1. The API version indicates which subclass + * version DrmHalVTSVendorModule this instance is. + */ + virtual uint32_t getAPIVersion() = 0; + + /** + * Return the UUID for the DRM HAL implementation. Protection System + * Specific + * UUID (see http://dashif.org/identifiers/protection/) + */ + virtual std::vector getUUID() = 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; + + private: + DrmHalVTSVendorModule(const DrmHalVTSVendorModule&) = delete; + void operator=(const DrmHalVTSVendorModule&) = delete; +}; + +/** + * API Version 1. This is the baseline version that supports a minimal set + * of VTS tests. + */ +class DrmHalVTSVendorModule_V1 : public DrmHalVTSVendorModule { + public: + DrmHalVTSVendorModule_V1() {} + virtual ~DrmHalVTSVendorModule_V1() {} + + virtual uint32_t getAPIVersion() { return 1; } + + /** + * Handle a provisioning request. This function will be called if the HAL + * module's getProvisionRequest returns a provision request. The vendor + * module should process the provisioning request, either by sending it + * to a provisioning server, or generating a mock response. The resulting + * provisioning response is returned to the VTS test. + * + * @param provisioningRequest the provisioning request recieved from + * the DRM HAL + * @param url the default url the HAL implementation provided with the + * provisioning request + * @return the generated provisioning response + */ + virtual std::vector handleProvisioningRequest( + const std::vector& provisioningRequest, + const std::string& url) = 0; + + /** + * Content configuration specifies content-specific parameters associated + * with a key request/response transaction. It allows the VTS test to + * request keys and use them to perform decryption. + */ + struct ContentConfiguration { + /** + * Assign a name for this configuration that will be referred to + * in log messages. + */ + const std::string name; + + /** + * Server to use when requesting a key response. This url will be + * passed as a parameter to the vendor vts module along with the + * key request to perform the key request transaction. + */ + const std::string serverUrl; + + /** + * Initialization data provided to getKeyRequest, e.g. PSSH for CENC + * content + */ + const std::vector initData; + + /** + * Mime type provided to getKeyRequest, e.g. "video/mp4", or "cenc" + */ + const std::string mimeType; + + /** + * Optional parameters to be associated with the key request + */ + const std::map optionalParameters; + + /** + * The keys that will be available once the keys are loaded + */ + struct Key { + /** + * Indicate if the key content is configured to require secure + * buffers, + * where the output buffers are protected and cannot be accessed. + * A vendor module should provide some content configurations where + * isSecure is false, to allow decrypt result verification tests to + * be + * run. + */ + bool isSecure; + + /** + * A key ID identifies a key to use for decryption + */ + 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. + */ + const std::vector keyValue; + }; + std::vector keys; + }; + + /** + * Return a list of content configurations that can be exercised by the + * VTS test. + */ + virtual std::vector getContentConfigurations() = 0; + + /** + * Handle a key request. This function will be called if the HAL + * module's getKeyRequest returns a key request. The vendor + * module should process the key request, either by sending it + * to a license server, or by generating a mock response. The resulting + * key response is returned to the VTS test. + * + * @param keyRequest the key request recieved from the DRM HAL + * @param serverUrl the url of the key server that was supplied + * by the ContentConfiguration + * @return the generated key response + */ + virtual std::vector handleKeyRequest( + const std::vector& keyRequest, + const std::string& serverUrl) = 0; +}; + +#endif // DRM_HAL_VENDOR_MODULE_API_H diff --git a/drm/1.0/vts/functional/drm_hal_vendor_test.cpp b/drm/1.0/vts/functional/drm_hal_vendor_test.cpp new file mode 100644 index 00000000..dcfee4e4 --- /dev/null +++ b/drm/1.0/vts/functional/drm_hal_vendor_test.cpp @@ -0,0 +1,980 @@ +/* + * Copyright (C) 2017 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 "drm_hal_vendor_test@1.0" + +#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" + +using ::android::hardware::drm::V1_0::BufferType; +using ::android::hardware::drm::V1_0::DestinationBuffer; +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::KeyedVector; +using ::android::hardware::drm::V1_0::KeyValue; +using ::android::hardware::drm::V1_0::KeyRequestType; +using ::android::hardware::drm::V1_0::KeyType; +using ::android::hardware::drm::V1_0::Mode; +using ::android::hardware::drm::V1_0::Pattern; +using ::android::hardware::drm::V1_0::SecureStop; +using ::android::hardware::drm::V1_0::SecureStopId; +using ::android::hardware::drm::V1_0::SessionId; +using ::android::hardware::drm::V1_0::SharedBuffer; +using ::android::hardware::drm::V1_0::Status; +using ::android::hardware::drm::V1_0::SubSample; + +using ::android::hardware::hidl_array; +using ::android::hardware::hidl_memory; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; +using ::android::hardware::Return; +using ::android::hidl::allocator::V1_0::IAllocator; +using ::android::hidl::memory::V1_0::IMemory; +using ::android::sp; + +using std::string; +using std::unique_ptr; +using std::random_device; +using std::map; +using std::mt19937; +using std::vector; + +#define ASSERT_OK(ret) ASSERT_TRUE(ret.isOk()) +#define EXPECT_OK(ret) EXPECT_TRUE(ret.isOk()) + +static const uint8_t kInvalidUUID[16] = { + 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, + 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, +}; + +static drm_vts::VendorModules* gVendorModules = nullptr; + +class DrmHalVendorFactoryTest : public testing::TestWithParam { + public: + DrmHalVendorFactoryTest() + : vendorModule(gVendorModules ? static_cast( + gVendorModules->getVendorModule( + GetParam())) + : nullptr) {} + + virtual ~DrmHalVendorFactoryTest() {} + + virtual void SetUp() { + const ::testing::TestInfo* const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + ALOGD("Running test %s.%s from vendor module %s", + test_info->test_case_name(), test_info->name(), + GetParam().c_str()); + + ASSERT_NE(vendorModule, nullptr); + string name = vendorModule->getServiceName(); + drmFactory = + ::testing::VtsHalHidlTargetTestBase::getService( + name != "default" ? name : "drm"); + ASSERT_NE(drmFactory, nullptr); + cryptoFactory = + ::testing::VtsHalHidlTargetTestBase::getService( + name != "default" ? name : "crypto"); + ASSERT_NE(cryptoFactory, nullptr); + } + + virtual void TearDown() override {} + + protected: + hidl_array getVendorUUID() { + vector uuid = vendorModule->getUUID(); + return hidl_array(&uuid[0]); + } + + sp drmFactory; + sp cryptoFactory; + unique_ptr vendorModule; +}; + +/** + * Ensure the factory supports its scheme UUID + */ +TEST_P(DrmHalVendorFactoryTest, VendorPluginSupported) { + EXPECT_TRUE(drmFactory->isCryptoSchemeSupported(getVendorUUID())); + EXPECT_TRUE(cryptoFactory->isCryptoSchemeSupported(getVendorUUID())); +} + +/** + * Ensure the factory doesn't support an invalid scheme UUID + */ +TEST_P(DrmHalVendorFactoryTest, InvalidPluginNotSupported) { + EXPECT_FALSE(drmFactory->isCryptoSchemeSupported(kInvalidUUID)); + EXPECT_FALSE(cryptoFactory->isCryptoSchemeSupported(kInvalidUUID)); +} + +/** + * Ensure vendor drm plugin can be created + */ +TEST_P(DrmHalVendorFactoryTest, CreateVendorDrmPlugin) { + hidl_string packageName("android.hardware.drm.test"); + auto res = drmFactory->createPlugin( + getVendorUUID(), packageName, + [&](Status status, const sp& plugin) { + EXPECT_EQ(Status::OK, status); + EXPECT_NE(plugin, nullptr); + }); + EXPECT_OK(res); +} + +/** + * Ensure vendor crypto plugin can be created + */ +TEST_P(DrmHalVendorFactoryTest, CreateVendorCryptoPlugin) { + hidl_vec initVec; + auto res = cryptoFactory->createPlugin( + getVendorUUID(), initVec, + [&](Status status, const sp& plugin) { + EXPECT_EQ(Status::OK, status); + EXPECT_NE(plugin, nullptr); + }); + EXPECT_OK(res); +} + +/** + * Ensure invalid drm plugin can't be created + */ +TEST_P(DrmHalVendorFactoryTest, CreateInvalidDrmPlugin) { + hidl_string packageName("android.hardware.drm.test"); + auto res = drmFactory->createPlugin( + kInvalidUUID, packageName, + [&](Status status, const sp& plugin) { + EXPECT_EQ(Status::ERROR_DRM_CANNOT_HANDLE, status); + EXPECT_EQ(plugin, nullptr); + }); + EXPECT_OK(res); +} + +/** + * Ensure invalid crypto plugin can't be created + */ +TEST_P(DrmHalVendorFactoryTest, CreateInvalidCryptoPlugin) { + hidl_vec initVec; + auto res = cryptoFactory->createPlugin( + kInvalidUUID, initVec, + [&](Status status, const sp& plugin) { + EXPECT_EQ(Status::ERROR_DRM_CANNOT_HANDLE, status); + EXPECT_EQ(plugin, nullptr); + }); + EXPECT_OK(res); +} + +class DrmHalVendorPluginTest : public DrmHalVendorFactoryTest { + public: + virtual ~DrmHalVendorPluginTest() {} + virtual void SetUp() override { + // Create factories + DrmHalVendorFactoryTest::SetUp(); + + hidl_string packageName("android.hardware.drm.test"); + auto res = drmFactory->createPlugin( + getVendorUUID(), packageName, + [this](Status status, const sp& plugin) { + EXPECT_EQ(Status::OK, status); + ASSERT_NE(plugin, nullptr); + drmPlugin = plugin; + }); + ASSERT_OK(res); + + hidl_vec initVec; + res = cryptoFactory->createPlugin( + getVendorUUID(), initVec, + [this](Status status, const sp& plugin) { + EXPECT_EQ(Status::OK, status); + ASSERT_NE(plugin, nullptr); + cryptoPlugin = plugin; + }); + ASSERT_OK(res); + } + + virtual void TearDown() override {} + + SessionId openSession(); + void closeSession(const SessionId& sessionId); + sp getDecryptMemory(size_t size, size_t index); + + protected: + sp drmPlugin; + sp cryptoPlugin; +}; + +/** + * DrmPlugin tests + */ + +/** + * Test that a DRM plugin can handle provisioning. While + * it is not required that a DRM scheme require provisioning, + * it should at least return appropriate status values. If + * a provisioning request is returned, it is passed to the + * vendor module which should provide a provisioning response + * that is delivered back to the HAL. + */ + +TEST_P(DrmHalVendorPluginTest, DoProvisioning) { + hidl_string certificateType; + hidl_string certificateAuthority; + hidl_vec provisionRequest; + hidl_string defaultUrl; + auto res = drmPlugin->getProvisionRequest( + certificateType, certificateAuthority, + [&](Status status, const hidl_vec& request, + const hidl_string& url) { + if (status == Status::OK) { + EXPECT_NE(request.size(), 0u); + provisionRequest = request; + defaultUrl = url; + } else if (status == Status::ERROR_DRM_CANNOT_HANDLE) { + EXPECT_EQ(0u, request.size()); + } + }); + EXPECT_OK(res); + + if (provisionRequest.size() > 0) { + vector response = vendorModule->handleProvisioningRequest( + provisionRequest, defaultUrl); + ASSERT_NE(0u, response.size()); + + auto res = drmPlugin->provideProvisionResponse( + response, [&](Status status, const hidl_vec&, + const hidl_vec&) { + EXPECT_EQ(Status::OK, status); + }); + EXPECT_OK(res); + } +} + +/** + * The DRM HAL should return BAD_VALUE if an empty provisioning + * response is provided. + */ +TEST_P(DrmHalVendorPluginTest, ProvideEmptyProvisionResponse) { + hidl_vec response; + auto res = drmPlugin->provideProvisionResponse( + response, [&](Status status, const hidl_vec&, + const hidl_vec&) { + EXPECT_EQ(Status::BAD_VALUE, status); + }); + EXPECT_OK(res); +} + +/** + * Helper method to open a session and verify that a non-empty + * session ID is returned + */ +SessionId DrmHalVendorPluginTest::openSession() { + SessionId sessionId; + + auto res = drmPlugin->openSession([&](Status status, const SessionId& id) { + EXPECT_EQ(Status::OK, status); + EXPECT_NE(id.size(), 0u); + sessionId = id; + }); + EXPECT_OK(res); + return sessionId; +} + +/** + * Helper method to close a session + */ +void DrmHalVendorPluginTest::closeSession(const SessionId& sessionId) { + Status status = drmPlugin->closeSession(sessionId); + EXPECT_EQ(Status::OK, status); +} + +/** + * Test that a session can be opened and closed + */ +TEST_P(DrmHalVendorPluginTest, OpenCloseSession) { + auto sessionId = openSession(); + closeSession(sessionId); +} + +/** + * Test that attempting to close an invalid (empty) sessionId + * is prohibited with the documented error code. + */ +TEST_P(DrmHalVendorPluginTest, CloseInvalidSession) { + SessionId invalidSessionId; + Status status = drmPlugin->closeSession(invalidSessionId); + EXPECT_EQ(Status::BAD_VALUE, status); +} + +/** + * Test that attempting to close a valid session twice + * is prohibited with the documented error code. + */ +TEST_P(DrmHalVendorPluginTest, CloseClosedSession) { + auto sessionId = openSession(); + closeSession(sessionId); + Status status = drmPlugin->closeSession(sessionId); + EXPECT_EQ(Status::ERROR_DRM_SESSION_NOT_OPENED, status); +} + +/** + * A get key request should fail if no sessionId is provided + */ +TEST_P(DrmHalVendorPluginTest, GetKeyRequestNoSession) { + SessionId invalidSessionId; + hidl_vec initData; + hidl_string mimeType = "video/mp4"; + KeyedVector optionalParameters; + auto res = drmPlugin->getKeyRequest( + invalidSessionId, initData, mimeType, KeyType::STREAMING, + optionalParameters, + [&](Status status, const hidl_vec&, KeyRequestType, + const hidl_string&) { EXPECT_EQ(Status::BAD_VALUE, status); }); + EXPECT_OK(res); +} + +/** + * Test that an empty sessionID returns BAD_VALUE + */ +TEST_P(DrmHalVendorPluginTest, ProvideKeyResponseEmptySessionId) { + SessionId session; + + hidl_vec keyResponse = {0x7b, 0x22, 0x6b, 0x65, + 0x79, 0x73, 0x22, 0x3a}; + auto res = drmPlugin->provideKeyResponse( + session, keyResponse, + [&](Status status, const hidl_vec& keySetId) { + EXPECT_EQ(Status::BAD_VALUE, status); + EXPECT_EQ(keySetId.size(), 0u); + }); + EXPECT_OK(res); +} + +/** + * Test that an empty key response returns BAD_VALUE + */ +TEST_P(DrmHalVendorPluginTest, ProvideKeyResponseEmptyResponse) { + SessionId session = openSession(); + hidl_vec emptyResponse; + auto res = drmPlugin->provideKeyResponse( + session, emptyResponse, + [&](Status status, const hidl_vec& keySetId) { + EXPECT_EQ(Status::BAD_VALUE, status); + EXPECT_EQ(keySetId.size(), 0u); + }); + EXPECT_OK(res); + closeSession(session); +} + +/** + * Test that the plugin either doesn't support getting + * secure stops, or has no secure stops available after + * clearing them. + */ +TEST_P(DrmHalVendorPluginTest, GetSecureStops) { + // There may be secure stops, depending on if there were keys + // loaded and unloaded previously. Clear them to get to a known + // state, then make sure there are none. + auto res = drmPlugin->getSecureStops( + [&](Status status, const hidl_vec&) { + if (status != Status::OK) { + EXPECT_EQ(Status::ERROR_DRM_CANNOT_HANDLE, status); + } + }); + EXPECT_OK(res); + + res = drmPlugin->getSecureStops( + [&](Status status, const hidl_vec& secureStops) { + if (status == Status::OK) { + EXPECT_EQ(secureStops.size(), 0u); + } else { + EXPECT_EQ(Status::ERROR_DRM_CANNOT_HANDLE, status); + } + }); + EXPECT_OK(res); +} + +/** + * Test that the clearkey plugin returns BAD_VALUE if + * an empty ssid is provided. + */ +TEST_P(DrmHalVendorPluginTest, GetSecureStopEmptySSID) { + SecureStopId ssid; + auto res = drmPlugin->getSecureStop( + ssid, [&](Status status, const SecureStop&) { + EXPECT_EQ(Status::BAD_VALUE, status); + }); + EXPECT_OK(res); +} + +/** + * Test that releasing all secure stops either isn't supported + * or is completed successfully + */ +TEST_P(DrmHalVendorPluginTest, ReleaseAllSecureStops) { + Status status = drmPlugin->releaseAllSecureStops(); + EXPECT_TRUE(status == Status::OK || + status == Status::ERROR_DRM_CANNOT_HANDLE); +} + +/** + * Releasing a secure stop without first getting one and sending it to the + * server to get a valid SSID should return ERROR_DRM_INVALID_STATE. + * This is an optional API so it can also return CANNOT_HANDLE. + */ +TEST_P(DrmHalVendorPluginTest, ReleaseSecureStopSequenceError) { + SecureStopId ssid = {1, 2, 3, 4}; + Status status = drmPlugin->releaseSecureStop(ssid); + EXPECT_TRUE(status == Status::ERROR_DRM_INVALID_STATE || + status == Status::ERROR_DRM_CANNOT_HANDLE); +} + +/** + * Test that releasing a specific secure stop with an empty ssid + * return BAD_VALUE. This is an optional API so it can also return + * CANNOT_HANDLE. + */ +TEST_P(DrmHalVendorPluginTest, ReleaseSecureStopEmptySSID) { + SecureStopId ssid; + Status status = drmPlugin->releaseSecureStop(ssid); + EXPECT_TRUE(status == Status::BAD_VALUE || + status == Status::ERROR_DRM_CANNOT_HANDLE); +} + +/** + * The following five tests verify that the properties + * defined in the MediaDrm API are supported by + * the plugin. + */ +TEST_P(DrmHalVendorPluginTest, GetVendorProperty) { + auto res = drmPlugin->getPropertyString( + "vendor", [&](Status status, const hidl_string& value) { + EXPECT_EQ(Status::OK, status); + EXPECT_NE(value.size(), 0u); + }); + EXPECT_OK(res); +} + +TEST_P(DrmHalVendorPluginTest, GetVersionProperty) { + auto res = drmPlugin->getPropertyString( + "version", [&](Status status, const hidl_string& value) { + EXPECT_EQ(Status::OK, status); + EXPECT_NE(value.size(), 0u); + }); + EXPECT_OK(res); +} + +TEST_P(DrmHalVendorPluginTest, GetDescriptionProperty) { + auto res = drmPlugin->getPropertyString( + "description", [&](Status status, const hidl_string& value) { + EXPECT_EQ(Status::OK, status); + EXPECT_NE(value.size(), 0u); + }); + EXPECT_OK(res); +} + +TEST_P(DrmHalVendorPluginTest, GetAlgorithmsProperty) { + auto res = drmPlugin->getPropertyString( + "algorithms", [&](Status status, const hidl_string& value) { + if (status == Status::OK) { + EXPECT_NE(value.size(), 0u); + } else { + EXPECT_EQ(Status::ERROR_DRM_CANNOT_HANDLE, status); + } + }); + EXPECT_OK(res); +} + +TEST_P(DrmHalVendorPluginTest, GetPropertyUniqueDeviceID) { + auto res = drmPlugin->getPropertyByteArray( + "deviceUniqueId", + [&](Status status, const hidl_vec& value) { + if (status == Status::OK) { + EXPECT_NE(value.size(), 0u); + } else { + EXPECT_EQ(Status::ERROR_DRM_CANNOT_HANDLE, status); + } + }); + EXPECT_OK(res); +} + +/** + * Test that attempting to read invalid string and byte array + * properties returns the documented error code. + */ +TEST_P(DrmHalVendorPluginTest, GetInvalidStringProperty) { + auto res = drmPlugin->getPropertyString( + "invalid", [&](Status status, const hidl_string&) { + EXPECT_EQ(Status::ERROR_DRM_CANNOT_HANDLE, status); + }); + EXPECT_OK(res); +} + +TEST_P(DrmHalVendorPluginTest, GetInvalidByteArrayProperty) { + auto res = drmPlugin->getPropertyByteArray( + "invalid", [&](Status status, const hidl_vec&) { + EXPECT_EQ(Status::ERROR_DRM_CANNOT_HANDLE, status); + }); + EXPECT_OK(res); +} + +/** + * Test that setting invalid string and byte array properties returns + * the expected status value. + */ +TEST_P(DrmHalVendorPluginTest, SetStringPropertyNotSupported) { + EXPECT_EQ(drmPlugin->setPropertyString("awefijaeflijwef", "value"), + Status::ERROR_DRM_CANNOT_HANDLE); +} + +TEST_P(DrmHalVendorPluginTest, SetByteArrayPropertyNotSupported) { + hidl_vec value; + EXPECT_EQ(drmPlugin->setPropertyByteArray("awefijaeflijwef", value), + Status::ERROR_DRM_CANNOT_HANDLE); +} + +/** + * Test that setting an invalid cipher algorithm returns + * the expected status value. + */ +TEST_P(DrmHalVendorPluginTest, SetCipherInvalidAlgorithm) { + SessionId session = openSession(); + hidl_string algorithm; + Status status = drmPlugin->setCipherAlgorithm(session, algorithm); + EXPECT_EQ(Status::BAD_VALUE, status); + closeSession(session); +} + +/** + * Test that setting a cipher algorithm with no session returns + * the expected status value. + */ +TEST_P(DrmHalVendorPluginTest, SetCipherAlgorithmNoSession) { + SessionId session; + hidl_string algorithm = "AES/CBC/NoPadding"; + Status status = drmPlugin->setCipherAlgorithm(session, algorithm); + EXPECT_EQ(Status::BAD_VALUE, status); +} + +/** + * Test that setting a valid cipher algorithm returns + * the expected status value. It is not required that all + * vendor modules support this algorithm, but they must + * either accept it or return ERROR_DRM_CANNOT_HANDLE + */ +TEST_P(DrmHalVendorPluginTest, SetCipherAlgorithm) { + SessionId session = openSession(); + ; + hidl_string algorithm = "AES/CBC/NoPadding"; + Status status = drmPlugin->setCipherAlgorithm(session, algorithm); + EXPECT_TRUE(status == Status::OK || + status == Status::ERROR_DRM_CANNOT_HANDLE); + closeSession(session); +} + +/** + * Test that setting an invalid mac algorithm returns + * the expected status value. + */ +TEST_P(DrmHalVendorPluginTest, SetMacInvalidAlgorithm) { + SessionId session = openSession(); + hidl_string algorithm; + Status status = drmPlugin->setMacAlgorithm(session, algorithm); + EXPECT_EQ(Status::BAD_VALUE, status); + closeSession(session); +} + +/** + * Test that setting a mac algorithm with no session returns + * the expected status value. + */ +TEST_P(DrmHalVendorPluginTest, SetMacNullAlgorithmNoSession) { + SessionId session; + hidl_string algorithm = "HmacSHA256"; + Status status = drmPlugin->setMacAlgorithm(session, algorithm); + EXPECT_EQ(Status::BAD_VALUE, status); +} + +/** + * Test that setting a valid mac algorithm returns + * the expected status value. It is not required that all + * vendor modules support this algorithm, but they must + * either accept it or return ERROR_DRM_CANNOT_HANDLE + */ +TEST_P(DrmHalVendorPluginTest, SetMacAlgorithm) { + SessionId session = openSession(); + hidl_string algorithm = "HmacSHA256"; + Status status = drmPlugin->setMacAlgorithm(session, algorithm); + EXPECT_TRUE(status == Status::OK || + status == Status::ERROR_DRM_CANNOT_HANDLE); + closeSession(session); +} + +/** + * The Generic* methods provide general purpose crypto operations + * that may be used for applications other than DRM. They leverage + * the hardware root of trust and secure key distribution mechanisms + * of a DRM system to enable app-specific crypto functionality where + * the crypto keys are not exposed outside of the trusted execution + * environment. + * + * Generic encrypt/decrypt/sign/verify should fail on invalid + * inputs, e.g. empty sessionId + */ +TEST_P(DrmHalVendorPluginTest, GenericEncryptNoSession) { + SessionId session; + hidl_vec keyId, input, iv; + auto res = drmPlugin->encrypt( + session, keyId, input, iv, + [&](Status status, const hidl_vec&) { + EXPECT_EQ(Status::ERROR_DRM_SESSION_NOT_OPENED, status); + }); + EXPECT_OK(res); +} + +TEST_P(DrmHalVendorPluginTest, GenericDecryptNoSession) { + SessionId session; + hidl_vec keyId, input, iv; + auto res = drmPlugin->decrypt( + session, keyId, input, iv, + [&](Status status, const hidl_vec&) { + EXPECT_EQ(Status::ERROR_DRM_SESSION_NOT_OPENED, status); + }); + EXPECT_OK(res); +} + +TEST_P(DrmHalVendorPluginTest, GenericSignNoSession) { + SessionId session; + hidl_vec keyId, message; + auto res = drmPlugin->sign( + session, keyId, message, + [&](Status status, const hidl_vec&) { + EXPECT_EQ(Status::ERROR_DRM_SESSION_NOT_OPENED, status); + }); + EXPECT_OK(res); +} + +TEST_P(DrmHalVendorPluginTest, GenericVerifyNoSession) { + SessionId session; + hidl_vec keyId, message, signature; + auto res = drmPlugin->verify( + session, keyId, message, signature, [&](Status status, bool) { + EXPECT_EQ(Status::ERROR_DRM_SESSION_NOT_OPENED, status); + }); + EXPECT_OK(res); +} + +TEST_P(DrmHalVendorPluginTest, GenericSignRSANoSession) { + SessionId session; + hidl_string algorithm; + hidl_vec message, wrappedKey; + auto res = drmPlugin->signRSA(session, algorithm, message, wrappedKey, + [&](Status status, const hidl_vec&) { + EXPECT_EQ(Status::BAD_VALUE, status); + }); + EXPECT_OK(res); +} + +/** + * Exercise the requiresSecureDecoderComponent method. Additional tests + * will verify positive cases with specific vendor content configurations. + * Below we just test the negative cases. + */ + +/** + * Verify that requiresSecureDecoderComponent handles empty mimetype. + */ +TEST_P(DrmHalVendorPluginTest, RequiresSecureDecoderEmptyMimeType) { + EXPECT_FALSE(cryptoPlugin->requiresSecureDecoderComponent("")); +} + +/** + * Verify that requiresSecureDecoderComponent handles invalid mimetype. + */ +TEST_P(DrmHalVendorPluginTest, RequiresSecureDecoderInvalidMimeType) { + EXPECT_FALSE(cryptoPlugin->requiresSecureDecoderComponent("bad")); +} + +/** + * CryptoPlugin tests + */ + +/** + * Exercise the NotifyResolution API. There is no observable result, + * just call the method for coverage. + */ +TEST_P(DrmHalVendorPluginTest, NotifyResolution) { + cryptoPlugin->notifyResolution(1920, 1080); +} + +/** + * getDecryptMemory allocates memory for decryption, then sets it + * as a shared buffer base in the crypto hal. The allocated and + * mapped IMemory is returned. + * + * @param size the size of the memory segment to allocate + * @param the index of the memory segment which will be used + * to refer to it for decryption. + */ +sp DrmHalVendorPluginTest::getDecryptMemory(size_t size, + size_t index) { + sp ashmemAllocator = IAllocator::getService("ashmem"); + EXPECT_NE(ashmemAllocator, nullptr); + + hidl_memory hidlMemory; + auto res = ashmemAllocator->allocate( + size, [&](bool success, const hidl_memory& memory) { + EXPECT_EQ(success, true); + EXPECT_EQ(memory.size(), size); + hidlMemory = memory; + }); + + EXPECT_OK(res); + + sp mappedMemory = mapMemory(hidlMemory); + EXPECT_NE(mappedMemory, nullptr); + res = cryptoPlugin->setSharedBufferBase(hidlMemory, index); + EXPECT_OK(res); + return mappedMemory; +} + +/** + * Exercise the setMediaDrmSession method. setMediaDrmSession + * is used to associate a drm session with a crypto session. + */ +TEST_P(DrmHalVendorPluginTest, SetMediaDrmSession) { + auto sessionId = openSession(); + Status status = cryptoPlugin->setMediaDrmSession(sessionId); + EXPECT_EQ(Status::OK, status); + closeSession(sessionId); +} + +/** + * setMediaDrmSession with a closed session id + */ +TEST_P(DrmHalVendorPluginTest, SetMediaDrmSessionClosedSession) { + auto sessionId = openSession(); + closeSession(sessionId); + Status status = cryptoPlugin->setMediaDrmSession(sessionId); + EXPECT_EQ(Status::ERROR_DRM_SESSION_NOT_OPENED, status); +} + +/** + * Decrypt tests + */ + +class DrmHalVendorDecryptTest : public DrmHalVendorPluginTest { + public: + DrmHalVendorDecryptTest() = default; + virtual ~DrmHalVendorDecryptTest() {} + + protected: + void loadKeys(const SessionId& sessionId, + const DrmHalVTSVendorModule_V1::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]); + } +}; + +KeyedVector DrmHalVendorDecryptTest::toHidlKeyedVector( + const map& params) { + std::vector stdKeyedVector; + for (auto it = params.begin(); it != params.end(); ++it) { + KeyValue keyValue; + keyValue.key = it->first; + keyValue.value = it->second; + stdKeyedVector.push_back(keyValue); + } + return KeyedVector(stdKeyedVector); +} + +/** + * Helper method to load keys for subsequent decrypt tests. + * 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) { + hidl_vec keyRequest; + auto res = drmPlugin->getKeyRequest( + sessionId, configuration.initData, configuration.mimeType, + KeyType::STREAMING, + toHidlKeyedVector(configuration.optionalParameters), + [&](Status status, const hidl_vec& request, + KeyRequestType type, const hidl_string&) { + EXPECT_EQ(Status::OK, status) + << "Failed to get " + "key request for configuration " + << configuration.name; + EXPECT_EQ(type, KeyRequestType::INITIAL); + EXPECT_NE(request.size(), 0u) << "Expected key request size" + " to have length > 0 bytes"; + keyRequest = request; + }); + EXPECT_OK(res); + + /** + * Get key response from vendor module + */ + hidl_vec keyResponse = + vendorModule->handleKeyRequest(keyRequest, configuration.serverUrl); + + EXPECT_NE(keyResponse.size(), 0u) << "Expected key response size " + "to have length > 0 bytes"; + + res = drmPlugin->provideKeyResponse( + sessionId, keyResponse, + [&](Status status, const hidl_vec&) { + EXPECT_EQ(Status::OK, status) + << "Failure providing " + "key response for configuration " + << configuration.name; + }); + EXPECT_OK(res); +} + +void DrmHalVendorDecryptTest::fillRandom(const sp& memory) { + random_device rd; + mt19937 rand(rd()); + for (size_t i = 0; i < memory->getSize() / sizeof(uint32_t); i++) { + auto p = static_cast( + static_cast(memory->getPointer())); + p[i] = rand(); + } +} + +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"; + } + } +} + +/** + * Positive decrypt test. "Decrypt" a single clear + * segment. Verify data matches. + */ +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}; + + sp sharedMemory = + getDecryptMemory(kSegmentSize * 2, kSegmentIndex); + + SharedBuffer sourceBuffer = { + .bufferId = kSegmentIndex, .offset = 0, .size = kSegmentSize}; + fillRandom(sharedMemory); + + DestinationBuffer destBuffer = {.type = BufferType::SHARED_MEMORY, + {.bufferId = kSegmentIndex, + .offset = kSegmentSize, + .size = kSegmentSize}, + .secureMemory = nullptr}; + + Pattern noPattern = {0, 0}; + vector subSamples = {{.numBytesOfClearData = kSegmentSize, + .numBytesOfEncryptedData = 0}}; + uint64_t offset = 0; + + 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"; + closeSession(sessionId); + } +} + +/** + * Instantiate the set of test cases for each vendor module + */ + +INSTANTIATE_TEST_CASE_P( + DrmHalVendorFactoryTestCases, DrmHalVendorFactoryTest, + testing::ValuesIn(gVendorModules->getVendorModulePaths())); + +INSTANTIATE_TEST_CASE_P( + DrmHalVendorPluginTestCases, DrmHalVendorPluginTest, + testing::ValuesIn(gVendorModules->getVendorModulePaths())); + +INSTANTIATE_TEST_CASE_P( + DrmHalVendorDecryptTestCases, DrmHalVendorDecryptTest, + testing::ValuesIn(gVendorModules->getVendorModulePaths())); + +int main(int argc, char** argv) { + gVendorModules = + new drm_vts::VendorModules("/data/nativetest/drm_hidl_test/vendor"); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/drm/1.0/vts/functional/shared_library.cpp b/drm/1.0/vts/functional/shared_library.cpp new file mode 100644 index 00000000..6658150f --- /dev/null +++ b/drm/1.0/vts/functional/shared_library.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2017 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 "drm-vts-shared-library" + +#include +#include + +using std::string; + +namespace drm_vts { + +SharedLibrary::SharedLibrary(const string& path) { + mLibHandle = dlopen(path.c_str(), RTLD_NOW); +} + +SharedLibrary::~SharedLibrary() { + if (mLibHandle != NULL) { + dlclose(mLibHandle); + mLibHandle = NULL; + } +} + +bool SharedLibrary::operator!() const { + return mLibHandle == NULL; +} + +void* SharedLibrary::lookup(const char* symbol) const { + if (!mLibHandle) { + return NULL; + } + + // Clear last error before we load the symbol again, + // in case the caller didn't retrieve it. + (void)dlerror(); + return dlsym(mLibHandle, symbol); +} + +const char* SharedLibrary::lastError() const { + const char* error = dlerror(); + return error ? error : "No errors or unknown error"; +} +}; diff --git a/drm/1.0/vts/functional/shared_library.h b/drm/1.0/vts/functional/shared_library.h new file mode 100644 index 00000000..1f32243a --- /dev/null +++ b/drm/1.0/vts/functional/shared_library.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SHARED_LIBRARY_H_ +#define SHARED_LIBRARY_H_ + +#include +#include + +namespace drm_vts { +class SharedLibrary { + public: + explicit SharedLibrary(const std::string& path); + ~SharedLibrary(); + + bool operator!() const; + void* lookup(const char* symbol) const; + const char* lastError() const; + + private: + void* mLibHandle; + + SharedLibrary(const SharedLibrary&) = delete; + void operator=(const SharedLibrary&) = delete; +}; +}; + +#endif // SHARED_LIBRARY_H_ diff --git a/drm/1.0/vts/functional/vendor/lib/libvtswidevine.so b/drm/1.0/vts/functional/vendor/lib/libvtswidevine.so new file mode 100755 index 00000000..d365b349 Binary files /dev/null and b/drm/1.0/vts/functional/vendor/lib/libvtswidevine.so differ diff --git a/drm/1.0/vts/functional/vendor_modules.cpp b/drm/1.0/vts/functional/vendor_modules.cpp new file mode 100644 index 00000000..34af6f86 --- /dev/null +++ b/drm/1.0/vts/functional/vendor_modules.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2017 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 "drm-vts-vendor-modules" + +#include +#include +#include +#include + +#include "shared_library.h" +#include "vendor_modules.h" + +using std::string; +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()); + 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); + } + } + + closedir(dir); + return mModuleList; +} + +DrmHalVTSVendorModule* VendorModules::getVendorModule(const string& path) { + unique_ptr& library = mOpenLibraries[path]; + if (!library) { + library = unique_ptr(new SharedLibrary(path)); + if (!library) { + ALOGE("failed to map shared library %s", path.c_str()); + return NULL; + } + } + void* symbol = library->lookup("vendorModuleFactory"); + if (symbol == NULL) { + ALOGE("getVendorModule failed to lookup 'vendorModuleFactory' in %s: " + "%s", + path.c_str(), library->lastError()); + return NULL; + } + typedef DrmHalVTSVendorModule* (*ModuleFactory)(); + ModuleFactory moduleFactory = reinterpret_cast(symbol); + return (*moduleFactory)(); +} +}; diff --git a/drm/1.0/vts/functional/vendor_modules.h b/drm/1.0/vts/functional/vendor_modules.h new file mode 100644 index 00000000..5371a0db --- /dev/null +++ b/drm/1.0/vts/functional/vendor_modules.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef VENDOR_MODULES_H +#define VENDOR_MODULES_H + +#include + +#include "shared_library.h" + +class DrmHalVTSVendorModule; + +namespace drm_vts { +class VendorModules { + public: + /** + * Initialize with a file system path where the shared libraries + * are to be found. + */ + explicit VendorModules(const std::string& path) : mModulesPath(path) {} + ~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); + + private: + std::string mModulesPath; + std::vector mModuleList; + std::map> mOpenLibraries; + + inline bool endsWith(const std::string& str, const std::string& suffix) { + if (suffix.size() > str.size()) return false; + return std::equal(suffix.rbegin(), suffix.rend(), str.rbegin()); + } + + VendorModules(const VendorModules&) = delete; + void operator=(const VendorModules&) = delete; +}; +}; + +#endif // VENDOR_MODULES_H diff --git a/drm/Android.bp b/drm/Android.bp index bbb3e4ba..33f70eba 100644 --- a/drm/Android.bp +++ b/drm/Android.bp @@ -1,4 +1,5 @@ // This is an autogenerated file, do not edit. subdirs = [ "1.0", + "1.0/vts/functional", ]