Cas Client repo update-2.
-Parse EMM in Cas plugin -Entitlement key rotation support -Multi_content_license support
This commit is contained in:
@@ -17,6 +17,9 @@
|
||||
#include "mock_crypto_session.h"
|
||||
#include "string_conversions.h"
|
||||
|
||||
namespace wvcas {
|
||||
namespace {
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::AllOf;
|
||||
using ::testing::DoAll;
|
||||
@@ -367,7 +370,6 @@ TEST_F(CasLicenseTest, HandleEntitlementResponse) {
|
||||
EXPECT_CALL(*cas_license.policy_engine_, CanPersist())
|
||||
.WillOnce(Return(false));
|
||||
status = cas_license.HandleEntitlementResponse(entitlement_response,
|
||||
/*content_id_filter=*/nullptr,
|
||||
/*device_file=*/nullptr);
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
|
||||
// Not a group license.
|
||||
@@ -381,16 +383,16 @@ TEST_F(CasLicenseTest, HandleEntitlementResponse) {
|
||||
EXPECT_CALL(*cas_license.policy_engine_, CanPersist())
|
||||
.WillOnce(Return(false));
|
||||
std::string device_file;
|
||||
status = cas_license.HandleEntitlementResponse(
|
||||
entitlement_response, /*content_id_filter=*/nullptr, &device_file);
|
||||
status =
|
||||
cas_license.HandleEntitlementResponse(entitlement_response, &device_file);
|
||||
EXPECT_TRUE(device_file.empty());
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
|
||||
|
||||
// Valid with device file and can_persist = true.
|
||||
EXPECT_CALL(*cas_license.policy_engine_, SetLicense(_));
|
||||
EXPECT_CALL(*cas_license.policy_engine_, CanPersist()).WillOnce(Return(true));
|
||||
status = cas_license.HandleEntitlementResponse(
|
||||
entitlement_response, /*content_id_filter=*/nullptr, &device_file);
|
||||
status =
|
||||
cas_license.HandleEntitlementResponse(entitlement_response, &device_file);
|
||||
EXPECT_FALSE(device_file.empty());
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
|
||||
|
||||
@@ -596,9 +598,7 @@ TEST_F(CasLicenseTest, RestoreLicense) {
|
||||
EXPECT_CALL(*cas_license.policy_engine_, SetLicense(_));
|
||||
EXPECT_CALL(*cas_license.policy_engine_, UpdateLicense(_));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_license
|
||||
.HandleStoredLicense(wrapped_rsa_key_, license_file_data,
|
||||
/*content_id_filter=*/nullptr)
|
||||
cas_license.HandleStoredLicense(wrapped_rsa_key_, license_file_data)
|
||||
.status_code());
|
||||
}
|
||||
|
||||
@@ -662,7 +662,6 @@ TEST_F(CasLicenseTest, HandleMultiContentEntitlementResponse) {
|
||||
EXPECT_CALL(*cas_license.policy_engine_, CanPersist()).WillOnce(Return(true));
|
||||
|
||||
status = cas_license.HandleEntitlementResponse(entitlement_response,
|
||||
/*content_id_filter=*/nullptr,
|
||||
/*device_file=*/nullptr);
|
||||
EXPECT_EQ(status.status_code(), wvcas::CasStatusCode::kNoError);
|
||||
// Not a group license.
|
||||
@@ -671,4 +670,103 @@ TEST_F(CasLicenseTest, HandleMultiContentEntitlementResponse) {
|
||||
testing::ElementsAre("content_id_1", "content_id_2"));
|
||||
EXPECT_FALSE(cas_license.IsGroupLicense());
|
||||
EXPECT_TRUE(cas_license.IsMultiContentLicense());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(GetEntitlementPeriodIndexTest, ValidIndexSuccess) {
|
||||
uint32_t expected_index = 123;
|
||||
// Create a valid pssh with entitlement key period index.
|
||||
video_widevine::WidevinePsshData pssh;
|
||||
pssh.set_entitlement_period_index(expected_index);
|
||||
// Create a license request containing the pssh.
|
||||
video_widevine::LicenseRequest license_request;
|
||||
license_request.mutable_content_id()->mutable_cenc_id_deprecated()->add_pssh(
|
||||
pssh.SerializeAsString());
|
||||
// Create a license file.
|
||||
File file;
|
||||
file.set_type(File::LICENSE);
|
||||
license_request.SerializeToString(
|
||||
file.mutable_license()->mutable_license_request());
|
||||
// Hash the created file
|
||||
HashedFile hashed_file;
|
||||
file.SerializeToString(hashed_file.mutable_file());
|
||||
Hash(hashed_file.file(), hashed_file.mutable_hash());
|
||||
uint32_t actual_index;
|
||||
|
||||
EXPECT_TRUE(CasLicense::GetEntitlementPeriodIndexFromStoredLicense(
|
||||
hashed_file.SerializeAsString(), actual_index)
|
||||
.ok());
|
||||
|
||||
EXPECT_EQ(actual_index, expected_index);
|
||||
}
|
||||
|
||||
TEST(GetEntitlementPeriodIndexTest, PsshHasNoIndexFail) {
|
||||
// Create a valid pssh without entitlement key period index.
|
||||
video_widevine::WidevinePsshData pssh;
|
||||
pssh.set_content_id("content_id");
|
||||
// Create a license request containing the pssh.
|
||||
video_widevine::LicenseRequest license_request;
|
||||
license_request.mutable_content_id()->mutable_cenc_id_deprecated()->add_pssh(
|
||||
pssh.SerializeAsString());
|
||||
// Create a license file.
|
||||
File file;
|
||||
file.set_type(File::LICENSE);
|
||||
license_request.SerializeToString(
|
||||
file.mutable_license()->mutable_license_request());
|
||||
// Hash the created file
|
||||
HashedFile hashed_file;
|
||||
file.SerializeToString(hashed_file.mutable_file());
|
||||
Hash(hashed_file.file(), hashed_file.mutable_hash());
|
||||
uint32_t actual_index;
|
||||
|
||||
EXPECT_FALSE(CasLicense::GetEntitlementPeriodIndexFromStoredLicense(
|
||||
hashed_file.SerializeAsString(), actual_index)
|
||||
.ok());
|
||||
}
|
||||
|
||||
TEST(GetEntitlementPeriodIndexTest, NoLicenseDataFail) {
|
||||
File file;
|
||||
file.set_type(File::LICENSE);
|
||||
// Hash the created file
|
||||
HashedFile hashed_file;
|
||||
file.SerializeToString(hashed_file.mutable_file());
|
||||
Hash(hashed_file.file(), hashed_file.mutable_hash());
|
||||
uint32_t actual_index;
|
||||
|
||||
EXPECT_FALSE(CasLicense::GetEntitlementPeriodIndexFromStoredLicense(
|
||||
hashed_file.SerializeAsString(), actual_index)
|
||||
.ok());
|
||||
}
|
||||
|
||||
TEST(GetEntitlementPeriodIndexTest, InvalidHashFail) {
|
||||
// Create a valid pssh with entitlement key period index.
|
||||
video_widevine::WidevinePsshData pssh;
|
||||
pssh.set_entitlement_period_index(123);
|
||||
// Create a license request containing the pssh.
|
||||
video_widevine::LicenseRequest license_request;
|
||||
license_request.mutable_content_id()->mutable_cenc_id_deprecated()->add_pssh(
|
||||
pssh.SerializeAsString());
|
||||
// Create a license file.
|
||||
File file;
|
||||
file.set_type(File::LICENSE);
|
||||
license_request.SerializeToString(
|
||||
file.mutable_license()->mutable_license_request());
|
||||
// Hash the created file
|
||||
HashedFile hashed_file;
|
||||
file.SerializeToString(hashed_file.mutable_file());
|
||||
hashed_file.set_hash("invalid_hash");
|
||||
uint32_t actual_index;
|
||||
|
||||
EXPECT_FALSE(CasLicense::GetEntitlementPeriodIndexFromStoredLicense(
|
||||
hashed_file.SerializeAsString(), actual_index)
|
||||
.ok());
|
||||
}
|
||||
|
||||
TEST(GetEntitlementPeriodIndexTest, InvalidFileFail) {
|
||||
uint32_t actual_index;
|
||||
EXPECT_FALSE(CasLicense::GetEntitlementPeriodIndexFromStoredLicense(
|
||||
"invalid file", actual_index)
|
||||
.ok());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace wvcas
|
||||
@@ -403,5 +403,40 @@ TEST(EcmParserV3Test, ParserGroupKeysWithOmittedFieldsSuccess) {
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv2);
|
||||
}
|
||||
|
||||
TEST(EcmParserV3Test, EntitlementRotationEnabledSuccess) {
|
||||
const uint32_t entitlement_period_index = 10;
|
||||
const uint32_t entitlement_rotation_window_left = 100;
|
||||
EcmPayload ecm_payload;
|
||||
ecm_payload.mutable_meta_data()->set_entitlement_period_index(
|
||||
entitlement_period_index);
|
||||
ecm_payload.mutable_meta_data()->set_entitlement_rotation_window_left(
|
||||
entitlement_rotation_window_left);
|
||||
SignedEcmPayload signed_ecm_payload;
|
||||
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
||||
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
||||
|
||||
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
|
||||
|
||||
ASSERT_TRUE(parser != nullptr);
|
||||
EXPECT_TRUE(parser->is_entitlement_rotation_enabled());
|
||||
EXPECT_EQ(parser->entitlement_period_index(), entitlement_period_index);
|
||||
EXPECT_EQ(parser->entitlement_rotation_window_left(),
|
||||
entitlement_rotation_window_left);
|
||||
}
|
||||
|
||||
TEST(EcmParserV3Test, EntitlementRotationDefaultDisabledSuccess) {
|
||||
EcmPayload ecm_payload;
|
||||
// Put something in the payload just to make the ECM valid.
|
||||
ecm_payload.mutable_even_key_data()->set_entitlement_key_id(kEntitlementId);
|
||||
SignedEcmPayload signed_ecm_payload;
|
||||
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
||||
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
||||
|
||||
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
|
||||
|
||||
ASSERT_TRUE(parser != nullptr);
|
||||
EXPECT_FALSE(parser->is_entitlement_rotation_enabled());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace wvcas
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
// Dynamically generated header created during build. The Runtest entry point is
|
||||
// defined in gopkg_carchive.go
|
||||
|
||||
#include "gowvcas_carchive.h"
|
||||
#include "golang/src/gowvcas_carchive.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
extern "C" int RunTest(GoString);
|
||||
|
||||
constexpr int kIntegrationTestPassed = 0;
|
||||
|
||||
// Invokes a test. Tests are named to allow them to be run individually. This
|
||||
@@ -123,3 +125,30 @@ TEST(IntegrationTests, TestSessionEventPassing) {
|
||||
TEST(IntegrationTests, TestProcessEcmV3) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestProcessEcmV3"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestGroupLicense) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestGroupLicense"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestMultiContentLicense) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestMultiContentLicense"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestAssignGroupLicense) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestAssignGroupLicense"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestLicenseRequestWithEntitlementPeriodIndex) {
|
||||
EXPECT_EQ(kIntegrationTestPassed,
|
||||
RunNamedTest("TestLicenseRequestWithEntitlementPeriodIndex"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestOfflineLicenseWithEntitlementPeriodIndex) {
|
||||
EXPECT_EQ(kIntegrationTestPassed,
|
||||
RunNamedTest("TestOfflineLicenseWithEntitlementPeriodIndex"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestNewLicenseRequestWithOutdatedOfflineLicense) {
|
||||
EXPECT_EQ(kIntegrationTestPassed,
|
||||
RunNamedTest("TestNewLicenseRequestWithOutdatedOfflineLicense"));
|
||||
}
|
||||
|
||||
@@ -82,6 +82,8 @@ class MockCryptoSession : public wvcas::CryptoSession {
|
||||
wvcas::CasStatus(uint32_t* entitled_key_session_id));
|
||||
MOCK_METHOD1(RemoveEntitledKeySession,
|
||||
wvcas::CasStatus(uint32_t entitled_key_session_id));
|
||||
MOCK_METHOD(wvcas::CasStatus, ReassociateEntitledKeySession,
|
||||
(uint32_t entitled_key_session_id));
|
||||
};
|
||||
|
||||
#endif // MOCK_CRYPTO_SESSION_H
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "license_key_status.h"
|
||||
#include "license_protocol.pb.h"
|
||||
#include "mock_crypto_session.h"
|
||||
#include "mock_event_listener.h"
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::DoAll;
|
||||
@@ -76,31 +77,6 @@ class MockLicenseKeys : public wvcas::LicenseKeys {
|
||||
void(const std::vector<WidevinePsshData_EntitledKey>& keys));
|
||||
};
|
||||
|
||||
class MockEventListener : public wvcas::CasEventListener {
|
||||
public:
|
||||
MockEventListener() {}
|
||||
virtual ~MockEventListener() {}
|
||||
|
||||
MOCK_METHOD0(OnSessionRenewalNeeded, void());
|
||||
MOCK_METHOD2(OnSessionKeysChange,
|
||||
void(const KeyStatusMap& keys_status, bool has_new_usable_key));
|
||||
MOCK_METHOD1(OnExpirationUpdate, void(int64_t new_expiry_time_seconds));
|
||||
MOCK_METHOD1(OnNewRenewalServerUrl,
|
||||
void(const std::string& renewal_server_url));
|
||||
MOCK_METHOD0(OnLicenseExpiration, void());
|
||||
MOCK_METHOD2(OnAgeRestrictionUpdated,
|
||||
void(const wvcas::WvCasSessionId& sessionId,
|
||||
uint8_t ecm_age_restriction));
|
||||
MOCK_METHOD(void, OnSessionFingerprintingUpdated,
|
||||
(const int32_t& sessionId,
|
||||
const std::vector<uint8_t>& fingerprinting),
|
||||
(override));
|
||||
MOCK_METHOD(void, OnSessionServiceBlockingUpdated,
|
||||
(const int32_t& sessionId,
|
||||
const std::vector<uint8_t>& service_blocking),
|
||||
(override));
|
||||
};
|
||||
|
||||
class TestablePolicyEngine : public wvcas::PolicyEngine {
|
||||
std::unique_ptr<wvcas::LicenseKeys> CreateLicenseKeys() override {
|
||||
std::unique_ptr<StrictMock<MockLicenseKeys> > license_keys =
|
||||
|
||||
@@ -63,4 +63,9 @@ bool Properties::GetOEMCryptoPath(std::string* path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Properties::GetWvCasPluginVersion(std::string& version) {
|
||||
version = "unit-test";
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace wvcas
|
||||
|
||||
@@ -12,11 +12,18 @@
|
||||
|
||||
#include "cas_license.h"
|
||||
#include "cas_util.h"
|
||||
#include "device_files.pb.h"
|
||||
#include "ecm_parser.h"
|
||||
#include "media_cas.pb.h"
|
||||
#include "mock_crypto_session.h"
|
||||
#include "mock_ecm_parser.h"
|
||||
#include "mock_event_listener.h"
|
||||
#include "string_conversions.h"
|
||||
#include "widevine_cas_session_map.h"
|
||||
|
||||
namespace wvcas {
|
||||
namespace {
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::DoAll;
|
||||
using ::testing::NiceMock;
|
||||
@@ -24,8 +31,9 @@ using ::testing::NotNull;
|
||||
using ::testing::Return;
|
||||
using ::testing::SetArgPointee;
|
||||
using ::testing::StrictMock;
|
||||
using ::video_widevine_client::sdk::File;
|
||||
using ::video_widevine_client::sdk::HashedFile;
|
||||
|
||||
namespace {
|
||||
constexpr char kBasePathPrefix[] = "/data/vendor/mediacas/IDM/widevine/";
|
||||
constexpr char kLicenseFileNameSuffix[] = ".lic";
|
||||
|
||||
@@ -39,7 +47,6 @@ std::string GenerateTestLicenseFileName(const std::string& mocked_file_name) {
|
||||
SHA256(input, mocked_file_name.size(), output);
|
||||
return kBasePathPrefix + wvutil::b2a_hex(hash) + kLicenseFileNameSuffix;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
typedef StrictMock<MockCryptoSession> StrictMockCryptoSession;
|
||||
|
||||
@@ -55,20 +62,16 @@ class MockLicense : public wvcas::CasLicense {
|
||||
const std::string& wrapped_rsa_key,
|
||||
wvcas::LicenseType license_type,
|
||||
std::string* signed_license_request));
|
||||
MOCK_METHOD3(HandleStoredLicense,
|
||||
wvcas::CasStatus(const std::string& wrapped_rsa_key,
|
||||
const std::string& license_file,
|
||||
const std::string* content_id_filter));
|
||||
MOCK_METHOD(wvcas::CasStatus, HandleStoredLicense,
|
||||
(const std::string&, const std::string&), (override));
|
||||
MOCK_METHOD2(GenerateEntitlementRenewalRequest,
|
||||
wvcas::CasStatus(const std::string& device_certificate,
|
||||
std::string* signed_renewal_request));
|
||||
MOCK_METHOD2(HandleEntitlementRenewalResponse,
|
||||
wvcas::CasStatus(const std::string& renewal_response,
|
||||
std::string* device_file));
|
||||
MOCK_METHOD3(HandleEntitlementResponse,
|
||||
wvcas::CasStatus(const std::string& entitlement_response,
|
||||
const std::string* content_id_filter,
|
||||
std::string* device_file));
|
||||
MOCK_METHOD(wvcas::CasStatus, HandleEntitlementResponse,
|
||||
(const std::string&, std::string*), (override));
|
||||
MOCK_METHOD0(BeginDecryption, void());
|
||||
MOCK_METHOD0(UpdateLicenseForLicenseRemove, void());
|
||||
MOCK_METHOD(std::string, GetGroupId, (), (const, override));
|
||||
@@ -78,30 +81,6 @@ class MockLicense : public wvcas::CasLicense {
|
||||
MOCK_METHOD(bool, IsGroupLicense, (), (const, override));
|
||||
};
|
||||
typedef StrictMock<MockLicense> StrictMockLicense;
|
||||
|
||||
class MockEventListener : public wvcas::CasEventListener {
|
||||
public:
|
||||
MockEventListener() {}
|
||||
~MockEventListener() override {}
|
||||
MOCK_METHOD0(OnSessionRenewalNeeded, void());
|
||||
MOCK_METHOD2(OnSessionKeysChange, void(const wvcas::KeyStatusMap& keys_status,
|
||||
bool has_new_usable_key));
|
||||
MOCK_METHOD1(OnExpirationUpdate, void(int64_t new_expiry_time_seconds));
|
||||
MOCK_METHOD1(OnNewRenewalServerUrl,
|
||||
void(const std::string& renewal_server_url));
|
||||
MOCK_METHOD0(OnLicenseExpiration, void());
|
||||
MOCK_METHOD2(OnAgeRestrictionUpdated,
|
||||
void(const wvcas::WvCasSessionId& sessionId,
|
||||
uint8_t ecm_age_restriction));
|
||||
MOCK_METHOD(void, OnSessionFingerprintingUpdated,
|
||||
(const int32_t& sessionId,
|
||||
const std::vector<uint8_t>& fingerprinting),
|
||||
(override));
|
||||
MOCK_METHOD(void, OnSessionServiceBlockingUpdated,
|
||||
(const int32_t& sessionId,
|
||||
const std::vector<uint8_t>& service_blocking),
|
||||
(override));
|
||||
};
|
||||
typedef StrictMock<MockEventListener> StrictMockEventListener;
|
||||
|
||||
class MockFile : public wvutil::File {
|
||||
@@ -153,6 +132,13 @@ class MockWidevineSession : public wvcas::WidevineCasSession {
|
||||
const wvcas::CasEcm& ecm));
|
||||
};
|
||||
|
||||
class MockEmmParser : public EmmParser {
|
||||
public:
|
||||
MOCK_METHOD(uint64_t, timestamp, (), (const, override));
|
||||
MOCK_METHOD(std::string, signature, (), (const, override));
|
||||
MOCK_METHOD(EmmPayload, emm_payload, (), (const, override));
|
||||
};
|
||||
|
||||
class TestWidevineCas : public wvcas::WidevineCas {
|
||||
public:
|
||||
TestWidevineCas() {
|
||||
@@ -169,10 +155,21 @@ class TestWidevineCas : public wvcas::WidevineCas {
|
||||
return std::move(pass_thru_crypto_session_);
|
||||
}
|
||||
|
||||
void setCryptoSession(
|
||||
std::unique_ptr<StrictMockCryptoSession> crypto_session) {
|
||||
pass_thru_crypto_session_ = std::move(crypto_session);
|
||||
crypto_session_ = pass_thru_crypto_session_.get();
|
||||
}
|
||||
|
||||
std::unique_ptr<wvcas::CasLicense> getCasLicense() override {
|
||||
return std::move(pass_thru_license_);
|
||||
}
|
||||
|
||||
void setCasLicense(std::unique_ptr<StrictMockLicense> license) {
|
||||
pass_thru_license_ = std::move(license);
|
||||
license_ = pass_thru_license_.get();
|
||||
}
|
||||
|
||||
std::unique_ptr<wvutil::FileSystem> getFileSystem() override {
|
||||
return std::move(pass_thru_file_system_);
|
||||
}
|
||||
@@ -181,9 +178,22 @@ class TestWidevineCas : public wvcas::WidevineCas {
|
||||
return std::make_shared<StrictMock<MockWidevineSession> >();
|
||||
}
|
||||
|
||||
std::unique_ptr<const EmmParser> getEmmParser(
|
||||
const wvcas::CasEmm& emm) const override {
|
||||
return std::move(pass_thru_emm_parser_);
|
||||
}
|
||||
|
||||
void setEmmParser(std::unique_ptr<MockEmmParser> parser) {
|
||||
pass_thru_emm_parser_ = std::move(parser);
|
||||
}
|
||||
|
||||
MOCK_METHOD(std::unique_ptr<wvcas::EcmParser>, getEcmParser,
|
||||
(const wvcas::CasEcm& ecm), (const override));
|
||||
|
||||
std::unique_ptr<StrictMockLicense> pass_thru_license_;
|
||||
std::unique_ptr<StrictMockCryptoSession> pass_thru_crypto_session_;
|
||||
std::unique_ptr<NiceMockFileSystem> pass_thru_file_system_;
|
||||
mutable std::unique_ptr<MockEmmParser> pass_thru_emm_parser_;
|
||||
|
||||
StrictMockLicense* license_ = nullptr;
|
||||
StrictMockCryptoSession* crypto_session_ = nullptr;
|
||||
@@ -633,8 +643,8 @@ TEST_F(WidevineCasTest, RemoveLicenseInUse) {
|
||||
.status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
// Install the license
|
||||
EXPECT_CALL(*cas_api.license_, HandleEntitlementResponse(_, _, NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<2>("device_file"),
|
||||
EXPECT_CALL(*cas_api.license_, HandleEntitlementResponse(_, NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<1>("device_file"),
|
||||
Return(wvcas::CasStatus::OkStatus())));
|
||||
EXPECT_CALL(*cas_api.license_, IsMultiContentLicense)
|
||||
.WillRepeatedly(Return(false));
|
||||
@@ -676,8 +686,8 @@ TEST_F(WidevineCasTest, handleMultiContentEntitlementResponse) {
|
||||
|
||||
std::string license_group_id = "license_group_id";
|
||||
std::vector<std::string> content_list = {"content1", "content2"};
|
||||
EXPECT_CALL(*cas_api.license_, HandleEntitlementResponse(_, _, NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<2>("device_file"),
|
||||
EXPECT_CALL(*cas_api.license_, HandleEntitlementResponse(_, NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<1>("device_file"),
|
||||
Return(wvcas::CasStatus::OkStatus())));
|
||||
EXPECT_CALL(*cas_api.license_, IsMultiContentLicense)
|
||||
.WillRepeatedly(Return(true));
|
||||
@@ -737,8 +747,8 @@ TEST_F(WidevineCasTest, handleGroupEntitlementResponse) {
|
||||
|
||||
std::string license_group_id = "license_group_id";
|
||||
std::vector<std::string> content_list = {};
|
||||
EXPECT_CALL(*cas_api.license_, HandleEntitlementResponse(_, _, NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<2>("device_file"),
|
||||
EXPECT_CALL(*cas_api.license_, HandleEntitlementResponse(_, NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<1>("device_file"),
|
||||
Return(wvcas::CasStatus::OkStatus())));
|
||||
EXPECT_CALL(*cas_api.license_, IsMultiContentLicense)
|
||||
.WillRepeatedly(Return(false));
|
||||
@@ -828,4 +838,529 @@ TEST_F(WidevineCasTest, ECMProcessingWithGroupId) {
|
||||
EXPECT_EQ(cas_api.processEcm(sid, ecm).status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
EXPECT_CALL(*(cas_api.crypto_session_), RemoveEntitledKeySession(sid));
|
||||
}
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_api.closeSession(sid).status_code());
|
||||
}
|
||||
|
||||
TEST_F(WidevineCasTest, ProcessCAPrivateData) {
|
||||
TestWidevineCas cas_api;
|
||||
video_widevine::CaDescriptorPrivateData private_data;
|
||||
private_data.set_provider("provider");
|
||||
private_data.set_content_id("content_id");
|
||||
private_data.add_group_ids("group1");
|
||||
private_data.add_group_ids("group2");
|
||||
std::string serialized_private_data;
|
||||
private_data.SerializeToString(&serialized_private_data);
|
||||
video_widevine::WidevinePsshData expected_pssh;
|
||||
expected_pssh.set_provider("provider");
|
||||
expected_pssh.set_content_id("content_id");
|
||||
expected_pssh.add_group_ids("group1");
|
||||
expected_pssh.add_group_ids("group2");
|
||||
expected_pssh.set_type(video_widevine::WidevinePsshData::ENTITLEMENT);
|
||||
std::string expected_serialized_pssh;
|
||||
expected_pssh.SerializeToString(&expected_serialized_pssh);
|
||||
std::string init_data;
|
||||
|
||||
ASSERT_EQ(cas_api
|
||||
.ProcessCAPrivateData({serialized_private_data.begin(),
|
||||
serialized_private_data.end()},
|
||||
&init_data)
|
||||
.status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
|
||||
EXPECT_EQ(init_data, expected_serialized_pssh);
|
||||
}
|
||||
|
||||
TEST_F(WidevineCasTest, ProcessEmmEmptyEmmPayload) {
|
||||
TestWidevineCas cas_api;
|
||||
MockEventListener event_listener;
|
||||
EXPECT_CALL(*cas_api.crypto_session_, initialize())
|
||||
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||
EXPECT_EQ(cas_api.initialize(&event_listener).status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
|
||||
auto emm_parser = make_unique<MockEmmParser>();
|
||||
MockEmmParser* parser = emm_parser.get();
|
||||
cas_api.setEmmParser(std::move(emm_parser));
|
||||
|
||||
video_widevine::EmmPayload emm_payload;
|
||||
EXPECT_CALL(*parser, emm_payload).WillOnce(Return(emm_payload));
|
||||
EXPECT_CALL(event_listener, OnFingerprintingUpdated).Times(0);
|
||||
EXPECT_CALL(event_listener, OnServiceBlockingUpdated).Times(0);
|
||||
cas_api.processEmm({});
|
||||
}
|
||||
|
||||
class WidevineCasProcessEmmTest : public WidevineCasTest {
|
||||
protected:
|
||||
void LoadFingerprinting() {
|
||||
auto fingerprinting_1 = emm_payload_.add_fingerprinting();
|
||||
fingerprinting_1->add_channels("CH1");
|
||||
fingerprinting_1->add_channels("CH2");
|
||||
fingerprinting_1->set_control("control");
|
||||
auto fingerprinting_2 = emm_payload_.add_fingerprinting();
|
||||
fingerprinting_2->add_channels("1003");
|
||||
fingerprinting_2->set_control("off");
|
||||
}
|
||||
|
||||
void LoadServiceBlocking() {
|
||||
auto service_blocking_1 = emm_payload_.add_service_blocking();
|
||||
service_blocking_1->add_channels("CH1");
|
||||
service_blocking_1->add_channels("CH2");
|
||||
service_blocking_1->add_device_groups("g1");
|
||||
service_blocking_1->add_device_groups("g2");
|
||||
service_blocking_1->set_start_time_sec(0x12345678);
|
||||
service_blocking_1->set_end_time_sec(0x87654321);
|
||||
auto service_blocking_2 = emm_payload_.add_service_blocking();
|
||||
service_blocking_2->add_channels("CH3");
|
||||
service_blocking_2->add_device_groups("100");
|
||||
service_blocking_2->set_end_time_sec(0x987654321);
|
||||
}
|
||||
|
||||
video_widevine::EmmPayload emm_payload_;
|
||||
};
|
||||
|
||||
TEST_F(WidevineCasProcessEmmTest, FingerprintingEmm) {
|
||||
TestWidevineCas cas_api;
|
||||
MockEventListener event_listener;
|
||||
EXPECT_CALL(*cas_api.crypto_session_, initialize())
|
||||
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||
EXPECT_EQ(cas_api.initialize(&event_listener).status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
|
||||
LoadFingerprinting();
|
||||
const std::vector<uint8_t> expected_message_1 = {
|
||||
0x00, // Type FINGERPRINTING_CHANNEL
|
||||
0x00, 0x03, // Length (bytes)
|
||||
'C', 'H', '1', // Value (channel)
|
||||
0x00, // Type FINGERPRINTING_CHANNEL
|
||||
0x00, 0x03, // Length (bytes)
|
||||
'C', 'H', '2', // Value (channel)
|
||||
0x01, // Type FINGERPRINTING_CONTROL
|
||||
0x00, 0x07, // Length (bytes)
|
||||
'c', 'o', 'n', 't', 'r', 'o', 'l' // Value (channel)
|
||||
};
|
||||
const std::vector<uint8_t> expected_message_2 = {
|
||||
0x00, // Type FINGERPRINTING_CHANNEL
|
||||
0x00, 0x04, // Length (bytes)
|
||||
'1', '0', '0', '3', // Value (channel)
|
||||
0x01, // Type FINGERPRINTING_CONTROL
|
||||
0x00, 0x03, // Length (bytes)
|
||||
'o', 'f', 'f' // Value (channel)
|
||||
};
|
||||
|
||||
auto emm_parser = make_unique<MockEmmParser>();
|
||||
MockEmmParser* parser = emm_parser.get();
|
||||
cas_api.setEmmParser(std::move(emm_parser));
|
||||
|
||||
EXPECT_CALL(*parser, emm_payload).WillOnce(Return(emm_payload_));
|
||||
EXPECT_CALL(event_listener, OnServiceBlockingUpdated).Times(0);
|
||||
EXPECT_CALL(event_listener, OnFingerprintingUpdated(expected_message_1))
|
||||
.Times(1);
|
||||
EXPECT_CALL(event_listener, OnFingerprintingUpdated(expected_message_2))
|
||||
.Times(1);
|
||||
|
||||
EXPECT_EQ(cas_api.processEmm({}).status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
}
|
||||
|
||||
TEST_F(WidevineCasProcessEmmTest, ServiceBlockingEmm) {
|
||||
TestWidevineCas cas_api;
|
||||
MockEventListener event_listener;
|
||||
EXPECT_CALL(*cas_api.crypto_session_, initialize())
|
||||
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||
EXPECT_EQ(cas_api.initialize(&event_listener).status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
|
||||
LoadServiceBlocking();
|
||||
const std::vector<uint8_t> expected_message_1 = {
|
||||
0x00, // Type SERVICE_BLOCKING_CHANNEL
|
||||
0x00, 0x03, // Length (bytes)
|
||||
'C', 'H', '1', // Value
|
||||
0x00, // Type SERVICE_BLOCKING_CHANNEL
|
||||
0x00, 0x03, // Length (bytes)
|
||||
'C', 'H', '2', // Value
|
||||
0x01, // Type SERVICE_BLOCKING_DEVICE_GROUP
|
||||
0x00, 0x02, // Length (bytes)
|
||||
'g', '1', // Value
|
||||
0x01, // Type SERVICE_BLOCKING_DEVICE_GROUP
|
||||
0x00, 0x02, // Length (bytes)
|
||||
'g', '2', // Value
|
||||
0x02, // Type SERVICE_BLOCKING_START_TIME_SECONDS
|
||||
0x00, 0x08, // Length (bytes)
|
||||
0x00, 0x00, 0x00, 0x00, 0x12, 0x34, 0x56, 0x78, // Value
|
||||
0x03, // Type SERVICE_BLOCKING_END_TIME_SECONDS
|
||||
0x00, 0x08, // Length (bytes)
|
||||
0x00, 0x00, 0x00, 0x00, 0x87, 0x65, 0x43, 0x21 // Value
|
||||
};
|
||||
const std::vector<uint8_t> expected_message_2 = {
|
||||
0x00, // Type SERVICE_BLOCKING_CHANNEL
|
||||
0x00, 0x03, // Length (bytes)
|
||||
'C', 'H', '3', // Value
|
||||
0x01, // Type SERVICE_BLOCKING_DEVICE_GROUP
|
||||
0x00, 0x03, // Length (bytes)
|
||||
'1', '0', '0', // Value
|
||||
0x03, // Type SERVICE_BLOCKING_END_TIME_SECONDS
|
||||
0x00, 0x08, // Length (bytes)
|
||||
0x00, 0x00, 0x00, 0x09, 0x87, 0x65, 0x43, 0x21 // Value
|
||||
};
|
||||
|
||||
auto emm_parser = make_unique<MockEmmParser>();
|
||||
MockEmmParser* parser = emm_parser.get();
|
||||
cas_api.setEmmParser(std::move(emm_parser));
|
||||
|
||||
EXPECT_CALL(*parser, emm_payload).WillOnce(Return(emm_payload_));
|
||||
EXPECT_CALL(event_listener, OnFingerprintingUpdated).Times(0);
|
||||
EXPECT_CALL(event_listener, OnServiceBlockingUpdated(expected_message_1))
|
||||
.Times(1);
|
||||
EXPECT_CALL(event_listener, OnServiceBlockingUpdated(expected_message_2))
|
||||
.Times(1);
|
||||
|
||||
EXPECT_EQ(cas_api.processEmm({}).status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
}
|
||||
|
||||
TEST_F(WidevineCasProcessEmmTest, MultipleSameEmm) {
|
||||
TestWidevineCas cas_api;
|
||||
MockEventListener event_listener;
|
||||
EXPECT_CALL(*cas_api.crypto_session_, initialize())
|
||||
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||
EXPECT_EQ(cas_api.initialize(&event_listener).status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
|
||||
LoadFingerprinting();
|
||||
LoadServiceBlocking();
|
||||
|
||||
auto emm_parser = make_unique<MockEmmParser>();
|
||||
MockEmmParser* parser = emm_parser.get();
|
||||
cas_api.setEmmParser(std::move(emm_parser));
|
||||
EXPECT_CALL(*parser, emm_payload).WillOnce(Return(emm_payload_));
|
||||
EXPECT_CALL(event_listener, OnServiceBlockingUpdated).Times(2);
|
||||
EXPECT_CALL(event_listener, OnFingerprintingUpdated).Times(2);
|
||||
EXPECT_EQ(cas_api.processEmm({}).status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
|
||||
// Calling again will not trigger the events again.
|
||||
auto emm_parser2 = make_unique<MockEmmParser>();
|
||||
MockEmmParser* parser2 = emm_parser2.get();
|
||||
cas_api.setEmmParser(std::move(emm_parser2));
|
||||
EXPECT_CALL(*parser2, emm_payload).WillOnce(Return(emm_payload_));
|
||||
EXPECT_CALL(event_listener, OnServiceBlockingUpdated).Times(0);
|
||||
EXPECT_CALL(event_listener, OnFingerprintingUpdated).Times(0);
|
||||
EXPECT_EQ(cas_api.processEmm({}).status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
}
|
||||
|
||||
TEST_F(WidevineCasProcessEmmTest, MultipleDifferentEmms) {
|
||||
TestWidevineCas cas_api;
|
||||
MockEventListener event_listener;
|
||||
EXPECT_CALL(*cas_api.crypto_session_, initialize())
|
||||
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||
EXPECT_EQ(cas_api.initialize(&event_listener).status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
|
||||
LoadFingerprinting();
|
||||
LoadServiceBlocking();
|
||||
|
||||
auto emm_parser = make_unique<MockEmmParser>();
|
||||
MockEmmParser* parser = emm_parser.get();
|
||||
cas_api.setEmmParser(std::move(emm_parser));
|
||||
EXPECT_CALL(*parser, emm_payload).WillOnce(Return(emm_payload_));
|
||||
EXPECT_CALL(event_listener, OnServiceBlockingUpdated).Times(2);
|
||||
EXPECT_CALL(event_listener, OnFingerprintingUpdated).Times(2);
|
||||
EXPECT_EQ(cas_api.processEmm({}).status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
|
||||
// Change one of the fingerprinting
|
||||
emm_payload_.mutable_fingerprinting(0)->set_control("changed");
|
||||
emm_payload_.mutable_service_blocking(0)->set_end_time_sec(100);
|
||||
// Calling again will trigger only one event.
|
||||
auto emm_parser2 = make_unique<MockEmmParser>();
|
||||
MockEmmParser* parser2 = emm_parser2.get();
|
||||
cas_api.setEmmParser(std::move(emm_parser2));
|
||||
EXPECT_CALL(*parser2, emm_payload).WillOnce(Return(emm_payload_));
|
||||
EXPECT_CALL(event_listener, OnServiceBlockingUpdated).Times(1);
|
||||
EXPECT_CALL(event_listener, OnFingerprintingUpdated).Times(1);
|
||||
EXPECT_EQ(cas_api.processEmm({}).status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
}
|
||||
|
||||
TEST_F(WidevineCasTest, ProcessCAPrivateDataWithEntitlementPeriodIndex) {
|
||||
TestWidevineCas cas_api;
|
||||
EXPECT_CALL(*(cas_api.crypto_session_), initialize())
|
||||
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||
EXPECT_EQ(cas_api.initialize(nullptr).status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
// Set up mock ecm parser.
|
||||
const uint32_t expected_entitlement_period_index = 10;
|
||||
auto ecm_parser = make_unique<MockEcmParser>();
|
||||
EXPECT_CALL(*ecm_parser, is_entitlement_rotation_enabled)
|
||||
.WillOnce(Return(true));
|
||||
EXPECT_CALL(*ecm_parser, entitlement_period_index)
|
||||
.WillOnce(Return(expected_entitlement_period_index));
|
||||
EXPECT_CALL(cas_api, getEcmParser)
|
||||
.WillOnce(Return(testing::ByMove(std::move(ecm_parser))));
|
||||
// Construct the private data.
|
||||
std::string private_data_str;
|
||||
video_widevine::CaDescriptorPrivateData private_data;
|
||||
private_data.set_provider("provider");
|
||||
private_data.set_content_id("content_id");
|
||||
private_data.SerializeToString(&private_data_str);
|
||||
std::string actual_init_data;
|
||||
|
||||
// Process the ECM without license to will extract the entitlement period
|
||||
// index.
|
||||
EXPECT_EQ(
|
||||
cas_api.processEcm(/*sessionId=*/0, /*ecm=*/{1, 2, 3}).status_code(),
|
||||
wvcas::CasStatusCode::kDeferedEcmProcessing);
|
||||
EXPECT_EQ(cas_api
|
||||
.ProcessCAPrivateData(
|
||||
{private_data_str.begin(), private_data_str.end()},
|
||||
&actual_init_data)
|
||||
.status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
|
||||
video_widevine::WidevinePsshData pssh;
|
||||
pssh.ParseFromString(actual_init_data);
|
||||
EXPECT_EQ(pssh.entitlement_period_index(), expected_entitlement_period_index);
|
||||
}
|
||||
|
||||
bool Hash(const std::string& data, std::string* hash) {
|
||||
if (!hash) return false;
|
||||
hash->resize(SHA256_DIGEST_LENGTH);
|
||||
const unsigned char* input =
|
||||
reinterpret_cast<const unsigned char*>(data.data());
|
||||
unsigned char* output = reinterpret_cast<unsigned char*>(&(*hash)[0]);
|
||||
SHA256(input, data.size(), output);
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST_F(WidevineCasTest, GenerateEntitlementRequestWithEntitlementPeriodIndex) {
|
||||
TestWidevineCas cas_api;
|
||||
EXPECT_CALL(*(cas_api.crypto_session_), initialize())
|
||||
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||
EXPECT_EQ(cas_api.initialize(nullptr).status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
// pssh is the init data input to generateEntitlementRequest().
|
||||
video_widevine::WidevinePsshData pssh;
|
||||
pssh.set_provider("provider");
|
||||
pssh.set_content_id("content_id");
|
||||
pssh.set_type(video_widevine::WidevinePsshData::ENTITLEMENT);
|
||||
pssh.set_entitlement_period_index(123);
|
||||
// Create a license request containing the pssh.
|
||||
video_widevine::LicenseRequest license_request;
|
||||
license_request.mutable_content_id()->mutable_cenc_id_deprecated()->add_pssh(
|
||||
pssh.SerializeAsString());
|
||||
// Create a license file.
|
||||
File file;
|
||||
file.set_type(File::LICENSE);
|
||||
license_request.SerializeToString(
|
||||
file.mutable_license()->mutable_license_request());
|
||||
// Hash the created file
|
||||
HashedFile hashed_file;
|
||||
file.SerializeToString(hashed_file.mutable_file());
|
||||
Hash(hashed_file.file(), hashed_file.mutable_hash());
|
||||
const std::string license_file = hashed_file.SerializeAsString();
|
||||
|
||||
// Call to Open will return a unique_ptr, freeing this mock_file object.
|
||||
MockFile* mock_file = new MockFile();
|
||||
EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillRepeatedly(Return(true));
|
||||
EXPECT_CALL(*cas_api.file_system_, FileSize(_))
|
||||
.WillRepeatedly(Return(license_file.size()));
|
||||
EXPECT_CALL(*cas_api.file_system_, DoOpen(_, _))
|
||||
.WillRepeatedly(Return(mock_file));
|
||||
EXPECT_CALL(*mock_file, Read(NotNull(), license_file.size()))
|
||||
.WillRepeatedly([&](char* buffer, size_t bytes) {
|
||||
memcpy(buffer, license_file.data(), bytes);
|
||||
return bytes;
|
||||
});
|
||||
EXPECT_CALL(*cas_api.license_, HandleStoredLicense)
|
||||
.WillRepeatedly(Return(wvcas::CasStatus::OkStatus()));
|
||||
EXPECT_CALL(*cas_api.license_, IsExpired()).WillRepeatedly(Return(false));
|
||||
EXPECT_CALL(*cas_api.license_, IsGroupLicense())
|
||||
.WillRepeatedly(Return(false));
|
||||
// A new license request will not be generated.
|
||||
EXPECT_CALL(*cas_api.license_, GenerateEntitlementRequest).Times(0);
|
||||
std::string entitlement_request;
|
||||
std::string license_id;
|
||||
|
||||
EXPECT_TRUE(cas_api
|
||||
.generateEntitlementRequest(pssh.SerializeAsString(),
|
||||
&entitlement_request, license_id)
|
||||
.ok());
|
||||
|
||||
EXPECT_TRUE(entitlement_request.empty());
|
||||
EXPECT_FALSE(license_id.empty());
|
||||
}
|
||||
|
||||
TEST_F(WidevineCasTest,
|
||||
GenerateEntitlementRequestWithOutdatedEntitlementPeriodIndex) {
|
||||
TestWidevineCas cas_api;
|
||||
EXPECT_CALL(*(cas_api.crypto_session_), initialize())
|
||||
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||
EXPECT_EQ(cas_api.initialize(nullptr).status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
video_widevine::WidevinePsshData pssh;
|
||||
pssh.set_provider("provider");
|
||||
pssh.set_content_id("content_id");
|
||||
pssh.set_type(video_widevine::WidevinePsshData::ENTITLEMENT);
|
||||
pssh.set_entitlement_period_index(123);
|
||||
video_widevine::WidevinePsshData stored_pssh;
|
||||
stored_pssh.set_provider("provider");
|
||||
stored_pssh.set_content_id("content_id");
|
||||
stored_pssh.set_type(video_widevine::WidevinePsshData::ENTITLEMENT);
|
||||
stored_pssh.set_entitlement_period_index(
|
||||
122); // Not equal to 123.
|
||||
// Create a license request containing the pssh.
|
||||
video_widevine::LicenseRequest license_request;
|
||||
license_request.mutable_content_id()->mutable_cenc_id_deprecated()->add_pssh(
|
||||
stored_pssh.SerializeAsString());
|
||||
// Create a license file.
|
||||
File file;
|
||||
file.set_type(File::LICENSE);
|
||||
license_request.SerializeToString(
|
||||
file.mutable_license()->mutable_license_request());
|
||||
// Hash the created file
|
||||
HashedFile hashed_file;
|
||||
file.SerializeToString(hashed_file.mutable_file());
|
||||
const std::string license_file = hashed_file.SerializeAsString();
|
||||
|
||||
// Call to Open will return a unique_ptr, freeing this mock_file object.
|
||||
MockFile* mock_file = new MockFile();
|
||||
EXPECT_CALL(*cas_api.file_system_, Exists(_)).WillRepeatedly(Return(true));
|
||||
EXPECT_CALL(*cas_api.file_system_, FileSize(_))
|
||||
.WillRepeatedly(Return(license_file.size()));
|
||||
EXPECT_CALL(*cas_api.file_system_, DoOpen(_, _))
|
||||
.WillRepeatedly(Return(mock_file));
|
||||
EXPECT_CALL(*mock_file, Read(NotNull(), license_file.size()))
|
||||
.WillRepeatedly([&](char* buffer, size_t bytes) {
|
||||
memcpy(buffer, license_file.data(), bytes);
|
||||
return bytes;
|
||||
});
|
||||
EXPECT_CALL(*cas_api.license_, HandleStoredLicense)
|
||||
.WillRepeatedly(Return(wvcas::CasStatus::OkStatus()));
|
||||
EXPECT_CALL(*cas_api.license_, IsExpired()).WillRepeatedly(Return(false));
|
||||
EXPECT_CALL(*cas_api.license_, IsGroupLicense())
|
||||
.WillRepeatedly(Return(false));
|
||||
std::string expected_request = "entitlement_request";
|
||||
EXPECT_CALL(*cas_api.license_,
|
||||
GenerateEntitlementRequest(_, _, _, _, NotNull()))
|
||||
.Times(1)
|
||||
.WillOnce(DoAll(SetArgPointee<4>(expected_request),
|
||||
Return(wvcas::CasStatus::OkStatus())));
|
||||
std::string entitlement_request;
|
||||
std::string license_id;
|
||||
|
||||
EXPECT_TRUE(cas_api
|
||||
.generateEntitlementRequest(pssh.SerializeAsString(),
|
||||
&entitlement_request, license_id)
|
||||
.ok());
|
||||
|
||||
// A license request is generated.
|
||||
EXPECT_EQ(entitlement_request, expected_request);
|
||||
EXPECT_TRUE(license_id.empty());
|
||||
}
|
||||
|
||||
TEST_F(WidevineCasTest, generateEntitlementPeriodUpdateRequestSuccess) {
|
||||
TestWidevineCas cas_api;
|
||||
EXPECT_CALL(*cas_api.crypto_session_, initialize())
|
||||
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||
EXPECT_EQ(cas_api.initialize(&event_listener_).status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
// Prepare for second call for initializing crypto session and cas license.
|
||||
auto second_license_pass_through = make_unique<StrictMockLicense>();
|
||||
StrictMockLicense* second_license = second_license_pass_through.get();
|
||||
cas_api.setCasLicense(std::move(second_license_pass_through));
|
||||
auto second_crypto_pass_through = make_unique<StrictMockCryptoSession>();
|
||||
StrictMockCryptoSession* second_crypto = second_crypto_pass_through.get();
|
||||
cas_api.setCryptoSession(std::move(second_crypto_pass_through));
|
||||
EXPECT_CALL(*second_crypto, initialize())
|
||||
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||
EXPECT_CALL(*second_license, GenerateEntitlementRequest)
|
||||
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||
EXPECT_CALL(event_listener_, OnEntitlementPeriodUpdateNeeded).Times(1);
|
||||
std::string init_data;
|
||||
|
||||
EXPECT_EQ(
|
||||
cas_api.generateEntitlementPeriodUpdateRequest(init_data).status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
}
|
||||
|
||||
TEST_F(WidevineCasTest,
|
||||
handleEntitlementPeriodUpdateResponseWithoutRequestFail) {
|
||||
TestWidevineCas cas_api;
|
||||
EXPECT_CALL(*cas_api.crypto_session_, initialize())
|
||||
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||
EXPECT_EQ(cas_api.initialize(nullptr).status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
std::string response, license_id;
|
||||
|
||||
EXPECT_EQ(cas_api.handleEntitlementPeriodUpdateResponse(response, license_id)
|
||||
.status_code(),
|
||||
wvcas::CasStatusCode::kInvalidParameter);
|
||||
}
|
||||
|
||||
TEST_F(WidevineCasTest, handleEntitlementPeriodUpdateResponseSuccess) {
|
||||
TestWidevineCas cas_api;
|
||||
auto first_license_pass_through = make_unique<StrictMockLicense>();
|
||||
cas_api.setCasLicense(std::move(first_license_pass_through));
|
||||
auto first_crypto_pass_through = make_unique<StrictMockCryptoSession>();
|
||||
StrictMockCryptoSession* first_crypto = first_crypto_pass_through.get();
|
||||
cas_api.setCryptoSession(std::move(first_crypto_pass_through));
|
||||
EXPECT_CALL(*first_crypto, initialize())
|
||||
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||
EXPECT_EQ(cas_api.initialize(&event_listener_).status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
// Set up two sessions.
|
||||
wvcas::WvCasSessionId session_id_1;
|
||||
wvcas::WvCasSessionId session_id_2;
|
||||
EXPECT_CALL(*first_crypto, CreateEntitledKeySession(NotNull()))
|
||||
.Times(2)
|
||||
.WillOnce(
|
||||
DoAll(SetArgPointee<0>(3), Return(wvcas::CasStatus::OkStatus())))
|
||||
.WillOnce(
|
||||
DoAll(SetArgPointee<0>(4), Return(wvcas::CasStatus::OkStatus())));
|
||||
EXPECT_EQ(cas_api.openSession(&session_id_1).status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
EXPECT_EQ(cas_api.openSession(&session_id_2).status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
// Prepare for second call for initializing crypto session and cas license.
|
||||
auto second_license_pass_through = make_unique<StrictMockLicense>();
|
||||
StrictMockLicense* second_license = second_license_pass_through.get();
|
||||
cas_api.setCasLicense(std::move(second_license_pass_through));
|
||||
auto second_crypto_pass_through = make_unique<StrictMockCryptoSession>();
|
||||
StrictMockCryptoSession* second_crypto = second_crypto_pass_through.get();
|
||||
cas_api.setCryptoSession(std::move(second_crypto_pass_through));
|
||||
EXPECT_CALL(*second_crypto, initialize())
|
||||
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||
EXPECT_CALL(*second_license, GenerateEntitlementRequest)
|
||||
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||
EXPECT_CALL(event_listener_, OnEntitlementPeriodUpdateNeeded).Times(1);
|
||||
std::string init_data;
|
||||
// Generate switch request.
|
||||
EXPECT_EQ(
|
||||
cas_api.generateEntitlementPeriodUpdateRequest(init_data).status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
std::string response, license_id;
|
||||
EXPECT_CALL(*second_license, HandleEntitlementResponse)
|
||||
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||
// Sessions will be reassociated.
|
||||
EXPECT_CALL(*second_crypto, ReassociateEntitledKeySession(session_id_1))
|
||||
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||
EXPECT_CALL(*second_crypto, ReassociateEntitledKeySession(session_id_2))
|
||||
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||
EXPECT_CALL(*first_crypto, close)
|
||||
.WillOnce(Return(wvcas::CasStatus::OkStatus()));
|
||||
|
||||
EXPECT_EQ(cas_api.handleEntitlementPeriodUpdateResponse(response, license_id)
|
||||
.status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
|
||||
EXPECT_CALL(*(cas_api.crypto_session_), RemoveEntitledKeySession).Times(2);
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_api.closeSession(session_id_1).status_code());
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_api.closeSession(session_id_2).status_code());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace wvcas
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
#include "cas_util.h"
|
||||
#include "media_cas.pb.h"
|
||||
#include "mock_crypto_session.h"
|
||||
#include "mock_ecm_parser.h"
|
||||
#include "mock_event_listener.h"
|
||||
#include "string_conversions.h"
|
||||
|
||||
namespace {
|
||||
@@ -112,29 +114,6 @@ MATCHER(IsValidKeyOddSlotData, "") {
|
||||
return true;
|
||||
}
|
||||
|
||||
class MockEcmParser : public wvcas::EcmParser {
|
||||
public:
|
||||
MOCK_CONST_METHOD0(version, uint8_t());
|
||||
MOCK_CONST_METHOD0(age_restriction, uint8_t());
|
||||
MOCK_CONST_METHOD0(crypto_mode, wvcas::CryptoMode());
|
||||
MOCK_CONST_METHOD0(rotation_enabled, bool());
|
||||
MOCK_CONST_METHOD0(content_iv_size, size_t());
|
||||
MOCK_CONST_METHOD1(entitlement_key_id,
|
||||
std::vector<uint8_t>(wvcas::KeySlotId id));
|
||||
MOCK_CONST_METHOD1(content_key_id, std::vector<uint8_t>(wvcas::KeySlotId id));
|
||||
MOCK_CONST_METHOD1(wrapped_key_data,
|
||||
std::vector<uint8_t>(wvcas::KeySlotId id));
|
||||
MOCK_CONST_METHOD1(wrapped_key_iv, std::vector<uint8_t>(wvcas::KeySlotId id));
|
||||
MOCK_CONST_METHOD1(content_iv, std::vector<uint8_t>(wvcas::KeySlotId id));
|
||||
MOCK_METHOD(bool, set_group_id, (const std::string& group_id), (override));
|
||||
MOCK_CONST_METHOD0(has_fingerprinting, bool());
|
||||
MOCK_CONST_METHOD0(fingerprinting, video_widevine::Fingerprinting());
|
||||
MOCK_CONST_METHOD0(has_service_blocking, bool());
|
||||
MOCK_CONST_METHOD0(service_blocking, video_widevine::ServiceBlocking());
|
||||
MOCK_CONST_METHOD0(ecm_serialized_payload, std::string());
|
||||
MOCK_CONST_METHOD0(signature, std::string());
|
||||
};
|
||||
|
||||
class CasSessionTest : public ::testing::Test {
|
||||
public:
|
||||
CasSessionTest() {}
|
||||
@@ -318,30 +297,6 @@ TEST_F(CasSessionTest, parentalControl) {
|
||||
EXPECT_CALL(*mock, RemoveEntitledKeySession(session_id));
|
||||
}
|
||||
|
||||
class MockEventListener : public wvcas::CasEventListener {
|
||||
public:
|
||||
MockEventListener() {}
|
||||
~MockEventListener() override {}
|
||||
MOCK_METHOD0(OnSessionRenewalNeeded, void());
|
||||
MOCK_METHOD2(OnSessionKeysChange, void(const wvcas::KeyStatusMap& keys_status,
|
||||
bool has_new_usable_key));
|
||||
MOCK_METHOD1(OnExpirationUpdate, void(int64_t new_expiry_time_seconds));
|
||||
MOCK_METHOD1(OnNewRenewalServerUrl,
|
||||
void(const std::string& renewal_server_url));
|
||||
MOCK_METHOD0(OnLicenseExpiration, void());
|
||||
MOCK_METHOD2(OnAgeRestrictionUpdated,
|
||||
void(const wvcas::WvCasSessionId& sessionId,
|
||||
uint8_t ecm_age_restriction));
|
||||
MOCK_METHOD(void, OnSessionFingerprintingUpdated,
|
||||
(const int32_t& sessionId,
|
||||
const std::vector<uint8_t>& fingerprinting),
|
||||
(override));
|
||||
MOCK_METHOD(void, OnSessionServiceBlockingUpdated,
|
||||
(const int32_t& sessionId,
|
||||
const std::vector<uint8_t>& service_blocking),
|
||||
(override));
|
||||
};
|
||||
|
||||
TEST_F(CasSessionTest, FingerprintingSuccess) {
|
||||
TestCasSession session;
|
||||
auto mock_crypto = std::make_shared<MockCryptoSession>();
|
||||
|
||||
@@ -209,5 +209,17 @@ TEST(WidevineCasPluginTest, HandleEntitlementResponseEmptyResponseFail) {
|
||||
android::OK);
|
||||
}
|
||||
|
||||
TEST(WidevineCasPluginTest, HandlePluginVersionQuerySuccess) {
|
||||
TestWidevineCasPlugin plugin;
|
||||
std::string expected_version = "uint-test";
|
||||
EXPECT_CALL(plugin, CallBack(_, WV_CAS_PLUGIN_VERSION, _, NotNull(),
|
||||
expected_version.size(), _))
|
||||
.Times(1);
|
||||
|
||||
EXPECT_EQ(plugin.sendEvent(QUERY_WV_CAS_PLUGIN_VERSION, /*arg=*/0,
|
||||
/*eventData=*/{}),
|
||||
android::OK);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace wvcas
|
||||
Reference in New Issue
Block a user