KeyFetcher interface and HTTP impl to retrieve keys from the Widevine

License Service.

Change-Id: Icb1af3fd26a5243293dd089888d4b396539fd768
This commit is contained in:
Ramji Chandramouli 2014-10-07 14:33:08 -07:00
parent e7e86d684a
commit 8cc29520b0
10 changed files with 167 additions and 109 deletions

View File

@ -4,7 +4,7 @@
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#include "packager/media/base/http_fetcher.h"
#include "packager/media/base/http_key_fetcher.h"
#include <curl/curl.h>
#include "packager/base/strings/stringprintf.h"
@ -40,33 +40,36 @@ size_t AppendToString(char* ptr, size_t size, size_t nmemb, std::string* respons
namespace edash_packager {
namespace media {
HttpFetcher::HttpFetcher() {}
HttpFetcher::~HttpFetcher() {}
SimpleHttpFetcher::SimpleHttpFetcher() : timeout_in_seconds_(0) {
HttpKeyFetcher::HttpKeyFetcher() : timeout_in_seconds_(0) {
curl_global_init(CURL_GLOBAL_DEFAULT);
}
SimpleHttpFetcher::SimpleHttpFetcher(uint32_t timeout_in_seconds)
HttpKeyFetcher::HttpKeyFetcher(uint32_t timeout_in_seconds)
: timeout_in_seconds_(timeout_in_seconds) {
curl_global_init(CURL_GLOBAL_DEFAULT);
}
SimpleHttpFetcher::~SimpleHttpFetcher() {
HttpKeyFetcher::~HttpKeyFetcher() {
curl_global_cleanup();
}
Status SimpleHttpFetcher::Get(const std::string& path, std::string* response) {
Status HttpKeyFetcher::FetchKeys(const std::string& url,
const std::string& request,
std::string* response) {
return Post(url, request, response);
}
Status HttpKeyFetcher::Get(const std::string& path, std::string* response) {
return FetchInternal(GET, path, "", response);
}
Status SimpleHttpFetcher::Post(const std::string& path,
Status HttpKeyFetcher::Post(const std::string& path,
const std::string& data,
std::string* response) {
return FetchInternal(POST, path, data, response);
}
Status SimpleHttpFetcher::FetchInternal(HttpMethod method,
Status HttpKeyFetcher::FetchInternal(HttpMethod method,
const std::string& path,
const std::string& data,
std::string* response) {

View File

@ -4,27 +4,41 @@
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#ifndef MEDIA_BASE_HTTP_FETCHER_H_
#define MEDIA_BASE_HTTP_FETCHER_H_
#ifndef MEDIA_BASE_HTTP_KEY_FETCHER_H_
#define MEDIA_BASE_HTTP_KEY_FETCHER_H_
#include "packager/base/compiler_specific.h"
#include "packager/media/base/key_fetcher.h"
#include "packager/media/base/status.h"
namespace edash_packager {
namespace media {
/// Defines a generic http fetcher interface.
class HttpFetcher {
/// A KeyFetcher implementation that retrieves keys over HTTP(s).
/// This class is not fully thread safe. It can be used in multi-thread
/// environment once constructed, but it may not be safe to create a
/// HttpKeyFetcher object when any other thread is running due to use of
/// curl_global_init.
class HttpKeyFetcher : public KeyFetcher {
public:
HttpFetcher();
virtual ~HttpFetcher();
/// Creates a fetcher with no timeout.
HttpKeyFetcher();
/// Create a fetcher with timeout.
/// @param timeout_in_seconds specifies the timeout in seconds.
HttpKeyFetcher(uint32_t timeout_in_seconds);
virtual ~HttpKeyFetcher();
/// @name KeyFetcher implementation overrides.
virtual Status FetchKeys(const std::string& url,
const std::string& request,
std::string* response) OVERRIDE;
/// Fetch content using HTTP GET.
/// @param url specifies the content URL.
/// @param[out] response will contain the body of the http response on
/// success. It should not be NULL.
/// @return OK on success.
virtual Status Get(const std::string& url, std::string* response) = 0;
virtual Status Get(const std::string& url, std::string* response);
/// Fetch content using HTTP POST.
/// @param url specifies the content URL.
@ -33,33 +47,7 @@ class HttpFetcher {
/// @return OK on success.
virtual Status Post(const std::string& url,
const std::string& data,
std::string* response) = 0;
private:
DISALLOW_COPY_AND_ASSIGN(HttpFetcher);
};
/// A simple HttpFetcher implementation.
/// This class is not fully thread safe. It can be used in multi-thread
/// environment once constructed, but it may not be safe to create a
/// SimpleHttpFetcher object when any other thread is running due to use of
/// curl_global_init.
class SimpleHttpFetcher : public HttpFetcher {
public:
/// Creates a fetcher with no timeout.
SimpleHttpFetcher();
/// Create a fetcher with timeout.
/// @param timeout_in_seconds specifies the timeout in seconds.
SimpleHttpFetcher(uint32_t timeout_in_seconds);
virtual ~SimpleHttpFetcher();
/// @name HttpFetcher implementation overrides.
/// @{
virtual Status Get(const std::string& url, std::string* response) OVERRIDE;
virtual Status Post(const std::string& url,
const std::string& data,
std::string* response) OVERRIDE;
/// @}
std::string* response);
private:
enum HttpMethod {
@ -74,11 +62,11 @@ class SimpleHttpFetcher : public HttpFetcher {
const uint32_t timeout_in_seconds_;
DISALLOW_COPY_AND_ASSIGN(SimpleHttpFetcher);
DISALLOW_COPY_AND_ASSIGN(HttpKeyFetcher);
};
} // namespace media
} // namespace edash_packager
#endif // MEDIA_BASE_HTTP_FETCHER_H_
#endif // MEDIA_BASE_HTTP_KEY_FETCHER_H_

View File

@ -4,7 +4,7 @@
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#include "packager/media/base/http_fetcher.h"
#include "packager/media/base/http_key_fetcher.h"
#include "packager/base/logging.h"
#include "packager/base/strings/string_number_conversions.h"
@ -29,7 +29,7 @@ namespace media {
static void CheckHttpGet(const std::string& url,
const std::string& expected_response) {
SimpleHttpFetcher fetcher;
HttpKeyFetcher fetcher;
std::string response;
ASSERT_OK(fetcher.Get(url, &response));
base::RemoveChars(response, "\r\n\t ", &response);
@ -38,14 +38,13 @@ static void CheckHttpGet(const std::string& url,
static void CheckHttpPost(const std::string& url, const std::string& data,
const std::string& expected_response) {
SimpleHttpFetcher fetcher;
HttpKeyFetcher fetcher;
std::string response;
ASSERT_OK(fetcher.Post(url, data, &response));
base::RemoveChars(response, "\r\n\t ", &response);
EXPECT_EQ(expected_response, response);
}
TEST(DISABLED_HttpFetcherTest, HttpGet) {
CheckHttpGet(kTestUrl, kExpectedGetResponse);
}
@ -54,34 +53,45 @@ TEST(DISABLED_HttpFetcherTest, HttpPost) {
CheckHttpPost(kTestUrl, kPostData, kExpectedPostResponse);
}
TEST(DISABLED_HttpFetcherTest, InvalidUrl) {
const char kHttpNotFound[] = "404";
TEST(DISABLED_HttpKeyFetcherTest, HttpFetchKeys) {
HttpKeyFetcher fetcher;
std::string response;
ASSERT_OK(fetcher.FetchKeys(kTestUrl, kPostData, &response));
base::RemoveChars(response, "\r\n\t ", &response);
EXPECT_EQ(kExpectedPostResponse, response);
}
SimpleHttpFetcher fetcher;
TEST(DISABLED_HttpKeyFetcherTest, InvalidUrl) {
const char kHttpNotFound[] = "404";
HttpKeyFetcher fetcher;
std::string response;
const std::string invalid_url(kTestUrl, sizeof(kTestUrl) - 2);
Status status = fetcher.Get(invalid_url, &response);
Status status = fetcher.FetchKeys(invalid_url, kPostData, &response);
EXPECT_EQ(error::HTTP_FAILURE, status.error_code());
EXPECT_NE(std::string::npos, status.error_message().find(kHttpNotFound));
}
TEST(DISABLED_HttpFetcherTest, UrlWithPort) {
CheckHttpGet(kTestUrlWithPort, kExpectedGetResponse);
TEST(DISABLED_HttpKeyFetcherTest, UrlWithPort) {
HttpKeyFetcher fetcher;
std::string response;
ASSERT_OK(fetcher.FetchKeys(kTestUrlWithPort, kPostData, &response));
base::RemoveChars(response, "\r\n\t ", &response);
EXPECT_EQ(kExpectedPostResponse, response);
}
TEST(DISABLED_HttpFetcherTest, SmallTimeout) {
TEST(DISABLED_HttpKeyFetcherTest, SmallTimeout) {
const uint32_t kTimeoutInSeconds = 1;
SimpleHttpFetcher fetcher(kTimeoutInSeconds);
HttpKeyFetcher fetcher(kTimeoutInSeconds);
std::string response;
Status status = fetcher.Post(kTestUrl, kDelayTwoSecs, &response);
Status status = fetcher.FetchKeys(kTestUrl, kDelayTwoSecs, &response);
EXPECT_EQ(error::TIME_OUT, status.error_code());
}
TEST(DISABLED_HttpFetcherTest, BigTimeout) {
TEST(DISABLED_HttpKeyFetcherTest, BigTimeout) {
const uint32_t kTimeoutInSeconds = 5;
SimpleHttpFetcher fetcher(kTimeoutInSeconds);
HttpKeyFetcher fetcher(kTimeoutInSeconds);
std::string response;
Status status = fetcher.Post(kTestUrl, kDelayTwoSecs, &response);
Status status = fetcher.FetchKeys(kTestUrl, kDelayTwoSecs, &response);
EXPECT_OK(status);
}

View File

@ -0,0 +1,16 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#include "packager/media/base/key_fetcher.h"
namespace edash_packager {
namespace media {
KeyFetcher::KeyFetcher() {}
KeyFetcher::~KeyFetcher() {}
} // namespace media
} // namespace edash_packager

View File

@ -0,0 +1,40 @@
// Copyright 2014 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#ifndef MEDIA_BASE_KEY_FETCHER_H_
#define MEDIA_BASE_KEY_FETCHER_H_
#include "base/macros.h"
#include "packager/media/base/status.h"
namespace edash_packager {
namespace media {
/// Base class for fetching keys from the license service.
class KeyFetcher {
public:
KeyFetcher();
virtual ~KeyFetcher();
/// Fetch Keys from license service.
/// |response| is owned by caller.
/// @param service_address license service address.
/// @param request JSON formatted request.
/// @param response JSON formatted response. Owned by caller.
/// @return OK on success.
virtual Status FetchKeys(const std::string& service_address,
const std::string& request,
std::string* response) = 0;
private:
DISALLOW_COPY_AND_ASSIGN(KeyFetcher);
};
} // namespace media
} // namespace edash_packager
#endif // MEDIA_BASE_KEY_FETCHER_H_

View File

@ -55,7 +55,7 @@ class KeySource {
/// @param asset_id is the Widevine Classic asset ID for the content to be
/// decrypted.
/// @return OK on success, an error status otherwise.
virtual Status FetchKeys(uint32_t asset_id) OVERRIDE;
virtual Status FetchKeys(uint32_t asset_id);
/// Get encryption key of the specified track type.
/// @param track_type is the type of track for which retrieving the key.

View File

@ -36,8 +36,10 @@
'decrypt_config.cc',
'decrypt_config.h',
'decryptor_source.h',
'http_fetcher.cc',
'http_fetcher.h',
'http_key_fetcher.cc',
'http_key_fetcher.h',
'key_fetcher.cc',
'key_fetcher.h',
'key_source.cc',
'key_source.h',
'limits.h',
@ -90,7 +92,7 @@
'container_names_unittest.cc',
'fake_prng.cc', # For rsa_key_unittest
'fake_prng.h', # For rsa_key_unittest
'http_fetcher_unittest.cc',
'http_key_fetcher_unittest.cc',
'muxer_util_unittest.cc',
'offset_byte_queue_unittest.cc',
'producer_consumer_queue_unittest.cc',

View File

@ -12,7 +12,7 @@
#include "packager/base/json/json_writer.h"
#include "packager/base/memory/ref_counted.h"
#include "packager/base/stl_util.h"
#include "packager/media/base/http_fetcher.h"
#include "packager/media/base/http_key_fetcher.h"
#include "packager/media/base/producer_consumer_queue.h"
#include "packager/media/base/request_signer.h"
@ -43,7 +43,7 @@ const int kFirstRetryDelayMilliseconds = 1000;
// key rotation enabled request.
const int kDefaultCryptoPeriodCount = 10;
const int kGetKeyTimeoutInSeconds = 5 * 60; // 5 minutes.
const int kHttpTimeoutInSeconds = 60; // 1 minute.
const int kKeyFetchTimeoutInSeconds = 60; // 1 minute.
bool Base64StringToBytes(const std::string& base64_string,
std::vector<uint8_t>* bytes) {
@ -143,7 +143,7 @@ WidevineKeySource::WidevineKeySource(
"KeyProductionThread",
base::Bind(&WidevineKeySource::FetchKeysTask,
base::Unretained(this))),
http_fetcher_(new SimpleHttpFetcher(kHttpTimeoutInSeconds)),
key_fetcher_(new HttpKeyFetcher(kKeyFetchTimeoutInSeconds)),
server_url_(server_url),
signer_(signer.Pass()),
crypto_period_count_(kDefaultCryptoPeriodCount),
@ -251,9 +251,9 @@ Status WidevineKeySource::GetCryptoPeriodKey(uint32_t crypto_period_index,
return GetKeyInternal(crypto_period_index, track_type, key);
}
void WidevineKeySource::set_http_fetcher(
scoped_ptr<HttpFetcher> http_fetcher) {
http_fetcher_ = http_fetcher.Pass();
void WidevineKeySource::set_key_fetcher(
scoped_ptr<KeyFetcher> key_fetcher) {
key_fetcher_ = key_fetcher.Pass();
}
Status WidevineKeySource::GetKeyInternal(uint32_t crypto_period_index,
@ -324,7 +324,7 @@ Status WidevineKeySource::FetchKeysInternal(bool enable_key_rotation,
// Perform client side retries if seeing server transient error to workaround
// server limitation.
for (int i = 0; i < kNumTransientErrorRetries; ++i) {
status = http_fetcher_->Post(server_url_, message, &raw_response);
status = key_fetcher_->FetchKeys(server_url_, message, &raw_response);
if (status.ok()) {
VLOG(1) << "Retry [" << i << "] Response:" << raw_response;

View File

@ -17,7 +17,7 @@
namespace edash_packager {
namespace media {
class HttpFetcher;
class KeyFetcher;
class RequestSigner;
template <class T> class ProducerConsumerQueue;
@ -47,9 +47,9 @@ class WidevineKeySource : public KeySource {
EncryptionKey* key) OVERRIDE;
/// @}
/// Inject an @b HttpFetcher object, mainly used for testing.
/// @param http_fetcher points to the @b HttpFetcher object to be injected.
void set_http_fetcher(scoped_ptr<HttpFetcher> http_fetcher);
/// Inject an @b KeyFetcher object, mainly used for testing.
/// @param key_fetcher points to the @b KeyFetcher object to be injected.
void set_key_fetcher(scoped_ptr<KeyFetcher> key_fetcher);
protected:
ClosureThread key_production_thread_;
@ -98,10 +98,10 @@ class WidevineKeySource : public KeySource {
// Push the keys to the key pool.
bool PushToKeyPool(EncryptionKeyMap* encryption_key_map);
// The fetcher object used to fetch HTTP response from server.
// The fetcher object used to fetch keys from the license service.
// It is initialized to a default fetcher on class initialization.
// Can be overridden using set_http_fetcher for testing or other purposes.
scoped_ptr<HttpFetcher> http_fetcher_;
// Can be overridden using set_key_fetcher for testing or other purposes.
scoped_ptr<KeyFetcher> key_fetcher_;
std::string server_url_;
scoped_ptr<RequestSigner> signer_;
base::DictionaryValue request_dict_;

View File

@ -10,7 +10,7 @@
#include "packager/base/base64.h"
#include "packager/base/strings/string_number_conversions.h"
#include "packager/base/strings/stringprintf.h"
#include "packager/media/base/http_fetcher.h"
#include "packager/media/base/key_fetcher.h"
#include "packager/media/base/request_signer.h"
#include "packager/media/base/status_test_util.h"
#include "packager/media/base/widevine_key_source.h"
@ -129,26 +129,25 @@ class MockRequestSigner : public RequestSigner {
DISALLOW_COPY_AND_ASSIGN(MockRequestSigner);
};
class MockHttpFetcher : public HttpFetcher {
class MockKeyFetcher : public KeyFetcher {
public:
MockHttpFetcher() : HttpFetcher() {}
virtual ~MockHttpFetcher() {}
MockKeyFetcher() : KeyFetcher() {}
virtual ~MockKeyFetcher() {}
MOCK_METHOD2(Get, Status(const std::string& url, std::string* response));
MOCK_METHOD3(Post,
Status(const std::string& url,
MOCK_METHOD3(FetchKeys,
Status(const std::string& service_address,
const std::string& data,
std::string* response));
private:
DISALLOW_COPY_AND_ASSIGN(MockHttpFetcher);
DISALLOW_COPY_AND_ASSIGN(MockKeyFetcher);
};
class WidevineKeySourceTest : public ::testing::Test {
public:
WidevineKeySourceTest()
: mock_request_signer_(new MockRequestSigner(kSignerName)),
mock_http_fetcher_(new MockHttpFetcher()) {}
mock_key_fetcher_(new MockKeyFetcher()) {}
virtual void SetUp() OVERRIDE {
content_id_.assign(
@ -161,8 +160,8 @@ class WidevineKeySourceTest : public ::testing::Test {
widevine_key_source_.reset(new WidevineKeySource(
kServerUrl,
mock_request_signer_.PassAs<RequestSigner>()));
widevine_key_source_->set_http_fetcher(
mock_http_fetcher_.PassAs<HttpFetcher>());
widevine_key_source_->set_key_fetcher(
mock_key_fetcher_.PassAs<KeyFetcher>());
}
void VerifyKeys(bool classic) {
@ -182,7 +181,7 @@ class WidevineKeySourceTest : public ::testing::Test {
}
}
scoped_ptr<MockRequestSigner> mock_request_signer_;
scoped_ptr<MockHttpFetcher> mock_http_fetcher_;
scoped_ptr<MockKeyFetcher> mock_key_fetcher_;
scoped_ptr<WidevineKeySource> widevine_key_source_;
std::vector<uint8_t> content_id_;
@ -212,7 +211,7 @@ TEST_F(WidevineKeySourceTest, GenerateSignatureFailure) {
// Check whether expected request message and post data was generated and
// verify the correct behavior on http failure.
TEST_F(WidevineKeySourceTest, HttpPostFailure) {
TEST_F(WidevineKeySourceTest, HttpFetchFailure) {
std::string expected_message = base::StringPrintf(
kExpectedRequestMessageFormat, Base64Encode(kContentId).c_str(), kPolicy);
EXPECT_CALL(*mock_request_signer_, GenerateSignature(expected_message, _))
@ -224,8 +223,8 @@ TEST_F(WidevineKeySourceTest, HttpPostFailure) {
Base64Encode(kMockSignature).c_str(),
kSignerName);
const Status kMockStatus = Status::UNKNOWN;
EXPECT_CALL(*mock_http_fetcher_,
Post(StrEq(kServerUrl), expected_post_data, _))
EXPECT_CALL(*mock_key_fetcher_,
FetchKeys(kServerUrl, expected_post_data, _))
.WillOnce(Return(kMockStatus));
CreateWidevineKeySource();
@ -240,7 +239,7 @@ TEST_F(WidevineKeySourceTest, LicenseStatusCencOK) {
std::string mock_response = base::StringPrintf(
kHttpResponseFormat, Base64Encode(GenerateMockLicenseResponse()).c_str());
EXPECT_CALL(*mock_http_fetcher_, Post(_, _, _))
EXPECT_CALL(*mock_key_fetcher_, FetchKeys(_, _, _))
.WillOnce(DoAll(SetArgPointee<2>(mock_response), Return(Status::OK)));
CreateWidevineKeySource();
@ -256,7 +255,7 @@ TEST_F(WidevineKeySourceTest, LicenseStatusCencNotOK) {
kHttpResponseFormat, Base64Encode(
GenerateMockClassicLicenseResponse()).c_str());
EXPECT_CALL(*mock_http_fetcher_, Post(_, _, _))
EXPECT_CALL(*mock_key_fetcher_, FetchKeys(_, _, _))
.WillOnce(DoAll(SetArgPointee<2>(mock_response), Return(Status::OK)));
CreateWidevineKeySource();
@ -272,7 +271,7 @@ TEST_F(WidevineKeySourceTest, LicenseStatusCencWithPsshDataOK) {
std::string mock_response = base::StringPrintf(
kHttpResponseFormat, Base64Encode(GenerateMockLicenseResponse()).c_str());
EXPECT_CALL(*mock_http_fetcher_, Post(_, _, _))
EXPECT_CALL(*mock_key_fetcher_, FetchKeys(_, _, _))
.WillOnce(DoAll(SetArgPointee<2>(mock_response), Return(Status::OK)));
CreateWidevineKeySource();
@ -291,7 +290,7 @@ TEST_F(WidevineKeySourceTest, LicenseStatusClassicOK) {
kHttpResponseFormat, Base64Encode(
GenerateMockClassicLicenseResponse()).c_str());
EXPECT_CALL(*mock_http_fetcher_, Post(_, _, _))
EXPECT_CALL(*mock_key_fetcher_, FetchKeys(_, _, _))
.WillOnce(DoAll(SetArgPointee<2>(mock_response), Return(Status::OK)));
CreateWidevineKeySource();
@ -307,7 +306,7 @@ TEST_F(WidevineKeySourceTest, RetryOnHttpTimeout) {
kHttpResponseFormat, Base64Encode(GenerateMockLicenseResponse()).c_str());
// Retry is expected on HTTP timeout.
EXPECT_CALL(*mock_http_fetcher_, Post(_, _, _))
EXPECT_CALL(*mock_key_fetcher_, FetchKeys(_, _, _))
.WillOnce(Return(Status(error::TIME_OUT, "")))
.WillOnce(DoAll(SetArgPointee<2>(mock_response), Return(Status::OK)));
@ -329,7 +328,7 @@ TEST_F(WidevineKeySourceTest, RetryOnTransientError) {
kHttpResponseFormat, Base64Encode(GenerateMockLicenseResponse()).c_str());
// Retry is expected on transient error.
EXPECT_CALL(*mock_http_fetcher_, Post(_, _, _))
EXPECT_CALL(*mock_key_fetcher_, FetchKeys(_, _, _))
.WillOnce(DoAll(SetArgPointee<2>(mock_response), Return(Status::OK)))
.WillOnce(DoAll(SetArgPointee<2>(expected_retried_response),
Return(Status::OK)));
@ -348,7 +347,7 @@ TEST_F(WidevineKeySourceTest, NoRetryOnUnknownError) {
std::string mock_response = base::StringPrintf(
kHttpResponseFormat, Base64Encode(mock_license_status).c_str());
EXPECT_CALL(*mock_http_fetcher_, Post(_, _, _))
EXPECT_CALL(*mock_key_fetcher_, FetchKeys(_, _, _))
.WillOnce(DoAll(SetArgPointee<2>(mock_response), Return(Status::OK)));
CreateWidevineKeySource();
@ -412,7 +411,7 @@ TEST_F(WidevineKeySourceTest, KeyRotationTest) {
.WillOnce(Return(true));
std::string mock_response = base::StringPrintf(
kHttpResponseFormat, Base64Encode(GenerateMockLicenseResponse()).c_str());
EXPECT_CALL(*mock_http_fetcher_, Post(_, _, _))
EXPECT_CALL(*mock_key_fetcher_, FetchKeys(_, _, _))
.WillOnce(DoAll(SetArgPointee<2>(mock_response), Return(Status::OK)));
for (uint32_t i = 0; i < kCryptoIterations; ++i) {
@ -432,7 +431,7 @@ TEST_F(WidevineKeySourceTest, KeyRotationTest) {
Base64Encode(GenerateMockKeyRotationLicenseResponse(
first_crypto_period_index, kCryptoPeriodCount))
.c_str());
EXPECT_CALL(*mock_http_fetcher_, Post(_, _, _))
EXPECT_CALL(*mock_key_fetcher_, FetchKeys(_, _, _))
.WillOnce(DoAll(SetArgPointee<2>(mock_response), Return(Status::OK)));
}