Widevine encryptor source unittest
Change-Id: I1b56d0a019480472eb462d8a39f81c4eb3c81a0e
This commit is contained in:
parent
f73f4bb773
commit
d90ca489f9
|
@ -4,7 +4,7 @@
|
||||||
// license that can be found in the LICENSE file or at
|
// license that can be found in the LICENSE file or at
|
||||||
// https://developers.google.com/open-source/licenses/bsd
|
// https://developers.google.com/open-source/licenses/bsd
|
||||||
|
|
||||||
#include "media/base/httpfetcher.h"
|
#include "media/base/http_fetcher.h"
|
||||||
|
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
#include <winsock2.h>
|
#include <winsock2.h>
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
struct HTTPResult {
|
struct HttpResult {
|
||||||
int status_code;
|
int status_code;
|
||||||
std::string status_message;
|
std::string status_message;
|
||||||
std::string response;
|
std::string response;
|
||||||
|
@ -74,7 +74,7 @@ void OnBegin(const happyhttp::Response* response, void* userdata) {
|
||||||
DLOG(INFO) << "BEGIN (" << response->getstatus() << ", "
|
DLOG(INFO) << "BEGIN (" << response->getstatus() << ", "
|
||||||
<< response->getreason() << ").";
|
<< response->getreason() << ").";
|
||||||
|
|
||||||
HTTPResult* result = static_cast<HTTPResult*>(userdata);
|
HttpResult* result = static_cast<HttpResult*>(userdata);
|
||||||
result->status_code = response->getstatus();
|
result->status_code = response->getstatus();
|
||||||
result->status_message = response->getreason();
|
result->status_message = response->getreason();
|
||||||
result->response.clear();
|
result->response.clear();
|
||||||
|
@ -85,13 +85,13 @@ void OnData(const happyhttp::Response* response,
|
||||||
const unsigned char* data,
|
const unsigned char* data,
|
||||||
int num_bytes) {
|
int num_bytes) {
|
||||||
DCHECK(response && userdata && data);
|
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);
|
result->response.append(reinterpret_cast<const char*>(data), num_bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnComplete(const happyhttp::Response* response, void* userdata) {
|
void OnComplete(const happyhttp::Response* response, void* userdata) {
|
||||||
DCHECK(response && userdata);
|
DCHECK(response && userdata);
|
||||||
HTTPResult* result = static_cast<HTTPResult*>(userdata);
|
HttpResult* result = static_cast<HttpResult*>(userdata);
|
||||||
DLOG(INFO) << "COMPLETE (" << result->response.size() << " bytes).";
|
DLOG(INFO) << "COMPLETE (" << result->response.size() << " bytes).";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +101,10 @@ const int kHttpOK = 200;
|
||||||
|
|
||||||
namespace media {
|
namespace media {
|
||||||
|
|
||||||
HTTPFetcher::HTTPFetcher() {
|
HttpFetcher::HttpFetcher() {}
|
||||||
|
HttpFetcher::~HttpFetcher() {}
|
||||||
|
|
||||||
|
SimpleHttpFetcher::SimpleHttpFetcher() {
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
WSAData wsa_data;
|
WSAData wsa_data;
|
||||||
int code = WSAStartup(MAKEWORD(1, 1), &wsa_data);
|
int code = WSAStartup(MAKEWORD(1, 1), &wsa_data);
|
||||||
|
@ -111,23 +114,24 @@ HTTPFetcher::HTTPFetcher() {
|
||||||
#endif // WIN32
|
#endif // WIN32
|
||||||
}
|
}
|
||||||
|
|
||||||
HTTPFetcher::~HTTPFetcher() {
|
SimpleHttpFetcher::~SimpleHttpFetcher() {
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
if (wsa_startup_succeeded_)
|
if (wsa_startup_succeeded_)
|
||||||
WSACleanup();
|
WSACleanup();
|
||||||
#endif // WIN32
|
#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);
|
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) {
|
std::string* response) {
|
||||||
return FetchInternal("POST", path, data, 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& url,
|
||||||
const std::string& data,
|
const std::string& data,
|
||||||
std::string* response) {
|
std::string* response) {
|
||||||
|
@ -145,7 +149,7 @@ Status HTTPFetcher::FetchInternal(const std::string& method,
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
HTTPResult result;
|
HttpResult result;
|
||||||
happyhttp::Connection connection(host.data(), port);
|
happyhttp::Connection connection(host.data(), port);
|
||||||
connection.setcallbacks(OnBegin, OnData, OnComplete, &result);
|
connection.setcallbacks(OnBegin, OnData, OnComplete, &result);
|
||||||
|
|
|
@ -4,32 +4,49 @@
|
||||||
// license that can be found in the LICENSE file or at
|
// license that can be found in the LICENSE file or at
|
||||||
// https://developers.google.com/open-source/licenses/bsd
|
// https://developers.google.com/open-source/licenses/bsd
|
||||||
|
|
||||||
#ifndef MEDIA_BASE_HTTPFETCHER_H_
|
#ifndef MEDIA_BASE_HTTP_FETCHER_H_
|
||||||
#define MEDIA_BASE_HTTPFETCHER_H_
|
#define MEDIA_BASE_HTTP_FETCHER_H_
|
||||||
|
|
||||||
|
#include "base/compiler_specific.h"
|
||||||
#include "media/base/status.h"
|
#include "media/base/status.h"
|
||||||
|
|
||||||
namespace media {
|
namespace media {
|
||||||
|
|
||||||
// A simple HTTP fetcher implementation using happyhttp.
|
class HttpFetcher {
|
||||||
class HTTPFetcher {
|
|
||||||
public:
|
public:
|
||||||
// TODO(kqyang): Add timeout support.
|
HttpFetcher();
|
||||||
HTTPFetcher();
|
virtual ~HttpFetcher();
|
||||||
~HTTPFetcher();
|
|
||||||
|
|
||||||
// Fetch |response| from |url| using HTTP GET.
|
// Fetch |response| from |url| using HTTP GET.
|
||||||
// |response| should not be NULL, will contain the body of the http response
|
// |response| should not be NULL, will contain the body of the http response
|
||||||
// on success.
|
// on success.
|
||||||
// Return OK 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.
|
// Fetch |response| from |url| using HTTP POST.
|
||||||
// |response| should not be NULL, will contain the body of the http response
|
// |response| should not be NULL, will contain the body of the http response
|
||||||
// on success.
|
// on success.
|
||||||
// Return OK on success.
|
// Return OK on success.
|
||||||
Status Post(const std::string& url, const std::string& data,
|
virtual Status Post(const std::string& url,
|
||||||
std::string* response);
|
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:
|
private:
|
||||||
// Internal implementation of HTTP functions, e.g. Get and Post.
|
// Internal implementation of HTTP functions, e.g. Get and Post.
|
||||||
|
@ -41,10 +58,10 @@ class HTTPFetcher {
|
||||||
bool wsa_startup_succeeded_;
|
bool wsa_startup_succeeded_;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(HTTPFetcher);
|
DISALLOW_COPY_AND_ASSIGN(SimpleHttpFetcher);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace media
|
} // namespace media
|
||||||
|
|
||||||
#endif // MEDIA_BASE_HTTPFETCHER_H_
|
#endif // MEDIA_BASE_HTTP_FETCHER_H_
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
// license that can be found in the LICENSE file or at
|
// license that can be found in the LICENSE file or at
|
||||||
// https://developers.google.com/open-source/licenses/bsd
|
// 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/logging.h"
|
||||||
#include "base/strings/string_number_conversions.h"
|
#include "base/strings/string_number_conversions.h"
|
||||||
|
@ -30,7 +30,7 @@ namespace media {
|
||||||
|
|
||||||
static void CheckHttpGet(const std::string& url,
|
static void CheckHttpGet(const std::string& url,
|
||||||
const std::string& expected_response) {
|
const std::string& expected_response) {
|
||||||
HTTPFetcher fetcher;
|
SimpleHttpFetcher fetcher;
|
||||||
std::string response;
|
std::string response;
|
||||||
ASSERT_OK(fetcher.Get(url, &response));
|
ASSERT_OK(fetcher.Get(url, &response));
|
||||||
base::RemoveChars(response, "\r\n\t ", &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,
|
static void CheckHttpPost(const std::string& url, const std::string& data,
|
||||||
const std::string& expected_response) {
|
const std::string& expected_response) {
|
||||||
HTTPFetcher fetcher;
|
SimpleHttpFetcher fetcher;
|
||||||
std::string response;
|
std::string response;
|
||||||
ASSERT_OK(fetcher.Post(url, data, &response));
|
ASSERT_OK(fetcher.Post(url, data, &response));
|
||||||
base::RemoveChars(response, "\r\n\t ", &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);
|
CheckHttpGet(kTestUrl, kExpectedGetResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(HTTPFetcherTest, HttpPost) {
|
TEST(HttpFetcherTest, HttpPost) {
|
||||||
CheckHttpPost(kTestUrl, kPostData, kExpectedPostResponse);
|
CheckHttpPost(kTestUrl, kPostData, kExpectedPostResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(HTTPFetcherTest, InvalidUrl) {
|
TEST(HttpFetcherTest, InvalidUrl) {
|
||||||
HTTPFetcher fetcher;
|
SimpleHttpFetcher fetcher;
|
||||||
std::string response;
|
std::string response;
|
||||||
const std::string invalid_url(kTestUrl, sizeof(kTestUrl) - 2);
|
const std::string invalid_url(kTestUrl, sizeof(kTestUrl) - 2);
|
||||||
Status status = fetcher.Get(invalid_url, &response);
|
Status status = fetcher.Get(invalid_url, &response);
|
||||||
|
@ -65,7 +65,7 @@ TEST(HTTPFetcherTest, InvalidUrl) {
|
||||||
EndsWith(status.error_message(), base::IntToString(kHttpNotFound), true));
|
EndsWith(status.error_message(), base::IntToString(kHttpNotFound), true));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(HTTPFetcherTest, UrlWithPort) {
|
TEST(HttpFetcherTest, UrlWithPort) {
|
||||||
CheckHttpGet(kTestUrlWithPort, kExpectedGetResponse);
|
CheckHttpGet(kTestUrlWithPort, kExpectedGetResponse);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,11 +23,11 @@
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'target_name': 'httpfetcher',
|
'target_name': 'http_fetcher',
|
||||||
'type': '<(component)',
|
'type': '<(component)',
|
||||||
'sources': [
|
'sources': [
|
||||||
'httpfetcher.cc',
|
'http_fetcher.cc',
|
||||||
'httpfetcher.h',
|
'http_fetcher.h',
|
||||||
],
|
],
|
||||||
'cflags!': [ '-fno-exceptions' ],
|
'cflags!': [ '-fno-exceptions' ],
|
||||||
'cflags_cc!': [ '-fno-exceptions' ],
|
'cflags_cc!': [ '-fno-exceptions' ],
|
||||||
|
@ -45,16 +45,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
# Note that this test performs real http requests to a http server.
|
# 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)',
|
'type': '<(gtest_target_type)',
|
||||||
'sources': [
|
'sources': [
|
||||||
'httpfetcher_unittest.cc',
|
'http_fetcher_unittest.cc',
|
||||||
],
|
],
|
||||||
'dependencies': [
|
'dependencies': [
|
||||||
'../../base/base.gyp:base',
|
'../../base/base.gyp:base',
|
||||||
'../../testing/gtest.gyp:gtest',
|
'../../testing/gtest.gyp:gtest',
|
||||||
'../../testing/gtest.gyp:gtest_main',
|
'../../testing/gtest.gyp:gtest_main',
|
||||||
'httpfetcher',
|
'http_fetcher',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -110,7 +110,7 @@
|
||||||
'dependencies': [
|
'dependencies': [
|
||||||
'../../base/base.gyp:base',
|
'../../base/base.gyp:base',
|
||||||
'../../third_party/openssl/openssl.gyp:openssl',
|
'../../third_party/openssl/openssl.gyp:openssl',
|
||||||
'httpfetcher',
|
'http_fetcher',
|
||||||
'status',
|
'status',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -130,6 +130,7 @@
|
||||||
'status_test_util.h',
|
'status_test_util.h',
|
||||||
'status_test_util_unittest.cc',
|
'status_test_util_unittest.cc',
|
||||||
'status_unittest.cc',
|
'status_unittest.cc',
|
||||||
|
'widevine_encryptor_source_unittest.cc',
|
||||||
],
|
],
|
||||||
'dependencies': [
|
'dependencies': [
|
||||||
'../../testing/gtest.gyp:gtest',
|
'../../testing/gtest.gyp:gtest',
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
#include "base/time/time.h"
|
#include "base/time/time.h"
|
||||||
#include "base/threading/platform_thread.h"
|
#include "base/threading/platform_thread.h"
|
||||||
#include "base/values.h"
|
#include "base/values.h"
|
||||||
#include "media/base/httpfetcher.h"
|
#include "media/base/http_fetcher.h"
|
||||||
#include "media/base/request_signer.h"
|
#include "media/base/request_signer.h"
|
||||||
|
|
||||||
// TODO(kqyang): Move media/mp4/rcheck.h to media/base/.
|
// TODO(kqyang): Move media/mp4/rcheck.h to media/base/.
|
||||||
|
@ -102,7 +102,8 @@ WidevineEncryptorSource::WidevineEncryptorSource(
|
||||||
const std::string& content_id,
|
const std::string& content_id,
|
||||||
TrackType track_type,
|
TrackType track_type,
|
||||||
scoped_ptr<RequestSigner> signer)
|
scoped_ptr<RequestSigner> signer)
|
||||||
: server_url_(server_url),
|
: http_fetcher_(new SimpleHttpFetcher()),
|
||||||
|
server_url_(server_url),
|
||||||
content_id_(content_id),
|
content_id_(content_id),
|
||||||
track_type_(track_type),
|
track_type_(track_type),
|
||||||
signer_(signer.Pass()) {
|
signer_(signer.Pass()) {
|
||||||
|
@ -120,14 +121,13 @@ Status WidevineEncryptorSource::Initialize() {
|
||||||
return status;
|
return status;
|
||||||
VLOG(1) << "Message: " << message;
|
VLOG(1) << "Message: " << message;
|
||||||
|
|
||||||
HTTPFetcher fetcher;
|
|
||||||
std::string raw_response;
|
std::string raw_response;
|
||||||
int64 sleep_duration = kFirstRetryDelayMilliseconds;
|
int64 sleep_duration = kFirstRetryDelayMilliseconds;
|
||||||
|
|
||||||
// Perform client side retries if seeing server transient error to workaround
|
// Perform client side retries if seeing server transient error to workaround
|
||||||
// server limitation.
|
// server limitation.
|
||||||
for (int i = 0; i < kNumTransientErrorRetries; ++i) {
|
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())
|
if (!status.ok())
|
||||||
return status;
|
return status;
|
||||||
VLOG(1) << "Retry [" << i << "] Response:" << raw_response;
|
VLOG(1) << "Retry [" << i << "] Response:" << raw_response;
|
||||||
|
@ -172,6 +172,11 @@ WidevineEncryptorSource::GetTrackTypeFromString(
|
||||||
return TRACK_TYPE_UNKNOWN;
|
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,
|
void WidevineEncryptorSource::FillRequest(const std::string& content_id,
|
||||||
std::string* request) {
|
std::string* request) {
|
||||||
DCHECK(request);
|
DCHECK(request);
|
||||||
|
@ -256,8 +261,7 @@ bool WidevineEncryptorSource::IsExpectedTrackType(
|
||||||
return track_type_ == GetTrackTypeFromString(track_type_string);
|
return track_type_ == GetTrackTypeFromString(track_type_string);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WidevineEncryptorSource::ExtractEncryptionKey(
|
bool WidevineEncryptorSource::ExtractEncryptionKey(const std::string& response,
|
||||||
const std::string& response,
|
|
||||||
bool* transient_error) {
|
bool* transient_error) {
|
||||||
DCHECK(transient_error);
|
DCHECK(transient_error);
|
||||||
*transient_error = false;
|
*transient_error = false;
|
||||||
|
|
|
@ -8,10 +8,12 @@
|
||||||
#define MEDIA_BASE_WIDEVINE_ENCRYPTOR_SOURCE_H_
|
#define MEDIA_BASE_WIDEVINE_ENCRYPTOR_SOURCE_H_
|
||||||
|
|
||||||
#include "base/basictypes.h"
|
#include "base/basictypes.h"
|
||||||
|
#include "base/memory/scoped_ptr.h"
|
||||||
#include "media/base/encryptor_source.h"
|
#include "media/base/encryptor_source.h"
|
||||||
|
|
||||||
namespace media {
|
namespace media {
|
||||||
|
|
||||||
|
class HttpFetcher;
|
||||||
class RequestSigner;
|
class RequestSigner;
|
||||||
|
|
||||||
// Defines an encryptor source which talks to Widevine encryption server.
|
// Defines an encryptor source which talks to Widevine encryption server.
|
||||||
|
@ -37,6 +39,8 @@ class WidevineEncryptorSource : public EncryptorSource {
|
||||||
static WidevineEncryptorSource::TrackType GetTrackTypeFromString(
|
static WidevineEncryptorSource::TrackType GetTrackTypeFromString(
|
||||||
const std::string& track_type_string);
|
const std::string& track_type_string);
|
||||||
|
|
||||||
|
void set_http_fetcher(scoped_ptr<HttpFetcher> http_fetcher);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Fill |request| with necessary fields for Widevine encryption request.
|
// Fill |request| with necessary fields for Widevine encryption request.
|
||||||
// |request| should not be NULL.
|
// |request| should not be NULL.
|
||||||
|
@ -55,6 +59,10 @@ class WidevineEncryptorSource : public EncryptorSource {
|
||||||
bool ExtractEncryptionKey(const std::string& response,
|
bool ExtractEncryptionKey(const std::string& response,
|
||||||
bool* transient_error);
|
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 server_url_;
|
||||||
std::string content_id_;
|
std::string content_id_;
|
||||||
TrackType track_type_;
|
TrackType track_type_;
|
||||||
|
|
|
@ -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
|
Loading…
Reference in New Issue