Files
android/libwvdrmengine/mediadrm/test/WVDrmPlugin_hal_test.cpp
Alex Dale 57d231db1b Check if license exists before calling remove.
[ Partial cherry-pick of http://go/wvgerrit/186230 ]

The removeOfflineLicense() API in the Media DRM plug would attempt
to remove the specified license from L1, then retry L3 if L1 failed
for any reason.  This causes error emitted by L1 to be masked by
errors emitted from L3.  In particular, if an internal error occurs
on L1 when removing the license, because the plugin would then
try L3 which does not contain the license, the app will receive either
a "does not exist" or "needs provisioning" error from L3.

This CL changes the plugin to first determines which security level
the license exists for.  Then only attempts removal on that security
level.

Bug: 301910628
Bug: 291181955
Bug: 296300842
Bug: 302612540
Test: MediaDrmParameterizedTests GTS on bluejay
Merged from https://widevine-internal-review.googlesource.com/187611

Merged from https://widevine-internal-review.googlesource.com/187832

Change-Id: I3d3975f945d2e97cfa9d866baf6ca5cf901f8af5
2024-02-01 13:40:53 -08:00

3299 lines
127 KiB
C++

//
// 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 <stdio.h>
#include <utils/Log.h>
#include <list>
#include <ostream>
#include <string>
#include <vector>
#include "WVDrmPlugin.h"
#include "WVErrors.h"
#include "cdm_client_property_set.h"
#include "cdm_random.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<uint8_t> 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::Contains;
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::CdmUsageReport;
using wvcdm::CdmUsageReportList;
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_ANALOG_OUTPUT_CAPABILITIES;
using wvcdm::QUERY_KEY_CAN_DISABLE_ANALOG_OUTPUT;
using wvcdm::QUERY_KEY_CURRENT_HDCP_LEVEL;
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_HDCP_LEVEL;
using wvcdm::QUERY_KEY_MAX_NUMBER_OF_SESSIONS;
using wvcdm::QUERY_KEY_MAX_USAGE_TABLE_ENTRIES;
using wvcdm::QUERY_KEY_NUMBER_OF_OPEN_SESSIONS;
using wvcdm::QUERY_KEY_OEMCRYPTO_API_MINOR_VERSION;
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_PRODUCTION_READY;
using wvcdm::QUERY_KEY_PROVISIONING_ID;
using wvcdm::QUERY_KEY_PROVISIONING_MODEL;
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_USAGE_SUPPORT;
using wvcdm::QUERY_KEY_WATERMARKING_SUPPORT;
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;
using wvutil::CdmRandom;
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<const uint8_t *>("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]))
class MockCDM : public WvContentDecryptionModule {
public:
virtual ~MockCDM() {}
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 &, CdmUsageReportList *),
(override));
MOCK_METHOD(CdmResponseType, GetUsageInfo,
(const std::string &, const CdmSecureStopId &,
const CdmIdentifier &, CdmUsageReport *),
(override));
MOCK_METHOD(CdmResponseType, RemoveAllUsageInfo,
(const std::string &, const CdmIdentifier &), (override));
MOCK_METHOD(CdmResponseType, ReleaseUsageInfo,
(const CdmKeyResponse &, const CdmIdentifier &), (override));
MOCK_METHOD(bool, IsValidServiceCertificate, (const std::string &),
(override));
MOCK_METHOD(CdmResponseType, GetCurrentMetrics,
(const CdmIdentifier &, drm_metrics::WvCdmMetrics *), (override));
MOCK_METHOD(CdmResponseType, GetDecryptHashError,
(const CdmSessionId &, std::string *), (override));
MOCK_METHOD(CdmResponseType, ListStoredLicenses,
(CdmSecurityLevel, const CdmIdentifier &,
std::vector<std::string> *),
(override));
MOCK_METHOD(CdmResponseType, GetOfflineLicenseState,
(const std::string &, CdmSecurityLevel, const CdmIdentifier &,
CdmOfflineLicenseState *),
(override));
MOCK_METHOD(CdmResponseType, RemoveOfflineLicense,
(const std::string &, CdmSecurityLevel, const CdmIdentifier &),
(override));
MOCK_METHOD(CdmResponseType, StoreAtscLicense,
(const CdmIdentifier &, wvcdm::RequestedSecurityLevel,
const std::string &, const std::string &),
(override));
// Generic crypto API.
MOCK_METHOD(CdmResponseType, GenericEncrypt,
(const CdmSessionId &, const wvcdm::KeyId &, const std::string &,
const std::string &, wvcdm::CdmEncryptionAlgorithm,
std::string *));
MOCK_METHOD(CdmResponseType, GenericDecrypt,
(const CdmSessionId &, const wvcdm::KeyId &, const std::string &,
const std::string &, wvcdm::CdmEncryptionAlgorithm,
std::string *));
MOCK_METHOD(CdmResponseType, GenericSign,
(const CdmSessionId &, const wvcdm::KeyId &, const std::string &,
wvcdm::CdmSigningAlgorithm, std::string *));
MOCK_METHOD(CdmResponseType, GenericVerify,
(const CdmSessionId &, const wvcdm::KeyId &, const std::string &,
wvcdm::CdmSigningAlgorithm, const std::string &));
};
class MockCrypto : public WVGenericCryptoInterface {
public:
MOCK_METHOD(OEMCryptoResult, loadDeviceRSAKey,
(OEMCrypto_SESSION, const uint8_t *, size_t), (override));
};
} // anonymous namespace
template <uint8_t DIGIT>
CdmResponseType setSessionIdOnMap(testing::Unused, CdmQueryMap *map) {
static const char oecId[] = {DIGIT + '0', '\0'};
(*map)[QUERY_KEY_OEMCRYPTO_SESSION_ID] = oecId;
return CdmResponseType(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<uint8_t> sessionId;
uint8_t keySetIdRaw[kKeySetIdSize];
uint8_t sessionIdRaw[kSessionIdSize];
CdmSessionId mCdmSessionId;
KeySetId keySetId;
android::sp<StrictMock<MockCDM>> mCdm = nullptr;
std::shared_ptr<WVDrmPlugin> mPlugin = nullptr;
std::string mAppPackageName;
StrictMock<MockCrypto> 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<char *>(keySetIdRaw),
kKeySetIdSize);
keySetId.keySetId.assign(keySetIdRaw, keySetIdRaw + kKeySetIdSize);
// Set default return values for gMock
DefaultValue<CdmResponseType>::Set(CdmResponseType(wvcdm::NO_ERROR));
DefaultValue<OEMCryptoResult>::Set(OEMCrypto_SUCCESS);
DefaultValue<bool>::Set(true);
mCdm = new StrictMock<MockCDM>();
ASSERT_TRUE(mCdm) << "Failed to create mocked CDM";
mPlugin = ::ndk::SharedRefBase::make<WVDrmPlugin>(
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 &param, ::std::ostream *os) {
*os << param.name << " Variant";
}
class WVDrmPluginHalOriginTest : public WVDrmPluginHalTest,
public WithParamInterface<OriginTestVariant> {
};
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(CdmResponseType(wvcdm::NO_ERROR))))
.WillOnce(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L3),
testing::Return(CdmResponseType(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(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, OpenSession(_, _, _, _, _))
.WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId),
SaveArg<1>(&propertySet),
testing::Return(CdmResponseType(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(CdmResponseType(wvcdm::SESSION_NOT_FOUND_1)));
auto ret = mPlugin->closeSession(sessionId);
EXPECT_EQ(static_cast<int>(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<char *>(keySetIdRaw),
kKeySetIdSize);
std::vector<uint8_t> keySetId;
keySetId.assign(keySetIdRaw, keySetIdRaw + kKeySetIdSize);
CdmInitData mCdmInitData(reinterpret_cast<char *>(initDataRaw),
kInitDataSize);
std::vector<uint8_t> 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<char *>(psshBoxRaw), kPsshBoxSize);
std::vector<uint8_t> psshBox;
psshBox.assign(psshBoxRaw, psshBoxRaw + kPsshBoxSize);
CdmKeyMessage mCdmRequest(requestRaw, requestRaw + kRequestSize);
std::map<std::string, std::string> parameters = {
{"paddingScheme", "BUBBLE WRAP"},
{"favorite-particle", "tetraquark"},
{"answer", "6 * 9"},
};
CdmAppParameterMap mCdmParameters;
for (const auto &pair : parameters) {
mCdmParameters[pair.first] = pair.second;
}
std::vector<KeyValue> 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<uint8_t> &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(CdmResponseType(wvcdm::KEY_MESSAGE))));
EXPECT_CALL(*mCdm,
GenerateKeyRequest(mCdmSessionId, "", mimeType, initData,
kLicenseTypeStreaming, mCdmParameters,
NotNull(), HasOrigin(EMPTY_ORIGIN), _))
.WillOnce(
DoAll(SetArgPointee<8>(renewalRequest),
testing::Return(CdmResponseType(wvcdm::KEY_MESSAGE))));
EXPECT_CALL(*mCdm,
GenerateKeyRequest("", mCdmKeySetId, mimeType, initData,
kLicenseTypeRelease, mCdmParameters,
NotNull(), HasOrigin(EMPTY_ORIGIN), _))
.WillOnce(
DoAll(SetArgPointee<8>(releaseRequest),
testing::Return(CdmResponseType(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<uint8_t> &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<uint8_t> response(responseRaw, responseRaw + kResponseSize);
memcpy(keySetIdRaw, KEY_SET_ID_PREFIX, sizeof(KEY_SET_ID_PREFIX) - 1);
CdmKeySetId mCdmKeySetId(reinterpret_cast<char *>(keySetIdRaw),
kKeySetIdSize);
EXPECT_CALL(*mCdm, AddKey(mCdmSessionId,
ElementsAreArray(responseRaw, kResponseSize), _))
.WillOnce(
DoAll(SetArgPointee<2>(mCdmKeySetId),
testing::Return(wvcdm::CdmResponseType(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(CdmResponseType(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(CdmResponseType(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<uint8_t> response;
response.assign(responseRaw, responseRaw + kResponseSize);
std::vector<uint8_t> 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<std::string, std::string> 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(CdmResponseType(wvcdm::NO_ERROR))));
std::vector<KeyValue> 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<std::string, std::string>::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(CdmResponseType(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(CdmResponseType(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(CdmResponseType(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<uint8_t> 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<uint8_t> 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<uint8_t> 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<int>(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<uint8_t> 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(CdmResponseType(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<uint8_t> cert;
std::vector<uint8_t> 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(CdmResponseType(wvcdm::UNKNOWN_ERROR)))
.WillOnce(testing::Return(CdmResponseType(wvcdm::NO_ERROR)))
.WillOnce(testing::Return(CdmResponseType(wvcdm::UNKNOWN_ERROR)));
EXPECT_CALL(*mCdm, Unprovision(kSecurityLevelL3, HasOrigin(EMPTY_ORIGIN)))
.WillOnce(testing::Return(CdmResponseType(wvcdm::NO_ERROR)))
.WillOnce(testing::Return(CdmResponseType(wvcdm::UNKNOWN_ERROR)))
.WillOnce(testing::Return(CdmResponseType(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<uint8_t> 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<uint8_t> specialResponse;
specialResponse.assign(kUnprovisionResponse,
kUnprovisionResponse + kUnprovisionResponseSize);
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
ASSERT_TRUE(cdm) << "Failed to create mocked CDM";
EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_DEVICE_ID, _))
.WillRepeatedly(DoAll(SetArgPointee<2>(kDeviceId),
testing::Return(CdmResponseType(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<WVDrmPlugin> plugin = ::ndk::SharedRefBase::make<WVDrmPlugin>(
cdm.get(), mAppPackageName.c_str(), &mCrypto, true);
ASSERT_TRUE(plugin) << "Failed to create drm plugin";
StrictMock<MockCrypto> crypto;
ProvideProvisionResponseResult result;
auto ret = plugin->provideProvisionResponse(specialResponse, &result);
EXPECT_TRUE(ret.isOk());
}
TEST_F(WVDrmPluginHalTest, WillNotUnprovisionWithoutOriginOrSpoid) {
std::vector<uint8_t> 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<uint8_t> 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(CdmResponseType(wvcdm::UNKNOWN_ERROR)))
.WillOnce(testing::Return(CdmResponseType(wvcdm::NO_ERROR)))
.WillOnce(testing::Return(CdmResponseType(wvcdm::UNKNOWN_ERROR)));
EXPECT_CALL(*mCdm, Unprovision(kSecurityLevelL3, HasOrigin(kOrigin.c_str())))
.WillOnce(testing::Return(CdmResponseType(wvcdm::NO_ERROR)))
.WillOnce(testing::Return(CdmResponseType(wvcdm::UNKNOWN_ERROR)))
.WillOnce(testing::Return(CdmResponseType(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 constexpr uint32_t kStopSize = 53;
static constexpr uint32_t kStopCount = 7;
CdmUsageReportList fakeSecureStops;
for (uint32_t i = 0; i < kStopCount; ++i) {
fakeSecureStops.push_back(CdmRandom::RandomData(kStopSize));
}
const std::string app_id = "my_app_id";
EXPECT_CALL(*mCdm, GetUsageInfo(app_id, _, _))
.WillOnce(DoAll(SetArgPointee<2>(fakeSecureStops),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
auto ret = mPlugin->setPropertyString("appId", app_id);
EXPECT_TRUE(ret.isOk());
std::vector<SecureStop> secureStops;
ret = mPlugin->getSecureStops(&secureStops);
EXPECT_TRUE(ret.isOk());
CdmUsageReportList stops;
for (const auto &stop : secureStops) {
stops.emplace_back(stop.opaqueData.begin(), stop.opaqueData.end());
}
EXPECT_EQ(kStopCount, stops.size());
for (const CdmUsageReport &expectedSecureStop : fakeSecureStops) {
EXPECT_THAT(stops, Contains(expectedSecureStop));
}
}
TEST_F(WVDrmPluginHalTest, ReleasesAllSecureStops) {
const std::string app_id = "";
EXPECT_CALL(*mCdm, RemoveAllUsageInfo(app_id, _)).Times(1);
auto ret = mPlugin->setPropertyString("appId", app_id);
EXPECT_TRUE(ret.isOk());
ret = mPlugin->releaseAllSecureStops();
EXPECT_TRUE(ret.isOk());
}
TEST_F(WVDrmPluginHalTest, ReleasesSecureStop) {
static constexpr uint32_t kMessageSize = 128;
const CdmKeyResponse releaseMessage = CdmRandom::RandomData(kMessageSize);
EXPECT_CALL(*mCdm, ReleaseUsageInfo(releaseMessage, _)).Times(1);
SecureStopId stopId;
stopId.secureStopId.assign(releaseMessage.begin(), releaseMessage.end());
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 analogOutputCap = wvcdm::QUERY_VALUE_CGMS_A;
static const std::string currentHdcpLevel = wvcdm::QUERY_VALUE_HDCP_NONE;
static const std::string maxHdcpLevel = wvcdm::QUERY_VALUE_HDCP_V2_3;
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 maxUsageEntries = "256";
static const std::string oemCryptoApiVersion = "13";
static const std::string oemCryptoApiMinorVersion = "2";
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";
static const std::string provisioningModel = "Zaphod Beeblebrox";
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(CdmResponseType(wvcdm::NO_ERROR))))
.WillRepeatedly(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L3),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_DEVICE_ID, _))
.WillOnce(DoAll(SetArgPointee<2>(kDeviceId),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_SYSTEM_ID, _))
.WillOnce(DoAll(SetArgPointee<2>(systemId),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_PROVISIONING_ID, _))
.WillOnce(DoAll(SetArgPointee<2>(provisioningId),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_NUMBER_OF_OPEN_SESSIONS, _))
.WillOnce(DoAll(SetArgPointee<2>(openSessions),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_MAX_NUMBER_OF_SESSIONS, _))
.WillOnce(DoAll(SetArgPointee<2>(maxSessions),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_OEMCRYPTO_API_VERSION, _))
.WillOnce(DoAll(SetArgPointee<2>(oemCryptoApiVersion),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_OEMCRYPTO_API_MINOR_VERSION, _))
.WillOnce(DoAll(SetArgPointee<2>(oemCryptoApiMinorVersion),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_SRM_UPDATE_SUPPORT, _))
.WillOnce(DoAll(SetArgPointee<2>("True"),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_CURRENT_SRM_VERSION, _))
.WillOnce(DoAll(SetArgPointee<2>(currentSRMVersion),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_WVCDM_VERSION, _))
.WillOnce(DoAll(SetArgPointee<2>(mCdmVersion),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_RESOURCE_RATING_TIER, _))
.WillOnce(DoAll(SetArgPointee<2>(resourceRatingTier),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_DECRYPT_HASH_SUPPORT, _))
.WillOnce(DoAll(SetArgPointee<2>("1"),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_OEMCRYPTO_BUILD_INFORMATION, _))
.WillOnce(DoAll(SetArgPointee<2>(oemCryptoBuildInformation),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, GetDecryptHashError(_, _))
.WillOnce(DoAll(SetArgPointee<1>(decryptHashErrorBadHashAndFrameNumber),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_PROVISIONING_MODEL, _))
.WillOnce(DoAll(SetArgPointee<2>(provisioningModel),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_ANALOG_OUTPUT_CAPABILITIES, _))
.WillOnce(DoAll(SetArgPointee<2>(analogOutputCap),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_CAN_DISABLE_ANALOG_OUTPUT, _))
.WillOnce(DoAll(SetArgPointee<2>("True"),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_CURRENT_HDCP_LEVEL, _))
.WillOnce(DoAll(SetArgPointee<2>(currentHdcpLevel),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_MAX_HDCP_LEVEL, _))
.WillOnce(DoAll(SetArgPointee<2>(maxHdcpLevel),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_MAX_USAGE_TABLE_ENTRIES, _))
.WillOnce(DoAll(SetArgPointee<2>(maxUsageEntries),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_PRODUCTION_READY, _))
.WillOnce(DoAll(SetArgPointee<2>(wvcdm::QUERY_VALUE_TRUE),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_USAGE_SUPPORT, _))
.WillOnce(DoAll(SetArgPointee<2>("True"),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_WATERMARKING_SUPPORT, _))
.WillOnce(DoAll(SetArgPointee<2>(wvcdm::QUERY_VALUE_NOT_SUPPORTED),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, GetCurrentMetrics(_, _))
.WillOnce(DoAll(SetArgPointee<1>(expected_metrics),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
std::string stringResult;
std::vector<uint8_t> 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("oemCryptoApiMinorVersion"),
&stringResult);
EXPECT_TRUE(ret.isOk());
EXPECT_STREQ(oemCryptoApiMinorVersion.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);
ret = mPlugin->getPropertyString(std::string("provisioningModel"),
&stringResult);
EXPECT_TRUE(ret.isOk());
EXPECT_EQ(provisioningModel, stringResult);
ret = mPlugin->getPropertyString(std::string("analogOutputCapabilities"),
&stringResult);
EXPECT_TRUE(ret.isOk());
EXPECT_EQ(analogOutputCap, stringResult.c_str());
ret = mPlugin->getPropertyString(std::string("canDisableAnalogOutput"),
&stringResult);
EXPECT_TRUE(ret.isOk());
EXPECT_STREQ("True", stringResult.c_str());
ret = mPlugin->getPropertyString(std::string("hdcpLevel"), &stringResult);
EXPECT_TRUE(ret.isOk());
EXPECT_EQ(currentHdcpLevel, stringResult.c_str());
ret = mPlugin->getPropertyString(std::string("maxHdcpLevel"), &stringResult);
EXPECT_TRUE(ret.isOk());
EXPECT_EQ(maxHdcpLevel, stringResult.c_str());
ret = mPlugin->getPropertyString(std::string("maxUsageEntriesSupported"),
&stringResult);
EXPECT_TRUE(ret.isOk());
EXPECT_EQ(maxUsageEntries, stringResult.c_str());
ret = mPlugin->getPropertyString(std::string("usageReportingSupport"),
&stringResult);
EXPECT_TRUE(ret.isOk());
EXPECT_STREQ("True", stringResult.c_str());
ret =
mPlugin->getPropertyString(std::string("productionReady"), &stringResult);
EXPECT_TRUE(ret.isOk());
EXPECT_EQ(wvcdm::QUERY_VALUE_TRUE, stringResult.c_str());
ret = mPlugin->getPropertyString(std::string("watermarkingSupport"),
&stringResult);
EXPECT_TRUE(ret.isOk());
EXPECT_EQ(wvcdm::QUERY_VALUE_NOT_SUPPORTED, stringResult.c_str());
// 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(CdmResponseType(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<uint8_t> 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<uint8_t> 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<uint8_t> spoids[kDeviceCount][kAppCount][kOriginCount]
[kPluginCount];
StrictMock<MockCrypto> crypto;
for (size_t deviceIndex = 0; deviceIndex < kDeviceCount; ++deviceIndex) {
const std::string &deviceId = kDeviceIds[deviceIndex];
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
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(CdmResponseType(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<WVDrmPlugin> plugin =
::ndk::SharedRefBase::make<WVDrmPlugin>(
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<uint8_t> 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<uint8_t> &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<uint8_t> &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<uint8_t> spoid[kSpoidQuery];
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
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(CdmResponseType(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(CdmResponseType(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<MockCrypto> crypto;
for (int i = 0; i < kSpoidQuery; ++i) {
std::shared_ptr<WVDrmPlugin> plugin =
::ndk::SharedRefBase::make<WVDrmPlugin>(
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<uint8_t> spoid[kSpoidQuery];
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
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(CdmResponseType(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(CdmResponseType(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<MockCrypto> crypto;
for (size_t i = 0; i < kSpoidQuery; ++i) {
std::shared_ptr<WVDrmPlugin> plugin =
::ndk::SharedRefBase::make<WVDrmPlugin>(
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<uint8_t> 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<uint8_t> spoid[kSpoidQuery];
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
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(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*cdm, QueryStatus(wvcdm::kLevel3, QUERY_KEY_DEVICE_ID, _))
.Times(kSpoidQuery)
.WillRepeatedly(DoAll(SetArgPointee<2>(kL3DeviceId),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
// Provide expected behavior to support session creation
EXPECT_CALL(*cdm, OpenSession(StrEq("com.widevine"), _, _, _, _))
.Times(kSpoidQuery)
.WillOnce(testing::Return(CdmResponseType(wvcdm::NEED_PROVISIONING)))
.WillOnce(DoAll(SetArgPointee<4>(mCdmSessionId),
testing::Return(CdmResponseType(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<MockCrypto> crypto;
std::shared_ptr<WVDrmPlugin> plugin = ::ndk::SharedRefBase::make<WVDrmPlugin>(
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<int>(Status::ERROR_DRM_NOT_PROVISIONED),
ret.getServiceSpecificError());
std::vector<uint8_t> 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<WVDrmPlugin>(
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<uint8_t> spoidL1;
std::vector<uint8_t> spoidL3[kSpoidQuery];
// Set expectations for the first mPlugin instance
android::sp<StrictMock<MockCDM>> cdm1 = new StrictMock<MockCDM>();
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(CdmResponseType(wvcdm::NO_ERROR))));
// Provide expected behavior to support session creation
EXPECT_CALL(*cdm1, OpenSession(StrEq("com.widevine"), _, _, _, _))
.WillOnce(DoAll(SetArgPointee<4>(mCdmSessionId),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))))
.WillOnce(testing::Return(CdmResponseType(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<MockCrypto> crypto;
std::shared_ptr<WVDrmPlugin> plugin = ::ndk::SharedRefBase::make<WVDrmPlugin>(
cdm1.get(), kAppPackageName.c_str(), &crypto, true);
ASSERT_TRUE(plugin) << "Failed to create drm plugin";
std::vector<uint8_t> sessionId;
auto ret = plugin->openSession(SecurityLevel::DEFAULT, &sessionId);
EXPECT_TRUE(ret.isOk());
ret = plugin->closeSession(sessionId);
EXPECT_TRUE(ret.isOk());
std::vector<uint8_t> 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<int>(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<StrictMock<MockCDM>> cdm2 = new StrictMock<MockCDM>();
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(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*cdm2, QueryStatus(wvcdm::kLevel3, QUERY_KEY_DEVICE_ID, _))
.WillOnce(DoAll(SetArgPointee<2>(kL3DeviceId),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
// Provide expected behavior to support session creation
EXPECT_CALL(*cdm2, OpenSession(StrEq("com.widevine"), _, _, _, _))
.WillOnce(DoAll(SetArgPointee<4>(mCdmSessionId),
testing::Return(CdmResponseType(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<WVDrmPlugin>(
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<uint8_t> keyId;
std::vector<uint8_t> input;
std::vector<uint8_t> iv;
std::vector<uint8_t> 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(CdmResponseType(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<uint8_t> result;
ret = mPlugin->encrypt(sessionId, keyId, input, iv, &result);
// NO_INIT is converted to Status::ERROR_DRM_UNKNOWN
EXPECT_EQ(static_cast<int>(Status::ERROR_DRM_UNKNOWN),
ret.getServiceSpecificError());
ret = mPlugin->decrypt(sessionId, keyId, input, iv, &result);
EXPECT_EQ(static_cast<int>(Status::ERROR_DRM_UNKNOWN),
ret.getServiceSpecificError());
ret = mPlugin->sign(sessionId, keyId, input, &result);
EXPECT_EQ(static_cast<int>(Status::ERROR_DRM_UNKNOWN),
ret.getServiceSpecificError());
bool match = false;
ret = mPlugin->verify(sessionId, keyId, input, signature, &match);
EXPECT_EQ(static_cast<int>(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<uint8_t> keyId;
keyId.assign(keyIdRaw, keyIdRaw + KEY_ID_SIZE);
std::vector<uint8_t> input;
input.assign(inputRaw, inputRaw + kDataSize);
std::vector<uint8_t> iv;
iv.assign(ivRaw, ivRaw + KEY_IV_SIZE);
std::vector<uint8_t> output;
// Provide expected behavior to support session creation
EXPECT_CALL(*mCdm, OpenSession(StrEq("com.widevine"), _, _, _, _))
.Times(AtLeast(1))
.WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _))
.Times(AtLeast(1))
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
EXPECT_CALL(*mCdm,
GenericEncrypt(mCdmSessionId, _, _, _,
wvcdm::kEncryptionAlgorithmAesCbc128, NotNull()))
.WillOnce(testing::Return(CdmResponseType(wvcdm::NO_ERROR)));
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<uint8_t> keyId;
keyId.assign(keyIdRaw, keyIdRaw + KEY_ID_SIZE);
std::vector<uint8_t> input;
input.assign(inputRaw, inputRaw + kDataSize);
std::vector<uint8_t> iv;
iv.assign(ivRaw, ivRaw + KEY_IV_SIZE);
std::vector<uint8_t> output;
// Provide expected behavior to support session creation
EXPECT_CALL(*mCdm, OpenSession(StrEq("com.widevine"), _, _, _, _))
.Times(AtLeast(1))
.WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _))
.Times(AtLeast(1))
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
EXPECT_CALL(*mCdm,
GenericDecrypt(mCdmSessionId, _, _, _,
wvcdm::kEncryptionAlgorithmAesCbc128, NotNull()))
.WillOnce(testing::Return(CdmResponseType(wvcdm::NO_ERROR)));
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<uint8_t> keyId;
keyId.assign(keyIdRaw, keyIdRaw + KEY_ID_SIZE);
std::vector<uint8_t> message;
message.assign(messageRaw, messageRaw + kDataSize);
// Provide expected behavior to support session creation
EXPECT_CALL(*mCdm, OpenSession(StrEq("com.widevine"), _, _, _, _))
.Times(AtLeast(1))
.WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _))
.Times(AtLeast(1))
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
const std::string kFakeSign = "super secure signature generated";
EXPECT_CALL(*mCdm, GenericSign(mCdmSessionId, _, _,
wvcdm::kSigningAlgorithmHmacSha256, NotNull()))
.WillOnce(DoAll(SetArgPointee<4>(kFakeSign),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
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());
std::vector<uint8_t> signature;
ret = mPlugin->sign(sessionId, keyId, message, &signature);
EXPECT_TRUE(ret.isOk());
const std::vector<uint8_t> kFakeSignVec(kFakeSign.begin(), kFakeSign.end());
ASSERT_EQ(signature, kFakeSignVec);
}
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<uint8_t> keyId;
keyId.assign(keyIdRaw, keyIdRaw + KEY_ID_SIZE);
std::vector<uint8_t> message;
message.assign(messageRaw, messageRaw + kDataSize);
std::vector<uint8_t> signature;
signature.assign(signatureRaw, signatureRaw + kSignatureSize);
const wvcdm::CdmResponseType kSignatureOkResponse(wvcdm::NO_ERROR);
const wvcdm::CdmResponseType kSignatureBadResponse(
wvcdm::UNKNOWN_ERROR, OEMCrypto_ERROR_SIGNATURE_FAILURE, "GenericVerify");
// Provide expected behavior to support session creation
EXPECT_CALL(*mCdm, OpenSession(StrEq("com.widevine"), _, _, _, _))
.Times(AtLeast(1))
.WillRepeatedly(DoAll(SetArgPointee<4>(mCdmSessionId),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, QueryOemCryptoSessionId(mCdmSessionId, _))
.Times(AtLeast(1))
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
EXPECT_CALL(*mCdm, GenericVerify(mCdmSessionId, _, _,
wvcdm::kSigningAlgorithmHmacSha256, _))
.WillOnce(testing::Return(kSignatureOkResponse))
.WillOnce(testing::Return(kSignatureBadResponse));
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(CdmResponseType(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(CdmResponseType(wvcdm::NO_ERROR))))
.WillOnce(DoAll(SetArgPointee<4>(mCdmSessionId2),
testing::Return(CdmResponseType(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<uint8_t> keyId;
std::vector<KeyStatus> keyStatusList;
KeyStatus keyStatus;
keyId.assign(kKeyId1.begin(), kKeyId1.end());
keyStatus.keyId = keyId;
keyStatus.type = KeyStatusType::EXPIRED;
keyStatusList.push_back(keyStatus);
std::vector<KeyStatus> 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<KeyStatus> 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(CdmResponseType(wvcdm::NEED_PROVISIONING))));
EXPECT_CALL(*mCdm, CloseSession(_)).Times(AtLeast(0));
SessionId sessionId;
auto ret = mPlugin->openSession(SecurityLevel::DEFAULT, &sessionId);
EXPECT_EQ(static_cast<int>(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(CdmResponseType(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(CdmResponseType(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(CdmResponseType(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<int>(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(CdmResponseType(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(CdmResponseType(wvcdm::NO_ERROR))))
.WillOnce(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L1),
testing::Return(CdmResponseType(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(CdmResponseType(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(CdmResponseType(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<uint8_t> privacyCert;
privacyCert.assign(privacyCertRaw, privacyCertRaw + kPrivacyCertSize);
std::string strPrivacyCert(reinterpret_cast<char *>(privacyCertRaw),
kPrivacyCertSize);
std::vector<uint8_t> 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(CdmResponseType(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(CdmResponseType(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(CdmResponseType(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<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> 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(CdmResponseType(wvcdm::NO_ERROR))))
.WillOnce(DoAll(SetArgPointee<4>(mCdmSessionId),
SaveArg<1>(&propertySet),
SaveArg<2>(&mCdmIdAtscModeSet),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))))
.WillOnce(DoAll(SetArgPointee<4>(mCdmSessionId),
SaveArg<1>(&propertySet),
SaveArg<2>(&mCdmIdAtscModeReset),
testing::Return(CdmResponseType(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<WVDrmPlugin> plugin1 =
::ndk::SharedRefBase::make<WVDrmPlugin>(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<WVDrmPlugin> plugin2 =
::ndk::SharedRefBase::make<WVDrmPlugin>(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<WVDrmPlugin> plugin3 =
::ndk::SharedRefBase::make<WVDrmPlugin>(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(CdmResponseType(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<uint8_t> 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:
// "<sessionId>,<frameNumber>,<base64encodedHash>"
static const std::string frameNumber = ",1";
uint32_t hash = 0xbeef; // crc32 hash
const std::vector<uint8_t> hashVector(reinterpret_cast<uint8_t *>(&hash),
reinterpret_cast<uint8_t *>(&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(CdmResponseType(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<uint8_t> 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:
// "<sessionId>,<frameNumber>,<base64encodedHash>"
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, GetOfflineLicenseKeySetIds_NonAtscMode) {
const std::vector<CdmKeySetId> cdmKeySetIdsL1 = {
// Non-ATSC key set IDs
"ksid1111", "ksid2222", "ksid3333", "ksid4444",
// ATSC key set IDs.
"atscksid1111", "atscksid2222", "atscksid3333", "atsc_group1_profile1",
"atsc_group1_profile7"};
const std::vector<CdmKeySetId> cdmKeySetIdsL3 = {
// Non-ATSC key set IDs
"ksid5555", "ksid6666", "ksid7777", "ksid8888",
// ATSC key set IDs.
"atscksid5555", "atscksid6666", "atscksid7777", "atsc_group3_profile1",
"atsc_group3_profile7"};
// Expect non-ATSC key set IDs (order is important).
const std::vector<CdmKeySetId> expectedCdmKeySetIds = {
// From L1
"ksid1111", "ksid2222", "ksid3333", "ksid4444",
// From L3
"ksid5555", "ksid6666", "ksid7777", "ksid8888"};
EXPECT_CALL(*mCdm, ListStoredLicenses(kSecurityLevelL1, _, NotNull()))
.WillOnce(DoAll(SetArgPointee<2>(cdmKeySetIdsL1),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, ListStoredLicenses(kSecurityLevelL3, _, NotNull()))
.WillOnce(DoAll(SetArgPointee<2>(cdmKeySetIdsL3),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
std::vector<KeySetId> offlineKeySetIds;
const auto ret = mPlugin->getOfflineLicenseKeySetIds(&offlineKeySetIds);
ASSERT_TRUE(ret.isOk());
std::vector<CdmKeySetId> offlineCdmKeySetIds;
for (const auto &keySetId : offlineKeySetIds) {
offlineCdmKeySetIds.emplace_back(keySetId.keySetId.begin(),
keySetId.keySetId.end());
}
EXPECT_EQ(expectedCdmKeySetIds.size(), offlineCdmKeySetIds.size());
EXPECT_EQ(expectedCdmKeySetIds, offlineCdmKeySetIds);
}
TEST_F(WVDrmPluginHalTest, GetOfflineLicenseKeySetIds_AtscMode) {
const std::vector<CdmKeySetId> cdmKeySetIdsL1 = {
// Non-ATSC key set IDs
"ksid1111", "ksid2222", "ksid3333", "ksid4444",
// ATSC key set IDs.
"atscksid1111", "atscksid2222", "atscksid3333", "atsc_group1_profile1",
"atsc_group1_profile7"};
const std::vector<CdmKeySetId> cdmKeySetIdsL3 = {
// Non-ATSC key set IDs
"ksid5555", "ksid6666", "ksid7777", "ksid8888",
// ATSC key set IDs.
"atscksid5555", "atscksid6666", "atscksid7777", "atsc_group3_profile1",
"atsc_group3_profile7"};
// Expect ATSC key set IDs (order is important).
const std::vector<CdmKeySetId> expectedCdmKeySetIds = {
// From L1
"atscksid1111", "atscksid2222", "atscksid3333", "atsc_group1_profile1",
"atsc_group1_profile7",
// From L3
"atscksid5555", "atscksid6666", "atscksid7777", "atsc_group3_profile1",
"atsc_group3_profile7"};
EXPECT_CALL(*mCdm, ListStoredLicenses(kSecurityLevelL1, _, NotNull()))
.WillOnce(DoAll(SetArgPointee<2>(cdmKeySetIdsL1),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, ListStoredLicenses(kSecurityLevelL3, _, NotNull()))
.WillOnce(DoAll(SetArgPointee<2>(cdmKeySetIdsL3),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
mPlugin->setPropertyString("atscMode", "enable");
std::vector<KeySetId> offlineKeySetIds;
const auto ret = mPlugin->getOfflineLicenseKeySetIds(&offlineKeySetIds);
ASSERT_TRUE(ret.isOk());
std::vector<CdmKeySetId> offlineCdmKeySetIds;
for (const auto &keySetId : offlineKeySetIds) {
offlineCdmKeySetIds.emplace_back(keySetId.keySetId.begin(),
keySetId.keySetId.end());
}
EXPECT_EQ(expectedCdmKeySetIds.size(), offlineCdmKeySetIds.size());
EXPECT_EQ(expectedCdmKeySetIds, offlineCdmKeySetIds);
}
TEST_F(WVDrmPluginHalTest, GetOfflineLicenseState) {
EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_SECURITY_LEVEL, _))
.WillRepeatedly(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L1),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, GetOfflineLicenseState(_, kSecurityLevelL1,
HasOrigin(EMPTY_ORIGIN), _))
.WillOnce(DoAll(SetArgPointee<3>(wvcdm::kLicenseStateActive),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))))
.WillOnce(DoAll(SetArgPointee<3>(wvcdm::kLicenseStateReleasing),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))))
.WillOnce(DoAll(SetArgPointee<3>(wvcdm::kLicenseStateUnknown),
testing::Return(CdmResponseType(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_L1) {
// Key set to remove.
const CdmKeySetId cdmKeySetId = "ksidDEADBEAF";
const KeySetId keySetId{
std::vector<uint8_t>(cdmKeySetId.begin(), cdmKeySetId.end())};
// Desired key set ID found in L1.
const std::vector<CdmKeySetId> cdmKeySetIdsL1 = {"ksid1234", "ksid9876",
"ksid9999", cdmKeySetId,
"ksidBAD", "ksidCAFEB0BA"};
EXPECT_CALL(*mCdm, ListStoredLicenses(kSecurityLevelL1, _, NotNull()))
.WillOnce(DoAll(SetArgPointee<2>(cdmKeySetIdsL1),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
// Only call L1.
EXPECT_CALL(*mCdm, RemoveOfflineLicense(cdmKeySetId, kSecurityLevelL1, _))
.WillOnce(testing::Return(CdmResponseType(wvcdm::NO_ERROR)));
EXPECT_CALL(*mCdm, RemoveOfflineLicense(_, kSecurityLevelL3, _)).Times(0);
const auto status = mPlugin->removeOfflineLicense(keySetId);
ASSERT_TRUE(status.isOk());
}
TEST_F(WVDrmPluginHalTest, RemoveOfflineLicense_L3) {
// Key set to remove.
const CdmKeySetId cdmKeySetId = "ksidDEADBEAF";
const KeySetId keySetId{
std::vector<uint8_t>(cdmKeySetId.begin(), cdmKeySetId.end())};
// Desired key set ID is not found in L1.
const std::vector<CdmKeySetId> cdmKeySetIdsL1 = {"ksid1234", "ksid9876",
"ksid9999"};
// Desired key set ID found in L3.
const std::vector<CdmKeySetId> cdmKeySetIdsL3 = {
"ksidDEADC0DE", "ksid1337", cdmKeySetId, "ksidBAD", "ksidCAFEB0BA"};
EXPECT_CALL(*mCdm, ListStoredLicenses(kSecurityLevelL1, _, NotNull()))
.WillOnce(DoAll(SetArgPointee<2>(cdmKeySetIdsL1),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, ListStoredLicenses(kSecurityLevelL3, _, NotNull()))
.WillOnce(DoAll(SetArgPointee<2>(cdmKeySetIdsL3),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
// Only call L3.
EXPECT_CALL(*mCdm, RemoveOfflineLicense(_, kSecurityLevelL1, _)).Times(0);
EXPECT_CALL(*mCdm, RemoveOfflineLicense(cdmKeySetId, kSecurityLevelL3, _))
.WillOnce(testing::Return(CdmResponseType(wvcdm::NO_ERROR)));
const auto status = mPlugin->removeOfflineLicense(keySetId);
ASSERT_TRUE(status.isOk());
}
TEST_F(WVDrmPluginHalTest, RemoveOfflineLicense_NotFound) {
// Key set to remove.
const CdmKeySetId cdmKeySetId = "ksidDEADBEAF";
const KeySetId keySetId{
std::vector<uint8_t>(cdmKeySetId.begin(), cdmKeySetId.end())};
// Desired key set ID is not found in L1.
const std::vector<CdmKeySetId> cdmKeySetIdsL1 = {"ksid1234", "ksid9876",
"ksid9999"};
// Desired key set ID is not found in L3.
const std::vector<CdmKeySetId> cdmKeySetIdsL3 = {"ksidDEADC0DE", "ksid1337",
"ksidBAD", "ksidCAFEB0BA"};
EXPECT_CALL(*mCdm, ListStoredLicenses(kSecurityLevelL1, _, NotNull()))
.WillOnce(DoAll(SetArgPointee<2>(cdmKeySetIdsL1),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
EXPECT_CALL(*mCdm, ListStoredLicenses(kSecurityLevelL3, _, NotNull()))
.WillOnce(DoAll(SetArgPointee<2>(cdmKeySetIdsL3),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
// No call to RemoveOfflineLicense should be made.
EXPECT_CALL(*mCdm, RemoveOfflineLicense(_, _, _)).Times(0);
const auto status = mPlugin->removeOfflineLicense(keySetId);
ASSERT_FALSE(status.isOk());
}
TEST_F(WVDrmPluginHalTest, CanStoreAtscLicense) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
std::string appPackageName = "com.test.package";
const CdmClientPropertySet *propertySet = nullptr;
CdmIdentifier mCdmIdAtscModeSet;
// Generate ATSC keyset and license data
uint8_t atscKeySetIdRaw[kKeySetIdSize];
std::vector<uint8_t> licenseDataRaw;
const size_t kLicenseDataSize = 512;
licenseDataRaw.resize(kLicenseDataSize);
FILE *fp = fopen("/dev/urandom", "r");
ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom";
fread(atscKeySetIdRaw, sizeof(uint8_t), sizeof(atscKeySetIdRaw), fp);
fread(&licenseDataRaw[0], sizeof(uint8_t), licenseDataRaw.size(), fp);
fclose(fp);
// Generate an ATSC key set ID of the form <ATSC prefix>+<random hex>
// with a total length of kKeySetIdSize
std::string atscKeySetId(wvcdm::ATSC_KEY_SET_ID_PREFIX);
atscKeySetId.append(wvutil::HexEncode(atscKeySetIdRaw, kKeySetIdSize));
atscKeySetId.resize(kKeySetIdSize);
memcpy(atscKeySetIdRaw, atscKeySetId.c_str(), sizeof(atscKeySetIdRaw));
std::string licenseDataBase64 = Base64Encode(licenseDataRaw);
std::string atscLicenseData = atscKeySetId + ":" + licenseDataBase64;
// 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>(&mCdmIdAtscModeSet),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
// Provide expected behavior when plugin requests session control info
EXPECT_CALL(*cdm, QueryOemCryptoSessionId(mCdmSessionId, _))
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
EXPECT_CALL(
*cdm, StoreAtscLicense(HasOrigin(EMPTY_ORIGIN), wvcdm::kLevelDefault,
ElementsAreArray(atscKeySetIdRaw, kKeySetIdSize),
ElementsAreArray(licenseDataRaw)))
.Times(1);
EXPECT_CALL(*cdm, CloseSession(_)).Times(1);
}
std::shared_ptr<WVDrmPlugin> plugin = ::ndk::SharedRefBase::make<WVDrmPlugin>(
cdm.get(), appPackageName, &crypto, false);
// Test turning on ATSC mode
auto ret =
plugin->setPropertyString(std::string("atscMode"), std::string("enable"));
EXPECT_TRUE(ret.isOk());
ret = plugin->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 = plugin->setPropertyString(std::string("storeAtscLicense"),
atscLicenseData);
EXPECT_TRUE(ret.isOk());
ret = plugin->closeSession(sessionId);
EXPECT_TRUE(ret.isOk());
}
TEST_F(WVDrmPluginHalTest, FailsToStoreAtscLicense) {
android::sp<StrictMock<MockCDM>> cdm1 = new StrictMock<MockCDM>();
android::sp<StrictMock<MockCDM>> cdm2 = new StrictMock<MockCDM>();
android::sp<StrictMock<MockCDM>> cdm3 = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
std::string appPackageName = "com.test.package";
const CdmClientPropertySet *propertySet = nullptr;
CdmIdentifier mCdmIdAtscModeSet;
// Generate ATSC keyset and license data
uint8_t atscKeySetIdRaw[kKeySetIdSize];
std::vector<uint8_t> licenseDataRaw;
const size_t kLicenseDataSize = 512;
licenseDataRaw.resize(kLicenseDataSize);
FILE *fp = fopen("/dev/urandom", "r");
ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom";
fread(atscKeySetIdRaw, sizeof(uint8_t), sizeof(atscKeySetIdRaw), fp);
fread(&licenseDataRaw[0], sizeof(uint8_t), licenseDataRaw.size(), fp);
fclose(fp);
std::string atscKeySetId(wvcdm::ATSC_KEY_SET_ID_PREFIX);
atscKeySetId.append(wvutil::HexEncode(&atscKeySetIdRaw[0], kKeySetIdSize));
atscKeySetId.resize(kKeySetIdSize);
memcpy(atscKeySetIdRaw, atscKeySetId.c_str(), sizeof(atscKeySetIdRaw));
std::string licenseDataBase64 = Base64Encode(licenseDataRaw);
std::string atscLicenseData = atscKeySetId + ":" + licenseDataBase64;
// Provide expected mock behavior
{
// No mock behavior expected for plugin1/cdm1
// For plugin2/cdm2
// Provide expected behavior in response to OpenSession and store the
// property set
EXPECT_CALL(*cdm2, OpenSession(_, _, _, _, _))
.WillOnce(DoAll(SetArgPointee<4>(mCdmSessionId),
SaveArg<1>(&propertySet),
SaveArg<2>(&mCdmIdAtscModeSet),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
// Provide expected behavior when plugin2 requests session control info
EXPECT_CALL(*cdm2, QueryOemCryptoSessionId(mCdmSessionId, _))
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
EXPECT_CALL(*cdm2, CloseSession(_)).Times(1);
// For plugin3/cdm3
// Provide expected behavior in response to OpenSession and store the
// property set
EXPECT_CALL(*cdm3, OpenSession(_, _, _, _, _))
.WillOnce(DoAll(SetArgPointee<4>(mCdmSessionId),
SaveArg<1>(&propertySet),
SaveArg<2>(&mCdmIdAtscModeSet),
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
// Provide expected behavior when plugin3 requests session control info
EXPECT_CALL(*cdm3, QueryOemCryptoSessionId(mCdmSessionId, _))
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
EXPECT_CALL(*cdm3, CloseSession(_)).Times(1);
}
// Try storing an ATSC license without opening a session. Should fail.
std::shared_ptr<WVDrmPlugin> plugin1 =
::ndk::SharedRefBase::make<WVDrmPlugin>(
cdm1.get(), appPackageName.c_str(), &crypto, false);
auto ret = plugin1->setPropertyString(std::string("storeAtscLicense"),
atscLicenseData);
EXPECT_TRUE(!ret.isOk());
EXPECT_EQ(static_cast<int>(Status::ERROR_DRM_CANNOT_HANDLE),
ret.getServiceSpecificError());
// Try storing an ATSC license without setting ATSC mode. Should fail.
std::shared_ptr<WVDrmPlugin> plugin2 =
::ndk::SharedRefBase::make<WVDrmPlugin>(
cdm2.get(), appPackageName.c_str(), &crypto, false);
ret = plugin2->openSession(SecurityLevel::DEFAULT, &sessionId);
EXPECT_TRUE(ret.isOk());
ASSERT_THAT(propertySet, NotNull());
EXPECT_FALSE(propertySet->use_atsc_mode());
EXPECT_NE(mCdmIdAtscModeSet.app_package_name, wvcdm::ATSC_APP_PACKAGE_NAME);
ret = plugin2->setPropertyString(std::string("storeAtscLicense"),
atscLicenseData);
EXPECT_TRUE(!ret.isOk());
EXPECT_EQ(static_cast<int>(Status::ERROR_DRM_CANNOT_HANDLE),
ret.getServiceSpecificError());
ret = plugin2->closeSession(sessionId);
EXPECT_TRUE(ret.isOk());
// Try storing an ATSC license
// (a) Without ATSC key set prefix
// (b) Only ATSC key set
// (c) Only license data
// (d) Invalid Base64 license data
// (e) No ATSC key set
// (f) No license data
// All these should fail.
std::shared_ptr<WVDrmPlugin> plugin3 =
::ndk::SharedRefBase::make<WVDrmPlugin>(
cdm3.get(), appPackageName.c_str(), &crypto, false);
// Test turning on ATSC mode
ret = plugin3->setPropertyString(std::string("atscMode"),
std::string("enable"));
EXPECT_TRUE(ret.isOk());
ret = plugin3->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);
// (a) No ATSC key set prefix
atscKeySetId.assign(wvutil::HexEncode(&atscKeySetIdRaw[0], kKeySetIdSize));
atscKeySetId.resize(kKeySetIdSize);
atscLicenseData = atscKeySetId + ":" + licenseDataBase64;
ret = plugin3->setPropertyString(std::string("storeAtscLicense"),
atscLicenseData);
EXPECT_TRUE(!ret.isOk());
EXPECT_EQ(static_cast<int>(Status::BAD_VALUE), ret.getServiceSpecificError());
// (b) Only ATSC key set
atscKeySetId.assign(wvcdm::ATSC_KEY_SET_ID_PREFIX);
atscKeySetId.append(wvutil::HexEncode(&atscKeySetIdRaw[0], kKeySetIdSize));
atscKeySetId.resize(kKeySetIdSize);
ret =
plugin3->setPropertyString(std::string("storeAtscLicense"), atscKeySetId);
EXPECT_TRUE(!ret.isOk());
// (c) Only license data
ret = plugin3->setPropertyString(std::string("storeAtscLicense"),
licenseDataBase64);
EXPECT_TRUE(!ret.isOk());
// (d) Invalid Base64 license data
licenseDataBase64.append("$");
atscLicenseData = atscKeySetId + ":" + licenseDataBase64;
ret = plugin3->setPropertyString(std::string("storeAtscLicense"),
atscLicenseData);
EXPECT_TRUE(!ret.isOk());
// (e) No ATSC key set
licenseDataBase64 = Base64Encode(licenseDataRaw);
atscLicenseData = ":" + licenseDataBase64;
ret = plugin3->setPropertyString(std::string("storeAtscLicense"),
atscLicenseData);
EXPECT_TRUE(!ret.isOk());
// (f) No license data
atscLicenseData = atscKeySetId + ":";
ret = plugin3->setPropertyString(std::string("storeAtscLicense"),
atscLicenseData);
EXPECT_TRUE(!ret.isOk());
ret = plugin3->closeSession(sessionId);
EXPECT_TRUE(ret.isOk());
}
} // namespace widevine
} // namespace drm
} // namespace hardware
} // namespace wvdrm