Source release 19.5.0
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
#include "test_base.h"
|
||||
#include "test_printers.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
#include "wv_cdm_types.h"
|
||||
#include "wv_metrics.pb.h"
|
||||
|
||||
using ::testing::_;
|
||||
@@ -413,6 +414,55 @@ TEST_F(CdmSessionTest, Init_L3SystemIdChanged_NoReProvisionNeeded) {
|
||||
ASSERT_EQ(NO_ERROR, cdm_session_->Init(nullptr));
|
||||
}
|
||||
|
||||
TEST_F(CdmSessionTest, Init_L3SystemIdChanged_DrmReprovisioningNeeded) {
|
||||
Sequence crypto_session_seq;
|
||||
CdmSecurityLevel level = kSecurityLevelL3;
|
||||
EXPECT_CALL(*crypto_session_, Open(Eq(kLevelDefault)))
|
||||
.WillOnce(Return(CdmResponseType(NO_ERROR)));
|
||||
EXPECT_CALL(*crypto_session_, GetSecurityLevel())
|
||||
.WillRepeatedly(Return(level));
|
||||
EXPECT_CALL(*crypto_session_, GetPreProvisionTokenType)
|
||||
.WillOnce(Return(kClientTokenDrmCertificateReprovisioning));
|
||||
EXPECT_CALL(*file_handle_, Init(Eq(level))).WillOnce(Return(true));
|
||||
EXPECT_CALL(*file_handle_, HasCertificate(false)).WillOnce(Return(true));
|
||||
EXPECT_CALL(*file_handle_, RetrieveCertificate(false, NotNull(), NotNull(),
|
||||
nullptr, NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<4>(kSystemId),
|
||||
Return(DeviceFiles::kCertificateValid)));
|
||||
EXPECT_CALL(*file_handle_, RemoveCertificate()).Times(1);
|
||||
EXPECT_CALL(*system_id_extractor_, ExtractSystemId(NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<0>(kUpdatedSystemId), Return(true)));
|
||||
|
||||
ASSERT_EQ(NEED_PROVISIONING, cdm_session_->Init(nullptr));
|
||||
}
|
||||
|
||||
TEST_F(CdmSessionTest, Init_L3SystemIdUnchanged_DrmReprovisioningNotNeeded) {
|
||||
Sequence crypto_session_seq;
|
||||
CdmSecurityLevel level = kSecurityLevelL3;
|
||||
EXPECT_CALL(*crypto_session_, Open(Eq(kLevelDefault)))
|
||||
.WillOnce(Return(CdmResponseType(NO_ERROR)));
|
||||
EXPECT_CALL(*crypto_session_, GetSecurityLevel())
|
||||
.WillRepeatedly(Return(level));
|
||||
EXPECT_CALL(*crypto_session_, GetPreProvisionTokenType)
|
||||
.WillOnce(Return(kClientTokenDrmCertificateReprovisioning));
|
||||
EXPECT_CALL(*file_handle_, Init(Eq(level))).WillOnce(Return(true));
|
||||
EXPECT_CALL(*file_handle_, HasCertificate(false)).WillOnce(Return(true));
|
||||
EXPECT_CALL(*file_handle_, RetrieveCertificate(false, NotNull(), NotNull(),
|
||||
nullptr, NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<4>(kSystemId),
|
||||
Return(DeviceFiles::kCertificateValid)));
|
||||
EXPECT_CALL(*file_handle_, RemoveCertificate()).Times(0);
|
||||
EXPECT_CALL(*system_id_extractor_, ExtractSystemId(NotNull()))
|
||||
.WillOnce(DoAll(SetArgPointee<0>(kSystemId), Return(true)));
|
||||
EXPECT_CALL(*license_parser_, Init(false, Eq(kEmptyString),
|
||||
Eq(crypto_session_), Eq(policy_engine_)))
|
||||
.WillOnce(Return(true));
|
||||
EXPECT_CALL(*license_parser_, provider_session_token())
|
||||
.WillRepeatedly(Return("Mock provider session token"));
|
||||
|
||||
ASSERT_EQ(NO_ERROR, cdm_session_->Init(nullptr));
|
||||
}
|
||||
|
||||
TEST_F(CdmSessionTest, UpdateUsageEntry) {
|
||||
// Setup common expectations for initializing the CdmSession object.
|
||||
Sequence crypto_session_seq;
|
||||
|
||||
@@ -74,6 +74,8 @@ struct RenewalPolicy {
|
||||
};
|
||||
|
||||
const RenewalPolicy kShortRenewal = {"CDM_LicenseWithRenewal_renewal", 15, 10};
|
||||
const RenewalPolicy kShortRenewalAlwaysHasClientID = {
|
||||
"CDM_LicenseWithRenewal_always_has_client_id_renewal", 15, 10};
|
||||
const RenewalPolicy kLongRenewal = {"CDM_LicenseWithRenewal_long_renewal", 30,
|
||||
10};
|
||||
const RenewalPolicy kLDLRenewal = {"CDM_LimitedDurationLicense_renewal", 0, 0};
|
||||
@@ -278,6 +280,13 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine,
|
||||
FailDecrypt();
|
||||
}
|
||||
|
||||
// Verify that we are not ready to playback at the |start| time.
|
||||
void ForbidPlaybackDecryptNotReady(uint64_t start) {
|
||||
SleepUntil(start + kFudge);
|
||||
LoadLicense();
|
||||
VerifyDecryptNotReady();
|
||||
}
|
||||
|
||||
void AllowLenience() { allow_lenience_ = true; }
|
||||
|
||||
void Decrypt() {
|
||||
@@ -298,6 +307,14 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine,
|
||||
}
|
||||
}
|
||||
|
||||
void VerifyDecryptNotReady() {
|
||||
const uint64_t now = CurrentRentalTime();
|
||||
ASSERT_EQ(DECRYPT_NOT_READY, license_holder_.Decrypt(kKeyId))
|
||||
<< "Decrypt should not be ready when rental clock = "
|
||||
<< now << ", and playback clock = "
|
||||
<< (now < start_of_playback_ ? 0 : now - start_of_playback_);
|
||||
}
|
||||
|
||||
void FailDecrypt() {
|
||||
bool allow_success = false;
|
||||
// We will be lenient for some tests if it is a low end device without a
|
||||
@@ -419,6 +436,207 @@ TEST_P(CdmUseCase_Streaming, Case5) {
|
||||
ForbidPlayback(EndOfRentalWindow() + kFudge);
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
// Use case with 1 year rental and playback durations. The user has 1
|
||||
// year to watch the movie from the time of the rental. (See above for note on
|
||||
// Use Case tests). This use case is not in the documentation because it is not
|
||||
// recommended.
|
||||
class CdmUseCase_Rental_1y_Playback_1y : public CdmDurationTest {
|
||||
public:
|
||||
CdmUseCase_Rental_1y_Playback_1y()
|
||||
: CdmDurationTest("CDM_Rental_1y_Playback_1y") {
|
||||
timer_limits_.soft_enforce_rental_duration = false;
|
||||
timer_limits_.rental_duration_seconds = 31536000;
|
||||
timer_limits_.total_playback_duration_seconds = 31536000;
|
||||
}
|
||||
|
||||
void SetUp() override final {
|
||||
CdmDurationTest::SetUp();
|
||||
if (!wvoec::global_features.usage_table && license_holder_.can_persist()) {
|
||||
// Skipping offline test on platforms where usage table is not supported.
|
||||
GTEST_SKIP() << "Usage tables are not supported.";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Playback within rental duration.
|
||||
TEST_P(CdmUseCase_Rental_1y_Playback_1y, Case1) {
|
||||
// Allow playback within the rental window.
|
||||
LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration);
|
||||
}
|
||||
|
||||
// Playback with stops/restarts within rental duration.
|
||||
TEST_P(CdmUseCase_Rental_1y_Playback_1y, Case2) {
|
||||
LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration);
|
||||
UnloadLicense();
|
||||
LoadAndAllowPlayback(start_of_playback_ + 2 * kPlayDuration,
|
||||
start_of_playback_ + 3 * kPlayDuration);
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
// Streaming with Unlimited Rental duration seconds and Limited Playback
|
||||
// duration seconds. The user has unlimited time to start watching the movie,
|
||||
// but has limited time to watch once playback starts. (See above for note on
|
||||
// Use Case tests). This use case is not in the documentation because it is not
|
||||
// recommended.
|
||||
class CdmUseCase_UnlimitedRental_LimitedPlayback : public CdmDurationTest {
|
||||
public:
|
||||
CdmUseCase_UnlimitedRental_LimitedPlayback()
|
||||
: CdmDurationTest("CDM_UnlimitedRental_LimitedPlayback") {
|
||||
timer_limits_.soft_enforce_rental_duration = false;
|
||||
timer_limits_.rental_duration_seconds = 0;
|
||||
timer_limits_.total_playback_duration_seconds = 20;
|
||||
}
|
||||
|
||||
void SetUp() override final {
|
||||
CdmDurationTest::SetUp();
|
||||
if (!wvoec::global_features.usage_table && license_holder_.can_persist()) {
|
||||
// Skipping offline test on platforms where usage table is not supported.
|
||||
GTEST_SKIP() << "Usage tables are not supported.";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Playback within rental duration.
|
||||
TEST_P(CdmUseCase_UnlimitedRental_LimitedPlayback, Case1) {
|
||||
// Allow playback within the rental window.
|
||||
LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration);
|
||||
}
|
||||
|
||||
// Playback exceeds playback duration.
|
||||
TEST_P(CdmUseCase_UnlimitedRental_LimitedPlayback, Case2) {
|
||||
// Allow playback within the playback window, but not beyond.
|
||||
LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow());
|
||||
}
|
||||
|
||||
// Restart exceeds playback duration.
|
||||
TEST_P(CdmUseCase_UnlimitedRental_LimitedPlayback, Case3) {
|
||||
LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration);
|
||||
UnloadLicense();
|
||||
LoadAndTerminatePlayback(EndOfPlaybackWindow() - kPlayDuration,
|
||||
EndOfPlaybackWindow());
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
// Usecase with Unlimited Rental Duration Seconds with license start time in
|
||||
// 1979. The user has unlimited time to start watching the movie, but has
|
||||
// limited time to watch once playback starts. (See above for note on Use Case
|
||||
// tests). This use case is not in the documentation because it is not
|
||||
// recommended.
|
||||
class CdmUseCase_UnlimitedRental_LimitedPlayback_Start_1979
|
||||
: public CdmDurationTest {
|
||||
public:
|
||||
CdmUseCase_UnlimitedRental_LimitedPlayback_Start_1979()
|
||||
: CdmDurationTest("CDM_UnlimitedRental_LimitedPlayback_Start_1979") {
|
||||
timer_limits_.soft_enforce_rental_duration = false;
|
||||
timer_limits_.rental_duration_seconds = 0;
|
||||
timer_limits_.total_playback_duration_seconds = 20;
|
||||
}
|
||||
|
||||
void SetUp() override final {
|
||||
CdmDurationTest::SetUp();
|
||||
if (!wvoec::global_features.usage_table && license_holder_.can_persist()) {
|
||||
// Skipping offline test on platforms where usage table is not supported.
|
||||
GTEST_SKIP() << "Usage tables are not supported.";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Playback within rental duration.
|
||||
TEST_P(CdmUseCase_UnlimitedRental_LimitedPlayback_Start_1979, Case1) {
|
||||
// Allow playback within the rental window.
|
||||
LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration);
|
||||
}
|
||||
|
||||
// Playback exceeds playback duration.
|
||||
TEST_P(CdmUseCase_UnlimitedRental_LimitedPlayback_Start_1979, Case2) {
|
||||
// Allow playback within the playback window, but not beyond.
|
||||
LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow());
|
||||
}
|
||||
|
||||
// Restart exceeds playback duration.
|
||||
TEST_P(CdmUseCase_UnlimitedRental_LimitedPlayback_Start_1979, Case3) {
|
||||
LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration);
|
||||
UnloadLicense();
|
||||
LoadAndTerminatePlayback(EndOfPlaybackWindow() - kPlayDuration,
|
||||
EndOfPlaybackWindow());
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
// Usecase with Unlimited Rental Duration Seconds with license start time in
|
||||
// Future. The user has unlimited time to start watching the movie, but has
|
||||
// limited time to watch once playback starts with license start time in
|
||||
// future. (See above for note on Use Case tests).
|
||||
class CdmUseCase_UnlimitedRental_LimitedPlayback_Start_Future
|
||||
: public CdmDurationTest {
|
||||
public:
|
||||
CdmUseCase_UnlimitedRental_LimitedPlayback_Start_Future()
|
||||
: CdmDurationTest("CDM_UnlimitedRental_LimitedPlayback_Start_Future") {
|
||||
timer_limits_.soft_enforce_rental_duration = false;
|
||||
timer_limits_.rental_duration_seconds = 0;
|
||||
timer_limits_.total_playback_duration_seconds = 20;
|
||||
}
|
||||
};
|
||||
|
||||
// Playback should not succeed.
|
||||
TEST_P(CdmUseCase_UnlimitedRental_LimitedPlayback_Start_Future, Case1) {
|
||||
// Playback should not succeed as license start time is a future date.
|
||||
ForbidPlaybackDecryptNotReady(start_of_playback_);
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
// Streaming with 30 seconds soft Rental duration seconds and Unlimited Playback
|
||||
// duration seconds. The user has 30 seconds to start watching the movie.
|
||||
// (See above for note on Use Case tests). This use case is not in the
|
||||
// documentation.
|
||||
class CdmUseCase_30sSoftRental_UnlimitedPlayback : public CdmDurationTest {
|
||||
public:
|
||||
CdmUseCase_30sSoftRental_UnlimitedPlayback()
|
||||
: CdmDurationTest("CDM_30sSoftRental_UnlimitedPlayback") {
|
||||
timer_limits_.soft_enforce_rental_duration = true;
|
||||
timer_limits_.rental_duration_seconds = 30;
|
||||
timer_limits_.total_playback_duration_seconds = 0;
|
||||
}
|
||||
|
||||
void SetUp() override final {
|
||||
CdmDurationTest::SetUp();
|
||||
if (!wvoec::global_features.usage_table && license_holder_.can_persist()) {
|
||||
// Skipping offline test on platforms where usage table is not supported.
|
||||
GTEST_SKIP() << "Usage tables are not supported.";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Playback within rental duration.
|
||||
TEST_P(CdmUseCase_30sSoftRental_UnlimitedPlayback, Case1) {
|
||||
// Allow playback within the rental window.
|
||||
LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration);
|
||||
}
|
||||
|
||||
// Playback exceeds rental duration.
|
||||
TEST_P(CdmUseCase_30sSoftRental_UnlimitedPlayback, Case2) {
|
||||
// Allow playback within the rental window, and also beyond rental window.
|
||||
LoadAndAllowPlayback(start_of_playback_, EndOfRentalWindow() + kFudge);
|
||||
}
|
||||
|
||||
// Playback with stops/restarts within rental duration, last one exceeds rental
|
||||
// duration.
|
||||
TEST_P(CdmUseCase_30sSoftRental_UnlimitedPlayback, Case3) {
|
||||
LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration);
|
||||
UnloadLicense();
|
||||
LoadAndAllowPlayback(start_of_playback_ + 2 * kPlayDuration,
|
||||
EndOfRentalWindow());
|
||||
UnloadLicense();
|
||||
// Check that we can restart the playback after the rental window has stopped.
|
||||
const uint64_t after_rental = EndOfRentalWindow() + kPlayDuration;
|
||||
LoadAndAllowPlayback(after_rental, after_rental + kPlayDuration);
|
||||
}
|
||||
|
||||
// Initial playback exceeds rental duration.
|
||||
TEST_P(CdmUseCase_30sSoftRental_UnlimitedPlayback, Case4) {
|
||||
ForbidPlayback(EndOfRentalWindow() + kFudge);
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
// Streaming Quick Start. The user must start watching within 30 seconds, and
|
||||
// then has three hours to finish. (See above for note on Use Case tests)
|
||||
@@ -904,8 +1122,40 @@ TEST_P(CdmUseCase_SevenSoftTwoSoft, Case9) {
|
||||
ForbidPlayback(EndOfRentalWindow() + kFudge);
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
// Use case which replicates loading license unrelated to content being played
|
||||
// back.
|
||||
class CdmUseCase_UnrelatedLicense_MissingDurationKey : public CdmDurationTest {
|
||||
public:
|
||||
CdmUseCase_UnrelatedLicense_MissingDurationKey() : CdmDurationTest("CDM_UnrelatedLicense_MissingDurationKey") {
|
||||
timer_limits_.soft_enforce_rental_duration = false;
|
||||
timer_limits_.rental_duration_seconds = 40;
|
||||
timer_limits_.total_playback_duration_seconds = 0;
|
||||
}
|
||||
};
|
||||
|
||||
// Playback should fail as duration key is missing.
|
||||
TEST_P(CdmUseCase_UnrelatedLicense_MissingDurationKey, Case1) {
|
||||
LoadLicense();
|
||||
FailDecrypt();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(Both, CdmUseCase_Streaming,
|
||||
::testing::Values(false, true));
|
||||
INSTANTIATE_TEST_SUITE_P(Both, CdmUseCase_Rental_1y_Playback_1y,
|
||||
::testing::Values(false, true));
|
||||
INSTANTIATE_TEST_SUITE_P(Both, CdmUseCase_UnlimitedRental_LimitedPlayback,
|
||||
::testing::Values(false, true));
|
||||
INSTANTIATE_TEST_SUITE_P(Both,
|
||||
CdmUseCase_UnlimitedRental_LimitedPlayback_Start_1979,
|
||||
::testing::Values(false, true));
|
||||
INSTANTIATE_TEST_SUITE_P(Both,
|
||||
CdmUseCase_UnlimitedRental_LimitedPlayback_Start_Future,
|
||||
::testing::Values(false, true));
|
||||
INSTANTIATE_TEST_SUITE_P(Both, CdmUseCase_UnrelatedLicense_MissingDurationKey,
|
||||
::testing::Values(false));
|
||||
INSTANTIATE_TEST_SUITE_P(Both, CdmUseCase_30sSoftRental_UnlimitedPlayback,
|
||||
::testing::Values(false, true));
|
||||
INSTANTIATE_TEST_SUITE_P(Both, CdmUseCase_StreamingQuickStart,
|
||||
::testing::Values(false, true));
|
||||
INSTANTIATE_TEST_SUITE_P(Both, CdmUseCase_SevenHardTwoHard,
|
||||
@@ -974,10 +1224,10 @@ class RenewalTest : public CdmDurationTest {
|
||||
void RenewAndContinue(uint64_t start, uint64_t load_time, uint64_t stop,
|
||||
const RenewalPolicy& renewal_policy) {
|
||||
EXPECT_LE(start, load_time);
|
||||
EXPECT_LT(load_time, stop);
|
||||
EXPECT_LE(load_time, stop);
|
||||
if (start < load_time) AllowPlayback(start, load_time);
|
||||
LoadRenewal(load_time, renewal_policy);
|
||||
AllowPlayback(load_time, stop);
|
||||
if (load_time < stop) AllowPlayback(load_time, stop);
|
||||
}
|
||||
|
||||
// Verify that a renewal can be processed and attempt to play from |start| to
|
||||
@@ -1068,6 +1318,12 @@ class CdmUseCase_LicenseWithRenewal : public RenewalTest {
|
||||
|
||||
// Playback within rental duration and renewal duration.
|
||||
TEST_P(CdmUseCase_LicenseWithRenewal, Case1) {
|
||||
if (license_holder_.can_persist()) {
|
||||
// Note: We are only testing persistent licenses with a PST and are assuming
|
||||
// if |can_persist| is false, then there is no need to test PST. "Unlimited
|
||||
// Offline" licenses are not being tested.
|
||||
license_holder_.set_renewal_info_check(true);
|
||||
}
|
||||
for (int i = 0; i < 4; i++) {
|
||||
SleepUntilRenewalNeeded();
|
||||
RequestRenewal(kShortRenewal);
|
||||
@@ -1238,6 +1494,122 @@ TEST_P(CdmUseCase_LicenseWithRenewal, Case7) {
|
||||
RenewAndTerminate(start, load_time, cutoff, kLongRenewal);
|
||||
}
|
||||
|
||||
// License with Renewal, limited by rental duration. (See above for note on Use
|
||||
// Case tests). These tests are parameterized by can_persist = true or false.
|
||||
class CdmUseCase_LicenseWithRenewalVerifyRenewalDelay : public RenewalTest {
|
||||
public:
|
||||
CdmUseCase_LicenseWithRenewalVerifyRenewalDelay()
|
||||
: RenewalTest("CDM_LicenseWithRenewal") {
|
||||
timer_limits_.soft_enforce_rental_duration = false;
|
||||
timer_limits_.rental_duration_seconds = 180u;
|
||||
initial_policy_.renewal_delay = 15u;
|
||||
initial_policy_.renewal_recovery_duration = 10u;
|
||||
timer_limits_.initial_renewal_duration_seconds =
|
||||
initial_policy_.renewal_delay +
|
||||
initial_policy_.renewal_recovery_duration;
|
||||
}
|
||||
|
||||
void SetUp() override final {
|
||||
RenewalTest::SetUp();
|
||||
if (!wvoec::global_features.usage_table) {
|
||||
GTEST_SKIP() << "Usage tables are not supported.";
|
||||
}
|
||||
const uint64_t next_renewal =
|
||||
start_of_playback_ + initial_policy_.renewal_delay;
|
||||
// Allow playback within the initial renewal window.
|
||||
SleepUntil(start_of_playback_);
|
||||
LoadLicense();
|
||||
// Play until just before we expect a renewal to be generated.
|
||||
current_cutoff_ = next_renewal + initial_policy_.renewal_recovery_duration;
|
||||
AllowPlayback(start_of_playback_,
|
||||
start_of_playback_ + initial_policy_.renewal_delay - kFudge);
|
||||
}
|
||||
|
||||
protected:
|
||||
RenewalPolicy initial_policy_;
|
||||
};
|
||||
|
||||
// Playback within rental duration and renewal duration. Verify that renewal is
|
||||
// not needed before renewal delay seconds.
|
||||
TEST_P(CdmUseCase_LicenseWithRenewalVerifyRenewalDelay, Case1) {
|
||||
if (license_holder_.can_persist()) {
|
||||
// Note: We are only testing persistent licenses with a PST and are assuming
|
||||
// if |can_persist| is false, then there is no need to test PST. "Unlimited
|
||||
// Offline" licenses are not being tested.
|
||||
license_holder_.set_renewal_info_check(true);
|
||||
}
|
||||
uint64_t renewal_timer_start;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
SleepUntilRenewalNeeded();
|
||||
const uint64_t time_before_renewal_needed = CurrentRentalTime();
|
||||
if (i > 0) {
|
||||
EXPECT_LE(kShortRenewal.renewal_delay - kFudge,
|
||||
time_before_renewal_needed - renewal_timer_start);
|
||||
}
|
||||
RequestRenewal(kShortRenewal);
|
||||
const uint64_t start = CurrentRentalTime();
|
||||
const uint64_t load_time = start + kRoundTripTime;
|
||||
const uint64_t stop = load_time;
|
||||
RenewAndContinue(start, load_time, stop, kShortRenewal);
|
||||
// Renewal timer begins at |start| time after the renewal has been
|
||||
// requested. So we want to use this time instead.
|
||||
renewal_timer_start = start;
|
||||
}
|
||||
}
|
||||
|
||||
// License with Renewal, where there is always a client ID
|
||||
class CdmUseCase_LicenseWithRenewalAlwaysHasClientID : public RenewalTest {
|
||||
public:
|
||||
CdmUseCase_LicenseWithRenewalAlwaysHasClientID()
|
||||
: RenewalTest("CDM_LicenseWithRenewal_always_has_client_id") {
|
||||
timer_limits_.soft_enforce_rental_duration = false;
|
||||
timer_limits_.rental_duration_seconds = 180u;
|
||||
initial_policy_.renewal_delay = 15u;
|
||||
initial_policy_.renewal_recovery_duration = 10u;
|
||||
timer_limits_.initial_renewal_duration_seconds =
|
||||
initial_policy_.renewal_delay +
|
||||
initial_policy_.renewal_recovery_duration;
|
||||
}
|
||||
|
||||
void SetUp() override final {
|
||||
RenewalTest::SetUp();
|
||||
if (!wvoec::global_features.usage_table) {
|
||||
GTEST_SKIP() << "Usage tables are not supported.";
|
||||
}
|
||||
const uint64_t next_renewal =
|
||||
start_of_playback_ + initial_policy_.renewal_delay;
|
||||
// Allow playback within the initial renewal window.
|
||||
SleepUntil(start_of_playback_);
|
||||
LoadLicense();
|
||||
// Play until just before we expect a renewal to be generated.
|
||||
current_cutoff_ = next_renewal + initial_policy_.renewal_recovery_duration;
|
||||
AllowPlayback(start_of_playback_, next_renewal);
|
||||
}
|
||||
|
||||
protected:
|
||||
RenewalPolicy initial_policy_;
|
||||
};
|
||||
|
||||
// Playback within rental duration and renewal duration.
|
||||
TEST_P(CdmUseCase_LicenseWithRenewalAlwaysHasClientID, Case1) {
|
||||
if (license_holder_.can_persist()) {
|
||||
// Note: We are only testing persistent licenses with a PST and are assuming
|
||||
// if |can_persist| is false, then there is no need to test PST. "Unlimited
|
||||
// Offline" licenses are not being tested.
|
||||
license_holder_.set_renewal_info_check(true);
|
||||
license_holder_.set_client_id_check(true);
|
||||
}
|
||||
for (int i = 0; i < 4; i++) {
|
||||
SleepUntilRenewalNeeded();
|
||||
RequestRenewal(kShortRenewalAlwaysHasClientID);
|
||||
const uint64_t start = CurrentRentalTime();
|
||||
const uint64_t load_time = start + kRoundTripTime;
|
||||
const uint64_t stop =
|
||||
load_time + kShortRenewalAlwaysHasClientID.renewal_delay;
|
||||
RenewAndContinue(start, load_time, stop, kShortRenewalAlwaysHasClientID);
|
||||
}
|
||||
}
|
||||
|
||||
// License with Renewal, limited by playback duration. (See above for note on
|
||||
// Use Case tests). These are similar to the tests above, except the playback
|
||||
// duration is limited instead of the rental duration. These tests are
|
||||
@@ -1980,8 +2352,12 @@ TEST_P(CdmUseCase_LicenseDurationWithRenewal, WithRenewal) {
|
||||
// All duration tests are parameterized by can_persist = true or false.
|
||||
INSTANTIATE_TEST_SUITE_P(Both, CdmUseCase_LicenseWithRenewal,
|
||||
::testing::Values(false, true));
|
||||
INSTANTIATE_TEST_SUITE_P(Both, CdmUseCase_LicenseWithRenewalVerifyRenewalDelay,
|
||||
::testing::Values(false, true));
|
||||
INSTANTIATE_TEST_SUITE_P(Both, CdmUseCase_LicenseWithRenewalPlayback,
|
||||
::testing::Values(false, true));
|
||||
INSTANTIATE_TEST_SUITE_P(Both, CdmUseCase_LicenseWithRenewalAlwaysHasClientID,
|
||||
::testing::Values(false, true));
|
||||
INSTANTIATE_TEST_SUITE_P(Both, CdmUseCase_LimitedDurationLicense,
|
||||
::testing::Values(false, true));
|
||||
INSTANTIATE_TEST_SUITE_P(Both, CdmUseCase_RenewOnLicenseLoad,
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
||||
// source code may only be used and distributed under the Widevine License
|
||||
// Agreement.
|
||||
|
||||
#include "http_socket.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
@@ -32,9 +32,7 @@
|
||||
#include "platform.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
namespace {
|
||||
|
||||
// Number of attempts to identify an Internet host and a service should the
|
||||
// host's nameserver be temporarily unavailable. See getaddrinfo(3) for
|
||||
// more info.
|
||||
@@ -138,6 +136,98 @@ const char* GetErrorString() {
|
||||
#endif
|
||||
}
|
||||
|
||||
// Formats the provided time point to ISO 8601 format with space
|
||||
// date-time separator (Google's recommended format).
|
||||
std::string FormatTimePoint(const HttpSocket::TimePoint& time_point) {
|
||||
const std::time_t epoch_time =
|
||||
std::chrono::system_clock::to_time_t(time_point);
|
||||
struct tm time_parts = {};
|
||||
if (::gmtime_r(&epoch_time, &time_parts) == nullptr) {
|
||||
const int saved_errno = GetError();
|
||||
if (saved_errno == EOVERFLOW) {
|
||||
LOGE("Overflow when converting to time parts: epoch_time = %zu",
|
||||
static_cast<size_t>(epoch_time));
|
||||
} else {
|
||||
LOGE(
|
||||
"Failed to convert time point to time parts: "
|
||||
"epoch_time = %zu, errno = %d",
|
||||
static_cast<size_t>(epoch_time), saved_errno);
|
||||
}
|
||||
// Just convert to epoch seconds.
|
||||
return std::to_string(epoch_time);
|
||||
}
|
||||
static constexpr size_t kMaxLength = 127;
|
||||
static constexpr char kTimeFormat[] = "%F %T";
|
||||
char time_buffer[kMaxLength + 1];
|
||||
const size_t res =
|
||||
::strftime(time_buffer, kMaxLength, kTimeFormat, &time_parts);
|
||||
if (res == 0) {
|
||||
LOGE("Failed to format time");
|
||||
return std::to_string(epoch_time);
|
||||
}
|
||||
if (res > kMaxLength) {
|
||||
// Very unlikely situation, but cannot trust the contents of
|
||||
// |buffer| in this case.
|
||||
LOGE("Unexpected output from strftime: max = %zu, res = %zu", kMaxLength,
|
||||
res);
|
||||
return std::to_string(epoch_time);
|
||||
}
|
||||
return std::string(time_buffer, &time_buffer[res]);
|
||||
}
|
||||
|
||||
// Formats the provided duration to Google style duration format,
|
||||
// with microsecond accuracy.
|
||||
// The template parameter D should be a std::chrono::duration
|
||||
// type. This is template to support C++ system_clock which
|
||||
// which duration accuracy may vary by platform.
|
||||
template <class D>
|
||||
std::string FormatDuration(const D& duration) {
|
||||
D working_duration = duration;
|
||||
std::string res;
|
||||
|
||||
// If duration is negative, add a '-' and continue with absolute.
|
||||
if (working_duration < D::zero()) {
|
||||
res.push_back('-');
|
||||
working_duration = -working_duration;
|
||||
}
|
||||
|
||||
// Format hours (if non-zero).
|
||||
using Hours = std::chrono::hours;
|
||||
const Hours h = std::chrono::floor<Hours>(working_duration);
|
||||
if (h != Hours::zero()) {
|
||||
res.append(std::to_string(h.count()));
|
||||
res.push_back('h');
|
||||
working_duration -= h;
|
||||
}
|
||||
|
||||
// Format minutes (if non-zero).
|
||||
using Minutes = std::chrono::minutes;
|
||||
const Minutes m = std::chrono::floor<Minutes>(working_duration);
|
||||
if (m != Minutes::zero()) {
|
||||
res.append(std::to_string(m.count()));
|
||||
res.push_back('m');
|
||||
working_duration -= m;
|
||||
}
|
||||
|
||||
// Format seconds (if non-zero).
|
||||
using Seconds = std::chrono::seconds;
|
||||
const Seconds s = std::chrono::floor<Seconds>(working_duration);
|
||||
if (s != Seconds::zero()) {
|
||||
res.append(std::to_string(s.count()));
|
||||
res.push_back('s');
|
||||
working_duration -= s;
|
||||
}
|
||||
|
||||
// Format microseconds (if non-zero).
|
||||
using Microseconds = std::chrono::microseconds;
|
||||
const Microseconds us = std::chrono::floor<Microseconds>(working_duration);
|
||||
if (us != Microseconds::zero()) {
|
||||
res.append(std::to_string(us.count()));
|
||||
res.append("us");
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// Parses the URL and extracts all relevant information.
|
||||
@@ -204,25 +294,22 @@ bool HttpSocket::ParseUrl(const std::string& url, std::string* scheme,
|
||||
return true;
|
||||
}
|
||||
|
||||
HttpSocket::HttpSocket(const std::string& url)
|
||||
: url_(url), socket_fd_(-1), ssl_(nullptr), ssl_ctx_(nullptr) {
|
||||
HttpSocket::HttpSocket(const std::string& url) : url_(url) {
|
||||
valid_url_ = ParseUrl(url, &scheme_, &secure_connect_, &domain_name_, &port_,
|
||||
&resource_path_);
|
||||
create_time_ =
|
||||
std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||
InitSslLibrary();
|
||||
}
|
||||
|
||||
HttpSocket::~HttpSocket() { CloseSocket(); }
|
||||
|
||||
void HttpSocket::CloseSocket() {
|
||||
if (socket_fd_ != -1) {
|
||||
if (socket_fd_ != kClosedFd) {
|
||||
#ifdef _WIN32
|
||||
closesocket(socket_fd_);
|
||||
#else
|
||||
close(socket_fd_);
|
||||
#endif
|
||||
socket_fd_ = -1;
|
||||
socket_fd_ = kClosedFd;
|
||||
}
|
||||
if (ssl_) {
|
||||
SSL_free(ssl_);
|
||||
@@ -235,12 +322,10 @@ void HttpSocket::CloseSocket() {
|
||||
}
|
||||
|
||||
bool HttpSocket::ConnectAndLogErrors(int timeout_in_ms) {
|
||||
std::time_t start =
|
||||
std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||
bool result = Connect(timeout_in_ms);
|
||||
std::time_t finish =
|
||||
std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||
if (!result) LogTime("socket connect error", start, finish);
|
||||
const TimePoint start = GetNowTimePoint();
|
||||
const bool result = Connect(timeout_in_ms);
|
||||
const TimePoint end = GetNowTimePoint();
|
||||
if (!result) LogTime("Socket connect error", start, end);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -250,7 +335,7 @@ bool HttpSocket::Connect(int timeout_in_ms) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (socket_fd_ != -1) {
|
||||
if (socket_fd_ != kClosedFd) {
|
||||
LOGE("Socket already connected");
|
||||
return false;
|
||||
}
|
||||
@@ -416,12 +501,10 @@ bool HttpSocket::Connect(int timeout_in_ms) {
|
||||
// The timeout here only applies to the span between packets of data, for the
|
||||
// sake of simplicity.
|
||||
int HttpSocket::ReadAndLogErrors(char* data, int len, int timeout_in_ms) {
|
||||
std::time_t start =
|
||||
std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||
int result = Read(data, len, timeout_in_ms);
|
||||
std::time_t finish =
|
||||
std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||
if (result < 0) LogTime("read error", start, finish);
|
||||
const TimePoint start = GetNowTimePoint();
|
||||
const int result = Read(data, len, timeout_in_ms);
|
||||
const TimePoint end = GetNowTimePoint();
|
||||
if (result < 0) LogTime("Read error", start, end);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -429,7 +512,7 @@ int HttpSocket::Read(char* data, int len, int timeout_in_ms) {
|
||||
int total_read = 0;
|
||||
int to_read = len;
|
||||
|
||||
if (socket_fd_ == -1) {
|
||||
if (socket_fd_ == kClosedFd) {
|
||||
LOGE("Socket to %s not open. Cannot read.", domain_name_.c_str());
|
||||
return -1;
|
||||
}
|
||||
@@ -494,12 +577,10 @@ int HttpSocket::Read(char* data, int len, int timeout_in_ms) {
|
||||
// sake of simplicity.
|
||||
int HttpSocket::WriteAndLogErrors(const char* data, int len,
|
||||
int timeout_in_ms) {
|
||||
std::time_t start =
|
||||
std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||
int result = Write(data, len, timeout_in_ms);
|
||||
std::time_t finish =
|
||||
std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||
if (result < 0) LogTime("write error", start, finish);
|
||||
const TimePoint start = GetNowTimePoint();
|
||||
const int result = Write(data, len, timeout_in_ms);
|
||||
const TimePoint end = GetNowTimePoint();
|
||||
if (result < 0) LogTime("Write error", start, end);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -507,7 +588,7 @@ int HttpSocket::Write(const char* data, int len, int timeout_in_ms) {
|
||||
int total_sent = 0;
|
||||
int to_send = len;
|
||||
|
||||
if (socket_fd_ == -1) {
|
||||
if (socket_fd_ == kClosedFd) {
|
||||
LOGE("Socket to %s not open. Cannot write.", domain_name_.c_str());
|
||||
return -1;
|
||||
}
|
||||
@@ -555,13 +636,11 @@ bool HttpSocket::Wait(bool for_read, int timeout_in_ms) {
|
||||
write_fds = &fds;
|
||||
}
|
||||
|
||||
const std::time_t start =
|
||||
std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||
int ret = select(socket_fd_ + 1, read_fds, write_fds, nullptr, &tv);
|
||||
const std::time_t finish =
|
||||
std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||
const TimePoint start = GetNowTimePoint();
|
||||
const int ret = select(socket_fd_ + 1, read_fds, write_fds, nullptr, &tv);
|
||||
const TimePoint end = GetNowTimePoint();
|
||||
if (ret == 0) {
|
||||
LogTime("socket select timeout", start, finish);
|
||||
LogTime("Socket select timeout", start, end);
|
||||
// TODO(b/186031735): Remove this when the bug is fixed.
|
||||
LOGE("Timeout = %0.3f. Consider adding a comment to http://b/186031735",
|
||||
0.001 * timeout_in_ms);
|
||||
@@ -575,13 +654,18 @@ bool HttpSocket::Wait(bool for_read, int timeout_in_ms) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void HttpSocket::LogTime(const char* note, const std::time_t& start,
|
||||
const std::time_t& finish) {
|
||||
std::string start_string = std::string(std::ctime(&start));
|
||||
start_string.pop_back(); // Remove new line character.
|
||||
LOGE("%s: start = %s = create + %0.3f, end = start + %0.3f", note,
|
||||
start_string.c_str(), difftime(start, create_time_),
|
||||
difftime(finish, start));
|
||||
}
|
||||
void HttpSocket::LogTime(const char* note, const TimePoint& start_time,
|
||||
const TimePoint& end_time) const {
|
||||
const std::string start_string = FormatTimePoint(start_time);
|
||||
const std::string create_start_diff_string =
|
||||
FormatDuration(start_time - create_time_);
|
||||
const std::string end_string = FormatTimePoint(end_time);
|
||||
const std::string start_end_diff_string =
|
||||
FormatDuration(end_time - start_time);
|
||||
|
||||
LOGE("%s: start = %s = create + %s, end = %s = start + %s",
|
||||
note ? note : "<unspecified>", start_string.c_str(),
|
||||
create_start_diff_string.c_str(), end_string.c_str(),
|
||||
start_end_diff_string.c_str());
|
||||
}
|
||||
} // namespace wvcdm
|
||||
|
||||
@@ -4,12 +4,9 @@
|
||||
#ifndef CDM_TEST_HTTP_SOCKET_H_
|
||||
#define CDM_TEST_HTTP_SOCKET_H_
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <ctime>
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
#include <gtest/gtest_prod.h>
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
#include "wv_class_utils.h"
|
||||
@@ -18,6 +15,13 @@ namespace wvcdm {
|
||||
// Provides basic Linux based TCP socket interface.
|
||||
class HttpSocket {
|
||||
public:
|
||||
using TimePoint = std::chrono::time_point<std::chrono::system_clock>;
|
||||
static TimePoint GetNowTimePoint() {
|
||||
return std::chrono::system_clock::now();
|
||||
}
|
||||
|
||||
static constexpr int kClosedFd = -1;
|
||||
|
||||
HttpSocket() = delete;
|
||||
WVCDM_DISALLOW_COPY_AND_MOVE(HttpSocket);
|
||||
// A scheme (http:// or https://) is required for the URL.
|
||||
@@ -37,6 +41,14 @@ class HttpSocket {
|
||||
int ReadAndLogErrors(char* data, int len, int timeout_in_ms);
|
||||
int WriteAndLogErrors(const char* data, int len, int timeout_in_ms);
|
||||
|
||||
// Allows for testing ParseUrl. Do not rely on this for other
|
||||
// test code.
|
||||
static bool ParseUrlForTest(const std::string& url, std::string* scheme,
|
||||
bool* secure_connect, std::string* domain_name,
|
||||
std::string* port, std::string* path) {
|
||||
return ParseUrl(url, scheme, secure_connect, domain_name, port, path);
|
||||
}
|
||||
|
||||
private:
|
||||
static bool ParseUrl(const std::string& url, std::string* scheme,
|
||||
bool* secure_connect, std::string* domain_name,
|
||||
@@ -46,30 +58,29 @@ class HttpSocket {
|
||||
int Read(char* data, int len, int timeout_in_ms);
|
||||
int Write(const char* data, int len, int timeout_in_ms);
|
||||
// Log times with a note as an error.
|
||||
void LogTime(const char* note, const std::time_t& start,
|
||||
const std::time_t& finish);
|
||||
void LogTime(const char* note, const TimePoint& start_time,
|
||||
const TimePoint& end_time) const;
|
||||
// Wait for a socket to be ready for reading or writing.
|
||||
// Establishing a connection counts as "ready for write".
|
||||
// Returns false on select error or timeout.
|
||||
// Returns true when the socket is ready.
|
||||
bool Wait(bool for_read, int timeout_in_ms);
|
||||
FRIEND_TEST(HttpSocketTest, ParseUrlTest);
|
||||
|
||||
std::string url_;
|
||||
std::string scheme_;
|
||||
bool secure_connect_;
|
||||
bool secure_connect_ = false;
|
||||
std::string domain_name_;
|
||||
std::string port_;
|
||||
std::string resource_path_;
|
||||
bool valid_url_;
|
||||
bool valid_url_ = false;
|
||||
|
||||
int socket_fd_;
|
||||
SSL* ssl_;
|
||||
SSL_CTX* ssl_ctx_;
|
||||
int socket_fd_ = kClosedFd;
|
||||
SSL* ssl_ = nullptr;
|
||||
SSL_CTX* ssl_ctx_ = nullptr;
|
||||
|
||||
// When the socket was created. Logged on error to help debug flaky
|
||||
// tests. e.g. b/186031735
|
||||
std::time_t create_time_;
|
||||
TimePoint create_time_ = GetNowTimePoint();
|
||||
}; // class HttpSocket
|
||||
} // namespace wvcdm
|
||||
#endif // CDM_TEST_HTTP_SOCKET_H_
|
||||
|
||||
@@ -107,7 +107,7 @@ struct ParseUrlTests {
|
||||
const char* path;
|
||||
};
|
||||
|
||||
ParseUrlTests parse_url_tests[] = {
|
||||
const ParseUrlTests parse_url_tests[] = {
|
||||
{
|
||||
"https://code.google.com/p/googletest/wiki/Primer", // url
|
||||
"https", // scheme
|
||||
@@ -173,11 +173,11 @@ TEST_F(HttpSocketTest, ParseUrlTest) {
|
||||
std::string domain_name;
|
||||
std::string port;
|
||||
std::string path;
|
||||
ParseUrlTests* test = nullptr;
|
||||
const ParseUrlTests* test = nullptr;
|
||||
|
||||
for (test = &parse_url_tests[0]; test->url != nullptr; ++test) {
|
||||
bool ok = HttpSocket::ParseUrl(test->url, &scheme, &secure_connect,
|
||||
&domain_name, &port, &path);
|
||||
const bool ok = HttpSocket::ParseUrlForTest(
|
||||
test->url, &scheme, &secure_connect, &domain_name, &port, &path);
|
||||
EXPECT_TRUE(ok);
|
||||
if (ok) {
|
||||
EXPECT_EQ(test->scheme, scheme);
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#include "properties.h"
|
||||
#include "test_base.h"
|
||||
|
||||
using video_widevine::SignedMessage;
|
||||
|
||||
namespace wvcdm {
|
||||
namespace {
|
||||
const std::string kCencMimeType = "cenc";
|
||||
@@ -42,11 +44,24 @@ void LicenseHolder::FetchLicense() {
|
||||
if (config_.dump_golden_data()) {
|
||||
MessageDumper::DumpLicenseRequest(key_request);
|
||||
}
|
||||
if (log_core_message_) MessageDumper::PrintLicenseRequest(key_request);
|
||||
ASSERT_NO_FATAL_FAILURE(GetKeyResponse(key_request))
|
||||
<< "Failed for " << content_id();
|
||||
if (config_.dump_golden_data()) {
|
||||
MessageDumper::DumpLicense(key_response_);
|
||||
}
|
||||
if (pst_length_ > 0) {
|
||||
SignedMessage signed_message;
|
||||
ASSERT_TRUE(signed_message.ParseFromString(key_response_));
|
||||
|
||||
video_widevine::License license;
|
||||
ASSERT_TRUE(license.ParseFromString(signed_message.msg()));
|
||||
|
||||
// Verify that pst exists and matches |pst_length_|.
|
||||
ASSERT_TRUE(!license.id().provider_session_token().empty());
|
||||
ASSERT_EQ(pst_length_, license.id().provider_session_token().length());
|
||||
}
|
||||
if (log_core_message_) MessageDumper::PrintLicenseResponse(key_response_);
|
||||
}
|
||||
|
||||
void LicenseHolder::LoadLicense() {
|
||||
@@ -93,6 +108,37 @@ void LicenseHolder::GenerateAndPostRenewalRequest(
|
||||
if (config_.dump_golden_data()) {
|
||||
MessageDumper::DumpRenewalRequest(request);
|
||||
}
|
||||
|
||||
if (renewal_info_check_) {
|
||||
SignedMessage signed_message;
|
||||
ASSERT_TRUE(signed_message.ParseFromString(request.message));
|
||||
|
||||
video_widevine::LicenseRequest renewal_request;
|
||||
ASSERT_TRUE(renewal_request.ParseFromString(signed_message.msg()));
|
||||
|
||||
if (client_id_check_) {
|
||||
// Check that a renewal request where client ID is always included
|
||||
// has a client ID.
|
||||
ASSERT_TRUE(renewal_request.has_client_id());
|
||||
} else {
|
||||
// Check that a renewal request where can_persist = true does not include
|
||||
// the client ID.
|
||||
ASSERT_FALSE(renewal_request.has_client_id());
|
||||
}
|
||||
|
||||
// Check that a renewal request where can_persist = true does not include
|
||||
// usage entry info.
|
||||
ASSERT_FALSE(renewal_request.content_id()
|
||||
.existing_license()
|
||||
.has_session_usage_table_entry());
|
||||
|
||||
// Check that a renewal request where can_persist = true includes the PST.
|
||||
ASSERT_TRUE(renewal_request.content_id()
|
||||
.existing_license()
|
||||
.license_id()
|
||||
.has_provider_session_token());
|
||||
}
|
||||
|
||||
const std::string url = MakeUrl(config_.renewal_server(), renewal_policy_id);
|
||||
request_in_flight_.reset(new UrlRequest(url));
|
||||
ASSERT_TRUE(request_in_flight_->is_connected())
|
||||
@@ -113,7 +159,17 @@ void LicenseHolder::LoadRenewal() {
|
||||
if (config_.dump_golden_data()) {
|
||||
MessageDumper::DumpRenewal(request_message_);
|
||||
}
|
||||
EXPECT_EQ(KEY_ADDED, cdm_engine_->RenewKey(session_id_, request_message_))
|
||||
if (renewal_info_check_) {
|
||||
SignedMessage signed_message;
|
||||
ASSERT_TRUE(signed_message.ParseFromString(request_message_));
|
||||
|
||||
video_widevine::License renewal;
|
||||
ASSERT_TRUE(renewal.ParseFromString(signed_message.msg()));
|
||||
|
||||
// Verify that pst exist.
|
||||
ASSERT_TRUE(renewal.id().has_provider_session_token());
|
||||
}
|
||||
ASSERT_EQ(KEY_ADDED, cdm_engine_->RenewKey(session_id_, request_message_))
|
||||
<< "Failed for " << content_id();
|
||||
}
|
||||
|
||||
@@ -148,10 +204,6 @@ void LicenseHolder::GenerateAndPostReleaseRequest(
|
||||
if (!Properties::AlwaysUseKeySetIds()) {
|
||||
cdm_engine_->CloseKeySetSession(key_set_id_);
|
||||
}
|
||||
if (config_.dump_golden_data()) {
|
||||
// TODO (b/295956275) vickymin: write DumpReleaseRequest function
|
||||
// MessageDumper::DumpReleaseRequest(request);
|
||||
}
|
||||
const std::string url = MakeUrl(config_.renewal_server(), release_policy_id);
|
||||
request_in_flight_.reset(new UrlRequest(url));
|
||||
ASSERT_TRUE(request_in_flight_->is_connected())
|
||||
@@ -169,14 +221,10 @@ void LicenseHolder::FetchRelease() {
|
||||
void LicenseHolder::LoadRelease() {
|
||||
LicenseRequest license_request;
|
||||
license_request.GetDrmMessage(request_response_, request_message_);
|
||||
if (config_.dump_golden_data()) {
|
||||
// TODO (b/295956275) vickymin: write DumpRelease function
|
||||
// MessageDumper::DumpRelease(request_message_);
|
||||
}
|
||||
}
|
||||
|
||||
void LicenseHolder::RemoveLicense() {
|
||||
EXPECT_EQ(NO_ERROR, cdm_engine_->RemoveLicense(session_id_))
|
||||
ASSERT_EQ(NO_ERROR, cdm_engine_->RemoveLicense(session_id_))
|
||||
<< "Failed for " << content_id();
|
||||
}
|
||||
|
||||
|
||||
@@ -116,6 +116,16 @@ class LicenseHolder {
|
||||
key_set_id_ = key_set_id;
|
||||
}
|
||||
SimpleEventListener& event_listener() { return event_listener_; }
|
||||
void set_log_core_message(bool log_core_message) {
|
||||
log_core_message_ = log_core_message;
|
||||
}
|
||||
void set_pst_length(uint64_t pst_length) { pst_length_ = pst_length; }
|
||||
void set_renewal_info_check(bool renewal_info_check) {
|
||||
renewal_info_check_ = renewal_info_check;
|
||||
}
|
||||
void set_client_id_check(bool client_id_check) {
|
||||
client_id_check_ = client_id_check;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string content_id_;
|
||||
@@ -130,6 +140,10 @@ class LicenseHolder {
|
||||
std::unique_ptr<UrlRequest> request_in_flight_;
|
||||
std::string request_message_;
|
||||
std::string request_response_;
|
||||
bool log_core_message_ = false;
|
||||
uint64_t pst_length_ = 0u;
|
||||
bool renewal_info_check_ = false;
|
||||
bool client_id_check_ = false;
|
||||
|
||||
// Generate the license request.
|
||||
void GenerateKeyRequest(const InitializationData& init_data,
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
|
||||
#include "message_dumper.h"
|
||||
|
||||
#include <iomanip>
|
||||
|
||||
#include "core_message_deserialize.h"
|
||||
#include "license_request.h"
|
||||
#include "odk.h"
|
||||
#include "odk_message.h"
|
||||
@@ -129,6 +132,65 @@ void MessageDumper::TearDown() {
|
||||
provision_file.close();
|
||||
}
|
||||
|
||||
void MessageDumper::PrintLicenseRequest(const CdmKeyRequest& request) {
|
||||
SignedMessage signed_message;
|
||||
ASSERT_TRUE(signed_message.ParseFromString(request.message));
|
||||
if (wvoec::global_features.api_version >= wvoec::kCoreMessagesAPI) {
|
||||
ASSERT_TRUE(signed_message.has_oemcrypto_core_message());
|
||||
const std::string& core_request = signed_message.oemcrypto_core_message();
|
||||
oemcrypto_core_message::ODK_LicenseRequest core_request_data;
|
||||
ASSERT_TRUE(
|
||||
oemcrypto_core_message::deserialize::CoreLicenseRequestFromMessage(
|
||||
core_request, &core_request_data));
|
||||
|
||||
// Print core message information for the license request.
|
||||
std::cout << std::endl << "License Request Information:" << std::endl;
|
||||
std::cout << " License Request Core Version = "
|
||||
<< core_request_data.api_major_version << "."
|
||||
<< core_request_data.api_minor_version << std::endl;
|
||||
std::cout << " Nonce = " << core_request_data.nonce << std::endl;
|
||||
std::cout << " Session ID = " << core_request_data.session_id << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void MessageDumper::PrintLicenseResponse(const std::string& response) {
|
||||
SignedMessage signed_response;
|
||||
ASSERT_TRUE(signed_response.ParseFromString(response));
|
||||
if (wvoec::global_features.api_version >= wvoec::kCoreMessagesAPI) {
|
||||
ASSERT_TRUE(signed_response.has_oemcrypto_core_message());
|
||||
video_widevine::License license;
|
||||
ASSERT_TRUE(license.ParseFromString(signed_response.msg()));
|
||||
|
||||
std::string message =
|
||||
signed_response.oemcrypto_core_message() + signed_response.msg();
|
||||
ODK_Message odk_msg = ODK_Message_Create(
|
||||
reinterpret_cast<uint8_t*>(const_cast<char*>(message.c_str())),
|
||||
message.length());
|
||||
ODK_Message_SetSize(&odk_msg,
|
||||
signed_response.oemcrypto_core_message().length());
|
||||
ODK_ParsedLicense odk_parsed_license = {};
|
||||
ODK_LicenseResponse odk_license_response = {};
|
||||
odk_license_response.parsed_license = &odk_parsed_license;
|
||||
Unpack_ODK_LicenseResponse(&odk_msg, &odk_license_response);
|
||||
ASSERT_EQ(ODK_Message_GetStatus(&odk_msg), MESSAGE_STATUS_OK);
|
||||
|
||||
// Print core message information for the license response.
|
||||
std::cout << std::endl << "License Response Information:" << std::endl;
|
||||
std::cout
|
||||
<< " License Response Core Version = "
|
||||
<< odk_license_response.core_message.nonce_values.api_major_version
|
||||
<< "."
|
||||
<< odk_license_response.core_message.nonce_values.api_minor_version
|
||||
<< std::endl;
|
||||
std::cout << " Nonce = "
|
||||
<< odk_license_response.core_message.nonce_values.nonce
|
||||
<< std::endl;
|
||||
std::cout << " Session ID = "
|
||||
<< odk_license_response.core_message.nonce_values.session_id
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void MessageDumper::DumpLicenseRequest(const CdmKeyRequest& request) {
|
||||
SignedMessage signed_message;
|
||||
DumpHeader(&license_file, "License");
|
||||
@@ -142,8 +204,8 @@ void MessageDumper::DumpLicenseRequest(const CdmKeyRequest& request) {
|
||||
// request is valid.
|
||||
video_widevine::LicenseRequest license_request;
|
||||
EXPECT_TRUE(license_request.ParseFromString(signed_message.msg()));
|
||||
// TODO(fredgc): figure out if we can build tests with full protobufs instead
|
||||
// of proto lite, so that we can use TextFormat.
|
||||
// TODO(fredgc): figure out if we can build tests with full protobufs
|
||||
// instead of proto lite, so that we can use TextFormat.
|
||||
PrintRequestProto("License Request", license_request);
|
||||
}
|
||||
|
||||
@@ -231,6 +293,131 @@ void MessageDumper::DumpRenewal(const std::string& response) {
|
||||
renewal_file << "}\n\n";
|
||||
}
|
||||
|
||||
void MessageDumper::PrintProvisioningRequest(
|
||||
const CdmProvisioningRequest& request) {
|
||||
SignedProvisioningMessage signed_message;
|
||||
ASSERT_TRUE(signed_message.ParseFromString(request));
|
||||
if (wvoec::global_features.api_version >= wvoec::kCoreMessagesAPI) {
|
||||
ASSERT_TRUE(signed_message.has_oemcrypto_core_message());
|
||||
const std::string& core_request = signed_message.oemcrypto_core_message();
|
||||
oemcrypto_core_message::ODK_ProvisioningRequest core_request_data;
|
||||
ASSERT_TRUE(
|
||||
oemcrypto_core_message::deserialize::CoreProvisioningRequestFromMessage(
|
||||
core_request, &core_request_data));
|
||||
|
||||
// Print core message information for the provisioning request.
|
||||
if (wvoec::global_features.derive_key_method ==
|
||||
wvoec::DeviceFeatures::TEST_PROVISION_40) {
|
||||
std::cout << std::endl
|
||||
<< "Provisioning 4.0 Request Information:" << std::endl;
|
||||
std::cout << " Provisioning 4.0 Request Core Version = "
|
||||
<< core_request_data.api_major_version << "."
|
||||
<< core_request_data.api_minor_version << std::endl;
|
||||
} else {
|
||||
std::cout << std::endl
|
||||
<< "Provisioning Request Information:" << std::endl;
|
||||
std::cout << " Provisioning Request Core Version = "
|
||||
<< core_request_data.api_major_version << "."
|
||||
<< core_request_data.api_minor_version << std::endl;
|
||||
}
|
||||
std::cout << " Nonce = " << core_request_data.nonce << std::endl;
|
||||
std::cout << " Session ID = " << core_request_data.session_id << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void MessageDumper::PrintProvisioningResponse(
|
||||
const CdmProvisioningResponse& response) {
|
||||
if (wvoec::global_features.derive_key_method ==
|
||||
wvoec::DeviceFeatures::TEST_PROVISION_40) {
|
||||
std::cout << " Provisioning 4.0 does not have a response. " << std::endl;
|
||||
return;
|
||||
}
|
||||
SignedProvisioningMessage signed_response;
|
||||
if (!signed_response.ParseFromString(response)) {
|
||||
// A binary provisioning response is buried within a json structure.
|
||||
std::string extracted_message;
|
||||
ASSERT_TRUE(CertificateProvisioning::ExtractAndDecodeSignedMessage(
|
||||
response, &extracted_message));
|
||||
ASSERT_TRUE(signed_response.ParseFromString(extracted_message));
|
||||
}
|
||||
if (wvoec::global_features.api_version >= wvoec::kCoreMessagesAPI) {
|
||||
ASSERT_TRUE(signed_response.has_oemcrypto_core_message());
|
||||
std::string message =
|
||||
signed_response.oemcrypto_core_message() + signed_response.message();
|
||||
ODK_Message odk_msg = ODK_Message_Create(
|
||||
reinterpret_cast<uint8_t*>(const_cast<char*>(message.c_str())),
|
||||
message.length());
|
||||
ODK_Message_SetSize(&odk_msg,
|
||||
signed_response.oemcrypto_core_message().length());
|
||||
|
||||
ODK_ParsedProvisioning odk_parsed_provisioning;
|
||||
// For v17 and earlier, we use a special v16/v17 provisioning response type.
|
||||
// So need to separate the way we unpack the provisioning response into
|
||||
// v16/v17 and v18+.
|
||||
if (wvoec::global_features.api_version > 17) {
|
||||
ODK_ProvisioningResponse odk_provisioning_response;
|
||||
odk_provisioning_response.parsed_provisioning = &odk_parsed_provisioning;
|
||||
Unpack_ODK_ProvisioningResponse(&odk_msg, &odk_provisioning_response);
|
||||
EXPECT_EQ(ODK_Message_GetStatus(&odk_msg), MESSAGE_STATUS_OK);
|
||||
|
||||
// Print core message information for the provisioning response.
|
||||
std::cout << std::endl
|
||||
<< "Provisioning Response Information:" << std::endl;
|
||||
std::cout << " Provisioning Response Core Version = "
|
||||
<< odk_provisioning_response.core_message.nonce_values
|
||||
.api_major_version
|
||||
<< "."
|
||||
<< odk_provisioning_response.core_message.nonce_values
|
||||
.api_minor_version
|
||||
<< std::endl;
|
||||
std::cout << " Nonce = "
|
||||
<< odk_provisioning_response.core_message.nonce_values.nonce
|
||||
<< std::endl;
|
||||
std::cout
|
||||
<< " Session ID = "
|
||||
<< odk_provisioning_response.core_message.nonce_values.session_id
|
||||
<< std::endl;
|
||||
std::cout << " Key Type = "
|
||||
<< ((odk_parsed_provisioning.key_type ==
|
||||
OEMCrypto_RSA_Private_Key)
|
||||
? "OEMCrypto_RSA_Private_Key"
|
||||
: "OEMCrypto_ECC_Private_Key")
|
||||
<< std::endl;
|
||||
} else {
|
||||
// ODK_ParsedProvisioning odk_parsed_provisioning;
|
||||
ODK_ProvisioningResponseV16 odk_provisioning_response;
|
||||
odk_provisioning_response.parsed_provisioning = &odk_parsed_provisioning;
|
||||
Unpack_ODK_ProvisioningResponseV16(&odk_msg, &odk_provisioning_response);
|
||||
EXPECT_EQ(ODK_Message_GetStatus(&odk_msg), MESSAGE_STATUS_OK);
|
||||
|
||||
// Print core message information for the provisioning response.
|
||||
std::cout << std::endl
|
||||
<< "Provisioning Response Information:" << std::endl;
|
||||
std::cout << " Provisioning Response Core Version = "
|
||||
<< odk_provisioning_response.request.core_message.nonce_values
|
||||
.api_major_version
|
||||
<< "."
|
||||
<< odk_provisioning_response.request.core_message.nonce_values
|
||||
.api_minor_version
|
||||
<< std::endl;
|
||||
std::cout
|
||||
<< " Nonce = "
|
||||
<< odk_provisioning_response.request.core_message.nonce_values.nonce
|
||||
<< std::endl;
|
||||
std::cout << " Session ID = "
|
||||
<< odk_provisioning_response.request.core_message.nonce_values
|
||||
.session_id
|
||||
<< std::endl;
|
||||
std::cout << " Key Type = "
|
||||
<< ((odk_parsed_provisioning.key_type ==
|
||||
OEMCrypto_RSA_Private_Key)
|
||||
? "OEMCrypto_RSA_Private_Key"
|
||||
: "OEMCrypto_ECC_Private_Key")
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MessageDumper::DumpProvisioningRequest(
|
||||
const CdmProvisioningRequest& request) {
|
||||
if (wvoec::global_features.derive_key_method ==
|
||||
@@ -255,7 +442,8 @@ void MessageDumper::DumpProvisioning(const CdmProvisioningResponse& response) {
|
||||
if (wvoec::global_features.derive_key_method ==
|
||||
wvoec::DeviceFeatures::TEST_PROVISION_40) {
|
||||
LOGD(
|
||||
"Provisioning 4.0 does not have a v17, v18 or v19 core message in the "
|
||||
"Provisioning 4.0 does not have a v17, v18 or v19 core message in "
|
||||
"the "
|
||||
"response.");
|
||||
provision_file << " RunTest();\n";
|
||||
provision_file << "}\n\n";
|
||||
@@ -285,13 +473,13 @@ void MessageDumper::DumpProvisioning(const CdmProvisioningResponse& response) {
|
||||
message.length());
|
||||
ODK_Message_SetSize(&odk_msg,
|
||||
signed_response.oemcrypto_core_message().length());
|
||||
ODK_ParsedProvisioning odk_parsed_response;
|
||||
ODK_ProvisioningResponse provisioning_response;
|
||||
provisioning_response.parsed_provisioning = &odk_parsed_response;
|
||||
Unpack_ODK_ProvisioningResponse(&odk_msg, &provisioning_response);
|
||||
ODK_ParsedProvisioning odk_parsed_provisioning;
|
||||
ODK_ProvisioningResponse odk_provisioning_response;
|
||||
odk_provisioning_response.parsed_provisioning = &odk_parsed_provisioning;
|
||||
Unpack_ODK_ProvisioningResponse(&odk_msg, &odk_provisioning_response);
|
||||
EXPECT_EQ(ODK_Message_GetStatus(&odk_msg), MESSAGE_STATUS_OK);
|
||||
provision_file << " device_key_type_ = "
|
||||
<< ((odk_parsed_response.key_type ==
|
||||
<< ((odk_parsed_provisioning.key_type ==
|
||||
OEMCrypto_RSA_Private_Key)
|
||||
? "OEMCrypto_RSA_Private_Key;\n"
|
||||
: "OEMCrypto_ECC_Private_Key;\n");
|
||||
|
||||
@@ -24,10 +24,15 @@ class MessageDumper : public ::testing::Environment {
|
||||
void SetUp() override;
|
||||
void TearDown() override;
|
||||
|
||||
static void PrintLicenseRequest(const CdmKeyRequest& request);
|
||||
static void PrintLicenseResponse(const std::string& response);
|
||||
static void DumpLicenseRequest(const CdmKeyRequest& request);
|
||||
static void DumpLicense(const std::string& response);
|
||||
static void DumpRenewalRequest(const CdmKeyRequest& request);
|
||||
static void DumpRenewal(const std::string& response);
|
||||
static void PrintProvisioningRequest(const CdmProvisioningRequest& request);
|
||||
static void PrintProvisioningResponse(
|
||||
const CdmProvisioningResponse& response);
|
||||
static void DumpProvisioningRequest(const CdmProvisioningRequest& request);
|
||||
static void DumpProvisioning(const CdmProvisioningResponse& response);
|
||||
static std::ofstream license_file;
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
#include "test_sleep.h"
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
const uint64_t kLargePstLength = 127;
|
||||
|
||||
namespace wvcdm {
|
||||
// Core Policy Integration Test
|
||||
class CorePIGTest : public WvCdmTestBaseWithEngine {
|
||||
@@ -36,6 +38,47 @@ class CorePIGTest : public WvCdmTestBaseWithEngine {
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Check the version of the response returned by the server.
|
||||
*/
|
||||
TEST_F(CorePIGTest, PrintClientAndServerVersionNumber) {
|
||||
LicenseHolder holder("CDM_OfflineNoNonce", &cdm_engine_, config_);
|
||||
holder.set_can_persist(true);
|
||||
const KeyId key_id = "0000000000000000";
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(holder.OpenSession());
|
||||
ASSERT_NO_FATAL_FAILURE(holder.set_log_core_message(true));
|
||||
ASSERT_NO_FATAL_FAILURE(holder.FetchLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(holder.LoadLicense());
|
||||
EXPECT_EQ(NO_ERROR, holder.Decrypt(key_id));
|
||||
ASSERT_NO_FATAL_FAILURE(holder.CloseSession());
|
||||
|
||||
if (wvoec::global_features.provisioning_method == OEMCrypto_DrmCertificate) {
|
||||
GTEST_SKIP() << "Test not for DRM certificate provisioning method.";
|
||||
}
|
||||
std::string level;
|
||||
ASSERT_EQ(
|
||||
NO_ERROR,
|
||||
cdm_engine_.QueryStatus(kLevelDefault, QUERY_KEY_SECURITY_LEVEL, &level)
|
||||
.code());
|
||||
CdmSecurityLevel security_level = level == QUERY_VALUE_SECURITY_LEVEL_L1
|
||||
? kSecurityLevelL1
|
||||
: kSecurityLevelL3;
|
||||
std::string prov_model;
|
||||
ASSERT_EQ(NO_ERROR,
|
||||
cdm_engine_.QueryStatus(kLevelDefault, QUERY_KEY_PROVISIONING_MODEL,
|
||||
&prov_model));
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(cdm_engine_.Unprovision(security_level));
|
||||
ASSERT_FALSE(cdm_engine_.IsProvisioned(security_level));
|
||||
ProvisioningHolder provisioner(&cdm_engine_, config_);
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.set_log_core_message(true));
|
||||
if (prov_model == QUERY_VALUE_BOOT_CERTIFICATE_CHAIN) {
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.Provision(binary_provisioning_));
|
||||
}
|
||||
ASSERT_NO_FATAL_FAILURE(provisioner.Provision(binary_provisioning_));
|
||||
}
|
||||
|
||||
/**
|
||||
* An offline license with nonce not required.
|
||||
*/
|
||||
@@ -78,6 +121,31 @@ TEST_F(CorePIGTest, OfflineWithPST) {
|
||||
ASSERT_NO_FATAL_FAILURE(holder.CloseSession());
|
||||
}
|
||||
|
||||
/**
|
||||
* An offline license with nonce and large provider session token.
|
||||
*/
|
||||
TEST_F(CorePIGTest, OfflineWithLargePST) {
|
||||
if (!wvoec::global_features.usage_table) {
|
||||
GTEST_SKIP() << "Test for usage table devices only.";
|
||||
}
|
||||
LicenseHolder holder("CDM_OfflineWithPST_Length_127", &cdm_engine_, config_);
|
||||
holder.set_can_persist(true);
|
||||
const KeyId key_id = "0000000000000000";
|
||||
|
||||
ASSERT_NO_FATAL_FAILURE(holder.OpenSession());
|
||||
holder.set_pst_length(kLargePstLength);
|
||||
ASSERT_NO_FATAL_FAILURE(holder.FetchLicense());
|
||||
ASSERT_NO_FATAL_FAILURE(holder.LoadLicense());
|
||||
EXPECT_EQ(NO_ERROR, holder.Decrypt(key_id));
|
||||
ASSERT_NO_FATAL_FAILURE(holder.CloseSession());
|
||||
// Should be able to close the previous session, open a new session,
|
||||
// and reload the license.
|
||||
ASSERT_NO_FATAL_FAILURE(holder.OpenSession());
|
||||
ASSERT_NO_FATAL_FAILURE(holder.ReloadLicense());
|
||||
EXPECT_EQ(NO_ERROR, holder.Decrypt(key_id));
|
||||
ASSERT_NO_FATAL_FAILURE(holder.CloseSession());
|
||||
}
|
||||
|
||||
TEST_F(CorePIGTest, OfflineMultipleLicensesWithDefrag) {
|
||||
const KeyId key_id = "0000000000000000";
|
||||
|
||||
|
||||
@@ -20,91 +20,123 @@ namespace wvcdm {
|
||||
|
||||
void ProvisioningHolder::Provision(CdmCertificateType cert_type,
|
||||
bool binary_provisioning) {
|
||||
CdmProvisioningRequest request;
|
||||
std::string provisioning_server_url;
|
||||
ASSERT_NO_FATAL_FAILURE(GenerateRequest(cert_type, binary_provisioning))
|
||||
<< "Failed to generate request";
|
||||
ASSERT_NO_FATAL_FAILURE(FetchResponse()) << "Failed to fetch response";
|
||||
ASSERT_NO_FATAL_FAILURE(LoadResponse(binary_provisioning))
|
||||
<< "Failed to load response";
|
||||
}
|
||||
|
||||
void ProvisioningHolder::GenerateRequest(CdmCertificateType cert_type,
|
||||
bool binary_provisioning) {
|
||||
// Preconditions.
|
||||
ASSERT_NE(cdm_engine_, nullptr) << "CdmEngine instance not set";
|
||||
|
||||
// Set cert authority if using X.509.
|
||||
std::string cert_authority;
|
||||
|
||||
CdmSessionId session_id;
|
||||
|
||||
if (cert_type == kCertificateX509) {
|
||||
cert_authority = "cast.google.com";
|
||||
}
|
||||
LOGV("Provision with type %s.", CdmCertificateTypeToString(cert_type));
|
||||
CdmResponseType result(CERT_PROVISIONING_NONCE_GENERATION_ERROR);
|
||||
LOGV("cert_authority = %s", cert_authority.c_str());
|
||||
|
||||
CdmResponseType status(CERT_PROVISIONING_NONCE_GENERATION_ERROR);
|
||||
CdmProvisioningRequest cdm_prov_request;
|
||||
std::string provisioning_server_url_unused;
|
||||
// Get a provisioning request. We might need one retry if there is a nonce
|
||||
// flood failure.
|
||||
for (int i = 0; i < 2 && result == CERT_PROVISIONING_NONCE_GENERATION_ERROR;
|
||||
for (int i = 0; i < 2 && status == CERT_PROVISIONING_NONCE_GENERATION_ERROR;
|
||||
i++) {
|
||||
result = cdm_engine_->GetProvisioningRequest(
|
||||
status = cdm_engine_->GetProvisioningRequest(
|
||||
cert_type, cert_authority, provisioning_service_certificate_,
|
||||
kLevelDefault, &request, &provisioning_server_url);
|
||||
if (result == CERT_PROVISIONING_NONCE_GENERATION_ERROR) {
|
||||
kLevelDefault, &cdm_prov_request, &provisioning_server_url_unused);
|
||||
if (status == CERT_PROVISIONING_NONCE_GENERATION_ERROR) {
|
||||
wvutil::TestSleep::Sleep(2);
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(NO_ERROR, result);
|
||||
LOGV("cert_authority = %s", cert_authority.c_str());
|
||||
ASSERT_EQ(status, NO_ERROR) << "Failed to generate provisioning request";
|
||||
|
||||
// |binary_provisioning| implies the CdmEngine is using binary
|
||||
// provisioning messages; however, ProvisioningHolder can only
|
||||
// operate using Base64 requests.
|
||||
if (binary_provisioning) {
|
||||
request = wvutil::Base64SafeEncodeNoPad(request);
|
||||
request_ = wvutil::Base64SafeEncodeNoPad(cdm_prov_request);
|
||||
} else {
|
||||
request_ = std::move(cdm_prov_request);
|
||||
}
|
||||
cert_type_ = cert_type;
|
||||
|
||||
if (config_.dump_golden_data()) {
|
||||
std::vector<uint8_t> binary_request = wvutil::Base64SafeDecode(request);
|
||||
CdmProvisioningRequest binary_request_string(binary_request.begin(),
|
||||
binary_request.end());
|
||||
const std::vector<uint8_t> binary_request =
|
||||
wvutil::Base64SafeDecode(request_);
|
||||
const CdmProvisioningRequest binary_request_string(binary_request.begin(),
|
||||
binary_request.end());
|
||||
MessageDumper::DumpProvisioningRequest(binary_request_string);
|
||||
}
|
||||
LOGV("Provisioning request: req = %s", request.c_str());
|
||||
|
||||
// Ignore URL provided by CdmEngine. Use ours, as configured
|
||||
// for test vs. production server.
|
||||
provisioning_server_url.assign(provisioning_server_url_);
|
||||
|
||||
// Make request.
|
||||
UrlRequest url_request(provisioning_server_url);
|
||||
if (!url_request.is_connected()) {
|
||||
LOGE("Failed to connect to provisioning server: url = %s",
|
||||
provisioning_server_url.c_str());
|
||||
if (log_core_message_) {
|
||||
const std::vector<uint8_t> binary_request =
|
||||
wvutil::Base64SafeDecode(request_);
|
||||
const CdmProvisioningRequest binary_request_string(binary_request.begin(),
|
||||
binary_request.end());
|
||||
MessageDumper::PrintProvisioningRequest(binary_request_string);
|
||||
}
|
||||
url_request.PostCertRequestInQueryString(request);
|
||||
}
|
||||
|
||||
// Receive and parse response.
|
||||
ASSERT_NO_FATAL_FAILURE(url_request.AssertOkResponseWithRetry(&response_))
|
||||
void ProvisioningHolder::FetchResponse() {
|
||||
// Preconditions.
|
||||
ASSERT_NE(cdm_engine_, nullptr) << "CdmEngine instance not set";
|
||||
ASSERT_FALSE(request_.empty()) << "No request was set";
|
||||
ASSERT_FALSE(provisioning_server_url_.empty())
|
||||
<< "Test config is missing provisioning URL";
|
||||
|
||||
UrlRequest url_request(provisioning_server_url_);
|
||||
ASSERT_TRUE(url_request.is_connected())
|
||||
<< "Failed to connect to provisoining server: "
|
||||
<< provisioning_server_url_;
|
||||
ASSERT_TRUE(url_request.PostCertRequestInQueryString(request_));
|
||||
std::string response;
|
||||
ASSERT_NO_FATAL_FAILURE(url_request.AssertOkResponseWithRetry(&response))
|
||||
<< "Failed to fetch provisioning response. "
|
||||
<< DumpProvAttempt(request, response_, cert_type);
|
||||
<< DumpProvAttempt(request_, response, cert_type_);
|
||||
ASSERT_FALSE(response.empty()) << "Missing response";
|
||||
response_ = std::move(response);
|
||||
}
|
||||
|
||||
void ProvisioningHolder::LoadResponse(bool binary_provisioning) {
|
||||
// Preconditions.
|
||||
ASSERT_FALSE(response_.empty()) << "No response was fetched";
|
||||
|
||||
std::string cdm_prov_response;
|
||||
if (binary_provisioning) {
|
||||
// extract provisioning response from received message
|
||||
// Extracts signed response from JSON string, result is serialized
|
||||
// protobuf.
|
||||
std::string protobuf_response;
|
||||
const bool extract_ok = ExtractSignedMessage(response_, &protobuf_response);
|
||||
ASSERT_TRUE(extract_ok) << "Failed to extract signed serialized "
|
||||
"response from JSON response";
|
||||
LOGV("Extracted response message: \n%s\n", protobuf_response.c_str());
|
||||
// CDM is expecting the response to be in binary form, response
|
||||
// must be extracted and decoded.
|
||||
std::string base_64_response;
|
||||
ASSERT_TRUE(ExtractSignedMessage(response_, &base_64_response))
|
||||
<< "Failed to extract signed serialized response from JSON response";
|
||||
ASSERT_FALSE(base_64_response.empty())
|
||||
<< "Base64 encoded provisioning response is unexpectedly empty";
|
||||
LOGV("Extracted response message: \n%s\n", base_64_response.c_str());
|
||||
|
||||
ASSERT_FALSE(protobuf_response.empty())
|
||||
<< "Protobuf response is unexpectedly empty";
|
||||
|
||||
// base64 decode response to yield binary protobuf
|
||||
const std::vector<uint8_t> response_vec(
|
||||
wvutil::Base64SafeDecode(protobuf_response));
|
||||
const std::vector<uint8_t> response_vec =
|
||||
wvutil::Base64SafeDecode(base_64_response);
|
||||
ASSERT_FALSE(response_vec.empty())
|
||||
<< "Failed to decode base64 of response: response = "
|
||||
<< protobuf_response;
|
||||
|
||||
response_.assign(response_vec.begin(), response_vec.end());
|
||||
<< "Failed to decode base64 response: " << base_64_response;
|
||||
cdm_prov_response.assign(response_vec.begin(), response_vec.end());
|
||||
} else {
|
||||
cdm_prov_response = response_;
|
||||
}
|
||||
|
||||
ASSERT_EQ(NO_ERROR,
|
||||
cdm_engine_->HandleProvisioningResponse(
|
||||
response_, kLevelDefault, &certificate_, &wrapped_key_))
|
||||
const CdmResponseType status = cdm_engine_->HandleProvisioningResponse(
|
||||
cdm_prov_response, kLevelDefault, &certificate_, &wrapped_key_);
|
||||
ASSERT_EQ(status, NO_ERROR)
|
||||
<< (binary_provisioning ? "Binary provisioning failed. "
|
||||
: "Non-binary provisioning failed. ")
|
||||
<< DumpProvAttempt(request, response_, cert_type);
|
||||
<< DumpProvAttempt(request_, response_, cert_type_);
|
||||
if (config_.dump_golden_data()) {
|
||||
MessageDumper::DumpProvisioning(response_);
|
||||
}
|
||||
if (log_core_message_) MessageDumper::PrintProvisioningResponse(response_);
|
||||
}
|
||||
|
||||
bool ProvisioningHolder::ExtractSignedMessage(const std::string& response,
|
||||
@@ -137,9 +169,9 @@ bool ProvisioningHolder::ExtractSignedMessage(const std::string& response,
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string ProvisioningHolder::DumpProvAttempt(const std::string& request,
|
||||
const std::string& response,
|
||||
CdmCertificateType cert_type) {
|
||||
std::string ProvisioningHolder::DumpProvAttempt(
|
||||
const std::string& request, const std::string& response,
|
||||
CdmCertificateType cert_type) const {
|
||||
std::stringstream info;
|
||||
info << "Cert Type: ";
|
||||
PrintTo(cert_type, &info);
|
||||
|
||||
@@ -19,20 +19,62 @@ class ProvisioningHolder {
|
||||
provisioning_server_url_(config.provisioning_server()),
|
||||
provisioning_service_certificate_(
|
||||
config.provisioning_service_certificate()) {}
|
||||
|
||||
// Generates requests, fetches response, and loads response.
|
||||
void Provision(CdmCertificateType cert_type, bool binary_provisioning);
|
||||
void Provision(bool binary_provisioning) {
|
||||
Provision(kCertificateWidevine, binary_provisioning);
|
||||
}
|
||||
std::string response() const { return response_; }
|
||||
std::string certificate() const { return certificate_; }
|
||||
std::string wrapped_key() const { return wrapped_key_; }
|
||||
|
||||
// Generates a provisioning request from the |cdm_engine_|.
|
||||
// If successful, the request the stored in |request_|.
|
||||
// The result of |request_| should always be the URL-Safe-Base64
|
||||
// encoded value of the request.
|
||||
void GenerateRequest(CdmCertificateType cert_type, bool binary_provisioning);
|
||||
void GenerateRequest(bool binary_provisioning) {
|
||||
GenerateRequest(kCertificateWidevine, binary_provisioning);
|
||||
}
|
||||
|
||||
// Fetch the provisioning response from the server.
|
||||
// Uses |request_| as the source of the request, and stores
|
||||
// JSON message of the response to |response_|.
|
||||
void FetchResponse();
|
||||
|
||||
void LoadResponse(bool binary_provisioning);
|
||||
|
||||
const std::string& request() const { return request_; }
|
||||
// Sets the request to be used on next call to FetchResponse().
|
||||
// The provided |request| must be a URL-Safe-Base64 encoding
|
||||
// of the provisioning request.
|
||||
void set_request(const std::string& request) { request_ = request; }
|
||||
const std::string& response() const { return response_; }
|
||||
const std::string& certificate() const { return certificate_; }
|
||||
const std::string& wrapped_key() const { return wrapped_key_; }
|
||||
|
||||
void ClearProvisioningData() {
|
||||
cert_type_ = kCertificateWidevine;
|
||||
request_.clear();
|
||||
response_.clear();
|
||||
certificate_.clear();
|
||||
wrapped_key_.clear();
|
||||
}
|
||||
|
||||
void set_log_core_message(bool log_core_message) {
|
||||
log_core_message_ = log_core_message;
|
||||
}
|
||||
|
||||
protected:
|
||||
TestCdmEngine* cdm_engine_;
|
||||
// Config variables.
|
||||
const ConfigTestEnv& config_;
|
||||
std::string provisioning_server_url_;
|
||||
std::string provisioning_service_certificate_;
|
||||
std::string response_;
|
||||
// Request variables.
|
||||
CdmCertificateType cert_type_;
|
||||
std::string request_; // URL-Safe-Base64 encoding of request.
|
||||
// Response variables.
|
||||
std::string response_; // JSON message containing response.
|
||||
// Post-provisioning variables.
|
||||
std::string certificate_;
|
||||
std::string wrapped_key_;
|
||||
|
||||
@@ -42,11 +84,15 @@ class ProvisioningHolder {
|
||||
// entire string represents a serialized protobuf mesaage and return true with
|
||||
// the entire string. If the end_substring match fails, return false with an
|
||||
// empty *result.
|
||||
bool ExtractSignedMessage(const std::string& response, std::string* result);
|
||||
static bool ExtractSignedMessage(const std::string& response,
|
||||
std::string* result);
|
||||
// Dump request and response information for use in a debug or failure log.
|
||||
std::string DumpProvAttempt(const std::string& request,
|
||||
const std::string& response,
|
||||
CdmCertificateType cert_type);
|
||||
CdmCertificateType cert_type) const;
|
||||
|
||||
private:
|
||||
bool log_core_message_ = false;
|
||||
};
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
Reference in New Issue
Block a user