diff --git a/libwvdrmengine/cdm/include/wv_content_decryption_module.h b/libwvdrmengine/cdm/include/wv_content_decryption_module.h index dbf92e4f..c1756470 100644 --- a/libwvdrmengine/cdm/include/wv_content_decryption_module.h +++ b/libwvdrmengine/cdm/include/wv_content_decryption_module.h @@ -99,6 +99,9 @@ class WvContentDecryptionModule : public android::RefBase, public TimerHandler { virtual CdmResponseType Unprovision(CdmSecurityLevel level, const CdmIdentifier& identifier); + virtual bool IsProvisioned(CdmSecurityLevel level, const std::string& origin, + const std::string& spoid, bool atsc_mode_enabled); + // Secure stop related methods virtual CdmResponseType GetUsageInfo(const std::string& app_id, const CdmIdentifier& identifier, diff --git a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp index a49ff111..96314de6 100644 --- a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp +++ b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp @@ -240,6 +240,18 @@ CdmResponseType WvContentDecryptionModule::Unprovision( return cdm_engine->Unprovision(level); } +bool WvContentDecryptionModule::IsProvisioned(CdmSecurityLevel security_level, + const std::string& origin, + const std::string& spoid, + bool atsc_mode_enabled) { + FileSystem file_system; + file_system.set_origin(origin); + file_system.set_identifier(spoid + origin); + DeviceFiles device_files(&file_system); + device_files.Init(security_level); + return device_files.HasCertificate(atsc_mode_enabled); +} + CdmResponseType WvContentDecryptionModule::GetUsageInfo( const std::string& app_id, const CdmIdentifier& identifier, CdmUsageInfo* usage_info) { diff --git a/libwvdrmengine/mediadrm/include_hidl/WVDrmPlugin.h b/libwvdrmengine/mediadrm/include_hidl/WVDrmPlugin.h index 06cf4d59..2b697e1e 100644 --- a/libwvdrmengine/mediadrm/include_hidl/WVDrmPlugin.h +++ b/libwvdrmengine/mediadrm/include_hidl/WVDrmPlugin.h @@ -413,12 +413,15 @@ struct WVDrmPlugin : public IDrmPlugin, IDrmPluginListener, const WVDrmPlugin& mParent; Status calculateSpoid(); + Status calculateSpoid(const std::string& deviceID, std::string* spoid); // Gets the device-unique ID from OEMCrypto. This must be private, since // this value must not be exposed to applications on SPOID devices. Code // outside this class should use getDeviceUniqueId() to get the // application-safe device-unique ID. Status getOemcryptoDeviceId(std::string* id); + Status getOemcryptoDeviceId(wvcdm::SecurityLevel securityLevel, + std::string* id); // The unique identifier is meant to ensure that two clients with the // same spoid, origin and app package name still get different cdm engine @@ -450,6 +453,11 @@ struct WVDrmPlugin : public IDrmPlugin, IDrmPluginListener, Status queryProperty(const std::string& property, std::vector& vector_value) const; + bool isProvisioned(wvcdm::CdmSecurityLevel securityLevel, + const std::string& origin, + const std::string& spoid, + bool atsc_mode_enabled) const; + Status mapAndNotifyOfCdmResponseType(const std::vector& sessionId, CdmResponseType res); diff --git a/libwvdrmengine/mediadrm/src_hidl/WVDrmPlugin.cpp b/libwvdrmengine/mediadrm/src_hidl/WVDrmPlugin.cpp index d14d6ed3..0801829b 100644 --- a/libwvdrmengine/mediadrm/src_hidl/WVDrmPlugin.cpp +++ b/libwvdrmengine/mediadrm/src_hidl/WVDrmPlugin.cpp @@ -2006,6 +2006,13 @@ Status WVDrmPlugin::queryProperty(const std::string& property, return Status::OK; } +bool WVDrmPlugin::isProvisioned(wvcdm::CdmSecurityLevel securityLevel, + const std::string& origin, + const std::string& spoid, + bool atsc_mode_enabled) const { + return mCDM->IsProvisioned(securityLevel, origin, spoid, atsc_mode_enabled); +} + Status WVDrmPlugin::mapAndNotifyOfCdmResponseType( const std::vector& sessionId, CdmResponseType res) { notifyOfCdmResponseType(sessionId, res); @@ -2189,23 +2196,97 @@ bool WVDrmPlugin::CdmIdentifierBuilder::set_use_atsc_mode(bool enable) { } Status WVDrmPlugin::CdmIdentifierBuilder::calculateSpoid() { - if (mUseSpoid) { - std::string deviceId; + if (!mUseSpoid) + return Status::OK; + + // Calculate SPOID for default security level if appropriate + std::string deviceId; + if (mParent.getRequestedSecurityLevel() == wvcdm::kLevelDefault) { Status res = getOemcryptoDeviceId(&deviceId); if (res != Status::OK) return res; - uint8_t hash[SHA256_DIGEST_LENGTH]; - SHA256_CTX ctx; - SHA256_Init(&ctx); - SHA256_Update(&ctx, deviceId.data(), deviceId.length()); - SHA256_Update(&ctx, mCdmIdentifier.app_package_name.data(), - mCdmIdentifier.app_package_name.length()); - SHA256_Update(&ctx, origin().data(), origin().length()); - SHA256_Final(hash, &ctx); - - mCdmIdentifier.spoid = - std::string(reinterpret_cast(hash), SHA256_DIGEST_LENGTH); + return calculateSpoid(deviceId, &mCdmIdentifier.spoid); } + + // If requested security level is L3, possibilities are + // (a) L3 has not been provisioned + // (b) L3 was provisioned with L3 device ID in the CdmIdentifier + // (c) L3 was provisioned (incorrectly) with L1 device ID in the CdmIdentifier + // Check (b) first. Get L3 device ID, calculate SPOID and if provisioned + // with this SPOID, return this SPOID. + // Check (c) next. Get L1 device ID, calculate SPOID and if provisioned + // with this SPOID, return this SPOID. + // On any errors in (c) or not provisioned return L3 SPOID. + Status res = getOemcryptoDeviceId(wvcdm::kLevel3, &deviceId); + if (res != Status::OK) return res; + + std::string spoidL3; + res = calculateSpoid(deviceId, &spoidL3); + if (res != Status::OK) return res; + + bool atsc_mode_enabled = + mCdmIdentifier.app_package_name == wvcdm::ATSC_APP_PACKAGE_NAME; + + if (mParent.isProvisioned(wvcdm::kSecurityLevelL3, origin(), spoidL3, + atsc_mode_enabled)) { + mCdmIdentifier.spoid = spoidL3; + return Status::OK; + } + + // Not provisioned with CdmIdentifier containing SPOID with L3 device ID. + // Try SPOID with L1 device ID. + std::string deviceIdLevelDefault; + res = getOemcryptoDeviceId(wvcdm::kLevelDefault, &deviceIdLevelDefault); + if (res != Status::OK) { + mCdmIdentifier.spoid = spoidL3; + return Status::OK; + } + + // If the L3 and default security level IDs are identical then the + // device does not support L1. + if (deviceId == deviceIdLevelDefault) { + mCdmIdentifier.spoid = spoidL3; + return Status::OK; + } + + std::string spoidLevelDefault; + res = calculateSpoid(deviceIdLevelDefault, &spoidLevelDefault); + if (res != Status::OK) { + mCdmIdentifier.spoid = spoidL3; + return Status::OK; + } + + if (mParent.isProvisioned(wvcdm::kSecurityLevelL1, origin(), + spoidLevelDefault, atsc_mode_enabled)) { + mCdmIdentifier.spoid = spoidLevelDefault; + return Status::OK; + } + + // Not provisioned with CdmIdentifier containing SPOID with L1 or L3 + // device ID. Return L3 SPOID. + mCdmIdentifier.spoid = spoidL3; + return Status::OK; +} + +Status WVDrmPlugin::CdmIdentifierBuilder::calculateSpoid( + const std::string& deviceId, std::string* spoid) { + if (spoid == nullptr) + return Status::ERROR_DRM_CANNOT_HANDLE; + + if (!mUseSpoid) { + spoid->clear(); + return Status::OK; + } + + uint8_t hash[SHA256_DIGEST_LENGTH]; + SHA256_CTX ctx; + SHA256_Init(&ctx); + SHA256_Update(&ctx, deviceId.data(), deviceId.length()); + SHA256_Update(&ctx, mAppPackageName.data(), mAppPackageName.length()); + SHA256_Update(&ctx, origin().data(), origin().length()); + SHA256_Final(hash, &ctx); + + *spoid = std::string(reinterpret_cast(hash), SHA256_DIGEST_LENGTH); return Status::OK; } @@ -2214,6 +2295,12 @@ Status WVDrmPlugin::CdmIdentifierBuilder::getOemcryptoDeviceId( return mParent.queryProperty(wvcdm::QUERY_KEY_DEVICE_ID, *id); } +Status WVDrmPlugin::CdmIdentifierBuilder::getOemcryptoDeviceId( + wvcdm::SecurityLevel securityLevel, + std::string* id) { + return mParent.queryProperty(securityLevel, wvcdm::QUERY_KEY_DEVICE_ID, *id); +} + uint32_t WVDrmPlugin::CdmIdentifierBuilder::getNextUniqueId() { // Start with 1. 0 is reserved for the default cdm identifier. static uint32_t unique_id = 1; diff --git a/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp b/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp index 6d04209f..61958787 100644 --- a/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp +++ b/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp @@ -190,6 +190,9 @@ class MockCDM : public WvContentDecryptionModule { MOCK_METHOD2(Unprovision, CdmResponseType(CdmSecurityLevel, const CdmIdentifier&)); + MOCK_METHOD3(IsProvisioned, bool(CdmSecurityLevel, const std::string&, + const std::string&)); + MOCK_METHOD3(GetUsageInfo, CdmResponseType(const std::string&, const CdmIdentifier&, CdmUsageInfo*)); @@ -1681,6 +1684,350 @@ TEST_F(WVDrmPluginTest, CompliesWithSpoidVariability) { } } +TEST_F(WVDrmPluginTest, ReturnsSameL1Spoid) { + StrictMock crypto; + + std::string kL1DeviceId = kDeviceId + "L1"; + + const std::string kAppPackageName("com.google.widevine"); + constexpr size_t kSpoidQuery = 2; + std::vector spoid[kSpoidQuery]; + + android::sp> cdm = new StrictMock(); + + 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>(cdmSessionId), + testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*cdm, QueryOemCryptoSessionId(cdmSessionId, _)) + .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 + for (int i = 0; i < kSpoidQuery; ++i) { + WVDrmPlugin plugin(cdm.get(), kAppPackageName, &crypto, true); + + plugin.openSession([&](Status status, hidl_vec hSessionId) { + ASSERT_EQ(Status::OK, status); + sessionId.clear(); + sessionId.assign(hSessionId.data(), + hSessionId.data() + hSessionId.size()); + }); + + EXPECT_EQ(Status::OK, plugin.closeSession(toHidlVec(sessionId))); + + plugin.getPropertyByteArray( + hidl_string("deviceUniqueId"), + [&](Status status, hidl_vec vectorResult) { + ASSERT_EQ(Status::OK, status); + spoid[i] = vectorResult; + }); + } + + EXPECT_EQ(spoid[0], spoid[1]); +} + +TEST_F(WVDrmPluginTest, ReturnsL3SpoidsWhenL3ProvisionedUsingL3Spoid) { + StrictMock crypto; + + std::string kL3DeviceId = kDeviceId + "L3"; + + const std::string kAppPackageName("com.google.widevine"); + constexpr size_t kSpoidQuery = 2; + std::vector spoid[kSpoidQuery]; + + android::sp> cdm = new StrictMock(); + + 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>(cdmSessionId), + testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*cdm, QueryOemCryptoSessionId(cdmSessionId, _)) + .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 + for (size_t i = 0; i < kSpoidQuery; ++i) { + WVDrmPlugin plugin(cdm.get(), kAppPackageName, &crypto, true); + + // Forcing L3 + Status status = plugin.setPropertyString(hidl_string("securityLevel"), + hidl_string("L3")); + ASSERT_EQ(Status::OK, status); + + + plugin.openSession([&](Status status, hidl_vec hSessionId) { + ASSERT_EQ(Status::OK, status); + sessionId.clear(); + sessionId.assign(hSessionId.data(), + hSessionId.data() + hSessionId.size()); + }); + + EXPECT_EQ(Status::OK, plugin.closeSession(toHidlVec(sessionId))); + + plugin.getPropertyByteArray( + hidl_string("deviceUniqueId"), + [&](Status status, hidl_vec vectorResult) { + ASSERT_EQ(Status::OK, status); + spoid[i] = vectorResult; + }); + } + + EXPECT_EQ(spoid[0], spoid[1]); +} + +TEST_F(WVDrmPluginTest, ReturnsL3SpoidsWhenL3Unprovisioned) { + StrictMock crypto; + + std::string kL1DeviceId = kDeviceId + "L1"; + std::string kL3DeviceId = kDeviceId + "L3"; + + const std::string kAppPackageName("com.google.widevine"); + constexpr size_t kSpoidQuery = 2; + std::vector spoid[kSpoidQuery]; + + android::sp> cdm = new StrictMock(); + + 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>(cdmSessionId), + testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*cdm, QueryOemCryptoSessionId(cdmSessionId, _)) + .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. + android::sp plugin = new WVDrmPlugin(cdm.get(), kAppPackageName, + &crypto, true); + + // Force L3 + Status status = plugin->setPropertyString(hidl_string("securityLevel"), + hidl_string("L3")); + ASSERT_EQ(Status::OK, status); + + plugin->openSession([&](Status status, hidl_vec) { + ASSERT_EQ(Status::ERROR_DRM_NOT_PROVISIONED, status); + }); + + plugin->getPropertyByteArray( + hidl_string("deviceUniqueId"), + [&](Status status, hidl_vec vectorResult) { + ASSERT_EQ(Status::OK, status); + spoid[0] = vectorResult; + }); + + // 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 = new WVDrmPlugin(cdm.get(), kAppPackageName, &crypto, true); + + // Force L3 + status = plugin->setPropertyString(hidl_string("securityLevel"), + hidl_string("L3")); + ASSERT_EQ(Status::OK, status); + + plugin->openSession([&](Status status, hidl_vec hSessionId) { + ASSERT_EQ(Status::OK, status); + sessionId.clear(); + sessionId.assign(hSessionId.data(), + hSessionId.data() + hSessionId.size()); + }); + + EXPECT_EQ(Status::OK, plugin->closeSession(toHidlVec(sessionId))); + + plugin->getPropertyByteArray( + hidl_string("deviceUniqueId"), + [&](Status status, hidl_vec vectorResult) { + ASSERT_EQ(Status::OK, status); + spoid[1] = vectorResult; + }); + + EXPECT_EQ(spoid[0], spoid[1]); +} + +TEST_F(WVDrmPluginTest, ReturnsL1SpoidsWhenL3ProvisionedUsingL1Spoid) { + StrictMock crypto; + + std::string kL1DeviceId = kDeviceId + "L1"; + std::string kL3DeviceId = kDeviceId + "L3"; + + const std::string kAppPackageName("com.google.widevine"); + constexpr size_t kSpoidQuery = 2; + std::vector spoidL1; + std::vector spoidL3[kSpoidQuery]; + + // Set expectations for the first plugin instance + android::sp> cdm1 = new StrictMock(); + + 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>(cdmSessionId), + testing::Return(wvcdm::NO_ERROR))) + .WillOnce(testing::Return(wvcdm::NEED_PROVISIONING)); + + EXPECT_CALL(*cdm1, QueryOemCryptoSessionId(cdmSessionId, _)) + .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 + android::sp plugin = new WVDrmPlugin(cdm1.get(), kAppPackageName, + &crypto, true); + + plugin->openSession([&](Status status, hidl_vec hSessionId) { + ASSERT_EQ(Status::OK, status); + sessionId.clear(); + sessionId.assign(hSessionId.data(), + hSessionId.data() + hSessionId.size()); + }); + + EXPECT_EQ(Status::OK, plugin->closeSession(toHidlVec(sessionId))); + + plugin->getPropertyByteArray( + hidl_string("deviceUniqueId"), + [&](Status status, hidl_vec vectorResult) { + ASSERT_EQ(Status::OK, status); + spoidL1 = vectorResult; + }); + + // 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. + Status status = plugin->setPropertyString(hidl_string("securityLevel"), + hidl_string("L3")); + ASSERT_EQ(Status::OK, status); + + plugin->openSession([&](Status status, hidl_vec) { + ASSERT_EQ(Status::ERROR_DRM_NOT_PROVISIONED, status); + }); + + plugin->getPropertyByteArray( + hidl_string("deviceUniqueId"), + [&](Status status, hidl_vec vectorResult) { + ASSERT_EQ(Status::OK, status); + spoidL3[0] = vectorResult; + }); + + // Set expectations for the second plugin instance + android::sp> cdm2 = new StrictMock(); + + 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>(cdmSessionId), + testing::Return(wvcdm::NO_ERROR))); + + EXPECT_CALL(*cdm2, QueryOemCryptoSessionId(cdmSessionId, _)) + .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 = new WVDrmPlugin(cdm2.get(), kAppPackageName, &crypto, true); + + // Force L3 + status = plugin->setPropertyString(hidl_string("securityLevel"), + hidl_string("L3")); + ASSERT_EQ(Status::OK, status); + + plugin->openSession([&](Status status, hidl_vec hSessionId) { + ASSERT_EQ(Status::OK, status); + sessionId.clear(); + sessionId.assign(hSessionId.data(), + hSessionId.data() + hSessionId.size()); + }); + + EXPECT_EQ(Status::OK, plugin->closeSession(toHidlVec(sessionId))); + + plugin->getPropertyByteArray( + hidl_string("deviceUniqueId"), + [&](Status status, hidl_vec vectorResult) { + ASSERT_EQ(Status::OK, status); + spoidL3[1] = vectorResult; + }); + + EXPECT_EQ(spoidL3[0], spoidL3[1]); + EXPECT_EQ(spoidL1, spoidL3[0]); +} + TEST_F(WVDrmPluginTest, FailsGenericMethodsWithoutAnAlgorithmSet) { android::sp> cdm = new StrictMock(); StrictMock crypto;