Files
ce_cdm/cdm/test/cdm_test.cpp
2017-05-04 14:01:27 -07:00

1887 lines
67 KiB
C++

// Copyright 2015 Google Inc. All Rights Reserved.
//
// This source file provides a basic set of unit tests for the Content
// Decryption Module (CDM).
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "cdm.h"
#include "cdm_test_printers.h"
#include "config_test_env.h"
#include "decryption_test_data.h"
#include "license_request.h"
#include "log.h"
#include "OEMCryptoCENC.h"
#include "override.h"
#include "properties_ce.h"
#include "scoped_ptr.h"
#include "string_conversions.h"
#include "test_host.h"
#include "url_request.h"
using namespace testing;
using namespace wvcdm;
namespace widevine {
namespace {
const int kHttpOk = 200;
const int kRenewalTestDelayMs = 3 * 60 * 1000;
const int kExpirationTestDelayMs = 5 * 60 * 1000;
const int kOfflineLicenseDurationMs = 604800 * 1000;
const Cdm::SessionType kBogusSessionType = static_cast<Cdm::SessionType>(-1);
const Cdm::InitDataType kBogusInitDataType = static_cast<Cdm::InitDataType>(-1);
const std::string kBogusSessionId = "asdf";
std::string g_provisioning_server;
std::string g_provisioning_service_certificate;
std::string g_license_server;
std::string g_license_service_certificate;
const std::string kCencInitData = a2bs_hex(
"00000042" // blob size
"70737368" // "pssh"
"00000000" // flags
"edef8ba979d64acea3c827dcd51d21ed" // Widevine system id
"00000022" // pssh data size
// pssh data:
"08011a0d7769646576696e655f746573"
"74220f73747265616d696e675f636c69"
"7031");
const std::string kCencPersistentInitData = a2bs_hex(
"00000040" // blob size
"70737368" // "pssh"
"00000000" // flags
"edef8ba979d64acea3c827dcd51d21ed" // Widevine system id
"00000020" // pssh data size
// pssh data:
"08011a0d7769646576696e655f746573"
"74220d6f66666c696e655f636c697032");
const std::string kInvalidCencInitData = a2bs_hex(
"0000000c" // blob size
"61736466" // "asdf" (wrong box type)
"01020304"); // nonsense
const std::string kNonWidevineCencInitData = a2bs_hex(
"00000020" // blob size
"70737368" // "pssh"
"00000000" // flags
"000102030405060708090a0b0c0d0e0f" // unknown system id
"00000000"); // pssh data size
const std::string kWebMInitData = a2bs_hex("deadbeefdeadbeefdeadbeefdeadbeef");
const std::string kKeyIdsInitData =
"{\"kids\":[\"67ef0gd8pvfd0\",\"77ef0gd8pvfd0\"]}";
const std::string kHlsInitData =
"#EXT-X-KEY:METHOD=SAMPLE-AES,KEYFORMAT=\"com.widevine\",KEYFORMATVERSIONS="
"\"1\",URI=\"data:text/plain;base64,ew0KICAgInByb3ZpZGVyIjogIndpZGV2aW5lX3R"
"lc3QiLA0KICAgImNvbnRlbnRfaWQiOiAiWW1sblluVmphMkoxYm01NSIsDQogICAia2V5X2lkc"
"yI6IFsNCiAgICAgICI5Yjc1OTA0MDMyMWE0MDhhNWM3NzY4YjQ1MTEyODdhNiINCiAgIF0NCn0"
"=\",IV=0x75537a79fa41abc7b598ea72aba0c26f";
// This Key ID must match the key retrieved from the license server by
// kCencInitData.
const std::vector<uint8_t> kKeyIdCtr = a2b_hex(
"371ea35e1a985d75d198a7f41020dc23");
// This Key ID must match the key retrieved from the license server by
// kHlsInitData.
const std::vector<uint8_t> kKeyIdCbc = a2b_hex(
"9b759040321a408a5c7768b4511287a6");
// A default pattern object disables patterns during decryption.
const Cdm::Pattern kPatternNone;
// The recommended pattern from CENC 3.0, which is also the pattern used by
// HLS. Encrypts 1 in every 10 crypto blocks.
const Cdm::Pattern kPatternRecommended(1, 9);
// The recommended pattern for HLS Audio, which should be decrypted in CENC 3.0
// cbcs mode despite not using patterns. This pattern disables patterned
// decryption by having one encrypted block and no clear blocks.
const Cdm::Pattern kPatternHlsAudio(1, 0);
const std::string kValue = "A Value";
const std::string kNewValue = "A New Value";
const std::string kParamName = "PARAM";
const std::string kParamName2 = "PARAM2";
class CdmTest : public Test, public Cdm::IEventListener {
public:
CdmTest() {}
virtual ~CdmTest() {}
// IEventListener mocks:
MOCK_METHOD3(onMessage,
void(const std::string& session_id,
Cdm::MessageType message_type,
const std::string& message));
MOCK_METHOD1(onKeyStatusesChange,
void(const std::string& session_id));
MOCK_METHOD1(onRemoveComplete,
void(const std::string& session_id));
MOCK_METHOD2(onDeferredComplete,
void(const std::string& session_id, Cdm::Status error_code));
MOCK_METHOD2(onDirectIndividualizationRequest,
void(const std::string& session_id,
const std::string& message));
protected:
virtual void SetUp() OVERRIDE {
ConfigTestEnv config(kContentProtectionStagingPlusProv30);
g_provisioning_service_certificate.assign(
config.provisioning_service_certificate());
g_license_service_certificate.assign(config.license_service_certificate());
g_provisioning_server.assign(config.provisioning_server());
g_license_server.assign(config.license_server());
// Clear anything stored, load default device cert.
g_host->Reset();
// Clear anything stored by OEMCrypto.
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Initialize());
int result = OEMCrypto_DeleteUsageTable();
// Don't fault OEMCrypto implementations without usage tables:
if (result != OEMCrypto_ERROR_NOT_IMPLEMENTED) {
EXPECT_EQ(OEMCrypto_SUCCESS, result);
}
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Terminate());
// Reinit the library.
Cdm::Status status = Cdm::initialize(
Cdm::kNoSecureOutput, PropertiesCE::GetClientInfo(),
g_host, g_host, g_host, static_cast<Cdm::LogLevel>(g_cutoff));
ASSERT_EQ(Cdm::kSuccess, status);
// Make a fresh CDM.
RecreateCdm(true /* privacy_mode */);
}
virtual void TearDown() OVERRIDE {
// So the OEMCrypto nonce flood check does not trigger.
// A 500ms delay allows up to 10 nonces to be generated per test without
// triggering an OEMCrypto error.
usleep(500 * 1000);
}
virtual void Provision() {
cdm_->removeProvisioning();
cdm_->setServiceCertificate(g_provisioning_service_certificate);
}
void RecreateCdm(bool privacy_mode) {
CreateAdditionalCdm(privacy_mode, &cdm_);
cdm_->setServiceCertificate(g_license_service_certificate);
}
void CreateAdditionalCdm(bool privacy_mode, scoped_ptr<Cdm>* cdm) {
cdm->reset(Cdm::create(this, NULL, privacy_mode));
ASSERT_NE((Cdm*)0, cdm->get());
}
bool Fetch(const std::string& url,
const std::string& message,
std::string* response,
int* status_code) {
UrlRequest url_request(url);
EXPECT_TRUE(url_request.is_connected());
if (!url_request.is_connected()) {
return false;
}
url_request.PostRequest(message);
std::string http_response;
url_request.GetResponse(&http_response);
// Some license servers return 400 for invalid message, some
// return 500; treat anything other than 200 as an invalid message.
int http_status_code = url_request.GetStatusCode(http_response);
if (status_code) {
*status_code = http_status_code;
}
if (response) {
if (http_status_code == kHttpOk) {
// Parse out HTTP and server headers and return the body only.
std::string reply_body;
LicenseRequest lic_request;
lic_request.GetDrmMessage(http_response, reply_body);
*response = reply_body;
} else {
*response = http_response;
}
LOGV("Reply body(hex): \n%s\n", b2a_hex(*response).c_str());
LOGV("Reply body(b64): \n%s\n",
Base64SafeEncode(
std::vector<uint8_t>(response->begin(),
response->end())).c_str());
}
return true;
}
void FetchCertificate(const std::string& url,
std::string* response) {
int status_code;
bool ok = Fetch(url, "", response, &status_code);
ASSERT_TRUE(ok);
if (ok) ASSERT_EQ(kHttpOk, status_code);
}
void FetchLicense(const std::string& license_server,
const std::string& message,
std::string* response) {
int status_code;
bool ok = Fetch(license_server, message, response, &status_code);
ASSERT_TRUE(ok);
if (ok) ASSERT_EQ(kHttpOk, status_code);
}
void FetchLicenseFailure(const std::string& message,
int expected_status_code) {
int status_code;
bool ok = Fetch(g_license_server, message, NULL, &status_code);
ASSERT_TRUE(ok);
if (ok) ASSERT_EQ(expected_status_code, status_code);
}
void CreateSessionAndGenerateRequest(Cdm::SessionType session_type,
Cdm::InitDataType init_data_type,
std::string* session_id,
std::string* message) {
Cdm::Status status;
status = cdm_->setServiceCertificate(g_license_service_certificate);
ASSERT_EQ(Cdm::kSuccess, status);
status = cdm_->createSession(session_type, session_id);
ASSERT_EQ(Cdm::kSuccess, status);
std::string init_data;
if (session_type == Cdm::kTemporary) {
if (init_data_type == Cdm::kCenc) {
init_data = kCencInitData;
} else if (init_data_type == Cdm::kHls) {
init_data = kHlsInitData;
}
} else if (session_type == Cdm::kPersistentLicense ||
session_type == Cdm::kPersistentUsageRecord) {
if (init_data_type == Cdm::kCenc) {
init_data = kCencPersistentInitData;
}
}
ASSERT_FALSE(init_data.empty());
EXPECT_CALL(*this, onMessage(*session_id, Cdm::kLicenseRequest, _)).
WillOnce(SaveArg<2>(message));
status = cdm_->generateRequest(*session_id, init_data_type, init_data);
ASSERT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
}
void CreateSessionAndFetchLicense(Cdm::SessionType session_type,
Cdm::InitDataType init_data_type,
std::string* session_id,
std::string* response) {
std::string message;
ASSERT_NO_FATAL_FAILURE(CreateSessionAndGenerateRequest(
session_type, init_data_type, session_id, &message));
std::string license_server;
if (init_data_type == Cdm::kCenc) {
license_server = g_license_server;
} else if (init_data_type == Cdm::kHls) {
license_server = g_license_server;
}
ASSERT_FALSE(license_server.empty());
FetchLicense(license_server, message, response);
}
void CreateSessionAndUpdate(Cdm::SessionType session_type,
Cdm::InitDataType init_data_type,
std::string* session_id) {
std::string response;
ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense(
session_type, init_data_type, session_id, &response));
EXPECT_CALL(*this, onKeyStatusesChange(*session_id));
Cdm::Status status = cdm_->update(*session_id, response);
ASSERT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
}
void FetchLicenseAndUpdate(const std::string& session_id,
const std::string& message) {
// Acquire a license.
std::string response;
ASSERT_NO_FATAL_FAILURE(FetchLicense(
g_license_server, message, &response));
// This license should be accepted, but the keys are not expected to change.
EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0);
Cdm::Status status = cdm_->update(session_id, response);
ASSERT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
}
scoped_ptr<Cdm> cdm_;
};
struct DecryptParam {
public:
DecryptParam(
const std::string& short_name_param,
Cdm::InitDataType init_data_type_param,
const std::vector<uint8_t>& key_id_param,
const std::vector<uint8_t>& iv_param,
Cdm::EncryptionScheme scheme_param,
const Cdm::Pattern& pattern_param,
const std::vector<uint8_t>& input_param,
const std::vector<uint8_t>& output_param)
: short_name(short_name_param),
init_data_type(init_data_type_param),
key_id(&key_id_param),
iv(&iv_param),
scheme(scheme_param),
pattern(&pattern_param),
input(&input_param),
output(&output_param) {}
const std::string short_name;
const Cdm::InitDataType init_data_type;
const std::vector<uint8_t>* const key_id;
const std::vector<uint8_t>* const iv;
const Cdm::EncryptionScheme scheme;
const Cdm::Pattern* const pattern;
const std::vector<uint8_t>* const input;
const std::vector<uint8_t>* const output;
};
void PrintTo(const DecryptParam& value, ::std::ostream* os) {
*os << value.short_name << " DecryptParam";
}
class CdmTestWithDecryptParam : public CdmTest,
public WithParamInterface<DecryptParam> {};
class MockTimerClient : public Cdm::ITimer::IClient {
public:
MockTimerClient() {}
virtual ~MockTimerClient() {}
MOCK_METHOD1(onTimerExpired, void(void*));
};
} // namespace
TEST_F(CdmTest, TestHostTimer) {
// Validate that the TestHost timers are processed in the correct order.
const int64_t kTimerDelayMs = 1000;
void* kCtx1 = reinterpret_cast<void*>(0x1);
void* kCtx2 = reinterpret_cast<void*>(0x2);
MockTimerClient client;
g_host->setTimeout(kTimerDelayMs * 1, &client, kCtx1);
g_host->setTimeout(kTimerDelayMs * 2, &client, kCtx2);
EXPECT_CALL(client, onTimerExpired(kCtx1));
g_host->ElapseTime(kTimerDelayMs);
Mock::VerifyAndClear(&client);
EXPECT_CALL(client, onTimerExpired(kCtx2));
g_host->ElapseTime(kTimerDelayMs);
Mock::VerifyAndClear(&client);
EXPECT_CALL(client, onTimerExpired(_)).Times(0);
g_host->ElapseTime(kTimerDelayMs);
Mock::VerifyAndClear(&client);
}
TEST_F(CdmTest, Initialize) {
Cdm::Status status;
// Try with an invalid output type.
status = Cdm::initialize(
static_cast<Cdm::SecureOutputType>(-1), PropertiesCE::GetClientInfo(),
g_host, g_host, g_host, static_cast<Cdm::LogLevel>(g_cutoff));
EXPECT_EQ(Cdm::kTypeError, status);
// Try with various client info properties missing.
Cdm::ClientInfo working_client_info = PropertiesCE::GetClientInfo();
Cdm::ClientInfo broken_client_info;
broken_client_info = working_client_info;
broken_client_info.product_name.clear();
status = Cdm::initialize(
Cdm::kNoSecureOutput, broken_client_info,
g_host, g_host, g_host, static_cast<Cdm::LogLevel>(g_cutoff));
EXPECT_EQ(Cdm::kTypeError, status);
broken_client_info = working_client_info;
broken_client_info.company_name.clear();
status = Cdm::initialize(
Cdm::kNoSecureOutput, broken_client_info,
g_host, g_host, g_host, static_cast<Cdm::LogLevel>(g_cutoff));
EXPECT_EQ(Cdm::kTypeError, status);
broken_client_info = working_client_info;
broken_client_info.device_name.clear(); // Not required
status = Cdm::initialize(
Cdm::kNoSecureOutput, broken_client_info,
g_host, g_host, g_host, static_cast<Cdm::LogLevel>(g_cutoff));
EXPECT_EQ(Cdm::kSuccess, status);
broken_client_info = working_client_info;
broken_client_info.model_name.clear();
status = Cdm::initialize(
Cdm::kNoSecureOutput, broken_client_info,
g_host, g_host, g_host, static_cast<Cdm::LogLevel>(g_cutoff));
EXPECT_EQ(Cdm::kTypeError, status);
broken_client_info = working_client_info;
broken_client_info.arch_name.clear(); // Not required
status = Cdm::initialize(
Cdm::kNoSecureOutput, broken_client_info,
g_host, g_host, g_host, static_cast<Cdm::LogLevel>(g_cutoff));
EXPECT_EQ(Cdm::kSuccess, status);
broken_client_info = working_client_info;
broken_client_info.build_info.clear(); // Not required
status = Cdm::initialize(
Cdm::kNoSecureOutput, broken_client_info,
g_host, g_host, g_host, static_cast<Cdm::LogLevel>(g_cutoff));
EXPECT_EQ(Cdm::kSuccess, status);
// Try with various host interfaces missing.
status = Cdm::initialize(
Cdm::kNoSecureOutput, working_client_info,
NULL, g_host, g_host, static_cast<Cdm::LogLevel>(g_cutoff));
EXPECT_EQ(Cdm::kTypeError, status);
status = Cdm::initialize(
Cdm::kNoSecureOutput, working_client_info,
g_host, NULL, g_host, static_cast<Cdm::LogLevel>(g_cutoff));
EXPECT_EQ(Cdm::kTypeError, status);
status = Cdm::initialize(
Cdm::kNoSecureOutput, working_client_info,
g_host, g_host, NULL, static_cast<Cdm::LogLevel>(g_cutoff));
EXPECT_EQ(Cdm::kTypeError, status);
// One last init with everything correct and working.
status = Cdm::initialize(
Cdm::kNoSecureOutput, working_client_info,
g_host, g_host, g_host, static_cast<Cdm::LogLevel>(g_cutoff));
EXPECT_EQ(Cdm::kSuccess, status);
}
TEST_F(CdmTest, GetServiceCertificateRequest) {
// Set a server certificate with privacy mode disabled - should work.
ASSERT_NO_FATAL_FAILURE(RecreateCdm(false /* privacy_mode */));
std::string message;
Cdm::Status status = cdm_->getServiceCertificateRequest(&message);
EXPECT_EQ(Cdm::kSuccess, status);
}
TEST_F(CdmTest, SetServiceCertificate) {
// Set a server certificate with privacy mode disabled - should work.
ASSERT_NO_FATAL_FAILURE(RecreateCdm(false /* privacy_mode */));
Cdm::Status status =
cdm_->setServiceCertificate(g_license_service_certificate);
EXPECT_EQ(Cdm::kSuccess, status);
// Can set a server certificate if privacy mode is enabled.
ASSERT_NO_FATAL_FAILURE(RecreateCdm(true /* privacy_mode */));
status = cdm_->setServiceCertificate(g_license_service_certificate);
EXPECT_EQ(Cdm::kSuccess, status);
// It is invalid to set a malformed cert.
status = cdm_->setServiceCertificate("asdf");
EXPECT_EQ(Cdm::kTypeError, status);
}
TEST_F(CdmTest, CreateSession) {
// Create a temporary session.
std::string session_id;
Cdm::Status status = cdm_->createSession(Cdm::kTemporary, &session_id);
EXPECT_EQ(Cdm::kSuccess, status);
EXPECT_FALSE(session_id.empty());
// Create another using the same pointer to an already-filled-out string,
// and expect the session ID to change.
std::string original_session_id = session_id;
status = cdm_->createSession(Cdm::kTemporary, &session_id);
EXPECT_EQ(Cdm::kSuccess, status);
EXPECT_NE(original_session_id, session_id);
// Create a persistent session.
status = cdm_->createSession(Cdm::kPersistentLicense, &session_id);
EXPECT_EQ(Cdm::kSuccess, status);
// Try a NULL pointer for session ID.
status = cdm_->createSession(Cdm::kTemporary, NULL);
EXPECT_EQ(Cdm::kTypeError, status);
// Try a bogus session type.
status = cdm_->createSession(kBogusSessionType, &session_id);
EXPECT_EQ(Cdm::kNotSupported, status);
}
TEST_F(CdmTest, GenerateRequest) {
std::string session_id;
Cdm::Status status;
status = cdm_->setServiceCertificate(g_license_service_certificate);
ASSERT_EQ(Cdm::kSuccess, status);
status = cdm_->createSession(Cdm::kTemporary, &session_id);
ASSERT_EQ(Cdm::kSuccess, status);
// Generate a license request for CENC.
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _));
status = cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData);
EXPECT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// Can't call generateRequest more than once on a session.
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(0);
status = cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData);
EXPECT_EQ(Cdm::kInvalidState, status);
Mock::VerifyAndClear(this);
// Create a new session and generate a license request for WebM.
status = cdm_->createSession(Cdm::kTemporary, &session_id);
ASSERT_EQ(Cdm::kSuccess, status);
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _));
status = cdm_->generateRequest(session_id, Cdm::kWebM, kWebMInitData);
EXPECT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// Create a new session and try the as-yet-unsupported key-ids format.
status = cdm_->createSession(Cdm::kTemporary, &session_id);
ASSERT_EQ(Cdm::kSuccess, status);
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(0);
status = cdm_->generateRequest(session_id, Cdm::kKeyIds, kKeyIdsInitData);
EXPECT_EQ(Cdm::kNotSupported, status);
Mock::VerifyAndClear(this);
// Create a new session and generate a license request for HLS.
status = cdm_->createSession(Cdm::kTemporary, &session_id);
ASSERT_EQ(Cdm::kSuccess, status);
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _));
status = cdm_->generateRequest(session_id, Cdm::kHls, kHlsInitData);
EXPECT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// Create a new session and try a bogus init data type.
status = cdm_->createSession(Cdm::kTemporary, &session_id);
ASSERT_EQ(Cdm::kSuccess, status);
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(0);
status = cdm_->generateRequest(session_id, kBogusInitDataType, "asdf");
EXPECT_EQ(Cdm::kTypeError, status);
Mock::VerifyAndClear(this);
// This same session should still be usable with a supported init data type
// after failing with an unsupported or bogus type.
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _));
status = cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData);
EXPECT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// Create a new session and try to pass empty init data.
status = cdm_->createSession(Cdm::kTemporary, &session_id);
ASSERT_EQ(Cdm::kSuccess, status);
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(0);
status = cdm_->generateRequest(session_id, Cdm::kCenc, "");
EXPECT_EQ(Cdm::kTypeError, status);
Mock::VerifyAndClear(this);
// Try to pass invalid CENC init data.
status = cdm_->createSession(Cdm::kTemporary, &session_id);
ASSERT_EQ(Cdm::kSuccess, status);
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(0);
status = cdm_->generateRequest(session_id, Cdm::kCenc, kInvalidCencInitData);
EXPECT_EQ(Cdm::kNotSupported, status);
Mock::VerifyAndClear(this);
// Try to pass non-Widevine CENC init data.
status = cdm_->createSession(Cdm::kTemporary, &session_id);
ASSERT_EQ(Cdm::kSuccess, status);
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(0);
status = cdm_->generateRequest(session_id, Cdm::kCenc,
kNonWidevineCencInitData);
EXPECT_EQ(Cdm::kNotSupported, status);
Mock::VerifyAndClear(this);
// Try a bogus session ID.
EXPECT_CALL(*this, onMessage(_, _, _)).Times(0);
status = cdm_->generateRequest(kBogusSessionId, Cdm::kCenc, kCencInitData);
EXPECT_EQ(Cdm::kSessionNotFound, status);
Mock::VerifyAndClear(this);
}
TEST_F(CdmTest, Update) {
std::string session_id;
std::string message;
Cdm::Status status;
status = cdm_->setServiceCertificate(g_license_service_certificate);
ASSERT_EQ(Cdm::kSuccess, status);
ASSERT_NO_FATAL_FAILURE(CreateSessionAndGenerateRequest(
Cdm::kTemporary, Cdm::kCenc, &session_id, &message));
// Acquire a license.
std::string response;
ASSERT_NO_FATAL_FAILURE(FetchLicense(
g_license_server, message, &response));
// Update the session.
EXPECT_CALL(*this, onKeyStatusesChange(session_id));
status = cdm_->update(session_id, response);
EXPECT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// Try updating a bogus session ID.
status = cdm_->update(kBogusSessionId, response);
EXPECT_EQ(Cdm::kSessionNotFound, status);
// Try updating with an empty response.
status = cdm_->update(session_id, "");
EXPECT_EQ(Cdm::kTypeError, status);
// Create a new session and try updating before generating a request.
status = cdm_->createSession(Cdm::kTemporary, &session_id);
ASSERT_EQ(Cdm::kSuccess, status);
status = cdm_->update(session_id, response);
ASSERT_EQ(Cdm::kInvalidState, status);
}
TEST_F(CdmTest, Close) {
// Create a temporary session.
std::string session_id;
Cdm::Status status = cdm_->createSession(Cdm::kTemporary, &session_id);
ASSERT_EQ(Cdm::kSuccess, status);
// Close it.
status = cdm_->close(session_id);
ASSERT_EQ(Cdm::kSuccess, status);
// Can't generate a license request after close.
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(0);
status = cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData);
EXPECT_EQ(Cdm::kSessionNotFound, status);
Mock::VerifyAndClear(this);
// Try to close the same session again.
status = cdm_->close(session_id);
EXPECT_EQ(Cdm::kSessionNotFound, status);
// Try to close a bogus session.
status = cdm_->close(kBogusSessionId);
EXPECT_EQ(Cdm::kSessionNotFound, status);
}
TEST_F(CdmTest, LoadTemporary) {
std::string session_id;
std::string response;
Cdm::Status status;
status = cdm_->setServiceCertificate(g_license_service_certificate);
ASSERT_EQ(Cdm::kSuccess, status);
ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense(
Cdm::kTemporary, Cdm::kCenc, &session_id, &response));
// Update the temporary session.
EXPECT_CALL(*this, onKeyStatusesChange(session_id));
status = cdm_->update(session_id, response);
ASSERT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// Close the session.
status = cdm_->close(session_id);
ASSERT_EQ(Cdm::kSuccess, status);
// Can't load a temporary session.
status = cdm_->load(session_id);
EXPECT_EQ(Cdm::kSessionNotFound, status);
}
TEST_F(CdmTest, LoadPersistent) {
std::string session_id;
std::string response;
Cdm::Status status;
ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense(
Cdm::kPersistentLicense, Cdm::kCenc, &session_id, &response));
// Update the persistent session.
EXPECT_CALL(*this, onKeyStatusesChange(session_id));
status = cdm_->update(session_id, response);
ASSERT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// Should be able to load the session again after closing it.
status = cdm_->close(session_id);
ASSERT_EQ(Cdm::kSuccess, status);
EXPECT_CALL(*this, onKeyStatusesChange(session_id));
status = cdm_->load(session_id);
EXPECT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// Should be able to load the session again after recreating the CDM.
ASSERT_NO_FATAL_FAILURE(RecreateCdm(true /* privacy_mode */));
EXPECT_CALL(*this, onKeyStatusesChange(session_id));
status = cdm_->load(session_id);
EXPECT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// Should not be able to load the session again clearing storage.
status = cdm_->close(session_id);
ASSERT_EQ(Cdm::kSuccess, status);
g_host->Reset();
EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0);
status = cdm_->load(session_id);
EXPECT_EQ(Cdm::kSessionNotFound, status);
Mock::VerifyAndClear(this);
}
TEST_F(CdmTest, LoadWillFireExpiration) {
// There was a bug where calling load() would not start the PolicyEngine timer
// because it was only started in update().
std::string session_id;
ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(
Cdm::kPersistentLicense, Cdm::kCenc, &session_id));
// Should be able to load the session again after recreating the CDM.
ASSERT_NO_FATAL_FAILURE(RecreateCdm(true /* privacy_mode */));
EXPECT_CALL(*this, onKeyStatusesChange(session_id));
ASSERT_EQ(Cdm::kSuccess, cdm_->load(session_id));
Mock::VerifyAndClear(this);
// Let the key expire, make sure we get a key status update.
EXPECT_CALL(*this, onKeyStatusesChange(session_id));
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRenewal, _))
.Times(AtLeast(1));
g_host->ElapseTime(kOfflineLicenseDurationMs);
Mock::VerifyAndClear(this);
}
TEST_F(CdmTest, PerOriginLoadPersistent) {
std::string session_id;
std::string response;
ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense(
Cdm::kPersistentLicense, Cdm::kCenc, &session_id, &response));
// Update and close the persistent session.
Cdm::Status status = cdm_->update(session_id, response);
ASSERT_EQ(Cdm::kSuccess, status);
status = cdm_->close(session_id);
ASSERT_EQ(Cdm::kSuccess, status);
// Should be able to load the session again after recreating the CDM.
ASSERT_NO_FATAL_FAILURE(RecreateCdm(true /* privacy_mode */));
status = cdm_->load(session_id);
EXPECT_EQ(Cdm::kSuccess, status);
// Create another host to use its storage. This will simulate another origin.
TestHost other_host;
scoped_ptr<Cdm> other_cdm(
Cdm::create(this, &other_host, /* privacy_mode */ true));
ASSERT_TRUE(other_cdm.get());
status = other_cdm->setServiceCertificate(g_license_service_certificate);
ASSERT_EQ(Cdm::kSuccess, status);
// Should not be able to load from another origin.
EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0);
status = other_cdm->load(session_id);
EXPECT_EQ(Cdm::kSessionNotFound, status);
Mock::VerifyAndClear(this);
}
TEST_F(CdmTest, LoadUsageRecord) {
std::string session_id;
std::string response;
ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense(
Cdm::kPersistentUsageRecord, Cdm::kCenc, &session_id, &response));
// Update the session.
EXPECT_CALL(*this, onKeyStatusesChange(session_id));
Cdm::Status status = cdm_->update(session_id, response);
ASSERT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// Should be able to load the session again after closing it.
status = cdm_->close(session_id);
ASSERT_EQ(Cdm::kSuccess, status);
// There should be no usable keys after loading this session.
EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0);
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).Times(0);
status = cdm_->load(session_id);
EXPECT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// Should be able to load the session again after recreating the CDM.
ASSERT_NO_FATAL_FAILURE(RecreateCdm(true /* privacy_mode */));
EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0);
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).Times(0);
status = cdm_->load(session_id);
EXPECT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// Should not be able to load the session again clearing storage.
status = cdm_->close(session_id);
ASSERT_EQ(Cdm::kSuccess, status);
g_host->Reset();
EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0);
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).Times(0);
status = cdm_->load(session_id);
EXPECT_EQ(Cdm::kSessionNotFound, status);
Mock::VerifyAndClear(this);
}
TEST_F(CdmTest, DestroyUsageRecord) {
std::string session_id;
std::string response;
ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense(
Cdm::kPersistentUsageRecord, Cdm::kCenc, &session_id, &response));
// Update the session.
EXPECT_CALL(*this, onKeyStatusesChange(session_id));
Cdm::Status status = cdm_->update(session_id, response);
ASSERT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// Should be able to load the session again after closing it.
status = cdm_->close(session_id);
ASSERT_EQ(Cdm::kSuccess, status);
// There should be no usable keys after loading this session.
EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0);
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).Times(0);
status = cdm_->load(session_id);
EXPECT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// Should be able to load the session again after recreating the CDM.
ASSERT_NO_FATAL_FAILURE(RecreateCdm(true /* privacy_mode */));
EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0);
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).Times(0);
status = cdm_->deleteUsageRecord(session_id);
EXPECT_EQ(Cdm::kSuccess, status);
status = cdm_->load(session_id);
EXPECT_EQ(Cdm::kSessionNotFound, status);
Mock::VerifyAndClear(this);
}
TEST_F(CdmTest, DestroyAllUsageRecords) {
std::string session_id;
std::string response;
ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense(
Cdm::kPersistentUsageRecord, Cdm::kCenc, &session_id, &response));
// Update the session.
EXPECT_CALL(*this, onKeyStatusesChange(session_id));
Cdm::Status status = cdm_->update(session_id, response);
ASSERT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// Should be able to load the session again after closing it.
status = cdm_->close(session_id);
ASSERT_EQ(Cdm::kSuccess, status);
// There should be no usable keys after loading this session.
EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0);
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).Times(0);
status = cdm_->load(session_id);
EXPECT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// Should be able to load the session again after recreating the CDM.
ASSERT_NO_FATAL_FAILURE(RecreateCdm(true /* privacy_mode */));
EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0);
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).Times(0);
status = cdm_->deleteAllUsageRecords();
EXPECT_EQ(Cdm::kSuccess, status);
status = cdm_->load(session_id);
EXPECT_EQ(Cdm::kSessionNotFound, status);
Mock::VerifyAndClear(this);
}
TEST_F(CdmTest, ListUsageRecords) {
std::string session_id;
std::string response;
ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense(
Cdm::kPersistentUsageRecord, Cdm::kCenc, &session_id, &response));
// Update the session.
EXPECT_CALL(*this, onKeyStatusesChange(session_id));
Cdm::Status status = cdm_->update(session_id, response);
ASSERT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// Should be able to load the session again after closing it.
status = cdm_->close(session_id);
ASSERT_EQ(Cdm::kSuccess, status);
// There should be no usable keys after loading this session.
EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0);
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).Times(0);
status = cdm_->load(session_id);
EXPECT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// Should be able to load the session again after recreating the CDM.
ASSERT_NO_FATAL_FAILURE(RecreateCdm(true /* privacy_mode */));
EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0);
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).Times(0);
std::vector<std::string> ksids;
status = cdm_->listUsageRecords(&ksids);
EXPECT_EQ(Cdm::kSuccess, status);
EXPECT_EQ(ksids.size(), 1UL);
if (ksids.size() > 0UL) {
EXPECT_TRUE(ksids[0] == session_id);
}
status = cdm_->load(session_id);
EXPECT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
}
TEST_F(CdmTest, LoadBogus) {
EXPECT_CALL(*this, onKeyStatusesChange(_)).Times(0);
Cdm::Status status = cdm_->load(kBogusSessionId);
EXPECT_EQ(Cdm::kSessionNotFound, status);
}
TEST_F(CdmTest, GetKeyStatuses) {
std::string session_id;
ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(
Cdm::kTemporary, Cdm::kCenc, &session_id));
// We should be able to query status and see a usable key.
Cdm::KeyStatusMap map;
Cdm::Status status = cdm_->getKeyStatuses(session_id, &map);
ASSERT_EQ(Cdm::kSuccess, status);
EXPECT_EQ(Cdm::kUsable, map.begin()->second);
// The key ID should be the one we are expecting.
const std::string expected_key_id(
reinterpret_cast<const char*>(kKeyIdCtr.data()), kKeyIdCtr.size());
EXPECT_EQ(expected_key_id, map.begin()->first);
// Let the key expire.
EXPECT_CALL(*this, onKeyStatusesChange(session_id));
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRenewal, _)).Times(
AtLeast(1));
g_host->ElapseTime(kExpirationTestDelayMs);
Mock::VerifyAndClear(this);
// We should see expiration reflected in the map.
status = cdm_->getKeyStatuses(session_id, &map);
ASSERT_EQ(Cdm::kSuccess, status);
EXPECT_EQ(Cdm::kExpired, map.begin()->second);
// We can't get status after closing a session.
status = cdm_->close(session_id);
ASSERT_EQ(Cdm::kSuccess, status);
status = cdm_->getKeyStatuses(session_id, &map);
ASSERT_EQ(Cdm::kSessionNotFound, status);
}
TEST_F(CdmTest, GetExpiration) {
std::string session_id;
ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(
Cdm::kTemporary, Cdm::kCenc, &session_id));
// We should be able to query expiration and get a value in the future.
int64_t expiration;
Cdm::Status status = cdm_->getExpiration(session_id, &expiration);
ASSERT_EQ(Cdm::kSuccess, status);
EXPECT_GT(expiration, g_host->now());
int64_t original_expiration = expiration;
// Let the key expire.
EXPECT_CALL(*this, onKeyStatusesChange(session_id));
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRenewal, _)).Times(
AtLeast(1));
g_host->ElapseTime(kExpirationTestDelayMs);
Mock::VerifyAndClear(this);
// We should see expiration in the past now.
status = cdm_->getExpiration(session_id, &expiration);
ASSERT_EQ(Cdm::kSuccess, status);
EXPECT_LE(expiration, g_host->now());
// Expiration should not have changed.
EXPECT_EQ(original_expiration, expiration);
// We can't get expiration after closing a session.
status = cdm_->close(session_id);
ASSERT_EQ(Cdm::kSuccess, status);
status = cdm_->getExpiration(session_id, &expiration);
ASSERT_EQ(Cdm::kSessionNotFound, status);
}
TEST_F(CdmTest, Remove) {
std::string session_id;
ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(
Cdm::kPersistentLicense, Cdm::kCenc, &session_id));
// Remove the session. This causes a release message to be generated.
std::string message;
EXPECT_CALL(*this, onKeyStatusesChange(session_id));
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).WillOnce(
SaveArg<2>(&message));
Cdm::Status status = cdm_->remove(session_id);
ASSERT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// The keys should already be unusable.
Cdm::KeyStatusMap map;
status = cdm_->getKeyStatuses(session_id, &map);
ASSERT_EQ(Cdm::kSuccess, status);
EXPECT_EQ(Cdm::kReleased, map.begin()->second);
// Post the release message to the license server.
std::string response;
ASSERT_NO_FATAL_FAILURE(FetchLicense(
g_license_server, message, &response));
// Update the session.
EXPECT_CALL(*this, onRemoveComplete(session_id));
status = cdm_->update(session_id, response);
ASSERT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// The session is now completely gone.
status = cdm_->close(session_id);
ASSERT_EQ(Cdm::kSessionNotFound, status);
status = cdm_->load(session_id);
ASSERT_EQ(Cdm::kSessionNotFound, status);
// Try a bogus session ID.
status = cdm_->remove(kBogusSessionId);
EXPECT_EQ(Cdm::kSessionNotFound, status);
// Try a new session.
status = cdm_->createSession(Cdm::kPersistentLicense, &session_id);
ASSERT_EQ(Cdm::kSuccess, status);
status = cdm_->remove(session_id);
EXPECT_EQ(Cdm::kInvalidState, status);
// Try a temporary session.
ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(
Cdm::kTemporary, Cdm::kCenc, &session_id));
status = cdm_->remove(session_id);
EXPECT_EQ(Cdm::kRangeError, status);
}
TEST_F(CdmTest, RemoveUsageRecord) {
std::string session_id;
ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(
Cdm::kPersistentUsageRecord, Cdm::kCenc, &session_id));
// Remove the session. This causes a release message to be generated.
std::string message;
EXPECT_CALL(*this, onKeyStatusesChange(session_id));
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).WillOnce(
SaveArg<2>(&message));
Cdm::Status status = cdm_->remove(session_id);
ASSERT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// The keys should already be unusable.
Cdm::KeyStatusMap map;
status = cdm_->getKeyStatuses(session_id, &map);
ASSERT_EQ(Cdm::kSuccess, status);
EXPECT_EQ(Cdm::kReleased, map.begin()->second);
// Post the release message to the license server.
std::string response;
ASSERT_NO_FATAL_FAILURE(FetchLicense(
g_license_server, message, &response));
// Update the session.
EXPECT_CALL(*this, onRemoveComplete(session_id));
status = cdm_->update(session_id, response);
ASSERT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// The session is now completely gone.
status = cdm_->close(session_id);
ASSERT_EQ(Cdm::kSessionNotFound, status);
}
TEST_F(CdmTest, RemoveIncomplete) {
std::string session_id;
ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(
Cdm::kPersistentLicense, Cdm::kCenc, &session_id));
// Remove the session. This causes a release message to be generated.
std::string message;
EXPECT_CALL(*this, onKeyStatusesChange(session_id));
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).WillOnce(
SaveArg<2>(&message));
Cdm::Status status = cdm_->remove(session_id);
ASSERT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// The keys should already be unusable, but they should still exist.
Cdm::KeyStatusMap map;
status = cdm_->getKeyStatuses(session_id, &map);
ASSERT_EQ(Cdm::kSuccess, status);
ASSERT_FALSE(map.empty());
EXPECT_EQ(Cdm::kReleased, map.begin()->second);
// Recreate the CDM.
ASSERT_NO_FATAL_FAILURE(RecreateCdm(true /* privacy_mode */));
// Load the partially removed session.
EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0);
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).Times(0);
status = cdm_->load(session_id);
ASSERT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// This session has no keys.
status = cdm_->getKeyStatuses(session_id, &map);
ASSERT_EQ(Cdm::kSuccess, status);
EXPECT_TRUE(map.empty());
// Remove the session again to fire the release message.
message.clear();
EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0);
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).WillOnce(
SaveArg<2>(&message));
status = cdm_->remove(session_id);
ASSERT_EQ(Cdm::kSuccess, status);
ASSERT_FALSE(message.empty());
Mock::VerifyAndClear(this);
// Post the release message to the license server.
std::string response;
ASSERT_NO_FATAL_FAILURE(FetchLicense(
g_license_server, message, &response));
// Update the session.
EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0);
EXPECT_CALL(*this, onRemoveComplete(session_id));
status = cdm_->update(session_id, response);
ASSERT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// The session is now completely gone.
status = cdm_->load(session_id);
ASSERT_EQ(Cdm::kSessionNotFound, status);
}
TEST_F(CdmTest, RemoveUsageTable) {
Cdm::Status status;
status = cdm_->removeUsageTable();
ASSERT_EQ(Cdm::kSuccess, status);
status = cdm_->removeUsageTable();
ASSERT_EQ(Cdm::kSuccess, status);
}
TEST_F(CdmTest, RemoveUsageRecordIncomplete) {
std::string session_id;
ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(
Cdm::kPersistentUsageRecord, Cdm::kCenc, &session_id));
// Remove the session. This causes a release message to be generated.
std::string message;
EXPECT_CALL(*this, onKeyStatusesChange(session_id));
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).WillOnce(
SaveArg<2>(&message));
Cdm::Status status = cdm_->remove(session_id);
ASSERT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// The keys should already be unusable, but they should still exist.
Cdm::KeyStatusMap map;
status = cdm_->getKeyStatuses(session_id, &map);
ASSERT_EQ(Cdm::kSuccess, status);
ASSERT_FALSE(map.empty());
EXPECT_EQ(Cdm::kReleased, map.begin()->second);
// Recreate the CDM.
ASSERT_NO_FATAL_FAILURE(RecreateCdm(true /* privacy_mode */));
// Load the partially removed session.
EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0);
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).Times(0);
status = cdm_->load(session_id);
ASSERT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// Remove the session again to fire a release message.
message.clear();
EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0);
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).WillOnce(
SaveArg<2>(&message));
status = cdm_->remove(session_id);
ASSERT_EQ(Cdm::kSuccess, status);
ASSERT_FALSE(message.empty());
Mock::VerifyAndClear(this);
// This session has no keys.
status = cdm_->getKeyStatuses(session_id, &map);
ASSERT_EQ(Cdm::kSuccess, status);
EXPECT_TRUE(map.empty());
// Post the release message to the license server.
std::string response;
ASSERT_NO_FATAL_FAILURE(FetchLicense(
g_license_server, message, &response));
// Update the session.
EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0);
EXPECT_CALL(*this, onRemoveComplete(session_id));
status = cdm_->update(session_id, response);
ASSERT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// The session is now completely gone.
status = cdm_->load(session_id);
ASSERT_EQ(Cdm::kSessionNotFound, status);
}
TEST_F(CdmTest, RemoveNotLoaded) {
// Create a persistent session and then close it.
std::string session_id;
ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(
Cdm::kPersistentLicense, Cdm::kCenc, &session_id));
Cdm::Status status = cdm_->close(session_id);
ASSERT_EQ(Cdm::kSuccess, status);
// A session must be loaded before removing it. Remove only works on active
// sessions.
status = cdm_->remove(session_id);
EXPECT_EQ(Cdm::kSessionNotFound, status);
}
TEST_F(CdmTest, RequestPersistentLicenseWithWrongInitData) {
// Generate a request for a persistent license without using the correct
// persistent content init data.
std::string session_id;
Cdm::Status status;
status = cdm_->setServiceCertificate(g_license_service_certificate);
ASSERT_EQ(Cdm::kSuccess, status);
status = cdm_->createSession(Cdm::kPersistentLicense, &session_id);
ASSERT_EQ(Cdm::kSuccess, status);
std::string message;
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).WillOnce(
SaveArg<2>(&message));
status = cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData);
ASSERT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// The license server will reject this.
FetchLicenseFailure(message, 500);
}
TEST_F(CdmTest, RequestTemporaryLicenseWithWrongInitData) {
// Generate a request for a temporary license using persistent init data.
std::string session_id;
Cdm::Status status;
status = cdm_->setServiceCertificate(g_license_service_certificate);
ASSERT_EQ(Cdm::kSuccess, status);
status = cdm_->createSession(Cdm::kTemporary, &session_id);
ASSERT_EQ(Cdm::kSuccess, status);
std::string message;
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).WillOnce(
SaveArg<2>(&message));
status = cdm_->generateRequest(session_id, Cdm::kCenc,
kCencPersistentInitData);
ASSERT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// Acquire a license.
std::string response;
ASSERT_NO_FATAL_FAILURE(FetchLicense(
g_license_server, message, &response));
// This license should not be accepted.
EXPECT_CALL(*this, onKeyStatusesChange(session_id));
status = cdm_->update(session_id, response);
EXPECT_EQ(Cdm::kRangeError, status);
Mock::VerifyAndClear(this);
}
TEST_F(CdmTest, Renewal) {
std::string session_id;
ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(
Cdm::kTemporary, Cdm::kCenc, &session_id));
// We should have a timer.
EXPECT_NE(0, g_host->NumTimers());
// When we elapse time, we should get a renewal message.
std::string message;
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRenewal, _)).WillOnce(
SaveArg<2>(&message));
g_host->ElapseTime(kRenewalTestDelayMs);
Mock::VerifyAndClear(this);
ASSERT_FALSE(message.empty()); // Stop the test if no message came through.
// When should still have a timer.
EXPECT_NE(0, g_host->NumTimers());
// We should be able to update the session.
ASSERT_NO_FATAL_FAILURE(FetchLicenseAndUpdate(session_id, message));
// After closing the session, there should be no more renewals.
Cdm::Status status = cdm_->close(session_id);
ASSERT_EQ(Cdm::kSuccess, status);
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRenewal, _)).Times(0);
g_host->ElapseTime(kRenewalTestDelayMs * 10);
Mock::VerifyAndClear(this);
}
TEST_F(CdmTest, SetAppParameters) {
// Must use privacy_mode = false to ensure that the message is in plain-text.
std::string session_id;
ASSERT_NO_FATAL_FAILURE(RecreateCdm(false /* privacy_mode */));
Cdm::Status status = cdm_->createSession(Cdm::kTemporary, &session_id);
ASSERT_EQ(Cdm::kSuccess, status);
// Set a new app parameter, and check by getting.
std::string result;
status = cdm_->setAppParameter(kParamName, kValue);
ASSERT_EQ(Cdm::kSuccess, status);
status = cdm_->getAppParameter(kParamName, &result);
ASSERT_EQ(Cdm::kSuccess, status);
ASSERT_EQ(kValue, result);
// Try to get using a null result.
status = cdm_->getAppParameter(kParamName, NULL);
ASSERT_EQ(Cdm::kTypeError, status);
// Try to get using an empty key.
status = cdm_->getAppParameter("", &result);
ASSERT_EQ(Cdm::kTypeError, status);
// Try to set using an empty key.
status = cdm_->setAppParameter("", kValue);
ASSERT_EQ(Cdm::kTypeError, status);
// Try to remove using an empty key.
status = cdm_->removeAppParameter("");
ASSERT_EQ(Cdm::kTypeError, status);
// Change an existing app parameter.
status = cdm_->setAppParameter(kParamName, kNewValue);
ASSERT_EQ(Cdm::kSuccess, status);
status = cdm_->getAppParameter(kParamName, &result);
ASSERT_EQ(Cdm::kSuccess, status);
ASSERT_EQ(kNewValue, result);
// Remove an existing app parameter, check for invalid access when it's gone.
status = cdm_->removeAppParameter(kParamName);
ASSERT_EQ(Cdm::kSuccess, status);
status = cdm_->getAppParameter(kParamName, &result);
ASSERT_EQ(Cdm::kTypeError, status);
// Try to remove an absent value.
status = cdm_->removeAppParameter(kParamName2);
ASSERT_EQ(Cdm::kTypeError, status);
// Set some values to check for.
status = cdm_->setAppParameter(kParamName, kValue);
ASSERT_EQ(Cdm::kSuccess, status);
status = cdm_->setAppParameter(kParamName2, kNewValue);
ASSERT_EQ(Cdm::kSuccess, status);
// Send a generate request to ensure the parameter is in the message.
std::string message;
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).WillOnce(
SaveArg<2>(&message));
status = cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData);
EXPECT_EQ(Cdm::kSuccess, status);
EXPECT_TRUE(!message.empty() && message.find(kValue) != std::string::npos);
Mock::VerifyAndClear(this);
// Ensure that the value is still present and correct.
status = cdm_->getAppParameter(kParamName, &result);
ASSERT_EQ(Cdm::kSuccess, status);
ASSERT_EQ(kValue, result);
status = cdm_->getAppParameter(kParamName2, &result);
ASSERT_EQ(Cdm::kSuccess, status);
ASSERT_EQ(kNewValue, result);
// Clear all the parameters.
status = cdm_->clearAppParameters();
ASSERT_EQ(Cdm::kSuccess, status);
status = cdm_->getAppParameter(kParamName, &result);
ASSERT_EQ(Cdm::kTypeError, status);
status = cdm_->getAppParameter(kParamName2, &result);
ASSERT_EQ(Cdm::kTypeError, status);
}
TEST_F(CdmTest, SetVideoResolutionBadSession) {
std::string session_id;
ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(
Cdm::kTemporary, Cdm::kCenc, &session_id));
Cdm::Status status = cdm_->setVideoResolution(kBogusSessionId, 100, 100);
ASSERT_EQ(Cdm::kSessionNotFound, status);
cdm_->close(session_id);
}
TEST_F(CdmTest, SetVideoResolutionOK) {
std::string session_id;
ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(
Cdm::kTemporary, Cdm::kCenc, &session_id));
Cdm::Status status = cdm_->setVideoResolution(session_id, 100, 100);
ASSERT_EQ(Cdm::kSuccess, status);
cdm_->close(session_id);
}
TEST_F(CdmTest, SetVideoResolutionOverflow) {
std::string session_id;
ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(
Cdm::kTemporary, Cdm::kCenc, &session_id));
const uint32_t kUintTooLarge = (1 << 16);
Cdm::Status status = cdm_->setVideoResolution(session_id, kUintTooLarge,
kUintTooLarge);
ASSERT_EQ(Cdm::kRangeError, status);
cdm_->close(session_id);
}
TEST_P(CdmTestWithDecryptParam, DecryptToClearBuffer) {
DecryptParam param = GetParam();
Cdm::InputBuffer input;
Cdm::OutputBuffer output;
input.key_id = param.key_id->data();
input.key_id_length = param.key_id->size();
input.iv = param.iv->data();
input.iv_length = param.iv->size();
input.data = param.input->data();
input.data_length = param.input->size();
input.encryption_scheme = param.scheme;
input.pattern = *param.pattern;
std::vector<uint8_t> output_buffer(input.data_length);
output.data = &(output_buffer[0]);
output.data_length = output_buffer.size();
// Decrypt without keys loaded should fail.
Cdm::Status status = cdm_->decrypt(input, output);
ASSERT_EQ(Cdm::kNoKey, status);
// Create a session with the right keys.
std::string session_id;
ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(
Cdm::kTemporary, param.init_data_type, &session_id));
// Decrypt should now succeed.
status = cdm_->decrypt(input, output);
ASSERT_EQ(Cdm::kSuccess, status);
EXPECT_EQ(*param.output, output_buffer);
}
INSTANTIATE_TEST_CASE_P(CdmDecryptTest, CdmTestWithDecryptParam, Values(
DecryptParam("CENC 3.0 cenc Mode",
Cdm::kCenc, kKeyIdCtr, kIvCenc, Cdm::kAesCtr,
kPatternNone, kInput, kOutputCenc),
DecryptParam("CENC 3.0 cens Mode",
Cdm::kCenc, kKeyIdCtr, kIvCens, Cdm::kAesCtr,
kPatternRecommended, kInput, kOutputCens),
DecryptParam("CENC 3.0 cbc1 Mode",
Cdm::kHls, kKeyIdCbc, kIvCbc1, Cdm::kAesCbc,
kPatternNone, kInput, kOutputCbc1),
DecryptParam("CENC 3.0 cbcs Mode",
Cdm::kHls, kKeyIdCbc, kIvCbcs, Cdm::kAesCbc,
kPatternRecommended, kInput, kOutputCbcs),
DecryptParam("HLS Audio (CENC 3.0 cbcs Mode Without a Pattern)",
Cdm::kHls, kKeyIdCbc, kIvCbc1, Cdm::kAesCbc,
kPatternHlsAudio, kInput, kOutputCbc1)
));
// TODO: add infrastructure to test secure buffer decrypt for some platforms
class CdmIndividualizationTest : public CdmTest {
protected:
bool CheckProvisioningSupport() {
uint32_t nonce = 0;
uint8_t buffer[1];
size_t size = 0;
EXPECT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Initialize());
int result = OEMCrypto_RewrapDeviceRSAKey(0, buffer, 0, buffer, 0, &nonce,
buffer, 0, buffer, buffer, &size);
EXPECT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Terminate());
if (result == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
LOGW(
"WARNING: Skipping PerOriginDeviceProvisioning because the device "
"does not support provisioning. If you are using a baked-in "
"certificate, this is expected. Otherwise, something is wrong.");
return false;
}
return true;
}
std::string GetProvisioningResponse(const std::string& message) {
std::string reply;
std::string uri = g_provisioning_server;
LOGV("GetProvisioningResponse: URI: %s", uri.c_str());
LOGV("GetProvisioningResponse: message:\n%s\n", message.c_str());
uri += "&signedRequest=" + message;
FetchCertificate(uri, &reply);
if (HasFatalFailure()) {
LOGE("GetProvisioningResponse: Failed.");
return "";
}
LOGV("GetProvisioningResponse: response:\n%s\n", reply.c_str());
return reply;
}
};
TEST_F(CdmIndividualizationTest, BasicFlow) {
if (!CheckProvisioningSupport()) return;
// Clear any existing certificates.
g_host->remove("cert.bin");
// Creating a session should succeed.
std::string session_id;
Cdm::Status status;
status = cdm_->setServiceCertificate(g_license_service_certificate);
EXPECT_EQ(Cdm::kSuccess, status);
status = cdm_->createSession(Cdm::kTemporary, &session_id);
EXPECT_EQ(Cdm::kSuccess, status);
// Should get an individualization request when we generate request.
std::string message;
EXPECT_CALL(*this, onDirectIndividualizationRequest(session_id, _))
.WillOnce(SaveArg<1>(&message));
status = cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData);
EXPECT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// Complete the provisioning request.
std::string reply = GetProvisioningResponse(message);
ASSERT_FALSE(reply.empty());
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(1);
EXPECT_CALL(*this, onDeferredComplete(_, _)).Times(0);
status = cdm_->update(session_id, reply);
ASSERT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// We should now be able to create a session and generate a request.
ASSERT_NO_FATAL_FAILURE(CreateSessionAndGenerateRequest(
Cdm::kTemporary, Cdm::kCenc, &session_id, &message));
// Acquire a license and update the session.
ASSERT_NO_FATAL_FAILURE(FetchLicense(
g_license_server, message, &reply));
EXPECT_CALL(*this, onKeyStatusesChange(session_id));
status = cdm_->update(session_id, reply);
EXPECT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
}
TEST_F(CdmIndividualizationTest, IsProvisioned) {
if (!CheckProvisioningSupport()) return;
// Clear any existing certificates.
g_host->remove("cert.bin");
EXPECT_FALSE(cdm_->isProvisioned());
// Creating a session should succeed.
std::string session_id;
Cdm::Status status = cdm_->createSession(Cdm::kTemporary, &session_id);
EXPECT_EQ(Cdm::kSuccess, status);
// Should get an individualization request when we generate request.
std::string message;
EXPECT_CALL(*this, onDirectIndividualizationRequest(session_id, _))
.WillOnce(SaveArg<1>(&message));
status = cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData);
EXPECT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// Complete the provisioning request.
std::string reply = GetProvisioningResponse(message);
ASSERT_FALSE(reply.empty());
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(1);
EXPECT_CALL(*this, onDeferredComplete(_, _)).Times(0);
status = cdm_->update(session_id, reply);
ASSERT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
EXPECT_TRUE(cdm_->isProvisioned());
}
TEST_F(CdmIndividualizationTest, RemoveProvisioning) {
if (!CheckProvisioningSupport()) return;
// Clear any existing certificates.
EXPECT_EQ(Cdm::kSuccess, cdm_->removeProvisioning());
EXPECT_FALSE(cdm_->isProvisioned());
// Creating a session should succeed.
std::string session_id;
Cdm::Status status = cdm_->createSession(Cdm::kTemporary, &session_id);
EXPECT_EQ(Cdm::kSuccess, status);
// Should get an individualization request when we generate request.
std::string message;
EXPECT_CALL(*this, onDirectIndividualizationRequest(session_id, _))
.WillOnce(SaveArg<1>(&message));
status = cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData);
EXPECT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
// Complete the provisioning request.
std::string reply = GetProvisioningResponse(message);
ASSERT_FALSE(reply.empty());
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(1);
EXPECT_CALL(*this, onDeferredComplete(_, _)).Times(0);
status = cdm_->update(session_id, reply);
ASSERT_EQ(Cdm::kSuccess, status);
Mock::VerifyAndClear(this);
EXPECT_TRUE(cdm_->isProvisioned());
EXPECT_EQ(Cdm::kSuccess, cdm_->removeProvisioning());
EXPECT_FALSE(cdm_->isProvisioned());
}
TEST_F(CdmIndividualizationTest, WillNotSendRequestTwice) {
if (!CheckProvisioningSupport()) return;
// Clear any existing certificates.
g_host->remove("cert.bin");
// Creating a session should succeed.
std::string session_id;
ASSERT_EQ(Cdm::kSuccess, cdm_->createSession(Cdm::kTemporary, &session_id));
// Should get an individualization request when we generate request.
std::string message;
EXPECT_CALL(*this, onDirectIndividualizationRequest(session_id, _))
.WillOnce(SaveArg<1>(&message));
ASSERT_EQ(Cdm::kSuccess,
cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData));
Mock::VerifyAndClear(this);
// Create a second session.
std::string session_id2;
ASSERT_EQ(Cdm::kSuccess, cdm_->createSession(Cdm::kTemporary, &session_id2));
// Should not get another individualization request.
EXPECT_CALL(*this, onMessage(_, _, _)).Times(0);
ASSERT_EQ(Cdm::kDeferred,
cdm_->generateRequest(session_id2, Cdm::kCenc, kCencInitData));
Mock::VerifyAndClear(this);
// Complete the provisioning request, should generate requests for both
// sessions.
std::string reply = GetProvisioningResponse(message);
ASSERT_FALSE(reply.empty());
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(1);
EXPECT_CALL(*this, onMessage(session_id2, Cdm::kLicenseRequest, _)).Times(1);
EXPECT_CALL(*this, onDeferredComplete(session_id2, Cdm::kSuccess)).Times(1);
EXPECT_EQ(Cdm::kSuccess, cdm_->update(session_id, reply));
Mock::VerifyAndClear(this);
}
TEST_F(CdmIndividualizationTest,
WillNotSendMessageWhenGenerateRequestNotCalled) {
if (!CheckProvisioningSupport()) return;
// Clear any existing certificates.
g_host->remove("cert.bin");
// Creating a session should succeed.
std::string session_id;
ASSERT_EQ(Cdm::kSuccess, cdm_->createSession(Cdm::kTemporary, &session_id));
// Should get an individualization request when we generate request.
std::string message;
EXPECT_CALL(*this, onDirectIndividualizationRequest(session_id, _))
.WillOnce(SaveArg<1>(&message));
ASSERT_EQ(Cdm::kSuccess,
cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData));
Mock::VerifyAndClear(this);
// Create a second session, don't call generateRequest for it.
std::string session_id2;
ASSERT_EQ(Cdm::kSuccess, cdm_->createSession(Cdm::kTemporary, &session_id2));
// Complete the provisioning request, should not get calls for the second
// session.
std::string reply = GetProvisioningResponse(message);
ASSERT_FALSE(reply.empty());
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(1);
EXPECT_CALL(*this, onMessage(session_id2, Cdm::kLicenseRequest, _)).Times(0);
EXPECT_CALL(*this, onDeferredComplete(_, _)).Times(0);
EXPECT_EQ(Cdm::kSuccess, cdm_->update(session_id, reply));
Mock::VerifyAndClear(this);
// We should get a license message for the second session.
EXPECT_CALL(*this, onMessage(session_id2, Cdm::kLicenseRequest, _)).Times(1);
ASSERT_EQ(Cdm::kSuccess,
cdm_->generateRequest(session_id2, Cdm::kCenc, kCencInitData));
Mock::VerifyAndClear(this);
}
TEST_F(CdmIndividualizationTest, PropagatesErrorsInUpdate) {
if (!CheckProvisioningSupport()) return;
// Clear any existing certificates.
g_host->remove("cert.bin");
// Creating a session should succeed.
std::string session_id;
ASSERT_EQ(Cdm::kSuccess, cdm_->createSession(Cdm::kTemporary, &session_id));
// Should get an individualization request when we generate request.
std::string message;
EXPECT_CALL(*this, onDirectIndividualizationRequest(session_id, _))
.WillOnce(SaveArg<1>(&message));
ASSERT_EQ(Cdm::kSuccess, cdm_->generateRequest(session_id, Cdm::kCenc,
kInvalidCencInitData));
Mock::VerifyAndClear(this);
// Complete the provisioning request, should get an error call.
std::string reply = GetProvisioningResponse(message);
ASSERT_FALSE(reply.empty());
EXPECT_CALL(*this, onMessage(_, _, _)).Times(0);
EXPECT_CALL(*this, onDeferredComplete(_, _)).Times(0);
EXPECT_EQ(Cdm::kNotSupported, cdm_->update(session_id, reply));
Mock::VerifyAndClear(this);
}
TEST_F(CdmIndividualizationTest, OnlyPropagatesErrorsForThisSession) {
if (!CheckProvisioningSupport()) return;
// Clear any existing certificates.
g_host->remove("cert.bin");
// Creating a session should succeed.
std::string session_id;
ASSERT_EQ(Cdm::kSuccess, cdm_->createSession(Cdm::kTemporary, &session_id));
// Should get an individualization request when we generate request.
std::string message;
EXPECT_CALL(*this, onDirectIndividualizationRequest(session_id, _))
.WillOnce(SaveArg<1>(&message));
ASSERT_EQ(Cdm::kSuccess,
cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData));
Mock::VerifyAndClear(this);
// Create another session that will cause an error.
std::string session_id_2;
ASSERT_EQ(Cdm::kSuccess,
cdm_->createSession(Cdm::kTemporary, &session_id_2));
ASSERT_EQ(Cdm::kDeferred, cdm_->generateRequest(session_id_2, Cdm::kCenc,
kInvalidCencInitData));
// Complete the provisioning request, should succeed, but get an error
// callback.
std::string reply = GetProvisioningResponse(message);
ASSERT_FALSE(reply.empty());
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(1);
EXPECT_CALL(*this, onMessage(session_id_2, _, _)).Times(0);
EXPECT_CALL(*this, onDeferredComplete(session_id_2, Cdm::kNotSupported))
.Times(1);
EXPECT_EQ(Cdm::kSuccess, cdm_->update(session_id, reply));
Mock::VerifyAndClear(this);
}
TEST_F(CdmIndividualizationTest, WorksWithLoad) {
if (!CheckProvisioningSupport()) return;
// Create an offline session to load.
std::string session_id;
ASSERT_NO_FATAL_FAILURE(
CreateSessionAndUpdate(Cdm::kPersistentLicense, Cdm::kCenc, &session_id));
EXPECT_EQ(Cdm::kSuccess, cdm_->close(session_id));
// Clear any existing certificates.
g_host->remove("cert.bin");
// Loading a session should succeed, we should get an individualization
// request right away.
std::string message;
EXPECT_CALL(*this, onDirectIndividualizationRequest(session_id, _))
.WillOnce(SaveArg<1>(&message));
ASSERT_EQ(Cdm::kSuccess, cdm_->load(session_id));
Mock::VerifyAndClear(this);
// Complete the provisioning request.
std::string reply = GetProvisioningResponse(message);
ASSERT_FALSE(reply.empty());
// Because we are now provisioned with a new key, we can't load the session,
// but we will still be provisioned.
EXPECT_CALL(*this, onDeferredComplete(_, _)).Times(0);
EXPECT_EQ(Cdm::kUnexpectedError, cdm_->update(session_id, reply));
Mock::VerifyAndClear(this);
// Create a second session, we should be previsioned at this point.
std::string session_id2;
ASSERT_EQ(Cdm::kSuccess, cdm_->createSession(Cdm::kTemporary, &session_id2));
EXPECT_CALL(*this, onMessage(session_id2, Cdm::kLicenseRequest, _)).Times(1);
ASSERT_EQ(Cdm::kSuccess,
cdm_->generateRequest(session_id2, Cdm::kCenc, kCencInitData));
Mock::VerifyAndClear(this);
}
TEST_F(CdmIndividualizationTest, WillResendOnProvisioningError) {
if (!CheckProvisioningSupport()) return;
// Clear any existing certificates.
g_host->remove("cert.bin");
// Creating a session should succeed.
std::string session_id;
ASSERT_EQ(Cdm::kSuccess, cdm_->createSession(Cdm::kTemporary, &session_id));
// Should get an individualization request when we generate request.
std::string message;
EXPECT_CALL(*this, onDirectIndividualizationRequest(session_id, _))
.WillOnce(SaveArg<1>(&message));
ASSERT_EQ(Cdm::kSuccess,
cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData));
Mock::VerifyAndClear(this);
// Fail to provision the device.
EXPECT_CALL(*this, onMessage(_, _, _)).Times(0);
EXPECT_CALL(*this, onDeferredComplete(_, _)).Times(0);
EXPECT_EQ(Cdm::kUnexpectedError, cdm_->update(session_id, ""));
Mock::VerifyAndClear(this);
// Should get another individualization request.
std::string session_id_2;
ASSERT_EQ(Cdm::kSuccess,
cdm_->createSession(Cdm::kTemporary, &session_id_2));
EXPECT_CALL(*this, onDirectIndividualizationRequest(session_id_2, _))
.WillOnce(SaveArg<1>(&message));
ASSERT_EQ(Cdm::kSuccess,
cdm_->generateRequest(session_id_2, Cdm::kCenc, kCencInitData));
Mock::VerifyAndClear(this);
}
} // namespace widevine