diff --git a/libwvdrmengine/mediadrm/test/Android.mk b/libwvdrmengine/mediadrm/test/Android.mk index 41386199..6b23d9c0 100644 --- a/libwvdrmengine/mediadrm/test/Android.mk +++ b/libwvdrmengine/mediadrm/test/Android.mk @@ -5,8 +5,11 @@ LOCAL_PATH := $(call my-dir) # include $(CLEAR_VARS) +WV_UNITTESTS_BUILD_TARGET?= +ifeq ($(WV_UNITTESTS_BUILD_TARGET), hidl) + LOCAL_SRC_FILES := \ - WVDrmPlugin_test.cpp \ + hidl/WVDrmPlugin_test.cpp \ LOCAL_C_INCLUDES := \ frameworks/av/include \ @@ -23,7 +26,7 @@ LOCAL_C_INCLUDES := \ LOCAL_STATIC_LIBRARIES := \ libcdm \ libcdm_protos \ - libcdm_utils_hidl \ + libcdm_utils \ libjsmn \ libgmock \ libgmock_main \ @@ -49,6 +52,48 @@ LOCAL_SHARED_LIBRARIES := \ libprotobuf-cpp-lite \ libutils \ +# build unit tests for Aidl +else + +LOCAL_SRC_FILES := \ + WVDrmPlugin_hal_test.cpp \ + +LOCAL_C_INCLUDES := \ + frameworks/av/include \ + frameworks/native/include \ + vendor/widevine/libwvdrmengine/cdm/core/include \ + vendor/widevine/libwvdrmengine/cdm/include \ + vendor/widevine/libwvdrmengine/cdm/metrics/include \ + vendor/widevine/libwvdrmengine/cdm/util/include \ + vendor/widevine/libwvdrmengine/aidl_include \ + vendor/widevine/libwvdrmengine/include \ + vendor/widevine/libwvdrmengine/mediadrm/aidl_include \ + vendor/widevine/libwvdrmengine/oemcrypto/include \ + +LOCAL_STATIC_LIBRARIES := \ + libcdm \ + libcdm_protos \ + libcdm_utils \ + libjsmn \ + libgmock \ + libgmock_main \ + libgtest \ + libwvlevel3 \ + libwvdrmdrmplugin_aidl \ + libwv_odk \ + +LOCAL_SHARED_LIBRARIES := \ + android.hardware.drm-V1-ndk \ + libbinder_ndk \ + libbase \ + libcrypto \ + libdl \ + liblog \ + libprotobuf-cpp-lite \ + libutils \ +# endif $(WV_UNITTESTS_BUILD_TARGET) +endif + LOCAL_HEADER_LIBRARIES := \ libstagefright_headers \ libstagefright_foundation_headers \ @@ -80,7 +125,7 @@ include $(BUILD_EXECUTABLE) include $(CLEAR_VARS) LOCAL_SRC_FILES := \ - hidl_metrics_adapter_unittest.cpp \ + hidl/hidl_metrics_adapter_unittest.cpp \ LOCAL_C_INCLUDES := \ vendor/widevine/libwvdrmengine/cdm/core/include \ diff --git a/libwvdrmengine/mediadrm/test/WVDrmPlugin_hal_test.cpp b/libwvdrmengine/mediadrm/test/WVDrmPlugin_hal_test.cpp new file mode 100644 index 00000000..185ad171 --- /dev/null +++ b/libwvdrmengine/mediadrm/test/WVDrmPlugin_hal_test.cpp @@ -0,0 +1,2890 @@ +// +// Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +// +// #define LOG_NDEBUG 0 +#define LOG_TAG "WVDrmPluginHalTest" +#include "WVDrmPlugin.h" + +#include +#include + +#include +#include +#include +#include + +#include "WVErrors.h" +#include "cdm_client_property_set.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "media/stagefright/MediaErrors.h" +#include "media/stagefright/foundation/ABase.h" +#include "string_conversions.h" +#include "wv_cdm_constants.h" +#include "wv_cdm_types.h" +#include "wv_content_decryption_module.h" + +namespace { +typedef std::vector<::aidl::android::hardware::drm::KeyValue> KeyedVector; +typedef std::vector SessionId; +} // namespace + +namespace wvdrm { +namespace hardware { +namespace drm { +namespace widevine { + +using ::aidl::android::hardware::drm::EventType; +using ::aidl::android::hardware::drm::KeyRequest; +using ::aidl::android::hardware::drm::KeyRequestType; +using ::aidl::android::hardware::drm::KeySetId; +using ::aidl::android::hardware::drm::KeyStatus; +using ::aidl::android::hardware::drm::KeyStatusType; +using ::aidl::android::hardware::drm::KeyType; +using ::aidl::android::hardware::drm::KeyValue; +using ::aidl::android::hardware::drm::OfflineLicenseState; +using ::aidl::android::hardware::drm::ProvideProvisionResponseResult; +using ::aidl::android::hardware::drm::ProvisionRequest; +using ::aidl::android::hardware::drm::SecureStop; +using ::aidl::android::hardware::drm::SecureStopId; +using ::aidl::android::hardware::drm::SecurityLevel; +using ::aidl::android::hardware::drm::Status; + +using ::testing::_; +using ::testing::AllOf; +using ::testing::Args; +using ::testing::AtLeast; +using ::testing::DefaultValue; +using ::testing::DoAll; +using ::testing::ElementsAreArray; +using ::testing::Field; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::IsEmpty; +using ::testing::Matcher; +using ::testing::NotNull; +using ::testing::Pointee; +using ::testing::SaveArg; +using ::testing::SetArgPointee; +using ::testing::SetArrayArgument; +using ::testing::StrEq; +using ::testing::StrictMock; +using ::testing::Test; +using ::testing::Values; +using ::testing::WithParamInterface; +using ::testing::internal::ElementsAreArrayMatcher; + +using wvcdm::CdmAppParameterMap; +using wvcdm::CdmCertificateType; +using wvcdm::CdmClientPropertySet; +using wvcdm::CdmIdentifier; +using wvcdm::CdmInitData; +using wvcdm::CdmKeyMessage; +using wvcdm::CdmKeyRequest; +using wvcdm::CdmKeyResponse; +using wvcdm::CdmKeySetId; +using wvcdm::CdmKeySystem; +using wvcdm::CdmLicenseType; +using wvcdm::CdmOfflineLicenseState; +using wvcdm::CdmProvisioningRequest; +using wvcdm::CdmProvisioningResponse; +using wvcdm::CdmQueryMap; +using wvcdm::CdmSecureStopId; +using wvcdm::CdmSecurityLevel; +using wvcdm::CdmUsageInfo; +using wvcdm::CdmUsageInfoReleaseMessage; +using wvcdm::EMPTY_ORIGIN; +using wvcdm::kCertificateWidevine; +using wvcdm::KEY_ID_SIZE; +using wvcdm::KEY_IV_SIZE; +using wvcdm::KEY_SET_ID_PREFIX; +using wvcdm::kKeyRequestTypeInitial; +using wvcdm::kKeyRequestTypeRelease; +using wvcdm::kKeyRequestTypeRenewal; +using wvcdm::kLicenseTypeOffline; +using wvcdm::kLicenseTypeRelease; +using wvcdm::kLicenseTypeStreaming; +using wvcdm::kSecurityLevelL1; +using wvcdm::kSecurityLevelL3; +using wvcdm::NEVER_EXPIRES; +using wvcdm::QUERY_KEY_CURRENT_SRM_VERSION; +using wvcdm::QUERY_KEY_DECRYPT_HASH_SUPPORT; +using wvcdm::QUERY_KEY_DEVICE_ID; +using wvcdm::QUERY_KEY_MAX_NUMBER_OF_SESSIONS; +using wvcdm::QUERY_KEY_NUMBER_OF_OPEN_SESSIONS; +using wvcdm::QUERY_KEY_OEMCRYPTO_API_VERSION; +using wvcdm::QUERY_KEY_OEMCRYPTO_BUILD_INFORMATION; +using wvcdm::QUERY_KEY_OEMCRYPTO_SESSION_ID; +using wvcdm::QUERY_KEY_PROVISIONING_ID; +using wvcdm::QUERY_KEY_RESOURCE_RATING_TIER; +using wvcdm::QUERY_KEY_SECURITY_LEVEL; +using wvcdm::QUERY_KEY_SRM_UPDATE_SUPPORT; +using wvcdm::QUERY_KEY_SYSTEM_ID; +using wvcdm::QUERY_KEY_WVCDM_VERSION; +using wvcdm::QUERY_VALUE_SECURITY_LEVEL_L1; +using wvcdm::QUERY_VALUE_SECURITY_LEVEL_L3; +using wvcdm::SESSION_ID_PREFIX; +using wvcdm::WvCdmEventListener; +using wvutil::Base64Encode; + +namespace { +const std::string kEmptyString; +const std::string kOrigin("widevine.com"); +const std::string kAppId("com.unittest.mock.app.id"); +const uint8_t *const kUnprovisionResponse = + reinterpret_cast("unprovision"); +const size_t kUnprovisionResponseSize = 11; +const std::string kDeviceId("0123456789\0ABCDEF", 17); + +// This is a serialized WvCdmMetrics message containing a small amount of +// sample data. This ensures we're able to extract it via a property. +const char kSerializedMetricsHex[] = + "0a580a001a0210072202100d2a02100832182216636f6d2e676f6f676c652e616e64726f69" + "642e676d734208220631342e302e304a06080112020800520610d5f3fad5056a0b1d00fd4c" + "47280132020804a2010608011202080012cb010a0622047369643412b5010a021001520919" + "1d5a643bdfff50405a0b1d00d01945280132021001620d1d00f8e84528013204080020006a" + "0310b739820102100d8a01060801120248009a010310ff01da0106080112024800e2010e1d" + "005243472801320528800248009a020d1d00b016452801320428404800a202060801120248" + "19aa0206080212024800b2020b1d8098f047280132024800ba02021001ca020b1d00101945" + "280132024800e202021004fa02021002a203021000b2030210021a09196891ed7c3f35504" + "0"; + +#define N_ELEM(a) (sizeof(a) / sizeof(a[0])) +} // anonymous namespace + +class MockCDM : public WvContentDecryptionModule { +public: + MOCK_METHOD(CdmResponseType, OpenSession, + (const CdmKeySystem &, CdmClientPropertySet *, + const CdmIdentifier &, WvCdmEventListener *, CdmSessionId *), + (override)); + + MOCK_METHOD(CdmResponseType, CloseSession, (const CdmSessionId &), + (override)); + + MOCK_METHOD(CdmResponseType, GenerateKeyRequest, + (const CdmSessionId &, const CdmKeySetId &, const std::string &, + const CdmInitData &, const CdmLicenseType, CdmAppParameterMap &, + CdmClientPropertySet *, const CdmIdentifier &, CdmKeyRequest *), + (override)); + + MOCK_METHOD(CdmResponseType, AddKey, + (const CdmSessionId &, const CdmKeyResponse &, CdmKeySetId *), + (override)); + + MOCK_METHOD(CdmResponseType, RemoveKeys, (const CdmSessionId &), (override)); + + MOCK_METHOD(CdmResponseType, RestoreKey, + (const CdmSessionId &, const CdmKeySetId &), (override)); + + MOCK_METHOD(CdmResponseType, QueryStatus, + (wvcdm::RequestedSecurityLevel, const std::string &, + std::string *), + (override)); + + MOCK_METHOD(CdmResponseType, QuerySessionStatus, + (const CdmSessionId &, CdmQueryMap *), (override)); + + MOCK_METHOD(CdmResponseType, QueryKeyStatus, + (const CdmSessionId &, CdmQueryMap *), (override)); + + MOCK_METHOD(CdmResponseType, QueryOemCryptoSessionId, + (const CdmSessionId &, CdmQueryMap *), (override)); + + MOCK_METHOD(CdmResponseType, GetProvisioningRequest, + (CdmCertificateType, const std::string &, const CdmIdentifier &, + const std::string &, wvcdm::RequestedSecurityLevel, + CdmProvisioningRequest *, std::string *), + (override)); + + MOCK_METHOD(CdmResponseType, HandleProvisioningResponse, + (const CdmIdentifier &, const CdmProvisioningResponse &, + wvcdm::RequestedSecurityLevel, std::string *, std::string *), + (override)); + + MOCK_METHOD(CdmResponseType, Unprovision, + (CdmSecurityLevel, const CdmIdentifier &), (override)); + + MOCK_METHOD(bool, IsProvisioned, + (CdmSecurityLevel, const std::string &, const std::string &, + bool), + (override)); + + MOCK_METHOD(CdmResponseType, GetUsageInfo, + (const std::string &, const CdmIdentifier &, CdmUsageInfo *), + (override)); + + MOCK_METHOD(CdmResponseType, GetUsageInfo, + (const std::string &, const CdmSecureStopId &, + const CdmIdentifier &, CdmUsageInfo *), + (override)); + + MOCK_METHOD(CdmResponseType, RemoveAllUsageInfo, + (const std::string &, const CdmIdentifier &), (override)); + + MOCK_METHOD(CdmResponseType, ReleaseUsageInfo, + (const CdmUsageInfoReleaseMessage &, const CdmIdentifier &), + (override)); + + MOCK_METHOD(bool, IsValidServiceCertificate, (const std::string &), + (override)); + + MOCK_METHOD(CdmResponseType, GetMetrics, + (const CdmIdentifier &, drm_metrics::WvCdmMetrics *), (override)); + + MOCK_METHOD(CdmResponseType, GetDecryptHashError, + (const CdmSessionId &, std::string *), (override)); + + MOCK_METHOD(CdmResponseType, ListStoredLicenses, + (CdmSecurityLevel, const CdmIdentifier &, + std::vector *), + (override)); + + MOCK_METHOD(CdmResponseType, GetOfflineLicenseState, + (const std::string &, CdmSecurityLevel, const CdmIdentifier &, + CdmOfflineLicenseState *), + (override)); + + MOCK_METHOD(CdmResponseType, RemoveOfflineLicense, + (const std::string &, CdmSecurityLevel, const CdmIdentifier &), + (override)); +}; + +class MockCrypto : public WVGenericCryptoInterface { +public: + MOCK_METHOD(OEMCryptoResult, selectKey, + (const OEMCrypto_SESSION, const uint8_t *, size_t), (override)); + + MOCK_METHOD(OEMCryptoResult, encrypt, + (OEMCrypto_SESSION, const uint8_t *, size_t, const uint8_t *, + OEMCrypto_Algorithm, uint8_t *), + (override)); + + MOCK_METHOD(OEMCryptoResult, decrypt, + (OEMCrypto_SESSION, const uint8_t *, size_t, const uint8_t *, + OEMCrypto_Algorithm, uint8_t *), + (override)); + + MOCK_METHOD(OEMCryptoResult, sign, + (OEMCrypto_SESSION, const uint8_t *, size_t, OEMCrypto_Algorithm, + uint8_t *, size_t *), + (override)); + + MOCK_METHOD(OEMCryptoResult, verify, + (OEMCrypto_SESSION, const uint8_t *, size_t, OEMCrypto_Algorithm, + const uint8_t *, size_t), + (override)); + + MOCK_METHOD(OEMCryptoResult, loadDeviceRSAKey, + (OEMCrypto_SESSION, const uint8_t *, size_t), (override)); + + MOCK_METHOD(OEMCryptoResult, generateRSASignature, + (OEMCrypto_SESSION, const uint8_t *, size_t, uint8_t *, size_t *, + RSA_Padding_Scheme), + (override)); +}; + +template +CdmResponseType setSessionIdOnMap(testing::Unused, CdmQueryMap *map) { + static const char oecId[] = {DIGIT + '0', '\0'}; + (*map)[QUERY_KEY_OEMCRYPTO_SESSION_ID] = oecId; + return wvcdm::NO_ERROR; +} + +MATCHER_P(HasOrigin, origin, "") { return arg.origin == origin; } + +class WVDrmPluginHalTest : public Test { +protected: + static const uint32_t kKeySetIdSize = 32; + static const uint32_t kSessionIdSize = 16; + std::vector sessionId; + uint8_t keySetIdRaw[kKeySetIdSize]; + uint8_t sessionIdRaw[kSessionIdSize]; + CdmSessionId mCdmSessionId; + KeySetId keySetId; + + android::sp> mCdm = nullptr; + std::shared_ptr mPlugin = nullptr; + std::string mAppPackageName; + StrictMock mCrypto; + +public: + void SetUp() override { + // Fill the session ID + FILE *fp = fopen("/dev/urandom", "r"); + ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; + fread(sessionIdRaw, sizeof(uint8_t), kSessionIdSize, fp); + + memcpy(sessionIdRaw, SESSION_ID_PREFIX, sizeof(SESSION_ID_PREFIX) - 1); + sessionId.assign(sessionIdRaw, sessionIdRaw + kSessionIdSize); + mCdmSessionId.assign(sessionId.begin(), sessionId.end()); + + fread(keySetIdRaw, sizeof(uint8_t), kKeySetIdSize, fp); + fclose(fp); + + memcpy(keySetIdRaw, KEY_SET_ID_PREFIX, sizeof(KEY_SET_ID_PREFIX)); + CdmKeySetId mCdmKeySetId(reinterpret_cast(keySetIdRaw), + kKeySetIdSize); + keySetId.keySetId.assign(keySetIdRaw, keySetIdRaw + kKeySetIdSize); + + // Set default return values for gMock + DefaultValue::Set(wvcdm::NO_ERROR); + DefaultValue::Set(OEMCrypto_SUCCESS); + DefaultValue::Set(true); + + mCdm = new StrictMock(); + ASSERT_TRUE(mCdm) << "Failed to create mocked CDM"; + mPlugin = ::ndk::SharedRefBase::make( + mCdm.get(), mAppPackageName.c_str(), &mCrypto, false); + ASSERT_TRUE(mPlugin) << "Failed to create drm plugin"; + } + + void TearDown() override { + if (mCdm) + mCdm.clear(); + if (mPlugin) + mPlugin.reset(); + } +}; + +struct OriginTestVariant { + // For a test that does not expect any follow-up queries + OriginTestVariant(const std::string &nameValue, + const std::string &originValue, + const std::string &expectedOriginValue) + : name(nameValue), origin(originValue), + expectedOrigin(expectedOriginValue) {} + + const std::string name; + const std::string origin; + const std::string expectedOrigin; +}; + +void PrintTo(const OriginTestVariant ¶m, ::std::ostream *os) { + *os << param.name << " Variant"; +} + +class WVDrmPluginHalOriginTest : public WVDrmPluginHalTest, + public WithParamInterface { +}; + +TEST_F(WVDrmPluginHalTest, OpensSessions) { + const CdmClientPropertySet *propertySet = nullptr; + + // Provide expected mock behavior + EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_SECURITY_LEVEL, _)) + .WillOnce(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L1), + testing::Return(wvcdm::NO_ERROR))) + .WillOnce(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L3), + testing::Return(wvcdm::NO_ERROR))); + + CdmQueryMap securityLevelQueryMap; + securityLevelQueryMap[wvcdm::QUERY_KEY_SECURITY_LEVEL] = + wvcdm::QUERY_VALUE_SECURITY_LEVEL_L3; + + EXPECT_CALL(*mCdm, QuerySessionStatus(_, NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(securityLevelQueryMap), + testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*mCdm, OpenSession(_, _, _, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId), + SaveArg<1>(&propertySet), + testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) + .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); + + EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); + + auto ret = mPlugin->setPropertyString(std::string("securityLevel"), + std::string("L1")); + EXPECT_TRUE(ret.isOk()); + + SessionId sessionId; + ret = mPlugin->openSession(SecurityLevel::SW_SECURE_CRYPTO, &sessionId); + EXPECT_TRUE(ret.isOk()); + ASSERT_THAT(propertySet, NotNull()); + EXPECT_THAT(sessionId, ElementsAreArray(sessionIdRaw, kSessionIdSize)); + + ret = mPlugin->closeSession(sessionId); + EXPECT_TRUE(ret.isOk()); +} + +TEST_F(WVDrmPluginHalTest, ClosesSessions) { + EXPECT_CALL(*mCdm, CloseSession(mCdmSessionId)).Times(1); + + auto ret = mPlugin->closeSession(sessionId); + EXPECT_TRUE(ret.isOk()); +} + +TEST_F(WVDrmPluginHalTest, ClosesSessionWithError) { + EXPECT_CALL(*mCdm, CloseSession(mCdmSessionId)) + .WillOnce(testing::Return(wvcdm::SESSION_NOT_FOUND_1)); + + auto ret = mPlugin->closeSession(sessionId); + EXPECT_EQ(static_cast(Status::ERROR_DRM_SESSION_NOT_OPENED), + ret.getServiceSpecificError()); +} + +// TODO b/35325611 Fix this disabled test +TEST_F(WVDrmPluginHalTest, DISABLED_GeneratesKeyRequests) { + static const size_t kInitDataSize = 128; + uint8_t initDataRaw[kInitDataSize]; + static const size_t kRequestSize = 256; + uint8_t requestRaw[kRequestSize]; + static const uint32_t kKeySetIdSize = 32; + uint8_t keySetIdRaw[kKeySetIdSize]; + FILE *fp = fopen("/dev/urandom", "r"); + ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; + fread(initDataRaw, sizeof(uint8_t), kInitDataSize, fp); + fread(requestRaw, sizeof(uint8_t), kRequestSize, fp); + fread(keySetIdRaw, sizeof(uint8_t), kKeySetIdSize, fp); + fclose(fp); + + memcpy(keySetIdRaw, KEY_SET_ID_PREFIX, sizeof(KEY_SET_ID_PREFIX) - 1); + CdmKeySetId mCdmKeySetId(reinterpret_cast(keySetIdRaw), + kKeySetIdSize); + std::vector keySetId; + keySetId.assign(keySetIdRaw, keySetIdRaw + kKeySetIdSize); + + CdmInitData mCdmInitData(reinterpret_cast(initDataRaw), + kInitDataSize); + std::vector initData; + initData.assign(initDataRaw, initDataRaw + kInitDataSize); + + // clang-format off + static const uint8_t psshPrefix[] = { + 0, 0, 0, 32 + kInitDataSize, // Total size + 'p', 's', 's', 'h', // "PSSH" + 0, 0, 0, 0, // Flags - must be zero + 0xED, 0xEF, 0x8B, 0xA9, 0x79, 0xD6, 0x4A, 0xCE, // Widevine UUID + 0xA3, 0xC8, 0x27, 0xDC, 0xD5, 0x1D, 0x21, 0xED, + 0, 0, 0, kInitDataSize // Size of initData + }; + // clang-format on + static const size_t kPsshPrefixSize = sizeof(psshPrefix); + static const size_t kPsshBoxSize = kPsshPrefixSize + kInitDataSize; + uint8_t psshBoxRaw[kPsshBoxSize]; + memcpy(psshBoxRaw, psshPrefix, kPsshPrefixSize); + memcpy(psshBoxRaw + kPsshPrefixSize, initDataRaw, kInitDataSize); + CdmInitData mCdmPsshBox(reinterpret_cast(psshBoxRaw), kPsshBoxSize); + std::vector psshBox; + psshBox.assign(psshBoxRaw, psshBoxRaw + kPsshBoxSize); + + CdmKeyMessage mCdmRequest(requestRaw, requestRaw + kRequestSize); + + std::map parameters = { + {"paddingScheme", "BUBBLE WRAP"}, + {"favorite-particle", "tetraquark"}, + {"answer", "6 * 9"}, + }; + CdmAppParameterMap mCdmParameters; + for (const auto &pair : parameters) { + mCdmParameters[pair.first] = pair.second; + } + + std::vector optionalParameters; + KeyValue keyValue; + for (auto itr = parameters.begin(); itr != parameters.end(); ++itr) { + keyValue.key = itr->first; + keyValue.value = itr->second; + optionalParameters.push_back(keyValue); + } + + static const char *kDefaultUrl = "http://google.com/"; + static const char *kIsoBmffMimeType = "cenc"; + static const char *kWebmMimeType = "webm"; + + struct TestSet { + const char *mimeType; + const std::vector &initDataIn; + const CdmInitData &initDataOut; + }; + + // We run the same set of operations on several sets of data, simulating + // different valid calling patterns. + TestSet testSets[] = { + {kIsoBmffMimeType, psshBox, mCdmPsshBox}, // ISO-BMFF, EME passing style + {kIsoBmffMimeType, initData, mCdmPsshBox}, // ISO-BMFF, old passing style + {kWebmMimeType, initData, mCdmInitData} // WebM + }; + size_t testSetCount = N_ELEM(testSets); + + // Set up the expected calls. Per gMock rules, this must be done for all test + // sets prior to testing any of them. + { + InSequence calls; + + for (size_t i = 0; i < testSetCount; ++i) { + const char *mimeType = testSets[i].mimeType; + const CdmInitData &initData = testSets[i].initDataOut; + + CdmKeyRequest initialRequest = {mCdmRequest, kKeyRequestTypeInitial, + kDefaultUrl}; + + CdmKeyRequest renewalRequest = {mCdmRequest, kKeyRequestTypeRenewal, + kDefaultUrl}; + + CdmKeyRequest releaseRequest = {mCdmRequest, kKeyRequestTypeRelease, + kDefaultUrl}; + + EXPECT_CALL(*mCdm, + GenerateKeyRequest(mCdmSessionId, "", mimeType, initData, + kLicenseTypeOffline, mCdmParameters, + NotNull(), HasOrigin(EMPTY_ORIGIN), _)) + .WillOnce(DoAll(SetArgPointee<8>(initialRequest), + testing::Return(wvcdm::KEY_MESSAGE))); + + EXPECT_CALL(*mCdm, + GenerateKeyRequest(mCdmSessionId, "", mimeType, initData, + kLicenseTypeStreaming, mCdmParameters, + NotNull(), HasOrigin(EMPTY_ORIGIN), _)) + .WillOnce(DoAll(SetArgPointee<8>(renewalRequest), + testing::Return(wvcdm::KEY_MESSAGE))); + + EXPECT_CALL(*mCdm, + GenerateKeyRequest("", mCdmKeySetId, mimeType, initData, + kLicenseTypeRelease, mCdmParameters, + NotNull(), HasOrigin(EMPTY_ORIGIN), _)) + + .WillOnce(DoAll(SetArgPointee<8>(releaseRequest), + testing::Return(wvcdm::KEY_MESSAGE))); + } + } + + // Performs the actual tests + for (size_t i = 0; i < testSetCount; ++i) { + const std::string mimeType(testSets[i].mimeType); + const std::vector &initData = testSets[i].initDataIn; + + KeyRequest result; + auto ret = + mPlugin->getKeyRequest(sessionId, initData, std::string(mimeType), + KeyType::OFFLINE, optionalParameters, &result); + EXPECT_TRUE(ret.isOk()); + EXPECT_THAT(result.request, ElementsAreArray(requestRaw, kRequestSize)); + EXPECT_EQ(KeyRequestType::INITIAL, result.requestType); + EXPECT_STREQ(kDefaultUrl, result.defaultUrl.c_str()); + + ret = + mPlugin->getKeyRequest(sessionId, initData, std::string(mimeType), + KeyType::STREAMING, optionalParameters, &result); + EXPECT_TRUE(ret.isOk()); + EXPECT_THAT(result.request, ElementsAreArray(requestRaw, kRequestSize)); + EXPECT_EQ(KeyRequestType::RENEWAL, result.requestType); + EXPECT_STREQ(kDefaultUrl, result.defaultUrl.c_str()); + + ret = mPlugin->getKeyRequest(sessionId, initData, std::string(mimeType), + KeyType::RELEASE, optionalParameters, &result); + EXPECT_TRUE(ret.isOk()); + EXPECT_THAT(result.request, ElementsAreArray(requestRaw, kRequestSize)); + EXPECT_EQ(KeyRequestType::RELEASE, result.requestType); + EXPECT_STREQ(kDefaultUrl, result.defaultUrl.c_str()); + } +} + +TEST_F(WVDrmPluginHalTest, AddsKeys) { + static const uint32_t kResponseSize = 256; + uint8_t responseRaw[kResponseSize]; + static const uint32_t kKeySetIdSize = 32; + uint8_t keySetIdRaw[kKeySetIdSize]; + FILE *fp = fopen("/dev/urandom", "r"); + ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; + fread(responseRaw, sizeof(uint8_t), kResponseSize, fp); + fread(keySetIdRaw, sizeof(uint8_t), kKeySetIdSize, fp); + fclose(fp); + + const std::vector response(responseRaw, responseRaw + kResponseSize); + + memcpy(keySetIdRaw, KEY_SET_ID_PREFIX, sizeof(KEY_SET_ID_PREFIX) - 1); + CdmKeySetId mCdmKeySetId(reinterpret_cast(keySetIdRaw), + kKeySetIdSize); + + EXPECT_CALL(*mCdm, AddKey(mCdmSessionId, + ElementsAreArray(responseRaw, kResponseSize), _)) + .WillOnce(DoAll(SetArgPointee<2>(mCdmKeySetId), + testing::Return(wvcdm::KEY_ADDED))); + + EXPECT_CALL(*mCdm, AddKey("", ElementsAreArray(responseRaw, kResponseSize), + Pointee(mCdmKeySetId))) + .Times(1); + + KeySetId result; + auto ret = mPlugin->provideKeyResponse(sessionId, response, &result); + EXPECT_TRUE(ret.isOk()); + ASSERT_THAT(result.keySetId, ElementsAreArray(keySetIdRaw, kKeySetIdSize)); + + ret = mPlugin->provideKeyResponse(result.keySetId, response, &result); + EXPECT_TRUE(ret.isOk()); + EXPECT_EQ(0u, result.keySetId.size()); +} + +TEST_F(WVDrmPluginHalTest, HandlesPrivacyCertCaseOfAddKey) { + const CdmClientPropertySet *propertySet = nullptr; + + // Provide expected behavior in response to OpenSession and store the + // property set + EXPECT_CALL(*mCdm, OpenSession(_, _, _, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId), + SaveArg<1>(&propertySet), + testing::Return(wvcdm::NO_ERROR))); + + // Provide expected behavior when mPlugin requests session control info + EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) + .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); + + EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); + + static const uint32_t kResponseSize = 256; + uint8_t responseRaw[kResponseSize]; + FILE *fp = fopen("/dev/urandom", "r"); + ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; + fread(responseRaw, sizeof(uint8_t), kResponseSize, fp); + fclose(fp); + + EXPECT_CALL(*mCdm, AddKey(_, _, _)) + .WillRepeatedly(testing::Return(wvcdm::NEED_KEY)); + + SessionId sessionId; + auto ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + EXPECT_THAT(sessionId, ElementsAreArray(sessionIdRaw, kSessionIdSize)); + ASSERT_THAT(propertySet, NotNull()); + + ret = mPlugin->setPropertyString(std::string("privacyMode"), + std::string("enable")); + EXPECT_TRUE(ret.isOk()); + EXPECT_TRUE(propertySet->use_privacy_mode()); + + std::vector response; + response.assign(responseRaw, responseRaw + kResponseSize); + std::vector keySetId; + + KeySetId result; + ret = mPlugin->provideKeyResponse(sessionId, response, &result); + EXPECT_TRUE(ret.isOk()); +} + +TEST_F(WVDrmPluginHalTest, RemovesKeys) { + EXPECT_CALL(*mCdm, RemoveKeys(mCdmSessionId)).Times(1); + + auto ret = mPlugin->removeKeys(sessionId); + EXPECT_TRUE(ret.isOk()); +} + +TEST_F(WVDrmPluginHalTest, RestoresKeys) { + EXPECT_CALL(*mCdm, RestoreKey(mCdmSessionId, + ElementsAreArray(keySetIdRaw, kKeySetIdSize))) + .Times(1); + + auto ret = mPlugin->restoreKeys(sessionId, keySetId); + EXPECT_TRUE(ret.isOk()); +} + +TEST_F(WVDrmPluginHalTest, QueriesKeyStatus) { + std::map expectedLicenseStatus = { + {"areTheKeysAllRight", "yes"}, + {"isGMockAwesome", "ohhhhhhYeah"}, + {"answer", "42"}, + }; + CdmQueryMap mCdmLicenseStatus; + for (const auto &pair : expectedLicenseStatus) { + mCdmLicenseStatus[pair.first] = pair.second; + } + + EXPECT_CALL(*mCdm, QueryKeyStatus(mCdmSessionId, _)) + .WillOnce(DoAll(SetArgPointee<1>(mCdmLicenseStatus), + testing::Return(wvcdm::NO_ERROR))); + + std::vector keyValues; + auto ret = mPlugin->queryKeyStatus(sessionId, &keyValues); + EXPECT_TRUE(ret.isOk()); + ASSERT_EQ(expectedLicenseStatus.size(), keyValues.size()); + + KeyValue keyValuePair; + size_t i = 0; + for (std::map::iterator itr = + expectedLicenseStatus.begin(); + itr != expectedLicenseStatus.end(); ++itr) { + keyValuePair.value = keyValues[i++].value; + EXPECT_EQ(itr->second.c_str(), std::string(keyValuePair.value.c_str())); + } +} + +TEST_F(WVDrmPluginHalTest, GetsProvisioningRequests) { + static const uint32_t kRequestSize = 256; + uint8_t requestRaw[kRequestSize]; + FILE *fp = fopen("/dev/urandom", "r"); + ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; + fread(requestRaw, sizeof(uint8_t), kRequestSize, fp); + fclose(fp); + + CdmProvisioningRequest mCdmRequest(requestRaw, requestRaw + kRequestSize); + + static const char *kDefaultUrl = "http://google.com/"; + + EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_SECURITY_LEVEL, _)) + .WillOnce(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L1), + testing::Return(wvcdm::NO_ERROR))); + + // The first and the third invocation should be at default security level, + // while the second one should be L3 + EXPECT_CALL(*mCdm, GetProvisioningRequest(kCertificateWidevine, IsEmpty(), + HasOrigin(EMPTY_ORIGIN), IsEmpty(), + wvcdm::kLevelDefault, _, _)) + .Times(2) + .WillRepeatedly(DoAll(SetArgPointee<5>(mCdmRequest), + SetArgPointee<6>(kDefaultUrl), + testing::Return(wvcdm::NO_ERROR))); + EXPECT_CALL(*mCdm, GetProvisioningRequest(kCertificateWidevine, IsEmpty(), + HasOrigin(EMPTY_ORIGIN), IsEmpty(), + wvcdm::kLevel3, _, _)) + .WillOnce(DoAll(SetArgPointee<5>(mCdmRequest), + SetArgPointee<6>(kDefaultUrl), + testing::Return(wvcdm::NO_ERROR))); + + // Make 3 provisioning requests at security level default then L3 then L1 + ProvisionRequest result; + + auto ret = + mPlugin->getProvisionRequest(std::string(""), std::string(""), &result); + EXPECT_TRUE(ret.isOk()); + std::vector request(result.request); + EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize)); + EXPECT_STREQ(kDefaultUrl, result.defaultUrl.c_str()); + + // Set L3 security level + ret = mPlugin->setPropertyString(std::string("securityLevel"), + std::string("L3")); + EXPECT_TRUE(ret.isOk()); + + ret = mPlugin->getProvisionRequest(std::string(""), std::string(""), &result); + EXPECT_TRUE(ret.isOk()); + std::vector requestL3(result.request); + EXPECT_THAT(requestL3, ElementsAreArray(requestRaw, kRequestSize)); + EXPECT_STREQ(kDefaultUrl, result.defaultUrl.c_str()); + + // Reset security level to L1 + ret = mPlugin->setPropertyString(std::string("securityLevel"), + std::string("L1")); + EXPECT_TRUE(ret.isOk()); + + ret = mPlugin->getProvisionRequest(std::string(""), std::string(""), &result); + EXPECT_TRUE(ret.isOk()); + std::vector requestL1(result.request); + EXPECT_THAT(requestL1, ElementsAreArray(requestRaw, kRequestSize)); + EXPECT_STREQ(kDefaultUrl, result.defaultUrl.c_str()); +} + +TEST_F(WVDrmPluginHalTest, RejectsAtscProvisioningRequests) { + auto ret = mPlugin->setPropertyString(std::string("atscMode"), + std::string("enable")); + EXPECT_TRUE(ret.isOk()); + + ProvisionRequest result; + ret = mPlugin->getProvisionRequest(std::string(""), std::string(""), &result); + EXPECT_EQ(static_cast(Status::ERROR_DRM_CANNOT_HANDLE), + ret.getServiceSpecificError()); +} + +TEST_F(WVDrmPluginHalTest, HandlesProvisioningResponses) { + static const uint32_t kResponseSize = 512; + uint8_t responseRaw[kResponseSize]; + FILE *fp = fopen("/dev/urandom", "r"); + ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; + fread(responseRaw, sizeof(uint8_t), kResponseSize, fp); + fclose(fp); + + std::vector response; + response.assign(responseRaw, responseRaw + kResponseSize); + + EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_SECURITY_LEVEL, _)) + .WillOnce(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L1), + testing::Return(wvcdm::NO_ERROR))); + + // The first and the third invocation should be at default security level, + // while the second one should be L3 + EXPECT_CALL(*mCdm, HandleProvisioningResponse( + HasOrigin(EMPTY_ORIGIN), + ElementsAreArray(responseRaw, kResponseSize), + wvcdm::kLevelDefault, _, _)) + .Times(2); + EXPECT_CALL(*mCdm, HandleProvisioningResponse( + HasOrigin(EMPTY_ORIGIN), + ElementsAreArray(responseRaw, kResponseSize), + wvcdm::kLevel3, _, _)) + .Times(1); + + std::vector cert; + std::vector key; + + // Process 3 provisioning responses at security level default then L3 then L1 + ProvideProvisionResponseResult result; + auto ret = mPlugin->provideProvisionResponse(response, &result); + EXPECT_TRUE(ret.isOk()); + + // Set L3 security level + ret = mPlugin->setPropertyString(std::string("securityLevel"), + std::string("L3")); + EXPECT_TRUE(ret.isOk()); + + ret = mPlugin->provideProvisionResponse(response, &result); + EXPECT_TRUE(ret.isOk()); + + // Reset security level to L1 + ret = mPlugin->setPropertyString(std::string("securityLevel"), + std::string("L1")); + EXPECT_TRUE(ret.isOk()); + + ret = mPlugin->provideProvisionResponse(response, &result); + EXPECT_TRUE(ret.isOk()); +} + +TEST_F(WVDrmPluginHalTest, UnprovisionsDevice) { + EXPECT_CALL(*mCdm, Unprovision(kSecurityLevelL1, HasOrigin(EMPTY_ORIGIN))) + .Times(1); + EXPECT_CALL(*mCdm, Unprovision(kSecurityLevelL3, HasOrigin(EMPTY_ORIGIN))) + .Times(1); + + Status res = mPlugin->unprovisionDevice(); + ASSERT_EQ(Status::OK, res); +} + +TEST_F(WVDrmPluginHalTest, MuxesUnprovisioningErrors) { + // Tests that both Unprovisions are called even if one fails. Also tests that + // no matter which fails, the function always propagates the error. + EXPECT_CALL(*mCdm, Unprovision(kSecurityLevelL1, HasOrigin(EMPTY_ORIGIN))) + .WillOnce(testing::Return(wvcdm::UNKNOWN_ERROR)) + .WillOnce(testing::Return(wvcdm::NO_ERROR)) + .WillOnce(testing::Return(wvcdm::UNKNOWN_ERROR)); + EXPECT_CALL(*mCdm, Unprovision(kSecurityLevelL3, HasOrigin(EMPTY_ORIGIN))) + .WillOnce(testing::Return(wvcdm::NO_ERROR)) + .WillOnce(testing::Return(wvcdm::UNKNOWN_ERROR)) + .WillOnce(testing::Return(wvcdm::UNKNOWN_ERROR)); + + Status res = mPlugin->unprovisionDevice(); + ASSERT_NE(Status::OK, res); + res = mPlugin->unprovisionDevice(); + ASSERT_NE(Status::OK, res); + res = mPlugin->unprovisionDevice(); + ASSERT_NE(Status::OK, res); +} + +TEST_F(WVDrmPluginHalTest, UnprovisionsOrigin) { + std::vector specialResponse; + specialResponse.assign(kUnprovisionResponse, + kUnprovisionResponse + kUnprovisionResponseSize); + + EXPECT_CALL(*mCdm, Unprovision(kSecurityLevelL1, HasOrigin(kOrigin.c_str()))) + .Times(1); + EXPECT_CALL(*mCdm, Unprovision(kSecurityLevelL3, HasOrigin(kOrigin.c_str()))) + .Times(1); + + auto ret = + mPlugin->setPropertyString(std::string("origin"), std::string(kOrigin)); + EXPECT_TRUE(ret.isOk()); + + ProvideProvisionResponseResult result; + ret = mPlugin->provideProvisionResponse(specialResponse, &result); + EXPECT_TRUE(ret.isOk()); +} + +TEST_F(WVDrmPluginHalTest, UnprovisionsGloballyWithSpoid) { + std::vector specialResponse; + specialResponse.assign(kUnprovisionResponse, + kUnprovisionResponse + kUnprovisionResponseSize); + + android::sp> cdm = new StrictMock(); + ASSERT_TRUE(cdm) << "Failed to create mocked CDM"; + + EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_DEVICE_ID, _)) + .WillRepeatedly( + DoAll(SetArgPointee<2>(kDeviceId), testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*cdm, Unprovision(kSecurityLevelL1, HasOrigin(EMPTY_ORIGIN))) + .Times(1); + EXPECT_CALL(*cdm, Unprovision(kSecurityLevelL3, HasOrigin(EMPTY_ORIGIN))) + .Times(1); + + std::shared_ptr plugin = ::ndk::SharedRefBase::make( + cdm.get(), mAppPackageName.c_str(), &mCrypto, true); + ASSERT_TRUE(plugin) << "Failed to create drm plugin"; + + StrictMock crypto; + ProvideProvisionResponseResult result; + auto ret = plugin->provideProvisionResponse(specialResponse, &result); + EXPECT_TRUE(ret.isOk()); +} + +TEST_F(WVDrmPluginHalTest, WillNotUnprovisionWithoutOriginOrSpoid) { + std::vector specialResponse; + specialResponse.assign(kUnprovisionResponse, + kUnprovisionResponse + kUnprovisionResponseSize); + + EXPECT_CALL(*mCdm, Unprovision(_, _)).Times(0); + + ProvideProvisionResponseResult result; + auto ret = mPlugin->provideProvisionResponse(specialResponse, &result); + EXPECT_FALSE(ret.isOk()); +} + +TEST_F(WVDrmPluginHalTest, MuxesOriginUnprovisioningErrors) { + std::vector specialResponse; + specialResponse.assign(kUnprovisionResponse, + kUnprovisionResponse + kUnprovisionResponseSize); + + // Tests that both Unprovisions are called even if one fails. Also tests that + // no matter which fails, the function always propagates the error. + EXPECT_CALL(*mCdm, Unprovision(kSecurityLevelL1, HasOrigin(kOrigin.c_str()))) + .WillOnce(testing::Return(wvcdm::UNKNOWN_ERROR)) + .WillOnce(testing::Return(wvcdm::NO_ERROR)) + .WillOnce(testing::Return(wvcdm::UNKNOWN_ERROR)); + EXPECT_CALL(*mCdm, Unprovision(kSecurityLevelL3, HasOrigin(kOrigin.c_str()))) + .WillOnce(testing::Return(wvcdm::NO_ERROR)) + .WillOnce(testing::Return(wvcdm::UNKNOWN_ERROR)) + .WillOnce(testing::Return(wvcdm::UNKNOWN_ERROR)); + + auto ret = + mPlugin->setPropertyString(std::string("origin"), std::string(kOrigin)); + EXPECT_TRUE(ret.isOk()); + + ProvideProvisionResponseResult result; + ret = mPlugin->provideProvisionResponse(specialResponse, &result); + EXPECT_FALSE(ret.isOk()); + + ret = mPlugin->provideProvisionResponse(specialResponse, &result); + EXPECT_FALSE(ret.isOk()); + + ret = mPlugin->provideProvisionResponse(specialResponse, &result); + EXPECT_FALSE(ret.isOk()); +} + +TEST_F(WVDrmPluginHalTest, RejectsAtscUnprovisionDeviceRequests) { + auto ret = mPlugin->setPropertyString(std::string("atscMode"), + std::string("enable")); + EXPECT_TRUE(ret.isOk()); + + Status status = mPlugin->unprovisionDevice(); + ASSERT_EQ(Status::ERROR_DRM_CANNOT_HANDLE, status); +} + +TEST_F(WVDrmPluginHalTest, GetsSecureStops) { + static const uint32_t kStopSize = 53; + static const uint32_t kStopCount = 7; + uint8_t stopsRaw[kStopCount][kStopSize]; + FILE *fp = fopen("/dev/urandom", "r"); + ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; + for (uint32_t i = 0; i < kStopCount; ++i) { + fread(stopsRaw[i], sizeof(uint8_t), kStopSize, fp); + } + fclose(fp); + + CdmUsageInfo mCdmStops; + for (uint32_t i = 0; i < kStopCount; ++i) { + mCdmStops.push_back(std::string(stopsRaw[i], stopsRaw[i] + kStopSize)); + } + + const char *app_id = "my_app_id"; + EXPECT_CALL(*mCdm, GetUsageInfo(StrEq(app_id), _, _)) + .WillOnce( + DoAll(SetArgPointee<2>(mCdmStops), testing::Return(wvcdm::NO_ERROR))); + + auto ret = + mPlugin->setPropertyString(std::string("appId"), std::string(app_id)); + EXPECT_TRUE(ret.isOk()); + + std::vector secureStops; + ret = mPlugin->getSecureStops(&secureStops); + EXPECT_TRUE(ret.isOk()); + + std::vector> stops; + for (const auto &stop : secureStops) { + stops.push_back(stop.opaqueData); + } + + size_t index = 0; + for (const auto &stop : stops) { + EXPECT_THAT(stop, ElementsAreArray(stopsRaw[index++], kStopSize)); + } + EXPECT_EQ(kStopCount, index); +} + +TEST_F(WVDrmPluginHalTest, ReleasesAllSecureStops) { + EXPECT_CALL(*mCdm, RemoveAllUsageInfo(StrEq(""), _)).Times(1); + + auto ret = mPlugin->setPropertyString(std::string("appId"), ""); + EXPECT_TRUE(ret.isOk()); + + ret = mPlugin->releaseAllSecureStops(); + EXPECT_TRUE(ret.isOk()); +} + +TEST_F(WVDrmPluginHalTest, ReleasesSecureStop) { + static const uint32_t kMessageSize = 128; + uint8_t messageRaw[kMessageSize]; + FILE *fp = fopen("/dev/urandom", "r"); + ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; + fread(messageRaw, sizeof(uint8_t), kMessageSize, fp); + fclose(fp); + + EXPECT_CALL(*mCdm, + ReleaseUsageInfo(ElementsAreArray(messageRaw, kMessageSize), _)) + .Times(1); + + SecureStopId stopId; + stopId.secureStopId.assign(messageRaw, messageRaw + kMessageSize); + auto ret = mPlugin->releaseSecureStop(stopId); + EXPECT_TRUE(ret.isOk()); +} + +TEST_F(WVDrmPluginHalTest, ReturnsExpectedPropertyValues) { + CdmQueryMap l1Map; + l1Map[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L1; + + CdmQueryMap l3Map; + l3Map[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L3; + + static const std::string systemId = "The Universe"; + static const std::string provisioningId("Life\0&Everything", 16); + static const std::string openSessions = "42"; + static const std::string maxSessions = "54"; + static const std::string oemCryptoApiVersion = "13"; + static const std::string currentSRMVersion = "1"; + static const std::string mCdmVersion = "Infinity Minus 1"; + static const std::string resourceRatingTier = "1"; + static const std::string oemCryptoBuildInformation = "Mostly Harmless"; + static const std::string oemCryptoHashNotSupported = "0"; + static const std::string oemCryptoCrcClearBuffer = "1"; + static const std::string oemCryptoPartnerDefinedHash = "2"; + static const std::string decryptHashErrorBadHashAndFrameNumber = "53, 1"; + drm_metrics::WvCdmMetrics expected_metrics; + std::string serialized_metrics = wvutil::a2bs_hex(kSerializedMetricsHex); + ASSERT_TRUE(expected_metrics.ParseFromString(serialized_metrics)); + + EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_SECURITY_LEVEL, _)) + .WillOnce(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L1), + testing::Return(wvcdm::NO_ERROR))) + .WillRepeatedly(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L3), + testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_DEVICE_ID, _)) + .WillOnce( + DoAll(SetArgPointee<2>(kDeviceId), testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_SYSTEM_ID, _)) + .WillOnce( + DoAll(SetArgPointee<2>(systemId), testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_PROVISIONING_ID, _)) + .WillOnce(DoAll(SetArgPointee<2>(provisioningId), + testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_NUMBER_OF_OPEN_SESSIONS, _)) + .WillOnce(DoAll(SetArgPointee<2>(openSessions), + testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_MAX_NUMBER_OF_SESSIONS, _)) + .WillOnce(DoAll(SetArgPointee<2>(maxSessions), + testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_OEMCRYPTO_API_VERSION, _)) + .WillOnce(DoAll(SetArgPointee<2>(oemCryptoApiVersion), + testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_SRM_UPDATE_SUPPORT, _)) + .WillOnce( + DoAll(SetArgPointee<2>("True"), testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_CURRENT_SRM_VERSION, _)) + .WillOnce(DoAll(SetArgPointee<2>(currentSRMVersion), + testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_WVCDM_VERSION, _)) + .WillOnce(DoAll(SetArgPointee<2>(mCdmVersion), + testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_RESOURCE_RATING_TIER, _)) + .WillOnce(DoAll(SetArgPointee<2>(resourceRatingTier), + testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_DECRYPT_HASH_SUPPORT, _)) + .WillOnce(DoAll(SetArgPointee<2>("1"), testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_OEMCRYPTO_BUILD_INFORMATION, _)) + .WillOnce(DoAll(SetArgPointee<2>(oemCryptoBuildInformation), + testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*mCdm, GetDecryptHashError(_, _)) + .WillOnce(DoAll(SetArgPointee<1>(decryptHashErrorBadHashAndFrameNumber), + testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*mCdm, GetMetrics(_, _)) + .WillOnce(DoAll(SetArgPointee<1>(expected_metrics), + testing::Return(wvcdm::NO_ERROR))); + + std::string stringResult; + std::vector vectorResult; + + auto ret = mPlugin->getPropertyString(std::string("vendor"), &stringResult); + EXPECT_TRUE(ret.isOk()); + EXPECT_STREQ("Google", stringResult.c_str()); + + ret = mPlugin->getPropertyString(std::string("version"), &stringResult); + EXPECT_TRUE(ret.isOk()); + EXPECT_STREQ(mCdmVersion.c_str(), stringResult.c_str()); + + ret = mPlugin->getPropertyString(std::string("description"), &stringResult); + EXPECT_TRUE(ret.isOk()); + EXPECT_STREQ("Widevine CDM", stringResult.c_str()); + + ret = mPlugin->getPropertyString(std::string("algorithms"), &stringResult); + EXPECT_TRUE(ret.isOk()); + EXPECT_STREQ("AES/CBC/NoPadding,HmacSHA256", stringResult.c_str()); + + ret = mPlugin->getPropertyString(std::string("securityLevel"), &stringResult); + EXPECT_TRUE(ret.isOk()); + EXPECT_STREQ(QUERY_VALUE_SECURITY_LEVEL_L1.c_str(), stringResult.c_str()); + + ret = mPlugin->getPropertyString(std::string("securityLevel"), &stringResult); + EXPECT_TRUE(ret.isOk()); + EXPECT_STREQ(QUERY_VALUE_SECURITY_LEVEL_L3.c_str(), stringResult.c_str()); + + ret = mPlugin->getPropertyByteArray(std::string("deviceUniqueId"), + &vectorResult); + EXPECT_TRUE(ret.isOk()); + EXPECT_THAT(vectorResult, + ElementsAreArray(kDeviceId.data(), kDeviceId.size())); + + ret = mPlugin->getPropertyString(std::string("systemId"), &stringResult); + EXPECT_TRUE(ret.isOk()); + EXPECT_STREQ(systemId.c_str(), stringResult.c_str()); + + ret = mPlugin->getPropertyByteArray(std::string("provisioningUniqueId"), + &vectorResult); + EXPECT_TRUE(ret.isOk()); + EXPECT_THAT(vectorResult, + ElementsAreArray(provisioningId.data(), provisioningId.size())); + + ret = mPlugin->getPropertyString(std::string("numberOfOpenSessions"), + &stringResult); + EXPECT_TRUE(ret.isOk()); + EXPECT_EQ(openSessions, stringResult.c_str()); + + ret = mPlugin->getPropertyString(std::string("maxNumberOfSessions"), + &stringResult); + EXPECT_TRUE(ret.isOk()); + EXPECT_EQ(maxSessions, stringResult.c_str()); + + ret = mPlugin->getPropertyString(std::string("oemCryptoApiVersion"), + &stringResult); + EXPECT_TRUE(ret.isOk()); + EXPECT_STREQ(oemCryptoApiVersion.c_str(), stringResult.c_str()); + + ret = mPlugin->getPropertyString(std::string("SRMUpdateSupport"), + &stringResult); + EXPECT_TRUE(ret.isOk()); + EXPECT_STREQ("True", stringResult.c_str()); + + ret = mPlugin->getPropertyString(std::string("CurrentSRMVersion"), + &stringResult); + EXPECT_TRUE(ret.isOk()); + EXPECT_STREQ(currentSRMVersion.c_str(), stringResult.c_str()); + + ret = mPlugin->getPropertyString(std::string("resourceRatingTier"), + &stringResult); + EXPECT_TRUE(ret.isOk()); + EXPECT_STREQ(resourceRatingTier.c_str(), stringResult.c_str()); + + ret = mPlugin->getPropertyString(std::string("oemCryptoBuildInformation"), + &stringResult); + EXPECT_TRUE(ret.isOk()); + EXPECT_STREQ(oemCryptoBuildInformation.c_str(), stringResult.c_str()); + + std::stringstream ss; + ss << oemCryptoHashNotSupported << " " << oemCryptoCrcClearBuffer << " " + << oemCryptoPartnerDefinedHash; + std::string validResults = ss.str(); + ret = mPlugin->getPropertyString(std::string("decryptHashSupport"), + &stringResult); + EXPECT_TRUE(ret.isOk()); + EXPECT_EQ(oemCryptoCrcClearBuffer, stringResult); + + ret = mPlugin->getPropertyString(std::string("decryptHashError"), + &stringResult); + EXPECT_TRUE(ret.isOk()); + EXPECT_EQ(decryptHashErrorBadHashAndFrameNumber, stringResult); + + // This call occurs before any open session or other call. This means + // that the mCdm identifier is not yet sealed, and metrics return empty + // metrics data. + ret = mPlugin->getPropertyByteArray(std::string("metrics"), &vectorResult); + EXPECT_TRUE(ret.isOk()); + const char empty[] = {}; + EXPECT_THAT(vectorResult, ElementsAreArray(empty, sizeof(empty))); + + // Set expectations for the OpenSession call and a CloseSession call. + EXPECT_CALL(*mCdm, OpenSession(StrEq("com.widevine"), _, + HasOrigin(EMPTY_ORIGIN), _, _)) + .WillOnce(DoAll(SetArgPointee<4>(mCdmSessionId), + testing::Return(wvcdm::NO_ERROR))); + // Provide expected behavior when mPlugin requests session control info + EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) + .Times(AtLeast(1)) + .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); + EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); + + // This call causes the mCdm identifier to become sealed. + SessionId sessionId; + ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + + // This call occurs after open session. The CDM identifier should be sealed. + // And the call should populate the mock metrics data. + ret = mPlugin->getPropertyByteArray(std::string("metrics"), &vectorResult); + EXPECT_TRUE(ret.isOk()); + EXPECT_THAT(vectorResult, ElementsAreArray(serialized_metrics.data(), + serialized_metrics.size())); + + ret = mPlugin->closeSession(sessionId); + EXPECT_TRUE(ret.isOk()); +} + +TEST_F(WVDrmPluginHalTest, DoesNotGetUnknownProperties) { + std::string stringResult; + std::vector vectorResult; + + auto ret = + mPlugin->getPropertyString(std::string("unknownProperty"), &stringResult); + EXPECT_FALSE(ret.isOk()); + EXPECT_TRUE(stringResult.empty()); + + ret = mPlugin->getPropertyByteArray(std::string("unknownProperty"), + &vectorResult); + EXPECT_FALSE(ret.isOk()); + EXPECT_EQ(0u, vectorResult.size()); +} + +TEST_F(WVDrmPluginHalTest, DoesNotSetUnknownProperties) { + static const uint32_t kValueSize = 32; + uint8_t valueRaw[kValueSize]; + FILE *fp = fopen("/dev/urandom", "r"); + ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; + fread(valueRaw, sizeof(uint8_t), kValueSize, fp); + fclose(fp); + + std::vector value; + value.assign(valueRaw, valueRaw + kValueSize); + + auto ret = mPlugin->setPropertyString(std::string("unknownProperty"), + std::string("ignored")); + EXPECT_FALSE(ret.isOk()); + + ret = mPlugin->setPropertyByteArray(std::string("unknownProperty"), value); + EXPECT_FALSE(ret.isOk()); +} + +TEST_F(WVDrmPluginHalTest, CompliesWithSpoidVariability) { + const std::string kDeviceIds[] = { + kDeviceId, + kDeviceId + " the Second", + }; + const size_t kDeviceCount = N_ELEM(kDeviceIds); + + const std::string kAppNames[] = { + std::string("com.google.widevine"), + std::string("com.youtube"), + }; + const size_t kAppCount = N_ELEM(kAppNames); + + const std::string kOrigins[] = { + kOrigin, + kOrigin + " but not that one, the other one.", + std::string(/* Intentionally Empty */), + }; + const size_t kOriginCount = N_ELEM(kOrigins); + + const size_t kPluginCount = 2; + + const size_t kPluginsPerCdm = kAppCount * kOriginCount * kPluginCount; + + // We will get kPluginCount SPOIDs for every app package name + device id + + // origin combination. + std::vector spoids[kDeviceCount][kAppCount][kOriginCount] + [kPluginCount]; + + StrictMock crypto; + + for (size_t deviceIndex = 0; deviceIndex < kDeviceCount; ++deviceIndex) { + const std::string &deviceId = kDeviceIds[deviceIndex]; + + android::sp> cdm = new StrictMock(); + ASSERT_TRUE(cdm) << "Failed to create mocked CDM"; + + EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_DEVICE_ID, _)) + .Times(AtLeast(kPluginsPerCdm)) + .WillRepeatedly(DoAll(SetArgPointee<2>(deviceId), + testing::Return(wvcdm::NO_ERROR))); + + for (size_t appIndex = 0; appIndex < kAppCount; ++appIndex) { + const std::string &appPackageName = kAppNames[appIndex]; + + for (size_t originIndex = 0; originIndex < kOriginCount; ++originIndex) { + const std::string &origin = kOrigins[originIndex]; + + for (size_t mPluginIndex = 0; mPluginIndex < kPluginCount; + ++mPluginIndex) { + std::shared_ptr plugin = + ::ndk::SharedRefBase::make( + cdm.get(), appPackageName.c_str(), &crypto, true); + ASSERT_TRUE(plugin) << "Failed to create drm plugin"; + + if (!origin.empty()) { + auto ret = plugin->setPropertyString(std::string("origin"), + std::string(origin)); + EXPECT_TRUE(ret.isOk()); + } + + std::vector vectorResult; + auto ret = plugin->getPropertyByteArray(std::string("deviceUniqueId"), + &vectorResult); + EXPECT_TRUE(ret.isOk()); + spoids[deviceIndex][appIndex][originIndex][mPluginIndex] = + vectorResult; + } + } + } + } + + // This nest of loops makes sure all the SPOIDs we retrieved above are + // identical if their parameters were identical and dissimilar otherwise. + for (size_t deviceIndex = 0; deviceIndex < kDeviceCount; ++deviceIndex) { + for (size_t appIndex = 0; appIndex < kAppCount; ++appIndex) { + for (size_t originIndex = 0; originIndex < kOriginCount; ++originIndex) { + for (size_t mPluginIndex = 0; mPluginIndex < kPluginCount; + ++mPluginIndex) { + const std::vector &firstSpoid = + spoids[deviceIndex][appIndex][originIndex][mPluginIndex]; + + for (size_t deviceIndex2 = 0; deviceIndex2 < kDeviceCount; + ++deviceIndex2) { + for (size_t appIndex2 = 0; appIndex2 < kAppCount; ++appIndex2) { + for (size_t originIndex2 = 0; originIndex2 < kOriginCount; + ++originIndex2) { + for (size_t mPluginIndex2 = 0; mPluginIndex2 < kPluginCount; + ++mPluginIndex2) { + const std::vector &secondSpoid = + spoids[deviceIndex2][appIndex2][originIndex2] + [mPluginIndex2]; + + if (deviceIndex == deviceIndex2 && appIndex == appIndex2 && + originIndex == originIndex2) { + EXPECT_EQ(firstSpoid, secondSpoid); + } else { + EXPECT_NE(firstSpoid, secondSpoid); + } + } + } + } + } + } + } + } + } +} + +TEST_F(WVDrmPluginHalTest, ReturnsSameL1Spoid) { + std::string kL1DeviceId = kDeviceId + "L1"; + constexpr size_t kSpoidQuery = 2; + std::vector spoid[kSpoidQuery]; + + android::sp> cdm = new StrictMock(); + ASSERT_TRUE(cdm) << "Failed to create mocked CDM"; + + EXPECT_CALL(*cdm, QueryStatus(wvcdm::kLevelDefault, QUERY_KEY_DEVICE_ID, _)) + .Times(kSpoidQuery) + .WillRepeatedly(DoAll(SetArgPointee<2>(kL1DeviceId), + testing::Return(wvcdm::NO_ERROR))); + + // Provide expected behavior to support session creation + EXPECT_CALL(*cdm, OpenSession(StrEq("com.widevine"), _, _, _, _)) + .Times(kSpoidQuery) + .WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId), + testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*cdm, QueryOemCryptoSessionId(mCdmSessionId, _)) + .Times(kSpoidQuery) + .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); + + EXPECT_CALL(*cdm, CloseSession(_)).Times(kSpoidQuery); + + // Open a session twice with the same security level, app package name and + // origin and make sure the spoids returned are the same + + const std::string kAppPackageName("com.google.widevine"); + StrictMock crypto; + + for (int i = 0; i < kSpoidQuery; ++i) { + std::shared_ptr plugin = + ::ndk::SharedRefBase::make( + cdm.get(), kAppPackageName.c_str(), &crypto, true); + ASSERT_TRUE(plugin) << "Failed to create drm plugin"; + + SessionId sessionId; + auto ret = plugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + + ret = plugin->closeSession(sessionId); + EXPECT_TRUE(ret.isOk()); + + ret = + plugin->getPropertyByteArray(std::string("deviceUniqueId"), &spoid[i]); + EXPECT_TRUE(ret.isOk()); + } + + EXPECT_EQ(spoid[0], spoid[1]); +} + +TEST_F(WVDrmPluginHalTest, ReturnsL3SpoidsWhenL3ProvisionedUsingL3Spoid) { + std::string kL3DeviceId = kDeviceId + "L3"; + constexpr size_t kSpoidQuery = 2; + std::vector spoid[kSpoidQuery]; + + android::sp> cdm = new StrictMock(); + ASSERT_TRUE(cdm) << "Failed to create mocked CDM"; + + EXPECT_CALL(*cdm, QueryStatus(wvcdm::kLevel3, QUERY_KEY_DEVICE_ID, _)) + .Times(kSpoidQuery) + .WillRepeatedly(DoAll(SetArgPointee<2>(kL3DeviceId), + testing::Return(wvcdm::NO_ERROR))); + + // Provide expected behavior to support session creation + EXPECT_CALL(*cdm, OpenSession(StrEq("com.widevine"), _, _, _, _)) + .Times(kSpoidQuery) + .WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId), + testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*cdm, QueryOemCryptoSessionId(mCdmSessionId, _)) + .Times(kSpoidQuery) + .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); + + EXPECT_CALL(*cdm, IsProvisioned(wvcdm::kSecurityLevelL3, _, _, _)) + .Times(kSpoidQuery) + .WillRepeatedly(testing::Return(true)); + + EXPECT_CALL(*cdm, CloseSession(_)).Times(kSpoidQuery); + + // The device is provisioned at L3 security level. Open a session twice + // and make sure that the spoids returned are the same when the + // session are opened at the same security level(L3), app package name + // and origin + + const std::string kAppPackageName("com.google.widevine"); + StrictMock crypto; + + for (size_t i = 0; i < kSpoidQuery; ++i) { + std::shared_ptr plugin = + ::ndk::SharedRefBase::make( + cdm.get(), kAppPackageName.c_str(), &crypto, true); + ASSERT_TRUE(plugin) << "Failed to create drm plugin"; + + // Forcing L3 + auto ret = plugin->setPropertyString(std::string("securityLevel"), + std::string("L3")); + EXPECT_TRUE(ret.isOk()); + + SessionId sessionId; + ret = plugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + + ret = plugin->closeSession(sessionId); + EXPECT_TRUE(ret.isOk()); + + std::vector result; + ret = plugin->getPropertyByteArray(std::string("deviceUniqueId"), &result); + EXPECT_TRUE(ret.isOk()); + spoid[i] = result; + } + + EXPECT_EQ(spoid[0], spoid[1]); +} + +TEST_F(WVDrmPluginHalTest, ReturnsL3SpoidsWhenL3Unprovisioned) { + std::string kL1DeviceId = kDeviceId + "L1"; + std::string kL3DeviceId = kDeviceId + "L3"; + constexpr size_t kSpoidQuery = 2; + std::vector spoid[kSpoidQuery]; + + android::sp> cdm = new StrictMock(); + ASSERT_TRUE(cdm) << "Failed to create mocked CDM"; + + EXPECT_CALL(*cdm, QueryStatus(wvcdm::kLevelDefault, QUERY_KEY_DEVICE_ID, _)) + .WillOnce(DoAll(SetArgPointee<2>(kL1DeviceId), + testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*cdm, QueryStatus(wvcdm::kLevel3, QUERY_KEY_DEVICE_ID, _)) + .Times(kSpoidQuery) + .WillRepeatedly(DoAll(SetArgPointee<2>(kL3DeviceId), + testing::Return(wvcdm::NO_ERROR))); + + // Provide expected behavior to support session creation + EXPECT_CALL(*cdm, OpenSession(StrEq("com.widevine"), _, _, _, _)) + .Times(kSpoidQuery) + .WillOnce(testing::Return(wvcdm::NEED_PROVISIONING)) + .WillOnce(DoAll(SetArgPointee<4>(mCdmSessionId), + testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*cdm, QueryOemCryptoSessionId(mCdmSessionId, _)) + .WillOnce(Invoke(setSessionIdOnMap<4>)); + + EXPECT_CALL(*cdm, IsProvisioned(wvcdm::kSecurityLevelL3, _, _, _)) + .WillOnce(testing::Return(false)) + .WillOnce(testing::Return(true)); + + EXPECT_CALL(*cdm, IsProvisioned(wvcdm::kSecurityLevelL1, _, _, _)) + .WillOnce(testing::Return(false)); + + EXPECT_CALL(*cdm, CloseSession(_)).Times(1); + + // This device is unprovisioned at the L3 security level. An attempt to + // open a session results in an ERROR_DRM_NOT_PROVISIONED error. + // Spoids are computed using device Unique IDs at each security level. + // Since provisioning has not occurred for either spoid, the spoid + // using the L3 device ID is used. + const std::string kAppPackageName("com.google.widevine"); + StrictMock crypto; + std::shared_ptr plugin = ::ndk::SharedRefBase::make( + cdm.get(), kAppPackageName.c_str(), &crypto, true); + ASSERT_TRUE(plugin) << "Failed to create drm plugin"; + + // Force L3 + auto ret = plugin->setPropertyString(std::string("securityLevel"), + std::string("L3")); + EXPECT_TRUE(ret.isOk()); + + SessionId sessionId; + ret = plugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_EQ(static_cast(Status::ERROR_DRM_NOT_PROVISIONED), + ret.getServiceSpecificError()); + + std::vector result; + ret = plugin->getPropertyByteArray(std::string("deviceUniqueId"), &result); + EXPECT_TRUE(ret.isOk()); + spoid[0] = result; + + // Try to open a session again. If provisioning took place, this time the + // attempt will be successful. Retrieve the spoid. This time only + // the device unique ID at the L3 security level will be queried. + // Confirm that it matches the spoid queried earlier. + plugin = ::ndk::SharedRefBase::make( + cdm.get(), kAppPackageName.c_str(), &crypto, true); + ASSERT_TRUE(plugin) << "Failed to create drm plugin"; + + // Force L3 + ret = plugin->setPropertyString(std::string("securityLevel"), + std::string("L3")); + EXPECT_TRUE(ret.isOk()); + + sessionId.clear(); + ret = plugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + + ret = plugin->closeSession(sessionId); + EXPECT_TRUE(ret.isOk()); + + result.clear(); + ret = plugin->getPropertyByteArray(std::string("deviceUniqueId"), &result); + EXPECT_TRUE(ret.isOk()); + spoid[1] = result; + + EXPECT_EQ(spoid[0], spoid[1]); +} + +TEST_F(WVDrmPluginHalTest, ReturnsL1SpoidsWhenL3ProvisionedUsingL1Spoid) { + std::string kL1DeviceId = kDeviceId + "L1"; + std::string kL3DeviceId = kDeviceId + "L3"; + constexpr size_t kSpoidQuery = 2; + std::vector spoidL1; + std::vector spoidL3[kSpoidQuery]; + + // Set expectations for the first mPlugin instance + android::sp> cdm1 = new StrictMock(); + ASSERT_TRUE(cdm1) << "Failed to create mocked CDM 1"; + + EXPECT_CALL(*cdm1, QueryStatus(wvcdm::kLevelDefault, QUERY_KEY_DEVICE_ID, _)) + .WillOnce(DoAll(SetArgPointee<2>(kL1DeviceId), + testing::Return(wvcdm::NO_ERROR))); + + // Provide expected behavior to support session creation + EXPECT_CALL(*cdm1, OpenSession(StrEq("com.widevine"), _, _, _, _)) + .WillOnce(DoAll(SetArgPointee<4>(mCdmSessionId), + testing::Return(wvcdm::NO_ERROR))) + .WillOnce(testing::Return(wvcdm::NEED_PROVISIONING)); + + EXPECT_CALL(*cdm1, QueryOemCryptoSessionId(mCdmSessionId, _)) + .WillOnce(Invoke(setSessionIdOnMap<4>)); + + EXPECT_CALL(*cdm1, CloseSession(_)); + + // Open a session at L1 security level. The spoid is now computed with + // the L1 device unique ID + const std::string kAppPackageName("com.google.widevine"); + StrictMock crypto; + std::shared_ptr plugin = ::ndk::SharedRefBase::make( + cdm1.get(), kAppPackageName.c_str(), &crypto, true); + ASSERT_TRUE(plugin) << "Failed to create drm plugin"; + + std::vector sessionId; + auto ret = plugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + + ret = plugin->closeSession(sessionId); + EXPECT_TRUE(ret.isOk()); + + std::vector result; + ret = plugin->getPropertyByteArray(std::string("deviceUniqueId"), &result); + EXPECT_TRUE(ret.isOk()); + spoidL1 = result; + + // Force L3. This should not be allowed since the spoid has been + // computed. We defer correcting this, as this might have an + // impact on apps. L3 is not provisioned so a call to openSession + // returns a ERROR_DRM_NOT_PROVISIONED error. No spoid computation takes + // place. + ret = plugin->setPropertyString(std::string("securityLevel"), + std::string("L3")); + EXPECT_TRUE(ret.isOk()); + + sessionId.clear(); + ret = plugin->openSession(SecurityLevel::DEFAULT, &sessionId); + ASSERT_EQ(static_cast(Status::ERROR_DRM_NOT_PROVISIONED), + ret.getServiceSpecificError()); + + result.clear(); + ret = plugin->getPropertyByteArray(std::string("deviceUniqueId"), &result); + EXPECT_TRUE(ret.isOk()); + spoidL3[0] = result; + + // Set expectations for the second mPlugin instance + android::sp> cdm2 = new StrictMock(); + ASSERT_TRUE(cdm2) << "Failed to create mocked CDM 2"; + + EXPECT_CALL(*cdm2, QueryStatus(wvcdm::kLevelDefault, QUERY_KEY_DEVICE_ID, _)) + .WillOnce(DoAll(SetArgPointee<2>(kL1DeviceId), + testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*cdm2, QueryStatus(wvcdm::kLevel3, QUERY_KEY_DEVICE_ID, _)) + .WillOnce(DoAll(SetArgPointee<2>(kL3DeviceId), + testing::Return(wvcdm::NO_ERROR))); + + // Provide expected behavior to support session creation + EXPECT_CALL(*cdm2, OpenSession(StrEq("com.widevine"), _, _, _, _)) + .WillOnce(DoAll(SetArgPointee<4>(mCdmSessionId), + testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*cdm2, QueryOemCryptoSessionId(mCdmSessionId, _)) + .WillOnce(Invoke(setSessionIdOnMap<4>)); + + EXPECT_CALL(*cdm2, IsProvisioned(wvcdm::kSecurityLevelL1, _, _, _)) + .WillOnce(testing::Return(true)); + + EXPECT_CALL(*cdm2, IsProvisioned(wvcdm::kSecurityLevelL3, _, _, _)) + .WillOnce(testing::Return(false)); + + EXPECT_CALL(*cdm2, CloseSession(_)); + + // Try to open a session again. If provisioning took place, this time the + // attempt will be successful. Spoids are computed using device unique IDs + // from both L1 and L3. The device is provisioned for L3 using the spoid with + // L1 but not L3 device unique ID, so the spoid with L1 device unique ID is + // used. + plugin = ::ndk::SharedRefBase::make( + cdm2.get(), kAppPackageName.c_str(), &crypto, true); + ASSERT_TRUE(plugin) << "Failed to create drm plugin"; + + // Force L3 + ret = plugin->setPropertyString(std::string("securityLevel"), + std::string("L3")); + EXPECT_TRUE(ret.isOk()); + + sessionId.clear(); + ret = plugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + + ret = plugin->closeSession(sessionId); + EXPECT_TRUE(ret.isOk()); + + result.clear(); + ret = plugin->getPropertyByteArray(std::string("deviceUniqueId"), &result); + EXPECT_TRUE(ret.isOk()); + spoidL3[1] = result; + + EXPECT_EQ(spoidL3[0], spoidL3[1]); + EXPECT_EQ(spoidL1, spoidL3[0]); +} + +TEST_F(WVDrmPluginHalTest, FailsGenericMethodsWithoutAnAlgorithmSet) { + std::vector keyId; + std::vector input; + std::vector iv; + std::vector signature; + + // Provide expected behavior to support session creation + EXPECT_CALL(*mCdm, OpenSession(StrEq("com.widevine"), _, _, _, _)) + .Times(AtLeast(1)) + .WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId), + testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) + .Times(AtLeast(1)) + .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); + + EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); + + SessionId sessionId; + auto ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + + // Note that we do not set the algorithms. This should cause these methods + // to fail. + + std::vector result; + ret = mPlugin->encrypt(sessionId, keyId, input, iv, &result); + // NO_INIT is converted to Status::ERROR_DRM_UNKNOWN + EXPECT_EQ(static_cast(Status::ERROR_DRM_UNKNOWN), + ret.getServiceSpecificError()); + + ret = mPlugin->decrypt(sessionId, keyId, input, iv, &result); + EXPECT_EQ(static_cast(Status::ERROR_DRM_UNKNOWN), + ret.getServiceSpecificError()); + + ret = mPlugin->sign(sessionId, keyId, input, &result); + EXPECT_EQ(static_cast(Status::ERROR_DRM_UNKNOWN), + ret.getServiceSpecificError()); + + bool match = false; + ret = mPlugin->verify(sessionId, keyId, input, signature, &match); + EXPECT_EQ(static_cast(Status::ERROR_DRM_UNKNOWN), + ret.getServiceSpecificError()); +} + +MATCHER_P(IsIV, iv, "") { + for (size_t i = 0; i < KEY_IV_SIZE; ++i) { + if (iv[i] != arg[i]) { + return false; + } + } + + return true; +} + +TEST_F(WVDrmPluginHalTest, CallsGenericEncrypt) { + static const size_t kDataSize = 256; + uint8_t keyIdRaw[KEY_ID_SIZE]; + uint8_t inputRaw[kDataSize]; + uint8_t ivRaw[KEY_IV_SIZE]; + + FILE *fp = fopen("/dev/urandom", "r"); + ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; + fread(keyIdRaw, sizeof(uint8_t), KEY_ID_SIZE, fp); + fread(inputRaw, sizeof(uint8_t), kDataSize, fp); + fread(ivRaw, sizeof(uint8_t), KEY_IV_SIZE, fp); + fclose(fp); + + std::vector keyId; + keyId.assign(keyIdRaw, keyIdRaw + KEY_ID_SIZE); + std::vector input; + input.assign(inputRaw, inputRaw + kDataSize); + std::vector iv; + iv.assign(ivRaw, ivRaw + KEY_IV_SIZE); + std::vector output; + + { + InSequence calls; + + EXPECT_CALL(mCrypto, selectKey(4, _, KEY_ID_SIZE)) + .With(Args<1, 2>(ElementsAreArray(keyIdRaw, KEY_ID_SIZE))) + .Times(1); + + EXPECT_CALL(mCrypto, encrypt(4, _, kDataSize, IsIV(ivRaw), + OEMCrypto_AES_CBC_128_NO_PADDING, _)) + .With(Args<1, 2>(ElementsAreArray(inputRaw, kDataSize))) + .Times(1); + } + + // Provide expected behavior to support session creation + EXPECT_CALL(*mCdm, OpenSession(StrEq("com.widevine"), _, _, _, _)) + .Times(AtLeast(1)) + .WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId), + testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) + .Times(AtLeast(1)) + .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); + + EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); + + SessionId sessionId; + auto ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + + ret = + mPlugin->setCipherAlgorithm(sessionId, std::string("AES/CBC/NoPadding")); + EXPECT_TRUE(ret.isOk()); + + ret = mPlugin->encrypt(sessionId, keyId, input, iv, &output); + EXPECT_TRUE(ret.isOk()); +} + +TEST_F(WVDrmPluginHalTest, CallsGenericDecrypt) { + static const size_t kDataSize = 256; + uint8_t keyIdRaw[KEY_ID_SIZE]; + uint8_t inputRaw[kDataSize]; + uint8_t ivRaw[KEY_IV_SIZE]; + + FILE *fp = fopen("/dev/urandom", "r"); + ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; + fread(keyIdRaw, sizeof(uint8_t), KEY_ID_SIZE, fp); + fread(inputRaw, sizeof(uint8_t), kDataSize, fp); + fread(ivRaw, sizeof(uint8_t), KEY_IV_SIZE, fp); + fclose(fp); + + std::vector keyId; + keyId.assign(keyIdRaw, keyIdRaw + KEY_ID_SIZE); + std::vector input; + input.assign(inputRaw, inputRaw + kDataSize); + std::vector iv; + iv.assign(ivRaw, ivRaw + KEY_IV_SIZE); + std::vector output; + + { + InSequence calls; + + EXPECT_CALL(mCrypto, selectKey(4, _, KEY_ID_SIZE)) + .With(Args<1, 2>(ElementsAreArray(keyIdRaw, KEY_ID_SIZE))) + .Times(1); + + EXPECT_CALL(mCrypto, decrypt(4, _, kDataSize, IsIV(ivRaw), + OEMCrypto_AES_CBC_128_NO_PADDING, _)) + .With(Args<1, 2>(ElementsAreArray(inputRaw, kDataSize))) + .Times(1); + } + + // Provide expected behavior to support session creation + EXPECT_CALL(*mCdm, OpenSession(StrEq("com.widevine"), _, _, _, _)) + .Times(AtLeast(1)) + .WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId), + testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) + .Times(AtLeast(1)) + .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); + + EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); + + SessionId sessionId; + auto ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + + ret = + mPlugin->setCipherAlgorithm(sessionId, std::string("AES/CBC/NoPadding")); + EXPECT_TRUE(ret.isOk()); + + ret = mPlugin->decrypt(sessionId, keyId, input, iv, &output); + EXPECT_TRUE(ret.isOk()); +} + +TEST_F(WVDrmPluginHalTest, CallsGenericSign) { + static const size_t kDataSize = 256; + uint8_t keyIdRaw[KEY_ID_SIZE]; + uint8_t messageRaw[kDataSize]; + + FILE *fp = fopen("/dev/urandom", "r"); + ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; + fread(keyIdRaw, sizeof(uint8_t), KEY_ID_SIZE, fp); + fread(messageRaw, sizeof(uint8_t), kDataSize, fp); + fclose(fp); + + std::vector keyId; + keyId.assign(keyIdRaw, keyIdRaw + KEY_ID_SIZE); + std::vector message; + message.assign(messageRaw, messageRaw + kDataSize); + std::vector signature; + + { + InSequence calls; + + EXPECT_CALL(mCrypto, selectKey(4, _, KEY_ID_SIZE)) + .With(Args<1, 2>(ElementsAreArray(keyIdRaw, KEY_ID_SIZE))) + .Times(1); + + EXPECT_CALL(mCrypto, + sign(4, _, kDataSize, OEMCrypto_HMAC_SHA256, _, Pointee(0))) + .With(Args<1, 2>(ElementsAreArray(messageRaw, kDataSize))) + .WillOnce(DoAll(SetArgPointee<5>(64), + testing::Return(OEMCrypto_ERROR_SHORT_BUFFER))); + + EXPECT_CALL(mCrypto, + sign(4, _, kDataSize, OEMCrypto_HMAC_SHA256, _, Pointee(64))) + .With(Args<1, 2>(ElementsAreArray(messageRaw, kDataSize))) + .Times(1); + } + + // Provide expected behavior to support session creation + EXPECT_CALL(*mCdm, OpenSession(StrEq("com.widevine"), _, _, _, _)) + .Times(AtLeast(1)) + .WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId), + testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) + .Times(AtLeast(1)) + .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); + + EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); + + SessionId sessionId; + auto ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + + ret = mPlugin->setMacAlgorithm(sessionId, std::string("HmacSHA256")); + EXPECT_TRUE(ret.isOk()); + + ret = mPlugin->sign(sessionId, keyId, message, &signature); + EXPECT_TRUE(ret.isOk()); + ASSERT_NE(0u, signature.size()); +} + +TEST_F(WVDrmPluginHalTest, CallsGenericVerify) { + static const size_t kDataSize = 256; + static const size_t kSignatureSize = 16; + uint8_t keyIdRaw[KEY_ID_SIZE]; + uint8_t messageRaw[kDataSize]; + uint8_t signatureRaw[kSignatureSize]; + + FILE *fp = fopen("/dev/urandom", "r"); + ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; + fread(keyIdRaw, sizeof(uint8_t), KEY_ID_SIZE, fp); + fread(messageRaw, sizeof(uint8_t), kDataSize, fp); + fread(signatureRaw, sizeof(uint8_t), kSignatureSize, fp); + fclose(fp); + + std::vector keyId; + keyId.assign(keyIdRaw, keyIdRaw + KEY_ID_SIZE); + std::vector message; + message.assign(messageRaw, messageRaw + kDataSize); + std::vector signature; + signature.assign(signatureRaw, signatureRaw + kSignatureSize); + + { + InSequence calls; + + EXPECT_CALL(mCrypto, selectKey(4, _, KEY_ID_SIZE)) + .With(Args<1, 2>(ElementsAreArray(keyIdRaw, KEY_ID_SIZE))) + .Times(1); + + EXPECT_CALL(mCrypto, verify(4, _, kDataSize, OEMCrypto_HMAC_SHA256, _, + kSignatureSize)) + .With(AllOf(Args<1, 2>(ElementsAreArray(messageRaw, kDataSize)), + Args<4, 5>(ElementsAreArray(signatureRaw, kSignatureSize)))) + .WillOnce(testing::Return(OEMCrypto_SUCCESS)); + + EXPECT_CALL(mCrypto, selectKey(4, _, KEY_ID_SIZE)) + .With(Args<1, 2>(ElementsAreArray(keyIdRaw, KEY_ID_SIZE))) + .Times(1); + + EXPECT_CALL(mCrypto, verify(4, _, kDataSize, OEMCrypto_HMAC_SHA256, _, + kSignatureSize)) + .With(AllOf(Args<1, 2>(ElementsAreArray(messageRaw, kDataSize)), + Args<4, 5>(ElementsAreArray(signatureRaw, kSignatureSize)))) + .WillOnce(testing::Return(OEMCrypto_ERROR_SIGNATURE_FAILURE)); + } + + // Provide expected behavior to support session creation + EXPECT_CALL(*mCdm, OpenSession(StrEq("com.widevine"), _, _, _, _)) + .Times(AtLeast(1)) + .WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId), + testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) + .Times(AtLeast(1)) + .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); + + EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); + + SessionId sessionId; + auto ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + + ret = mPlugin->setMacAlgorithm(sessionId, std::string("HmacSHA256")); + EXPECT_TRUE(ret.isOk()); + + bool match = false; + ret = mPlugin->verify(sessionId, keyId, message, signature, &match); + EXPECT_TRUE(ret.isOk()); + EXPECT_TRUE(match); + + ret = mPlugin->verify(sessionId, keyId, message, signature, &match); + EXPECT_TRUE(ret.isOk()); + EXPECT_FALSE(match); +} + +TEST_F(WVDrmPluginHalTest, RegistersForEvents) { + // Provide expected behavior to support session creation + EXPECT_CALL(*mCdm, OpenSession(StrEq("com.widevine"), _, _, _, _)) + .Times(AtLeast(1)) + .WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId), + testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) + .Times(AtLeast(1)) + .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); + + EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); + + SessionId sessionId; + auto ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); +} + +TEST_F(WVDrmPluginHalTest, UnregistersForAllEventsOnDestruction) { + uint8_t sessionIdRaw1[kSessionIdSize]; + uint8_t sessionIdRaw2[kSessionIdSize]; + FILE *fp = fopen("/dev/urandom", "r"); + ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; + fread(sessionIdRaw1, sizeof(uint8_t), kSessionIdSize, fp); + fread(sessionIdRaw2, sizeof(uint8_t), kSessionIdSize, fp); + fclose(fp); + + CdmSessionId mCdmSessionId1(sessionIdRaw1, sessionIdRaw1 + kSessionIdSize); + CdmSessionId mCdmSessionId2(sessionIdRaw2, sessionIdRaw2 + kSessionIdSize); + + EXPECT_CALL(*mCdm, OpenSession(StrEq("com.widevine"), _, _, _, _)) + .WillOnce(DoAll(SetArgPointee<4>(mCdmSessionId1), + testing::Return(wvcdm::NO_ERROR))) + .WillOnce(DoAll(SetArgPointee<4>(mCdmSessionId2), + testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId1, _)) + .WillOnce(Invoke(setSessionIdOnMap<4>)); + + EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId2, _)) + .WillOnce(Invoke(setSessionIdOnMap<5>)); + + EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); + + { + SessionId sessionId; + auto ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + + ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + } +} + +TEST_F(WVDrmPluginHalTest, MarshalsEvents) { + const int64_t kExpiryTimeInSeconds = 123456789012LL; + std::string kKeyId1 = "Testing Key1 Id "; + std::string kKeyId2 = "Testing Key2 Id "; + std::string kKeyId3 = "Testing Key3 Id "; + std::string kKeyId4 = "Testing Key4 Id "; + std::string kKeyId5 = "Testing Key5 Id "; + + { + InSequence calls; + + std::vector keyId; + std::vector keyStatusList; + + KeyStatus keyStatus; + keyId.assign(kKeyId1.begin(), kKeyId1.end()); + keyStatus.keyId = keyId; + keyStatus.type = KeyStatusType::EXPIRED; + keyStatusList.push_back(keyStatus); + + std::vector hKeyStatusList = keyStatusList; + + keyStatusList.clear(); + keyId.clear(); + keyId.assign(kKeyId1.begin(), kKeyId1.end()); + keyStatus.type = KeyStatusType::USABLE; + keyStatusList.push_back(keyStatus); + keyId.assign(kKeyId2.begin(), kKeyId2.end()); + keyStatus.type = KeyStatusType::OUTPUT_NOT_ALLOWED; + keyStatusList.push_back(keyStatus); + keyId.assign(kKeyId3.begin(), kKeyId3.end()); + keyStatus.type = KeyStatusType::INTERNAL_ERROR; + keyStatusList.push_back(keyStatus); + keyId.assign(kKeyId4.begin(), kKeyId4.end()); + keyStatus.type = KeyStatusType::STATUS_PENDING; + keyStatusList.push_back(keyStatus); + keyId.assign(kKeyId5.begin(), kKeyId5.end()); + keyStatus.type = KeyStatusType::USABLE_IN_FUTURE; + keyStatusList.push_back(keyStatus); + + std::vector hKeyStatusList2 = keyStatusList; + } + + CdmKeyStatusMap mCdmKeysStatus; + mCdmKeysStatus[kKeyId1] = wvcdm::kKeyStatusExpired; + mPlugin->OnSessionKeysChange(mCdmSessionId, mCdmKeysStatus, false); + + mPlugin->OnSessionRenewalNeeded(mCdmSessionId); + mPlugin->OnExpirationUpdate(mCdmSessionId, NEVER_EXPIRES); + mPlugin->OnExpirationUpdate(mCdmSessionId, kExpiryTimeInSeconds); + + mCdmKeysStatus[kKeyId1] = wvcdm::kKeyStatusUsable; + mCdmKeysStatus[kKeyId2] = wvcdm::kKeyStatusOutputNotAllowed; + mCdmKeysStatus[kKeyId3] = wvcdm::kKeyStatusInternalError; + mCdmKeysStatus[kKeyId4] = wvcdm::kKeyStatusPending; + mCdmKeysStatus[kKeyId5] = wvcdm::kKeyStatusUsableInFuture; + mPlugin->OnSessionKeysChange(mCdmSessionId, mCdmKeysStatus, true); +} + +TEST_F(WVDrmPluginHalTest, GeneratesProvisioningNeededEvent) { + EXPECT_CALL(*mCdm, OpenSession(StrEq("com.widevine"), _, _, _, _)) + .Times(AtLeast(1)) + .WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId), + testing::Return(wvcdm::NEED_PROVISIONING))); + + EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); + + SessionId sessionId; + auto ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_EQ(static_cast(Status::ERROR_DRM_NOT_PROVISIONED), + ret.getServiceSpecificError()); +} + +TEST_F(WVDrmPluginHalTest, ProvidesExpectedDefaultPropertiesToCdm) { + const CdmClientPropertySet *propertySet = nullptr; + + // Provide expected mock behavior + { + // Provide expected behavior in response to OpenSession and store the + // property set + EXPECT_CALL(*mCdm, OpenSession(_, _, _, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId), + SaveArg<1>(&propertySet), + testing::Return(wvcdm::NO_ERROR))); + + // Provide expected behavior when mPlugin requests session control info + EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) + .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); + + EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); + } + + SessionId sessionId; + auto ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + + ASSERT_THAT(propertySet, NotNull()); + EXPECT_STREQ("", propertySet->security_level().c_str()); + EXPECT_FALSE(propertySet->use_privacy_mode()); + EXPECT_EQ(0u, propertySet->service_certificate().size()); + EXPECT_FALSE(propertySet->is_session_sharing_enabled()); + EXPECT_EQ(0u, propertySet->session_sharing_id()); + EXPECT_STREQ("", propertySet->app_id().c_str()); +} + +TEST_F(WVDrmPluginHalTest, CanSetAppId) { + const CdmClientPropertySet *propertySet = nullptr; + + // Provide expected mock behavior + { + // Provide expected behavior in response to OpenSession and store the + // property set + EXPECT_CALL(*mCdm, OpenSession(_, _, _, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId), + SaveArg<1>(&propertySet), + testing::Return(wvcdm::NO_ERROR))); + + // Provide expected behavior when mPlugin queries for the security level + EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_SECURITY_LEVEL, _)) + .WillRepeatedly(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L3), + testing::Return(wvcdm::NO_ERROR))); + + // Provide expected behavior when mPlugin requests session control info + EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) + .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); + + EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); + } + + // Test setting an empty string + auto ret = mPlugin->setPropertyString(std::string("appId"), std::string("")); + EXPECT_TRUE(ret.isOk()); + + // Test setting an application id before a session is opened. + ret = mPlugin->setPropertyString(std::string("appId"), std::string(kAppId)); + EXPECT_TRUE(ret.isOk()); + + SessionId sessionId; + ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + ASSERT_THAT(propertySet, NotNull()); + + // Verify application id is set correctly. + EXPECT_STREQ(kAppId.c_str(), propertySet->app_id().c_str()); + + // Test setting application id while session is opened, this should fail. + ret = mPlugin->setPropertyString(std::string("appId"), std::string(kAppId)); + EXPECT_EQ(static_cast(Status::ERROR_DRM_UNKNOWN), + ret.getServiceSpecificError()); +} + +TEST_P(WVDrmPluginHalOriginTest, CanSetOrigin) { + OriginTestVariant params = GetParam(); + + // Provide expected mock behavior + { + // Provide expected behavior when mPlugin requests session control info + EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) + .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); + + // Provide expected behavior when mPlugin closes a session + EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); + } + + // Note which mock calls we expect + EXPECT_CALL(*mCdm, OpenSession(_, _, HasOrigin(params.expectedOrigin), _, _)) + .WillOnce(DoAll(SetArgPointee<4>(mCdmSessionId), + testing::Return(wvcdm::NO_ERROR))); + + // Set the properties & run the test + + if (!params.origin.empty()) { + auto ret = mPlugin->setPropertyString(std::string("origin"), + std::string(params.origin)); + EXPECT_TRUE(ret.isOk()); + } + + SessionId sessionId; + auto ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + + // Test setting an origin while sessions are opened. This should fail. + ret = mPlugin->setPropertyString(std::string("origin"), std::string(kOrigin)); + EXPECT_FALSE(ret.isOk()); + ret = mPlugin->closeSession(sessionId); + EXPECT_TRUE(ret.isOk()); +} + +INSTANTIATE_TEST_CASE_P( + OriginTests, WVDrmPluginHalOriginTest, + Values(OriginTestVariant("No Origin", kEmptyString, EMPTY_ORIGIN), + OriginTestVariant("With an Origin", kOrigin, kOrigin.c_str()))); + +TEST_F(WVDrmPluginHalTest, CanSetSecurityLevel) { + const CdmClientPropertySet *propertySet = nullptr; + + EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_SECURITY_LEVEL, _)) + .WillOnce(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L3), + testing::Return(wvcdm::NO_ERROR))) + .WillOnce(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L1), + testing::Return(wvcdm::NO_ERROR))); + + // Provide expected mock behavior + { + // Provide expected behavior in response to OpenSession and store the + // property set + EXPECT_CALL(*mCdm, OpenSession(_, _, _, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId), + SaveArg<1>(&propertySet), + testing::Return(wvcdm::NO_ERROR))); + + // Provide expected behavior when mPlugin requests session control info + EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) + .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); + + EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); + } + + // Test forcing L3 + auto ret = mPlugin->setPropertyString(std::string("securityLevel"), + std::string("L3")); + EXPECT_TRUE(ret.isOk()); + + SessionId sessionId; + ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + + ASSERT_THAT(propertySet, NotNull()); + EXPECT_STREQ("L3", propertySet->security_level().c_str()); + ret = mPlugin->closeSession(sessionId); + EXPECT_TRUE(ret.isOk()); + + // Test returning to L1 on an L3 device (Should Fail) + ret = mPlugin->setPropertyString(std::string("securityLevel"), + std::string("L1")); + EXPECT_FALSE(ret.isOk()); + + sessionId.clear(); + ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + + ASSERT_THAT(propertySet, NotNull()); + EXPECT_STREQ("L3", propertySet->security_level().c_str()); + ret = mPlugin->closeSession(sessionId); + EXPECT_TRUE(ret.isOk()); + + // Test returning to L1 on an L1 device + ret = mPlugin->setPropertyString(std::string("securityLevel"), + std::string("L1")); + EXPECT_TRUE(ret.isOk()); + + sessionId.clear(); + ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + + ASSERT_THAT(propertySet, NotNull()); + EXPECT_STREQ("", propertySet->security_level().c_str()); + ret = mPlugin->closeSession(sessionId); + EXPECT_TRUE(ret.isOk()); + + // Test un-forcing a level (first forcing to L3 so we have something to reset) + ret = mPlugin->setPropertyString(std::string("securityLevel"), + std::string("L3")); + EXPECT_TRUE(ret.isOk()); + + sessionId.clear(); + ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + + ASSERT_THAT(propertySet, NotNull()); + EXPECT_STREQ("L3", propertySet->security_level().c_str()); + ret = mPlugin->closeSession(sessionId); + EXPECT_TRUE(ret.isOk()); + + ret = + mPlugin->setPropertyString(std::string("securityLevel"), std::string("")); + EXPECT_TRUE(ret.isOk()); + + sessionId.clear(); + ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + + ASSERT_THAT(propertySet, NotNull()); + EXPECT_STREQ("", propertySet->security_level().c_str()); + ret = mPlugin->closeSession(sessionId); + EXPECT_TRUE(ret.isOk()); + + // Test nonsense (Should Fail) + ret = mPlugin->setPropertyString(std::string("securityLevel"), + std::string("nonsense")); + EXPECT_FALSE(ret.isOk()); + + sessionId.clear(); + ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + + ASSERT_THAT(propertySet, NotNull()); + EXPECT_STREQ("", propertySet->security_level().c_str()); + ret = mPlugin->closeSession(sessionId); + EXPECT_TRUE(ret.isOk()); + + // Test attempting to force a level with a session open (Should Fail) + sessionId.clear(); + ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + + ret = mPlugin->setPropertyString(std::string("securityLevel"), + std::string("L3")); + EXPECT_FALSE(ret.isOk()); +} + +TEST_F(WVDrmPluginHalTest, CanSetPrivacyMode) { + const CdmClientPropertySet *propertySet = nullptr; + + // Provide expected mock behavior + { + // Provide expected behavior in response to OpenSession and store the + // property set + EXPECT_CALL(*mCdm, OpenSession(_, _, _, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId), + SaveArg<1>(&propertySet), + testing::Return(wvcdm::NO_ERROR))); + + // Provide expected behavior when mPlugin requests session control info + EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) + .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); + + EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); + } + + SessionId sessionId; + auto ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + ASSERT_THAT(propertySet, NotNull()); + + // Test turning on privacy mode + ret = mPlugin->setPropertyString(std::string("privacyMode"), + std::string("enable")); + EXPECT_TRUE(ret.isOk()); + EXPECT_TRUE(propertySet->use_privacy_mode()); + + // Test turning off privacy mode + ret = mPlugin->setPropertyString(std::string("privacyMode"), + std::string("disable")); + EXPECT_TRUE(ret.isOk()); + EXPECT_FALSE(propertySet->use_privacy_mode()); + + // Test nonsense (Should Fail) + ret = mPlugin->setPropertyString(std::string("privacyMode"), + std::string("nonsense")); + EXPECT_FALSE(ret.isOk()); +} + +TEST_F(WVDrmPluginHalTest, CanSetServiceCertificate) { + const CdmClientPropertySet *propertySet = nullptr; + + static const size_t kPrivacyCertSize = 256; + uint8_t privacyCertRaw[kPrivacyCertSize]; + + FILE *fp = fopen("/dev/urandom", "r"); + ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; + fread(privacyCertRaw, sizeof(uint8_t), kPrivacyCertSize, fp); + fclose(fp); + + std::vector privacyCert; + privacyCert.assign(privacyCertRaw, privacyCertRaw + kPrivacyCertSize); + std::string strPrivacyCert(reinterpret_cast(privacyCertRaw), + kPrivacyCertSize); + std::vector emptyVector; + + // Provide expected mock behavior + { + // Provide expected behavior in response to OpenSession and store the + // property set + EXPECT_CALL(*mCdm, OpenSession(_, _, _, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId), + SaveArg<1>(&propertySet), + testing::Return(wvcdm::NO_ERROR))); + + // Provide expected behavior when mPlugin requests session control info + EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) + .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); + + EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); + } + + // Validate that the certificate is validated. Accept it once and reject it + // once. Note that there is no expected call for when the certificate is + // cleared. + EXPECT_CALL(*mCdm, IsValidServiceCertificate(strPrivacyCert)) + .WillOnce(testing::Return(true)) + .WillOnce(testing::Return(false)); + + SessionId sessionId; + auto ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + ASSERT_THAT(propertySet, NotNull()); + + // Test setting a certificate + ret = mPlugin->setPropertyByteArray(std::string("serviceCertificate"), + privacyCert); + EXPECT_TRUE(ret.isOk()); + EXPECT_THAT(propertySet->service_certificate(), + ElementsAreArray(privacyCertRaw, kPrivacyCertSize)); + + // Test clearing a certificate + ret = mPlugin->setPropertyByteArray(std::string("serviceCertificate"), + emptyVector); + EXPECT_TRUE(ret.isOk()); + EXPECT_EQ(0u, propertySet->service_certificate().size()); + + // Test setting a certificate and having it fail + ret = mPlugin->setPropertyByteArray(std::string("serviceCertificate"), + privacyCert); + EXPECT_FALSE(ret.isOk()); + EXPECT_EQ(0u, propertySet->service_certificate().size()); +} + +TEST_F(WVDrmPluginHalTest, CanSetSessionSharing) { + const CdmClientPropertySet *propertySet = nullptr; + + // Provide expected mock behavior + { + // Provide expected behavior in response to OpenSession and store the + // property set + EXPECT_CALL(*mCdm, OpenSession(_, _, _, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId), + SaveArg<1>(&propertySet), + testing::Return(wvcdm::NO_ERROR))); + + // Provide expected behavior when mPlugin requests session control info + EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) + .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); + + EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); + } + + // Test turning on session sharing + auto ret = mPlugin->setPropertyString(std::string("sessionSharing"), + std::string("enable")); + EXPECT_TRUE(ret.isOk()); + + SessionId sessionId; + ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + ASSERT_THAT(propertySet, NotNull()); + EXPECT_TRUE(propertySet->is_session_sharing_enabled()); + ret = mPlugin->closeSession(sessionId); + EXPECT_TRUE(ret.isOk()); + + // Test turning off session sharing + ret = mPlugin->setPropertyString(std::string("sessionSharing"), + std::string("disable")); + EXPECT_TRUE(ret.isOk()); + + sessionId.clear(); + ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + ASSERT_THAT(propertySet, NotNull()); + EXPECT_FALSE(propertySet->is_session_sharing_enabled()); + ret = mPlugin->closeSession(sessionId); + EXPECT_TRUE(ret.isOk()); + + // Test nonsense (Should Fail) + ret = mPlugin->setPropertyString(std::string("sessionSharing"), + std::string("nonsense")); + EXPECT_FALSE(ret.isOk()); + + // Test changing sharing with a session open (Should Fail) + sessionId.clear(); + ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + ret = mPlugin->setPropertyString(std::string("sessionSharing"), + std::string("enable")); + EXPECT_FALSE(ret.isOk()); + + ret = mPlugin->closeSession(sessionId); + EXPECT_TRUE(ret.isOk()); +} + +TEST_F(WVDrmPluginHalTest, AllowsStoringOfSessionSharingId) { + CdmClientPropertySet *propertySet = nullptr; + + uint32_t sharingId; + FILE *fp = fopen("/dev/urandom", "r"); + ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; + fread(&sharingId, sizeof(uint32_t), 1, fp); + fclose(fp); + + // Provide expected mock behavior + { + // Provide expected behavior in response to OpenSession and store the + // property set + EXPECT_CALL(*mCdm, OpenSession(_, _, _, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId), + SaveArg<1>(&propertySet), + testing::Return(wvcdm::NO_ERROR))); + + // Provide expected behavior when mPlugin requests session control info + EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) + .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); + + EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); + } + + SessionId sessionId; + auto ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + ASSERT_THAT(propertySet, NotNull()); + propertySet->set_session_sharing_id(sharingId); + EXPECT_EQ(sharingId, propertySet->session_sharing_id()); +} + +TEST_F(WVDrmPluginHalTest, CanSetAtscMode) { + android::sp> cdm = new StrictMock(); + StrictMock crypto; + std::string appPackageName = "com.test.package"; + + const CdmClientPropertySet *propertySet = nullptr; + CdmIdentifier mCdmIdAtscModeNotSet; + CdmIdentifier mCdmIdAtscModeSet; + CdmIdentifier mCdmIdAtscModeReset; + + // Provide expected mock behavior + { + // Provide expected behavior in response to OpenSession and store the + // property set + EXPECT_CALL(*cdm, OpenSession(_, _, _, _, _)) + .WillOnce(DoAll(SetArgPointee<4>(mCdmSessionId), + SaveArg<1>(&propertySet), + SaveArg<2>(&mCdmIdAtscModeNotSet), + testing::Return(wvcdm::NO_ERROR))) + .WillOnce(DoAll( + SetArgPointee<4>(mCdmSessionId), SaveArg<1>(&propertySet), + SaveArg<2>(&mCdmIdAtscModeSet), testing::Return(wvcdm::NO_ERROR))) + .WillOnce(DoAll(SetArgPointee<4>(mCdmSessionId), + SaveArg<1>(&propertySet), + SaveArg<2>(&mCdmIdAtscModeReset), + testing::Return(wvcdm::NO_ERROR))); + + // Provide expected behavior when mPlugin requests session control info + EXPECT_CALL(*cdm, QueryOemCryptoSessionId(mCdmSessionId, _)) + .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); + + EXPECT_CALL(*cdm, CloseSession(_)).Times(AtLeast(0)); + } + + std::shared_ptr plugin1 = + ::ndk::SharedRefBase::make(cdm.get(), appPackageName.c_str(), + &crypto, false); + ASSERT_TRUE(plugin1) << "Failed to create first drm plugin"; + + // Verify that ATSC mode is disabled by default + SessionId sessionId; + auto ret = plugin1->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + ASSERT_TRUE(plugin1); + ASSERT_THAT(propertySet, NotNull()); + EXPECT_FALSE(propertySet->use_atsc_mode()); + EXPECT_EQ(mCdmIdAtscModeNotSet.app_package_name, appPackageName); + ret = plugin1->closeSession(sessionId); + EXPECT_TRUE(ret.isOk()); + + // Verify that ATSC mode can be enabled + std::shared_ptr plugin2 = + ::ndk::SharedRefBase::make(cdm.get(), appPackageName.c_str(), + &crypto, false); + ASSERT_TRUE(plugin2) << "Failed to create first drm plugin"; + + // Test turning on ATSC mode + ret = plugin2->setPropertyString(std::string("atscMode"), + std::string("enable")); + EXPECT_TRUE(ret.isOk()); + + ret = plugin2->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + ASSERT_THAT(propertySet, NotNull()); + EXPECT_TRUE(propertySet->use_atsc_mode()); + EXPECT_EQ(mCdmIdAtscModeSet.app_package_name, wvcdm::ATSC_APP_PACKAGE_NAME); + ret = plugin2->closeSession(sessionId); + EXPECT_TRUE(ret.isOk()); + + // Verify that ATSC mode can be enabled and disabled + std::shared_ptr plugin3 = + ::ndk::SharedRefBase::make(cdm.get(), appPackageName.c_str(), + &crypto, false); + ASSERT_TRUE(plugin3) << "Failed to create first drm plugin"; + + // Test turning on ATSC mode + ret = plugin3->setPropertyString(std::string("atscMode"), + std::string("enable")); + EXPECT_TRUE(ret.isOk()); + + // Test turning off ATSC mode + ret = plugin3->setPropertyString(std::string("atscMode"), + std::string("disable")); + EXPECT_TRUE(ret.isOk()); + + ret = plugin3->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + ASSERT_THAT(propertySet, NotNull()); + EXPECT_FALSE(propertySet->use_atsc_mode()); + EXPECT_EQ(mCdmIdAtscModeReset.app_package_name, appPackageName); + ret = plugin3->closeSession(sessionId); + EXPECT_TRUE(ret.isOk()); + + // Test turning on and off ATSC mode. They should be rejected since the SPOID + // has been calculated + ret = plugin3->setPropertyString(std::string("atscMode"), + std::string("enable")); + EXPECT_FALSE(ret.isOk()); + + ret = plugin3->setPropertyString(std::string("atscMode"), + std::string("disable")); + EXPECT_FALSE(ret.isOk()); +} + +TEST_F(WVDrmPluginHalTest, CanSetDecryptHashProperties) { + // Provide expected mock behavior + { + // Provide expected behavior in response to OpenSession and store the + // property set + EXPECT_CALL(*mCdm, OpenSession(_, _, _, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId), + testing::Return(wvcdm::NO_ERROR))); + + // Provide expected behavior when mPlugin requests session control info + EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) + .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); + + EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); + } + + std::vector sessionId(sessionIdRaw, sessionIdRaw + kSessionIdSize); + auto ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + + // CDM expects the string property value to be in the following format: + // ",," + static const std::string frameNumber = ",1"; + uint32_t hash = 0xbeef; // crc32 hash + const std::vector hashVector(reinterpret_cast(&hash), + reinterpret_cast(&hash) + + sizeof(uint32_t)); + const std::string base64EncodedHash = wvutil::Base64Encode(hashVector); + std::string computedHash(sessionId.begin(), sessionId.end()); + computedHash.append(frameNumber.c_str()); + computedHash.append(base64EncodedHash.c_str()); + ret = mPlugin->setPropertyString(std::string("decryptHash"), + std::string(computedHash)); + EXPECT_FALSE(ret.isOk()); + + ret = mPlugin->closeSession(sessionId); + EXPECT_TRUE(ret.isOk()); +} + +TEST_F(WVDrmPluginHalTest, DoesNotSetDecryptHashProperties) { + // Provide expected mock behavior + { + // Provide expected behavior in response to OpenSession and store the + // property set + EXPECT_CALL(*mCdm, OpenSession(_, _, _, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId), + testing::Return(wvcdm::NO_ERROR))); + + // Provide expected behavior when mPlugin requests session control info + EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _)) + .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); + + EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0)); + } + + std::vector sessionId(sessionIdRaw, sessionIdRaw + kSessionIdSize); + auto ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId); + EXPECT_TRUE(ret.isOk()); + + // CDM expects the string property value to be in the following format: + // ",," + static const std::string frameNumber = ",1"; + static const std::string hash = ",AZaz0+,/"; + std::string value(sessionId.begin(), sessionId.end()); + value.append(frameNumber.c_str()); + + // Tests for missing token handling + ret = mPlugin->setPropertyString(std::string("decryptHash"), + std::string(value)); + EXPECT_FALSE(ret.isOk()); + + // Tests for empty token + value.append(","); + ret = mPlugin->setPropertyString(std::string("decryptHash"), + std::string(value)); + EXPECT_FALSE(ret.isOk()); + + // Tests for invalid sessionId + value.clear(); + value.append("bad session id"); + value.append(",1"); + value.append(hash.c_str()); + ret = mPlugin->setPropertyString(std::string("decryptHash"), + std::string(value)); + EXPECT_FALSE(ret.isOk()); + + // Tests for malformed Base64encode hash, with a "," + std::string computedHash(sessionId.begin(), sessionId.end()); + computedHash.append(frameNumber.c_str()); + computedHash.append(hash.c_str()); + ret = mPlugin->setPropertyString(std::string("decryptHash"), + std::string(computedHash)); + EXPECT_FALSE(ret.isOk()); + + ret = mPlugin->closeSession(sessionId); + EXPECT_TRUE(ret.isOk()); +} + +TEST_F(WVDrmPluginHalTest, GetOfflineLicenseIds) { + const uint32_t kLicenseCount = 5; + + uint8_t mockIdsRaw[kLicenseCount * 2][kKeySetIdSize]; + FILE *fp = fopen("/dev/urandom", "r"); + ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; + for (uint32_t i = 0; i < kLicenseCount * 2; ++i) { + fread(mockIdsRaw[i], sizeof(uint8_t), kKeySetIdSize, fp); + } + fclose(fp); + + std::vector mockIdsL1; + for (uint32_t i = 0; i < kLicenseCount; ++i) { + mockIdsL1.push_back( + std::string(mockIdsRaw[i], mockIdsRaw[i] + kKeySetIdSize)); + } + + std::vector mockIdsL3; + for (uint32_t i = 0; i < kLicenseCount; ++i) { + mockIdsL3.push_back( + std::string(mockIdsRaw[i + 5], mockIdsRaw[i + 5] + kKeySetIdSize)); + } + + EXPECT_CALL(*mCdm, + ListStoredLicenses(kSecurityLevelL1, HasOrigin(EMPTY_ORIGIN), _)) + .WillOnce( + DoAll(SetArgPointee<2>(mockIdsL1), testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*mCdm, + ListStoredLicenses(kSecurityLevelL3, HasOrigin(EMPTY_ORIGIN), _)) + .WillOnce( + DoAll(SetArgPointee<2>(mockIdsL3), testing::Return(wvcdm::NO_ERROR))); + + std::vector offlineIds; + auto ret = mPlugin->getOfflineLicenseKeySetIds(&offlineIds); + EXPECT_TRUE(ret.isOk()); + + size_t index = 0; + for (auto id : offlineIds) { + EXPECT_THAT(id.keySetId, + ElementsAreArray(mockIdsRaw[index++], kKeySetIdSize)); + } + EXPECT_EQ(kLicenseCount * 2, index); +} + +TEST_F(WVDrmPluginHalTest, GetOfflineLicenseState) { + EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_SECURITY_LEVEL, _)) + .WillRepeatedly(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L1), + testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*mCdm, GetOfflineLicenseState(_, kSecurityLevelL1, + HasOrigin(EMPTY_ORIGIN), _)) + .WillOnce(DoAll(SetArgPointee<3>(wvcdm::kLicenseStateActive), + testing::Return(wvcdm::NO_ERROR))) + .WillOnce(DoAll(SetArgPointee<3>(wvcdm::kLicenseStateReleasing), + testing::Return(wvcdm::NO_ERROR))) + .WillOnce(DoAll(SetArgPointee<3>(wvcdm::kLicenseStateUnknown), + testing::Return(wvcdm::NO_ERROR))); + + auto ret = mPlugin->setPropertyString(std::string("securityLevel"), + std::string("L1")); + EXPECT_TRUE(ret.isOk()); + + OfflineLicenseState result; + ret = mPlugin->getOfflineLicenseState(keySetId, &result); + EXPECT_TRUE(ret.isOk()); + ASSERT_EQ(OfflineLicenseState::USABLE, result); + + ret = mPlugin->getOfflineLicenseState(keySetId, &result); + EXPECT_TRUE(ret.isOk()); + ASSERT_EQ(OfflineLicenseState::INACTIVE, result); + + ret = mPlugin->getOfflineLicenseState(keySetId, &result); + EXPECT_TRUE(ret.isOk()); + ASSERT_EQ(OfflineLicenseState::UNKNOWN, result); +} + +TEST_F(WVDrmPluginHalTest, RemoveOfflineLicense) { + EXPECT_CALL( + *mCdm, RemoveOfflineLicense(_, kSecurityLevelL1, HasOrigin(EMPTY_ORIGIN))) + .Times(1); + + auto ret = mPlugin->removeOfflineLicense(keySetId); + EXPECT_TRUE(ret.isOk()); +} + +} // namespace widevine +} // namespace drm +} // namespace hardware +} // namespace wvdrm diff --git a/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp b/libwvdrmengine/mediadrm/test/hidl/WVDrmPlugin_test.cpp similarity index 100% rename from libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp rename to libwvdrmengine/mediadrm/test/hidl/WVDrmPlugin_test.cpp diff --git a/libwvdrmengine/mediadrm/test/hidl_metrics_adapter_unittest.cpp b/libwvdrmengine/mediadrm/test/hidl/hidl_metrics_adapter_unittest.cpp similarity index 100% rename from libwvdrmengine/mediadrm/test/hidl_metrics_adapter_unittest.cpp rename to libwvdrmengine/mediadrm/test/hidl/hidl_metrics_adapter_unittest.cpp