Widevine encryptor source unittest

Change-Id: I1b56d0a019480472eb462d8a39f81c4eb3c81a0e
This commit is contained in:
Kongqun Yang 2014-02-20 14:38:28 -08:00
parent f73f4bb773
commit d90ca489f9
7 changed files with 315 additions and 49 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 "media/base/httpfetcher.h"
#include "media/base/http_fetcher.h"
#ifdef WIN32
#include <winsock2.h>
@ -16,7 +16,7 @@
namespace {
struct HTTPResult {
struct HttpResult {
int status_code;
std::string status_message;
std::string response;
@ -74,7 +74,7 @@ void OnBegin(const happyhttp::Response* response, void* userdata) {
DLOG(INFO) << "BEGIN (" << response->getstatus() << ", "
<< response->getreason() << ").";
HTTPResult* result = static_cast<HTTPResult*>(userdata);
HttpResult* result = static_cast<HttpResult*>(userdata);
result->status_code = response->getstatus();
result->status_message = response->getreason();
result->response.clear();
@ -85,13 +85,13 @@ void OnData(const happyhttp::Response* response,
const unsigned char* data,
int num_bytes) {
DCHECK(response && userdata && data);
HTTPResult* result = static_cast<HTTPResult*>(userdata);
HttpResult* result = static_cast<HttpResult*>(userdata);
result->response.append(reinterpret_cast<const char*>(data), num_bytes);
}
void OnComplete(const happyhttp::Response* response, void* userdata) {
DCHECK(response && userdata);
HTTPResult* result = static_cast<HTTPResult*>(userdata);
HttpResult* result = static_cast<HttpResult*>(userdata);
DLOG(INFO) << "COMPLETE (" << result->response.size() << " bytes).";
}
@ -101,7 +101,10 @@ const int kHttpOK = 200;
namespace media {
HTTPFetcher::HTTPFetcher() {
HttpFetcher::HttpFetcher() {}
HttpFetcher::~HttpFetcher() {}
SimpleHttpFetcher::SimpleHttpFetcher() {
#ifdef WIN32
WSAData wsa_data;
int code = WSAStartup(MAKEWORD(1, 1), &wsa_data);
@ -111,23 +114,24 @@ HTTPFetcher::HTTPFetcher() {
#endif // WIN32
}
HTTPFetcher::~HTTPFetcher() {
SimpleHttpFetcher::~SimpleHttpFetcher() {
#ifdef WIN32
if (wsa_startup_succeeded_)
WSACleanup();
#endif // WIN32
}
Status HTTPFetcher::Get(const std::string& path, std::string* response) {
Status SimpleHttpFetcher::Get(const std::string& path, std::string* response) {
return FetchInternal("GET", path, "", response);
}
Status HTTPFetcher::Post(const std::string& path, const std::string& data,
Status SimpleHttpFetcher::Post(const std::string& path,
const std::string& data,
std::string* response) {
return FetchInternal("POST", path, data, response);
}
Status HTTPFetcher::FetchInternal(const std::string& method,
Status SimpleHttpFetcher::FetchInternal(const std::string& method,
const std::string& url,
const std::string& data,
std::string* response) {
@ -145,7 +149,7 @@ Status HTTPFetcher::FetchInternal(const std::string& method,
}
try {
HTTPResult result;
HttpResult result;
happyhttp::Connection connection(host.data(), port);
connection.setcallbacks(OnBegin, OnData, OnComplete, &result);

View File

@ -4,32 +4,49 @@
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#ifndef MEDIA_BASE_HTTPFETCHER_H_
#define MEDIA_BASE_HTTPFETCHER_H_
#ifndef MEDIA_BASE_HTTP_FETCHER_H_
#define MEDIA_BASE_HTTP_FETCHER_H_
#include "base/compiler_specific.h"
#include "media/base/status.h"
namespace media {
// A simple HTTP fetcher implementation using happyhttp.
class HTTPFetcher {
class HttpFetcher {
public:
// TODO(kqyang): Add timeout support.
HTTPFetcher();
~HTTPFetcher();
HttpFetcher();
virtual ~HttpFetcher();
// Fetch |response| from |url| using HTTP GET.
// |response| should not be NULL, will contain the body of the http response
// on success.
// Return OK on success.
Status Get(const std::string& url, std::string* response);
virtual Status Get(const std::string& url, std::string* response) = 0;
// Fetch |response| from |url| using HTTP POST.
// |response| should not be NULL, will contain the body of the http response
// on success.
// Return OK on success.
Status Post(const std::string& url, const std::string& data,
std::string* response);
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 using happyhttp.
class SimpleHttpFetcher : public HttpFetcher {
public:
// TODO(kqyang): Add timeout support.
SimpleHttpFetcher();
virtual ~SimpleHttpFetcher();
// 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;
private:
// Internal implementation of HTTP functions, e.g. Get and Post.
@ -41,10 +58,10 @@ class HTTPFetcher {
bool wsa_startup_succeeded_;
#endif
DISALLOW_COPY_AND_ASSIGN(HTTPFetcher);
DISALLOW_COPY_AND_ASSIGN(SimpleHttpFetcher);
};
} // namespace media
#endif // MEDIA_BASE_HTTPFETCHER_H_
#endif // MEDIA_BASE_HTTP_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 "media/base/httpfetcher.h"
#include "media/base/http_fetcher.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
@ -30,7 +30,7 @@ namespace media {
static void CheckHttpGet(const std::string& url,
const std::string& expected_response) {
HTTPFetcher fetcher;
SimpleHttpFetcher fetcher;
std::string response;
ASSERT_OK(fetcher.Get(url, &response));
base::RemoveChars(response, "\r\n\t ", &response);
@ -39,7 +39,7 @@ static void CheckHttpGet(const std::string& url,
static void CheckHttpPost(const std::string& url, const std::string& data,
const std::string& expected_response) {
HTTPFetcher fetcher;
SimpleHttpFetcher fetcher;
std::string response;
ASSERT_OK(fetcher.Post(url, data, &response));
base::RemoveChars(response, "\r\n\t ", &response);
@ -47,16 +47,16 @@ static void CheckHttpPost(const std::string& url, const std::string& data,
}
TEST(HTTPFetcherTest, HttpGet) {
TEST(HttpFetcherTest, HttpGet) {
CheckHttpGet(kTestUrl, kExpectedGetResponse);
}
TEST(HTTPFetcherTest, HttpPost) {
TEST(HttpFetcherTest, HttpPost) {
CheckHttpPost(kTestUrl, kPostData, kExpectedPostResponse);
}
TEST(HTTPFetcherTest, InvalidUrl) {
HTTPFetcher fetcher;
TEST(HttpFetcherTest, InvalidUrl) {
SimpleHttpFetcher fetcher;
std::string response;
const std::string invalid_url(kTestUrl, sizeof(kTestUrl) - 2);
Status status = fetcher.Get(invalid_url, &response);
@ -65,7 +65,7 @@ TEST(HTTPFetcherTest, InvalidUrl) {
EndsWith(status.error_message(), base::IntToString(kHttpNotFound), true));
}
TEST(HTTPFetcherTest, UrlWithPort) {
TEST(HttpFetcherTest, UrlWithPort) {
CheckHttpGet(kTestUrlWithPort, kExpectedGetResponse);
}

View File

@ -23,11 +23,11 @@
],
},
{
'target_name': 'httpfetcher',
'target_name': 'http_fetcher',
'type': '<(component)',
'sources': [
'httpfetcher.cc',
'httpfetcher.h',
'http_fetcher.cc',
'http_fetcher.h',
],
'cflags!': [ '-fno-exceptions' ],
'cflags_cc!': [ '-fno-exceptions' ],
@ -45,16 +45,16 @@
},
{
# Note that this test performs real http requests to a http server.
'target_name': 'httpfetcher_unittest',
'target_name': 'http_fetcher_unittest',
'type': '<(gtest_target_type)',
'sources': [
'httpfetcher_unittest.cc',
'http_fetcher_unittest.cc',
],
'dependencies': [
'../../base/base.gyp:base',
'../../testing/gtest.gyp:gtest',
'../../testing/gtest.gyp:gtest_main',
'httpfetcher',
'http_fetcher',
],
},
{
@ -110,7 +110,7 @@
'dependencies': [
'../../base/base.gyp:base',
'../../third_party/openssl/openssl.gyp:openssl',
'httpfetcher',
'http_fetcher',
'status',
],
},
@ -130,6 +130,7 @@
'status_test_util.h',
'status_test_util_unittest.cc',
'status_unittest.cc',
'widevine_encryptor_source_unittest.cc',
],
'dependencies': [
'../../testing/gtest.gyp:gtest',

View File

@ -12,7 +12,7 @@
#include "base/time/time.h"
#include "base/threading/platform_thread.h"
#include "base/values.h"
#include "media/base/httpfetcher.h"
#include "media/base/http_fetcher.h"
#include "media/base/request_signer.h"
// TODO(kqyang): Move media/mp4/rcheck.h to media/base/.
@ -102,7 +102,8 @@ WidevineEncryptorSource::WidevineEncryptorSource(
const std::string& content_id,
TrackType track_type,
scoped_ptr<RequestSigner> signer)
: server_url_(server_url),
: http_fetcher_(new SimpleHttpFetcher()),
server_url_(server_url),
content_id_(content_id),
track_type_(track_type),
signer_(signer.Pass()) {
@ -120,14 +121,13 @@ Status WidevineEncryptorSource::Initialize() {
return status;
VLOG(1) << "Message: " << message;
HTTPFetcher fetcher;
std::string raw_response;
int64 sleep_duration = kFirstRetryDelayMilliseconds;
// Perform client side retries if seeing server transient error to workaround
// server limitation.
for (int i = 0; i < kNumTransientErrorRetries; ++i) {
status = fetcher.Post(server_url_, message, &raw_response);
status = http_fetcher_->Post(server_url_, message, &raw_response);
if (!status.ok())
return status;
VLOG(1) << "Retry [" << i << "] Response:" << raw_response;
@ -172,6 +172,11 @@ WidevineEncryptorSource::GetTrackTypeFromString(
return TRACK_TYPE_UNKNOWN;
}
void WidevineEncryptorSource::set_http_fetcher(
scoped_ptr<HttpFetcher> http_fetcher) {
http_fetcher_ = http_fetcher.Pass();
}
void WidevineEncryptorSource::FillRequest(const std::string& content_id,
std::string* request) {
DCHECK(request);
@ -256,8 +261,7 @@ bool WidevineEncryptorSource::IsExpectedTrackType(
return track_type_ == GetTrackTypeFromString(track_type_string);
}
bool WidevineEncryptorSource::ExtractEncryptionKey(
const std::string& response,
bool WidevineEncryptorSource::ExtractEncryptionKey(const std::string& response,
bool* transient_error) {
DCHECK(transient_error);
*transient_error = false;

View File

@ -8,10 +8,12 @@
#define MEDIA_BASE_WIDEVINE_ENCRYPTOR_SOURCE_H_
#include "base/basictypes.h"
#include "base/memory/scoped_ptr.h"
#include "media/base/encryptor_source.h"
namespace media {
class HttpFetcher;
class RequestSigner;
// Defines an encryptor source which talks to Widevine encryption server.
@ -37,6 +39,8 @@ class WidevineEncryptorSource : public EncryptorSource {
static WidevineEncryptorSource::TrackType GetTrackTypeFromString(
const std::string& track_type_string);
void set_http_fetcher(scoped_ptr<HttpFetcher> http_fetcher);
private:
// Fill |request| with necessary fields for Widevine encryption request.
// |request| should not be NULL.
@ -55,6 +59,10 @@ class WidevineEncryptorSource : public EncryptorSource {
bool ExtractEncryptionKey(const std::string& response,
bool* transient_error);
// The fetcher object used to fetch HTTP response from server.
// 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_;
std::string server_url_;
std::string content_id_;
TrackType track_type_;

View File

@ -0,0 +1,232 @@
// 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 "media/base/widevine_encryptor_source.h"
#include "base/base64.h"
#include "base/strings/stringprintf.h"
#include "media/base/http_fetcher.h"
#include "media/base/request_signer.h"
#include "media/base/status_test_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
const char kServerUrl[] = "http://www.foo.com/getcontentkey";
const char kContentId[] = "ContentFoo";
const char kTrackType[] = "SD";
const char kSignerName[] = "SignerFoo";
const char kMockSignature[] = "MockSignature";
const char kMockKeyId[] = "MockKeyId";
const char kMockKey[] = "MockKey";
const char kMockPsshData[] = "MockPsshData";
// The lisence service may return an error indicating a transient error has
// just happened in the server, or other types of errors.
// WidevineEncryptorSource will perform a number of retries on transient errors;
// WidevineEncryptorSource does not know about other errors and retries are not
// performed.
const char kLicenseStatusTransientError[] = "INTERNAL_ERROR";
const char kLicenseStatusUnknownError[] = "UNKNOWN_ERROR";
const char kExpectedRequestMessageFormat[] =
"{\"content_id\":\"%s\",\"drm_types\":[\"WIDEVINE\"],\"policy\":\"\","
"\"tracks\":[{\"type\":\"SD\"},{\"type\":\"HD\"},{\"type\":\"AUDIO\"}]}";
const char kExpectedSignedMessageFormat[] =
"{\"request\":\"%s\",\"signature\":\"%s\",\"signer\":\"%s\"}";
const char kLicenseOkResponseFormat[] =
"{\"status\":\"OK\",\"tracks\":[{\"type\":\"%s\",\"key_id\":\"%s\",\"key\":"
"\"%s\",\"pssh\":[{\"drm_type\":\"WIDEVINE\",\"data\":\"%s\"}]}]}";
const char kLicenseErrorResponseFormat[] =
"{\"status\":\"%s\",\"drm\":[],\"tracks\":[]}";
const char kHttpResponseFormat[] = "{\"response\":\"%s\"}";
std::string Base64Encode(const std::string& input) {
std::string output;
base::Base64Encode(input, &output);
return output;
}
std::string ToString(const std::vector<uint8> v) {
return std::string(v.begin(), v.end());
}
} // namespace
using ::testing::_;
using ::testing::DoAll;
using ::testing::Return;
using ::testing::SetArgPointee;
namespace media {
class MockRequestSigner : public RequestSigner {
public:
explicit MockRequestSigner(const std::string& signer_name)
: RequestSigner(signer_name) {}
virtual ~MockRequestSigner() {}
MOCK_METHOD2(GenerateSignature,
bool(const std::string& message, std::string* signature));
private:
DISALLOW_COPY_AND_ASSIGN(MockRequestSigner);
};
class MockHttpFetcher : public HttpFetcher {
public:
MockHttpFetcher() : HttpFetcher() {}
virtual ~MockHttpFetcher() {}
MOCK_METHOD2(Get, Status(const std::string& url, std::string* response));
MOCK_METHOD3(Post,
Status(const std::string& url,
const std::string& data,
std::string* response));
private:
DISALLOW_COPY_AND_ASSIGN(MockHttpFetcher);
};
class WidevineEncryptorSourceTest : public ::testing::Test {
public:
WidevineEncryptorSourceTest()
: mock_request_signer_(new MockRequestSigner(kSignerName)),
mock_http_fetcher_(new MockHttpFetcher()) {}
protected:
void CreateWidevineEncryptorSource() {
widevine_encryptor_source_.reset(new WidevineEncryptorSource(
kServerUrl,
kContentId,
WidevineEncryptorSource::GetTrackTypeFromString(kTrackType),
mock_request_signer_.PassAs<RequestSigner>()));
widevine_encryptor_source_->set_http_fetcher(
mock_http_fetcher_.PassAs<HttpFetcher>());
}
scoped_ptr<MockRequestSigner> mock_request_signer_;
scoped_ptr<MockHttpFetcher> mock_http_fetcher_;
scoped_ptr<WidevineEncryptorSource> widevine_encryptor_source_;
private:
DISALLOW_COPY_AND_ASSIGN(WidevineEncryptorSourceTest);
};
TEST_F(WidevineEncryptorSourceTest, GetTrackTypeFromString) {
EXPECT_EQ(WidevineEncryptorSource::TRACK_TYPE_SD,
WidevineEncryptorSource::GetTrackTypeFromString("SD"));
EXPECT_EQ(WidevineEncryptorSource::TRACK_TYPE_HD,
WidevineEncryptorSource::GetTrackTypeFromString("HD"));
EXPECT_EQ(WidevineEncryptorSource::TRACK_TYPE_AUDIO,
WidevineEncryptorSource::GetTrackTypeFromString("AUDIO"));
EXPECT_EQ(WidevineEncryptorSource::TRACK_TYPE_UNKNOWN,
WidevineEncryptorSource::GetTrackTypeFromString("FOO"));
}
TEST_F(WidevineEncryptorSourceTest, GeneratureSignatureFailure) {
EXPECT_CALL(*mock_request_signer_, GenerateSignature(_, _))
.WillOnce(Return(false));
CreateWidevineEncryptorSource();
ASSERT_EQ(Status(error::INTERNAL_ERROR, "Signature generation failed."),
widevine_encryptor_source_->Initialize());
}
// Check whether expected request message and post data was generated and
// verify the correct behavior on http failure.
TEST_F(WidevineEncryptorSourceTest, HttpPostFailure) {
std::string expected_message = base::StringPrintf(
kExpectedRequestMessageFormat, Base64Encode(kContentId).c_str());
EXPECT_CALL(*mock_request_signer_, GenerateSignature(expected_message, _))
.WillOnce(DoAll(SetArgPointee<1>(kMockSignature), Return(true)));
std::string expected_post_data =
base::StringPrintf(kExpectedSignedMessageFormat,
Base64Encode(expected_message).c_str(),
Base64Encode(kMockSignature).c_str(),
kSignerName);
const Status kMockStatus = Status::UNKNOWN;
EXPECT_CALL(*mock_http_fetcher_, Post(kServerUrl, expected_post_data, _))
.WillOnce(Return(kMockStatus));
CreateWidevineEncryptorSource();
ASSERT_EQ(kMockStatus, widevine_encryptor_source_->Initialize());
}
TEST_F(WidevineEncryptorSourceTest, LicenseStatusOK) {
EXPECT_CALL(*mock_request_signer_, GenerateSignature(_, _))
.WillOnce(Return(true));
std::string mock_license_status =
base::StringPrintf(kLicenseOkResponseFormat,
kTrackType,
Base64Encode(kMockKeyId).c_str(),
Base64Encode(kMockKey).c_str(),
Base64Encode(kMockPsshData).c_str());
std::string expected_response = base::StringPrintf(
kHttpResponseFormat, Base64Encode(mock_license_status).c_str());
EXPECT_CALL(*mock_http_fetcher_, Post(_, _, _))
.WillOnce(DoAll(SetArgPointee<2>(expected_response), Return(Status::OK)));
CreateWidevineEncryptorSource();
ASSERT_OK(widevine_encryptor_source_->Initialize());
EXPECT_EQ(kMockKeyId, ToString(widevine_encryptor_source_->key_id()));
EXPECT_EQ(kMockKey, ToString(widevine_encryptor_source_->key()));
EXPECT_EQ(kMockPsshData, ToString(widevine_encryptor_source_->pssh()));
}
TEST_F(WidevineEncryptorSourceTest, RetryOnTransientError) {
EXPECT_CALL(*mock_request_signer_, GenerateSignature(_, _))
.WillOnce(Return(true));
std::string mock_license_status = base::StringPrintf(
kLicenseErrorResponseFormat, kLicenseStatusTransientError);
std::string expected_response = base::StringPrintf(
kHttpResponseFormat, Base64Encode(mock_license_status).c_str());
std::string mock_retried_license_status =
base::StringPrintf(kLicenseOkResponseFormat,
kTrackType,
Base64Encode(kMockKeyId).c_str(),
Base64Encode(kMockKey).c_str(),
Base64Encode(kMockPsshData).c_str());
std::string expected_retried_response = base::StringPrintf(
kHttpResponseFormat, Base64Encode(mock_retried_license_status).c_str());
// Retry is expected on transient error.
EXPECT_CALL(*mock_http_fetcher_, Post(_, _, _))
.WillOnce(DoAll(SetArgPointee<2>(expected_response), Return(Status::OK)))
.WillOnce(DoAll(SetArgPointee<2>(expected_retried_response),
Return(Status::OK)));
CreateWidevineEncryptorSource();
ASSERT_OK(widevine_encryptor_source_->Initialize());
EXPECT_EQ(kMockKeyId, ToString(widevine_encryptor_source_->key_id()));
EXPECT_EQ(kMockKey, ToString(widevine_encryptor_source_->key()));
EXPECT_EQ(kMockPsshData, ToString(widevine_encryptor_source_->pssh()));
}
TEST_F(WidevineEncryptorSourceTest, NoRetryOnUnknownError) {
EXPECT_CALL(*mock_request_signer_, GenerateSignature(_, _))
.WillOnce(Return(true));
std::string mock_license_status = base::StringPrintf(
kLicenseErrorResponseFormat, kLicenseStatusUnknownError);
std::string mock_response = base::StringPrintf(
kHttpResponseFormat, Base64Encode(mock_license_status).c_str());
EXPECT_CALL(*mock_http_fetcher_, Post(_, _, _))
.WillOnce(DoAll(SetArgPointee<2>(mock_response), Return(Status::OK)));
CreateWidevineEncryptorSource();
ASSERT_EQ(error::SERVER_ERROR,
widevine_encryptor_source_->Initialize().error_code());
}
} // namespace media