Source release 19.5.0

This commit is contained in:
Cong Lin
2025-04-02 10:27:18 -07:00
parent 4407acee62
commit f7ec4fdeff
295 changed files with 32196 additions and 21748 deletions

View File

@@ -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;

View File

@@ -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,

View File

@@ -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

View File

@@ -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_

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -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,

View File

@@ -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");

View File

@@ -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;

View 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";

View File

@@ -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);

View File

@@ -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