diff options
Diffstat (limited to 'drm')
29 files changed, 2500 insertions, 82 deletions
diff --git a/drm/1.0/Android.bp b/drm/1.0/Android.bp index aca5ae492..fea851f54 100644 --- a/drm/1.0/Android.bp +++ b/drm/1.0/Android.bp @@ -17,22 +17,6 @@ hidl_interface { interfaces: [ "android.hidl.base@1.0", ], - types: [ - "BufferType", - "DestinationBuffer", - "EventType", - "KeyRequestType", - "KeyStatus", - "KeyStatusType", - "KeyType", - "KeyValue", - "Mode", - "Pattern", - "SecureStop", - "SharedBuffer", - "Status", - "SubSample", - ], gen_java: false, } diff --git a/drm/1.0/default/Android.mk b/drm/1.0/default/Android.mk index 99773be38..d66f377b5 100644 --- a/drm/1.0/default/Android.mk +++ b/drm/1.0/default/Android.mk @@ -19,39 +19,23 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) + +include $(LOCAL_PATH)/common_default_service.mk LOCAL_MODULE := android.hardware.drm@1.0-service LOCAL_INIT_RC := android.hardware.drm@1.0-service.rc -LOCAL_PROPRIETARY_MODULE := true -LOCAL_MODULE_RELATIVE_PATH := hw -LOCAL_SRC_FILES := \ - service.cpp \ +LOCAL_SRC_FILES := service.cpp -LOCAL_SHARED_LIBRARIES := \ - android.hardware.drm@1.0 \ - android.hidl.memory@1.0 \ - libhidlbase \ - libhidltransport \ - libhardware \ - liblog \ - libutils \ - libbinder \ - -LOCAL_STATIC_LIBRARIES := \ - android.hardware.drm@1.0-helper \ +include $(BUILD_EXECUTABLE) -LOCAL_C_INCLUDES := \ - hardware/interfaces/drm +############# Build legacy drm lazy service ############ -LOCAL_HEADER_LIBRARIES := \ - media_plugin_headers +include $(CLEAR_VARS) -# TODO(b/18948909) Some legacy DRM plugins only support 32-bit. They need to be -# migrated to 64-bit. Once all of a device's legacy DRM plugins support 64-bit, -# that device can turn on TARGET_ENABLE_MEDIADRM_64 to build this service as -# 64-bit. -ifneq ($(TARGET_ENABLE_MEDIADRM_64), true) -LOCAL_32_BIT_ONLY := true -endif +include $(LOCAL_PATH)/common_default_service.mk +LOCAL_MODULE := android.hardware.drm@1.0-service-lazy +LOCAL_OVERRIDES_MODULES := android.hardware.drm@1.0-service +LOCAL_INIT_RC := android.hardware.drm@1.0-service-lazy.rc +LOCAL_SRC_FILES := serviceLazy.cpp include $(BUILD_EXECUTABLE) diff --git a/drm/1.0/default/CryptoPlugin.cpp b/drm/1.0/default/CryptoPlugin.cpp index 4fe6c9b43..8ddc38022 100644 --- a/drm/1.0/default/CryptoPlugin.cpp +++ b/drm/1.0/default/CryptoPlugin.cpp @@ -52,7 +52,6 @@ namespace implementation { Return<void> CryptoPlugin::setSharedBufferBase(const hidl_memory& base, uint32_t bufferId) { sp<IMemory> hidlMemory = mapMemory(base); - ALOGE_IF(hidlMemory == nullptr, "mapMemory returns nullptr"); // allow mapMemory to return nullptr mSharedBufferMap[bufferId] = hidlMemory; diff --git a/drm/1.0/default/android.hardware.drm@1.0-service-lazy.rc b/drm/1.0/default/android.hardware.drm@1.0-service-lazy.rc new file mode 100644 index 000000000..4b32f7f1f --- /dev/null +++ b/drm/1.0/default/android.hardware.drm@1.0-service-lazy.rc @@ -0,0 +1,10 @@ +service vendor.drm-hal-1-0 /vendor/bin/hw/android.hardware.drm@1.0-service-lazy + interface android.hardware.drm@1.0::ICryptoFactory default + interface android.hardware.drm@1.0::IDrmFactory default + oneshot + disabled + class hal + user media + group mediadrm drmrpc + ioprio rt 4 + writepid /dev/cpuset/foreground/tasks diff --git a/drm/1.0/default/android.hardware.drm@1.0-service.rc b/drm/1.0/default/android.hardware.drm@1.0-service.rc index a3457b523..790ededbe 100644 --- a/drm/1.0/default/android.hardware.drm@1.0-service.rc +++ b/drm/1.0/default/android.hardware.drm@1.0-service.rc @@ -1,4 +1,6 @@ service vendor.drm-hal-1-0 /vendor/bin/hw/android.hardware.drm@1.0-service + interface android.hardware.drm@1.0::ICryptoFactory default + interface android.hardware.drm@1.0::IDrmFactory default class hal user media group mediadrm drmrpc diff --git a/drm/1.0/default/common_default_service.mk b/drm/1.0/default/common_default_service.mk new file mode 100644 index 000000000..28db567a9 --- /dev/null +++ b/drm/1.0/default/common_default_service.mk @@ -0,0 +1,45 @@ +# +# Copyright (C) 2019 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include $(CLEAR_VARS) +LOCAL_PROPRIETARY_MODULE := true +LOCAL_MODULE_RELATIVE_PATH := hw + +LOCAL_SHARED_LIBRARIES := \ + android.hardware.drm@1.0 \ + android.hidl.memory@1.0 \ + libhidlbase \ + libhidltransport \ + libhardware \ + liblog \ + libutils \ + libbinder \ + +LOCAL_STATIC_LIBRARIES := \ + android.hardware.drm@1.0-helper \ + +LOCAL_C_INCLUDES := \ + hardware/interfaces/drm + +LOCAL_HEADER_LIBRARIES := \ + media_plugin_headers + +# TODO(b/18948909) Some legacy DRM plugins only support 32-bit. They need to be +# migrated to 64-bit. Once all of a device's legacy DRM plugins support 64-bit, +# that device can turn on TARGET_ENABLE_MEDIADRM_64 to build this service as +# 64-bit. +ifneq ($(TARGET_ENABLE_MEDIADRM_64), true) +LOCAL_32_BIT_ONLY := true +endif diff --git a/drm/1.0/default/service.cpp b/drm/1.0/default/service.cpp index 1a44ce225..98d2c3b54 100644 --- a/drm/1.0/default/service.cpp +++ b/drm/1.0/default/service.cpp @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#define LOG_TAG "android.hardware.drm@1.0-service" #include <1.0/default/CryptoFactory.h> #include <1.0/default/DrmFactory.h> @@ -31,15 +30,8 @@ using android::hardware::drm::V1_0::ICryptoFactory; using android::hardware::drm::V1_0::IDrmFactory; int main() { - ALOGD("android.hardware.drm@1.0-service starting..."); - - // The DRM HAL may communicate to other vendor components via - // /dev/vndbinder - android::ProcessState::initWithDriver("/dev/vndbinder"); - configureRpcThreadpool(8, true /* callerWillJoin */); - android::status_t status = - registerPassthroughServiceImplementation<IDrmFactory>(); + android::status_t status = registerPassthroughServiceImplementation<IDrmFactory>(); LOG_ALWAYS_FATAL_IF( status != android::OK, "Error while registering drm service: %d", status); diff --git a/drm/1.0/default/serviceLazy.cpp b/drm/1.0/default/serviceLazy.cpp new file mode 100644 index 000000000..e5068b569 --- /dev/null +++ b/drm/1.0/default/serviceLazy.cpp @@ -0,0 +1,40 @@ +/* + * Copyright 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <1.0/default/CryptoFactory.h> +#include <1.0/default/DrmFactory.h> + +#include <hidl/HidlTransportSupport.h> +#include <hidl/LegacySupport.h> + +#include <binder/ProcessState.h> + +using android::hardware::configureRpcThreadpool; +using android::hardware::joinRpcThreadpool; +using android::hardware::registerLazyPassthroughServiceImplementation; + +using android::hardware::drm::V1_0::ICryptoFactory; +using android::hardware::drm::V1_0::IDrmFactory; + +int main() { + configureRpcThreadpool(8, true /* callerWillJoin */); + android::status_t status = registerLazyPassthroughServiceImplementation<IDrmFactory>(); + LOG_ALWAYS_FATAL_IF(status != android::OK, "Error while registering drm service: %d", status); + status = registerLazyPassthroughServiceImplementation<ICryptoFactory>(); + LOG_ALWAYS_FATAL_IF(status != android::OK, "Error while registering crypto service: %d", + status); + joinRpcThreadpool(); +} diff --git a/drm/1.0/vts/functional/Android.bp b/drm/1.0/vts/functional/Android.bp index 57678fb46..d6ebfdddf 100644 --- a/drm/1.0/vts/functional/Android.bp +++ b/drm/1.0/vts/functional/Android.bp @@ -32,4 +32,5 @@ cc_test { "libssl", "libcrypto", ], + test_suites: ["general-tests"], } diff --git a/drm/1.0/vts/functional/drm_hal_vendor_test.cpp b/drm/1.0/vts/functional/drm_hal_vendor_test.cpp index d03b2af37..20a2ca4af 100644 --- a/drm/1.0/vts/functional/drm_hal_vendor_test.cpp +++ b/drm/1.0/vts/functional/drm_hal_vendor_test.cpp @@ -177,7 +177,7 @@ class DrmHalVendorFactoryTest : public testing::TestWithParam<std::string> { TEST_P(DrmHalVendorFactoryTest, ValidateConfigurations) { const char* kVendorStr = "Vendor module "; size_t count = 0; - for (auto config : contentConfigurations) { + for (const auto& config : contentConfigurations) { ASSERT_TRUE(config.name.size() > 0) << kVendorStr << "has no name"; ASSERT_TRUE(config.serverUrl.size() > 0) << kVendorStr << "has no serverUrl"; @@ -186,7 +186,7 @@ TEST_P(DrmHalVendorFactoryTest, ValidateConfigurations) { 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) { + for (const auto& key : config.keys) { ASSERT_TRUE(key.keyId.size() > 0) << kVendorStr << " has zero length keyId"; ASSERT_TRUE(key.keyId.size() > 0) << kVendorStr @@ -245,7 +245,7 @@ TEST_P(DrmHalVendorFactoryTest, InvalidContentTypeNotSupported) { */ TEST_P(DrmHalVendorFactoryTest, ValidContentTypeSupported) { RETURN_IF_SKIPPED; - for (auto config : contentConfigurations) { + for (const auto& config : contentConfigurations) { EXPECT_TRUE(drmFactory->isContentTypeSupported(config.mimeType)); } } @@ -610,7 +610,7 @@ TEST_P(DrmHalVendorPluginTest, RemoveKeysNewSession) { */ TEST_P(DrmHalVendorPluginTest, RestoreKeys) { RETURN_IF_SKIPPED; - for (auto config : contentConfigurations) { + for (const auto& config : contentConfigurations) { if (config.policy.allowOffline) { auto sessionId = openSession(); hidl_vec<uint8_t> keySetId = @@ -645,7 +645,7 @@ TEST_P(DrmHalVendorPluginTest, RestoreKeysNull) { */ TEST_P(DrmHalVendorPluginTest, RestoreKeysClosedSession) { RETURN_IF_SKIPPED; - for (auto config : contentConfigurations) { + for (const auto& config : contentConfigurations) { if (config.policy.allowOffline) { auto sessionId = openSession(); hidl_vec<uint8_t> keySetId = @@ -1022,8 +1022,8 @@ TEST_P(DrmHalVendorPluginTest, RequiresSecureDecoderInvalidMimeType) { */ TEST_P(DrmHalVendorPluginTest, RequiresSecureDecoderConfig) { RETURN_IF_SKIPPED; - for (auto config : contentConfigurations) { - for (auto key : config.keys) { + for (const auto& config : contentConfigurations) { + for (const auto& key : config.keys) { if (key.isSecure) { EXPECT_TRUE(cryptoPlugin->requiresSecureDecoderComponent(config.mimeType)); break; @@ -1471,7 +1471,7 @@ TEST_P(DrmHalVendorDecryptTest, QueryKeyStatusWithNoKeys) { */ TEST_P(DrmHalVendorDecryptTest, QueryKeyStatus) { RETURN_IF_SKIPPED; - for (auto config : contentConfigurations) { + for (const auto& config : contentConfigurations) { auto sessionId = openSession(); loadKeys(sessionId, config); auto keyStatus = queryKeyStatus(sessionId); @@ -1485,8 +1485,8 @@ TEST_P(DrmHalVendorDecryptTest, QueryKeyStatus) { */ TEST_P(DrmHalVendorDecryptTest, ClearSegmentTest) { RETURN_IF_SKIPPED; - for (auto config : contentConfigurations) { - for (auto key : config.keys) { + 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}; @@ -1513,8 +1513,8 @@ TEST_P(DrmHalVendorDecryptTest, ClearSegmentTest) { */ TEST_P(DrmHalVendorDecryptTest, EncryptedAesCtrSegmentTest) { RETURN_IF_SKIPPED; - for (auto config : contentConfigurations) { - for (auto key : config.keys) { + 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}; @@ -1540,8 +1540,8 @@ TEST_P(DrmHalVendorDecryptTest, EncryptedAesCtrSegmentTest) { */ TEST_P(DrmHalVendorDecryptTest, EncryptedAesCtrSegmentTestNoKeys) { RETURN_IF_SKIPPED; - for (auto config : contentConfigurations) { - for (auto key : config.keys) { + 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, @@ -1567,8 +1567,8 @@ TEST_P(DrmHalVendorDecryptTest, EncryptedAesCtrSegmentTestNoKeys) { */ TEST_P(DrmHalVendorDecryptTest, AttemptDecryptWithKeysRemoved) { RETURN_IF_SKIPPED; - for (auto config : contentConfigurations) { - for (auto key : config.keys) { + 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, diff --git a/drm/1.1/Android.bp b/drm/1.1/Android.bp index dba3e427a..739b47078 100644 --- a/drm/1.1/Android.bp +++ b/drm/1.1/Android.bp @@ -16,13 +16,6 @@ hidl_interface { "android.hardware.drm@1.0", "android.hidl.base@1.0", ], - types: [ - "DrmMetricGroup", - "HdcpLevel", - "KeyRequestType", - "SecureStopRelease", - "SecurityLevel", - ], gen_java: false, } diff --git a/drm/1.1/README.md b/drm/1.1/README.md new file mode 100644 index 000000000..e88e6abce --- /dev/null +++ b/drm/1.1/README.md @@ -0,0 +1,84 @@ +# VINTF Device Manifest + +In Android Pie, an `<fqname>` tag was introduced to be able to express multiple +different versions of the same HAL in VINTF manifests (for DRM) +in device manifest. For devices launching with previous versions of Android and +upgrading to Android Pie, the device manifest must not use `<fqname>` to +satisfy requirements for non-optional HALs, because older version of `libvintf` +do not recognize it, causing errors during OTA update. + +Assuming that the HAL provides `@1.0::I*/default`, +`@1.1::I*/clearkey` and `@1.1::I*/foo` instances: + +## Devices upgrading to Android Pie + +### `target-level=1` or `target-level=2` + +FCM (framework compatibility matrix) version 2 (released in Android Oreo MR1) +requires DRM 1.0. If the new device manifest has Target FCM Version (i.e. +`target-level`) 1 or 2, it should use the following snippet: + +```xml +<hal format="hidl"> + <name>android.hardware.drm</name> + <transport>hwbinder</transport> + <version>1.0</version> + <interface> + <name>ICryptoFactory</name> + <instance>default</instance> + </interface> + <interface> + <name>IDrmFactory</name> + <instance>default</instance> + </interface> + <fqname>@1.1::ICryptoFactory/clearkey</fqname> + <fqname>@1.1::IDrmFactory/clearkey</fqname> + <fqname>@1.1::ICryptoFactory/foo</fqname> + <fqname>@1.1::IDrmFactory/foo</fqname> +</hal> +``` + +### `target-level=3` + +FCM (framework compatibility matrix) version 3 (released in Android Pie) +requires DRM 1.1. If the new device manifest has Target FCM Version (i.e. +`target-level`) 3, it should use the following snippet: + + +```xml +<hal format="hidl"> + <name>android.hardware.drm</name> + <transport>hwbinder</transport> + <version>1.1</version> + <interface> + <name>ICryptoFactory</name> + <instance>clearkey</instance> + <instance>foo</instance> + </interface> + <interface> + <name>IDrmFactory</name> + <instance>clearkey</instance> + <instance>foo</instance> + </interface> + <fqname>@1.0::ICryptoFactory/default</fqname> + <fqname>@1.0::IDrmFactory/default</fqname> +</hal> +``` + +## Devices launching with Android Pie +If you have a new device launched with Android Pie (no OTA), both of the +aforementioned snippets can be used. Besides, it is recommended to use the +new, clearer format: + +```xml +<hal format="hidl"> + <name>android.hardware.drm</name> + <transport>hwbinder</transport> + <fqname>@1.0::ICryptoFactory/default</fqname> + <fqname>@1.0::IDrmFactory/default</fqname> + <fqname>@1.1::ICryptoFactory/clearkey</fqname> + <fqname>@1.1::IDrmFactory/clearkey</fqname> + <fqname>@1.1::ICryptoFactory/foo</fqname> + <fqname>@1.1::IDrmFactory/foo</fqname> +</hal> +``` diff --git a/drm/1.1/vts/functional/Android.bp b/drm/1.1/vts/functional/Android.bp index 782f2833c..1090b6900 100644 --- a/drm/1.1/vts/functional/Android.bp +++ b/drm/1.1/vts/functional/Android.bp @@ -31,4 +31,5 @@ cc_test { "libssl", "libcrypto", ], + test_suites: ["general-tests"], } diff --git a/drm/1.1/vts/functional/drm_hal_clearkey_test.cpp b/drm/1.1/vts/functional/drm_hal_clearkey_test.cpp index 124661696..6be30d34d 100644 --- a/drm/1.1/vts/functional/drm_hal_clearkey_test.cpp +++ b/drm/1.1/vts/functional/drm_hal_clearkey_test.cpp @@ -24,7 +24,7 @@ #include <android/hardware/drm/1.0/types.h> #include <android/hardware/drm/1.1/types.h> #include <android/hidl/allocator/1.0/IAllocator.h> -#include <android/hidl/manager/1.0/IServiceManager.h> +#include <android/hidl/manager/1.2/IServiceManager.h> #include <gtest/gtest.h> #include <hidl/HidlSupport.h> #include <hidl/ServiceManagement.h> @@ -129,9 +129,9 @@ public: ALOGD("DrmHalClearkeyTest: Running test %s.%s", test_info->test_case_name(), test_info->name()); - auto manager = android::hardware::defaultServiceManager(); + auto manager = android::hardware::defaultServiceManager1_2(); ASSERT_NE(nullptr, manager.get()); - manager->listByInterface(IDrmFactory::descriptor, + manager->listManifestByInterface(IDrmFactory::descriptor, [&](const hidl_vec<hidl_string> ®istered) { for (const auto &instance : registered) { sp<IDrmFactory> drmFactory = @@ -144,7 +144,7 @@ public: } ); - manager->listByInterface(ICryptoFactory::descriptor, + manager->listManifestByInterface(ICryptoFactory::descriptor, [&](const hidl_vec<hidl_string> ®istered) { for (const auto &instance : registered) { sp<ICryptoFactory> cryptoFactory = @@ -228,13 +228,13 @@ protected: const std::string& componentName, const VT& componentValue) { bool validAttribute = false; bool validComponent = false; - for (DrmMetricGroup::Attribute attribute : metric.attributes) { + for (const DrmMetricGroup::Attribute& attribute : metric.attributes) { if (attribute.name == attributeName && ValueEquals(attribute.type, attributeValue, attribute)) { validAttribute = true; } } - for (DrmMetricGroup::Value value : metric.values) { + for (const DrmMetricGroup::Value& value : metric.values) { if (value.componentName == componentName && ValueEquals(value.type, componentValue, value)) { validComponent = true; diff --git a/drm/1.2/Android.bp b/drm/1.2/Android.bp new file mode 100644 index 000000000..2d54302a5 --- /dev/null +++ b/drm/1.2/Android.bp @@ -0,0 +1,24 @@ +// This file is autogenerated by hidl-gen -Landroidbp. + +hidl_interface { + name: "android.hardware.drm@1.2", + root: "android.hardware", + vndk: { + enabled: true, + }, + srcs: [ + "types.hal", + "ICryptoFactory.hal", + "ICryptoPlugin.hal", + "IDrmFactory.hal", + "IDrmPlugin.hal", + "IDrmPluginListener.hal", + ], + interfaces: [ + "android.hardware.drm@1.0", + "android.hardware.drm@1.1", + "android.hidl.base@1.0", + ], + gen_java: false, +} + diff --git a/drm/1.2/ICryptoFactory.hal b/drm/1.2/ICryptoFactory.hal new file mode 100644 index 000000000..c4a9b4b31 --- /dev/null +++ b/drm/1.2/ICryptoFactory.hal @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.hardware.drm@1.2; + +import @1.1::ICryptoFactory; + +/** + * ICryptoFactory is the main entry point for interacting with a vendor's + * crypto HAL to create crypto plugins. Crypto plugins create crypto sessions + * which are used by a codec to decrypt protected video content. + * + * The 1.2 factory must always create 1.2 ICryptoPlugin interfaces, which are + * returned via the 1.0 createPlugin method. + * + * To use 1.2 features the caller must cast the returned interface to a + * 1.2 HAL, using V1_2::IDrmPlugin::castFrom(). + * + * The ICryptoFactory hal is required because all top-level interfaces + * have to be updated in a minor uprev. + */ +interface ICryptoFactory extends @1.1::ICryptoFactory { +}; diff --git a/drm/1.2/ICryptoPlugin.hal b/drm/1.2/ICryptoPlugin.hal new file mode 100644 index 000000000..07006768c --- /dev/null +++ b/drm/1.2/ICryptoPlugin.hal @@ -0,0 +1,84 @@ +/** + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.hardware.drm@1.2; + +import @1.0::DestinationBuffer; +import @1.0::ICryptoPlugin; +import @1.0::Mode; +import @1.0::Pattern; +import @1.0::SessionId; +import @1.0::SharedBuffer; +import @1.0::SubSample; + +/** + * ICryptoPlugin is the HAL for vendor-provided crypto plugins. + * It allows crypto sessions to be opened and operated on, to + * load crypto keys for a codec to decrypt protected video content. + */ +interface ICryptoPlugin extends @1.0::ICryptoPlugin { + + /** + * Decrypt an array of subsamples from the source memory buffer to the + * destination memory buffer. + * + * decrypt_1_2() only differs from decrypt() in that additional status + * codes must be returned. + * + * @param secure a flag to indicate if a secure decoder is being used. This + * enables the plugin to configure buffer modes to work consistently with + * a secure decoder. + * @param the keyId for the key that is used to do the the decryption. The + * keyId refers to a key in the associated MediaDrm instance. + * @param iv the initialization vector to use + * @param mode the crypto mode to use + * @param pattern the crypto pattern to use + * @param subSamples a vector of subsamples indicating the number + * of clear and encrypted bytes to process. This allows the decrypt + * call to operate on a range of subsamples in a single call + * @param source the input buffer for the decryption + * @param offset the offset of the first byte of encrypted data from + * the base of the source buffer + * @param destination the output buffer for the decryption + * @return status the status of the call. The status must be OK or one + * of the following errors: + * ERROR_DRM_NO_LICENSE if no license keys have been loaded + * ERROR_DRM_LICENSE_EXPIRED if the license keys have expired + * ERROR_DRM_RESOURCE_BUSY if the resources required to perform + * the decryption are not available + * ERROR_DRM_INSUFFICIENT_OUTPUT_PROTECTION if required output + * protections are not active + * ERROR_DRM_INSUFFICIENT_SECURITY if the security level of the + * device is not sufficient to meet the requirements in + * the license policy + * ERROR_DRM_FRAME_TOO_LARGE if the frame being decrypted into + * the secure output buffer exceeds the size of the buffer + * ERROR_DRM_SESSION_NOT_OPENED if the decrypt session is not + * opened + * ERROR_DRM_DECRYPT if the decrypt operation fails + * ERROR_DRM_INVALID_STATE if the device is in a state where it + * is not able to perform decryption + * ERROR_DRM_CANNOT_HANDLE in other failure cases. + * + * @return bytesWritten the number of bytes output from the decryption + * @return detailedError if the error is a vendor-specific error, the + * vendor's crypto HAL may provide a detailed error string to help + * describe the error. + */ + decrypt_1_2(bool secure, uint8_t[16] keyId, uint8_t[16] iv, Mode mode, + Pattern pattern, vec<SubSample> subSamples, + SharedBuffer source, uint64_t offset, DestinationBuffer destination) + generates(Status status, uint32_t bytesWritten, string detailedError); +}; diff --git a/drm/1.2/IDrmFactory.hal b/drm/1.2/IDrmFactory.hal new file mode 100644 index 000000000..682889c22 --- /dev/null +++ b/drm/1.2/IDrmFactory.hal @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.hardware.drm@1.2; + +import @1.1::IDrmFactory; +import @1.1::IDrmPlugin; +import @1.1::SecurityLevel; + +/** + * IDrmFactory is the main entry point for interacting with a vendor's + * drm HAL to create drm plugin instances. A drm plugin instance + * creates drm sessions which are used to obtain keys for a crypto + * session so it can decrypt protected video content. + * + * The 1.2 factory must always create 1.2 IDrmPlugin interfaces, which are + * returned via the 1.0 createPlugin method. + * + * To use 1.2 features the caller must cast the returned interface to a + * 1.2 HAL, using V1_2::IDrmPlugin::castFrom(). + * + * The IDrmFactory hal is required because all top-level interfaces + * have to be updated in a minor uprev. + */ + +interface IDrmFactory extends @1.1::IDrmFactory { + /** + * Determine if a specific security level is supported by the device. + * This method only differs from @1.0 isCryptoSchemeSupported + * by the addition of a security level. + * + * @param uuid identifies the crypto scheme in question + * @param mimeType identifies the mime type in question + * @param securityLevel specifies the security level required + * @return isSupported must be true only if the scheme is supported + */ + isCryptoSchemeSupported_1_2(uint8_t[16] uuid, string mimeType, + @1.1::SecurityLevel securityLevel) generates(bool isSupported); +}; diff --git a/drm/1.2/IDrmPlugin.hal b/drm/1.2/IDrmPlugin.hal new file mode 100644 index 000000000..df09ccf91 --- /dev/null +++ b/drm/1.2/IDrmPlugin.hal @@ -0,0 +1,247 @@ +/** + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.hardware.drm@1.2; + +import @1.0::KeyedVector; +import @1.0::KeyType; +import @1.0::SessionId; +import @1.0::Status; +import @1.1::IDrmPlugin; +import @1.1::KeyRequestType; +import @1.2::IDrmPluginListener; + +/** + * IDrmPlugin is used to interact with a specific drm plugin that was + * created by IDrm::createPlugin. A drm plugin provides methods for + * obtaining drm keys to be used by a codec to decrypt protected video + * content. + */ +interface IDrmPlugin extends @1.1::IDrmPlugin { + + /** + * The keys in an offline license allow protected content to be + * played even if the device is not connected to a network. + * Offline licenses are stored on the device after a key + * request/response exchange when the key request KeyType is + * OFFLINE. Normally each app is responsible for keeping track of + * the KeySetIds it has created. In some situations however, it + * will be necessary to request the list of stored offline license + * KeySetIds. If an app loses the KeySetId for any stored licenses + * that it created, for example, it must be able to recover the + * stored KeySetIds so those licenses will be removed when they + * expire or when the app is uninstalled. + * <p> + * This method returns a list of the KeySetIds for all offline + * licenses. The offline license KeySetId allows an app to query + * the status of an offline license or remove it. + * + * @return status the status of the call. Must be OK or + * ERROR_DRM_INVALID_STATE if the HAL is in a state where the + * KeySetIds can't be returned. + * @return a list of offline license keySetIds. If there are no offline + * licenses, the list must be empty and OK must be returned as the + * status. + */ + getOfflineLicenseKeySetIds() generates (@1.0::Status status, + vec<KeySetId> keySetIds); + + /** + * Normally offline licenses are released using a key + * request/response exchange using getKeyRequest where the KeyType + * is RELEASE, followed by provideKeyResponse. This allows the + * server to cryptographically confirm that the license has been + * removed and then adjust the count of offline licenses allocated + * to the device. + * <p> + * In some exceptional situations it will be necessary to directly + * remove offline licenses without notifying the server, which is + * performed by this method. + * + * @param keySetId the id of the offline license to remove + * @return status the status of the call. Must be one of OK on + * success, BAD_VALUE if the license is not found or + * ERROR_DRM_INVALID_STATE if the HAL is in a state where the + * KeySetIds can't be removed. + */ + removeOfflineLicense(KeySetId keySetId) generates (@1.0::Status status); + + /** + * Request the state of an offline license. An offline license must + * be usable or inactive. The keys in a usable offline license are + * available for decryption. When the offline license state is + * inactive, the keys have been marked for release using + * getKeyRequest with KeyType RELEASE but the key response has not + * been received. The keys in an inactive offline license are not + * usable for decryption. + * + * @param keySetId the id of the offline license + * @return status the status of the call. Must be one of OK on + * success, BAD_VALUE if the license is not found or + * ERROR_DRM_INVALID_STATE if the HAL is in a state where the + * offline license state can't be queried. + * @return the offline license state, one of USABLE or INACTIVE. + * If the return status is not OK then state must be set to + * UNKNOWN. + */ + getOfflineLicenseState(KeySetId keySetId) generates ( + @1.0::Status status, OfflineLicenseState state); + + /** + * A key request/response exchange occurs between the app and a License + * Server to obtain the keys required to decrypt the content. + * getKeyRequest_1_2() is used to obtain an opaque key request blob that is + * delivered to the license server. + * + * getKeyRequest_1_2() only differs from getKeyRequest_1_1() in that + * additional status codes must be returned. + * + * @param scope either a sessionId or a keySetId, depending on the + * specified keyType. When the keyType is OFFLINE or STREAMING, scope + * must be set to the sessionId the keys will be provided to. When the + * keyType is RELEASE, scope must be set to the keySetId of the keys + * being released. + * @param initData container-specific data, its meaning is interpreted + * based on the mime type provided in the mimeType parameter. It could + * contain, for example, the content ID, key ID or other data obtained + * from the content metadata that is required to generate the key + * request. initData must be empty when keyType is RELEASE. + * @param mimeType identifies the mime type of the content + * @param keyType specifies if the keys are to be used for streaming, + * offline or a release + * @param optionalParameters included in the key request message to + * allow a client application to provide additional message parameters + * to the server. + * @return status the status of the call. The status must be OK or one of + * the following errors: ERROR_DRM_SESSION_NOT_OPENED if the session is + * not opened, ERROR_DRM_NOT_PROVISIONED if the device requires + * provisioning before it is able to generate a key request, + * ERROR_DRM_RESOURCE_CONTENTION if client applications using the hal + * are temporarily exceeding the available crypto resources such that a + * retry of the operation is likely to succeed, ERROR_DRM_CANNOT_HANDLE + * if getKeyRequest is not supported at the time of the call, BAD_VALUE + * if any parameters are invalid or ERROR_DRM_INVALID_STATE if the HAL + * is in a state where a key request cannot be generated. + * @return request if successful, the opaque key request blob is returned + * @return requestType indicates type information about the returned + * request. The type must be one of INITIAL, RENEWAL, RELEASE, NONE or + * UPDATE. An INITIAL request is the first key request for a + * license. RENEWAL is a subsequent key request used to refresh the + * keys in a license. RELEASE corresponds to a keyType of RELEASE, + * which indicates keys are being released. NONE indicates that no + * request is needed because the keys are already loaded. UPDATE + * indicates that the keys need to be refetched after the initial + * license request. + * @return defaultUrl the URL that the request may be sent to, if + * provided by the drm HAL. The app can choose to override this URL. + */ + getKeyRequest_1_2(vec<uint8_t> scope, vec<uint8_t> initData, + string mimeType, KeyType keyType, KeyedVector optionalParameters) + generates (Status status, vec<uint8_t> request, + KeyRequestType requestType, string defaultUrl); + + /** + * A provision request/response exchange occurs between the app and a + * provisioning server to retrieve a device certificate. getProvisionRequest + * is used to obtain an opaque provisioning request blob that is delivered + * to the provisioning server. + * + * getProvisionRequest_1_2() only differs from getProvisionRequest_1_0() in + * that additional status codes must be returned. + * + * @param certificateType the type of certificate requested, e.g. "X.509" + * @param certificateAuthority identifies the certificate authority. A + * certificate authority (CA) is an entity which issues digital + * certificates for use by other parties. It is an example of a trusted + * third party. + * @return status the status of the call. The status must be OK or one of + * the following errors: ERROR_DRM_RESOURCE_CONTENTION if client + * applications using the hal are temporarily exceeding the available + * crypto resources such that a retry of the operation is likely to + * succeed, ERROR_DRM_CANNOT_HANDLE if the drm scheme does not require + * provisioning or ERROR_DRM_INVALID_STATE if the HAL is in a state + * where the provision request cannot be generated. + * @return request if successful the opaque certificate request blob + * is returned + * @return defaultUrl URL that the provisioning request may be + * sent to, if known by the HAL implementation. An app can choose to + * override this URL. If the HAL implementation does not provide a + * defaultUrl, the returned string must be empty. + */ + getProvisionRequest_1_2(string certificateType, string certificateAuthority) + generates (Status status, vec<uint8_t> request, string defaultUrl); + + /** + * Return the currently negotiated and max supported HDCP levels. + * + * This method only differs from @1.1 version by the addition of + * support for HDCP 2.3. + * + * The current level is based on the display(s) the device is connected to. + * If multiple HDCP-capable displays are simultaneously connected to + * separate interfaces, this method returns the lowest negotiated HDCP level + * of all interfaces. + * + * The maximum HDCP level is the highest level that can potentially be + * negotiated. It is a constant for any device, i.e. it does not depend on + * downstream receiving devices that could be connected. For example, if + * the device has HDCP 1.x keys and is capable of negotiating HDCP 1.x, but + * does not have HDCP 2.x keys, then the maximum HDCP capability would be + * reported as 1.x. If multiple HDCP-capable interfaces are present, it + * indicates the highest of the maximum HDCP levels of all interfaces. + * + * This method should only be used for informational purposes, not for + * enforcing compliance with HDCP requirements. Trusted enforcement of HDCP + * policies must be handled by the DRM system. + * + * @return status the status of the call. The status must be OK or + * ERROR_DRM_INVALID_STATE if the HAL is in a state where the HDCP + * level cannot be queried. + * @return connectedLevel the lowest HDCP level for any connected + * displays + * @return maxLevel the highest HDCP level that can be supported + * by the device + */ + getHdcpLevels_1_2() generates (Status status, HdcpLevel connectedLevel, + HdcpLevel maxLevel); + + /** + * Send a session lost state event to the listener. This event + * indicates that a session's state has become invalid because the + * device crypto hardware is incapable of retaining crypto session + * state across suspend and resume cycles. + * + * @param sessionId identifies the session the event originated from + */ + sendSessionLostState(SessionId sessionId); + + /** + * Send a keys change event to the listener. The keys change event + * indicates the status of each key in the session. Keys can be + * indicated as being usable, expired, outputnotallowed or statuspending. + * + * This method only differs from @1.0 version by the addition of new + * KeyStatusType(s) in keyStatusList. + * + * @param sessionId identifies the session the event originated from + * @param keyStatusList indicates the status for each key ID in the + * session. + * @param hasNewUsableKey indicates if the event includes at least one + * key that has become usable. + */ + sendKeysChange_1_2(SessionId sessionId, vec<KeyStatus> keyStatusList, + bool hasNewUsableKey); + +}; diff --git a/drm/1.2/IDrmPluginListener.hal b/drm/1.2/IDrmPluginListener.hal new file mode 100644 index 000000000..e8cb91a9c --- /dev/null +++ b/drm/1.2/IDrmPluginListener.hal @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.drm@1.2; + +import @1.0::IDrmPluginListener; +import @1.0::SessionId; + +/** + * IDrmPluginListener is a listener interface for Drm events sent from an + * IDrmPlugin instance. + */ +interface IDrmPluginListener extends @1.0::IDrmPluginListener { + /** + * Some device crypto hardware is incapable of retaining crypto + * session state across suspend and resume cycles. A + * SessionLostState event must be signaled when a session has + * become invalid for this reason. This event must not be used to + * indicate a failure in the crypto system. Closing the session + * and opening a new one must allow the application to resume + * normal use of the drm hal module. + * + * @param sessionId identifies the session that has been invalidated + */ + oneway sendSessionLostState(SessionId sessionId); + + /** + * Send a keys change event to the listener. The keys change event + * indicates the status of each key in the session. Keys can be + * indicated as being usable, expired, outputnotallowed or statuspending. + * + * This method only differs from @1.0 version by the addition of new + * KeyStatusType(s) in keyStatusList. + * + * @param sessionId identifies the session the event originated from + * @param keyStatusList indicates the status for each key ID in the + * session. + * @param hasNewUsableKey indicates if the event includes at least one + * key that has become usable. + */ + oneway sendKeysChange_1_2(SessionId sessionId, vec<KeyStatus> keyStatusList, + bool hasNewUsableKey); + +}; diff --git a/drm/1.2/types.hal b/drm/1.2/types.hal new file mode 100644 index 000000000..87218a420 --- /dev/null +++ b/drm/1.2/types.hal @@ -0,0 +1,118 @@ +/** + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.drm@1.2; + +import @1.0::KeyStatusType; +import @1.0::Status; +import @1.1::HdcpLevel; + +enum OfflineLicenseState : uint32_t { + /** + * Offline license state is unknown + */ + UNKNOWN, + + /** + * Offline license state is usable, the keys are usable for decryption. + */ + USABLE, + + /** + * Offline license state is inactive, the keys have been marked for + * release using {@link #getKeyRequest} with KEY_TYPE_RELEASE but the + * key response has not been received. + */ + INACTIVE +}; + +enum Status : @1.0::Status { + /** + * The drm HAL module must return ERROR_DRM_INSUFFICIENT_SECURITY + * from the crypto plugin decrypt method when the security level + * of the device is not sufficient to meet the requirements in the + * license policy. + */ + ERROR_DRM_INSUFFICIENT_SECURITY, + + /** + * The drm HAL module must return ERROR_FRAME_TOO_LARGE from the + * decrypt method when the frame being decrypted into the secure + * output buffer exceeds the size of the buffer. + */ + ERROR_DRM_FRAME_TOO_LARGE, + + /** + * This error must be returned from any session method when an + * attempt is made to use the session after the crypto hardware + * state has been invalidated. Some devices are not able to + * retain crypto session state across device suspend/resume which + * results in invalid session state. + */ + ERROR_DRM_SESSION_LOST_STATE, + + /** + * The drm HAL module must return this error if client + * applications using the hal are temporarily exceeding the + * capacity of available crypto resources such that a retry of + * the operation is likely to succeed. + */ + ERROR_DRM_RESOURCE_CONTENTION, +}; + +/** + * HDCP specifications are defined by Digital Content Protection LLC (DCP). + * "HDCP Specification Rev. 2.3 Interface Independent Adaptation" + * "HDCP 2.3 on HDMI Specification" + */ +enum HdcpLevel : @1.1::HdcpLevel { + /** + * HDCP version 2.3 Type 1. + */ + HDCP_V2_3 +}; + + +/** + * KeySetId is an identifier that references a set of keys in an + * offline license. The keySetId is created by the HAL implementation + * and returned from provideKeyResponse and getOfflineLicenseIds. The + * framework passes KeySetId back to the HAL when referring to the key + * set in methods that take a KeySetId as an input parameter. + */ +typedef vec<uint8_t> KeySetId; + +enum KeyStatusType : @1.0::KeyStatusType { + /** + * The key is not yet usable to decrypt media because the start + * time is in the future. The key must become usable when + * its start time is reached. + */ + USABLEINFUTURE +}; + +/** + * Used by sendKeysChange_1_2 to report the usability status of each key to the + * app. + * + * This struct only differs from @1.0 version by the addition of new + * KeyStatusType(s). + * + */ +struct KeyStatus { + KeySetId keyId; + KeyStatusType type; +}; 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..bfffbe8d2 --- /dev/null +++ b/drm/1.2/vts/functional/drm_hal_common.cpp @@ -0,0 +1,496 @@ +/* + * 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"; +const char *kCallbackKeysChange = "KeysChange"; + +drm_vts::VendorModules *DrmHalTest::gVendorModules = nullptr; + +/** + * DrmHalPluginListener + */ + +Return<void> DrmHalPluginListener::sendSessionLostState(const hidl_vec<uint8_t>& sessionId) { + ListenerEventArgs args; + args.sessionId = sessionId; + NotifyFromCallback(kCallbackLostState, args); + return Void(); +} + +Return<void> DrmHalPluginListener::sendKeysChange_1_2(const hidl_vec<uint8_t>& sessionId, + const hidl_vec<KeyStatus>& keyStatusList, bool hasNewUsableKey) { + ListenerEventArgs args; + args.sessionId = sessionId; + args.keyStatusList = keyStatusList; + args.hasNewUsableKey = hasNewUsableKey; + NotifyFromCallback(kCallbackKeysChange, args); + 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..e34866474 --- /dev/null +++ b/drm/1.2/vts/functional/drm_hal_common.h @@ -0,0 +1,204 @@ +/* + * 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 KeyStatusV1_0 = ::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 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; +extern const char *kCallbackKeysChange; + +struct ListenerEventArgs { + SessionId sessionId; + hidl_vec<KeyStatus> keyStatusList; + bool hasNewUsableKey; +}; + +class DrmHalPluginListener + : public ::testing::VtsHalHidlTargetCallbackBase<ListenerEventArgs>, + 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<KeyStatusV1_0>&, bool) override { return Void(); } + + virtual Return<void> sendSessionLostState(const hidl_vec<uint8_t>& sessionId) override; + + virtual Return<void> sendKeysChange_1_2(const hidl_vec<uint8_t>&, + const hidl_vec<KeyStatus>&, bool) 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..37ecc2543 --- /dev/null +++ b/drm/1.2/vts/functional/drm_hal_test.cpp @@ -0,0 +1,591 @@ +/* + * 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_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::KeyStatus; +using ::android::hardware::drm::V1_2::KeyStatusType; +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::drm::V1_2::vts::kCallbackKeysChange; + +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 kDrmErrorInvalidState = "invalidState"; +static const char* const kDrmErrorResourceContention = "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 drm plugin offline key support + */ +TEST_P(DrmHalTest, OfflineLicenseTest) { + auto sessionId = openSession(); + hidl_vec<uint8_t> keySetId = loadKeys(sessionId, KeyType::OFFLINE); + + auto res = drmPlugin->getOfflineLicenseKeySetIds( + [&](Status status, const hidl_vec<KeySetId>& keySetIds) { + bool found = false; + EXPECT_EQ(Status::OK, status); + for (KeySetId keySetId2: keySetIds) { + if (keySetId == keySetId2) { + found = true; + break; + } + } + EXPECT_TRUE(found) << "keySetId not found"; + }); + EXPECT_OK(res); + + Status err = drmPlugin->removeOfflineLicense(keySetId); + EXPECT_EQ(Status::OK, err); + + res = drmPlugin->getOfflineLicenseKeySetIds( + [&](Status status, const hidl_vec<KeySetId>& keySetIds) { + EXPECT_EQ(Status::OK, status); + for (KeySetId keySetId2: keySetIds) { + EXPECT_NE(keySetId, keySetId2); + } + }); + EXPECT_OK(res); + + err = drmPlugin->removeOfflineLicense(keySetId); + EXPECT_EQ(Status::BAD_VALUE, err); + + closeSession(sessionId); +} + +/** + * Test drm 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); +} + +/** + * Simulate the plugin sending keys change and make sure + * the listener gets them. + */ +TEST_P(DrmHalTest, ListenerKeysChange) { + RETURN_IF_SKIPPED; + sp<DrmHalPluginListener> listener = new DrmHalPluginListener(); + auto res = drmPlugin->setListener(listener); + EXPECT_OK(res); + + auto sessionId = openSession(); + const hidl_vec<KeyStatus> keyStatusList = { + {{1}, KeyStatusType::USABLE}, + {{2}, KeyStatusType::EXPIRED}, + {{3}, KeyStatusType::OUTPUTNOTALLOWED}, + {{4}, KeyStatusType::STATUSPENDING}, + {{5}, KeyStatusType::INTERNALERROR}, + {{6}, KeyStatusType::USABLEINFUTURE}, + }; + + drmPlugin->sendKeysChange_1_2(sessionId, keyStatusList, true); + auto result = listener->WaitForCallback(kCallbackKeysChange); + EXPECT_TRUE(result.no_timeout); + EXPECT_TRUE(result.args); + EXPECT_EQ(sessionId, result.args->sessionId); + EXPECT_EQ(keyStatusList, result.args->keyStatusList); + closeSession(sessionId); +} + +/** + * 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, kDrmErrorResourceContention); + 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, kDrmErrorInvalidState); + 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, kDrmErrorInvalidState); + 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->sessionId); +} + +/** + * 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 |