[ Merge of http://go/wvgerrit/173170 ] This CL updates the Widevine's Android DRM plugin to use the generic crypto operations provided by CDM core rather than its own implementation. Bug: 274984456 Test: atest WvtsDeviceTestCases Change-Id: I94e1c92c7da577aad5ec43bd3bf0bb380b607b80
3193 lines
121 KiB
C++
3193 lines
121 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 ¶m, ::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, GetOfflineLicenseIds) {
|
|
const uint32_t kLicenseCount = 5;
|
|
|
|
uint8_t mockIdsRaw[kLicenseCount * 2][kKeySetIdSize];
|
|
FILE *fp = fopen("/dev/urandom", "r");
|
|
ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom";
|
|
for (uint32_t i = 0; i < kLicenseCount * 2; ++i) {
|
|
fread(mockIdsRaw[i], sizeof(uint8_t), kKeySetIdSize, fp);
|
|
}
|
|
fclose(fp);
|
|
|
|
std::vector<std::string> mockIdsL1;
|
|
for (uint32_t i = 0; i < kLicenseCount; ++i) {
|
|
mockIdsL1.push_back(
|
|
std::string(mockIdsRaw[i], mockIdsRaw[i] + kKeySetIdSize));
|
|
}
|
|
|
|
std::vector<std::string> mockIdsL3;
|
|
for (uint32_t i = 0; i < kLicenseCount; ++i) {
|
|
mockIdsL3.push_back(
|
|
std::string(mockIdsRaw[i + 5], mockIdsRaw[i + 5] + kKeySetIdSize));
|
|
}
|
|
|
|
EXPECT_CALL(*mCdm,
|
|
ListStoredLicenses(kSecurityLevelL1, HasOrigin(EMPTY_ORIGIN), _))
|
|
.WillOnce(DoAll(SetArgPointee<2>(mockIdsL1),
|
|
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
|
|
|
|
EXPECT_CALL(*mCdm,
|
|
ListStoredLicenses(kSecurityLevelL3, HasOrigin(EMPTY_ORIGIN), _))
|
|
.WillOnce(DoAll(SetArgPointee<2>(mockIdsL3),
|
|
testing::Return(CdmResponseType(wvcdm::NO_ERROR))));
|
|
|
|
std::vector<KeySetId> offlineIds;
|
|
auto ret = mPlugin->getOfflineLicenseKeySetIds(&offlineIds);
|
|
EXPECT_TRUE(ret.isOk());
|
|
|
|
size_t index = 0;
|
|
for (auto id : offlineIds) {
|
|
EXPECT_THAT(id.keySetId,
|
|
ElementsAreArray(mockIdsRaw[index++], kKeySetIdSize));
|
|
}
|
|
EXPECT_EQ(kLicenseCount * 2, index);
|
|
}
|
|
|
|
TEST_F(WVDrmPluginHalTest, GetOfflineLicenseState) {
|
|
EXPECT_CALL(*mCdm, QueryStatus(_, QUERY_KEY_SECURITY_LEVEL, _))
|
|
.WillRepeatedly(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L1),
|
|
testing::Return(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) {
|
|
EXPECT_CALL(
|
|
*mCdm, RemoveOfflineLicense(_, kSecurityLevelL1, HasOrigin(EMPTY_ORIGIN)))
|
|
.Times(1);
|
|
|
|
auto ret = mPlugin->removeOfflineLicense(keySetId);
|
|
EXPECT_TRUE(ret.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
|