// // Copyright 2013 Google Inc. All Rights Reserved. // #include #include #include #include "cdm_client_property_set.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include "media/stagefright/foundation/ABase.h" #include "media/stagefright/foundation/AString.h" #include "media/stagefright/MediaErrors.h" #include "wv_cdm_constants.h" #include "wv_cdm_types.h" #include "wv_content_decryption_module.h" #include "WVDrmPlugin.h" #include "WVErrors.h" using namespace android; using namespace std; using namespace testing; using namespace wvcdm; using namespace wvdrm; class MockCDM : public WvContentDecryptionModule { public: MOCK_METHOD4(OpenSession, CdmResponseType(const CdmKeySystem&, CdmClientPropertySet*, WvCdmEventListener*, CdmSessionId*)); MOCK_METHOD1(CloseSession, CdmResponseType(const CdmSessionId&)); MOCK_METHOD10(GenerateKeyRequest, CdmResponseType(const CdmSessionId&, const CdmKeySetId&, const std::string&, const CdmInitData&, const CdmLicenseType, CdmAppParameterMap&, CdmClientPropertySet*, CdmKeyMessage*, CdmKeyRequestType*, string*)); MOCK_METHOD3(AddKey, CdmResponseType(const CdmSessionId&, const CdmKeyResponse&, CdmKeySetId*)); MOCK_METHOD1(RemoveKeys, CdmResponseType(const CdmSessionId&)); MOCK_METHOD2(RestoreKey, CdmResponseType(const CdmSessionId&, const CdmKeySetId&)); MOCK_METHOD1(QueryStatus, CdmResponseType(CdmQueryMap*)); MOCK_METHOD2(QueryKeyStatus, CdmResponseType(const CdmSessionId&, CdmQueryMap*)); MOCK_METHOD2(QueryKeyControlInfo, CdmResponseType(const CdmSessionId&, CdmQueryMap*)); MOCK_METHOD4(GetProvisioningRequest, CdmResponseType(CdmCertificateType, const std::string&, CdmProvisioningRequest*, std::string*)); MOCK_METHOD3(HandleProvisioningResponse, CdmResponseType(CdmProvisioningResponse&, std::string*, std::string*)); MOCK_METHOD2(GetUsageInfo, CdmResponseType(const std::string&, CdmUsageInfo*)); MOCK_METHOD3(GetUsageInfo, CdmResponseType(const std::string&, const CdmSecureStopId&, CdmUsageInfo*)); MOCK_METHOD1(Unprovision, CdmResponseType(CdmSecurityLevel)); MOCK_METHOD1(ReleaseAllUsageInfo, CdmResponseType(const std::string&)); MOCK_METHOD1(ReleaseUsageInfo, CdmResponseType(const CdmUsageInfoReleaseMessage&)); }; class MockCrypto : public WVGenericCryptoInterface { public: MOCK_METHOD3(selectKey, OEMCryptoResult(const OEMCrypto_SESSION, const uint8_t*, size_t)); MOCK_METHOD6(encrypt, OEMCryptoResult(OEMCrypto_SESSION, const uint8_t*, size_t, const uint8_t*, OEMCrypto_Algorithm, uint8_t*)); MOCK_METHOD6(decrypt, OEMCryptoResult(OEMCrypto_SESSION, const uint8_t*, size_t, const uint8_t*, OEMCrypto_Algorithm, uint8_t*)); MOCK_METHOD6(sign, OEMCryptoResult(OEMCrypto_SESSION, const uint8_t*, size_t, OEMCrypto_Algorithm, uint8_t*, size_t*)); MOCK_METHOD6(verify, OEMCryptoResult(OEMCrypto_SESSION, const uint8_t*, size_t, OEMCrypto_Algorithm, const uint8_t*, size_t)); MOCK_METHOD3(loadDeviceRSAKey, OEMCryptoResult(OEMCrypto_SESSION, const uint8_t*, size_t)); MOCK_METHOD6(generateRSASignature, OEMCryptoResult(OEMCrypto_SESSION, const uint8_t*, size_t, uint8_t*, size_t*, RSA_Padding_Scheme)); }; class MockDrmPluginListener : public DrmPluginListener { public: MOCK_METHOD4(sendEvent, void(DrmPlugin::EventType, int, const Vector*, const Vector*)); }; template CdmResponseType setSessionIdOnMap(Unused, CdmQueryMap* map) { static const char oecId[] = {DIGIT + '0', '\0'}; (*map)[QUERY_KEY_OEMCRYPTO_SESSION_ID] = oecId; return wvcdm::NO_ERROR; } class WVDrmPluginTest : public Test { protected: static const uint32_t kSessionIdSize = 16; uint8_t sessionIdRaw[kSessionIdSize]; Vector sessionId; CdmSessionId cdmSessionId; virtual void SetUp() { // Fill the session ID FILE* fp = fopen("/dev/urandom", "r"); fread(sessionIdRaw, sizeof(uint8_t), kSessionIdSize, fp); fclose(fp); memcpy(sessionIdRaw, SESSION_ID_PREFIX, sizeof(SESSION_ID_PREFIX) - 1); sessionId.appendArray(sessionIdRaw, kSessionIdSize); cdmSessionId.assign(sessionId.begin(), sessionId.end()); // Set default return values for gMock DefaultValue::Set(wvcdm::NO_ERROR); DefaultValue::Set(OEMCrypto_SUCCESS); DefaultValue::Set(true); } }; TEST_F(WVDrmPluginTest, OpensSessions) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); EXPECT_CALL(cdm, OpenSession(StrEq("com.widevine"), _, _, _)) .WillOnce(DoAll(SetArgPointee<3>(cdmSessionId), Return(wvcdm::NO_ERROR))); // Provide expected behavior when plugin requests session control info EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _)) .Times(AtLeast(1)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(cdm, CloseSession(_)) .Times(AtLeast(0)); status_t res = plugin.openSession(sessionId); ASSERT_EQ(OK, res); EXPECT_THAT(sessionId, ElementsAreArray(sessionIdRaw, kSessionIdSize)); } TEST_F(WVDrmPluginTest, ClosesSessions) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); EXPECT_CALL(cdm, CloseSession(cdmSessionId)) .Times(1); status_t res = plugin.closeSession(sessionId); ASSERT_EQ(OK, res); } TEST_F(WVDrmPluginTest, GeneratesKeyRequests) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); 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"); 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 cdmKeySetId(reinterpret_cast(keySetIdRaw), kKeySetIdSize); Vector keySetId; keySetId.appendArray(keySetIdRaw, kKeySetIdSize); CdmInitData cdmInitData(reinterpret_cast(initDataRaw), kInitDataSize); Vector initData; initData.appendArray(initDataRaw, kInitDataSize); 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 }; 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 cdmPsshBox(reinterpret_cast(psshBoxRaw), kPsshBoxSize); Vector psshBox; psshBox.appendArray(psshBoxRaw, kPsshBoxSize); CdmKeyMessage cdmRequest(requestRaw, requestRaw + kRequestSize); KeyedVector parameters; CdmAppParameterMap cdmParameters; parameters.add(String8("paddingScheme"), String8("BUBBLE WRAP")); cdmParameters["paddingScheme"] = "BUBBLE WRAP"; parameters.add(String8("favorite-particle"), String8("tetraquark")); cdmParameters["favorite-particle"] = "tetraquark"; parameters.add(String8("answer"), String8("6 * 9")); cdmParameters["answer"] = "6 * 9"; static const char* kDefaultUrl = "http://google.com/"; static const char* kIsoBmffMimeType = "cenc"; static const char* kWebmMimeType = "webm"; struct TestSet { const char* mimeType; const 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, cdmPsshBox}, // ISO-BMFF, EME passing style {kIsoBmffMimeType, initData, cdmPsshBox}, // ISO-BMFF, old passing style {kWebmMimeType, initData, cdmInitData} // WebM }; size_t testSetCount = sizeof(testSets) / sizeof(TestSet); // 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; EXPECT_CALL(cdm, GenerateKeyRequest(cdmSessionId, "", mimeType, initData, kLicenseTypeOffline, cdmParameters, _, _, _, _)) .WillOnce(DoAll(SetArgPointee<7>(cdmRequest), SetArgPointee<8>(kKeyRequestTypeInitial), SetArgPointee<9>(kDefaultUrl), Return(wvcdm::KEY_MESSAGE))); EXPECT_CALL(cdm, GenerateKeyRequest(cdmSessionId, "", mimeType, initData, kLicenseTypeStreaming, cdmParameters, _, _, _, _)) .WillOnce(DoAll(SetArgPointee<7>(cdmRequest), SetArgPointee<8>(kKeyRequestTypeRenewal), SetArgPointee<9>(kDefaultUrl), Return(wvcdm::KEY_MESSAGE))); EXPECT_CALL(cdm, GenerateKeyRequest("", cdmKeySetId, mimeType, initData, kLicenseTypeRelease, cdmParameters, NotNull(), _, _, _)) .WillOnce(DoAll(SetArgPointee<7>(cdmRequest), SetArgPointee<8>(kKeyRequestTypeRelease), SetArgPointee<9>(kDefaultUrl), Return(wvcdm::KEY_MESSAGE))); } } // Performs the actual tests for (size_t i = 0; i < testSetCount; ++i) { const String8 mimeType(testSets[i].mimeType); const Vector& initData = testSets[i].initDataIn; Vector request; String8 defaultUrl; DrmPlugin::KeyRequestType keyRequestType; status_t res = plugin.getKeyRequest(sessionId, initData, mimeType, DrmPlugin::kKeyType_Offline, parameters, request, defaultUrl, &keyRequestType); ASSERT_EQ(OK, res); EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize)); EXPECT_EQ(DrmPlugin::kKeyRequestType_Initial, keyRequestType); EXPECT_STREQ(kDefaultUrl, defaultUrl.string()); res = plugin.getKeyRequest(sessionId, initData, mimeType, DrmPlugin::kKeyType_Streaming, parameters, request, defaultUrl, &keyRequestType); ASSERT_EQ(OK, res); EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize)); EXPECT_EQ(DrmPlugin::kKeyRequestType_Renewal, keyRequestType); EXPECT_STREQ(kDefaultUrl, defaultUrl.string()); res = plugin.getKeyRequest(keySetId, initData, mimeType, DrmPlugin::kKeyType_Release, parameters, request, defaultUrl, &keyRequestType); ASSERT_EQ(OK, res); EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize)); EXPECT_EQ(DrmPlugin::kKeyRequestType_Release, keyRequestType); EXPECT_STREQ(kDefaultUrl, defaultUrl.string()); } } TEST_F(WVDrmPluginTest, AddsKeys) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); 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"); fread(responseRaw, sizeof(uint8_t), kResponseSize, fp); fread(keySetIdRaw, sizeof(uint8_t), kKeySetIdSize, fp); fclose(fp); Vector response; response.appendArray(responseRaw, kResponseSize); memcpy(keySetIdRaw, KEY_SET_ID_PREFIX, sizeof(KEY_SET_ID_PREFIX) - 1); CdmKeySetId cdmKeySetId(reinterpret_cast(keySetIdRaw), kKeySetIdSize); Vector keySetId; Vector emptyKeySetId; EXPECT_CALL(cdm, AddKey(cdmSessionId, ElementsAreArray(responseRaw, kResponseSize), _)) .WillOnce(DoAll(SetArgPointee<2>(cdmKeySetId), Return(wvcdm::KEY_ADDED))); EXPECT_CALL(cdm, AddKey("", ElementsAreArray(responseRaw, kResponseSize), Pointee(cdmKeySetId))) .Times(1); status_t res = plugin.provideKeyResponse(sessionId, response, keySetId); ASSERT_EQ(OK, res); ASSERT_THAT(keySetId, ElementsAreArray(keySetIdRaw, kKeySetIdSize)); res = plugin.provideKeyResponse(keySetId, response, emptyKeySetId); ASSERT_EQ(OK, res); EXPECT_EQ(0u, emptyKeySetId.size()); } TEST_F(WVDrmPluginTest, HandlesPrivacyCertCaseOfAddKey) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); sp > listener = new StrictMock(); const CdmClientPropertySet* propertySet = NULL; // Provide expected behavior in response to OpenSession and store the // property set EXPECT_CALL(cdm, OpenSession(_, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<3>(cdmSessionId), SaveArg<1>(&propertySet), Return(wvcdm::NO_ERROR))); // Provide expected behavior when plugin requests session control info EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(cdm, CloseSession(_)) .Times(AtLeast(0)); static const uint32_t kResponseSize = 256; uint8_t responseRaw[kResponseSize]; FILE* fp = fopen("/dev/urandom", "r"); fread(responseRaw, sizeof(uint8_t), kResponseSize, fp); fclose(fp); Vector response; response.appendArray(responseRaw, kResponseSize); Vector keySetId; EXPECT_CALL(*listener, sendEvent(DrmPlugin::kDrmPluginEventKeyNeeded, 0, Pointee(ElementsAreArray(sessionIdRaw, kSessionIdSize)), NULL)) .Times(1); EXPECT_CALL(cdm, AddKey(_, _, _)) .WillRepeatedly(Return(wvcdm::NEED_KEY)); plugin.openSession(sessionId); ASSERT_THAT(propertySet, NotNull()); status_t res = plugin.setListener(listener); ASSERT_EQ(OK, res); res = plugin.setPropertyString(String8("privacyMode"), String8("enable")); ASSERT_EQ(OK, res); EXPECT_TRUE(propertySet->use_privacy_mode()); res = plugin.provideKeyResponse(sessionId, response, keySetId); ASSERT_EQ(OK, res); } TEST_F(WVDrmPluginTest, RemovesKeys) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); EXPECT_CALL(cdm, RemoveKeys(cdmSessionId)) .Times(1); status_t res = plugin.removeKeys(sessionId); ASSERT_EQ(OK, res); } TEST_F(WVDrmPluginTest, RestoresKeys) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); static const size_t kKeySetIdSize = 32; uint8_t keySetIdRaw[kKeySetIdSize]; FILE* fp = fopen("/dev/urandom", "r"); fread(keySetIdRaw, sizeof(uint8_t), kKeySetIdSize, fp); fclose(fp); Vector keySetId; keySetId.appendArray(keySetIdRaw, kKeySetIdSize); EXPECT_CALL(cdm, RestoreKey(cdmSessionId, ElementsAreArray(keySetIdRaw, kKeySetIdSize))) .Times(1); status_t res = plugin.restoreKeys(sessionId, keySetId); ASSERT_EQ(OK, res); } TEST_F(WVDrmPluginTest, QueriesKeyStatus) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); KeyedVector expectedLicenseStatus; CdmQueryMap cdmLicenseStatus; expectedLicenseStatus.add(String8("areTheKeysAllRight"), String8("yes")); cdmLicenseStatus["areTheKeysAllRight"] = "yes"; expectedLicenseStatus.add(String8("isGMockAwesome"), String8("ohhhhhhYeah")); cdmLicenseStatus["isGMockAwesome"] = "ohhhhhhYeah"; expectedLicenseStatus.add(String8("answer"), String8("42")); cdmLicenseStatus["answer"] = "42"; EXPECT_CALL(cdm, QueryKeyStatus(cdmSessionId, _)) .WillOnce(DoAll(SetArgPointee<1>(cdmLicenseStatus), Return(wvcdm::NO_ERROR))); KeyedVector licenseStatus; status_t res = plugin.queryKeyStatus(sessionId, licenseStatus); ASSERT_EQ(OK, res); ASSERT_EQ(expectedLicenseStatus.size(), licenseStatus.size()); for (size_t i = 0; i < expectedLicenseStatus.size(); ++i) { const String8& key = expectedLicenseStatus.keyAt(i); EXPECT_NE(android::NAME_NOT_FOUND, licenseStatus.indexOfKey(key)); EXPECT_EQ(expectedLicenseStatus.valueFor(key), licenseStatus.valueFor(key)); } } TEST_F(WVDrmPluginTest, GetsProvisioningRequests) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); static const uint32_t kRequestSize = 256; uint8_t requestRaw[kRequestSize]; FILE* fp = fopen("/dev/urandom", "r"); fread(requestRaw, sizeof(uint8_t), kRequestSize, fp); fclose(fp); CdmProvisioningRequest cdmRequest(requestRaw, requestRaw + kRequestSize); static const char* kDefaultUrl = "http://google.com/"; EXPECT_CALL(cdm, GetProvisioningRequest(kCertificateWidevine, IsEmpty(), _, _)) .WillOnce(DoAll(SetArgPointee<2>(cdmRequest), SetArgPointee<3>(kDefaultUrl), Return(wvcdm::NO_ERROR))); Vector request; String8 defaultUrl; status_t res = plugin.getProvisionRequest(String8(""), String8(""), request, defaultUrl); ASSERT_EQ(OK, res); EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize)); EXPECT_STREQ(kDefaultUrl, defaultUrl.string()); } TEST_F(WVDrmPluginTest, HandlesProvisioningResponses) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); static const uint32_t kResponseSize = 512; uint8_t responseRaw[kResponseSize]; FILE* fp = fopen("/dev/urandom", "r"); fread(responseRaw, sizeof(uint8_t), kResponseSize, fp); fclose(fp); Vector response; response.appendArray(responseRaw, kResponseSize); EXPECT_CALL(cdm, HandleProvisioningResponse(ElementsAreArray(responseRaw, kResponseSize), _, _)) .Times(1); Vector cert; Vector key; status_t res = plugin.provideProvisionResponse(response, cert, key); ASSERT_EQ(OK, res); } TEST_F(WVDrmPluginTest, UnprovisionsDevice) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); EXPECT_CALL(cdm, Unprovision(kSecurityLevelL1)) .Times(1); EXPECT_CALL(cdm, Unprovision(kSecurityLevelL3)) .Times(1); status_t res = plugin.unprovisionDevice(); ASSERT_EQ(OK, res); } TEST_F(WVDrmPluginTest, MuxesUnprovisioningErrors) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); // 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(cdm, Unprovision(kSecurityLevelL1)) .WillOnce(Return(wvcdm::UNKNOWN_ERROR)) .WillOnce(Return(wvcdm::NO_ERROR)) .WillOnce(Return(wvcdm::UNKNOWN_ERROR)); EXPECT_CALL(cdm, Unprovision(kSecurityLevelL3)) .WillOnce(Return(wvcdm::NO_ERROR)) .WillOnce(Return(wvcdm::UNKNOWN_ERROR)) .WillOnce(Return(wvcdm::UNKNOWN_ERROR)); status_t res = plugin.unprovisionDevice(); ASSERT_NE(OK, res); res = plugin.unprovisionDevice(); ASSERT_NE(OK, res); res = plugin.unprovisionDevice(); ASSERT_NE(OK, res); } TEST_F(WVDrmPluginTest, GetsSecureStops) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); const char* app_id = "my_app_id"; plugin.setPropertyString(String8("appId"), String8(app_id)); static const uint32_t kStopSize = 53; static const uint32_t kStopCount = 7; uint8_t stopsRaw[kStopCount][kStopSize]; FILE* fp = fopen("/dev/urandom", "r"); for (uint32_t i = 0; i < kStopCount; ++i) { fread(stopsRaw[i], sizeof(uint8_t), kStopSize, fp); } fclose(fp); CdmUsageInfo cdmStops; for (uint32_t i = 0; i < kStopCount; ++i) { cdmStops.push_back(string(stopsRaw[i], stopsRaw[i] + kStopSize)); } EXPECT_CALL(cdm, GetUsageInfo(StrEq(app_id), _)) .WillOnce(DoAll(SetArgPointee<1>(cdmStops), Return(wvcdm::NO_ERROR))); List > stops; status_t res = plugin.getSecureStops(stops); ASSERT_EQ(OK, res); List >::iterator iter = stops.begin(); uint32_t rawIter = 0; while (rawIter < kStopCount && iter != stops.end()) { EXPECT_THAT(*iter, ElementsAreArray(stopsRaw[rawIter], kStopSize)); ++iter; ++rawIter; } // Assert that both lists are the same length EXPECT_EQ(kStopCount, rawIter); EXPECT_EQ(stops.end(), iter); } TEST_F(WVDrmPluginTest, ReleasesAllSecureStops) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); status_t res = plugin.setPropertyString(String8("appId"), String8("")); ASSERT_EQ(OK, res); EXPECT_CALL(cdm, ReleaseAllUsageInfo(StrEq(""))) .Times(1); res = plugin.releaseAllSecureStops(); ASSERT_EQ(OK, res); } TEST_F(WVDrmPluginTest, ReleasesSecureStops) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); static const uint32_t kMessageSize = 128; uint8_t messageRaw[kMessageSize]; FILE* fp = fopen("/dev/urandom", "r"); fread(messageRaw, sizeof(uint8_t), kMessageSize, fp); fclose(fp); Vector message; message.appendArray(messageRaw, kMessageSize); EXPECT_CALL(cdm, ReleaseUsageInfo(ElementsAreArray(messageRaw, kMessageSize))) .Times(1); status_t res = plugin.releaseSecureStops(message); ASSERT_EQ(OK, res); } TEST_F(WVDrmPluginTest, ReturnsExpectedPropertyValues) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); 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 string uniqueId = "The Universe"; CdmQueryMap deviceIDMap; deviceIDMap[QUERY_KEY_DEVICE_ID] = uniqueId; static const string systemId = "42"; CdmQueryMap systemIDMap; systemIDMap[QUERY_KEY_SYSTEM_ID] = systemId; static const string provisioningId("Life\0&Everything", 16); CdmQueryMap provisioningIDMap; provisioningIDMap[QUERY_KEY_PROVISIONING_ID] = provisioningId; static const string openSessions = "15"; CdmQueryMap openSessionsMap; openSessionsMap[QUERY_KEY_NUMBER_OF_OPEN_SESSIONS] = openSessions; static const string maxSessions = "18"; CdmQueryMap maxSessionsMap; maxSessionsMap[QUERY_KEY_MAX_NUMBER_OF_SESSIONS] = maxSessions; EXPECT_CALL(cdm, QueryStatus(_)) .WillOnce(DoAll(SetArgPointee<0>(l1Map), Return(wvcdm::NO_ERROR))) .WillOnce(DoAll(SetArgPointee<0>(l3Map), Return(wvcdm::NO_ERROR))) .WillOnce(DoAll(SetArgPointee<0>(deviceIDMap), Return(wvcdm::NO_ERROR))) .WillOnce(DoAll(SetArgPointee<0>(systemIDMap), Return(wvcdm::NO_ERROR))) .WillOnce(DoAll(SetArgPointee<0>(provisioningIDMap), Return(wvcdm::NO_ERROR))) .WillOnce(DoAll(SetArgPointee<0>(openSessionsMap), Return(wvcdm::NO_ERROR))) .WillOnce(DoAll(SetArgPointee<0>(maxSessionsMap), Return(wvcdm::NO_ERROR))); String8 stringResult; Vector vectorResult; status_t res = plugin.getPropertyString(String8("vendor"), stringResult); ASSERT_EQ(OK, res); EXPECT_STREQ("Google", stringResult.string()); res = plugin.getPropertyString(String8("version"), stringResult); ASSERT_EQ(OK, res); EXPECT_STREQ("1.0", stringResult.string()); res = plugin.getPropertyString(String8("description"), stringResult); ASSERT_EQ(OK, res); EXPECT_STREQ("Widevine CDM", stringResult.string()); res = plugin.getPropertyString(String8("algorithms"), stringResult); ASSERT_EQ(OK, res); EXPECT_STREQ("AES/CBC/NoPadding,HmacSHA256", stringResult.string()); res = plugin.getPropertyString(String8("securityLevel"), stringResult); ASSERT_EQ(OK, res); EXPECT_STREQ(QUERY_VALUE_SECURITY_LEVEL_L1.c_str(), stringResult.string()); res = plugin.getPropertyString(String8("securityLevel"), stringResult); ASSERT_EQ(OK, res); EXPECT_STREQ(QUERY_VALUE_SECURITY_LEVEL_L3.c_str(), stringResult.string()); res = plugin.getPropertyByteArray(String8("deviceUniqueId"), vectorResult); ASSERT_EQ(OK, res); EXPECT_THAT(vectorResult, ElementsAreArray(uniqueId.data(), uniqueId.size())); res = plugin.getPropertyString(String8("systemId"), stringResult); ASSERT_EQ(OK, res); EXPECT_STREQ(systemId.c_str(), stringResult.string()); res = plugin.getPropertyByteArray(String8("provisioningUniqueId"), vectorResult); ASSERT_EQ(OK, res); EXPECT_THAT(vectorResult, ElementsAreArray(provisioningId.data(), provisioningId.size())); res = plugin.getPropertyString(String8("numberOfOpenSessions"), stringResult); ASSERT_EQ(OK, res); EXPECT_EQ(openSessions, stringResult.string()); res = plugin.getPropertyString(String8("maxNumberOfSessions"), stringResult); ASSERT_EQ(OK, res); EXPECT_EQ(maxSessions, stringResult.string()); } TEST_F(WVDrmPluginTest, DoesNotGetUnknownProperties) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); String8 stringResult; Vector vectorResult; status_t res = plugin.getPropertyString(String8("unknownProperty"), stringResult); ASSERT_NE(OK, res); EXPECT_TRUE(stringResult.isEmpty()); res = plugin.getPropertyByteArray(String8("unknownProperty"), vectorResult); ASSERT_NE(OK, res); EXPECT_TRUE(vectorResult.isEmpty()); } TEST_F(WVDrmPluginTest, DoesNotSetUnknownProperties) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); static const uint32_t kValueSize = 32; uint8_t valueRaw[kValueSize]; FILE* fp = fopen("/dev/urandom", "r"); fread(valueRaw, sizeof(uint8_t), kValueSize, fp); fclose(fp); Vector value; value.appendArray(valueRaw, kValueSize); status_t res = plugin.setPropertyString(String8("unknownProperty"), String8("ignored")); ASSERT_NE(OK, res); res = plugin.setPropertyByteArray(String8("unknownProperty"), value); ASSERT_NE(OK, res); } TEST_F(WVDrmPluginTest, FailsGenericMethodsWithoutAnAlgorithmSet) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); Vector keyId; Vector input; Vector iv; Vector output; bool match; // Provide expected behavior to support session creation EXPECT_CALL(cdm, OpenSession(StrEq("com.widevine"), _, _, _)) .Times(AtLeast(1)) .WillRepeatedly(DoAll(SetArgPointee<3>(cdmSessionId), Return(wvcdm::NO_ERROR))); EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _)) .Times(AtLeast(1)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(cdm, CloseSession(_)) .Times(AtLeast(0)); status_t res = plugin.openSession(sessionId); ASSERT_EQ(OK, res); // Note that we do not set the algorithms. This should cause these methods // to fail. res = plugin.encrypt(sessionId, keyId, input, iv, output); EXPECT_EQ(NO_INIT, res); res = plugin.decrypt(sessionId, keyId, input, iv, output); EXPECT_EQ(NO_INIT, res); res = plugin.sign(sessionId, keyId, input, output); EXPECT_EQ(NO_INIT, res); res = plugin.verify(sessionId, keyId, input, output, match); EXPECT_EQ(NO_INIT, res); } 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(WVDrmPluginTest, CallsGenericEncrypt) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); 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"); 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); Vector keyId; keyId.appendArray(keyIdRaw, KEY_ID_SIZE); Vector input; input.appendArray(inputRaw, kDataSize); Vector iv; iv.appendArray(ivRaw, KEY_IV_SIZE); Vector output; { InSequence calls; EXPECT_CALL(crypto, selectKey(4, _, KEY_ID_SIZE)) .With(Args<1, 2>(ElementsAreArray(keyIdRaw, KEY_ID_SIZE))) .Times(1); EXPECT_CALL(crypto, 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(cdm, OpenSession(StrEq("com.widevine"), _, _, _)) .Times(AtLeast(1)) .WillRepeatedly(DoAll(SetArgPointee<3>(cdmSessionId), Return(wvcdm::NO_ERROR))); EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _)) .Times(AtLeast(1)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(cdm, CloseSession(_)) .Times(AtLeast(0)); status_t res = plugin.openSession(sessionId); ASSERT_EQ(OK, res); res = plugin.setCipherAlgorithm(sessionId, String8("AES/CBC/NoPadding")); ASSERT_EQ(OK, res); res = plugin.encrypt(sessionId, keyId, input, iv, output); ASSERT_EQ(OK, res); } TEST_F(WVDrmPluginTest, CallsGenericDecrypt) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); 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"); 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); Vector keyId; keyId.appendArray(keyIdRaw, KEY_ID_SIZE); Vector input; input.appendArray(inputRaw, kDataSize); Vector iv; iv.appendArray(ivRaw, KEY_IV_SIZE); Vector output; { InSequence calls; EXPECT_CALL(crypto, selectKey(4, _, KEY_ID_SIZE)) .With(Args<1, 2>(ElementsAreArray(keyIdRaw, KEY_ID_SIZE))) .Times(1); EXPECT_CALL(crypto, 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(cdm, OpenSession(StrEq("com.widevine"), _, _, _)) .Times(AtLeast(1)) .WillRepeatedly(DoAll(SetArgPointee<3>(cdmSessionId), Return(wvcdm::NO_ERROR))); EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _)) .Times(AtLeast(1)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(cdm, CloseSession(_)) .Times(AtLeast(0)); status_t res = plugin.openSession(sessionId); ASSERT_EQ(OK, res); res = plugin.setCipherAlgorithm(sessionId, String8("AES/CBC/NoPadding")); ASSERT_EQ(OK, res); res = plugin.decrypt(sessionId, keyId, input, iv, output); ASSERT_EQ(OK, res); } TEST_F(WVDrmPluginTest, CallsGenericSign) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); static const size_t kDataSize = 256; uint8_t keyIdRaw[KEY_ID_SIZE]; uint8_t messageRaw[kDataSize]; FILE* fp = fopen("/dev/urandom", "r"); fread(keyIdRaw, sizeof(uint8_t), KEY_ID_SIZE, fp); fread(messageRaw, sizeof(uint8_t), kDataSize, fp); fclose(fp); Vector keyId; keyId.appendArray(keyIdRaw, KEY_ID_SIZE); Vector message; message.appendArray(messageRaw, kDataSize); Vector signature; { InSequence calls; EXPECT_CALL(crypto, selectKey(4, _, KEY_ID_SIZE)) .With(Args<1, 2>(ElementsAreArray(keyIdRaw, KEY_ID_SIZE))) .Times(1); EXPECT_CALL(crypto, sign(4, _, kDataSize, OEMCrypto_HMAC_SHA256, _, Pointee(0))) .With(Args<1, 2>(ElementsAreArray(messageRaw, kDataSize))) .WillOnce(DoAll(SetArgPointee<5>(64), Return(OEMCrypto_ERROR_SHORT_BUFFER))); EXPECT_CALL(crypto, 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(cdm, OpenSession(StrEq("com.widevine"), _, _, _)) .Times(AtLeast(1)) .WillRepeatedly(DoAll(SetArgPointee<3>(cdmSessionId), Return(wvcdm::NO_ERROR))); EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _)) .Times(AtLeast(1)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(cdm, CloseSession(_)) .Times(AtLeast(0)); status_t res = plugin.openSession(sessionId); ASSERT_EQ(OK, res); res = plugin.setMacAlgorithm(sessionId, String8("HmacSHA256")); ASSERT_EQ(OK, res); res = plugin.sign(sessionId, keyId, message, signature); ASSERT_EQ(OK, res); } TEST_F(WVDrmPluginTest, CallsGenericVerify) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); 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"); 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); Vector keyId; keyId.appendArray(keyIdRaw, KEY_ID_SIZE); Vector message; message.appendArray(messageRaw, kDataSize); Vector signature; signature.appendArray(signatureRaw, kSignatureSize); bool match; { InSequence calls; EXPECT_CALL(crypto, selectKey(4, _, KEY_ID_SIZE)) .With(Args<1, 2>(ElementsAreArray(keyIdRaw, KEY_ID_SIZE))) .Times(1); EXPECT_CALL(crypto, verify(4, _, kDataSize, OEMCrypto_HMAC_SHA256, _, kSignatureSize)) .With(AllOf(Args<1, 2>(ElementsAreArray(messageRaw, kDataSize)), Args<4, 5>(ElementsAreArray(signatureRaw, kSignatureSize)))) .WillOnce(Return(OEMCrypto_SUCCESS)); EXPECT_CALL(crypto, selectKey(4, _, KEY_ID_SIZE)) .With(Args<1, 2>(ElementsAreArray(keyIdRaw, KEY_ID_SIZE))) .Times(1); EXPECT_CALL(crypto, verify(4, _, kDataSize, OEMCrypto_HMAC_SHA256, _, kSignatureSize)) .With(AllOf(Args<1, 2>(ElementsAreArray(messageRaw, kDataSize)), Args<4, 5>(ElementsAreArray(signatureRaw, kSignatureSize)))) .WillOnce(Return(OEMCrypto_ERROR_SIGNATURE_FAILURE)); } // Provide expected behavior to support session creation EXPECT_CALL(cdm, OpenSession(StrEq("com.widevine"), _, _, _)) .Times(AtLeast(1)) .WillRepeatedly(DoAll(SetArgPointee<3>(cdmSessionId), Return(wvcdm::NO_ERROR))); EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _)) .Times(AtLeast(1)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(cdm, CloseSession(_)) .Times(AtLeast(0)); status_t res = plugin.openSession(sessionId); ASSERT_EQ(OK, res); res = plugin.setMacAlgorithm(sessionId, String8("HmacSHA256")); ASSERT_EQ(OK, res); res = plugin.verify(sessionId, keyId, message, signature, match); ASSERT_EQ(OK, res); EXPECT_TRUE(match); res = plugin.verify(sessionId, keyId, message, signature, match); ASSERT_EQ(OK, res); EXPECT_FALSE(match); } TEST_F(WVDrmPluginTest, RegistersForEvents) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); // Provide expected behavior to support session creation EXPECT_CALL(cdm, OpenSession(StrEq("com.widevine"), _, &plugin, _)) .Times(AtLeast(1)) .WillRepeatedly(DoAll(SetArgPointee<3>(cdmSessionId), Return(wvcdm::NO_ERROR))); EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _)) .Times(AtLeast(1)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(cdm, CloseSession(_)) .Times(AtLeast(0)); status_t res = plugin.openSession(sessionId); ASSERT_EQ(OK, res); } TEST_F(WVDrmPluginTest, UnregistersForAllEventsOnDestruction) { StrictMock cdm; StrictMock crypto; { WVDrmPlugin plugin(&cdm, &crypto); uint8_t sessionIdRaw1[kSessionIdSize]; uint8_t sessionIdRaw2[kSessionIdSize]; FILE* fp = fopen("/dev/urandom", "r"); fread(sessionIdRaw1, sizeof(uint8_t), kSessionIdSize, fp); fread(sessionIdRaw2, sizeof(uint8_t), kSessionIdSize, fp); fclose(fp); CdmSessionId cdmSessionId1(sessionIdRaw1, sessionIdRaw1 + kSessionIdSize); CdmSessionId cdmSessionId2(sessionIdRaw2, sessionIdRaw2 + kSessionIdSize); EXPECT_CALL(cdm, OpenSession(StrEq("com.widevine"), _, _, _)) .WillOnce(DoAll(SetArgPointee<3>(cdmSessionId1), Return(wvcdm::NO_ERROR))) .WillOnce(DoAll(SetArgPointee<3>(cdmSessionId2), Return(wvcdm::NO_ERROR))); EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId1, _)) .WillOnce(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId2, _)) .WillOnce(Invoke(setSessionIdOnMap<5>)); EXPECT_CALL(cdm, CloseSession(_)) .Times(AtLeast(0)); status_t res = plugin.openSession(sessionId); ASSERT_EQ(OK, res); res = plugin.openSession(sessionId); ASSERT_EQ(OK, res); } } TEST_F(WVDrmPluginTest, MarshalsEvents) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); sp > listener = new StrictMock(); { InSequence calls; EXPECT_CALL(*listener, sendEvent(DrmPlugin::kDrmPluginEventKeyExpired, 0, Pointee(ElementsAreArray(sessionIdRaw, kSessionIdSize)), NULL)) .Times(1); EXPECT_CALL(*listener, sendEvent(DrmPlugin::kDrmPluginEventKeyNeeded, 0, Pointee(ElementsAreArray(sessionIdRaw, kSessionIdSize)), NULL)) .Times(1); } status_t res = plugin.setListener(listener); ASSERT_EQ(OK, res); plugin.OnSessionExpiration(cdmSessionId); plugin.OnSessionRenewalNeeded(cdmSessionId); } TEST_F(WVDrmPluginTest, GeneratesProvisioningNeededEvent) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); sp > listener = new StrictMock(); EXPECT_CALL(*listener, sendEvent(DrmPlugin::kDrmPluginEventProvisionRequired, 0, Pointee(ElementsAreArray(sessionIdRaw, kSessionIdSize)), NULL)) .Times(1); EXPECT_CALL(cdm, OpenSession(StrEq("com.widevine"), _, _, _)) .Times(AtLeast(1)) .WillRepeatedly(DoAll(SetArgPointee<3>(cdmSessionId), Return(wvcdm::NEED_PROVISIONING))); EXPECT_CALL(cdm, CloseSession(_)) .Times(AtLeast(0)); status_t res = plugin.setListener(listener); ASSERT_EQ(OK, res); res = plugin.openSession(sessionId); ASSERT_EQ(ERROR_DRM_NOT_PROVISIONED, res); } TEST_F(WVDrmPluginTest, ProvidesExpectedDefaultPropertiesToCdm) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); const CdmClientPropertySet* propertySet = NULL; // Provide expected mock behavior { // Provide expected behavior in response to OpenSession and store the // property set EXPECT_CALL(cdm, OpenSession(_, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<3>(cdmSessionId), SaveArg<1>(&propertySet), Return(wvcdm::NO_ERROR))); // Provide expected behavior when plugin requests session control info EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(cdm, CloseSession(_)) .Times(AtLeast(0)); } plugin.openSession(sessionId); 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(WVDrmPluginTest, CanSetAppId) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); const CdmClientPropertySet* propertySet = NULL; // Provide expected mock behavior { // Provide expected behavior in response to OpenSession and store the // property set EXPECT_CALL(cdm, OpenSession(_, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<3>(cdmSessionId), SaveArg<1>(&propertySet), Return(wvcdm::NO_ERROR))); // Provide expected behavior when plugin requests session control info EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(cdm, CloseSession(_)) .Times(AtLeast(0)); } status_t res; // Test setting an empty string res = plugin.setPropertyString(String8("appId"), String8("")); ASSERT_EQ(OK, res); // Test setting an application id before a session is opened. const String8 kAppId("com.unittest.mock.app.id"); res = plugin.setPropertyString(String8("appId"), kAppId); ASSERT_EQ(OK, res); plugin.openSession(sessionId); ASSERT_THAT(propertySet, NotNull()); // Verify application id is set correctly. EXPECT_STREQ(kAppId, propertySet->app_id().c_str()); // Test setting application id while session is opened, this should fail. res = plugin.setPropertyString(String8("appId"), kAppId); ASSERT_EQ(kErrorSessionIsOpen, res); } TEST_F(WVDrmPluginTest, CanSetSecurityLevel) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); const CdmClientPropertySet* propertySet = NULL; CdmQueryMap l1Map; l1Map[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L1; CdmQueryMap l3Map; l3Map[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L3; EXPECT_CALL(cdm, QueryStatus(_)) .WillOnce(DoAll(SetArgPointee<0>(l3Map), Return(wvcdm::NO_ERROR))) .WillOnce(DoAll(SetArgPointee<0>(l1Map), Return(wvcdm::NO_ERROR))); // Provide expected mock behavior { // Provide expected behavior in response to OpenSession and store the // property set EXPECT_CALL(cdm, OpenSession(_, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<3>(cdmSessionId), SaveArg<1>(&propertySet), Return(wvcdm::NO_ERROR))); // Provide expected behavior when plugin requests session control info EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(cdm, CloseSession(_)) .Times(AtLeast(0)); } status_t res; // Test forcing L3 res = plugin.setPropertyString(String8("securityLevel"), String8("L3")); ASSERT_EQ(OK, res); plugin.openSession(sessionId); ASSERT_THAT(propertySet, NotNull()); EXPECT_STREQ("L3", propertySet->security_level().c_str()); plugin.closeSession(sessionId); // Test returning to L1 on an L3 device (Should Fail) res = plugin.setPropertyString(String8("securityLevel"), String8("L1")); ASSERT_NE(OK, res); plugin.openSession(sessionId); ASSERT_THAT(propertySet, NotNull()); EXPECT_STREQ("L3", propertySet->security_level().c_str()); plugin.closeSession(sessionId); // Test returning to L1 on an L1 device res = plugin.setPropertyString(String8("securityLevel"), String8("L1")); ASSERT_EQ(OK, res); plugin.openSession(sessionId); ASSERT_THAT(propertySet, NotNull()); EXPECT_STREQ("", propertySet->security_level().c_str()); plugin.closeSession(sessionId); // Test un-forcing a level (first forcing to L3 so we have something to reset) res = plugin.setPropertyString(String8("securityLevel"), String8("L3")); ASSERT_EQ(OK, res); plugin.openSession(sessionId); ASSERT_THAT(propertySet, NotNull()); EXPECT_STREQ("L3", propertySet->security_level().c_str()); plugin.closeSession(sessionId); res = plugin.setPropertyString(String8("securityLevel"), String8("")); ASSERT_EQ(OK, res); plugin.openSession(sessionId); ASSERT_THAT(propertySet, NotNull()); EXPECT_STREQ("", propertySet->security_level().c_str()); plugin.closeSession(sessionId); // Test nonsense (Should Fail) res = plugin.setPropertyString(String8("securityLevel"), String8("nonsense")); ASSERT_NE(OK, res); plugin.openSession(sessionId); ASSERT_THAT(propertySet, NotNull()); EXPECT_STREQ("", propertySet->security_level().c_str()); plugin.closeSession(sessionId); // Test attempting to force a level with a session open (Should Fail) plugin.openSession(sessionId); res = plugin.setPropertyString(String8("securityLevel"), String8("L3")); ASSERT_NE(OK, res); } TEST_F(WVDrmPluginTest, CanSetPrivacyMode) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); const CdmClientPropertySet* propertySet = NULL; // Provide expected mock behavior { // Provide expected behavior in response to OpenSession and store the // property set EXPECT_CALL(cdm, OpenSession(_, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<3>(cdmSessionId), SaveArg<1>(&propertySet), Return(wvcdm::NO_ERROR))); // Provide expected behavior when plugin requests session control info EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(cdm, CloseSession(_)) .Times(AtLeast(0)); } plugin.openSession(sessionId); ASSERT_THAT(propertySet, NotNull()); status_t res; // Test turning on privacy mode res = plugin.setPropertyString(String8("privacyMode"), String8("enable")); ASSERT_EQ(OK, res); EXPECT_TRUE(propertySet->use_privacy_mode()); // Test turning off privacy mode res = plugin.setPropertyString(String8("privacyMode"), String8("disable")); ASSERT_EQ(OK, res); EXPECT_FALSE(propertySet->use_privacy_mode()); // Test nonsense (Should Fail) res = plugin.setPropertyString(String8("privacyMode"), String8("nonsense")); ASSERT_NE(OK, res); } TEST_F(WVDrmPluginTest, CanSetServiceCertificate) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); const CdmClientPropertySet* propertySet = NULL; static const size_t kPrivacyCertSize = 256; uint8_t privacyCertRaw[kPrivacyCertSize]; FILE* fp = fopen("/dev/urandom", "r"); fread(privacyCertRaw, sizeof(uint8_t), kPrivacyCertSize, fp); fclose(fp); Vector privacyCert; privacyCert.appendArray(privacyCertRaw, kPrivacyCertSize); Vector emptyVector; // Provide expected mock behavior { // Provide expected behavior in response to OpenSession and store the // property set EXPECT_CALL(cdm, OpenSession(_, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<3>(cdmSessionId), SaveArg<1>(&propertySet), Return(wvcdm::NO_ERROR))); // Provide expected behavior when plugin requests session control info EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(cdm, CloseSession(_)) .Times(AtLeast(0)); } plugin.openSession(sessionId); ASSERT_THAT(propertySet, NotNull()); status_t res; // Test setting a certificate res = plugin.setPropertyByteArray(String8("serviceCertificate"), privacyCert); ASSERT_EQ(OK, res); EXPECT_THAT(propertySet->service_certificate(), ElementsAreArray(privacyCertRaw, kPrivacyCertSize)); // Test clearing a certificate res = plugin.setPropertyByteArray(String8("serviceCertificate"), emptyVector); ASSERT_EQ(OK, res); EXPECT_EQ(0u, propertySet->service_certificate().size()); } TEST_F(WVDrmPluginTest, CanSetSessionSharing) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); const CdmClientPropertySet* propertySet = NULL; // Provide expected mock behavior { // Provide expected behavior in response to OpenSession and store the // property set EXPECT_CALL(cdm, OpenSession(_, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<3>(cdmSessionId), SaveArg<1>(&propertySet), Return(wvcdm::NO_ERROR))); // Provide expected behavior when plugin requests session control info EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(cdm, CloseSession(_)) .Times(AtLeast(0)); } status_t res; // Test turning on session sharing res = plugin.setPropertyString(String8("sessionSharing"), String8("enable")); ASSERT_EQ(OK, res); plugin.openSession(sessionId); ASSERT_THAT(propertySet, NotNull()); EXPECT_TRUE(propertySet->is_session_sharing_enabled()); plugin.closeSession(sessionId); // Test turning off session sharing res = plugin.setPropertyString(String8("sessionSharing"), String8("disable")); ASSERT_EQ(OK, res); plugin.openSession(sessionId); ASSERT_THAT(propertySet, NotNull()); EXPECT_FALSE(propertySet->is_session_sharing_enabled()); plugin.closeSession(sessionId); // Test nonsense (Should Fail) res = plugin.setPropertyString(String8("sessionSharing"), String8("nonsense")); ASSERT_NE(OK, res); // Test changing sharing with a session open (Should Fail) plugin.openSession(sessionId); res = plugin.setPropertyString(String8("sessionSharing"), String8("enable")); ASSERT_NE(OK, res); } TEST_F(WVDrmPluginTest, AllowsStoringOfSessionSharingId) { StrictMock cdm; StrictMock crypto; WVDrmPlugin plugin(&cdm, &crypto); CdmClientPropertySet* propertySet = NULL; uint32_t sharingId; FILE* fp = fopen("/dev/urandom", "r"); 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(cdm, OpenSession(_, _, _, _)) .WillRepeatedly(DoAll(SetArgPointee<3>(cdmSessionId), SaveArg<1>(&propertySet), Return(wvcdm::NO_ERROR))); // Provide expected behavior when plugin requests session control info EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _)) .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); EXPECT_CALL(cdm, CloseSession(_)) .Times(AtLeast(0)); } plugin.openSession(sessionId); ASSERT_THAT(propertySet, NotNull()); propertySet->set_session_sharing_id(sharingId); EXPECT_EQ(sharingId, propertySet->session_sharing_id()); }