diff options
| author | Robert Shih <robertshih@google.com> | 2019-02-28 13:42:28 -0800 |
|---|---|---|
| committer | Robert Shih <robertshih@google.com> | 2019-03-06 15:54:53 -0800 |
| commit | 456f3a6447c86b8f478e2021008e49ec43f3f023 (patch) | |
| tree | 997056811966628bfe9a8ceb044c512238cc8efc /drm | |
| parent | b098cb0ddade233538b7d651e8dcfa0dc70bd0f7 (diff) | |
| download | android_hardware_interfaces-456f3a6447c86b8f478e2021008e49ec43f3f023.tar.gz android_hardware_interfaces-456f3a6447c86b8f478e2021008e49ec43f3f023.tar.bz2 android_hardware_interfaces-456f3a6447c86b8f478e2021008e49ec43f3f023.zip | |
Add vts tests for new drm@1.2 methods
Bug: 118402843
Test: VtsHalDrmV1_2TargetTest
Change-Id: If0da1538c33e0284fa6fa6dd48d56a0ce1753f02
Diffstat (limited to 'drm')
| -rw-r--r-- | drm/1.2/vts/functional/Android.bp | 40 | ||||
| -rw-r--r-- | drm/1.2/vts/functional/drm_hal_clearkey_module.cpp | 130 | ||||
| -rw-r--r-- | drm/1.2/vts/functional/drm_hal_clearkey_module.h | 61 | ||||
| -rw-r--r-- | drm/1.2/vts/functional/drm_hal_common.cpp | 483 | ||||
| -rw-r--r-- | drm/1.2/vts/functional/drm_hal_common.h | 198 | ||||
| -rw-r--r-- | drm/1.2/vts/functional/drm_hal_test.cpp | 542 | ||||
| -rw-r--r-- | drm/1.2/vts/functional/vendor_modules.cpp | 72 | ||||
| -rw-r--r-- | drm/1.2/vts/functional/vendor_modules.h | 73 |
8 files changed, 1599 insertions, 0 deletions
diff --git a/drm/1.2/vts/functional/Android.bp b/drm/1.2/vts/functional/Android.bp new file mode 100644 index 000000000..6b4a4c0ad --- /dev/null +++ b/drm/1.2/vts/functional/Android.bp @@ -0,0 +1,40 @@ +// +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +cc_test { + name: "VtsHalDrmV1_2TargetTest", + defaults: ["VtsHalTargetTestDefaults"], + srcs: [ + "drm_hal_clearkey_module.cpp", + "drm_hal_common.cpp", + "drm_hal_test.cpp", + "vendor_modules.cpp", + ], + include_dirs: ["hardware/interfaces/drm/1.0/vts/functional"], + static_libs: [ + "android.hardware.drm@1.0", + "android.hardware.drm@1.1", + "android.hardware.drm@1.2", + "android.hardware.drm@1.0-helper", + "android.hidl.allocator@1.0", + "android.hidl.memory@1.0", + "libhidlmemory", + "libnativehelper", + "libssl", + "libcrypto", + ], + test_suites: ["general-tests"], +} diff --git a/drm/1.2/vts/functional/drm_hal_clearkey_module.cpp b/drm/1.2/vts/functional/drm_hal_clearkey_module.cpp new file mode 100644 index 000000000..a0dc00155 --- /dev/null +++ b/drm/1.2/vts/functional/drm_hal_clearkey_module.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "drm_hal_clearkey_module@1.2" + +#include <gtest/gtest.h> +#include "drm_hal_clearkey_module.h" + +namespace android { +namespace hardware { +namespace drm { +namespace V1_2 { +namespace vts { + +std::vector<uint8_t> DrmHalVTSClearkeyModule::handleProvisioningRequest( + const std::vector<uint8_t>& /*provisioningRequest*/, + const std::string& /*url*/) { + EXPECT_TRUE(false) << "Clearkey doesn't support provisioning"; + return {}; +} + +std::vector<DrmHalVTSClearkeyModule::ContentConfiguration> + DrmHalVTSClearkeyModule::getContentConfigurations() const { + DrmHalVTSClearkeyModule::ContentConfiguration conf = { + .name = "DrmHalVTSClearkeyModule", // name + .serverUrl = "", // serverUrl + .initData = { // 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 + }, + .mimeType = "video/mp4", // mimeType + .optionalParameters = {}, // optionalParameters + .policy = { .allowOffline = true }, // allowOffline + .keys = { // keys + { + .isSecure = false, // isSecure + .keyId = { // keyId + 0x60, 0x06, 0x1e, 0x01, 0x7e, 0x47, 0x7e, 0x87, + 0x7e, 0x57, 0xd0, 0x0d, 0x1e, 0xd0, 0x0d, 0x1e + }, + .clearContentKey = { // clearContentKey + 0x1a, 0x8a, 0x20, 0x95, 0xe4, 0xde, 0xb2, 0xd2, + 0x9e, 0xc8, 0x16, 0xac, 0x7b, 0xae, 0x20, 0x82 + } + } + } + }; + return { conf }; +} + +std::vector<uint8_t> DrmHalVTSClearkeyModule::handleKeyRequest( + const std::vector<uint8_t>& keyRequest, + const std::string& /*serverUrl*/) { + + // {"kids":["YAYeAX5Hfod-V9ANHtANHg"],"type":"temporary"} + std::vector<uint8_t> expectedKeyRequest = { + 0x7b, 0x22, 0x6b, 0x69, 0x64, 0x73, 0x22, 0x3a, 0x5b, 0x22, 0x59, 0x41, 0x59, 0x65, + 0x41, 0x58, 0x35, 0x48, 0x66, 0x6f, 0x64, 0x2d, 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}; + + // {"kids":["YAYeAX5Hfod-V9ANHtANHg"],"type":"persistent-license"} + std::vector<uint8_t> expectedKeyRequestPersistent = { + 0x7b, 0x22, 0x6b, 0x69, 0x64, 0x73, 0x22, 0x3a, 0x5b, 0x22, 0x59, 0x41, 0x59, 0x65, + 0x41, 0x58, 0x35, 0x48, 0x66, 0x6f, 0x64, 0x2d, 0x56, 0x39, 0x41, 0x4e, 0x48, 0x74, + 0x41, 0x4e, 0x48, 0x67, 0x22, 0x5d, 0x2c, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, + 0x22, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x69, + 0x63, 0x65, 0x6e, 0x73, 0x65, 0x22, 0x7d}; + + // {"keys":[{"kty":"oct","kid":"YAYeAX5Hfod-V9ANHtANHg","k":"GoogleTestKeyBase64ggg"}]} + std::vector<uint8_t> 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, 0x2d, 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}; + + // {"keys":[{"kty":"oct","kid":"YAYeAX5Hfod-V9ANHtANHg","k":"GoogleTestKeyBase64ggg"}],"type":"persistent-license"} + std::vector<uint8_t> knownKeyResponsePersistent = { + 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, 0x2d, 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, 0x2c, 0x22, 0x74, 0x79, 0x70, 0x65, 0x22, + 0x3a, 0x22, 0x70, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x69, + 0x63, 0x65, 0x6e, 0x73, 0x65, 0x22, 0x7d}; + + std::string req(keyRequest.begin(), keyRequest.end()); + if (req.find("persistent-license") != std::string::npos) { + EXPECT_EQ(expectedKeyRequestPersistent, keyRequest); + return knownKeyResponsePersistent; + } else { + EXPECT_EQ(expectedKeyRequest, keyRequest); + return knownKeyResponse; + } + +} + +} // namespace vts +} // namespace V1_2 +} // namespace drm +} // namespace hardware +} // namespace android diff --git a/drm/1.2/vts/functional/drm_hal_clearkey_module.h b/drm/1.2/vts/functional/drm_hal_clearkey_module.h new file mode 100644 index 000000000..7250cf25d --- /dev/null +++ b/drm/1.2/vts/functional/drm_hal_clearkey_module.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DRM_HAL_CLEARKEY_MODULE_H +#define DRM_HAL_CLEARKEY_MODULE_H + +#include "drm_hal_vendor_module_api.h" + +namespace android { +namespace hardware { +namespace drm { +namespace V1_2 { +namespace vts { + +class DrmHalVTSClearkeyModule : public DrmHalVTSVendorModule_V1 { + public: + DrmHalVTSClearkeyModule() {} + virtual ~DrmHalVTSClearkeyModule() {} + + virtual uint32_t getAPIVersion() const override { return 1; } + + virtual std::string getServiceName() const override { return "clearkey"; } + + virtual std::vector<uint8_t> getUUID() const override { + return {0xE2, 0x71, 0x9D, 0x58, 0xA9, 0x85, 0xB3, 0xC9, + 0x78, 0x1A, 0xB0, 0x30, 0xAF, 0x78, 0xD3, 0x0E }; + } + + + virtual std::vector<uint8_t> handleProvisioningRequest( + const std::vector<uint8_t>& provisioningRequest, + const std::string& url) override; + + virtual std::vector<DrmHalVTSClearkeyModule::ContentConfiguration> + getContentConfigurations() const override; + + virtual std::vector<uint8_t> handleKeyRequest( + const std::vector<uint8_t>& keyRequest, + const std::string& serverUrl) override; +}; + +} // namespace vts +} // namespace V1_2 +} // namespace drm +} // namespace hardware +} // namespace android + +#endif // DRM_HAL_CLEARKEY_MODULE_H diff --git a/drm/1.2/vts/functional/drm_hal_common.cpp b/drm/1.2/vts/functional/drm_hal_common.cpp new file mode 100644 index 000000000..b9a8425fc --- /dev/null +++ b/drm/1.2/vts/functional/drm_hal_common.cpp @@ -0,0 +1,483 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "drm_hal_common@1.2" + +#include <android/hidl/allocator/1.0/IAllocator.h> +#include <gtest/gtest.h> +#include <hidl/HidlSupport.h> +#include <hidlmemory/mapping.h> +#include <log/log.h> +#include <openssl/aes.h> +#include <random> + +#include "drm_hal_clearkey_module.h" +#include "drm_hal_common.h" + +using ::android::hardware::drm::V1_0::BufferType; +using ::android::hardware::drm::V1_0::DestinationBuffer; +using ICryptoPluginV1_0 = ::android::hardware::drm::V1_0::ICryptoPlugin; +using IDrmPluginV1_0 = ::android::hardware::drm::V1_0::IDrmPlugin; +using ::android::hardware::drm::V1_0::KeyValue; +using ::android::hardware::drm::V1_0::SharedBuffer; +using StatusV1_0 = ::android::hardware::drm::V1_0::Status; + +using ::android::hardware::drm::V1_1::KeyRequestType; + +using ::android::hardware::drm::V1_2::KeySetId; +using ::android::hardware::drm::V1_2::OfflineLicenseState; +using StatusV1_2 = ::android::hardware::drm::V1_2::Status; + +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_memory; + +using ::android::hidl::allocator::V1_0::IAllocator; + +using std::random_device; +using std::mt19937; + +namespace android { +namespace hardware { +namespace drm { +namespace V1_2 { +namespace vts { + +const char *kCallbackLostState = "LostState"; + +drm_vts::VendorModules *DrmHalTest::gVendorModules = nullptr; + +/** + * DrmHalPluginListener + */ + +Return<void> DrmHalPluginListener::sendSessionLostState(const hidl_vec<uint8_t>& sessionId) { + NotifyFromCallback(kCallbackLostState, sessionId); + return Void(); +} + +/** + * DrmHalTest + */ + +DrmHalTest::DrmHalTest() + : vendorModule(GetParam() == "clearkey" + ? new DrmHalVTSClearkeyModule() + : static_cast<DrmHalVTSVendorModule_V1*>(gVendorModules->getModule(GetParam()))), + contentConfigurations(vendorModule->getContentConfigurations()) { +} + +void DrmHalTest::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()); + + string name = vendorModule->getServiceName(); + drmFactory = VtsHalHidlTargetTestBase::getService<IDrmFactory>(name); + if (drmFactory == nullptr) { + drmFactory = VtsHalHidlTargetTestBase::getService<IDrmFactory>(); + } + if (drmFactory != nullptr) { + drmPlugin = createDrmPlugin(); + } + + cryptoFactory = VtsHalHidlTargetTestBase::getService<ICryptoFactory>(name); + if (cryptoFactory == nullptr) { + cryptoFactory = VtsHalHidlTargetTestBase::getService<ICryptoFactory>(); + } + if (cryptoFactory != nullptr) { + cryptoPlugin = createCryptoPlugin(); + } + + // If drm scheme not installed skip subsequent tests + if (!drmFactory->isCryptoSchemeSupported(getVendorUUID())) { + vendorModule->setInstalled(false); + return; + } + + ASSERT_NE(nullptr, drmPlugin.get()) << "Can't find " << vendorModule->getServiceName() << " drm@1.2 plugin"; + ASSERT_NE(nullptr, cryptoPlugin.get()) << "Can't find " << vendorModule->getServiceName() << " crypto@1.2 plugin"; + +} + +sp<IDrmPlugin> DrmHalTest::createDrmPlugin() { + if (drmFactory == nullptr) { + return nullptr; + } + sp<IDrmPlugin> plugin = nullptr; + hidl_string packageName("android.hardware.drm.test"); + auto res = drmFactory->createPlugin( + getVendorUUID(), packageName, + [&](StatusV1_0 status, const sp<IDrmPluginV1_0>& pluginV1_0) { + EXPECT_EQ(StatusV1_0::OK, status); + plugin = IDrmPlugin::castFrom(pluginV1_0); + }); + + if (!res.isOk()) { + ALOGE("createDrmPlugin remote call failed"); + } + return plugin; +} + +sp<ICryptoPlugin> DrmHalTest::createCryptoPlugin() { + if (cryptoFactory == nullptr) { + return nullptr; + } + sp<ICryptoPlugin> plugin = nullptr; + hidl_vec<uint8_t> initVec; + auto res = cryptoFactory->createPlugin( + getVendorUUID(), initVec, + [&](StatusV1_0 status, const sp<ICryptoPluginV1_0>& pluginV1_0) { + EXPECT_EQ(StatusV1_0::OK, status); + plugin = ICryptoPlugin::castFrom(pluginV1_0); + }); + if (!res.isOk()) { + ALOGE("createCryptoPlugin remote call failed"); + } + return plugin; +} + +hidl_array<uint8_t, 16> DrmHalTest::getVendorUUID() { + vector<uint8_t> uuid = vendorModule->getUUID(); + return hidl_array<uint8_t, 16>(&uuid[0]); +} + +/** + * Helper method to open a session and verify that a non-empty + * session ID is returned + */ +SessionId DrmHalTest::openSession() { + SessionId sessionId; + + auto res = drmPlugin->openSession([&](StatusV1_0 status, const hidl_vec<unsigned char> &id) { + EXPECT_EQ(StatusV1_0::OK, status); + EXPECT_NE(id.size(), 0u); + sessionId = id; + }); + EXPECT_OK(res); + return sessionId; +} + +/** + * Helper method to close a session + */ +void DrmHalTest::closeSession(const SessionId& sessionId) { + StatusV1_0 status = drmPlugin->closeSession(sessionId); + EXPECT_EQ(StatusV1_0::OK, status); +} + +hidl_vec<uint8_t> DrmHalTest::getKeyRequest( + const SessionId& sessionId, + const DrmHalVTSVendorModule_V1::ContentConfiguration& configuration, + const KeyType& type = KeyType::STREAMING) { + hidl_vec<uint8_t> keyRequest; + auto res = drmPlugin->getKeyRequest_1_2( + sessionId, configuration.initData, configuration.mimeType, type, + toHidlKeyedVector(configuration.optionalParameters), + [&](Status status, const hidl_vec<uint8_t>& request, + KeyRequestType requestType, const hidl_string&) { + EXPECT_EQ(Status::OK, status) << "Failed to get " + "key request for configuration " + << configuration.name; + if (type == KeyType::RELEASE) { + EXPECT_EQ(KeyRequestType::RELEASE, requestType); + } else { + EXPECT_EQ(KeyRequestType::INITIAL, requestType); + } + EXPECT_NE(request.size(), 0u) << "Expected key request size" + " to have length > 0 bytes"; + keyRequest = request; + }); + EXPECT_OK(res); + return keyRequest; +} + +DrmHalVTSVendorModule_V1::ContentConfiguration DrmHalTest::getContent(const KeyType& type) const { + for (const auto& config : contentConfigurations) { + if (type != KeyType::OFFLINE || config.policy.allowOffline) { + return config; + } + } + EXPECT_TRUE(false) << "no content configurations found"; + return {}; +} + +hidl_vec<uint8_t> DrmHalTest::provideKeyResponse( + const SessionId& sessionId, + const hidl_vec<uint8_t>& keyResponse) { + hidl_vec<uint8_t> keySetId; + auto res = drmPlugin->provideKeyResponse( + sessionId, keyResponse, + [&](StatusV1_0 status, const hidl_vec<uint8_t>& myKeySetId) { + EXPECT_EQ(StatusV1_0::OK, status) << "Failure providing " + "key response for configuration "; + keySetId = myKeySetId; + }); + EXPECT_OK(res); + return keySetId; +} + +/** + * 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. + */ +hidl_vec<uint8_t> DrmHalTest::loadKeys( + const SessionId& sessionId, + const DrmHalVTSVendorModule_V1::ContentConfiguration& configuration, + const KeyType& type) { + hidl_vec<uint8_t> keyRequest = getKeyRequest(sessionId, configuration, type); + + /** + * Get key response from vendor module + */ + hidl_vec<uint8_t> keyResponse = + vendorModule->handleKeyRequest(keyRequest, configuration.serverUrl); + EXPECT_NE(keyResponse.size(), 0u) << "Expected key response size " + "to have length > 0 bytes"; + + return provideKeyResponse(sessionId, keyResponse); +} + +hidl_vec<uint8_t> DrmHalTest::loadKeys( + const SessionId& sessionId, + const KeyType& type) { + return loadKeys(sessionId, getContent(type), type); +} + +KeyedVector DrmHalTest::toHidlKeyedVector( + const map<string, string>& params) { + std::vector<KeyValue> 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); +} + +hidl_array<uint8_t, 16> DrmHalTest::toHidlArray(const vector<uint8_t>& vec) { + EXPECT_EQ(16u, vec.size()); + return hidl_array<uint8_t, 16>(&vec[0]); +} + +/** + * 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<IMemory> DrmHalTest::getDecryptMemory(size_t size, size_t index) { + sp<IAllocator> ashmemAllocator = IAllocator::getService("ashmem"); + EXPECT_NE(nullptr, ashmemAllocator.get()); + + 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<IMemory> mappedMemory = mapMemory(hidlMemory); + EXPECT_NE(nullptr, mappedMemory.get()); + res = cryptoPlugin->setSharedBufferBase(hidlMemory, index); + EXPECT_OK(res); + return mappedMemory; +} + +void DrmHalTest::fillRandom(const sp<IMemory>& memory) { + random_device rd; + mt19937 rand(rd()); + for (size_t i = 0; i < memory->getSize() / sizeof(uint32_t); i++) { + auto p = static_cast<uint32_t*>( + static_cast<void*>(memory->getPointer())); + p[i] = rand(); + } +} + +uint32_t DrmHalTest::decrypt(Mode mode, bool isSecure, + const hidl_array<uint8_t, 16>& keyId, uint8_t* iv, + const hidl_vec<SubSample>& subSamples, const Pattern& pattern, + const vector<uint8_t>& key, StatusV1_2 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 (if exists) is the decrypted output. + size_t factor = expectedStatus == StatusV1_2::ERROR_DRM_FRAME_TOO_LARGE ? 1 : 2; + sp<IMemory> sharedMemory = + getDecryptMemory(totalSize * factor, kSegmentIndex); + + const SharedBuffer sourceBuffer = { + .bufferId = kSegmentIndex, .offset = 0, .size = totalSize}; + fillRandom(sharedMemory); + + const DestinationBuffer destBuffer = {.type = BufferType::SHARED_MEMORY, + {.bufferId = kSegmentIndex, + .offset = totalSize, + .size = totalSize}, + .secureMemory = nullptr}; + const uint64_t offset = 0; + uint32_t bytesWritten = 0; + auto res = cryptoPlugin->decrypt_1_2(isSecure, keyId, localIv, mode, pattern, + subSamples, sourceBuffer, offset, destBuffer, + [&](StatusV1_2 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<uint8_t*>( + static_cast<void*>(sharedMemory->getPointer())); + + // generate reference vector + vector<uint8_t> 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<void *>(&reference[0]), + static_cast<void*>(base + totalSize), totalSize)) + << "decrypt data mismatch"; + return totalSize; +} + +/** + * Decrypt a list of clear+encrypted subsamples using the specified key + * in AES-CTR mode + */ +void DrmHalTest::aes_ctr_decrypt(uint8_t* dest, uint8_t* src, + uint8_t* iv, const hidl_vec<SubSample>& subSamples, + const vector<uint8_t>& 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 DrmHalTest::aes_cbc_decrypt(uint8_t* dest, uint8_t* src, + uint8_t* iv, const hidl_vec<SubSample>& subSamples, + const vector<uint8_t>& key) { + AES_KEY decryptionKey; + AES_set_encrypt_key(&key[0], 128, &decryptionKey); + + size_t offset = 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; + } +} + +/** + * Helper method to test decryption with invalid keys is returned + */ +void DrmHalClearkeyTest::decryptWithInvalidKeys( + hidl_vec<uint8_t>& invalidResponse, + vector<uint8_t>& iv, + const Pattern& noPattern, + const vector<SubSample>& subSamples) { + DrmHalVTSVendorModule_V1::ContentConfiguration content = getContent(); + if (content.keys.empty()) { + FAIL() << "no keys"; + } + + const auto& key = content.keys[0]; + auto sessionId = openSession(); + auto res = drmPlugin->provideKeyResponse( + sessionId, invalidResponse, + [&](StatusV1_0 status, const hidl_vec<uint8_t>& myKeySetId) { + EXPECT_EQ(StatusV1_0::OK, status); + EXPECT_EQ(0u, myKeySetId.size()); + }); + EXPECT_OK(res); + + EXPECT_TRUE(cryptoPlugin->setMediaDrmSession(sessionId).isOk()); + + 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); +} + +} // namespace vts +} // namespace V1_2 +} // namespace drm +} // namespace hardware +} // namespace android diff --git a/drm/1.2/vts/functional/drm_hal_common.h b/drm/1.2/vts/functional/drm_hal_common.h new file mode 100644 index 000000000..1b95ddeea --- /dev/null +++ b/drm/1.2/vts/functional/drm_hal_common.h @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DRM_HAL_COMMON_H +#define DRM_HAL_COMMON_H + +#include <android/hardware/drm/1.2/ICryptoFactory.h> +#include <android/hardware/drm/1.2/ICryptoPlugin.h> +#include <android/hardware/drm/1.2/IDrmFactory.h> +#include <android/hardware/drm/1.2/IDrmPlugin.h> +#include <android/hardware/drm/1.2/IDrmPluginListener.h> +#include <android/hardware/drm/1.2/types.h> + +#include <chrono> +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include "drm_hal_vendor_module_api.h" +#include "vendor_modules.h" +#include "VtsHalHidlTargetCallbackBase.h" +#include "VtsHalHidlTargetTestBase.h" + +using ::android::hardware::drm::V1_0::EventType; +using ::android::hardware::drm::V1_0::KeyedVector; +using ::android::hardware::drm::V1_0::KeyStatus; +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::SessionId; +using ::android::hardware::drm::V1_0::SubSample; + +using ::android::hardware::drm::V1_1::ICryptoFactory; + +using ::android::hardware::drm::V1_2::ICryptoPlugin; +using ::android::hardware::drm::V1_2::IDrmFactory; +using ::android::hardware::drm::V1_2::IDrmPlugin; +using ::android::hardware::drm::V1_2::IDrmPluginListener; +using StatusV1_2 = ::android::hardware::drm::V1_2::Status; + +using ::android::hardware::hidl_array; +using ::android::hardware::hidl_vec; +using ::android::hardware::Return; +using ::android::hardware::Void; + +using ::android::hidl::memory::V1_0::IMemory; +using ::android::sp; + +using ::testing::VtsHalHidlTargetTestBase; + +using std::map; +using std::string; +using std::unique_ptr; +using std::vector; + +#define EXPECT_OK(ret) EXPECT_TRUE(ret.isOk()) + +#define RETURN_IF_SKIPPED \ + if (!vendorModule->isInstalled()) { \ + std::cout << "[ SKIPPED ] This drm scheme not supported." << \ + " library:" << GetParam() << " service-name:" << \ + vendorModule->getServiceName() << std::endl; \ + return; \ + } + +namespace android { +namespace hardware { +namespace drm { +namespace V1_2 { +namespace vts { + +class DrmHidlEnvironment : public ::testing::VtsHalHidlTargetTestEnvBase { + public: + // get the test environment singleton + static DrmHidlEnvironment* Instance() { + static DrmHidlEnvironment* instance = new DrmHidlEnvironment; + return instance; + } + + void registerTestServices() override { + registerTestService<ICryptoFactory>(); + registerTestService<IDrmFactory>(); + setServiceCombMode(::testing::HalServiceCombMode::NO_COMBINATION); + } + + private: + DrmHidlEnvironment() {} + + GTEST_DISALLOW_COPY_AND_ASSIGN_(DrmHidlEnvironment); +}; + +class DrmHalTest : public ::testing::TestWithParam<std::string> { + public: + static drm_vts::VendorModules* gVendorModules; + DrmHalTest(); + virtual void SetUp() override; + virtual void TearDown() override {} + + protected: + hidl_array<uint8_t, 16> getVendorUUID(); + SessionId openSession(); + void closeSession(const SessionId& sessionId); + hidl_vec<uint8_t> loadKeys(const SessionId& sessionId, + const KeyType& type = KeyType::STREAMING); + hidl_vec<uint8_t> loadKeys(const SessionId& sessionId, + const DrmHalVTSVendorModule_V1::ContentConfiguration&, + const KeyType& type = KeyType::STREAMING); + hidl_vec<uint8_t> getKeyRequest(const SessionId& sessionId, + const DrmHalVTSVendorModule_V1::ContentConfiguration&, + const KeyType& type); + hidl_vec<uint8_t> provideKeyResponse(const SessionId& sessionId, + const hidl_vec<uint8_t>& keyResponse); + DrmHalVTSVendorModule_V1::ContentConfiguration getContent( + const KeyType& type = KeyType::STREAMING) const; + + KeyedVector toHidlKeyedVector(const map<string, string>& params); + hidl_array<uint8_t, 16> toHidlArray(const vector<uint8_t>& vec); + + void fillRandom(const sp<IMemory>& memory); + sp<IMemory> getDecryptMemory(size_t size, size_t index); + uint32_t decrypt(Mode mode, bool isSecure, + const hidl_array<uint8_t, 16>& keyId, uint8_t* iv, + const hidl_vec<SubSample>& subSamples, const Pattern& pattern, + const vector<uint8_t>& key, StatusV1_2 expectedStatus); + void aes_ctr_decrypt(uint8_t* dest, uint8_t* src, uint8_t* iv, + const hidl_vec<SubSample>& subSamples, const vector<uint8_t>& key); + void aes_cbc_decrypt(uint8_t* dest, uint8_t* src, uint8_t* iv, + const hidl_vec<SubSample>& subSamples, const vector<uint8_t>& key); + + sp<IDrmFactory> drmFactory; + sp<ICryptoFactory> cryptoFactory; + sp<IDrmPlugin> drmPlugin; + sp<ICryptoPlugin> cryptoPlugin; + unique_ptr<DrmHalVTSVendorModule_V1> vendorModule; + const vector<DrmHalVTSVendorModule_V1::ContentConfiguration> contentConfigurations; + + private: + sp<IDrmPlugin> createDrmPlugin(); + sp<ICryptoPlugin> createCryptoPlugin(); + +}; + +class DrmHalClearkeyTest : public DrmHalTest { + public: + virtual void SetUp() override { DrmHalTest::SetUp(); } + virtual void TearDown() override {} + void decryptWithInvalidKeys(hidl_vec<uint8_t>& invalidResponse, + vector<uint8_t>& iv, const Pattern& noPattern, const vector<SubSample>& subSamples); +}; + +/** + * Event Handling tests + */ +extern const char *kCallbackLostState; + +class DrmHalPluginListener + : public ::testing::VtsHalHidlTargetCallbackBase<SessionId>, + public IDrmPluginListener { +public: + DrmHalPluginListener() { + SetWaitTimeoutDefault(std::chrono::milliseconds(500)); + } + virtual ~DrmHalPluginListener() {} + + virtual Return<void> sendEvent(EventType, const hidl_vec<uint8_t>&, + const hidl_vec<uint8_t>& ) override { return Void(); } + + virtual Return<void> sendExpirationUpdate(const hidl_vec<uint8_t>&, + int64_t) override { return Void(); } + + virtual Return<void> sendKeysChange(const hidl_vec<uint8_t>&, + const hidl_vec<KeyStatus>&, bool) override { return Void(); } + + virtual Return<void> sendSessionLostState(const hidl_vec<uint8_t>& sessionId) override; + +}; + +} // namespace vts +} // namespace V1_2 +} // namespace drm +} // namespace hardware +} // namespace android + +#endif // DRM_HAL_COMMON_H diff --git a/drm/1.2/vts/functional/drm_hal_test.cpp b/drm/1.2/vts/functional/drm_hal_test.cpp new file mode 100644 index 000000000..067b5e474 --- /dev/null +++ b/drm/1.2/vts/functional/drm_hal_test.cpp @@ -0,0 +1,542 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "drm_hal_clearkey_test@1.2" + +#include <gtest/gtest.h> +#include <hidl/HidlSupport.h> +#include <log/log.h> +#include <openssl/aes.h> + +#include "drm_hal_common.h" + +using ::android::hardware::drm::V1_0::Status; +using ::android::hardware::drm::V1_1::KeyRequestType; +using ::android::hardware::drm::V1_1::SecurityLevel; +using ::android::hardware::drm::V1_2::HdcpLevel; +using ::android::hardware::drm::V1_2::KeySetId; +using ::android::hardware::drm::V1_2::OfflineLicenseState; + +using ::android::hardware::drm::V1_2::vts::DrmHalClearkeyTest; +using ::android::hardware::drm::V1_2::vts::DrmHalPluginListener; +using ::android::hardware::drm::V1_2::vts::DrmHalTest; +using ::android::hardware::drm::V1_2::vts::DrmHidlEnvironment; +using ::android::hardware::drm::V1_2::vts::kCallbackLostState; + +using ::android::hardware::hidl_string; + +static const char* const kVideoMp4 = "video/mp4"; +static const char* const kBadMime = "video/unknown"; +static const char* const kDrmErrorTestKey = "drmErrorTest"; +static const char* const kDrmErrorGeneric = ""; +static const char* const kResourceContentionValue = "resourceContention"; +static const SecurityLevel kSwSecureCrypto = SecurityLevel::SW_SECURE_CRYPTO; + +/** + * Ensure drm factory supports module UUID Scheme + */ +TEST_P(DrmHalTest, VendorUuidSupported) { + auto res = drmFactory->isCryptoSchemeSupported_1_2(getVendorUUID(), kVideoMp4, kSwSecureCrypto); + ALOGI("kVideoMp4 = %s res %d", kVideoMp4, (bool)res); + EXPECT_TRUE(res); +} + +/** + * Ensure drm factory doesn't support an invalid scheme UUID + */ +TEST_P(DrmHalTest, InvalidPluginNotSupported) { + const uint8_t kInvalidUUID[16] = { + 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80, + 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80}; + EXPECT_FALSE(drmFactory->isCryptoSchemeSupported_1_2(kInvalidUUID, kVideoMp4, kSwSecureCrypto)); +} + +/** + * Ensure drm factory doesn't support an empty UUID + */ +TEST_P(DrmHalTest, EmptyPluginUUIDNotSupported) { + hidl_array<uint8_t, 16> emptyUUID; + memset(emptyUUID.data(), 0, 16); + EXPECT_FALSE(drmFactory->isCryptoSchemeSupported_1_2(emptyUUID, kVideoMp4, kSwSecureCrypto)); +} + +/** + * Ensure drm factory doesn't support an invalid mime type + */ +TEST_P(DrmHalTest, BadMimeNotSupported) { + EXPECT_FALSE(drmFactory->isCryptoSchemeSupported_1_2(getVendorUUID(), kBadMime, kSwSecureCrypto)); +} + +/** + * 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(DrmHalTest, DoProvisioning) { + RETURN_IF_SKIPPED; + hidl_string certificateType; + hidl_string certificateAuthority; + hidl_vec<uint8_t> provisionRequest; + hidl_string defaultUrl; + auto res = drmPlugin->getProvisionRequest_1_2( + certificateType, certificateAuthority, + [&](StatusV1_2 status, const hidl_vec<uint8_t>& request, + const hidl_string& url) { + if (status == StatusV1_2::OK) { + EXPECT_NE(request.size(), 0u); + provisionRequest = request; + defaultUrl = url; + } else if (status == StatusV1_2::ERROR_DRM_CANNOT_HANDLE) { + EXPECT_EQ(0u, request.size()); + } + }); + EXPECT_OK(res); + + if (provisionRequest.size() > 0) { + vector<uint8_t> response = vendorModule->handleProvisioningRequest( + provisionRequest, defaultUrl); + ASSERT_NE(0u, response.size()); + + auto res = drmPlugin->provideProvisionResponse( + response, [&](Status status, const hidl_vec<uint8_t>&, + const hidl_vec<uint8_t>&) { + EXPECT_EQ(Status::OK, status); + }); + EXPECT_OK(res); + } +} + +/** + * A get key request should fail if no sessionId is provided + */ +TEST_P(DrmHalTest, GetKeyRequestNoSession) { + SessionId invalidSessionId; + hidl_vec<uint8_t> initData; + KeyedVector optionalParameters; + auto res = drmPlugin->getKeyRequest_1_2( + invalidSessionId, initData, kVideoMp4, KeyType::STREAMING, + optionalParameters, + [&](StatusV1_2 status, const hidl_vec<uint8_t>&, KeyRequestType, + const hidl_string&) { EXPECT_EQ(StatusV1_2::BAD_VALUE, status); }); + EXPECT_OK(res); +} + +/** + * Test that the plugin returns the documented error for the + * case of attempting to generate a key request using an + * invalid mime type + */ +TEST_P(DrmHalTest, GetKeyRequestBadMime) { + auto sessionId = openSession(); + hidl_vec<uint8_t> initData; + KeyedVector optionalParameters; + auto res = drmPlugin->getKeyRequest_1_2( + sessionId, initData, kBadMime, KeyType::STREAMING, + optionalParameters, [&](StatusV1_2 status, const hidl_vec<uint8_t>&, + KeyRequestType, const hidl_string&) { + EXPECT_NE(StatusV1_2::OK, status); + }); + EXPECT_OK(res); + closeSession(sessionId); +} + +template <Status S, size_t N> +void checkKeySetIds(Status status, const hidl_vec<KeySetId>& keySetIds) { + EXPECT_EQ(S, status); + if (S == Status::OK) { + EXPECT_EQ(N, keySetIds.size()); + } +} + +template <Status S, OfflineLicenseState T> +void checkKeySetIdState(Status status, OfflineLicenseState state) { + if (S == Status::OK) { + EXPECT_TRUE(T == OfflineLicenseState::USABLE || T == OfflineLicenseState::INACTIVE); + } else { + EXPECT_TRUE(T == OfflineLicenseState::UNKNOWN); + } + EXPECT_EQ(S, status); + EXPECT_EQ(T, state); +} + +/** + * Test clearkey plugin offline key support + */ +TEST_P(DrmHalTest, OfflineLicenseTest) { + auto sessionId = openSession(); + hidl_vec<uint8_t> keySetId = loadKeys(sessionId, KeyType::OFFLINE); + + auto res = drmPlugin->getOfflineLicenseKeySetIds(checkKeySetIds<Status::OK, 1u>); + EXPECT_OK(res); + + Status err = drmPlugin->removeOfflineLicense(keySetId); + EXPECT_EQ(Status::OK, err); + + res = drmPlugin->getOfflineLicenseKeySetIds(checkKeySetIds<Status::OK, 0u>); + EXPECT_OK(res); + + err = drmPlugin->removeOfflineLicense(keySetId); + EXPECT_EQ(Status::BAD_VALUE, err); + + closeSession(sessionId); +} + +/** + * Test clearkey plugin offline key state + */ +TEST_P(DrmHalTest, OfflineLicenseStateTest) { + auto sessionId = openSession(); + DrmHalVTSVendorModule_V1::ContentConfiguration content = getContent(KeyType::OFFLINE); + hidl_vec<uint8_t> keySetId = loadKeys(sessionId, content, KeyType::OFFLINE); + drmPlugin->getOfflineLicenseState(keySetId, checkKeySetIdState<Status::OK, OfflineLicenseState::USABLE>); + + hidl_vec<uint8_t> keyRequest = getKeyRequest(keySetId, content, KeyType::RELEASE); + drmPlugin->getOfflineLicenseState(keySetId, checkKeySetIdState<Status::OK, OfflineLicenseState::INACTIVE>); + + /** + * Get key response from vendor module + */ + hidl_vec<uint8_t> keyResponse = + vendorModule->handleKeyRequest(keyRequest, content.serverUrl); + EXPECT_GT(keyResponse.size(), 0u); + + provideKeyResponse(keySetId, keyResponse); + drmPlugin->getOfflineLicenseState(keySetId, checkKeySetIdState<Status::BAD_VALUE, OfflineLicenseState::UNKNOWN>); + closeSession(sessionId); +} + +/** + * Negative offline license test. Remove empty keySetId + */ +TEST_P(DrmHalTest, RemoveEmptyKeySetId) { + KeySetId emptyKeySetId; + Status err = drmPlugin->removeOfflineLicense(emptyKeySetId); + EXPECT_EQ(Status::BAD_VALUE, err); +} + +/** + * Negative offline license test. Get empty keySetId state + */ +TEST_P(DrmHalTest, GetEmptyKeySetIdState) { + KeySetId emptyKeySetId; + auto res = drmPlugin->getOfflineLicenseState(emptyKeySetId, checkKeySetIdState<Status::BAD_VALUE, OfflineLicenseState::UNKNOWN>); + EXPECT_OK(res); +} + +/** + * Test that the plugin returns valid connected and max HDCP levels + */ +TEST_P(DrmHalTest, GetHdcpLevels) { + auto res = drmPlugin->getHdcpLevels_1_2( + [&](StatusV1_2 status, const HdcpLevel &connectedLevel, + const HdcpLevel &maxLevel) { + EXPECT_EQ(StatusV1_2::OK, status); + EXPECT_GE(connectedLevel, HdcpLevel::HDCP_NONE); + EXPECT_LE(maxLevel, HdcpLevel::HDCP_V2_3); + }); + EXPECT_OK(res); +} + +/** + * CryptoPlugin Decrypt tests + */ + +/** + * Positive decrypt test. "Decrypt" a single clear segment + */ +TEST_P(DrmHalTest, ClearSegmentTest) { + RETURN_IF_SKIPPED; + for (const auto& config : contentConfigurations) { + for (const auto& key : config.keys) { + const size_t kSegmentSize = 1024; + vector<uint8_t> iv(AES_BLOCK_SIZE, 0); + const Pattern noPattern = {0, 0}; + const vector<SubSample> 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, StatusV1_2::OK); + EXPECT_EQ(kSegmentSize, byteCount); + + closeSession(sessionId); + } + } +} + +/** + * Positive decrypt test. Decrypt a single segment using aes_ctr. + * Verify data matches. + */ +TEST_P(DrmHalTest, EncryptedAesCtrSegmentTest) { + RETURN_IF_SKIPPED; + for (const auto& config : contentConfigurations) { + for (const auto& key : config.keys) { + const size_t kSegmentSize = 1024; + vector<uint8_t> iv(AES_BLOCK_SIZE, 0); + const Pattern noPattern = {0, 0}; + const vector<SubSample> 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, StatusV1_2::OK); + EXPECT_EQ(kSegmentSize, byteCount); + + closeSession(sessionId); + } + } +} + +/** + * Negative decrypt test. Decrypted frame too large to fit in output buffer + */ +TEST_P(DrmHalTest, ErrorFrameTooLarge) { + RETURN_IF_SKIPPED; + for (const auto& config : contentConfigurations) { + for (const auto& key : config.keys) { + const size_t kSegmentSize = 1024; + vector<uint8_t> iv(AES_BLOCK_SIZE, 0); + const Pattern noPattern = {0, 0}; + const vector<SubSample> subSamples = {{.numBytesOfClearData = kSegmentSize, + .numBytesOfEncryptedData = 0}}; + auto sessionId = openSession(); + loadKeys(sessionId, config); + + Status status = cryptoPlugin->setMediaDrmSession(sessionId); + EXPECT_EQ(Status::OK, status); + + decrypt(Mode::UNENCRYPTED, key.isSecure, toHidlArray(key.keyId), + &iv[0], subSamples, noPattern, key.clearContentKey, StatusV1_2::ERROR_DRM_FRAME_TOO_LARGE); + + closeSession(sessionId); + } + } +} + +/** + * Negative decrypt test. Decrypt without loading keys. + */ +TEST_P(DrmHalTest, EncryptedAesCtrSegmentTestNoKeys) { + RETURN_IF_SKIPPED; + for (const auto& config : contentConfigurations) { + for (const auto& key : config.keys) { + vector<uint8_t> iv(AES_BLOCK_SIZE, 0); + const Pattern noPattern = {0, 0}; + const vector<SubSample> 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, StatusV1_2::ERROR_DRM_NO_LICENSE); + EXPECT_EQ(0u, byteCount); + + closeSession(sessionId); + } + } +} + +/** + * Ensure clearkey drm factory doesn't support security level higher than supported + */ +TEST_P(DrmHalClearkeyTest, BadLevelNotSupported) { + const SecurityLevel kHwSecureAll = SecurityLevel::HW_SECURE_ALL; + EXPECT_FALSE(drmFactory->isCryptoSchemeSupported_1_2(getVendorUUID(), kVideoMp4, kHwSecureAll)); +} + +/** + * Test resource contention during attempt to generate key request + */ +TEST_P(DrmHalClearkeyTest, GetKeyRequestResourceContention) { + Status status = drmPlugin->setPropertyString(kDrmErrorTestKey, kResourceContentionValue); + EXPECT_EQ(Status::OK, status); + auto sessionId = openSession(); + hidl_vec<uint8_t> initData; + KeyedVector optionalParameters; + auto res = drmPlugin->getKeyRequest_1_2( + sessionId, initData, kVideoMp4, KeyType::STREAMING, + optionalParameters, [&](StatusV1_2 status, const hidl_vec<uint8_t>&, + KeyRequestType, const hidl_string&) { + EXPECT_EQ(StatusV1_2::ERROR_DRM_RESOURCE_CONTENTION, status); + }); + EXPECT_OK(res); + + status = drmPlugin->closeSession(sessionId); + EXPECT_NE(Status::OK, status); +} + +/** + * Test clearkey plugin offline key with mock error + */ +TEST_P(DrmHalClearkeyTest, OfflineLicenseInvalidState) { + auto sessionId = openSession(); + hidl_vec<uint8_t> keySetId = loadKeys(sessionId, KeyType::OFFLINE); + Status status = drmPlugin->setPropertyString(kDrmErrorTestKey, kDrmErrorGeneric); + EXPECT_EQ(Status::OK, status); + + // everything should start failing + const Status kInvalidState = Status::ERROR_DRM_INVALID_STATE; + const OfflineLicenseState kUnknownState = OfflineLicenseState::UNKNOWN; + auto res = drmPlugin->getOfflineLicenseKeySetIds(checkKeySetIds<kInvalidState, 0u>); + EXPECT_OK(res); + res = drmPlugin->getOfflineLicenseState(keySetId, checkKeySetIdState<kInvalidState, kUnknownState>); + EXPECT_OK(res); + Status err = drmPlugin->removeOfflineLicense(keySetId); + EXPECT_EQ(kInvalidState, err); + closeSession(sessionId); +} + +/** + * Test SessionLostState is triggered on error + */ +TEST_P(DrmHalClearkeyTest, SessionLostState) { + sp<DrmHalPluginListener> listener = new DrmHalPluginListener(); + auto res = drmPlugin->setListener(listener); + EXPECT_OK(res); + + Status status = drmPlugin->setPropertyString(kDrmErrorTestKey, kDrmErrorGeneric); + EXPECT_EQ(Status::OK, status); + + auto sessionId = openSession(); + drmPlugin->closeSession(sessionId); + + auto result = listener->WaitForCallback(kCallbackLostState); + EXPECT_TRUE(result.no_timeout); + EXPECT_TRUE(result.args); + EXPECT_EQ(sessionId, *(result.args)); +} + +/** + * Negative decrypt test. Decrypt with invalid key. + */ +TEST_P(DrmHalClearkeyTest, DecryptWithEmptyKey) { + vector<uint8_t> iv(AES_BLOCK_SIZE, 0); + const Pattern noPattern = {0, 0}; + const uint32_t kClearBytes = 512; + const uint32_t kEncryptedBytes = 512; + const vector<SubSample> subSamples = { + {.numBytesOfClearData = kClearBytes, + .numBytesOfEncryptedData = kEncryptedBytes}}; + + // base 64 encoded JSON response string, must not contain padding character '=' + const hidl_string emptyKeyResponse = + "{\"keys\":[" \ + "{" \ + "\"kty\":\"oct\"" \ + "\"alg\":\"A128KW2\"" \ + "\"k\":\"SGVsbG8gRnJpZW5kIQ\"" \ + "\"kid\":\"Y2xlYXJrZXlrZXlpZDAyAy\"" \ + "}" \ + "{" \ + "\"kty\":\"oct\"," \ + "\"alg\":\"A128KW2\"" \ + "\"kid\":\"Y2xlYXJrZXlrZXlpZDAzAy\"," \ + // empty key follows + "\"k\":\"R\"" \ + "}]" \ + "}"; + const size_t kEmptyKeyResponseSize = emptyKeyResponse.size(); + + hidl_vec<uint8_t> invalidResponse; + invalidResponse.resize(kEmptyKeyResponseSize); + memcpy(invalidResponse.data(), emptyKeyResponse.c_str(), kEmptyKeyResponseSize); + decryptWithInvalidKeys(invalidResponse, iv, noPattern, subSamples); +} + +/** + * Negative decrypt test. Decrypt with a key exceeds AES_BLOCK_SIZE. + */ +TEST_P(DrmHalClearkeyTest, DecryptWithKeyTooLong) { + vector<uint8_t> iv(AES_BLOCK_SIZE, 0); + const Pattern noPattern = {0, 0}; + const uint32_t kClearBytes = 512; + const uint32_t kEncryptedBytes = 512; + const vector<SubSample> subSamples = { + {.numBytesOfClearData = kClearBytes, + .numBytesOfEncryptedData = kEncryptedBytes}}; + + // base 64 encoded JSON response string, must not contain padding character '=' + const hidl_string keyTooLongResponse = + "{\"keys\":[" \ + "{" \ + "\"kty\":\"oct\"," \ + "\"alg\":\"A128KW2\"" \ + "\"kid\":\"Y2xlYXJrZXlrZXlpZDAzAy\"," \ + // key too long + "\"k\":\"V2lubmllIHRoZSBwb29oIVdpbm5pZSB0aGUgcG9vaCE=\"" \ + "}]" \ + "}"; + const size_t kKeyTooLongResponseSize = keyTooLongResponse.size(); + + hidl_vec<uint8_t> invalidResponse; + invalidResponse.resize(kKeyTooLongResponseSize); + memcpy(invalidResponse.data(), keyTooLongResponse.c_str(), kKeyTooLongResponseSize); + decryptWithInvalidKeys(invalidResponse, iv, noPattern, subSamples); +} + +/** + * Instantiate the set of test cases for each vendor module + */ + +INSTANTIATE_TEST_CASE_P( + DrmHalTestClearkey, DrmHalTest, + testing::Values("clearkey")); + +INSTANTIATE_TEST_CASE_P( + DrmHalTestClearkeyExtended, DrmHalClearkeyTest, + testing::Values("clearkey")); + +INSTANTIATE_TEST_CASE_P( + DrmHalTestVendor, DrmHalTest, + testing::ValuesIn(DrmHalTest::gVendorModules->getPathList())); + +int main(int argc, char** argv) { +#if defined(__LP64__) + const char* kModulePath = "/data/local/tmp/64/lib"; +#else + const char* kModulePath = "/data/local/tmp/32/lib"; +#endif + DrmHalTest::gVendorModules = new drm_vts::VendorModules(kModulePath); + if (DrmHalTest::gVendorModules->getPathList().size() == 0) { + std::cerr << "WARNING: No vendor modules found in " << kModulePath << + ", all vendor tests will be skipped" << std::endl; + } + ::testing::AddGlobalTestEnvironment(DrmHidlEnvironment::Instance()); + ::testing::InitGoogleTest(&argc, argv); + DrmHidlEnvironment::Instance()->init(&argc, argv); + int status = RUN_ALL_TESTS(); + ALOGI("Test result = %d", status); + return status; +} diff --git a/drm/1.2/vts/functional/vendor_modules.cpp b/drm/1.2/vts/functional/vendor_modules.cpp new file mode 100644 index 000000000..efcb90a91 --- /dev/null +++ b/drm/1.2/vts/functional/vendor_modules.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "drm-vts-vendor-modules" + +#include <dirent.h> +#include <dlfcn.h> +#include <log/log.h> +#include <memory> +#include <utils/String8.h> +#include <SharedLibrary.h> + +#include "vendor_modules.h" + +using std::string; +using std::vector; +using std::unique_ptr; +using ::android::String8; +using ::android::hardware::drm::V1_0::helper::SharedLibrary; + +namespace drm_vts { +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", 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); + } +} + +DrmHalVTSVendorModule* VendorModules::getModule(const string& path) { + if (mOpenLibraries.find(path) == mOpenLibraries.end()) { + auto library = std::make_unique<SharedLibrary>(String8(path.c_str())); + if (!library) { + ALOGE("failed to map shared library %s", path.c_str()); + return NULL; + } + mOpenLibraries[path] = std::move(library); + } + const unique_ptr<SharedLibrary>& 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()); + return NULL; + } + typedef DrmHalVTSVendorModule* (*ModuleFactory)(); + ModuleFactory moduleFactory = reinterpret_cast<ModuleFactory>(symbol); + return (*moduleFactory)(); +} +}; diff --git a/drm/1.2/vts/functional/vendor_modules.h b/drm/1.2/vts/functional/vendor_modules.h new file mode 100644 index 000000000..9b730adcd --- /dev/null +++ b/drm/1.2/vts/functional/vendor_modules.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef VENDOR_MODULES_H +#define VENDOR_MODULES_H + +#include <map> +#include <vector> +#include <string> + +#include <SharedLibrary.h> + +using ::android::hardware::drm::V1_0::helper::SharedLibrary; + +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& dir) { + scanModules(dir); + } + ~VendorModules() {} + + /** + * Retrieve a DrmHalVTSVendorModule given its full path. The + * getAPIVersion method can be used to determine the versioned + * subclass type. + */ + DrmHalVTSVendorModule* getModule(const std::string& path); + + /** + * Return the list of paths to available vendor modules. + */ + std::vector<std::string> getPathList() const {return mPathList;} + + private: + std::vector<std::string> mPathList; + std::map<std::string, std::unique_ptr<SharedLibrary>> mOpenLibraries; + + /** + * 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()); + } + + VendorModules(const VendorModules&) = delete; + void operator=(const VendorModules&) = delete; +}; +}; + +#endif // VENDOR_MODULES_H |
