V18.4.0 CAS plugin
Note that this version does not have Widevine Provisioning 4.0 support. It is only suitable for device upgrades. A new patch with provisioning 4.0 support will be made later.
This commit is contained in:
42
tests/Android.bp
Normal file
42
tests/Android.bp
Normal file
@@ -0,0 +1,42 @@
|
||||
cc_binary {
|
||||
name: "wv_cas_tests",
|
||||
proprietary: true,
|
||||
srcs: [
|
||||
"src/cas_license_test.cpp",
|
||||
"src/cas_session_map_test.cpp",
|
||||
"src/crypto_session_test.cpp",
|
||||
"src/ecm_parser_test.cpp",
|
||||
"src/ecm_parser_v2_test.cpp",
|
||||
"src/ecm_parser_v3_test.cpp",
|
||||
"src/emm_parser_test.cpp",
|
||||
"src/license_key_status_test.cpp",
|
||||
"src/policy_engine_test.cpp",
|
||||
"src/test_properties.cpp",
|
||||
"src/timer_test.cpp",
|
||||
"src/widevine_cas_api_test.cpp",
|
||||
"src/widevine_cas_session_test.cpp",
|
||||
"src/widevine_media_cas_plugin_test.cpp",
|
||||
"src/wv_cas_test_main.cpp",
|
||||
],
|
||||
header_libs: [
|
||||
"//vendor/widevine/libwvmediacas/oemcrypto:oemcastroheaders",
|
||||
"media_plugin_headers",
|
||||
],
|
||||
static_libs: [
|
||||
"//vendor/widevine/libwvmediacas/wvutil:libcasutil",
|
||||
"//vendor/widevine/libwvmediacas/plugin:libwvcasplugins",
|
||||
"//vendor/widevine/libwvmediacas/protos:libcas_protos",
|
||||
"libgmock",
|
||||
"libgtest",
|
||||
],
|
||||
shared_libs: [
|
||||
"libcrypto",
|
||||
"libutils",
|
||||
"liblog",
|
||||
"libprotobuf-cpp-lite",
|
||||
"libhidlbase",
|
||||
],
|
||||
proto: {
|
||||
type: "lite",
|
||||
},
|
||||
}
|
||||
777
tests/src/cas_license_test.cpp
Normal file
777
tests/src/cas_license_test.cpp
Normal file
@@ -0,0 +1,777 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "cas_license.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "cas_status.h"
|
||||
#include "cas_util.h"
|
||||
#include "device_files.pb.h"
|
||||
#include "license_protocol.pb.h"
|
||||
#include "mock_crypto_session.h"
|
||||
#include "string_conversions.h"
|
||||
|
||||
namespace wvcas {
|
||||
namespace {
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::AllOf;
|
||||
using ::testing::DoAll;
|
||||
using ::testing::Eq;
|
||||
using ::testing::Invoke;
|
||||
using ::testing::IsNull;
|
||||
using ::testing::NotNull;
|
||||
using ::testing::Pointee;
|
||||
using ::testing::Return;
|
||||
using ::testing::ReturnRef;
|
||||
using ::testing::SetArgPointee;
|
||||
using ::testing::SetArgReferee;
|
||||
using ::testing::StrictMock;
|
||||
|
||||
using wvutil::Base64Decode;
|
||||
using wvutil::Base64SafeDecode;
|
||||
using wvutil::Base64SafeEncodeNoPad;
|
||||
|
||||
using video_widevine_client::sdk::DeviceCertificate;
|
||||
using video_widevine_client::sdk::File;
|
||||
using video_widevine_client::sdk::HashedFile;
|
||||
|
||||
static constexpr char kKeyboxToken[] = "KeyBoxToken";
|
||||
static constexpr char kExpectedRenewalRequest[] = "ExpectedRenewalRequest";
|
||||
static constexpr char kExpectedSignature[] = "ExpectedSignature";
|
||||
static constexpr char kDeviceRsaKey[] = "DeviceRSAKeyDeviceRSAKeyDeviceRSAKey";
|
||||
static constexpr char kDeviceRsaKeyIV[] = "0123456789abcdef";
|
||||
static constexpr char kInitializationData[] = "CasInitializationData";
|
||||
static constexpr char kSessionKey[] = "fedcba9876543210";
|
||||
static constexpr uint32_t kNonce = 0x5555;
|
||||
static constexpr size_t kExpectedKeyboxSizeBytes = 72;
|
||||
static constexpr char kJsonStartSubstr[] = "\"signedResponse\": \"";
|
||||
static constexpr char kJsonEndSubstr[] = "\"";
|
||||
static constexpr char kKeyIDVideo[] = "KeyIdVideo";
|
||||
static constexpr char kKeyIDAudio[] = "KeyIdAudio";
|
||||
static constexpr char kKeyVideoIV[] = "KeyVideoIV";
|
||||
static constexpr char kKeyAudioIV[] = "KeyAudioIV";
|
||||
static constexpr char kKeyVideo[] = "KeyVideo";
|
||||
static constexpr char kKeyAudio[] = "KeyAudio";
|
||||
static constexpr char kKeyControlVideo[] = "KeyControlVideo";
|
||||
static constexpr char kKeyControlAudio[] = "KeyControlAudio";
|
||||
static constexpr char kKeyControlIVVideo[] = "KeyControlIVVideo";
|
||||
static constexpr char kKeyControlIVAudio[] = "KeyControlIVAudio";
|
||||
static constexpr char kTrackTypeVideo[] = "Video";
|
||||
static constexpr char kTrackTypeAudio[] = "Audio";
|
||||
static constexpr char kKeyCompanyName[] = "company_name";
|
||||
static constexpr char kKeyModelName[] = "model_name";
|
||||
static constexpr char kKeyArchitectureName[] = "architecture_name";
|
||||
static constexpr char kKeyDeviceName[] = "device_name";
|
||||
static constexpr char kKeyProductName[] = "product_name";
|
||||
static constexpr char kKeyBuildInfo[] = "build_info";
|
||||
static constexpr char kKeyDeviceId[] = "device_id";
|
||||
static constexpr char kKeyOemCryptoSecurityPatchLevel[] =
|
||||
"oem_crypto_security_patch_level";
|
||||
static constexpr char kRenewalSereverURL[] = "ExpectedRenewalURL";
|
||||
static constexpr char kCoreMessage[] = "CoreMessage";
|
||||
|
||||
typedef StrictMock<MockCryptoSession> StrictMockCryptoSession;
|
||||
|
||||
class MockPolicyEngine : public wvcas::PolicyEngine {
|
||||
public:
|
||||
MockPolicyEngine() {}
|
||||
~MockPolicyEngine() override {}
|
||||
MOCK_METHOD2(initialize,
|
||||
void(std::shared_ptr<wvcas::CryptoSession> crypto_session,
|
||||
wvcas::CasEventListener* event_listener));
|
||||
MOCK_METHOD1(SetLicense, void(const video_widevine::License& license));
|
||||
MOCK_METHOD1(UpdateLicense, void(const video_widevine::License& license));
|
||||
MOCK_CONST_METHOD0(CanRenew, bool());
|
||||
MOCK_CONST_METHOD0(renewal_server_url, const std::string&());
|
||||
MOCK_CONST_METHOD0(IsExpired, bool());
|
||||
MOCK_CONST_METHOD0(CanPersist, bool());
|
||||
MOCK_CONST_METHOD0(always_include_client_id, bool());
|
||||
};
|
||||
typedef StrictMock<MockPolicyEngine> StrictMockPolicyEngine;
|
||||
|
||||
class TestCasLicense : public wvcas::CasLicense {
|
||||
public:
|
||||
explicit TestCasLicense() {}
|
||||
~TestCasLicense() override{};
|
||||
std::unique_ptr<wvcas::PolicyEngine> GetPolicyEngine() override {
|
||||
policy_engine_ = pass_thru_.get();
|
||||
return std::move(pass_thru_);
|
||||
}
|
||||
std::unique_ptr<StrictMockPolicyEngine> pass_thru_ =
|
||||
make_unique<StrictMockPolicyEngine>();
|
||||
StrictMockPolicyEngine* policy_engine_ = pass_thru_.get();
|
||||
};
|
||||
|
||||
class CasLicenseTest : public ::testing::TestWithParam<bool> {
|
||||
public:
|
||||
CasLicenseTest() {}
|
||||
virtual ~CasLicenseTest() {}
|
||||
void SetKeyboxData(uint8_t* keyData, size_t* keyDataLength) {
|
||||
ASSERT_EQ(kExpectedKeyboxSizeBytes, *keyDataLength);
|
||||
memset(keyData, 0, *keyDataLength);
|
||||
memcpy(keyData, kKeyboxToken, 11);
|
||||
}
|
||||
|
||||
std::string CreateProvisioningResponse();
|
||||
|
||||
std::shared_ptr<StrictMockCryptoSession> strict_mock_;
|
||||
std::string wrapped_rsa_key_;
|
||||
std::string device_certificate_;
|
||||
};
|
||||
|
||||
std::string CasLicenseTest::CreateProvisioningResponse() {
|
||||
video_widevine::SignedProvisioningMessage signed_message;
|
||||
signed_message.set_signature(kExpectedSignature);
|
||||
signed_message.set_oemcrypto_core_message(kCoreMessage);
|
||||
|
||||
video_widevine::ProvisioningResponse response;
|
||||
std::string* nonce = response.mutable_nonce();
|
||||
nonce->resize(4);
|
||||
memcpy(&nonce->at(0), &kNonce, 4);
|
||||
|
||||
response.set_device_rsa_key(kDeviceRsaKey);
|
||||
response.set_device_rsa_key_iv(kDeviceRsaKeyIV);
|
||||
response.SerializeToString(signed_message.mutable_message());
|
||||
|
||||
std::vector<uint8_t> b64_message(signed_message.ByteSize());
|
||||
signed_message.SerializeToArray(&b64_message[0], b64_message.size());
|
||||
|
||||
return kJsonStartSubstr + Base64SafeEncodeNoPad(b64_message) + kJsonEndSubstr;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
std::string CreateEntitlementResponse() {
|
||||
video_widevine::License license;
|
||||
auto* key = license.add_key();
|
||||
key->set_type(video_widevine::License_KeyContainer::ENTITLEMENT);
|
||||
|
||||
video_widevine::SignedMessage signed_message;
|
||||
license.SerializeToString(signed_message.mutable_msg());
|
||||
signed_message.set_type(video_widevine::SignedMessage::CAS_LICENSE);
|
||||
signed_message.set_signature(kExpectedSignature);
|
||||
signed_message.set_session_key(kExpectedSignature);
|
||||
signed_message.set_oemcrypto_core_message(kCoreMessage);
|
||||
return signed_message.SerializeAsString();
|
||||
}
|
||||
|
||||
std::string CreateEntitlementRenewalResponse() {
|
||||
video_widevine::License license;
|
||||
video_widevine::SignedMessage signed_message;
|
||||
license.SerializeToString(signed_message.mutable_msg());
|
||||
signed_message.set_type(video_widevine::SignedMessage::CAS_LICENSE);
|
||||
signed_message.set_signature(kExpectedSignature);
|
||||
signed_message.set_oemcrypto_core_message(kCoreMessage);
|
||||
return signed_message.SerializeAsString();
|
||||
}
|
||||
|
||||
std::string CreateLicenseFileData() {
|
||||
File file;
|
||||
file.set_type(File::LICENSE);
|
||||
|
||||
video_widevine::SignedMessage license_request;
|
||||
license_request.set_msg("license_request");
|
||||
license_request.set_signature("license_request_signature");
|
||||
license_request.set_type(video_widevine::SignedMessage::CAS_LICENSE_REQUEST);
|
||||
|
||||
video_widevine::SignedMessage renewal_request;
|
||||
renewal_request.set_msg("renewal_request");
|
||||
renewal_request.set_signature("renewal_request_signature");
|
||||
renewal_request.set_type(video_widevine::SignedMessage::CAS_LICENSE_REQUEST);
|
||||
|
||||
video_widevine_client::sdk::License* license = file.mutable_license();
|
||||
license_request.SerializeToString(license->mutable_license_request());
|
||||
license->set_license(CreateEntitlementResponse());
|
||||
renewal_request.SerializeToString(license->mutable_renewal_request());
|
||||
license->set_renewal(CreateEntitlementRenewalResponse());
|
||||
|
||||
HashedFile hashed_file;
|
||||
file.SerializeToString(hashed_file.mutable_file());
|
||||
Hash(hashed_file.file(), hashed_file.mutable_hash());
|
||||
|
||||
return hashed_file.SerializeAsString();
|
||||
}
|
||||
|
||||
TEST_F(CasLicenseTest, GenerateDeviceProvisioningRequest) {
|
||||
strict_mock_ = std::make_shared<StrictMockCryptoSession>();
|
||||
TestCasLicense cas_license;
|
||||
EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_license.initialize(strict_mock_, nullptr).status_code());
|
||||
|
||||
std::string serialized_provisioning_request;
|
||||
std::string expected_signature(kExpectedSignature);
|
||||
|
||||
EXPECT_CALL(*strict_mock_, provisioning_method())
|
||||
.WillRepeatedly(Return(wvcas::Keybox));
|
||||
EXPECT_CALL(*strict_mock_, GetKeyData(NotNull(), NotNull()))
|
||||
.WillOnce(DoAll(Invoke(this, &CasLicenseTest::SetKeyboxData),
|
||||
Return(wvcas::CasStatusCode::kNoError)));
|
||||
EXPECT_CALL(*strict_mock_, supported_certificates())
|
||||
.WillOnce(Return(wvcas::SupportedCertificates(0x13)));
|
||||
EXPECT_CALL(*strict_mock_, GenerateNonce(NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<0>(kNonce),
|
||||
Return(wvcas::CasStatusCode::kNoError)));
|
||||
EXPECT_CALL(*strict_mock_, GenerateDerivedKeys(_, _, _, _))
|
||||
.WillOnce(Return(wvcas::CasStatusCode::kNoError));
|
||||
EXPECT_CALL(*strict_mock_,
|
||||
PrepareAndSignProvisioningRequest(_, NotNull(), NotNull(), _, _))
|
||||
.WillOnce(DoAll(SetArgPointee<2>(kExpectedSignature),
|
||||
SetArgReferee<3>(false),
|
||||
Return(wvcas::CasStatusCode::kNoError)));
|
||||
EXPECT_CALL(*strict_mock_, GetDeviceID(NotNull()))
|
||||
.WillOnce(Return(wvcas::CasStatusCode::kNoError));
|
||||
|
||||
wvcas::CasStatus status = cas_license.GenerateDeviceProvisioningRequest(
|
||||
&serialized_provisioning_request);
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
|
||||
|
||||
// Verify the provisioning request.
|
||||
video_widevine::SignedProvisioningMessage signed_message;
|
||||
auto data = Base64Decode(serialized_provisioning_request);
|
||||
EXPECT_TRUE(signed_message.ParseFromArray(&data[0], data.size()));
|
||||
EXPECT_EQ(kExpectedSignature, signed_message.signature());
|
||||
EXPECT_EQ(video_widevine::SignedProvisioningMessage::PROVISIONING_20,
|
||||
signed_message.protocol_version());
|
||||
|
||||
video_widevine::ProvisioningRequest provisioning_request;
|
||||
EXPECT_TRUE(provisioning_request.ParseFromString(signed_message.message()));
|
||||
|
||||
auto& client_id = provisioning_request.client_id();
|
||||
EXPECT_EQ(video_widevine::ClientIdentification::KEYBOX, client_id.type());
|
||||
std::string token(kKeyboxToken);
|
||||
token.resize(kExpectedKeyboxSizeBytes, 0);
|
||||
EXPECT_EQ(token, client_id.token());
|
||||
|
||||
ASSERT_EQ(sizeof(uint32_t), provisioning_request.nonce().size());
|
||||
EXPECT_EQ(kNonce, *reinterpret_cast<const uint32_t*>(
|
||||
provisioning_request.nonce().data()));
|
||||
}
|
||||
|
||||
TEST_F(CasLicenseTest, HandleProvisioningResponse) {
|
||||
const std::string provisioning_response = CreateProvisioningResponse();
|
||||
|
||||
strict_mock_ = std::make_shared<StrictMockCryptoSession>();
|
||||
TestCasLicense cas_license;
|
||||
EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_license.initialize(strict_mock_, nullptr).status_code());
|
||||
|
||||
EXPECT_CALL(*strict_mock_, reset())
|
||||
.WillOnce(Return(wvcas::CasStatusCode::kNoError));
|
||||
EXPECT_CALL(*strict_mock_, LoadProvisioning(_, _, _, _));
|
||||
|
||||
wvcas::CasStatus status = cas_license.HandleDeviceProvisioningResponse(
|
||||
provisioning_response, &device_certificate_, &wrapped_rsa_key_, nullptr);
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
|
||||
}
|
||||
|
||||
TEST_F(CasLicenseTest, HandleProvisioningResponseWithDeviceFileOutput) {
|
||||
const std::string provisioning_response = CreateProvisioningResponse();
|
||||
|
||||
strict_mock_ = std::make_shared<StrictMockCryptoSession>();
|
||||
TestCasLicense cas_license;
|
||||
EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_license.initialize(strict_mock_, nullptr).status_code());
|
||||
|
||||
EXPECT_CALL(*strict_mock_, reset())
|
||||
.WillOnce(Return(wvcas::CasStatusCode::kNoError));
|
||||
EXPECT_CALL(*strict_mock_, LoadProvisioning(_, _, _, _));
|
||||
|
||||
std::string device_cert_file;
|
||||
wvcas::CasStatus status = cas_license.HandleDeviceProvisioningResponse(
|
||||
provisioning_response, &device_certificate_, &wrapped_rsa_key_,
|
||||
&device_cert_file);
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
|
||||
EXPECT_FALSE(device_cert_file.empty());
|
||||
}
|
||||
|
||||
TEST_F(CasLicenseTest, GenerateEntitlementRequest) {
|
||||
strict_mock_ = std::make_shared<StrictMockCryptoSession>();
|
||||
TestCasLicense cas_license;
|
||||
EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_license.initialize(strict_mock_, nullptr).status_code());
|
||||
|
||||
EXPECT_CALL(*strict_mock_, GetOEMPublicCertificate(_, _))
|
||||
.WillOnce(Return(wvcas::CasStatusCode::kCryptoSessionError));
|
||||
|
||||
EXPECT_CALL(*strict_mock_, APIVersion(_))
|
||||
.WillOnce(Return(wvcas::CasStatusCode::kNoError));
|
||||
|
||||
EXPECT_CALL(*strict_mock_, GenerateNonce(_))
|
||||
.WillOnce(Return(wvcas::CasStatusCode::kNoError));
|
||||
EXPECT_CALL(*strict_mock_,
|
||||
PrepareAndSignLicenseRequest(_, NotNull(), NotNull(), _, _))
|
||||
.WillOnce(DoAll(SetArgPointee<2>(kExpectedSignature),
|
||||
SetArgReferee<3>(false),
|
||||
Return(wvcas::CasStatusCode::kNoError)));
|
||||
EXPECT_CALL(*strict_mock_, LoadDeviceRSAKey(_, _));
|
||||
|
||||
std::string serialized_entitlement_request;
|
||||
wvcas::CasStatus status = cas_license.GenerateEntitlementRequest(
|
||||
kInitializationData, device_certificate_, wrapped_rsa_key_,
|
||||
wvcas::LicenseType::kStreaming, &serialized_entitlement_request);
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
|
||||
}
|
||||
|
||||
TEST_F(CasLicenseTest, HandleEntitlementResponse) {
|
||||
strict_mock_ = std::make_shared<StrictMockCryptoSession>();
|
||||
|
||||
TestCasLicense cas_license;
|
||||
EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_license.initialize(strict_mock_, nullptr).status_code());
|
||||
|
||||
EXPECT_CALL(*strict_mock_, GetOEMPublicCertificate(_, _))
|
||||
.WillOnce(Return(wvcas::CasStatusCode::kCryptoSessionError));
|
||||
EXPECT_CALL(*strict_mock_, APIVersion(_))
|
||||
.WillOnce(Return(wvcas::CasStatusCode::kNoError));
|
||||
|
||||
EXPECT_CALL(*strict_mock_, GenerateNonce(_))
|
||||
.WillOnce(Return(wvcas::CasStatusCode::kNoError));
|
||||
EXPECT_CALL(*strict_mock_,
|
||||
PrepareAndSignLicenseRequest(_, NotNull(), NotNull(), _, _))
|
||||
.WillOnce(DoAll(SetArgPointee<2>(kExpectedSignature),
|
||||
SetArgReferee<3>(false),
|
||||
Return(wvcas::CasStatusCode::kNoError)));
|
||||
EXPECT_CALL(*strict_mock_, LoadDeviceRSAKey(_, _));
|
||||
|
||||
std::string serialized_entitlement_request;
|
||||
wvcas::CasStatus status = cas_license.GenerateEntitlementRequest(
|
||||
kInitializationData, device_certificate_, wrapped_rsa_key_,
|
||||
wvcas::LicenseType::kStreaming, &serialized_entitlement_request);
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
|
||||
|
||||
video_widevine::SignedMessage signed_message;
|
||||
ASSERT_TRUE(signed_message.ParseFromString(serialized_entitlement_request));
|
||||
|
||||
const std::string entitlement_response = CreateEntitlementResponse();
|
||||
|
||||
EXPECT_CALL(*strict_mock_, DeriveKeysFromSessionKey(_, _, _, _, _, _))
|
||||
.WillRepeatedly(Return(wvcas::CasStatusCode::kNoError));
|
||||
EXPECT_CALL(*strict_mock_, LoadLicense(_, _, _))
|
||||
.WillRepeatedly(Return(wvcas::CasStatusCode::kNoError));
|
||||
|
||||
// Valid.
|
||||
EXPECT_CALL(*cas_license.policy_engine_, SetLicense(_));
|
||||
EXPECT_CALL(*cas_license.policy_engine_, CanPersist())
|
||||
.WillOnce(Return(false));
|
||||
status = cas_license.HandleEntitlementResponse(entitlement_response,
|
||||
/*device_file=*/nullptr);
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
|
||||
// Not a group license.
|
||||
EXPECT_TRUE(cas_license.GetGroupId().empty());
|
||||
EXPECT_TRUE(cas_license.GetContentIdList().empty());
|
||||
EXPECT_FALSE(cas_license.IsGroupLicense());
|
||||
EXPECT_FALSE(cas_license.IsMultiContentLicense());
|
||||
|
||||
// Valid with device file.
|
||||
EXPECT_CALL(*cas_license.policy_engine_, SetLicense(_));
|
||||
EXPECT_CALL(*cas_license.policy_engine_, CanPersist())
|
||||
.WillOnce(Return(false));
|
||||
std::string 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, &device_file);
|
||||
EXPECT_FALSE(device_file.empty());
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
|
||||
|
||||
HashedFile hashed_file;
|
||||
ASSERT_TRUE(hashed_file.ParseFromString(device_file));
|
||||
std::string hash;
|
||||
Hash(hashed_file.file(), &hash);
|
||||
EXPECT_EQ(std::vector<uint8_t>(hash.begin(), hash.end()),
|
||||
std::vector<uint8_t>(hashed_file.hash().begin(),
|
||||
hashed_file.hash().end()));
|
||||
|
||||
File file;
|
||||
ASSERT_TRUE(file.ParseFromString(hashed_file.file()));
|
||||
|
||||
const auto& license_file = file.license();
|
||||
EXPECT_FALSE(license_file.license_request().empty());
|
||||
EXPECT_FALSE(license_file.license().empty());
|
||||
EXPECT_TRUE(license_file.renewal_request().empty());
|
||||
EXPECT_TRUE(license_file.renewal().empty());
|
||||
|
||||
std::string emm_request = *signed_message.mutable_msg();
|
||||
EXPECT_EQ(std::vector<uint8_t>(emm_request.begin(), emm_request.end()),
|
||||
std::vector<uint8_t>(license_file.license_request().begin(),
|
||||
license_file.license_request().end()));
|
||||
EXPECT_EQ(std::vector<uint8_t>(entitlement_response.begin(),
|
||||
entitlement_response.end()),
|
||||
std::vector<uint8_t>(license_file.license().begin(),
|
||||
license_file.license().end()));
|
||||
}
|
||||
|
||||
TEST_P(CasLicenseTest, GenerateRenewalRequest) {
|
||||
strict_mock_ = std::make_shared<StrictMockCryptoSession>();
|
||||
TestCasLicense cas_license;
|
||||
EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _));
|
||||
EXPECT_CALL(*cas_license.policy_engine_, always_include_client_id())
|
||||
.WillOnce(Return(GetParam()));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_license.initialize(strict_mock_, nullptr).status_code());
|
||||
|
||||
std::string serialized_renewal_request;
|
||||
|
||||
// Policy can_renew is false.
|
||||
EXPECT_CALL(*cas_license.policy_engine_, CanRenew()).WillOnce(Return(false));
|
||||
EXPECT_NE(wvcas::CasStatusCode::kNoError,
|
||||
cas_license
|
||||
.GenerateEntitlementRenewalRequest(device_certificate_,
|
||||
&serialized_renewal_request)
|
||||
.status_code());
|
||||
|
||||
// Policy can_renew is true.
|
||||
EXPECT_CALL(*cas_license.policy_engine_, CanRenew()).WillOnce(Return(true));
|
||||
EXPECT_CALL(*strict_mock_,
|
||||
PrepareAndSignRenewalRequest(_, NotNull(), NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<2>(kExpectedSignature),
|
||||
Return(wvcas::CasStatusCode::kNoError)));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_license
|
||||
.GenerateEntitlementRenewalRequest(device_certificate_,
|
||||
&serialized_renewal_request)
|
||||
.status_code());
|
||||
video_widevine::SignedMessage signed_message;
|
||||
ASSERT_TRUE(signed_message.ParseFromString(serialized_renewal_request));
|
||||
video_widevine::LicenseRequest license_request;
|
||||
ASSERT_TRUE(license_request.ParseFromString(signed_message.msg()));
|
||||
if (GetParam()) {
|
||||
// Always include client id == true.
|
||||
ASSERT_TRUE(license_request.has_client_id());
|
||||
EXPECT_EQ(device_certificate_, license_request.client_id().token());
|
||||
} else {
|
||||
ASSERT_FALSE(license_request.has_client_id());
|
||||
}
|
||||
}
|
||||
|
||||
// Test renewal request generation with always_include_client_id == true and
|
||||
// false;
|
||||
// Suppress warning "INSTANTIATE_TEST_CASE_P is deprecated".
|
||||
// TODO(b/142954362): Remove the suppression once gtest on pi-tv-dev branch
|
||||
// updates to the latest version.
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
INSTANTIATE_TEST_CASE_P(GenerateRenewalRequest, CasLicenseTest,
|
||||
::testing::Bool());
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
TEST_F(CasLicenseTest, HandleRenewalResponse) {
|
||||
strict_mock_ = std::make_shared<StrictMockCryptoSession>();
|
||||
TestCasLicense cas_license;
|
||||
EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _));
|
||||
EXPECT_CALL(*cas_license.policy_engine_, always_include_client_id())
|
||||
.WillOnce(Return(false));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_license.initialize(strict_mock_, nullptr).status_code());
|
||||
|
||||
std::string serialized_renewal_request;
|
||||
|
||||
// Policy can_renew is false.
|
||||
EXPECT_CALL(*cas_license.policy_engine_, CanRenew()).WillOnce(Return(false));
|
||||
EXPECT_NE(wvcas::CasStatusCode::kNoError,
|
||||
cas_license
|
||||
.GenerateEntitlementRenewalRequest(device_certificate_,
|
||||
&serialized_renewal_request)
|
||||
.status_code());
|
||||
|
||||
const std::string renewal_response = CreateEntitlementRenewalResponse();
|
||||
|
||||
// Policy can_renew is true. Set expectations to build a renewal request.
|
||||
EXPECT_CALL(*cas_license.policy_engine_, CanRenew()).WillOnce(Return(true));
|
||||
EXPECT_CALL(*strict_mock_,
|
||||
PrepareAndSignRenewalRequest(_, NotNull(), NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<2>(kExpectedSignature),
|
||||
Return(wvcas::CasStatusCode::kNoError)));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_license
|
||||
.GenerateEntitlementRenewalRequest(device_certificate_,
|
||||
&serialized_renewal_request)
|
||||
.status_code());
|
||||
|
||||
video_widevine::SignedMessage signed_message;
|
||||
ASSERT_TRUE(signed_message.ParseFromString(serialized_renewal_request));
|
||||
|
||||
std::string device_file;
|
||||
EXPECT_CALL(*strict_mock_, LoadRenewal(_, _, _))
|
||||
.WillRepeatedly(Return(wvcas::CasStatusCode::kNoError));
|
||||
// Policy can_persist is false. no file information is generated.
|
||||
EXPECT_CALL(*cas_license.policy_engine_, UpdateLicense(_));
|
||||
EXPECT_CALL(*cas_license.policy_engine_, CanPersist())
|
||||
.WillOnce(Return(false));
|
||||
EXPECT_EQ(
|
||||
wvcas::CasStatusCode::kNoError,
|
||||
cas_license
|
||||
.HandleEntitlementRenewalResponse(renewal_response, &device_file)
|
||||
.status_code());
|
||||
EXPECT_TRUE(device_file.empty());
|
||||
|
||||
// Policy can_persist is true. Validate that device_file is populated with a
|
||||
// valid license file.
|
||||
EXPECT_CALL(*cas_license.policy_engine_, CanPersist()).WillOnce(Return(true));
|
||||
EXPECT_CALL(*cas_license.policy_engine_, UpdateLicense(_));
|
||||
ASSERT_EQ(
|
||||
wvcas::CasStatusCode::kNoError,
|
||||
cas_license
|
||||
.HandleEntitlementRenewalResponse(renewal_response, &device_file)
|
||||
.status_code());
|
||||
ASSERT_FALSE(device_file.empty());
|
||||
|
||||
HashedFile hashed_file;
|
||||
ASSERT_TRUE(hashed_file.ParseFromString(device_file));
|
||||
std::string hash;
|
||||
Hash(hashed_file.file(), &hash);
|
||||
EXPECT_EQ(std::vector<uint8_t>(hash.begin(), hash.end()),
|
||||
std::vector<uint8_t>(hashed_file.hash().begin(),
|
||||
hashed_file.hash().end()));
|
||||
|
||||
File file;
|
||||
ASSERT_TRUE(file.ParseFromString(hashed_file.file()));
|
||||
|
||||
const auto& license_file = file.license();
|
||||
EXPECT_TRUE(license_file.license_request().empty());
|
||||
EXPECT_TRUE(license_file.license().empty());
|
||||
EXPECT_FALSE(license_file.renewal_request().empty());
|
||||
EXPECT_FALSE(license_file.renewal().empty());
|
||||
|
||||
std::string renewal_request = *signed_message.mutable_msg();
|
||||
EXPECT_EQ(
|
||||
std::vector<uint8_t>(renewal_request.begin(), renewal_request.end()),
|
||||
std::vector<uint8_t>(license_file.renewal_request().begin(),
|
||||
license_file.renewal_request().end()));
|
||||
EXPECT_EQ(
|
||||
std::vector<uint8_t>(renewal_response.begin(), renewal_response.end()),
|
||||
std::vector<uint8_t>(license_file.renewal().begin(),
|
||||
license_file.renewal().end()));
|
||||
}
|
||||
|
||||
TEST_F(CasLicenseTest, IsExpired) {
|
||||
TestCasLicense cas_license;
|
||||
strict_mock_ = std::make_shared<StrictMockCryptoSession>();
|
||||
EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_license.initialize(strict_mock_, nullptr).status_code());
|
||||
EXPECT_CALL(*cas_license.policy_engine_, IsExpired())
|
||||
.WillOnce(Return(false))
|
||||
.WillOnce(Return(true));
|
||||
|
||||
EXPECT_FALSE(cas_license.IsExpired());
|
||||
EXPECT_TRUE(cas_license.IsExpired());
|
||||
}
|
||||
|
||||
TEST_F(CasLicenseTest, RestoreLicense) {
|
||||
TestCasLicense cas_license;
|
||||
strict_mock_ = std::make_shared<StrictMockCryptoSession>();
|
||||
EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_license.initialize(strict_mock_, nullptr).status_code());
|
||||
|
||||
std::string license_file_data = CreateLicenseFileData();
|
||||
EXPECT_CALL(*strict_mock_, LoadDeviceRSAKey(_, _));
|
||||
EXPECT_CALL(*strict_mock_, DeriveKeysFromSessionKey(_, _, _, _, _, _))
|
||||
.WillRepeatedly(Return(wvcas::CasStatusCode::kNoError));
|
||||
EXPECT_CALL(*strict_mock_, LoadLicense(_, _, _))
|
||||
.WillRepeatedly(Return(wvcas::CasStatusCode::kNoError));
|
||||
EXPECT_CALL(*strict_mock_, LoadRenewal(_, _, _))
|
||||
.WillRepeatedly(Return(wvcas::CasStatusCode::kNoError));
|
||||
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)
|
||||
.status_code());
|
||||
}
|
||||
|
||||
TEST_F(CasLicenseTest, HandleMultiContentEntitlementResponse) {
|
||||
strict_mock_ = std::make_shared<StrictMockCryptoSession>();
|
||||
TestCasLicense cas_license;
|
||||
EXPECT_CALL(*cas_license.policy_engine_, initialize(_, _));
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
cas_license.initialize(strict_mock_, nullptr).status_code());
|
||||
EXPECT_CALL(*strict_mock_, GetOEMPublicCertificate(_, _))
|
||||
.WillOnce(Return(wvcas::CasStatusCode::kCryptoSessionError));
|
||||
EXPECT_CALL(*strict_mock_, APIVersion(_))
|
||||
.WillOnce(Return(wvcas::CasStatusCode::kNoError));
|
||||
EXPECT_CALL(*strict_mock_, GenerateNonce(_))
|
||||
.WillOnce(Return(wvcas::CasStatusCode::kNoError));
|
||||
EXPECT_CALL(*strict_mock_,
|
||||
PrepareAndSignLicenseRequest(_, NotNull(), NotNull(), _, _))
|
||||
.WillOnce(DoAll(SetArgPointee<2>(kExpectedSignature),
|
||||
SetArgReferee<3>(false),
|
||||
Return(wvcas::CasStatusCode::kNoError)));
|
||||
EXPECT_CALL(*strict_mock_, LoadDeviceRSAKey(_, _));
|
||||
|
||||
std::string serialized_entitlement_request;
|
||||
wvcas::CasStatus status = cas_license.GenerateEntitlementRequest(
|
||||
kInitializationData, device_certificate_, wrapped_rsa_key_,
|
||||
wvcas::LicenseType::kStreaming, &serialized_entitlement_request);
|
||||
EXPECT_EQ(wvcas::CasStatusCode::kNoError, status.status_code());
|
||||
|
||||
// Create multi content entitlement response.
|
||||
video_widevine::LicenseCategorySpec license_category_spec;
|
||||
license_category_spec.set_license_category(
|
||||
video_widevine::LicenseCategorySpec::MULTI_CONTENT_LICENSE);
|
||||
license_category_spec.set_group_id("group_id");
|
||||
video_widevine::License license;
|
||||
*license.mutable_license_category_spec() = license_category_spec;
|
||||
video_widevine::License::KeyContainer::KeyCategorySpec key_category_spec;
|
||||
key_category_spec.set_content_id("content_id_1");
|
||||
auto* key = license.add_key();
|
||||
key->set_type(video_widevine::License_KeyContainer::ENTITLEMENT);
|
||||
*key->mutable_key_category_spec() = key_category_spec;
|
||||
key = license.add_key();
|
||||
key->set_type(video_widevine::License_KeyContainer::ENTITLEMENT);
|
||||
*key->mutable_key_category_spec() = key_category_spec;
|
||||
key = license.add_key();
|
||||
key->set_type(video_widevine::License_KeyContainer::ENTITLEMENT);
|
||||
key_category_spec.set_content_id("content_id_2");
|
||||
*key->mutable_key_category_spec() = key_category_spec;
|
||||
|
||||
video_widevine::SignedMessage signed_message;
|
||||
license.SerializeToString(signed_message.mutable_msg());
|
||||
signed_message.set_type(video_widevine::SignedMessage::CAS_LICENSE);
|
||||
signed_message.set_signature(kExpectedSignature);
|
||||
signed_message.set_session_key(kExpectedSignature);
|
||||
signed_message.set_oemcrypto_core_message(kCoreMessage);
|
||||
std::string entitlement_response = signed_message.SerializeAsString();
|
||||
|
||||
EXPECT_CALL(*strict_mock_, DeriveKeysFromSessionKey(_, _, _, _, _, _))
|
||||
.WillRepeatedly(Return(wvcas::CasStatusCode::kNoError));
|
||||
EXPECT_CALL(*strict_mock_, LoadLicense(_, _, _))
|
||||
.WillRepeatedly(Return(wvcas::CasStatusCode::kNoError));
|
||||
EXPECT_CALL(*cas_license.policy_engine_, SetLicense(_));
|
||||
EXPECT_CALL(*cas_license.policy_engine_, CanPersist()).WillOnce(Return(true));
|
||||
|
||||
status = cas_license.HandleEntitlementResponse(entitlement_response,
|
||||
/*device_file=*/nullptr);
|
||||
EXPECT_EQ(status.status_code(), wvcas::CasStatusCode::kNoError);
|
||||
// Not a group license.
|
||||
EXPECT_EQ(cas_license.GetGroupId(), "group_id");
|
||||
EXPECT_THAT(cas_license.GetContentIdList(),
|
||||
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
|
||||
25
tests/src/cas_session_map_test.cpp
Normal file
25
tests/src/cas_session_map_test.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "widevine_cas_session.h"
|
||||
#include "widevine_cas_session_map.h"
|
||||
|
||||
using wvcas::CasSessionPtr;
|
||||
using wvcas::WidevineCasSession;
|
||||
using wvcas::WidevineCasSessionMap;
|
||||
|
||||
TEST(WidevineCasSessionMap, HappyPath) {
|
||||
std::vector<uint8_t> base_key = {0x01, 0x02, 0x03};
|
||||
WidevineCasSessionMap& map = WidevineCasSessionMap::instance();
|
||||
CasSessionPtr session = std::make_shared<WidevineCasSession>();
|
||||
|
||||
EXPECT_TRUE(map.AddSession(base_key, session));
|
||||
EXPECT_FALSE(map.AddSession(base_key, session));
|
||||
|
||||
EXPECT_EQ(map.GetSession(base_key).get(), session.get());
|
||||
|
||||
map.RemoveSession(base_key);
|
||||
EXPECT_EQ(map.GetSession(base_key).get(), nullptr);
|
||||
}
|
||||
1057
tests/src/crypto_session_test.cpp
Normal file
1057
tests/src/crypto_session_test.cpp
Normal file
File diff suppressed because it is too large
Load Diff
130
tests/src/ecm_parser_test.cpp
Normal file
130
tests/src/ecm_parser_test.cpp
Normal file
@@ -0,0 +1,130 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "ecm_parser.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <tuple>
|
||||
|
||||
#include "media_cas.pb.h"
|
||||
|
||||
namespace wvcas {
|
||||
namespace {
|
||||
|
||||
constexpr int kCasIdSizeBytes = 2;
|
||||
constexpr int kVersionSizeBytes = 1;
|
||||
constexpr int kEcmHeaderSizeBytes = kCasIdSizeBytes + kVersionSizeBytes;
|
||||
constexpr int kEcmVersion2 = 2;
|
||||
constexpr int kEcmVersion3 = 3;
|
||||
|
||||
constexpr size_t kValidEcmV2SizeBytes = 165;
|
||||
|
||||
constexpr uint16_t kSectionHeader1 = 0x80;
|
||||
constexpr uint16_t kSectionHeader2 = 0x81;
|
||||
constexpr uint8_t kPointerFieldZero = 0x00;
|
||||
|
||||
constexpr uint16_t kWidevineCasId = 0x4AD4;
|
||||
// New Widevine CAS IDs 0x56C0 to 0x56C9 (all inclusive).
|
||||
constexpr uint16_t kWidevineNewCasIdLowerBound = 0x56C0;
|
||||
constexpr uint16_t kWidevineNewCasIdUpperBound = 0x56C9;
|
||||
|
||||
std::vector<uint8_t> BuildEcm(uint16_t cas_id, uint8_t version) {
|
||||
std::vector<uint8_t> ecm_data;
|
||||
ecm_data.resize(kEcmHeaderSizeBytes);
|
||||
ecm_data[0] = cas_id >> 8;
|
||||
ecm_data[1] = cas_id & 0xff;
|
||||
ecm_data[2] = version;
|
||||
|
||||
// Put some dummy data to make the ECM a valid one.
|
||||
if (version <= 2) {
|
||||
ecm_data.resize(kValidEcmV2SizeBytes);
|
||||
} else {
|
||||
video_widevine::EcmPayload ecm_payload;
|
||||
ecm_payload.mutable_even_key_data()->set_entitlement_key_id("123");
|
||||
video_widevine::SignedEcmPayload signed_ecm_payload;
|
||||
signed_ecm_payload.set_serialized_payload(ecm_payload.SerializeAsString());
|
||||
ecm_data.resize(ecm_data.size() + signed_ecm_payload.ByteSize());
|
||||
signed_ecm_payload.SerializeToArray(ecm_data.data() + kEcmHeaderSizeBytes,
|
||||
signed_ecm_payload.ByteSize());
|
||||
}
|
||||
return ecm_data;
|
||||
}
|
||||
|
||||
// Verifies ECM parser can be created with different version.
|
||||
class EcmParserVersionTest : public testing::Test,
|
||||
public ::testing::WithParamInterface<uint8_t> {};
|
||||
|
||||
TEST_P(EcmParserVersionTest, CreateSuccess) {
|
||||
std::vector<uint8_t> ecm_data = BuildEcm(kWidevineCasId, GetParam());
|
||||
ASSERT_TRUE(wvcas::EcmParser::Create(ecm_data) != nullptr);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(EcmParserVersionTest, EcmParserVersionTest,
|
||||
::testing::Values(kEcmVersion2, kEcmVersion3));
|
||||
|
||||
// Verifies CAS ID returned by the parser must be expected ones.
|
||||
class EcmParserCasIdTest
|
||||
: public testing::Test,
|
||||
public ::testing::WithParamInterface<::testing::tuple<uint16_t, bool>> {};
|
||||
|
||||
TEST_P(EcmParserCasIdTest, ValidateCasIds) {
|
||||
const uint16_t cas_id = ::testing::get<0>(GetParam());
|
||||
std::vector<uint8_t> ecm_data = BuildEcm(cas_id, kEcmVersion2);
|
||||
|
||||
const bool is_valid_id = ::testing::get<1>(GetParam());
|
||||
if (is_valid_id) {
|
||||
ASSERT_TRUE(wvcas::EcmParser::Create(ecm_data) != nullptr);
|
||||
} else {
|
||||
ASSERT_TRUE(wvcas::EcmParser::Create(ecm_data) == nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(EcmWithLegacyCasId, EcmParserCasIdTest,
|
||||
::testing::Values(std::make_tuple(kWidevineCasId,
|
||||
true)));
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
EcmWithNewCasId, EcmParserCasIdTest,
|
||||
::testing::Combine(
|
||||
::testing::Range(static_cast<uint16_t>(kWidevineNewCasIdLowerBound),
|
||||
static_cast<uint16_t>(kWidevineNewCasIdUpperBound +
|
||||
1)),
|
||||
::testing::Values(true)));
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
EcmWithInvalidCasId, EcmParserCasIdTest,
|
||||
::testing::Combine(::testing::Values(0, kWidevineCasId - 1,
|
||||
kWidevineCasId + 1,
|
||||
kWidevineNewCasIdLowerBound - 1,
|
||||
kWidevineNewCasIdUpperBound + 1),
|
||||
::testing::Values(false)));
|
||||
|
||||
// Verifies Section header and pointer field may be prepended to ECM.
|
||||
class EcmParserSectionHeaderTest
|
||||
: public testing::Test,
|
||||
public ::testing::WithParamInterface<uint8_t> {};
|
||||
|
||||
TEST_P(EcmParserSectionHeaderTest, EcmWithSectionHeaderOnly) {
|
||||
std::vector<uint8_t> ecm_data = BuildEcm(kWidevineCasId, kEcmVersion2);
|
||||
const std::vector<uint8_t> section_header = {GetParam(), 0, 0};
|
||||
ecm_data.insert(ecm_data.begin(), section_header.begin(),
|
||||
section_header.end());
|
||||
ASSERT_TRUE(wvcas::EcmParser::Create(ecm_data) != nullptr);
|
||||
}
|
||||
|
||||
TEST_P(EcmParserSectionHeaderTest, EcmWithSectionHeaderAndPointerField) {
|
||||
std::vector<uint8_t> ecm_data = BuildEcm(kWidevineCasId, kEcmVersion2);
|
||||
const std::vector<uint8_t> section_header = {kPointerFieldZero, GetParam(), 0,
|
||||
0};
|
||||
ecm_data.insert(ecm_data.begin(), section_header.begin(),
|
||||
section_header.end());
|
||||
ASSERT_TRUE(wvcas::EcmParser::Create(ecm_data) != nullptr);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(EcmWithSectionHeader, EcmParserSectionHeaderTest,
|
||||
::testing::Values(kSectionHeader1, kSectionHeader2));
|
||||
|
||||
} // namespace
|
||||
} // namespace wvcas
|
||||
270
tests/src/ecm_parser_v2_test.cpp
Normal file
270
tests/src/ecm_parser_v2_test.cpp
Normal file
@@ -0,0 +1,270 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "ecm_parser_v2.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kCasIdSizeBytes = 2;
|
||||
constexpr int kModeSizeBytes = 1;
|
||||
constexpr int kVersionSizeBytes = 1;
|
||||
constexpr int kIVFlagsSizeBytes = 1;
|
||||
constexpr int kEntitlementKeyIDSizeBytes = 16;
|
||||
constexpr int kContentKeyIDSizeBytes = 16;
|
||||
constexpr int kContentKeyDataSize = 16;
|
||||
constexpr int kWrappedKeyIVSizeBytes = 16;
|
||||
|
||||
constexpr int kEcmDescriptorSizeBytes =
|
||||
kCasIdSizeBytes + kModeSizeBytes + kVersionSizeBytes + kIVFlagsSizeBytes;
|
||||
|
||||
constexpr int kECMVersion = 2;
|
||||
// The cipher mode flags field in the ECM V2 is 4 bits.
|
||||
constexpr uint8_t kAESCBCCryptoModeFlagsVal = (0x0 << 1);
|
||||
constexpr uint8_t kAESCTRCryptoModeFlagsVal = (0x1 << 1);
|
||||
constexpr uint8_t kDvbCsa2CryptoModeFlagsVal = (0x2 << 1);
|
||||
constexpr uint8_t kDvbCsa3CryptoModeFlagsVal = (0x3 << 1);
|
||||
constexpr uint8_t kDvbOFBCryptoModeFlagsVal = (0x4 << 1);
|
||||
constexpr uint8_t kDvbSCTECryptoModeFlagsVal = (0x5 << 1);
|
||||
constexpr uint8_t kRotationFlag = (0x1 << 0);
|
||||
constexpr uint8_t kContentIVSizeFlag = (0x1 << 6);
|
||||
|
||||
constexpr uint8_t kEntitlementKeyIDFill = '1';
|
||||
constexpr uint8_t kEvenContentKeyIDFill = '2';
|
||||
constexpr uint8_t kEvenContentKeyDataFill = '3';
|
||||
constexpr uint8_t kEvenWrappedKeyIVFill = '4';
|
||||
constexpr uint8_t kEvenContentKeyIVFill = '5';
|
||||
constexpr uint8_t kOddContentKeyIDFill = '6';
|
||||
constexpr uint8_t kOddContentKeyDataFill = '7';
|
||||
constexpr uint8_t kOddWrappedKeyIVFill = '8';
|
||||
constexpr uint8_t kOddContentKeyIVFill = '9';
|
||||
|
||||
constexpr size_t kMaxEcmSizeBytes = 184;
|
||||
constexpr uint16_t kWidevineCasId = 0x4AD4;
|
||||
|
||||
} // namespace
|
||||
|
||||
class EcmParserV2Test : public testing::Test {
|
||||
protected:
|
||||
void SetUp() { BuildEcm(/*with_rotation=*/true, /*content_iv_flag=*/false); }
|
||||
size_t ContentKeyIVSize(bool content_iv_flag);
|
||||
size_t CalculateEcmSize(bool with_rotation, bool content_iv_flag = false);
|
||||
void BuildEcm(bool with_rotation, bool content_iv_flag);
|
||||
|
||||
std::vector<uint8_t> ecm_data_;
|
||||
std::unique_ptr<wvcas::EcmParserV2> parser_;
|
||||
};
|
||||
|
||||
size_t EcmParserV2Test::ContentKeyIVSize(bool content_iv_flag) {
|
||||
// Content key iv is 8 bytes if Content_IV flag is zero, and 16 bytes
|
||||
// othersize.
|
||||
return content_iv_flag ? 16 : 8;
|
||||
}
|
||||
|
||||
size_t EcmParserV2Test::CalculateEcmSize(bool with_rotation,
|
||||
bool content_iv_flag) {
|
||||
size_t ecm_key_data_size =
|
||||
kContentKeyIDSizeBytes + kContentKeyDataSize + kWrappedKeyIVSizeBytes +
|
||||
kEntitlementKeyIDSizeBytes + ContentKeyIVSize(content_iv_flag);
|
||||
return kEcmDescriptorSizeBytes + ecm_key_data_size * (with_rotation ? 2 : 1);
|
||||
}
|
||||
|
||||
void EcmParserV2Test::BuildEcm(bool with_rotation, bool content_iv_flag) {
|
||||
ecm_data_.clear();
|
||||
ecm_data_.reserve(CalculateEcmSize(with_rotation, content_iv_flag));
|
||||
ecm_data_.resize(kCasIdSizeBytes, 0);
|
||||
ecm_data_[0] = kWidevineCasId >> 8;
|
||||
ecm_data_[1] = kWidevineCasId & 0xff;
|
||||
ecm_data_.resize(ecm_data_.size() + kVersionSizeBytes, kECMVersion);
|
||||
ecm_data_.resize(ecm_data_.size() + kModeSizeBytes,
|
||||
kAESCBCCryptoModeFlagsVal);
|
||||
uint8_t iv_flag_value = content_iv_flag ? kContentIVSizeFlag : 0;
|
||||
ecm_data_.resize(ecm_data_.size() + kIVFlagsSizeBytes, iv_flag_value);
|
||||
ASSERT_EQ(kEcmDescriptorSizeBytes, ecm_data_.size());
|
||||
|
||||
// Even key fields.
|
||||
ecm_data_.resize(ecm_data_.size() + kEntitlementKeyIDSizeBytes,
|
||||
kEntitlementKeyIDFill);
|
||||
ecm_data_.resize(ecm_data_.size() + kContentKeyIDSizeBytes,
|
||||
kEvenContentKeyIDFill);
|
||||
ecm_data_.resize(ecm_data_.size() + kContentKeyDataSize,
|
||||
kEvenContentKeyDataFill);
|
||||
ecm_data_.resize(ecm_data_.size() + kWrappedKeyIVSizeBytes,
|
||||
kEvenWrappedKeyIVFill);
|
||||
ecm_data_.resize(ecm_data_.size() + ContentKeyIVSize(content_iv_flag),
|
||||
kEvenContentKeyIVFill);
|
||||
ASSERT_EQ(CalculateEcmSize(false, content_iv_flag), ecm_data_.size());
|
||||
|
||||
if (with_rotation) {
|
||||
// Entitlement key id field for odd key.
|
||||
ecm_data_.resize(ecm_data_.size() + kEntitlementKeyIDSizeBytes,
|
||||
kEntitlementKeyIDFill);
|
||||
ecm_data_.resize(ecm_data_.size() + kContentKeyIDSizeBytes,
|
||||
kOddContentKeyIDFill);
|
||||
ecm_data_.resize(ecm_data_.size() + kContentKeyDataSize,
|
||||
kOddContentKeyDataFill);
|
||||
ecm_data_.resize(ecm_data_.size() + kWrappedKeyIVSizeBytes,
|
||||
kOddWrappedKeyIVFill);
|
||||
ecm_data_.resize(ecm_data_.size() + ContentKeyIVSize(content_iv_flag),
|
||||
kOddContentKeyIVFill);
|
||||
ASSERT_EQ(CalculateEcmSize(true, content_iv_flag), ecm_data_.size());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(EcmParserV2Test, FieldsWithoutKeyRotation) {
|
||||
bool content_key_iv_16b = false;
|
||||
ecm_data_.resize(CalculateEcmSize(false, content_key_iv_16b));
|
||||
ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
ASSERT_FALSE(parser_->rotation_enabled());
|
||||
|
||||
std::vector<uint8_t> test_data;
|
||||
test_data.resize(kEntitlementKeyIDSizeBytes, kEntitlementKeyIDFill);
|
||||
EXPECT_EQ(test_data,
|
||||
parser_->entitlement_key_id(wvcas::KeySlotId::kEvenKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
test_data.resize(kContentKeyIDSizeBytes, kEvenContentKeyIDFill);
|
||||
EXPECT_EQ(test_data, parser_->content_key_id(wvcas::KeySlotId::kEvenKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
test_data.resize(kContentKeyIDSizeBytes, kEvenContentKeyDataFill);
|
||||
EXPECT_EQ(test_data,
|
||||
parser_->wrapped_key_data(wvcas::KeySlotId::kEvenKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
test_data.resize(kWrappedKeyIVSizeBytes, kEvenWrappedKeyIVFill);
|
||||
EXPECT_EQ(test_data, parser_->wrapped_key_iv(wvcas::KeySlotId::kEvenKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
test_data.resize(ContentKeyIVSize(content_key_iv_16b), kEvenContentKeyIVFill);
|
||||
EXPECT_EQ(test_data, parser_->content_iv(wvcas::KeySlotId::kEvenKeySlot));
|
||||
|
||||
EXPECT_TRUE(parser_->content_key_id(wvcas::KeySlotId::kOddKeySlot).empty());
|
||||
EXPECT_TRUE(parser_->wrapped_key_data(wvcas::KeySlotId::kOddKeySlot).empty());
|
||||
EXPECT_TRUE(parser_->wrapped_key_iv(wvcas::KeySlotId::kOddKeySlot).empty());
|
||||
EXPECT_TRUE(parser_->content_iv(wvcas::KeySlotId::kOddKeySlot).empty());
|
||||
}
|
||||
|
||||
TEST_F(EcmParserV2Test, FieldsWithKeyRotation) {
|
||||
ecm_data_[3] |= kRotationFlag;
|
||||
ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
ASSERT_TRUE(parser_->rotation_enabled());
|
||||
|
||||
std::vector<uint8_t> test_data;
|
||||
test_data.resize(kEntitlementKeyIDSizeBytes, kEntitlementKeyIDFill);
|
||||
EXPECT_EQ(test_data,
|
||||
parser_->entitlement_key_id(wvcas::KeySlotId::kEvenKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
test_data.resize(kContentKeyIDSizeBytes, kEvenContentKeyIDFill);
|
||||
EXPECT_EQ(test_data, parser_->content_key_id(wvcas::KeySlotId::kEvenKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
test_data.resize(kContentKeyIDSizeBytes, kEvenContentKeyDataFill);
|
||||
EXPECT_EQ(test_data,
|
||||
parser_->wrapped_key_data(wvcas::KeySlotId::kEvenKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
test_data.resize(kWrappedKeyIVSizeBytes, kEvenWrappedKeyIVFill);
|
||||
EXPECT_EQ(test_data, parser_->wrapped_key_iv(wvcas::KeySlotId::kEvenKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
bool content_key_iv_16b = false;
|
||||
test_data.resize(ContentKeyIVSize(content_key_iv_16b), kEvenContentKeyIVFill);
|
||||
EXPECT_EQ(test_data, parser_->content_iv(wvcas::KeySlotId::kEvenKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
test_data.resize(kContentKeyIDSizeBytes, kOddContentKeyIDFill);
|
||||
EXPECT_EQ(test_data, parser_->content_key_id(wvcas::KeySlotId::kOddKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
test_data.resize(kContentKeyIDSizeBytes, kOddContentKeyDataFill);
|
||||
EXPECT_EQ(test_data,
|
||||
parser_->wrapped_key_data(wvcas::KeySlotId::kOddKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
test_data.resize(kWrappedKeyIVSizeBytes, kOddWrappedKeyIVFill);
|
||||
EXPECT_EQ(test_data, parser_->wrapped_key_iv(wvcas::KeySlotId::kOddKeySlot));
|
||||
|
||||
test_data.clear();
|
||||
test_data.resize(ContentKeyIVSize(content_key_iv_16b), kOddContentKeyIVFill);
|
||||
EXPECT_EQ(test_data, parser_->content_iv(wvcas::KeySlotId::kOddKeySlot));
|
||||
}
|
||||
|
||||
TEST_F(EcmParserV2Test, create) {
|
||||
EXPECT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
|
||||
ecm_data_.resize(4);
|
||||
EXPECT_FALSE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
|
||||
ecm_data_.resize(4 + CalculateEcmSize(false));
|
||||
EXPECT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
|
||||
ecm_data_.resize(kMaxEcmSizeBytes);
|
||||
EXPECT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
|
||||
ecm_data_.resize(CalculateEcmSize(true));
|
||||
EXPECT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
EXPECT_FALSE(wvcas::EcmParserV2::create(ecm_data_, nullptr));
|
||||
|
||||
ecm_data_.resize(CalculateEcmSize(true));
|
||||
EXPECT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
EXPECT_FALSE(wvcas::EcmParserV2::create(ecm_data_, nullptr));
|
||||
}
|
||||
|
||||
TEST_F(EcmParserV2Test, crypto_mode) {
|
||||
ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kAesCBC);
|
||||
|
||||
ecm_data_[3] = kAESCTRCryptoModeFlagsVal;
|
||||
ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kAesCTR);
|
||||
|
||||
ecm_data_[3] = kDvbCsa2CryptoModeFlagsVal;
|
||||
ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kDvbCsa2);
|
||||
|
||||
ecm_data_[3] = kDvbCsa3CryptoModeFlagsVal;
|
||||
ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kDvbCsa3);
|
||||
|
||||
ecm_data_[3] = kDvbOFBCryptoModeFlagsVal;
|
||||
ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kAesOFB);
|
||||
|
||||
ecm_data_[3] = kDvbSCTECryptoModeFlagsVal;
|
||||
ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
EXPECT_EQ(parser_->crypto_mode(), wvcas::CryptoMode::kAesSCTE);
|
||||
}
|
||||
|
||||
TEST_F(EcmParserV2Test, ContentKeyIVSizes) {
|
||||
bool with_rotation = true;
|
||||
bool iv_flag = false;
|
||||
ecm_data_.resize(CalculateEcmSize(with_rotation, iv_flag));
|
||||
ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
EXPECT_EQ(parser_->content_iv_size(), ContentKeyIVSize(iv_flag));
|
||||
|
||||
iv_flag = true;
|
||||
ecm_data_[4] = kContentIVSizeFlag;
|
||||
ecm_data_.resize(CalculateEcmSize(with_rotation, iv_flag));
|
||||
ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
EXPECT_EQ(parser_->content_iv_size(), ContentKeyIVSize(iv_flag));
|
||||
}
|
||||
|
||||
TEST_F(EcmParserV2Test, AgeRestriction) {
|
||||
ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
EXPECT_EQ(0, parser_->age_restriction());
|
||||
|
||||
uint8_t age_restriction = 16;
|
||||
ecm_data_[4] |= age_restriction << 1;
|
||||
ASSERT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
EXPECT_EQ(age_restriction, parser_->age_restriction());
|
||||
}
|
||||
|
||||
TEST_F(EcmParserV2Test, Version) {
|
||||
EXPECT_TRUE(wvcas::EcmParserV2::create(ecm_data_, &parser_));
|
||||
EXPECT_EQ(parser_->version(), kECMVersion);
|
||||
}
|
||||
443
tests/src/ecm_parser_v3_test.cpp
Normal file
443
tests/src/ecm_parser_v3_test.cpp
Normal file
@@ -0,0 +1,443 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "ecm_parser_v3.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "media_cas.pb.h"
|
||||
|
||||
namespace wvcas {
|
||||
namespace {
|
||||
|
||||
using video_widevine::EcmGroupKeyData;
|
||||
using video_widevine::EcmMetaData;
|
||||
using video_widevine::EcmPayload;
|
||||
using video_widevine::SignedEcmPayload;
|
||||
|
||||
constexpr int kEcmVersion = 3;
|
||||
constexpr uint16_t kWidevineCasId = 0x4AD4;
|
||||
constexpr int kEcmHeaderSizeByte = 3;
|
||||
|
||||
constexpr char kWrappedKeyIv[] = "wrapped_key_iv..";
|
||||
constexpr char kWrappedKeyIv2[] = "wrapped_key_iv.2";
|
||||
constexpr char kEntitlementId[] = "entitlement_id..";
|
||||
constexpr char kEntitlementId2[] = "entitlement_id.2";
|
||||
constexpr char kContentIv[] = "c_iv....c_iv....";
|
||||
constexpr char kContentIv2[] = "c_iv....c_iv...2";
|
||||
constexpr char kWrappedContentKey[] = "wrapped_key.....";
|
||||
constexpr char kWrappedContentKey2[] = "wrapped_key....2";
|
||||
|
||||
void WriteEcmHeader(std::vector<uint8_t>* ecm) {
|
||||
ecm->push_back(kWidevineCasId >> 8);
|
||||
ecm->push_back(kWidevineCasId & 0xff);
|
||||
ecm->push_back(kEcmVersion);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> GenerateEcm(const SignedEcmPayload& signed_ecm_payload) {
|
||||
std::vector<uint8_t> ecm;
|
||||
WriteEcmHeader(&ecm);
|
||||
ecm.resize(kEcmHeaderSizeByte + signed_ecm_payload.ByteSize());
|
||||
signed_ecm_payload.SerializeToArray(ecm.data() + kEcmHeaderSizeByte,
|
||||
signed_ecm_payload.ByteSize());
|
||||
return ecm;
|
||||
}
|
||||
|
||||
TEST(EcmParserV3Test, CreateWithEmptyEcmFail) {
|
||||
std::vector<uint8_t> ecm;
|
||||
EXPECT_TRUE(EcmParserV3::Create(ecm) == nullptr);
|
||||
}
|
||||
|
||||
TEST(EcmParserV3Test, CreateWithOnlyEcmHeaderFail) {
|
||||
std::vector<uint8_t> ecm;
|
||||
WriteEcmHeader(&ecm);
|
||||
EXPECT_TRUE(EcmParserV3::Create(ecm) == nullptr);
|
||||
}
|
||||
|
||||
TEST(EcmParserV3Test, CreateWithInvalidSignedEcmPayloadFail) {
|
||||
std::vector<uint8_t> ecm;
|
||||
WriteEcmHeader(&ecm);
|
||||
// appends some chars as payload
|
||||
ecm.resize(100, 'x');
|
||||
|
||||
EXPECT_TRUE(EcmParserV3::Create(ecm) == nullptr);
|
||||
}
|
||||
|
||||
TEST(EcmParserV3Test, CreateWithInvalidSerializedEcmFail) {
|
||||
SignedEcmPayload signed_ecm_payload;
|
||||
signed_ecm_payload.set_serialized_payload("invalid");
|
||||
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
||||
|
||||
EXPECT_TRUE(EcmParserV3::Create(ecm) == nullptr);
|
||||
}
|
||||
|
||||
TEST(EcmParserV3Test, CreateWithEvenKeySuccess) {
|
||||
EcmPayload ecm_payload;
|
||||
ecm_payload.mutable_even_key_data()->set_entitlement_key_id(kEntitlementId);
|
||||
ecm_payload.mutable_even_key_data()->set_wrapped_key_data(kWrappedContentKey);
|
||||
ecm_payload.mutable_even_key_data()->set_content_iv(kContentIv);
|
||||
ecm_payload.mutable_even_key_data()->set_wrapped_key_iv(kWrappedKeyIv);
|
||||
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_EQ(parser->version(), kEcmVersion);
|
||||
EXPECT_EQ(parser->age_restriction(), 0);
|
||||
EXPECT_EQ(parser->crypto_mode(), CryptoMode::kInvalid);
|
||||
EXPECT_FALSE(parser->has_fingerprinting());
|
||||
EXPECT_FALSE(parser->has_service_blocking());
|
||||
EXPECT_EQ(parser->ecm_serialized_payload(), ecm_payload.SerializeAsString());
|
||||
EXPECT_TRUE(parser->signature().empty());
|
||||
EXPECT_FALSE(parser->rotation_enabled());
|
||||
EXPECT_EQ(parser->content_iv_size(), 16);
|
||||
std::vector<uint8_t> result =
|
||||
parser->entitlement_key_id(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId);
|
||||
result = parser->wrapped_key_data(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey);
|
||||
result = parser->content_iv(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv);
|
||||
result = parser->wrapped_key_iv(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv);
|
||||
}
|
||||
|
||||
TEST(EcmParserV3Test, CreateWithEvenOddKeysSuccess) {
|
||||
EcmPayload ecm_payload;
|
||||
ecm_payload.mutable_even_key_data()->set_entitlement_key_id(kEntitlementId);
|
||||
ecm_payload.mutable_even_key_data()->set_wrapped_key_data(kWrappedContentKey);
|
||||
ecm_payload.mutable_even_key_data()->set_content_iv(kContentIv);
|
||||
ecm_payload.mutable_even_key_data()->set_wrapped_key_iv(kWrappedKeyIv);
|
||||
ecm_payload.mutable_odd_key_data()->set_entitlement_key_id(kEntitlementId2);
|
||||
ecm_payload.mutable_odd_key_data()->set_wrapped_key_data(kWrappedContentKey2);
|
||||
ecm_payload.mutable_odd_key_data()->set_content_iv(kContentIv2);
|
||||
ecm_payload.mutable_odd_key_data()->set_wrapped_key_iv(kWrappedKeyIv2);
|
||||
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->rotation_enabled());
|
||||
std::vector<uint8_t> result =
|
||||
parser->entitlement_key_id(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId);
|
||||
result = parser->wrapped_key_data(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey);
|
||||
result = parser->content_iv(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv);
|
||||
result = parser->wrapped_key_iv(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv);
|
||||
result = parser->entitlement_key_id(KeySlotId::kOddKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId2);
|
||||
result = parser->wrapped_key_data(KeySlotId::kOddKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey2);
|
||||
result = parser->content_iv(KeySlotId::kOddKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv2);
|
||||
result = parser->wrapped_key_iv(KeySlotId::kOddKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv2);
|
||||
}
|
||||
|
||||
TEST(EcmParserV3Test, CreateWithOmittedOddKeyFieldsSuccess) {
|
||||
EcmPayload ecm_payload;
|
||||
ecm_payload.mutable_even_key_data()->set_entitlement_key_id(kEntitlementId);
|
||||
ecm_payload.mutable_even_key_data()->set_wrapped_key_data(kWrappedContentKey);
|
||||
ecm_payload.mutable_even_key_data()->set_content_iv(kContentIv);
|
||||
ecm_payload.mutable_even_key_data()->set_wrapped_key_iv(kWrappedKeyIv);
|
||||
ecm_payload.mutable_odd_key_data()->set_wrapped_key_data(kWrappedContentKey2);
|
||||
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->rotation_enabled());
|
||||
std::vector<uint8_t> result =
|
||||
parser->entitlement_key_id(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId);
|
||||
result = parser->wrapped_key_data(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey);
|
||||
result = parser->content_iv(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv);
|
||||
result = parser->wrapped_key_iv(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv);
|
||||
result = parser->entitlement_key_id(KeySlotId::kOddKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId);
|
||||
result = parser->wrapped_key_data(KeySlotId::kOddKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey2);
|
||||
result = parser->content_iv(KeySlotId::kOddKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv);
|
||||
result = parser->wrapped_key_iv(KeySlotId::kOddKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv);
|
||||
}
|
||||
|
||||
TEST(EcmParserV3Test, AgeRestrictionSuccess) {
|
||||
const int expected_age_restriction = 3;
|
||||
EcmPayload ecm_payload;
|
||||
ecm_payload.mutable_meta_data()->set_age_restriction(
|
||||
expected_age_restriction);
|
||||
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_EQ(parser->age_restriction(), expected_age_restriction);
|
||||
}
|
||||
|
||||
class EcmParserV3AgeRestrictionTest
|
||||
: public testing::Test,
|
||||
public testing::WithParamInterface<uint8_t> {};
|
||||
|
||||
TEST_P(EcmParserV3AgeRestrictionTest, ExpectedAgeRestriction) {
|
||||
const uint8_t expected_age_restriction = GetParam();
|
||||
EcmPayload ecm_payload;
|
||||
ecm_payload.mutable_meta_data()->set_age_restriction(
|
||||
expected_age_restriction);
|
||||
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_EQ(parser->age_restriction(), expected_age_restriction);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(EcmParserV3AgeRestrictionTest,
|
||||
EcmParserV3AgeRestrictionTest,
|
||||
testing::Values(0, 3, 18));
|
||||
|
||||
class EcmParserV3CipherModeTest
|
||||
: public testing::Test,
|
||||
public testing::WithParamInterface<
|
||||
testing::tuple<CryptoMode, EcmMetaData::CipherMode>> {};
|
||||
|
||||
TEST_P(EcmParserV3CipherModeTest, ExpectedCipherMode) {
|
||||
const CryptoMode expected = std::get<0>(GetParam());
|
||||
EcmPayload ecm_payload;
|
||||
ecm_payload.mutable_meta_data()->set_cipher_mode(std::get<1>(GetParam()));
|
||||
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_EQ(parser->crypto_mode(), expected);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
EcmParserV3CipherModeTest, EcmParserV3CipherModeTest,
|
||||
testing::Values(
|
||||
std::make_tuple(CryptoMode::kAesCBC, EcmMetaData::AES_CBC),
|
||||
std::make_tuple(CryptoMode::kAesCTR, EcmMetaData::AES_CTR),
|
||||
std::make_tuple(CryptoMode::kDvbCsa2, EcmMetaData::DVB_CSA2),
|
||||
std::make_tuple(CryptoMode::kDvbCsa3, EcmMetaData::DVB_CSA3),
|
||||
std::make_tuple(CryptoMode::kAesOFB, EcmMetaData::AES_OFB),
|
||||
std::make_tuple(CryptoMode::kAesSCTE, EcmMetaData::AES_SCTE52),
|
||||
std::make_tuple(CryptoMode::kAesECB, EcmMetaData::AES_ECB)));
|
||||
|
||||
TEST(EcmParserV3Test, FingerprintingSuccess) {
|
||||
EcmPayload ecm_payload;
|
||||
ecm_payload.mutable_fingerprinting()->set_control("control");
|
||||
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->has_fingerprinting());
|
||||
EXPECT_EQ(parser->fingerprinting().SerializeAsString(),
|
||||
ecm_payload.fingerprinting().SerializeAsString());
|
||||
}
|
||||
|
||||
TEST(EcmParserV3Test, ServiceBlockingSuccess) {
|
||||
EcmPayload ecm_payload;
|
||||
ecm_payload.mutable_service_blocking()->add_device_groups("group");
|
||||
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->has_service_blocking());
|
||||
EXPECT_EQ(parser->service_blocking().SerializeAsString(),
|
||||
ecm_payload.service_blocking().SerializeAsString());
|
||||
}
|
||||
|
||||
TEST(EcmParserV3Test, SignatureSuccess) {
|
||||
const std::string expected_signature = "signature";
|
||||
SignedEcmPayload signed_ecm_payload;
|
||||
signed_ecm_payload.set_signature(expected_signature);
|
||||
std::vector<uint8_t> ecm = GenerateEcm(signed_ecm_payload);
|
||||
|
||||
std::unique_ptr<EcmParserV3> parser = EcmParserV3::Create(ecm);
|
||||
|
||||
ASSERT_TRUE(parser != nullptr);
|
||||
EXPECT_EQ(parser->signature(), expected_signature);
|
||||
}
|
||||
|
||||
TEST(EcmParserV3Test, SetGroupIdSuccess) {
|
||||
const std::string group_id = "group_id";
|
||||
const std::string group_id2 = "another_group";
|
||||
EcmPayload ecm_payload;
|
||||
EcmGroupKeyData* group_key_data = ecm_payload.add_group_key_data();
|
||||
group_key_data->set_group_id(group_id);
|
||||
group_key_data = ecm_payload.add_group_key_data();
|
||||
group_key_data->set_group_id(group_id2);
|
||||
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->set_group_id(group_id));
|
||||
EXPECT_TRUE(parser->set_group_id(group_id2));
|
||||
}
|
||||
|
||||
TEST(EcmParserV3Test, SetUnknownGroupIdFail) {
|
||||
EcmPayload ecm_payload;
|
||||
EcmGroupKeyData* group_key_data = ecm_payload.add_group_key_data();
|
||||
group_key_data->set_group_id("group_id");
|
||||
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->set_group_id("unknown"));
|
||||
}
|
||||
|
||||
TEST(EcmParserV3Test, ParserWithGroupIdSuccess) {
|
||||
const std::string group_id = "group_id";
|
||||
EcmPayload ecm_payload;
|
||||
EcmGroupKeyData* group_key_data = ecm_payload.add_group_key_data();
|
||||
group_key_data->set_group_id(group_id);
|
||||
group_key_data->mutable_even_key_data()->set_entitlement_key_id(
|
||||
kEntitlementId);
|
||||
group_key_data->mutable_even_key_data()->set_wrapped_key_data(
|
||||
kWrappedContentKey);
|
||||
group_key_data->mutable_even_key_data()->set_content_iv(kContentIv);
|
||||
group_key_data->mutable_even_key_data()->set_wrapped_key_iv(kWrappedKeyIv);
|
||||
ecm_payload.mutable_even_key_data()->set_entitlement_key_id(kEntitlementId2);
|
||||
ecm_payload.mutable_even_key_data()->set_wrapped_key_data(
|
||||
kWrappedContentKey2);
|
||||
ecm_payload.mutable_even_key_data()->set_content_iv(kContentIv2);
|
||||
ecm_payload.mutable_even_key_data()->set_wrapped_key_iv(kWrappedKeyIv2);
|
||||
|
||||
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);
|
||||
// If group Id is not set, the normal keys will be returned.
|
||||
std::vector<uint8_t> result =
|
||||
parser->entitlement_key_id(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId2);
|
||||
result = parser->wrapped_key_data(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey2);
|
||||
result = parser->content_iv(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv2);
|
||||
result = parser->wrapped_key_iv(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv2);
|
||||
|
||||
// Now set the group id.
|
||||
EXPECT_TRUE(parser->set_group_id(group_id));
|
||||
result = parser->entitlement_key_id(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId);
|
||||
result = parser->wrapped_key_data(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey);
|
||||
result = parser->content_iv(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv);
|
||||
result = parser->wrapped_key_iv(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedKeyIv);
|
||||
}
|
||||
|
||||
TEST(EcmParserV3Test, ParserGroupKeysWithOmittedFieldsSuccess) {
|
||||
const std::string group_id = "group_id";
|
||||
EcmPayload ecm_payload;
|
||||
EcmGroupKeyData* group_key_data = ecm_payload.add_group_key_data();
|
||||
group_key_data->set_group_id(group_id);
|
||||
group_key_data->mutable_even_key_data()->set_entitlement_key_id(
|
||||
kEntitlementId);
|
||||
group_key_data->mutable_even_key_data()->set_wrapped_key_data(
|
||||
kWrappedContentKey);
|
||||
// Content IV and wrapped key iv is omitted in |group_key_data|/
|
||||
ecm_payload.mutable_even_key_data()->set_entitlement_key_id(kEntitlementId2);
|
||||
ecm_payload.mutable_even_key_data()->set_wrapped_key_data(
|
||||
kWrappedContentKey2);
|
||||
ecm_payload.mutable_even_key_data()->set_content_iv(kContentIv2);
|
||||
ecm_payload.mutable_even_key_data()->set_wrapped_key_iv(kWrappedKeyIv2);
|
||||
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->set_group_id(group_id));
|
||||
|
||||
std::vector<uint8_t> result =
|
||||
parser->entitlement_key_id(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kEntitlementId);
|
||||
result = parser->wrapped_key_data(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kWrappedContentKey);
|
||||
// Content IV and wrapped key iv are from normal non-group key.
|
||||
result = parser->content_iv(KeySlotId::kEvenKeySlot);
|
||||
EXPECT_EQ(std::string(result.begin(), result.end()), kContentIv2);
|
||||
result = parser->wrapped_key_iv(KeySlotId::kEvenKeySlot);
|
||||
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
|
||||
133
tests/src/emm_parser_test.cpp
Normal file
133
tests/src/emm_parser_test.cpp
Normal file
@@ -0,0 +1,133 @@
|
||||
// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "emm_parser.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "cas_types.h"
|
||||
|
||||
namespace wvcas {
|
||||
namespace {
|
||||
|
||||
using video_widevine::EmmPayload;
|
||||
using video_widevine::SignedEmmPayload;
|
||||
|
||||
constexpr uint8_t kSectionHeader = 0x82;
|
||||
constexpr int64_t kDefaultTimestamp = 1598905921;
|
||||
constexpr char kDefaultSignature[] = "signature";
|
||||
|
||||
class EmmParserTest : public testing::Test {
|
||||
protected:
|
||||
EmmParserTest()
|
||||
: timestamp_(kDefaultTimestamp), signature_(kDefaultSignature) {}
|
||||
void SetSectionHeader(const std::vector<uint8_t> section_header) {
|
||||
section_header_.assign(section_header.begin(), section_header.end());
|
||||
}
|
||||
void SetTimestamp(uint64_t timestamp) { timestamp_ = timestamp; }
|
||||
void SetSignedEmm(const std::string& signed_emm) {
|
||||
serialized_signed_emm_ = signed_emm;
|
||||
}
|
||||
void SetEmmPayload(const std::string& serialized_payload) {
|
||||
serialized_emm_payload_ = serialized_payload;
|
||||
}
|
||||
void SetSignature(const std::string& signature) { signature_ = signature; }
|
||||
|
||||
std::vector<uint8_t> BuildEmm() const {
|
||||
std::vector<uint8_t> emm_data(section_header_.begin(),
|
||||
section_header_.end());
|
||||
if (!serialized_signed_emm_.empty()) {
|
||||
emm_data.insert(emm_data.end(), serialized_signed_emm_.begin(),
|
||||
serialized_signed_emm_.end());
|
||||
return emm_data;
|
||||
}
|
||||
|
||||
SignedEmmPayload signed_emm;
|
||||
if (serialized_emm_payload_.empty()) {
|
||||
EmmPayload emm_payload;
|
||||
emm_payload.set_timestamp_secs(timestamp_);
|
||||
emm_payload.SerializeToString(signed_emm.mutable_serialized_payload());
|
||||
} else {
|
||||
signed_emm.set_serialized_payload(serialized_emm_payload_);
|
||||
}
|
||||
signed_emm.set_signature(signature_);
|
||||
|
||||
emm_data.resize(emm_data.size() + signed_emm.ByteSizeLong());
|
||||
signed_emm.SerializeToArray(&emm_data[section_header_.size()],
|
||||
emm_data.size());
|
||||
return emm_data;
|
||||
}
|
||||
|
||||
void ValidateParserAgainstDefault(const EmmParser* const parser) {
|
||||
ASSERT_NE(parser, nullptr);
|
||||
EXPECT_EQ(parser->timestamp(), kDefaultTimestamp);
|
||||
EmmPayload expected_emm_payload;
|
||||
expected_emm_payload.set_timestamp_secs(timestamp_);
|
||||
EXPECT_EQ(parser->emm_payload().SerializeAsString(),
|
||||
expected_emm_payload.SerializeAsString());
|
||||
EXPECT_EQ(parser->signature(), kDefaultSignature);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> section_header_;
|
||||
uint64_t timestamp_;
|
||||
std::string signature_;
|
||||
std::string serialized_signed_emm_;
|
||||
std::string serialized_emm_payload_;
|
||||
};
|
||||
|
||||
TEST_F(EmmParserTest, ParseDefaultSuccess) {
|
||||
auto parser = EmmParser::Create(BuildEmm());
|
||||
ValidateParserAgainstDefault(parser.get());
|
||||
}
|
||||
|
||||
TEST_F(EmmParserTest, EmmWithSectionHeaderSuccess) {
|
||||
SetSectionHeader({kSectionHeader, 0, 0});
|
||||
auto parser = EmmParser::Create(BuildEmm());
|
||||
ValidateParserAgainstDefault(parser.get());
|
||||
}
|
||||
|
||||
TEST_F(EmmParserTest, EmmWithSectionHeaderAndPointerFieldSuccess) {
|
||||
SetSectionHeader({0, kSectionHeader, 0, 0});
|
||||
auto parser = EmmParser::Create(BuildEmm());
|
||||
ValidateParserAgainstDefault(parser.get());
|
||||
}
|
||||
|
||||
TEST_F(EmmParserTest, EmmWithMalformedEmmCreateFail) {
|
||||
SetSignedEmm("some emm");
|
||||
EXPECT_THAT(EmmParser::Create(BuildEmm()), ::testing::IsNull());
|
||||
}
|
||||
|
||||
TEST_F(EmmParserTest, EmmWithMalformedPayloadCreateFail) {
|
||||
SetEmmPayload("some payload");
|
||||
EXPECT_THAT(EmmParser::Create(BuildEmm()), ::testing::IsNull());
|
||||
}
|
||||
|
||||
TEST_F(EmmParserTest, EmmWithNoSignatureCreateFail) {
|
||||
SetSignature("");
|
||||
EXPECT_THAT(EmmParser::Create(BuildEmm()), ::testing::IsNull());
|
||||
}
|
||||
|
||||
class EmmParserWrongPrefixTest
|
||||
: public EmmParserTest,
|
||||
public ::testing::WithParamInterface<std::vector<uint8_t>> {};
|
||||
|
||||
TEST_P(EmmParserWrongPrefixTest, EmmWithWrongPrefixCreateFail) {
|
||||
SetSectionHeader(GetParam());
|
||||
EXPECT_THAT(EmmParser::Create(BuildEmm()), ::testing::IsNull());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
EmmParserWrongPrefixes, EmmParserWrongPrefixTest,
|
||||
::testing::Values(std::vector<uint8_t>({0}),
|
||||
std::vector<uint8_t>({1, 0, 0}),
|
||||
std::vector<uint8_t>({kSectionHeader, 0}),
|
||||
std::vector<uint8_t>({1, kSectionHeader, 0, 0})));
|
||||
|
||||
} // namespace
|
||||
} // namespace wvcas
|
||||
937
tests/src/license_key_status_test.cpp
Normal file
937
tests/src/license_key_status_test.cpp
Normal file
@@ -0,0 +1,937 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "license_key_status.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "cas_types.h"
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
namespace {
|
||||
|
||||
static const uint32_t dev_lo_res = 200;
|
||||
static const uint32_t dev_hi_res = 400;
|
||||
static const uint32_t dev_top_res = 800;
|
||||
|
||||
static const uint32_t key_lo_res_min = 151;
|
||||
static const uint32_t key_lo_res_max = 300;
|
||||
static const uint32_t key_hi_res_min = 301;
|
||||
static const uint32_t key_hi_res_max = 450;
|
||||
static const uint32_t key_top_res_min = 451;
|
||||
static const uint32_t key_top_res_max = 650;
|
||||
|
||||
// Content Keys
|
||||
static const KeyId ck_sw_crypto = "c_key_SW_SECURE_CRYPTO";
|
||||
static const KeyId ck_sw_decode = "c_key_SW_SECURE_DECODE";
|
||||
static const KeyId ck_hw_crypto = "c_key_HW_SECURE_CRYPTO";
|
||||
static const KeyId ck_hw_decode = "c_key_HW_SECURE_DECODE";
|
||||
static const KeyId ck_hw_secure = "c_key_HW_SECURE_ALL";
|
||||
|
||||
// Operator Session Keys
|
||||
static const KeyId osk_decrypt = "os_key_generic_decrypt";
|
||||
static const KeyId osk_encrypt = "os_key_generic_encrypt";
|
||||
static const KeyId osk_sign = "os_key_generic_sign";
|
||||
static const KeyId osk_verify = "os_key_generic_verify";
|
||||
static const KeyId osk_encrypt_decrypt = "os_key_generic_encrypt_decrypt";
|
||||
static const KeyId osk_sign_verify = "os_key_generic_sign_verify";
|
||||
static const KeyId osk_all = "os_key_generic_all";
|
||||
|
||||
// HDCP test keys
|
||||
static const KeyId ck_sw_crypto_NO_HDCP = "ck_sw_crypto_NO_HDCP";
|
||||
static const KeyId ck_hw_secure_NO_HDCP = "ck_hw_secure_NO_HDCP";
|
||||
static const KeyId ck_sw_crypto_HDCP_V2_1 = "ck_sw_crypto_HDCP_V2_1";
|
||||
static const KeyId ck_hw_secure_HDCP_V2_1 = "ck_hw_secure_HDCP_V2_1";
|
||||
static const KeyId ck_sw_crypto_HDCP_NO_OUTPUT = "ck_sw_crypto_HDCP_NO_OUT";
|
||||
static const KeyId ck_hw_secure_HDCP_NO_OUTPUT = "ck_hw_secure_HDCP_NO_OUT";
|
||||
|
||||
// Constraint test keys
|
||||
static const KeyId ck_NO_HDCP_lo_res = "ck_NO_HDCP_lo_res";
|
||||
static const KeyId ck_HDCP_NO_OUTPUT_hi_res = "ck_HDCP_NO_OUTPUT_hi_res";
|
||||
static const KeyId ck_HDCP_V2_1_max_res = "ck_HDCP_V2_1_max_res";
|
||||
static const KeyId ck_NO_HDCP_dual_res = "ck_NO_HDCP_dual_res";
|
||||
|
||||
} // namespace
|
||||
|
||||
// protobuf generated classes.
|
||||
using video_widevine::License;
|
||||
using video_widevine::LicenseIdentification;
|
||||
using video_widevine::OFFLINE;
|
||||
using video_widevine::STREAMING;
|
||||
|
||||
typedef ::video_widevine::License::KeyContainer KeyContainer;
|
||||
typedef KeyContainer::VideoResolutionConstraint VideoResolutionConstraint;
|
||||
|
||||
class LicenseKeysTest : public ::testing::Test {
|
||||
protected:
|
||||
enum KeyFlag { kKeyFlagNull, kKeyFlagFalse, kKeyFlagTrue };
|
||||
|
||||
static const KeyFlag kEncryptNull = kKeyFlagNull;
|
||||
static const KeyFlag kEncryptFalse = kKeyFlagFalse;
|
||||
static const KeyFlag kEncryptTrue = kKeyFlagTrue;
|
||||
static const KeyFlag kDecryptNull = kKeyFlagNull;
|
||||
static const KeyFlag kDecryptFalse = kKeyFlagFalse;
|
||||
static const KeyFlag kDecryptTrue = kKeyFlagTrue;
|
||||
static const KeyFlag kSignNull = kKeyFlagNull;
|
||||
static const KeyFlag kSignFalse = kKeyFlagFalse;
|
||||
static const KeyFlag kSignTrue = kKeyFlagTrue;
|
||||
static const KeyFlag kVerifyNull = kKeyFlagNull;
|
||||
static const KeyFlag kVerifyFalse = kKeyFlagFalse;
|
||||
static const KeyFlag kVerifyTrue = kKeyFlagTrue;
|
||||
|
||||
static const KeyFlag kContentSecureFalse = kKeyFlagFalse;
|
||||
static const KeyFlag kContentSecureTrue = kKeyFlagTrue;
|
||||
static const KeyFlag kContentClearFalse = kKeyFlagFalse;
|
||||
static const KeyFlag kContentClearTrue = kKeyFlagTrue;
|
||||
|
||||
virtual void SetUp() {
|
||||
LicenseIdentification* id = license_.mutable_id();
|
||||
id->set_version(1);
|
||||
id->set_type(STREAMING);
|
||||
}
|
||||
|
||||
virtual void AddContentKey(
|
||||
const KeyId& key_id, bool set_level = false,
|
||||
KeyContainer::SecurityLevel level = KeyContainer::SW_SECURE_CRYPTO,
|
||||
bool set_hdcp = false,
|
||||
KeyContainer::OutputProtection::HDCP hdcp_value =
|
||||
KeyContainer::OutputProtection::HDCP_NONE,
|
||||
bool set_constraints = false,
|
||||
std::vector<VideoResolutionConstraint>* constraints = NULL) {
|
||||
KeyContainer* key = license_.add_key();
|
||||
key->set_type(KeyContainer::CONTENT);
|
||||
if (set_level) {
|
||||
key->set_level(level);
|
||||
}
|
||||
if (set_hdcp) {
|
||||
KeyContainer::OutputProtection* pro = key->mutable_required_protection();
|
||||
pro->set_hdcp(hdcp_value);
|
||||
}
|
||||
if (set_constraints) {
|
||||
for (std::vector<VideoResolutionConstraint>::iterator it =
|
||||
constraints->begin();
|
||||
it != constraints->end(); ++it) {
|
||||
VideoResolutionConstraint* constraint =
|
||||
key->add_video_resolution_constraints();
|
||||
constraint->set_min_resolution_pixels(it->min_resolution_pixels());
|
||||
constraint->set_max_resolution_pixels(it->max_resolution_pixels());
|
||||
constraint->mutable_required_protection()->set_hdcp(
|
||||
it->required_protection().hdcp());
|
||||
}
|
||||
}
|
||||
key->set_id(key_id);
|
||||
}
|
||||
|
||||
virtual void AddEntitlementKey(
|
||||
const KeyId& key_id, bool set_level = false,
|
||||
KeyContainer::SecurityLevel level = KeyContainer::SW_SECURE_CRYPTO,
|
||||
bool set_hdcp = false,
|
||||
KeyContainer::OutputProtection::HDCP hdcp_value =
|
||||
KeyContainer::OutputProtection::HDCP_NONE,
|
||||
bool set_constraints = false,
|
||||
std::vector<VideoResolutionConstraint>* constraints = NULL) {
|
||||
AddContentKey(key_id, set_level, level, set_hdcp, hdcp_value,
|
||||
set_constraints, constraints);
|
||||
license_.mutable_key(license_.key_size() - 1)
|
||||
->set_type(KeyContainer::ENTITLEMENT);
|
||||
}
|
||||
|
||||
virtual void AddOperatorSessionKey(const KeyId& key_id,
|
||||
bool set_perms = false,
|
||||
KeyFlag encrypt = kKeyFlagNull,
|
||||
KeyFlag decrypt = kKeyFlagNull,
|
||||
KeyFlag sign = kKeyFlagNull,
|
||||
KeyFlag verify = kKeyFlagNull) {
|
||||
KeyContainer* non_content_key = license_.add_key();
|
||||
non_content_key->set_type(KeyContainer::OPERATOR_SESSION);
|
||||
non_content_key->set_id(key_id);
|
||||
if (set_perms) {
|
||||
KeyContainer::OperatorSessionKeyPermissions* permissions =
|
||||
non_content_key->mutable_operator_session_key_permissions();
|
||||
if (encrypt != kKeyFlagNull) {
|
||||
permissions->set_allow_encrypt(encrypt == kKeyFlagTrue);
|
||||
}
|
||||
if (decrypt != kKeyFlagNull) {
|
||||
permissions->set_allow_decrypt(decrypt == kKeyFlagTrue);
|
||||
}
|
||||
if (sign != kKeyFlagNull) {
|
||||
permissions->set_allow_sign(sign == kKeyFlagTrue);
|
||||
}
|
||||
if (verify != kKeyFlagNull) {
|
||||
permissions->set_allow_signature_verify(verify == kKeyFlagTrue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual void AddSigningKey(const KeyId& key_id) {
|
||||
KeyContainer* key = license_.add_key();
|
||||
key->set_type(KeyContainer::SIGNING);
|
||||
key->set_id(key_id);
|
||||
}
|
||||
|
||||
virtual void ExpectAllowedUsageContent(const KeyAllowedUsage& key_usage,
|
||||
KeyFlag secure, KeyFlag clear,
|
||||
KeySecurityLevel key_security_level) {
|
||||
EXPECT_EQ(key_usage.decrypt_to_secure_buffer, secure == kKeyFlagTrue);
|
||||
EXPECT_EQ(key_usage.decrypt_to_clear_buffer, clear == kKeyFlagTrue);
|
||||
EXPECT_EQ(key_usage.key_security_level_, key_security_level);
|
||||
EXPECT_FALSE(key_usage.generic_encrypt);
|
||||
EXPECT_FALSE(key_usage.generic_decrypt);
|
||||
EXPECT_FALSE(key_usage.generic_sign);
|
||||
EXPECT_FALSE(key_usage.generic_verify);
|
||||
}
|
||||
|
||||
virtual void ExpectAllowedUsageOperator(const KeyAllowedUsage& key_usage,
|
||||
KeyFlag encrypt, KeyFlag decrypt,
|
||||
KeyFlag sign, KeyFlag verify) {
|
||||
EXPECT_FALSE(key_usage.decrypt_to_secure_buffer);
|
||||
EXPECT_FALSE(key_usage.decrypt_to_clear_buffer);
|
||||
EXPECT_EQ(key_usage.generic_encrypt, encrypt == kKeyFlagTrue);
|
||||
EXPECT_EQ(key_usage.generic_decrypt, decrypt == kKeyFlagTrue);
|
||||
EXPECT_EQ(key_usage.generic_sign, sign == kKeyFlagTrue);
|
||||
EXPECT_EQ(key_usage.generic_verify, verify == kKeyFlagTrue);
|
||||
}
|
||||
|
||||
virtual int NumContentKeys() { return content_key_count_; }
|
||||
|
||||
virtual void StageContentKeys() {
|
||||
content_key_count_ = 0;
|
||||
AddContentKey(ck_sw_crypto, true, KeyContainer::SW_SECURE_CRYPTO);
|
||||
content_key_count_++;
|
||||
AddContentKey(ck_sw_decode, true, KeyContainer::SW_SECURE_DECODE);
|
||||
content_key_count_++;
|
||||
AddContentKey(ck_hw_crypto, true, KeyContainer::HW_SECURE_CRYPTO);
|
||||
content_key_count_++;
|
||||
AddContentKey(ck_hw_decode, true, KeyContainer::HW_SECURE_DECODE);
|
||||
content_key_count_++;
|
||||
AddContentKey(ck_hw_secure, true, KeyContainer::HW_SECURE_ALL);
|
||||
content_key_count_++;
|
||||
license_keys_.SetFromLicense(license_);
|
||||
}
|
||||
|
||||
virtual void StageOperatorSessionKeys() {
|
||||
AddOperatorSessionKey(osk_decrypt, true, kEncryptNull, kDecryptTrue,
|
||||
kSignNull, kVerifyNull);
|
||||
AddOperatorSessionKey(osk_encrypt, true, kEncryptTrue, kDecryptNull,
|
||||
kSignNull, kVerifyNull);
|
||||
AddOperatorSessionKey(osk_sign, true, kEncryptNull, kDecryptNull, kSignTrue,
|
||||
kVerifyNull);
|
||||
AddOperatorSessionKey(osk_verify, true, kEncryptNull, kDecryptNull,
|
||||
kSignNull, kVerifyTrue);
|
||||
AddOperatorSessionKey(osk_encrypt_decrypt, true, kEncryptTrue, kDecryptTrue,
|
||||
kSignNull, kVerifyNull);
|
||||
AddOperatorSessionKey(osk_sign_verify, true, kEncryptNull, kDecryptNull,
|
||||
kSignTrue, kVerifyTrue);
|
||||
AddOperatorSessionKey(osk_all, true, kEncryptTrue, kDecryptTrue, kSignTrue,
|
||||
kVerifyTrue);
|
||||
license_keys_.SetFromLicense(license_);
|
||||
}
|
||||
|
||||
virtual void StageHdcpKeys() {
|
||||
content_key_count_ = 0;
|
||||
AddContentKey(ck_sw_crypto_NO_HDCP, true, KeyContainer::SW_SECURE_CRYPTO,
|
||||
true, KeyContainer::OutputProtection::HDCP_NONE);
|
||||
content_key_count_++;
|
||||
AddContentKey(ck_hw_secure_NO_HDCP, true, KeyContainer::HW_SECURE_ALL, true,
|
||||
KeyContainer::OutputProtection::HDCP_NONE);
|
||||
content_key_count_++;
|
||||
|
||||
AddContentKey(ck_sw_crypto_HDCP_V2_1, true, KeyContainer::SW_SECURE_CRYPTO,
|
||||
true, KeyContainer::OutputProtection::HDCP_V2_1);
|
||||
content_key_count_++;
|
||||
AddContentKey(ck_hw_secure_HDCP_V2_1, true, KeyContainer::HW_SECURE_ALL,
|
||||
true, KeyContainer::OutputProtection::HDCP_V2_1);
|
||||
content_key_count_++;
|
||||
|
||||
AddContentKey(ck_sw_crypto_HDCP_NO_OUTPUT, true,
|
||||
KeyContainer::SW_SECURE_CRYPTO, true,
|
||||
KeyContainer::OutputProtection::HDCP_NO_DIGITAL_OUTPUT);
|
||||
content_key_count_++;
|
||||
AddContentKey(ck_hw_secure_HDCP_NO_OUTPUT, true,
|
||||
KeyContainer::HW_SECURE_ALL, true,
|
||||
KeyContainer::OutputProtection::HDCP_NO_DIGITAL_OUTPUT);
|
||||
content_key_count_++;
|
||||
license_keys_.SetFromLicense(license_);
|
||||
}
|
||||
|
||||
virtual void AddConstraint(
|
||||
std::vector<VideoResolutionConstraint>& constraints, uint32_t min_res,
|
||||
uint32_t max_res, bool set_hdcp = false,
|
||||
KeyContainer::OutputProtection::HDCP hdcp =
|
||||
KeyContainer::OutputProtection::HDCP_NONE) {
|
||||
VideoResolutionConstraint constraint;
|
||||
constraint.set_min_resolution_pixels(min_res);
|
||||
constraint.set_max_resolution_pixels(max_res);
|
||||
if (set_hdcp) {
|
||||
constraint.mutable_required_protection()->set_hdcp(hdcp);
|
||||
}
|
||||
constraints.push_back(constraint);
|
||||
}
|
||||
|
||||
virtual void StageConstraintKeys() {
|
||||
content_key_count_ = 0;
|
||||
|
||||
std::vector<VideoResolutionConstraint> constraints;
|
||||
|
||||
AddConstraint(constraints, key_lo_res_min, key_lo_res_max);
|
||||
|
||||
AddContentKey(ck_NO_HDCP_lo_res, true, KeyContainer::SW_SECURE_CRYPTO, true,
|
||||
KeyContainer::OutputProtection::HDCP_NONE, true,
|
||||
&constraints);
|
||||
content_key_count_++;
|
||||
|
||||
constraints.clear();
|
||||
AddConstraint(constraints, key_hi_res_min, key_hi_res_max);
|
||||
|
||||
AddContentKey(ck_HDCP_NO_OUTPUT_hi_res, true,
|
||||
KeyContainer::SW_SECURE_CRYPTO, true,
|
||||
KeyContainer::OutputProtection::HDCP_NO_DIGITAL_OUTPUT, true,
|
||||
&constraints);
|
||||
content_key_count_++;
|
||||
|
||||
constraints.clear();
|
||||
AddConstraint(constraints, key_top_res_min, key_top_res_max);
|
||||
|
||||
AddContentKey(ck_HDCP_V2_1_max_res, true, KeyContainer::SW_SECURE_CRYPTO,
|
||||
true, KeyContainer::OutputProtection::HDCP_V2_1, true,
|
||||
&constraints);
|
||||
content_key_count_++;
|
||||
|
||||
constraints.clear();
|
||||
AddConstraint(constraints, key_lo_res_min, key_lo_res_max);
|
||||
AddConstraint(constraints, key_hi_res_min, key_hi_res_max, true,
|
||||
KeyContainer::OutputProtection::HDCP_NO_DIGITAL_OUTPUT);
|
||||
|
||||
AddContentKey(ck_NO_HDCP_dual_res, true, KeyContainer::HW_SECURE_ALL, true,
|
||||
KeyContainer::OutputProtection::HDCP_NONE, true,
|
||||
&constraints);
|
||||
content_key_count_++;
|
||||
|
||||
license_keys_.SetFromLicense(license_);
|
||||
}
|
||||
|
||||
virtual void ExpectKeyStatusesEqual(KeyStatusMap& key_status_map,
|
||||
KeyStatus expected_status) {
|
||||
for (KeyStatusMap::iterator it = key_status_map.begin();
|
||||
it != key_status_map.end(); ++it) {
|
||||
EXPECT_TRUE(it->second == expected_status);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void ExpectKeyStatusEqual(KeyStatusMap& key_status_map,
|
||||
const KeyId& key_id,
|
||||
KeyStatus expected_status) {
|
||||
for (KeyStatusMap::iterator it = key_status_map.begin();
|
||||
it != key_status_map.end(); ++it) {
|
||||
if (key_id == it->first) {
|
||||
EXPECT_TRUE(it->second == expected_status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t content_key_count_;
|
||||
LicenseKeys license_keys_;
|
||||
License license_;
|
||||
};
|
||||
|
||||
TEST_F(LicenseKeysTest, Empty) { EXPECT_TRUE(license_keys_.Empty()); }
|
||||
|
||||
TEST_F(LicenseKeysTest, NotEmpty) {
|
||||
const KeyId c_key = "content_key";
|
||||
AddContentKey(c_key);
|
||||
license_keys_.SetFromLicense(license_);
|
||||
EXPECT_FALSE(license_keys_.Empty());
|
||||
}
|
||||
|
||||
TEST_F(LicenseKeysTest, BadKeyId) {
|
||||
const KeyId c_key = "content_key";
|
||||
const KeyId os_key = "op_sess_key";
|
||||
const KeyId unk_key = "unknown_key";
|
||||
KeyAllowedUsage allowed_usage;
|
||||
AddContentKey(c_key);
|
||||
AddOperatorSessionKey(os_key);
|
||||
license_keys_.SetFromLicense(license_);
|
||||
EXPECT_FALSE(license_keys_.IsContentKey(unk_key));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(unk_key));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(unk_key));
|
||||
EXPECT_FALSE(license_keys_.GetAllowedUsage(unk_key, &allowed_usage));
|
||||
}
|
||||
|
||||
TEST_F(LicenseKeysTest, SigningKey) {
|
||||
const KeyId c_key = "content_key";
|
||||
const KeyId os_key = "op_sess_key";
|
||||
const KeyId sign_key = "signing_key";
|
||||
KeyAllowedUsage allowed_usage;
|
||||
AddSigningKey(sign_key);
|
||||
AddContentKey(c_key);
|
||||
AddOperatorSessionKey(os_key);
|
||||
license_keys_.SetFromLicense(license_);
|
||||
EXPECT_FALSE(license_keys_.IsContentKey(sign_key));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(sign_key));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(sign_key));
|
||||
EXPECT_FALSE(license_keys_.GetAllowedUsage(sign_key, &allowed_usage));
|
||||
}
|
||||
|
||||
TEST_F(LicenseKeysTest, ContentKey) {
|
||||
const KeyId c_key = "content_key";
|
||||
AddContentKey(c_key);
|
||||
EXPECT_FALSE(license_keys_.IsContentKey(c_key));
|
||||
|
||||
license_keys_.SetFromLicense(license_);
|
||||
EXPECT_TRUE(license_keys_.IsContentKey(c_key));
|
||||
}
|
||||
|
||||
TEST_F(LicenseKeysTest, EntitlementKey) {
|
||||
const KeyId e_key = "entitlement_key";
|
||||
const KeyId c_key = "content_key";
|
||||
AddEntitlementKey(e_key);
|
||||
EXPECT_FALSE(license_keys_.IsContentKey(e_key));
|
||||
|
||||
license_keys_.SetFromLicense(license_);
|
||||
// TODO(juce, rfrias): For simplicity entitlement keys are indicated as
|
||||
// content keys. It doesn't break anything, but CanDecryptContent returns true
|
||||
// for and entitlement key id.
|
||||
EXPECT_TRUE(license_keys_.IsContentKey(e_key));
|
||||
|
||||
std::vector<WidevinePsshData_EntitledKey> entitled_keys(1);
|
||||
entitled_keys[0].set_entitlement_key_id(e_key);
|
||||
entitled_keys[0].set_key_id(c_key);
|
||||
EXPECT_FALSE(license_keys_.IsContentKey(c_key));
|
||||
license_keys_.SetEntitledKeys(entitled_keys);
|
||||
EXPECT_TRUE(license_keys_.IsContentKey(c_key));
|
||||
}
|
||||
|
||||
TEST_F(LicenseKeysTest, OperatorSessionKey) {
|
||||
const KeyId os_key = "op_sess_key";
|
||||
EXPECT_FALSE(license_keys_.IsContentKey(os_key));
|
||||
AddOperatorSessionKey(os_key);
|
||||
|
||||
license_keys_.SetFromLicense(license_);
|
||||
EXPECT_FALSE(license_keys_.IsContentKey(os_key));
|
||||
}
|
||||
|
||||
TEST_F(LicenseKeysTest, CanDecrypt) {
|
||||
const KeyId os_key = "op_sess_key";
|
||||
const KeyId c_key = "content_key";
|
||||
const KeyId e_key = "entitlement_key";
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(c_key));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(os_key));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(e_key));
|
||||
AddOperatorSessionKey(os_key);
|
||||
AddContentKey(c_key);
|
||||
AddEntitlementKey(e_key);
|
||||
license_keys_.SetFromLicense(license_);
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(c_key));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(os_key));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(e_key));
|
||||
bool new_usable_keys = false;
|
||||
bool any_change = false;
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys);
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_TRUE(new_usable_keys);
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(c_key));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(os_key));
|
||||
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusExpired, &new_usable_keys);
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_FALSE(new_usable_keys);
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(c_key));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(os_key));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(e_key));
|
||||
}
|
||||
|
||||
TEST_F(LicenseKeysTest, AllowedUsageNull) {
|
||||
const KeyId os_key = "op_sess_key";
|
||||
const KeyId c_key = "content_key";
|
||||
const KeyId sign_key = "signing_key";
|
||||
const KeyId e_key = "entitlement_key";
|
||||
AddOperatorSessionKey(os_key);
|
||||
AddContentKey(c_key);
|
||||
AddSigningKey(sign_key);
|
||||
AddEntitlementKey(e_key);
|
||||
license_keys_.SetFromLicense(license_);
|
||||
KeyAllowedUsage usage_1;
|
||||
EXPECT_FALSE(license_keys_.GetAllowedUsage(sign_key, &usage_1));
|
||||
|
||||
KeyAllowedUsage usage_2;
|
||||
EXPECT_TRUE(license_keys_.GetAllowedUsage(c_key, &usage_2));
|
||||
ExpectAllowedUsageContent(usage_2, kContentClearTrue, kContentSecureTrue,
|
||||
kKeySecurityLevelUnset);
|
||||
|
||||
KeyAllowedUsage usage_3;
|
||||
EXPECT_TRUE(license_keys_.GetAllowedUsage(os_key, &usage_3));
|
||||
ExpectAllowedUsageContent(usage_3, kContentClearFalse, kContentSecureFalse,
|
||||
kKeySecurityLevelUnset);
|
||||
|
||||
KeyAllowedUsage usage_4;
|
||||
EXPECT_TRUE(license_keys_.GetAllowedUsage(os_key, &usage_4));
|
||||
ExpectAllowedUsageContent(usage_4, kContentClearFalse, kContentSecureFalse,
|
||||
kKeySecurityLevelUnset);
|
||||
}
|
||||
|
||||
TEST_F(LicenseKeysTest, AllowedUsageContent) {
|
||||
StageContentKeys();
|
||||
KeyAllowedUsage u_sw_crypto;
|
||||
EXPECT_TRUE(license_keys_.GetAllowedUsage(ck_sw_crypto, &u_sw_crypto));
|
||||
ExpectAllowedUsageContent(u_sw_crypto, kContentSecureTrue, kContentClearTrue,
|
||||
kSoftwareSecureCrypto);
|
||||
|
||||
KeyAllowedUsage u_sw_decode;
|
||||
EXPECT_TRUE(license_keys_.GetAllowedUsage(ck_sw_decode, &u_sw_decode));
|
||||
ExpectAllowedUsageContent(u_sw_decode, kContentSecureTrue, kContentClearTrue,
|
||||
kSoftwareSecureDecode);
|
||||
|
||||
KeyAllowedUsage u_hw_crypto;
|
||||
EXPECT_TRUE(license_keys_.GetAllowedUsage(ck_hw_crypto, &u_hw_crypto));
|
||||
ExpectAllowedUsageContent(u_hw_crypto, kContentSecureTrue, kContentClearTrue,
|
||||
kHardwareSecureCrypto);
|
||||
|
||||
KeyAllowedUsage u_hw_decode;
|
||||
EXPECT_TRUE(license_keys_.GetAllowedUsage(ck_hw_decode, &u_hw_decode));
|
||||
ExpectAllowedUsageContent(u_hw_decode, kContentSecureTrue, kContentClearFalse,
|
||||
kHardwareSecureDecode);
|
||||
|
||||
KeyAllowedUsage u_hw_secure;
|
||||
EXPECT_TRUE(license_keys_.GetAllowedUsage(ck_hw_secure, &u_hw_secure));
|
||||
ExpectAllowedUsageContent(u_hw_secure, kContentSecureTrue, kContentClearFalse,
|
||||
kHardwareSecureAll);
|
||||
}
|
||||
|
||||
TEST_F(LicenseKeysTest, AllowedUsageOperatorSession) {
|
||||
StageOperatorSessionKeys();
|
||||
KeyAllowedUsage u_encrypt;
|
||||
EXPECT_TRUE(license_keys_.GetAllowedUsage(osk_encrypt, &u_encrypt));
|
||||
ExpectAllowedUsageOperator(u_encrypt, kEncryptTrue, kDecryptFalse, kSignFalse,
|
||||
kVerifyFalse);
|
||||
|
||||
KeyAllowedUsage u_decrypt;
|
||||
EXPECT_TRUE(license_keys_.GetAllowedUsage(osk_decrypt, &u_decrypt));
|
||||
ExpectAllowedUsageOperator(u_decrypt, kEncryptFalse, kDecryptTrue, kSignFalse,
|
||||
kVerifyFalse);
|
||||
|
||||
KeyAllowedUsage u_sign;
|
||||
EXPECT_TRUE(license_keys_.GetAllowedUsage(osk_sign, &u_sign));
|
||||
ExpectAllowedUsageOperator(u_sign, kEncryptFalse, kDecryptFalse, kSignTrue,
|
||||
kVerifyFalse);
|
||||
|
||||
KeyAllowedUsage u_verify;
|
||||
EXPECT_TRUE(license_keys_.GetAllowedUsage(osk_verify, &u_verify));
|
||||
ExpectAllowedUsageOperator(u_verify, kEncryptFalse, kDecryptFalse, kSignFalse,
|
||||
kVerifyTrue);
|
||||
|
||||
KeyAllowedUsage u_encrypt_decrypt;
|
||||
EXPECT_TRUE(
|
||||
license_keys_.GetAllowedUsage(osk_encrypt_decrypt, &u_encrypt_decrypt));
|
||||
ExpectAllowedUsageOperator(u_encrypt_decrypt, kEncryptTrue, kDecryptTrue,
|
||||
kSignFalse, kVerifyFalse);
|
||||
|
||||
KeyAllowedUsage u_sign_verify;
|
||||
EXPECT_TRUE(license_keys_.GetAllowedUsage(osk_sign_verify, &u_sign_verify));
|
||||
ExpectAllowedUsageOperator(u_sign_verify, kEncryptFalse, kDecryptFalse,
|
||||
kSignTrue, kVerifyTrue);
|
||||
|
||||
KeyAllowedUsage u_all;
|
||||
EXPECT_TRUE(license_keys_.GetAllowedUsage(osk_all, &u_all));
|
||||
ExpectAllowedUsageOperator(u_all, kEncryptTrue, kDecryptTrue, kSignTrue,
|
||||
kVerifyTrue);
|
||||
}
|
||||
|
||||
TEST_F(LicenseKeysTest, ExtractKeyStatuses) {
|
||||
KeyStatusMap key_status_map;
|
||||
StageOperatorSessionKeys();
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
EXPECT_EQ(0u, key_status_map.size());
|
||||
StageContentKeys();
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
EXPECT_EQ(content_key_count_, key_status_map.size());
|
||||
ExpectKeyStatusesEqual(key_status_map, kKeyStatusInternalError);
|
||||
}
|
||||
|
||||
TEST_F(LicenseKeysTest, KeyStatusChanges) {
|
||||
bool new_usable_keys = false;
|
||||
bool any_change = false;
|
||||
KeyStatusMap key_status_map;
|
||||
StageOperatorSessionKeys();
|
||||
StageContentKeys();
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
EXPECT_EQ(content_key_count_, key_status_map.size());
|
||||
ExpectKeyStatusesEqual(key_status_map, kKeyStatusInternalError);
|
||||
|
||||
// change to pending
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusPending, &new_usable_keys);
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_FALSE(new_usable_keys);
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure));
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
EXPECT_EQ(content_key_count_, key_status_map.size());
|
||||
ExpectKeyStatusesEqual(key_status_map, kKeyStatusPending);
|
||||
|
||||
// change to pending (again)
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusPending, &new_usable_keys);
|
||||
EXPECT_FALSE(any_change);
|
||||
EXPECT_FALSE(new_usable_keys);
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure));
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
EXPECT_EQ(content_key_count_, key_status_map.size());
|
||||
ExpectKeyStatusesEqual(key_status_map, kKeyStatusPending);
|
||||
|
||||
// change to usable
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys);
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_TRUE(new_usable_keys);
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure));
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
EXPECT_EQ(content_key_count_, key_status_map.size());
|
||||
ExpectKeyStatusesEqual(key_status_map, kKeyStatusUsable);
|
||||
|
||||
// change to usable (again)
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys);
|
||||
EXPECT_FALSE(any_change);
|
||||
EXPECT_FALSE(new_usable_keys);
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure));
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
EXPECT_EQ(content_key_count_, key_status_map.size());
|
||||
ExpectKeyStatusesEqual(key_status_map, kKeyStatusUsable);
|
||||
|
||||
// change to expired
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusExpired, &new_usable_keys);
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_FALSE(new_usable_keys);
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure));
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
EXPECT_EQ(content_key_count_, key_status_map.size());
|
||||
ExpectKeyStatusesEqual(key_status_map, kKeyStatusExpired);
|
||||
}
|
||||
|
||||
TEST_F(LicenseKeysTest, HdcpChanges) {
|
||||
bool new_usable_keys = false;
|
||||
bool any_change = false;
|
||||
KeyStatusMap key_status_map;
|
||||
StageHdcpKeys();
|
||||
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys);
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_TRUE(new_usable_keys);
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_V2_1));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_V2_1));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_NO_OUTPUT));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_NO_OUTPUT));
|
||||
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_V2_1));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_V2_1));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_NO_OUTPUT));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_NO_OUTPUT));
|
||||
|
||||
license_keys_.ApplyConstraints(100, HDCP_NONE);
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys);
|
||||
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_FALSE(new_usable_keys);
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_NO_HDCP));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_V2_1));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_V2_1));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_NO_OUTPUT));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_NO_OUTPUT));
|
||||
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_NO_HDCP));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_V2_1));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_V2_1));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_NO_OUTPUT));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_NO_OUTPUT));
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_hw_secure_NO_HDCP, kKeyStatusUsable);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_V2_1,
|
||||
kKeyStatusOutputNotAllowed);
|
||||
|
||||
license_keys_.ApplyConstraints(100, HDCP_V1);
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys);
|
||||
|
||||
EXPECT_FALSE(any_change);
|
||||
EXPECT_FALSE(new_usable_keys);
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_NO_HDCP));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_V2_1));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_V2_1));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_NO_OUTPUT));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_NO_OUTPUT));
|
||||
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_NO_HDCP));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_V2_1));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_V2_1));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_NO_OUTPUT));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_NO_OUTPUT));
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_NO_HDCP, kKeyStatusUsable);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_HDCP_V2_1,
|
||||
kKeyStatusOutputNotAllowed);
|
||||
|
||||
license_keys_.ApplyConstraints(100, HDCP_V2_2);
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys);
|
||||
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_TRUE(new_usable_keys);
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_V2_1));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_V2_1));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_NO_OUTPUT));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_NO_OUTPUT));
|
||||
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_V2_1));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_V2_1));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_NO_OUTPUT));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_NO_OUTPUT));
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_HDCP_V2_1,
|
||||
kKeyStatusUsable);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_HDCP_NO_OUTPUT,
|
||||
kKeyStatusOutputNotAllowed);
|
||||
|
||||
license_keys_.ApplyConstraints(100, HDCP_NO_DIGITAL_OUTPUT);
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys);
|
||||
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_TRUE(new_usable_keys);
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_V2_1));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_V2_1));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_NO_OUTPUT));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_NO_OUTPUT));
|
||||
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_V2_1));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_V2_1));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_NO_OUTPUT));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_NO_OUTPUT));
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_V2_1,
|
||||
kKeyStatusUsable);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_NO_OUTPUT,
|
||||
kKeyStatusUsable);
|
||||
|
||||
license_keys_.ApplyConstraints(100, HDCP_NONE);
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys);
|
||||
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_FALSE(new_usable_keys);
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_sw_crypto_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_hw_secure_NO_HDCP));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_V2_1));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_V2_1));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_sw_crypto_HDCP_NO_OUTPUT));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_hw_secure_HDCP_NO_OUTPUT));
|
||||
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_sw_crypto_NO_HDCP));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_hw_secure_NO_HDCP));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_V2_1));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_V2_1));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_sw_crypto_HDCP_NO_OUTPUT));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_hw_secure_HDCP_NO_OUTPUT));
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_hw_secure_NO_HDCP, kKeyStatusUsable);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_NO_OUTPUT,
|
||||
kKeyStatusOutputNotAllowed);
|
||||
}
|
||||
|
||||
TEST_F(LicenseKeysTest, ConstraintChanges) {
|
||||
bool new_usable_keys = false;
|
||||
bool any_change = false;
|
||||
KeyStatusMap key_status_map;
|
||||
StageConstraintKeys();
|
||||
|
||||
// No constraints set by device
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys);
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_TRUE(new_usable_keys);
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res));
|
||||
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res));
|
||||
|
||||
// Low-res device, no HDCP support
|
||||
license_keys_.ApplyConstraints(dev_lo_res, HDCP_NONE);
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys);
|
||||
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_FALSE(new_usable_keys);
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res));
|
||||
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res));
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_hw_secure_NO_HDCP, kKeyStatusUsable);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_V2_1,
|
||||
kKeyStatusOutputNotAllowed);
|
||||
|
||||
// Hi-res device, HDCP_V1 support
|
||||
license_keys_.ApplyConstraints(dev_hi_res, HDCP_V1);
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys);
|
||||
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_TRUE(new_usable_keys);
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res));
|
||||
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res));
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_NO_HDCP, kKeyStatusUsable);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_HDCP_V2_1,
|
||||
kKeyStatusOutputNotAllowed);
|
||||
|
||||
// Lo-res device, HDCP V2.2 support
|
||||
license_keys_.ApplyConstraints(dev_lo_res, HDCP_V2_2);
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys);
|
||||
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_TRUE(new_usable_keys);
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res));
|
||||
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res));
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_HDCP_V2_1,
|
||||
kKeyStatusUsable);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_sw_crypto_HDCP_NO_OUTPUT,
|
||||
kKeyStatusOutputNotAllowed);
|
||||
|
||||
// Hi-res device, Maximal HDCP support
|
||||
license_keys_.ApplyConstraints(dev_hi_res, HDCP_NO_DIGITAL_OUTPUT);
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys);
|
||||
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_TRUE(new_usable_keys);
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res));
|
||||
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res));
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_V2_1,
|
||||
kKeyStatusUsable);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_NO_OUTPUT,
|
||||
kKeyStatusUsable);
|
||||
|
||||
// Lo-res device, no HDCP support
|
||||
license_keys_.ApplyConstraints(dev_lo_res, HDCP_NONE);
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys);
|
||||
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_TRUE(new_usable_keys);
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res));
|
||||
EXPECT_TRUE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res));
|
||||
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res));
|
||||
EXPECT_TRUE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res));
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_hw_secure_NO_HDCP, kKeyStatusUsable);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_NO_OUTPUT,
|
||||
kKeyStatusOutputNotAllowed);
|
||||
|
||||
// Too-high-res -- all keys rejected
|
||||
license_keys_.ApplyConstraints(dev_top_res, HDCP_NONE);
|
||||
any_change =
|
||||
license_keys_.ApplyStatusChange(kKeyStatusUsable, &new_usable_keys);
|
||||
|
||||
EXPECT_TRUE(any_change);
|
||||
EXPECT_FALSE(new_usable_keys);
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_NO_HDCP_lo_res));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_NO_OUTPUT_hi_res));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_HDCP_V2_1_max_res));
|
||||
EXPECT_FALSE(license_keys_.CanDecryptContent(ck_NO_HDCP_dual_res));
|
||||
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_NO_HDCP_lo_res));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_NO_OUTPUT_hi_res));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_HDCP_V2_1_max_res));
|
||||
EXPECT_FALSE(license_keys_.MeetsConstraints(ck_NO_HDCP_dual_res));
|
||||
|
||||
license_keys_.ExtractKeyStatuses(&key_status_map);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_hw_secure_NO_HDCP, kKeyStatusUsable);
|
||||
ExpectKeyStatusEqual(key_status_map, ck_hw_secure_HDCP_NO_OUTPUT,
|
||||
kKeyStatusOutputNotAllowed);
|
||||
}
|
||||
|
||||
} // namespace wvcas
|
||||
154
tests/src/mediacas_integration_test.cpp
Normal file
154
tests/src/mediacas_integration_test.cpp
Normal file
@@ -0,0 +1,154 @@
|
||||
// Dynamically generated header created during build. The Runtest entry point is
|
||||
// defined in gopkg_carchive.go
|
||||
|
||||
#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
|
||||
// may be the best solution. The downside is the test prints PASS for each
|
||||
// individual test.
|
||||
|
||||
// It is also possible to not address them individually but run
|
||||
// them all as a batch. If running the tests as a single batch it may be
|
||||
// possible to pass command line flags to the test to indicate individual tests.
|
||||
// The downside of this is that it forces a user to pass in two command line
|
||||
// flags for each test.
|
||||
int RunNamedTest(const std::string testname) {
|
||||
GoString go_testname = {
|
||||
p : testname.data(),
|
||||
n : static_cast<unsigned>(testname.size())
|
||||
};
|
||||
return RunTest(go_testname);
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCasFactoryCreation) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCasFactoryCreation"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCreateCasPlugin) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCreateCasPlugin"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCreateCasPluginWithNewCasIds) {
|
||||
EXPECT_EQ(kIntegrationTestPassed,
|
||||
RunNamedTest("TestCreateCasPluginWithNewCasIds"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCreateCasPluginWithInvalidCasIds) {
|
||||
EXPECT_EQ(kIntegrationTestPassed,
|
||||
RunNamedTest("TestCreateCasPluginWithInvalidCasIds"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCreateCasPluginWithSessionEvent) {
|
||||
EXPECT_EQ(kIntegrationTestPassed,
|
||||
RunNamedTest("TestCreateCasPluginWithSessionEvent"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCreateCasPluginWithSessionEventWithNewCasIds) {
|
||||
EXPECT_EQ(kIntegrationTestPassed,
|
||||
RunNamedTest("TestCreateCasPluginWithSessionEventWithNewCasIds"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCasPluginEventPassing) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCasPluginEventPassing"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestSessionFailWithoutProvisioning) {
|
||||
EXPECT_EQ(kIntegrationTestPassed,
|
||||
RunNamedTest("TestSessionFailWithoutProvisioning"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestUniqueIdQuery) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestUniqueIdQuery"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCasPluginProvision) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCasPluginProvision"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCasPluginEmmRequestWithInitData) {
|
||||
EXPECT_EQ(kIntegrationTestPassed,
|
||||
RunNamedTest("TestCasPluginEmmRequestWithInitData"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCasEmmRequestWithPrivateData) {
|
||||
EXPECT_EQ(kIntegrationTestPassed,
|
||||
RunNamedTest("TestCasEmmRequestWithPrivateData"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCasWithOfflineEMM) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCasWithOfflineEMM"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCasCanStoreOfflineEMM) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCasCanStoreOfflineEMM"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCasCanNotStoreOfflineEMM) {
|
||||
EXPECT_EQ(kIntegrationTestPassed,
|
||||
RunNamedTest("TestCasCanNotStoreOfflineEMM"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestSession) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestSession"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestCasRenewal) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestCasRenewal"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestRestoreRenewalAndExpiredLicense) {
|
||||
EXPECT_EQ(kIntegrationTestPassed,
|
||||
RunNamedTest("TestRestoreRenewalAndExpiredLicense"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestLicenseExpiration) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestLicenseExpiration"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestParentalControl) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestParentalControl"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestRemoveLicense) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("TestRemoveLicense"));
|
||||
}
|
||||
|
||||
TEST(IntegrationTests, TestSessionEventPassing) {
|
||||
EXPECT_EQ(kIntegrationTestPassed, RunNamedTest("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"));
|
||||
}
|
||||
93
tests/src/mock_crypto_session.h
Normal file
93
tests/src/mock_crypto_session.h
Normal file
@@ -0,0 +1,93 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#ifndef MOCK_CRYPTO_SESSION_H
|
||||
#define MOCK_CRYPTO_SESSION_H
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "crypto_session.h"
|
||||
|
||||
class MockCryptoSession : public wvcas::CryptoSession {
|
||||
public:
|
||||
MockCryptoSession() {}
|
||||
virtual ~MockCryptoSession() {}
|
||||
|
||||
MOCK_METHOD0(initialize, wvcas::CasStatus());
|
||||
MOCK_METHOD0(reset, wvcas::CasStatus());
|
||||
MOCK_METHOD0(close, wvcas::CasStatus());
|
||||
MOCK_METHOD0(provisioning_method, wvcas::CasProvisioningMethod());
|
||||
MOCK_METHOD2(GetKeyData,
|
||||
wvcas::CasStatus(uint8_t* keyData, size_t* keyDataLength));
|
||||
MOCK_METHOD0(supported_certificates, wvcas::SupportedCertificates());
|
||||
MOCK_METHOD1(APIVersion, wvcas::CasStatus(uint32_t* api_version));
|
||||
MOCK_METHOD1(GenerateNonce, wvcas::CasStatus(uint32_t* nonce));
|
||||
MOCK_METHOD4(GenerateDerivedKeys,
|
||||
wvcas::CasStatus(const uint8_t* mac_key_context,
|
||||
uint32_t mac_key_context_length,
|
||||
const uint8_t* enc_key_context,
|
||||
uint32_t enc_key_context_length));
|
||||
MOCK_METHOD5(PrepareAndSignLicenseRequest,
|
||||
wvcas::CasStatus(const std::string& message,
|
||||
std::string* core_message,
|
||||
std::string* signature, bool&,
|
||||
OEMCrypto_SignatureHashAlgorithm&)
|
||||
);
|
||||
MOCK_METHOD3(PrepareAndSignRenewalRequest,
|
||||
wvcas::CasStatus(const std::string& message,
|
||||
std::string* core_message,
|
||||
std::string* signature));
|
||||
MOCK_METHOD5(PrepareAndSignProvisioningRequest,
|
||||
wvcas::CasStatus(const std::string& message,
|
||||
std::string* core_message,
|
||||
std::string* signature, bool&,
|
||||
OEMCrypto_SignatureHashAlgorithm&)
|
||||
);
|
||||
MOCK_METHOD4(LoadProvisioning,
|
||||
wvcas::CasStatus(const std::string& signed_message,
|
||||
const std::string& core_message,
|
||||
const std::string& signature,
|
||||
std::string* wrapped_private_key));
|
||||
MOCK_METHOD2(GetOEMPublicCertificate,
|
||||
wvcas::CasStatus(uint8_t* public_cert,
|
||||
size_t* public_cert_length));
|
||||
MOCK_METHOD5(GenerateRSASignature,
|
||||
wvcas::CasStatus(const uint8_t* message, size_t message_length,
|
||||
uint8_t* signature, size_t* signature_length,
|
||||
RSA_Padding_Scheme padding_scheme));
|
||||
MOCK_METHOD6(DeriveKeysFromSessionKey,
|
||||
wvcas::CasStatus(const uint8_t* enc_session_key,
|
||||
size_t enc_session_key_length,
|
||||
const uint8_t* mac_key_context,
|
||||
size_t mac_key_context_length,
|
||||
const uint8_t* enc_key_context,
|
||||
size_t enc_key_context_length));
|
||||
MOCK_METHOD3(LoadLicense, wvcas::CasStatus(const std::string& signed_message,
|
||||
const std::string& core_message,
|
||||
const std::string& signature));
|
||||
MOCK_METHOD3(LoadRenewal, wvcas::CasStatus(const std::string& signed_message,
|
||||
const std::string& core_message,
|
||||
const std::string& signature));
|
||||
MOCK_METHOD3(LoadCasECMKeys, wvcas::CasStatus(OEMCrypto_SESSION session,
|
||||
const wvcas::KeySlot* even_key,
|
||||
const wvcas::KeySlot* odd_key));
|
||||
MOCK_METHOD2(GetHdcpCapabilities, bool(wvcas::HdcpCapability* current,
|
||||
wvcas::HdcpCapability* max));
|
||||
MOCK_METHOD1(GetDeviceID, wvcas::CasStatus(std::string* buffer));
|
||||
MOCK_METHOD2(LoadDeviceRSAKey,
|
||||
wvcas::CasStatus(const uint8_t* wrapped_rsa_key,
|
||||
size_t wrapped_rsa_key_length));
|
||||
MOCK_METHOD1(CreateEntitledKeySession,
|
||||
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));
|
||||
MOCK_METHOD(wvcas::CasStatus, GetOEMKeyToken,
|
||||
(OEMCrypto_SESSION entitled_key_session_id,
|
||||
std::vector<uint8_t>& token));
|
||||
};
|
||||
|
||||
#endif // MOCK_CRYPTO_SESSION_H
|
||||
45
tests/src/mock_ecm_parser.h
Normal file
45
tests/src/mock_ecm_parser.h
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#ifndef MOCK_ECM_PARSER_H
|
||||
#define MOCK_ECM_PARSER_H
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "ecm_parser.h"
|
||||
|
||||
class MockEcmParser : public wvcas::EcmParser {
|
||||
public:
|
||||
MOCK_METHOD(uint8_t, version, (), (const, override));
|
||||
MOCK_METHOD(uint8_t, age_restriction, (), (const, override));
|
||||
MOCK_METHOD(wvcas::CryptoMode, crypto_mode, (), (const, override));
|
||||
MOCK_METHOD(bool, rotation_enabled, (), (const, override));
|
||||
MOCK_METHOD(size_t, content_iv_size, (), (const, override));
|
||||
MOCK_METHOD(std::vector<uint8_t>, entitlement_key_id, (wvcas::KeySlotId id),
|
||||
(const, override));
|
||||
MOCK_METHOD(std::vector<uint8_t>, content_key_id, (wvcas::KeySlotId id),
|
||||
(const, override));
|
||||
MOCK_METHOD(std::vector<uint8_t>, wrapped_key_data, (wvcas::KeySlotId id),
|
||||
(const, override));
|
||||
MOCK_METHOD(std::vector<uint8_t>, wrapped_key_iv, (wvcas::KeySlotId id),
|
||||
(const, override));
|
||||
MOCK_METHOD(std::vector<uint8_t>, content_iv, (wvcas::KeySlotId id),
|
||||
(const, override));
|
||||
MOCK_METHOD(bool, set_group_id, (const std::string& group_id), (override));
|
||||
MOCK_METHOD(bool, has_fingerprinting, (), (const, override));
|
||||
MOCK_METHOD(video_widevine::Fingerprinting, fingerprinting, (),
|
||||
(const, override));
|
||||
MOCK_METHOD(bool, has_service_blocking, (), (const, override));
|
||||
MOCK_METHOD(video_widevine::ServiceBlocking, service_blocking, (),
|
||||
(const, override));
|
||||
MOCK_METHOD(std::string, ecm_serialized_payload, (), (const, override));
|
||||
MOCK_METHOD(std::string, signature, (), (const, override));
|
||||
MOCK_METHOD(bool, is_entitlement_rotation_enabled, (), (const, override));
|
||||
MOCK_METHOD(uint32_t, entitlement_period_index, (), (const, override));
|
||||
MOCK_METHOD(uint32_t, entitlement_rotation_window_left, (),
|
||||
(const, override));
|
||||
};
|
||||
|
||||
#endif // MOCK_ECM_PARSER_H
|
||||
47
tests/src/mock_event_listener.h
Normal file
47
tests/src/mock_event_listener.h
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#ifndef MOCK_EVENT_LISTENER_H
|
||||
#define MOCK_EVENT_LISTENER_H
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "cas_types.h"
|
||||
|
||||
class MockEventListener : public wvcas::CasEventListener {
|
||||
public:
|
||||
MockEventListener() {}
|
||||
virtual ~MockEventListener() {}
|
||||
|
||||
MOCK_METHOD(void, OnSessionRenewalNeeded, (), (override));
|
||||
MOCK_METHOD(void, OnSessionKeysChange,
|
||||
(const wvcas::KeyStatusMap& keys_status, bool has_new_usable_key),
|
||||
(override));
|
||||
MOCK_METHOD(void, OnExpirationUpdate, (int64_t new_expiry_time_seconds),
|
||||
(override));
|
||||
MOCK_METHOD(void, OnNewRenewalServerUrl,
|
||||
(const std::string& renewal_server_url), (override));
|
||||
MOCK_METHOD(void, OnLicenseExpiration, (), (override));
|
||||
MOCK_METHOD(void, OnAgeRestrictionUpdated,
|
||||
(const wvcas::WvCasSessionId& sessionId,
|
||||
uint8_t ecm_age_restriction),
|
||||
(override));
|
||||
MOCK_METHOD(void, OnSessionFingerprintingUpdated,
|
||||
(const wvcas::WvCasSessionId& sessionId,
|
||||
const std::vector<uint8_t>& fingerprinting),
|
||||
(override));
|
||||
MOCK_METHOD(void, OnSessionServiceBlockingUpdated,
|
||||
(const wvcas::WvCasSessionId& sessionId,
|
||||
const std::vector<uint8_t>& service_blocking),
|
||||
(override));
|
||||
MOCK_METHOD(void, OnFingerprintingUpdated,
|
||||
(const std::vector<uint8_t>& fingerprinting), (override));
|
||||
MOCK_METHOD(void, OnServiceBlockingUpdated,
|
||||
(const std::vector<uint8_t>& service_blocking), (override));
|
||||
MOCK_METHOD(void, OnEntitlementPeriodUpdateNeeded,
|
||||
(const std::string& signed_license_request), (override));
|
||||
};
|
||||
|
||||
#endif // MOCK_EVENT_LISTENER_H
|
||||
340
tests/src/policy_engine_test.cpp
Normal file
340
tests/src/policy_engine_test.cpp
Normal file
@@ -0,0 +1,340 @@
|
||||
#include "policy_engine.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "cas_types.h"
|
||||
#include "cas_util.h"
|
||||
#include "clock.h"
|
||||
#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;
|
||||
using ::testing::NotNull;
|
||||
using ::testing::Return;
|
||||
using ::testing::Sequence;
|
||||
using ::testing::SetArgPointee;
|
||||
using ::testing::StrictMock;
|
||||
using wvcas::HdcpCapability;
|
||||
using wvcas::KeyAllowedUsage;
|
||||
using wvcas::KeyId;
|
||||
using wvcas::KeyStatus;
|
||||
using wvcas::KeyStatusMap;
|
||||
using wvcas::WidevinePsshData_EntitledKey;
|
||||
|
||||
namespace {
|
||||
const int64_t kDurationUnlimited = 0;
|
||||
const int64_t kLicenseStartTime = 1413517500; // ~ 01/01/2013
|
||||
const int64_t kPlaybackStartTime = kLicenseStartTime + 5;
|
||||
const int64_t kRentalDuration = 604800; // 7 days
|
||||
const int64_t kPlaybackDuration = 172800; // 48 hours
|
||||
const int64_t kLicenseDuration = kRentalDuration + kPlaybackDuration;
|
||||
const int64_t kLicenseRenewalPeriod = 120; // 2 minutes
|
||||
const int64_t kLicenseRenewalRetryInterval = 30; // 30 seconds
|
||||
const int64_t kLicenseRenewalRecoveryDuration = 30; // 30 seconds
|
||||
const int64_t kLowDuration = 300; // 5 minutes
|
||||
const int64_t kHighDuration =
|
||||
std::max(std::max(kRentalDuration, kPlaybackDuration), kLicenseDuration);
|
||||
const char* kRenewalServerUrl =
|
||||
"https://test.google.com/license/GetCencLicense";
|
||||
const KeyId kKeyId = "357adc89f1673433c36c621f1b5c41ee";
|
||||
const KeyId kEntitlementKeyId = "entitlementkeyid";
|
||||
const KeyId kAnotherKeyId = "another_key_id";
|
||||
const KeyId kSomeRandomKeyId = "some_random_key_id";
|
||||
const KeyId kUnknownKeyId = "some_random_unknown_key_id";
|
||||
} // namespace
|
||||
|
||||
class MockClock : public wvutil::Clock {
|
||||
public:
|
||||
MockClock() {}
|
||||
virtual ~MockClock() {}
|
||||
|
||||
MOCK_METHOD0(GetCurrentTime, int64_t());
|
||||
};
|
||||
|
||||
class MockLicenseKeys : public wvcas::LicenseKeys {
|
||||
public:
|
||||
MockLicenseKeys() {}
|
||||
virtual ~MockLicenseKeys() {}
|
||||
|
||||
MOCK_METHOD0(Empty, bool());
|
||||
MOCK_METHOD1(IsContentKey, bool(const KeyId& key_id));
|
||||
MOCK_METHOD1(CanDecryptContent, bool(const KeyId& key_id));
|
||||
MOCK_METHOD2(GetAllowedUsage,
|
||||
bool(const KeyId& key_id, KeyAllowedUsage* allowed_usage));
|
||||
MOCK_METHOD2(ApplyStatusChange,
|
||||
bool(KeyStatus new_status, bool* new_usable_keys));
|
||||
MOCK_METHOD1(GetKeyStatus, KeyStatus(const KeyId& key_id));
|
||||
MOCK_METHOD1(ExtractKeyStatuses, void(KeyStatusMap* content_keys));
|
||||
MOCK_METHOD1(MeetsConstraints, bool(const KeyId& key_id));
|
||||
MOCK_METHOD2(ApplyConstraints,
|
||||
void(uint32_t new_resolution, HdcpCapability new_hdcp_level));
|
||||
MOCK_METHOD1(SetFromLicense, void(const video_widevine::License& license));
|
||||
MOCK_METHOD1(SetEntitledKeys,
|
||||
void(const std::vector<WidevinePsshData_EntitledKey>& keys));
|
||||
};
|
||||
|
||||
class TestablePolicyEngine : public wvcas::PolicyEngine {
|
||||
std::unique_ptr<wvcas::LicenseKeys> CreateLicenseKeys() override {
|
||||
std::unique_ptr<StrictMock<MockLicenseKeys> > license_keys =
|
||||
make_unique<StrictMock<MockLicenseKeys> >();
|
||||
license_keys_ = license_keys.get();
|
||||
return license_keys;
|
||||
}
|
||||
std::unique_ptr<wvutil::Clock> CreateClock() override {
|
||||
std::unique_ptr<StrictMock<MockClock> > clock =
|
||||
make_unique<StrictMock<MockClock> >();
|
||||
clock_ = clock.get();
|
||||
return clock;
|
||||
}
|
||||
|
||||
public:
|
||||
MockClock* clock_ = nullptr;
|
||||
MockLicenseKeys* license_keys_ = nullptr;
|
||||
};
|
||||
|
||||
class PolicyEngineTest : public ::testing::Test {
|
||||
public:
|
||||
PolicyEngineTest() {}
|
||||
virtual ~PolicyEngineTest() {}
|
||||
|
||||
void SetUp() {
|
||||
crypto_session_ = std::make_shared<StrictMock<MockCryptoSession> >();
|
||||
policy_engine_.initialize(crypto_session_, &event_listener_);
|
||||
|
||||
ASSERT_NE(policy_engine_.clock_, nullptr);
|
||||
ASSERT_NE(policy_engine_.license_keys_, nullptr);
|
||||
|
||||
license_.set_license_start_time(kLicenseStartTime);
|
||||
video_widevine::LicenseIdentification* id = license_.mutable_id();
|
||||
id->set_version(1);
|
||||
id->set_type(video_widevine::STREAMING);
|
||||
video_widevine::License::KeyContainer* key = license_.add_key();
|
||||
key->set_type(video_widevine::License::KeyContainer::CONTENT);
|
||||
key->set_id(kKeyId);
|
||||
video_widevine::License_Policy* policy = license_.mutable_policy();
|
||||
policy = license_.mutable_policy();
|
||||
policy->set_can_play(true);
|
||||
policy->set_can_persist(false);
|
||||
policy->set_can_renew(false);
|
||||
// This is similar to an OFFLINE policy.
|
||||
policy->set_rental_duration_seconds(kRentalDuration);
|
||||
policy->set_playback_duration_seconds(kPlaybackDuration);
|
||||
policy->set_license_duration_seconds(kLicenseDuration);
|
||||
policy->set_renewal_recovery_duration_seconds(
|
||||
kLicenseRenewalRecoveryDuration);
|
||||
policy->set_renewal_delay_seconds(0);
|
||||
policy->set_renewal_retry_interval_seconds(kLicenseRenewalRetryInterval);
|
||||
policy->set_renew_with_usage(false);
|
||||
}
|
||||
|
||||
MockEventListener event_listener_;
|
||||
std::shared_ptr<StrictMock<MockCryptoSession> > crypto_session_;
|
||||
video_widevine::License license_;
|
||||
TestablePolicyEngine policy_engine_;
|
||||
};
|
||||
|
||||
TEST_F(PolicyEngineTest, CanDecryptContent) {
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, IsContentKey(kKeyId))
|
||||
.WillOnce(Return(false))
|
||||
.WillOnce(Return(true))
|
||||
.WillOnce(Return(true));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, CanDecryptContent(kKeyId))
|
||||
.WillOnce(Return(false))
|
||||
.WillOnce(Return(true));
|
||||
|
||||
// IsContentKey == false : CanDecryptContent is not called.
|
||||
EXPECT_FALSE(policy_engine_.CanDecryptContent(kKeyId));
|
||||
// IsContentKey == true : CanDecryptContent is false.
|
||||
EXPECT_FALSE(policy_engine_.CanDecryptContent(kKeyId));
|
||||
// IsContentKey == true : CanDecryptContent is true.
|
||||
EXPECT_TRUE(policy_engine_.CanDecryptContent(kKeyId));
|
||||
}
|
||||
|
||||
TEST_F(PolicyEngineTest, SetLicense_NoKey) {
|
||||
video_widevine::License license;
|
||||
license.mutable_policy();
|
||||
license.set_license_start_time(kLicenseStartTime);
|
||||
|
||||
EXPECT_FALSE(policy_engine_.IsExpired());
|
||||
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, SetFromLicense(_));
|
||||
EXPECT_CALL(*policy_engine_.clock_, GetCurrentTime())
|
||||
.WillOnce(Return(kLicenseStartTime + 1));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_,
|
||||
ApplyStatusChange(wvcas::kKeyStatusExpired, _))
|
||||
.WillOnce(Return(false));
|
||||
EXPECT_CALL(event_listener_, OnLicenseExpiration);
|
||||
policy_engine_.SetLicense(license);
|
||||
EXPECT_TRUE(policy_engine_.IsExpired());
|
||||
}
|
||||
|
||||
TEST_F(PolicyEngineTest, PlaybackSuccess_OfflineLicense) {
|
||||
// CryptoSession
|
||||
EXPECT_CALL(*crypto_session_, GetHdcpCapabilities(NotNull(), NotNull()))
|
||||
.WillRepeatedly(
|
||||
DoAll(SetArgPointee<0>(HDCP_NO_DIGITAL_OUTPUT), Return(true)));
|
||||
EXPECT_CALL(*policy_engine_.clock_, GetCurrentTime())
|
||||
.WillOnce(Return(kLicenseStartTime + 1))
|
||||
.WillOnce(Return(kPlaybackStartTime))
|
||||
.WillOnce(Return(kLicenseStartTime + 10));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, Empty())
|
||||
.WillOnce(Return(true))
|
||||
.WillRepeatedly(Return(false));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, SetFromLicense(_));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_,
|
||||
ApplyStatusChange(wvcas::kKeyStatusUsable, NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<1>(true), Return(true)))
|
||||
.WillRepeatedly(DoAll(SetArgPointee<1>(false), Return(false)));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, ExtractKeyStatuses(NotNull()));
|
||||
EXPECT_CALL(event_listener_, OnSessionKeysChange(_, true));
|
||||
EXPECT_CALL(event_listener_,
|
||||
OnExpirationUpdate(kLicenseStartTime + kRentalDuration));
|
||||
EXPECT_CALL(event_listener_,
|
||||
OnExpirationUpdate(kPlaybackStartTime + kPlaybackDuration));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_,
|
||||
ApplyConstraints(_, HDCP_NO_DIGITAL_OUTPUT))
|
||||
.Times(2);
|
||||
|
||||
policy_engine_.SetLicense(license_);
|
||||
policy_engine_.BeginDecryption();
|
||||
policy_engine_.OnTimerEvent();
|
||||
}
|
||||
|
||||
TEST_F(PolicyEngineTest, PlaybackSuccess_EntitlementLicenseExpiration) {
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, SetFromLicense(_));
|
||||
EXPECT_CALL(*policy_engine_.clock_, GetCurrentTime())
|
||||
.WillOnce(Return(kLicenseStartTime + 1))
|
||||
.WillOnce(Return(kPlaybackStartTime))
|
||||
.WillOnce(Return(kPlaybackStartTime + kPlaybackDuration + 10));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, Empty())
|
||||
.WillOnce(Return(true))
|
||||
.WillRepeatedly(Return(false));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_,
|
||||
ApplyStatusChange(wvcas::kKeyStatusUsable, NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<1>(true), Return(true)))
|
||||
.WillRepeatedly(DoAll(SetArgPointee<1>(false), Return(false)));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_,
|
||||
ApplyStatusChange(wvcas::kKeyStatusExpired, NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<1>(true), Return(false)));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, ExtractKeyStatuses(NotNull()));
|
||||
EXPECT_CALL(event_listener_, OnSessionKeysChange(_, true));
|
||||
EXPECT_CALL(event_listener_, OnLicenseExpiration);
|
||||
EXPECT_CALL(event_listener_,
|
||||
OnExpirationUpdate(kLicenseStartTime + kRentalDuration));
|
||||
EXPECT_CALL(event_listener_,
|
||||
OnExpirationUpdate(kPlaybackStartTime + kPlaybackDuration));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, SetEntitledKeys(_));
|
||||
|
||||
video_widevine::License::KeyContainer* key = license_.mutable_key(0);
|
||||
key->set_type(video_widevine::License::KeyContainer::ENTITLEMENT);
|
||||
key->set_id(kEntitlementKeyId);
|
||||
|
||||
EXPECT_FALSE(policy_engine_.IsExpired());
|
||||
policy_engine_.SetLicense(license_);
|
||||
policy_engine_.BeginDecryption();
|
||||
policy_engine_.OnTimerEvent();
|
||||
|
||||
std::vector<WidevinePsshData_EntitledKey> entitled_keys(1);
|
||||
entitled_keys[0].set_entitlement_key_id(kEntitlementKeyId);
|
||||
entitled_keys[0].set_key_id(kKeyId);
|
||||
policy_engine_.SetEntitledLicenseKeys(entitled_keys);
|
||||
EXPECT_TRUE(policy_engine_.IsExpired());
|
||||
}
|
||||
|
||||
TEST_F(PolicyEngineTest, PlaybackSuccess_StreamingLicense) {
|
||||
video_widevine::License_Policy* policy = license_.mutable_policy();
|
||||
policy->set_license_duration_seconds(kLowDuration);
|
||||
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, SetFromLicense(_));
|
||||
EXPECT_CALL(*policy_engine_.clock_, GetCurrentTime())
|
||||
.WillOnce(Return(kLicenseStartTime + 1))
|
||||
.WillOnce(Return(kPlaybackStartTime))
|
||||
.WillOnce(Return(kLicenseStartTime + 10));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, Empty())
|
||||
.WillOnce(Return(true))
|
||||
.WillRepeatedly(Return(false));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_,
|
||||
ApplyStatusChange(wvcas::kKeyStatusUsable, NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<1>(true), Return(true)))
|
||||
.WillRepeatedly(DoAll(SetArgPointee<1>(false), Return(false)));
|
||||
EXPECT_CALL(event_listener_, OnSessionKeysChange(_, true));
|
||||
EXPECT_CALL(event_listener_,
|
||||
OnExpirationUpdate(kLicenseStartTime + kLowDuration));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, ExtractKeyStatuses(NotNull()));
|
||||
EXPECT_CALL(*crypto_session_, GetHdcpCapabilities(_, _))
|
||||
.WillRepeatedly(
|
||||
DoAll(SetArgPointee<0>(HDCP_NO_DIGITAL_OUTPUT), Return(false)));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, ApplyConstraints(_, HDCP_NONE))
|
||||
.Times(2);
|
||||
|
||||
policy_engine_.SetLicense(license_);
|
||||
policy_engine_.BeginDecryption();
|
||||
policy_engine_.OnTimerEvent();
|
||||
}
|
||||
|
||||
TEST_F(PolicyEngineTest, RenewalEvents) {
|
||||
video_widevine::License_Policy* policy = license_.mutable_policy();
|
||||
policy->set_license_duration_seconds(kLowDuration);
|
||||
policy->set_can_renew(true);
|
||||
policy->set_renewal_delay_seconds(kLicenseRenewalPeriod);
|
||||
;
|
||||
policy->set_renewal_retry_interval_seconds(kLicenseRenewalRetryInterval);
|
||||
|
||||
{
|
||||
Sequence set_license;
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, SetFromLicense(_));
|
||||
EXPECT_CALL(*policy_engine_.clock_, GetCurrentTime())
|
||||
.WillOnce(Return(kLicenseStartTime + 1));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, Empty()).WillOnce(Return(true));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_,
|
||||
ApplyStatusChange(wvcas::kKeyStatusUsable, NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<1>(true), Return(true)));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, ExtractKeyStatuses(NotNull()));
|
||||
EXPECT_CALL(event_listener_,
|
||||
OnExpirationUpdate(kLicenseStartTime + kLowDuration));
|
||||
EXPECT_CALL(event_listener_, OnSessionKeysChange(_, true));
|
||||
policy_engine_.SetLicense(license_);
|
||||
}
|
||||
|
||||
{
|
||||
Sequence begin_decryption;
|
||||
EXPECT_CALL(*policy_engine_.clock_, GetCurrentTime())
|
||||
.WillOnce(Return(kPlaybackStartTime));
|
||||
policy_engine_.BeginDecryption();
|
||||
}
|
||||
|
||||
EXPECT_CALL(*crypto_session_, GetHdcpCapabilities(_, _))
|
||||
.WillRepeatedly(
|
||||
DoAll(SetArgPointee<0>(HDCP_NO_DIGITAL_OUTPUT), Return(false)));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_,
|
||||
ApplyStatusChange(wvcas::kKeyStatusUsable, NotNull()))
|
||||
.WillRepeatedly(DoAll(SetArgPointee<1>(false), Return(false)));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, Empty())
|
||||
.WillRepeatedly(Return(false));
|
||||
|
||||
{
|
||||
Sequence on_timer;
|
||||
EXPECT_CALL(*policy_engine_.clock_, GetCurrentTime())
|
||||
.WillOnce(Return(kLicenseStartTime + 10));
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, ApplyConstraints(_, HDCP_NONE))
|
||||
.Times(2);
|
||||
policy_engine_.OnTimerEvent();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(PolicyEngineTest, RenewalUrl) {
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, SetFromLicense(_));
|
||||
EXPECT_CALL(*policy_engine_.clock_, GetCurrentTime());
|
||||
EXPECT_CALL(*policy_engine_.license_keys_, ApplyStatusChange(_, NotNull()));
|
||||
EXPECT_CALL(event_listener_, OnExpirationUpdate(_));
|
||||
EXPECT_CALL(event_listener_,
|
||||
OnNewRenewalServerUrl(::testing::StrEq(kRenewalServerUrl)));
|
||||
|
||||
video_widevine::License_Policy* policy = license_.mutable_policy();
|
||||
policy->set_renewal_server_url(kRenewalServerUrl);
|
||||
policy_engine_.SetLicense(license_);
|
||||
}
|
||||
71
tests/src/test_properties.cpp
Normal file
71
tests/src/test_properties.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "cas_properties.h"
|
||||
|
||||
// TODO(widevine-eng): Reevaluate using "www" in the company name and model name
|
||||
// fields. For now this is consistent with the values used in unit cdm testing.
|
||||
static constexpr char kGenericCompanyName[] = "www";
|
||||
static constexpr char kGenericModelName[] = "www";
|
||||
static constexpr char kProductName[] = "WidevineCasTests";
|
||||
static constexpr char kKeyArchitectureName[] = "architecture_name";
|
||||
static constexpr char kKeyDeviceName[] = "device_name";
|
||||
static constexpr char kOemcPath[] = "cas_oemc_path.so";
|
||||
|
||||
namespace wvcas {
|
||||
|
||||
bool Properties::GetCompanyName(std::string* company_name) {
|
||||
if (company_name == nullptr) {
|
||||
return false;
|
||||
}
|
||||
*company_name = kGenericCompanyName;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Properties::GetModelName(std::string* model_name) {
|
||||
if (model_name == nullptr) {
|
||||
return false;
|
||||
}
|
||||
*model_name = kGenericModelName;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Properties::GetProductName(std::string* product_name) {
|
||||
if (product_name == nullptr) {
|
||||
return false;
|
||||
}
|
||||
*product_name = kProductName;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Properties::GetArchitectureName(std::string* arch_name) {
|
||||
if (arch_name == nullptr) {
|
||||
return false;
|
||||
}
|
||||
*arch_name = kKeyArchitectureName;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Properties::GetDeviceName(std::string* device_name) {
|
||||
if (device_name == nullptr) {
|
||||
return false;
|
||||
}
|
||||
*device_name = kKeyDeviceName;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Properties::GetOEMCryptoPath(std::string* path) {
|
||||
if (path == nullptr) {
|
||||
return false;
|
||||
}
|
||||
*path = kOemcPath;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Properties::GetWvCasPluginVersion(std::string& version) {
|
||||
version = "unit-test";
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace wvcas
|
||||
35
tests/src/timer_test.cpp
Normal file
35
tests/src/timer_test.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "timer.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "clock.h"
|
||||
|
||||
class TimerTest : public wvutil::TimerHandler, public ::testing::Test {
|
||||
public:
|
||||
void OnTimerEvent() override { ++count_; }
|
||||
wvutil::Clock clock_;
|
||||
wvutil::Timer timer_;
|
||||
uint64_t event_time_ = 0;
|
||||
uint64_t count_ = 0;
|
||||
};
|
||||
|
||||
TEST_F(TimerTest, Timer) {
|
||||
timer_.Start(this, 1);
|
||||
EXPECT_TRUE(timer_.IsRunning());
|
||||
sleep(2);
|
||||
timer_.Stop();
|
||||
EXPECT_EQ(2, count_);
|
||||
EXPECT_FALSE(timer_.IsRunning());
|
||||
|
||||
timer_.Start(this, 1);
|
||||
EXPECT_TRUE(timer_.IsRunning());
|
||||
sleep(3);
|
||||
timer_.Stop();
|
||||
EXPECT_EQ(5, count_);
|
||||
EXPECT_FALSE(timer_.IsRunning());
|
||||
}
|
||||
1396
tests/src/widevine_cas_api_test.cpp
Normal file
1396
tests/src/widevine_cas_api_test.cpp
Normal file
File diff suppressed because it is too large
Load Diff
436
tests/src/widevine_cas_session_test.cpp
Normal file
436
tests/src/widevine_cas_session_test.cpp
Normal file
@@ -0,0 +1,436 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "widevine_cas_session.h"
|
||||
|
||||
#include <cas_events.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "cas_types.h"
|
||||
#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 wvcas {
|
||||
namespace {
|
||||
using ::testing::_;
|
||||
using ::testing::DoAll;
|
||||
using ::testing::Eq;
|
||||
using ::testing::Invoke;
|
||||
using ::testing::NiceMock;
|
||||
using ::testing::NotNull;
|
||||
using ::testing::Return;
|
||||
using ::testing::SetArgPointee;
|
||||
using ::testing::StrictMock;
|
||||
|
||||
static const char kEvenEntitlementKeyId[] = "even_entitlement_key_id";
|
||||
static const char kOddEntitlementKeyId[] = "odd_entitlement_key_id";
|
||||
static const char kEvenKeyId[] = "even_key_id";
|
||||
static const char kOddKeyId[] = "odd_key_id";
|
||||
static const char kEvenWrappedKey[] = "even_wrapped_content_key";
|
||||
static const char kOddWrappedKey[] = "odd_wrapped_content_key";
|
||||
static const char kEvenWrappedKeyIv[] = "even_wrapped_content_key_iv";
|
||||
static const char kOddWrappedKeyIv[] = "odd_wrapped_content_key_iv";
|
||||
static const char kEvenContentIv[] = "even_content_iv";
|
||||
static const char kOddContentIv[] = "odd_content_iv";
|
||||
static const OEMCrypto_SESSION kEntitledKeySessionId = 0x1111;
|
||||
constexpr char kEmptyGroupId[] = "";
|
||||
|
||||
MATCHER(IsValidKeyEvenSlotData, "") {
|
||||
if (nullptr == arg) {
|
||||
*result_listener << " keyslot is nullptr";
|
||||
return false;
|
||||
}
|
||||
std::string value = kEvenEntitlementKeyId;
|
||||
if (arg->entitlement_key_id !=
|
||||
std::vector<uint8_t>(value.begin(), value.end())) {
|
||||
*result_listener << " entitlement key_id is invalid";
|
||||
return false;
|
||||
}
|
||||
value = kEvenKeyId;
|
||||
if (arg->key_id != std::vector<uint8_t>(value.begin(), value.end())) {
|
||||
*result_listener << " key_id is invalid";
|
||||
return false;
|
||||
}
|
||||
value = kEvenWrappedKey;
|
||||
if (arg->wrapped_key != std::vector<uint8_t>(value.begin(), value.end())) {
|
||||
*result_listener << " wrapped_key is invalid";
|
||||
return false;
|
||||
}
|
||||
value = kEvenWrappedKeyIv;
|
||||
if (arg->wrapped_key_iv != std::vector<uint8_t>(value.begin(), value.end())) {
|
||||
*result_listener << " wapped_key_iv is invalid";
|
||||
return false;
|
||||
}
|
||||
value = kEvenContentIv;
|
||||
if (arg->content_iv != std::vector<uint8_t>(value.begin(), value.end())) {
|
||||
*result_listener << " content_iv is invalid";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
MATCHER(IsValidKeyOddSlotData, "") {
|
||||
if (nullptr == arg) {
|
||||
*result_listener << " keyslot is nullptr";
|
||||
return false;
|
||||
}
|
||||
std::string value = kOddEntitlementKeyId;
|
||||
if (arg->entitlement_key_id !=
|
||||
std::vector<uint8_t>(value.begin(), value.end())) {
|
||||
*result_listener << " entitlement key_id is invalid";
|
||||
return false;
|
||||
}
|
||||
value = kOddKeyId;
|
||||
if (arg->key_id != std::vector<uint8_t>(value.begin(), value.end())) {
|
||||
*result_listener << " key_id is invalid";
|
||||
return false;
|
||||
}
|
||||
value = kOddWrappedKey;
|
||||
if (arg->wrapped_key != std::vector<uint8_t>(value.begin(), value.end())) {
|
||||
*result_listener << " wrapped_key is invalid";
|
||||
return false;
|
||||
}
|
||||
value = kOddWrappedKeyIv;
|
||||
if (arg->wrapped_key_iv != std::vector<uint8_t>(value.begin(), value.end())) {
|
||||
*result_listener << " wapped_key_iv is invalid";
|
||||
return false;
|
||||
}
|
||||
value = kOddContentIv;
|
||||
if (arg->content_iv != std::vector<uint8_t>(value.begin(), value.end())) {
|
||||
*result_listener << " content_iv is invalid";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
class CasSessionTest : public ::testing::Test {
|
||||
public:
|
||||
CasSessionTest() {}
|
||||
virtual ~CasSessionTest() {}
|
||||
|
||||
void SetUp() override {
|
||||
mock_crypto_session_ = std::make_shared<MockCryptoSession>();
|
||||
ON_CALL(*mock_crypto_session_, CreateEntitledKeySession(NotNull()))
|
||||
.WillByDefault(DoAll(SetArgPointee<0>(kEntitledKeySessionId),
|
||||
Return(wvcas::CasStatusCode::kNoError)));
|
||||
ON_CALL(*mock_crypto_session_, GetOEMKeyToken(kEntitledKeySessionId, _))
|
||||
.WillByDefault([&](OEMCrypto_SESSION, std::vector<uint8_t>& token) {
|
||||
token.assign(expected_session_id_.begin(),
|
||||
expected_session_id_.end());
|
||||
return CasStatusCode::kNoError;
|
||||
});
|
||||
}
|
||||
|
||||
std::shared_ptr<MockCryptoSession> mock_crypto_session_;
|
||||
WvCasSessionId expected_session_id_ = {0x01, 0x02, 0x03};
|
||||
};
|
||||
|
||||
// Allow getEcmParser to return a mocked ecm.
|
||||
class TestCasSession : public wvcas::WidevineCasSession {
|
||||
public:
|
||||
TestCasSession() {}
|
||||
virtual ~TestCasSession() {}
|
||||
|
||||
std::unique_ptr<wvcas::EcmParser> getEcmParser(
|
||||
const wvcas::CasEcm& ecm) const override;
|
||||
|
||||
std::vector<uint8_t> entitlement_key_id(wvcas::KeySlotId id) const {
|
||||
std::string key_id;
|
||||
if (id == wvcas::KeySlotId::kEvenKeySlot) {
|
||||
key_id = kEvenEntitlementKeyId;
|
||||
} else if (id == wvcas::KeySlotId::kOddKeySlot) {
|
||||
key_id = kOddEntitlementKeyId;
|
||||
}
|
||||
return std::vector<uint8_t>(key_id.begin(), key_id.end());
|
||||
}
|
||||
|
||||
std::vector<uint8_t> content_key_id(wvcas::KeySlotId id) const {
|
||||
std::string key_id;
|
||||
if (id == wvcas::KeySlotId::kEvenKeySlot) {
|
||||
key_id = kEvenKeyId;
|
||||
} else if (id == wvcas::KeySlotId::kOddKeySlot) {
|
||||
key_id = kOddKeyId;
|
||||
}
|
||||
return std::vector<uint8_t>(key_id.begin(), key_id.end());
|
||||
}
|
||||
|
||||
std::vector<uint8_t> wrapped_key_data(wvcas::KeySlotId id) const {
|
||||
std::string key;
|
||||
if (id == wvcas::KeySlotId::kEvenKeySlot) {
|
||||
key = kEvenWrappedKey;
|
||||
} else if (id == wvcas::KeySlotId::kOddKeySlot) {
|
||||
key = kOddWrappedKey;
|
||||
}
|
||||
return std::vector<uint8_t>(key.begin(), key.end());
|
||||
}
|
||||
|
||||
std::vector<uint8_t> wrapped_key_iv(wvcas::KeySlotId id) const {
|
||||
std::string iv;
|
||||
if (id == wvcas::KeySlotId::kEvenKeySlot) {
|
||||
iv = kEvenWrappedKeyIv;
|
||||
} else if (id == wvcas::KeySlotId::kOddKeySlot) {
|
||||
iv = kOddWrappedKeyIv;
|
||||
}
|
||||
return std::vector<uint8_t>(iv.begin(), iv.end());
|
||||
}
|
||||
|
||||
std::vector<uint8_t> content_iv(wvcas::KeySlotId id) const {
|
||||
std::string iv;
|
||||
if (id == wvcas::KeySlotId::kEvenKeySlot) {
|
||||
iv = kEvenContentIv;
|
||||
} else if (id == wvcas::KeySlotId::kOddKeySlot) {
|
||||
iv = kOddContentIv;
|
||||
}
|
||||
return std::vector<uint8_t>(iv.begin(), iv.end());
|
||||
}
|
||||
|
||||
void set_age_restriction(uint8_t age_restriction) {
|
||||
age_restriction_ = age_restriction;
|
||||
}
|
||||
|
||||
void set_fingerprinting_control(const std::string& control) {
|
||||
fingerprinting_.clear_control();
|
||||
if (!control.empty()) {
|
||||
fingerprinting_.set_control(control);
|
||||
}
|
||||
}
|
||||
|
||||
void set_service_blocking_groups(const std::vector<std::string>& groups) {
|
||||
service_blocking_.clear_device_groups();
|
||||
for (auto const& group : groups) {
|
||||
service_blocking_.add_device_groups(group);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t age_restriction_ = 0;
|
||||
video_widevine::Fingerprinting fingerprinting_;
|
||||
video_widevine::ServiceBlocking service_blocking_;
|
||||
};
|
||||
|
||||
std::unique_ptr<wvcas::EcmParser> TestCasSession::getEcmParser(
|
||||
const wvcas::CasEcm& ecm) const {
|
||||
std::unique_ptr<NiceMock<MockEcmParser>> mock_ecm_parser(
|
||||
new NiceMock<MockEcmParser>);
|
||||
ON_CALL(*mock_ecm_parser, age_restriction())
|
||||
.WillByDefault(Return(age_restriction_));
|
||||
ON_CALL(*mock_ecm_parser, crypto_mode())
|
||||
.WillByDefault(Return(wvcas::CryptoMode::kAesCTR));
|
||||
ON_CALL(*mock_ecm_parser, rotation_enabled()).WillByDefault(Return(true));
|
||||
ON_CALL(*mock_ecm_parser, entitlement_key_id(_))
|
||||
.WillByDefault(Invoke(this, &TestCasSession::entitlement_key_id));
|
||||
ON_CALL(*mock_ecm_parser, content_key_id(_))
|
||||
.WillByDefault(Invoke(this, &TestCasSession::content_key_id));
|
||||
ON_CALL(*mock_ecm_parser, wrapped_key_data(_))
|
||||
.WillByDefault(Invoke(this, &TestCasSession::wrapped_key_data));
|
||||
ON_CALL(*mock_ecm_parser, wrapped_key_iv(_))
|
||||
.WillByDefault(Invoke(this, &TestCasSession::wrapped_key_iv));
|
||||
ON_CALL(*mock_ecm_parser, content_iv(_))
|
||||
.WillByDefault(Invoke(this, &TestCasSession::content_iv));
|
||||
ON_CALL(*mock_ecm_parser, set_group_id(_)).WillByDefault(Return(true));
|
||||
ON_CALL(*mock_ecm_parser, has_fingerprinting())
|
||||
.WillByDefault(Return(fingerprinting_.has_control()));
|
||||
ON_CALL(*mock_ecm_parser, fingerprinting())
|
||||
.WillByDefault(Return(fingerprinting_));
|
||||
ON_CALL(*mock_ecm_parser, has_service_blocking())
|
||||
.WillByDefault(Return(service_blocking_.device_groups_size() > 0));
|
||||
ON_CALL(*mock_ecm_parser, service_blocking())
|
||||
.WillByDefault(Return(service_blocking_));
|
||||
return std::unique_ptr<wvcas::EcmParser>(mock_ecm_parser.release());
|
||||
}
|
||||
|
||||
TEST_F(CasSessionTest, sessionInitializeTest) {
|
||||
TestCasSession session;
|
||||
WvCasSessionId session_id;
|
||||
|
||||
ASSERT_EQ(session
|
||||
.initialize(mock_crypto_session_, /*event_listener=*/nullptr,
|
||||
&session_id)
|
||||
.status_code(),
|
||||
CasStatusCode::kNoError);
|
||||
|
||||
EXPECT_EQ(session_id, expected_session_id_);
|
||||
}
|
||||
|
||||
TEST_F(CasSessionTest, processEcm) {
|
||||
TestCasSession session;
|
||||
WvCasSessionId session_id;
|
||||
ASSERT_EQ(session
|
||||
.initialize(mock_crypto_session_, /*event_listener=*/nullptr,
|
||||
&session_id)
|
||||
.status_code(),
|
||||
CasStatusCode::kNoError);
|
||||
|
||||
wvcas::CasEcm ecm(184);
|
||||
EXPECT_CALL(*mock_crypto_session_,
|
||||
LoadCasECMKeys(kEntitledKeySessionId, IsValidKeyEvenSlotData(),
|
||||
IsValidKeyOddSlotData()));
|
||||
session.processEcm(ecm, 0, kEmptyGroupId);
|
||||
EXPECT_CALL(*mock_crypto_session_,
|
||||
RemoveEntitledKeySession(kEntitledKeySessionId));
|
||||
}
|
||||
|
||||
TEST_F(CasSessionTest, parentalControl) {
|
||||
TestCasSession session;
|
||||
WvCasSessionId session_id;
|
||||
ASSERT_EQ(session
|
||||
.initialize(mock_crypto_session_, /*event_listener=*/nullptr,
|
||||
&session_id)
|
||||
.status_code(),
|
||||
CasStatusCode::kNoError);
|
||||
|
||||
EXPECT_CALL(*mock_crypto_session_, LoadCasECMKeys(_, IsValidKeyEvenSlotData(),
|
||||
IsValidKeyOddSlotData()));
|
||||
wvcas::CasEcm ecm(184);
|
||||
session.set_age_restriction(0); // No restriction.
|
||||
// Different Ecm to make sure processEcm() processes this ecm.
|
||||
std::generate(ecm.begin(), ecm.end(), std::rand);
|
||||
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
session.processEcm(ecm, 0, kEmptyGroupId).status_code());
|
||||
std::generate(ecm.begin(), ecm.end(), std::rand);
|
||||
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
session.processEcm(ecm, 13, kEmptyGroupId).status_code());
|
||||
|
||||
// Parental control age must >= 10 (if non-zero).
|
||||
session.set_age_restriction(10);
|
||||
std::generate(ecm.begin(), ecm.end(), std::rand);
|
||||
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
session.processEcm(ecm, 0, kEmptyGroupId).status_code());
|
||||
std::generate(ecm.begin(), ecm.end(), std::rand);
|
||||
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
session.processEcm(ecm, 10, kEmptyGroupId).status_code());
|
||||
std::generate(ecm.begin(), ecm.end(), std::rand);
|
||||
ASSERT_EQ(wvcas::CasStatusCode::kNoError,
|
||||
session.processEcm(ecm, 13, kEmptyGroupId).status_code());
|
||||
std::generate(ecm.begin(), ecm.end(), std::rand);
|
||||
ASSERT_EQ(wvcas::CasStatusCode::kAccessDeniedByParentalControl,
|
||||
session.processEcm(ecm, 3, kEmptyGroupId).status_code());
|
||||
EXPECT_CALL(*mock_crypto_session_, RemoveEntitledKeySession(_));
|
||||
}
|
||||
|
||||
TEST_F(CasSessionTest, FingerprintingSuccess) {
|
||||
TestCasSession session;
|
||||
auto mock_crypto = std::make_shared<MockCryptoSession>();
|
||||
MockEventListener mock_listener;
|
||||
WvCasSessionId session_id;
|
||||
ASSERT_EQ(session.initialize(mock_crypto, &mock_listener, &session_id)
|
||||
.status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
session.set_fingerprinting_control("control");
|
||||
std::vector<uint8_t> expected_message = {0x00, 0x00, 0x07, 'c', 'o',
|
||||
'n', 't', 'r', 'o', 'l'};
|
||||
EXPECT_CALL(mock_listener,
|
||||
OnSessionFingerprintingUpdated(session_id, expected_message))
|
||||
.Times(1);
|
||||
|
||||
session.processEcm(wvcas::CasEcm(184, '0'), 0, kEmptyGroupId);
|
||||
}
|
||||
|
||||
TEST_F(CasSessionTest, RepeatedFingerprintingNoEventSuccess) {
|
||||
TestCasSession session;
|
||||
auto mock_crypto = std::make_shared<MockCryptoSession>();
|
||||
MockEventListener mock_listener;
|
||||
WvCasSessionId session_id;
|
||||
ASSERT_EQ(session.initialize(mock_crypto, &mock_listener, &session_id)
|
||||
.status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
session.set_fingerprinting_control("control");
|
||||
EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(1);
|
||||
|
||||
session.processEcm(wvcas::CasEcm(184, '0'), 0, kEmptyGroupId);
|
||||
// Same fingerprinting will not trigger event.
|
||||
EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(0);
|
||||
session.processEcm(wvcas::CasEcm(184, '1'), 0, kEmptyGroupId);
|
||||
}
|
||||
|
||||
TEST_F(CasSessionTest, DifferentFingerprintingTriggerEventSuccess) {
|
||||
TestCasSession session;
|
||||
auto mock_crypto = std::make_shared<MockCryptoSession>();
|
||||
MockEventListener mock_listener;
|
||||
WvCasSessionId session_id;
|
||||
ASSERT_EQ(session.initialize(mock_crypto, &mock_listener, &session_id)
|
||||
.status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
session.set_fingerprinting_control("control");
|
||||
EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(1);
|
||||
|
||||
session.processEcm(wvcas::CasEcm(184, '0'), 0, kEmptyGroupId);
|
||||
// Different fingerprinting will trigger event.
|
||||
session.set_fingerprinting_control("control2");
|
||||
EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(1);
|
||||
session.processEcm(wvcas::CasEcm(184, '1'), 0, kEmptyGroupId);
|
||||
// Different fingerprinting (including empty) will trigger event.
|
||||
session.set_fingerprinting_control("");
|
||||
EXPECT_CALL(mock_listener, OnSessionFingerprintingUpdated).Times(1);
|
||||
session.processEcm(wvcas::CasEcm(184, '2'), 0, kEmptyGroupId);
|
||||
}
|
||||
|
||||
TEST_F(CasSessionTest, ServiceBlockingSuccess) {
|
||||
TestCasSession session;
|
||||
auto mock_crypto = std::make_shared<MockCryptoSession>();
|
||||
MockEventListener mock_listener;
|
||||
WvCasSessionId session_id;
|
||||
ASSERT_EQ(session.initialize(mock_crypto, &mock_listener, &session_id)
|
||||
.status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
session.set_service_blocking_groups({"Group1", "g2"});
|
||||
std::vector<uint8_t> expected_message = {0x00, 0x00, 0x06, 'G', 'r',
|
||||
'o', 'u', 'p', '1', 0x00,
|
||||
0x00, 0x02, 'g', '2'};
|
||||
EXPECT_CALL(mock_listener,
|
||||
OnSessionServiceBlockingUpdated(session_id, expected_message))
|
||||
.Times(1);
|
||||
|
||||
session.processEcm(wvcas::CasEcm(184, '0'), 0, kEmptyGroupId);
|
||||
}
|
||||
|
||||
TEST_F(CasSessionTest, RepeatedServiceBlockingNoEventSuccess) {
|
||||
TestCasSession session;
|
||||
auto mock_crypto = std::make_shared<MockCryptoSession>();
|
||||
MockEventListener mock_listener;
|
||||
WvCasSessionId session_id;
|
||||
ASSERT_EQ(session.initialize(mock_crypto, &mock_listener, &session_id)
|
||||
.status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
session.set_service_blocking_groups({"Group1", "g2"});
|
||||
EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(1);
|
||||
|
||||
session.processEcm(wvcas::CasEcm(184, '0'), 0, kEmptyGroupId);
|
||||
EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(0);
|
||||
session.processEcm(wvcas::CasEcm(184, '1'), 0, kEmptyGroupId);
|
||||
}
|
||||
|
||||
TEST_F(CasSessionTest, DifferentServiceBlockingTriggerEventSuccess) {
|
||||
TestCasSession session;
|
||||
auto mock_crypto = std::make_shared<MockCryptoSession>();
|
||||
MockEventListener mock_listener;
|
||||
WvCasSessionId session_id;
|
||||
ASSERT_EQ(session.initialize(mock_crypto, &mock_listener, &session_id)
|
||||
.status_code(),
|
||||
wvcas::CasStatusCode::kNoError);
|
||||
session.set_service_blocking_groups({"Group1", "g2"});
|
||||
EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(1);
|
||||
|
||||
session.processEcm(wvcas::CasEcm(184, '0'), 0, kEmptyGroupId);
|
||||
EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(1);
|
||||
session.set_service_blocking_groups({"Group1"});
|
||||
session.processEcm(wvcas::CasEcm(184, '1'), 0, kEmptyGroupId);
|
||||
EXPECT_CALL(mock_listener, OnSessionServiceBlockingUpdated).Times(1);
|
||||
session.set_service_blocking_groups({});
|
||||
session.processEcm(wvcas::CasEcm(184, '2'), 0, kEmptyGroupId);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace wvcas
|
||||
227
tests/src/widevine_media_cas_plugin_test.cpp
Normal file
227
tests/src/widevine_media_cas_plugin_test.cpp
Normal file
@@ -0,0 +1,227 @@
|
||||
// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include "widevine_media_cas_plugin.h"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <utils/String8.h>
|
||||
|
||||
#include "cas_events.h"
|
||||
#include "cas_status.h"
|
||||
#include "media/cas/CasAPI.h"
|
||||
#include "media/stagefright/MediaErrors.h"
|
||||
#include "widevine_cas_api.h"
|
||||
|
||||
namespace android {
|
||||
// Minimalist implementation of Android string class to support test.
|
||||
std::map<const String8*, std::unique_ptr<std::string> > string8s;
|
||||
|
||||
String8::String8(const String8& value)
|
||||
: String8(value.c_str(), value.length()) {}
|
||||
|
||||
String8::String8(char const* data, size_t data_length) {
|
||||
auto result =
|
||||
string8s.emplace(this, make_unique<std::string>(data, data_length));
|
||||
mString = result.first->second->data();
|
||||
}
|
||||
|
||||
size_t String8::length() const {
|
||||
auto it = string8s.find(this);
|
||||
return it == string8s.end() ? 0 : it->second->size();
|
||||
}
|
||||
|
||||
String8::~String8() { string8s.erase(this); }
|
||||
|
||||
} // namespace android
|
||||
|
||||
namespace wvcas {
|
||||
namespace {
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::DoAll;
|
||||
using ::testing::Eq;
|
||||
using ::testing::IsNull;
|
||||
using ::testing::NotNull;
|
||||
using ::testing::Return;
|
||||
using ::testing::SetArgPointee;
|
||||
using ::testing::SetArgReferee;
|
||||
|
||||
class MockWidevineCas : public WidevineCas {
|
||||
public:
|
||||
MockWidevineCas() {}
|
||||
~MockWidevineCas() override {}
|
||||
|
||||
MOCK_METHOD(CasStatus, openSession, (WvCasSessionId * sessionId), (override));
|
||||
MOCK_METHOD(bool, is_provisioned, (), (const, override));
|
||||
MOCK_METHOD(CasStatus, generateEntitlementRequest,
|
||||
(const std::string& init_data, std::string* entitlement_request,
|
||||
std::string& license_id),
|
||||
(override));
|
||||
MOCK_METHOD(CasStatus, RecordLicenseId, (const std::string& license_id),
|
||||
(override));
|
||||
MOCK_METHOD(CasStatus, handleEntitlementResponse,
|
||||
(const std::string& response, std::string& license_id,
|
||||
std::string& multi_content_license_info,
|
||||
std::string& group_license_info),
|
||||
(override));
|
||||
};
|
||||
|
||||
// Override WidevineCasPlugin to set WidevineCas and mock callbacks.
|
||||
class TestWidevineCasPlugin : public WidevineCasPlugin {
|
||||
public:
|
||||
TestWidevineCasPlugin() : WidevineCasPlugin() {}
|
||||
~TestWidevineCasPlugin() override {}
|
||||
|
||||
void SetWidevineCasApi(
|
||||
std::unique_ptr<WidevineCas> widevine_cas_api) override {
|
||||
WidevineCasPlugin::SetWidevineCasApi(std::move(widevine_cas_api));
|
||||
}
|
||||
|
||||
MOCK_METHOD(void, CallBack,
|
||||
(void* appData, int32_t event, int32_t arg, uint8_t* data,
|
||||
size_t size, const CasSessionId* sessionId),
|
||||
(const, override));
|
||||
};
|
||||
|
||||
TEST(WidevineCasPluginTest, openSessionSuccess) {
|
||||
TestWidevineCasPlugin plugin;
|
||||
auto pass_through_cas_api = make_unique<MockWidevineCas>();
|
||||
MockWidevineCas* cas_api = pass_through_cas_api.get();
|
||||
plugin.SetWidevineCasApi(std::move(pass_through_cas_api));
|
||||
const std::vector<uint8_t> expected_android_session_id = {0x78, 0x56, 0x34,
|
||||
0x12};
|
||||
const int32_t created_session_id = 0x12345678;
|
||||
EXPECT_CALL(*cas_api, is_provisioned).WillOnce(Return(true));
|
||||
EXPECT_CALL(*cas_api, openSession(NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<0>(expected_android_session_id),
|
||||
Return(CasStatus::OkStatus())));
|
||||
EXPECT_CALL(plugin, CallBack(_, CAS_SESSION_ID, created_session_id, NotNull(),
|
||||
expected_android_session_id.size(), IsNull()));
|
||||
std::vector<uint8_t> session_id;
|
||||
|
||||
EXPECT_EQ(plugin.openSession(&session_id), android::OK);
|
||||
|
||||
EXPECT_EQ(session_id, expected_android_session_id);
|
||||
}
|
||||
|
||||
TEST(WidevineCasPluginTest, openSessionWithoutProvisionFail) {
|
||||
TestWidevineCasPlugin plugin;
|
||||
auto pass_through_cas_api = make_unique<MockWidevineCas>();
|
||||
MockWidevineCas* cas_api = pass_through_cas_api.get();
|
||||
plugin.SetWidevineCasApi(std::move(pass_through_cas_api));
|
||||
EXPECT_CALL(*cas_api, is_provisioned).WillOnce(Return(false));
|
||||
EXPECT_CALL(*cas_api, openSession(NotNull())).Times((0));
|
||||
std::vector<uint8_t> session_id;
|
||||
|
||||
EXPECT_EQ(plugin.openSession(&session_id),
|
||||
android::ERROR_CAS_NOT_PROVISIONED);
|
||||
}
|
||||
|
||||
TEST(WidevineCasPluginTest,
|
||||
provisionWithProvisionStringAlreadyProvisionedSuccess) {
|
||||
TestWidevineCasPlugin plugin;
|
||||
auto pass_through_cas_api = make_unique<MockWidevineCas>();
|
||||
MockWidevineCas* cas_api = pass_through_cas_api.get();
|
||||
plugin.SetWidevineCasApi(std::move(pass_through_cas_api));
|
||||
const std::string provision_string = "init_data";
|
||||
EXPECT_CALL(*cas_api, is_provisioned).WillOnce(Return(true));
|
||||
EXPECT_CALL(plugin, CallBack(_, INDIVIDUALIZATION_COMPLETE, _, _, _, _));
|
||||
// Provision string is init data; it triggers license request.
|
||||
EXPECT_CALL(*cas_api,
|
||||
generateEntitlementRequest(Eq(provision_string), NotNull(), _))
|
||||
.WillOnce(DoAll(SetArgPointee<1>("signed_license_request"),
|
||||
Return(CasStatus::OkStatus())));
|
||||
EXPECT_CALL(plugin, CallBack(_, LICENSE_REQUEST, _, _, _, _));
|
||||
|
||||
EXPECT_EQ(plugin.provision(android::String8(provision_string.c_str(),
|
||||
provision_string.size())),
|
||||
android::OK);
|
||||
}
|
||||
|
||||
TEST(WidevineCasPluginTest, HandleAssignLicenseIDSuccess) {
|
||||
TestWidevineCasPlugin plugin;
|
||||
auto pass_through_cas_api = make_unique<MockWidevineCas>();
|
||||
MockWidevineCas* cas_api = pass_through_cas_api.get();
|
||||
plugin.SetWidevineCasApi(std::move(pass_through_cas_api));
|
||||
const std::string license_id = "license_id";
|
||||
EXPECT_CALL(*cas_api, RecordLicenseId(license_id))
|
||||
.WillOnce(Return(CasStatus::OkStatus()));
|
||||
EXPECT_CALL(plugin, CallBack(_, LICENSE_ID_ASSIGNED, _, _, _, _)).Times(1);
|
||||
|
||||
EXPECT_EQ(plugin.sendEvent(ASSIGN_LICENSE_ID, /*arg=*/0,
|
||||
{license_id.begin(), license_id.end()}),
|
||||
android::OK);
|
||||
}
|
||||
|
||||
TEST(WidevineCasPluginTest, HandleAssignLicenseIDApiError) {
|
||||
TestWidevineCasPlugin plugin;
|
||||
auto pass_through_cas_api = make_unique<MockWidevineCas>();
|
||||
MockWidevineCas* cas_api = pass_through_cas_api.get();
|
||||
plugin.SetWidevineCasApi(std::move(pass_through_cas_api));
|
||||
const std::string license_id = "license_id";
|
||||
EXPECT_CALL(*cas_api, RecordLicenseId)
|
||||
.WillOnce(Return(CasStatus(CasStatusCode::kInvalidParameter, "invalid")));
|
||||
EXPECT_CALL(plugin, CallBack(_, CAS_ERROR, _, _, _, _)).Times(1);
|
||||
|
||||
EXPECT_NE(plugin.sendEvent(ASSIGN_LICENSE_ID, /*arg=*/0,
|
||||
{license_id.begin(), license_id.end()}),
|
||||
android::OK);
|
||||
}
|
||||
|
||||
TEST(WidevineCasPluginTest, HandleEntitlementResponseSuccess) {
|
||||
TestWidevineCasPlugin plugin;
|
||||
auto pass_through_cas_api = make_unique<MockWidevineCas>();
|
||||
MockWidevineCas* cas_api = pass_through_cas_api.get();
|
||||
plugin.SetWidevineCasApi(std::move(pass_through_cas_api));
|
||||
const std::string license = "license";
|
||||
const std::string license_id = "id";
|
||||
const std::string multi_content_license_info = "info";
|
||||
const std::string group_license_info = "info2";
|
||||
EXPECT_CALL(*cas_api, handleEntitlementResponse(_, _, _, _))
|
||||
.WillOnce(DoAll(SetArgReferee<1>(license_id),
|
||||
SetArgReferee<2>(multi_content_license_info),
|
||||
SetArgReferee<3>(group_license_info),
|
||||
Return(CasStatus::OkStatus())));
|
||||
EXPECT_CALL(plugin,
|
||||
CallBack(_, LICENSE_CAS_READY, _, _, license_id.size(), _))
|
||||
.Times(1);
|
||||
EXPECT_CALL(plugin, CallBack(_, MULTI_CONTENT_LICENSE_INFO, _, _,
|
||||
multi_content_license_info.size(), _))
|
||||
.Times(1);
|
||||
EXPECT_CALL(plugin, CallBack(_, GROUP_LICENSE_INFO, _, _,
|
||||
group_license_info.size(), _))
|
||||
.Times(1);
|
||||
|
||||
EXPECT_EQ(plugin.sendEvent(LICENSE_RESPONSE, /*arg=*/0,
|
||||
{license.begin(), license.end()}),
|
||||
android::OK);
|
||||
}
|
||||
|
||||
TEST(WidevineCasPluginTest, HandleEntitlementResponseEmptyResponseFail) {
|
||||
TestWidevineCasPlugin plugin;
|
||||
auto pass_through_cas_api = make_unique<MockWidevineCas>();
|
||||
plugin.SetWidevineCasApi(std::move(pass_through_cas_api));
|
||||
EXPECT_CALL(plugin, CallBack(_, CAS_ERROR, _, _, _, _)).Times(1);
|
||||
|
||||
EXPECT_NE(plugin.sendEvent(LICENSE_RESPONSE, /*arg=*/0, /*eventData=*/{}),
|
||||
android::OK);
|
||||
}
|
||||
|
||||
TEST(WidevineCasPluginTest, HandlePluginVersionQuerySuccess) {
|
||||
TestWidevineCasPlugin plugin;
|
||||
auto pass_through_cas_api = make_unique<MockWidevineCas>();
|
||||
plugin.SetWidevineCasApi(std::move(pass_through_cas_api));
|
||||
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
|
||||
20
tests/src/wv_cas_test_main.cpp
Normal file
20
tests/src/wv_cas_test_main.cpp
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine Master
|
||||
// License Agreement.
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "OEMCryptoCENC.h"
|
||||
#include "log.h"
|
||||
|
||||
namespace wvutil {
|
||||
extern LogPriority g_cutoff;
|
||||
} // namespace wvutil
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
wvutil::g_cutoff = wvutil::CDM_LOG_INFO;
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
Reference in New Issue
Block a user