Initial import of Widevine Common Encryption DRM engine
Builds libwvmdrmengine.so, which is loaded by the new MediaDrm APIs to support playback of Widevine/CENC protected content. Change-Id: I6f57dd37083dfd96c402cb9dd137c7d74edc8f1c
This commit is contained in:
245
libwvdrmengine/cdm/core/test/cdm_engine_test.cpp
Normal file
245
libwvdrmengine/cdm/core/test/cdm_engine_test.cpp
Normal file
@@ -0,0 +1,245 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include <errno.h>
|
||||
#include <getopt.h>
|
||||
|
||||
#include "cdm_engine.h"
|
||||
#include "config_test_env.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "license_request.h"
|
||||
#include "log.h"
|
||||
#include "string_conversions.h"
|
||||
#include "url_request.h"
|
||||
|
||||
namespace {
|
||||
// Default license server, can be configured using --server command line option
|
||||
// Default key id (pssh), can be configured using --keyid command line option
|
||||
std::string g_client_auth;
|
||||
wvcdm::KeyId g_key_id;
|
||||
wvcdm::CdmKeySystem g_key_system;
|
||||
std::string g_license_server;
|
||||
std::string g_port;
|
||||
wvcdm::KeyId g_wrong_key_id;
|
||||
int g_use_full_path = 0; // cannot use boolean in getopt_long
|
||||
} // namespace
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class WvCdmEngineTest : public testing::Test {
|
||||
public:
|
||||
WvCdmEngineTest() {}
|
||||
~WvCdmEngineTest() {}
|
||||
|
||||
protected:
|
||||
void GenerateKeyRequest(const std::string& key_system,
|
||||
const std::string& init_data) {
|
||||
wvcdm::CdmNameValueMap app_parameters;
|
||||
EXPECT_EQ(cdm_engine_.GenerateKeyRequest(session_id_,
|
||||
true, // is_key_system_present
|
||||
key_system,
|
||||
init_data,
|
||||
kLicenseTypeStreaming,
|
||||
app_parameters,
|
||||
&key_msg_), wvcdm::KEY_MESSAGE);
|
||||
}
|
||||
|
||||
void GenerateRenewalRequest(const std::string& key_system,
|
||||
const std::string& init_data) {
|
||||
EXPECT_EQ(cdm_engine_.GenerateRenewalRequest(session_id_,
|
||||
true, // is_key_system_init_data_present,
|
||||
key_system,
|
||||
init_data,
|
||||
&key_msg_),
|
||||
wvcdm::KEY_MESSAGE);
|
||||
}
|
||||
|
||||
// posts a request and extracts the drm message from the response
|
||||
std::string GetKeyRequestResponse(const std::string& server_url,
|
||||
const std::string& client_auth,
|
||||
int expected_response) {
|
||||
UrlRequest url_request(server_url + client_auth, g_port);
|
||||
|
||||
if (!url_request.is_connected()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
url_request.PostRequest(key_msg_);
|
||||
std::string response;
|
||||
int resp_bytes = url_request.GetResponse(response);
|
||||
LOGD("response:\r\n%s", response.c_str());
|
||||
LOGD("end %d bytes response dump", resp_bytes);
|
||||
|
||||
// Youtube server returns 400 for invalid message while play server returns
|
||||
// 500, so just test inequity here for invalid message
|
||||
int status_code = url_request.GetStatusCode(response);
|
||||
if (expected_response == 200) {
|
||||
EXPECT_EQ(200, status_code);
|
||||
} else {
|
||||
EXPECT_NE(200, status_code);
|
||||
}
|
||||
|
||||
std::string drm_msg;
|
||||
if (200 == status_code) {
|
||||
LicenseRequest lic_request;
|
||||
lic_request.GetDrmMessage(response, drm_msg);
|
||||
LOGV("drm msg: %u bytes\r\n%s", drm_msg.size(),
|
||||
HexEncode(reinterpret_cast<const uint8_t*>(drm_msg.data()),
|
||||
drm_msg.size()).c_str());
|
||||
}
|
||||
return drm_msg;
|
||||
}
|
||||
|
||||
void VerifyKeyRequestResponse(const std::string& server_url,
|
||||
const std::string& client_auth,
|
||||
std::string& init_data,
|
||||
bool is_renewal) {
|
||||
std::string resp = GetKeyRequestResponse(server_url,
|
||||
client_auth,
|
||||
200);
|
||||
if (is_renewal) {
|
||||
EXPECT_EQ(cdm_engine_.RenewKey(session_id_,
|
||||
true, // is_key_system_init_data_present
|
||||
g_key_system,
|
||||
init_data,
|
||||
resp), wvcdm::KEY_ADDED);
|
||||
}
|
||||
else {
|
||||
EXPECT_EQ(cdm_engine_.AddKey(session_id_,
|
||||
true, // is_key_system_init_data_present
|
||||
g_key_system,
|
||||
init_data,
|
||||
resp), wvcdm::KEY_ADDED);
|
||||
}
|
||||
}
|
||||
|
||||
wvcdm::CdmEngine cdm_engine_;
|
||||
std::string key_msg_;
|
||||
std::string session_id_;
|
||||
};
|
||||
|
||||
TEST_F(WvCdmEngineTest, BaseMessageTest) {
|
||||
cdm_engine_.OpenSession(g_key_system, &session_id_);
|
||||
GenerateKeyRequest(g_key_system, g_key_id);
|
||||
GetKeyRequestResponse(g_license_server, g_client_auth, 200);
|
||||
cdm_engine_.CloseSession(session_id_);
|
||||
}
|
||||
|
||||
TEST_F(WvCdmEngineTest, WrongMessageTest) {
|
||||
cdm_engine_.OpenSession(g_key_system, &session_id_);
|
||||
|
||||
std::string wrong_message = wvcdm::a2bs_hex(g_wrong_key_id);
|
||||
GenerateKeyRequest(g_key_system, wrong_message);
|
||||
GetKeyRequestResponse(g_license_server, g_client_auth, 500);
|
||||
cdm_engine_.CloseSession(session_id_);
|
||||
}
|
||||
|
||||
TEST_F(WvCdmEngineTest, NormalDecryption) {
|
||||
cdm_engine_.OpenSession(g_key_system, &session_id_);
|
||||
GenerateKeyRequest(g_key_system, g_key_id);
|
||||
VerifyKeyRequestResponse(g_license_server, g_client_auth, g_key_id, false);
|
||||
cdm_engine_.CloseSession(session_id_);
|
||||
}
|
||||
|
||||
TEST_F(WvCdmEngineTest, LicenseRenewal) {
|
||||
cdm_engine_.OpenSession(g_key_system, &session_id_);
|
||||
GenerateKeyRequest(g_key_system, g_key_id);
|
||||
VerifyKeyRequestResponse(g_license_server, g_client_auth, g_key_id, false);
|
||||
|
||||
GenerateRenewalRequest(g_key_system, g_key_id);
|
||||
VerifyKeyRequestResponse(g_license_server, g_client_auth, g_key_id, true);
|
||||
cdm_engine_.CloseSession(session_id_);
|
||||
}
|
||||
} // namespace wvcdm
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
|
||||
wvcdm::ConfigTestEnv config;
|
||||
g_client_auth.assign(config.client_auth());
|
||||
g_key_system.assign(config.key_system());
|
||||
g_wrong_key_id.assign(config.wrong_key_id());
|
||||
|
||||
// The following variables are configurable through command line options.
|
||||
g_license_server.assign(config.license_server());
|
||||
g_key_id.assign(config.key_id());
|
||||
g_port.assign(config.port());
|
||||
std::string license_server(g_license_server);
|
||||
|
||||
int show_usage = 0;
|
||||
static const struct option long_options[] = {
|
||||
{ "use_full_path", no_argument, &g_use_full_path, 0 },
|
||||
{ "keyid", required_argument, NULL, 'k' },
|
||||
{ "port", required_argument, NULL, 'p' },
|
||||
{ "server", required_argument, NULL, 's' },
|
||||
{ NULL, 0, NULL, '\0' }
|
||||
};
|
||||
|
||||
int option_index = 0;
|
||||
int opt = 0;
|
||||
while ((opt = getopt_long(argc, argv, "k:p:s:u", long_options, &option_index)) != -1) {
|
||||
switch (opt) {
|
||||
case 'k': {
|
||||
g_key_id.clear();
|
||||
g_key_id.assign(optarg);
|
||||
break;
|
||||
}
|
||||
case 'p': {
|
||||
g_port.clear();
|
||||
g_port.assign(optarg);
|
||||
break;
|
||||
}
|
||||
case 's': {
|
||||
g_license_server.clear();
|
||||
g_license_server.assign(optarg);
|
||||
break;
|
||||
}
|
||||
case 'u': {
|
||||
g_use_full_path = 1;
|
||||
break;
|
||||
}
|
||||
case '?': {
|
||||
show_usage = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (show_usage) {
|
||||
std::cout << std::endl;
|
||||
std::cout << "usage: " << argv[0] << " [options]" << std::endl << std::endl;
|
||||
std::cout << " enclose multiple arguments in '' when using adb shell" << std::endl;
|
||||
std::cout << " e.g. adb shell '" << argv[0] << " --server=\"url\"'" << std::endl << std::endl;
|
||||
|
||||
std::cout << std::setw(30) << std::left << " --port=<connection port>";
|
||||
std::cout << "specifies the port number, in decimal format" << std::endl;
|
||||
std::cout << std::setw(30) << std::left << " ";
|
||||
std::cout << "default: " << g_port << std::endl;
|
||||
|
||||
std::cout << std::setw(30) << std::left << " --server=<server_url>";
|
||||
std::cout << "configure the license server url, please include http[s] in the url" << std::endl;
|
||||
std::cout << std::setw(30) << std::left << " ";
|
||||
std::cout << "default: " << license_server << std::endl;
|
||||
|
||||
std::cout << std::setw(30) << std::left << " --keyid=<key_id>";
|
||||
std::cout << "configure the key id or pssh, in hex format" << std::endl;
|
||||
std::cout << std::setw(30) << std::left << " default keyid:";
|
||||
std::cout << g_key_id << std::endl;
|
||||
|
||||
std::cout << std::setw(30) << std::left << " --use_full_path";
|
||||
std::cout << "specify server url is not a proxy server" << std::endl;
|
||||
std::cout << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
std::cout << "Server: " << g_license_server << std::endl;
|
||||
std::cout << "Port: " << g_port << std::endl;
|
||||
std::cout << "KeyID: " << g_key_id << std::endl << std::endl;
|
||||
|
||||
g_key_id = wvcdm::a2bs_hex(g_key_id);
|
||||
config.set_license_server(g_license_server);
|
||||
config.set_port(g_port);
|
||||
config.set_key_id(g_key_id);
|
||||
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
77
libwvdrmengine/cdm/core/test/config_test_env.cpp
Normal file
77
libwvdrmengine/cdm/core/test/config_test_env.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "config_test_env.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// choice of YT, GP, GP_Huahui, SDK_Hali
|
||||
#define USE_SERVER_YT 1
|
||||
#define USE_SERVER_GP 2
|
||||
#define USE_SERVER_SDK_Hali 3
|
||||
|
||||
// select which server to use for testing
|
||||
#define USE_SERVER USE_SERVER_GP
|
||||
|
||||
#if (USE_SERVER == USE_SERVER_SDK_Hali)
|
||||
|
||||
static const std::string kLicenseServer =
|
||||
"http://hamid.kir.corp.google.com:8888/drm";
|
||||
static const std::string kClientAuth = "";
|
||||
static const std::string kKeyId =
|
||||
"000000347073736800000000" // blob size and pssh
|
||||
"EDEF8BA979D64ACEA3C827DCD51D21ED00000014" // Widevine system id
|
||||
"0801121030313233343536373839616263646566"; // key - for gHali
|
||||
|
||||
#elif (USE_SERVER == USE_SERVER_YT)
|
||||
|
||||
static const std::string kLicenseServer =
|
||||
"https://www.youtube.com/api/drm/widevine?video_id=03681262dc412c06&source=YOUTUBE";
|
||||
static const std::string kClientAuth = "";
|
||||
static const std::string kKeyId =
|
||||
"000000347073736800000000" // blob size and pssh
|
||||
"EDEF8BA979D64ACEA3C827DCD51D21ED00000014" // Widevine system id
|
||||
"0801121093789920E8D6520098577DF8F2DD5546"; // pssh data
|
||||
|
||||
#elif (USE_SERVER == USE_SERVER_GP)
|
||||
|
||||
static const std::string kLicenseServer =
|
||||
"https://jmt17.google.com/video-dev/license/GetCencLicense";
|
||||
|
||||
// NOTE: Append a userdata attribute to place a unique marker that the
|
||||
// server team can use to track down specific requests during debugging
|
||||
// e.g., "<existing-client-auth-string>&userdata=<your-ldap>.<your-tag>"
|
||||
// "<existing-client-auth-string>&userdata=jbmr2.dev"
|
||||
static const std::string kClientAuth =
|
||||
"?source=YOUTUBE&video_id=EGHC6OHNbOo&oauth=ya.gtsqawidevine";
|
||||
|
||||
static const std::string kKeyId =
|
||||
"000000347073736800000000" // blob size and pssh
|
||||
"edef8ba979d64acea3c827dcd51d21ed00000014" // Widevine system id
|
||||
"08011210e02562e04cd55351b14b3d748d36ed8e"; // pssh data
|
||||
|
||||
#else
|
||||
#error "Must define USE_SERVER"
|
||||
#endif
|
||||
|
||||
//static const char kWidevineKeySystem[] = "com.widevine.alpha";
|
||||
|
||||
// An invalid key id, expected to fail
|
||||
static const std::string kWrongKeyId =
|
||||
"000000347073736800000000" // blob size and pssh
|
||||
"EDEF8BA979D64ACEA3C827DCD51D21ED00000014" // Widevine system id
|
||||
"0901121094889920E8D6520098577DF8F2DD5546"; // pssh data
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
ConfigTestEnv::ConfigTestEnv()
|
||||
: client_auth_(kClientAuth),
|
||||
key_id_(kKeyId),
|
||||
key_system_("com.widevine.alpha"),
|
||||
license_server_(kLicenseServer),
|
||||
port_("80"),
|
||||
wrong_key_id_(kWrongKeyId) {
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
46
libwvdrmengine/cdm/core/test/config_test_env.h
Normal file
46
libwvdrmengine/cdm/core/test/config_test_env.h
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef CDM_TEST_CONFIG_TEST_ENV_H_
|
||||
#define CDM_TEST_CONFIG_TEST_ENV_H_
|
||||
|
||||
#include <string>
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// Configures default test environment.
|
||||
class ConfigTestEnv {
|
||||
public:
|
||||
ConfigTestEnv();
|
||||
~ConfigTestEnv() {};
|
||||
|
||||
const std::string& client_auth() const { return client_auth_; }
|
||||
const KeyId& key_id() const { return key_id_; }
|
||||
const CdmKeySystem& key_system() const { return key_system_; }
|
||||
const std::string& license_server() const { return license_server_; }
|
||||
const std::string& port() const { return port_; }
|
||||
const KeyId& wrong_key_id() const { return wrong_key_id_; }
|
||||
|
||||
void set_key_id(KeyId& key_id) { key_id_.assign(key_id); }
|
||||
void set_key_system(CdmKeySystem& key_system) {
|
||||
key_system_.assign(key_system);
|
||||
}
|
||||
void set_license_server(std::string& license_server) {
|
||||
license_server_.assign(license_server);
|
||||
}
|
||||
void set_port(std::string& port) { port_.assign(port); }
|
||||
|
||||
private:
|
||||
std::string client_auth_;
|
||||
KeyId key_id_;
|
||||
CdmKeySystem key_system_;
|
||||
std::string license_server_;
|
||||
std::string port_;
|
||||
KeyId wrong_key_id_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(ConfigTestEnv);
|
||||
};
|
||||
|
||||
}; // namespace wvcdm
|
||||
|
||||
#endif // CDM_TEST_CONFIG_TEST_ENV_H_
|
||||
192
libwvdrmengine/cdm/core/test/http_socket.cpp
Normal file
192
libwvdrmengine/cdm/core/test/http_socket.cpp
Normal file
@@ -0,0 +1,192 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "http_socket.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <netdb.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include "log.h"
|
||||
namespace wvcdm {
|
||||
|
||||
HttpSocket::HttpSocket() : socket_fd_(-1), timeout_enabled_(false) {}
|
||||
|
||||
HttpSocket::~HttpSocket()
|
||||
{
|
||||
CloseSocket();
|
||||
}
|
||||
|
||||
void HttpSocket::CloseSocket()
|
||||
{
|
||||
if (socket_fd_ != -1) {
|
||||
close(socket_fd_);
|
||||
socket_fd_ = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Extracts the domain name and resource path from the input url parameter.
|
||||
// The results are put in domain_name and resource_path respectively.
|
||||
// The format of the url can begin with <protocol/scheme>:://domain server/...
|
||||
// or dowmain server/resource_path
|
||||
void HttpSocket::GetDomainNameAndPathFromUrl(const std::string& url,
|
||||
std::string& domain_name,
|
||||
std::string& resource_path) {
|
||||
domain_name.clear();
|
||||
resource_path.clear();
|
||||
|
||||
size_t start = url.find("//");
|
||||
size_t end = url.npos;
|
||||
if (start != url.npos) {
|
||||
end = url.find("/", start + 2);
|
||||
if (end != url.npos) {
|
||||
domain_name.assign(url, start + 2, end - start - 2);
|
||||
resource_path.assign(url, end + 1, url.npos);
|
||||
} else {
|
||||
domain_name.assign(url, start + 2, url.npos);
|
||||
}
|
||||
} else {
|
||||
// no scheme/protocol in url
|
||||
end = url.find("/");
|
||||
if (end != url.npos) {
|
||||
domain_name.assign(url, 0, end);
|
||||
resource_path.assign(url, end + 1, url.npos);
|
||||
} else {
|
||||
domain_name.assign(url);
|
||||
}
|
||||
}
|
||||
// strips port number if present, e.g. https://www.domain.com:8888/...
|
||||
end = domain_name.find(":");
|
||||
if (end != domain_name.npos) {
|
||||
domain_name.erase(end);
|
||||
}
|
||||
}
|
||||
|
||||
bool HttpSocket::Connect(const char* url, const std::string& port, bool enable_timeout)
|
||||
{
|
||||
GetDomainNameAndPathFromUrl(url, domain_name_, resource_path_);
|
||||
|
||||
socket_fd_ = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (socket_fd_ < 0) {
|
||||
LOGE("cannot open socket %d", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
int reuse = 1;
|
||||
if (setsockopt(socket_fd_, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1) {
|
||||
CloseSocket();
|
||||
LOGE("setsockopt error %d", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct addrinfo hints;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
struct addrinfo* addr_info = NULL;
|
||||
bool status = true;
|
||||
int ret = getaddrinfo(domain_name_.c_str(), port.c_str(), &hints, &addr_info);
|
||||
if (ret != 0) {
|
||||
CloseSocket();
|
||||
LOGE("getaddrinfo failed with %d", ret);
|
||||
status = false;
|
||||
} else {
|
||||
if (connect(socket_fd_, addr_info->ai_addr, addr_info->ai_addrlen) == -1) {
|
||||
CloseSocket();
|
||||
LOGE("cannot connect socket to %s, error=%d", domain_name_.c_str(), errno);
|
||||
status = false;
|
||||
}
|
||||
}
|
||||
timeout_enabled_ = enable_timeout;
|
||||
if (addr_info != NULL) {
|
||||
freeaddrinfo(addr_info);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
int HttpSocket::Read(char* data, int len) {
|
||||
return(Read(data, len, 0));
|
||||
}
|
||||
|
||||
// makes non-blocking mode only during read, it supports timeout for read
|
||||
// returns -1 for error, number of bytes read for success
|
||||
int HttpSocket::Read(char* data, int len, int timeout_in_ms)
|
||||
{
|
||||
bool use_timeout = (timeout_enabled_ && (timeout_in_ms > 0));
|
||||
int original_flags = 0;
|
||||
if (use_timeout) {
|
||||
original_flags = fcntl(socket_fd_, F_GETFL, 0);
|
||||
if (original_flags == -1) {
|
||||
LOGE("fcntl error %d", errno);
|
||||
return -1;
|
||||
}
|
||||
if (fcntl(socket_fd_, F_SETFL, original_flags | O_NONBLOCK) == -1) {
|
||||
LOGE("fcntl error %d", errno);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int total_read = 0;
|
||||
int read = 0;
|
||||
int to_read = len;
|
||||
while (to_read > 0) {
|
||||
if (use_timeout) {
|
||||
fd_set read_fds;
|
||||
struct timeval tv;
|
||||
tv.tv_sec = timeout_in_ms / 1000;
|
||||
tv.tv_usec = (timeout_in_ms % 1000) * 1000;
|
||||
FD_ZERO(&read_fds);
|
||||
FD_SET(socket_fd_, &read_fds);
|
||||
if (select(socket_fd_ + 1, &read_fds, NULL, NULL, &tv) == -1) {
|
||||
LOGE("select failed");
|
||||
break;
|
||||
}
|
||||
if (!FD_ISSET(socket_fd_, &read_fds)) {
|
||||
LOGE("socket read timeout");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
read = recv(socket_fd_, data, to_read, 0);
|
||||
if (read > 0) {
|
||||
to_read -= read;
|
||||
data += read;
|
||||
total_read += read;
|
||||
} else if (read == 0) {
|
||||
// in blocking mode, zero read mean's peer closed.
|
||||
// in non-blocking mode, select said that there is data. so it should not happen
|
||||
break;
|
||||
} else {
|
||||
LOGE("recv returned %d, error = %d", read, errno);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (use_timeout) {
|
||||
fcntl(socket_fd_, F_SETFL, original_flags); // now blocking again
|
||||
}
|
||||
return total_read;
|
||||
}
|
||||
|
||||
int HttpSocket::Write(const char* data, int len)
|
||||
{
|
||||
int total_sent = 0;
|
||||
int sent = 0;
|
||||
int to_send = len;
|
||||
while (to_send > 0) {
|
||||
sent = send(socket_fd_, data, to_send, 0);
|
||||
if (sent > 0) {
|
||||
to_send -= sent;
|
||||
data += sent;
|
||||
total_sent += sent;
|
||||
} else if (sent == 0) {
|
||||
usleep(10); // retry later
|
||||
} else {
|
||||
LOGE("send returned error %d", errno);
|
||||
}
|
||||
}
|
||||
return total_sent;
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
39
libwvdrmengine/cdm/core/test/http_socket.h
Normal file
39
libwvdrmengine/cdm/core/test/http_socket.h
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef CDM_TEST_HTTP_SOCKET_H_
|
||||
#define CDM_TEST_HTTP_SOCKET_H_
|
||||
|
||||
#include <string>
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// Provides basic Linux based TCP socket interface.
|
||||
class HttpSocket {
|
||||
public:
|
||||
HttpSocket();
|
||||
~HttpSocket();
|
||||
|
||||
void CloseSocket();
|
||||
bool Connect(const char* url, const std::string& port, bool enable_timeout);
|
||||
void GetDomainNameAndPathFromUrl(const std::string& url,
|
||||
std::string& domain_name,
|
||||
std::string& resource_path);
|
||||
const std::string& domain_name() const { return domain_name_; };
|
||||
const std::string& resource_path() const { return resource_path_; };
|
||||
int Read(char* data, int len);
|
||||
int Read(char* data, int len, int timeout_in_ms);
|
||||
int Write(const char* data, int len);
|
||||
|
||||
private:
|
||||
std::string domain_name_;
|
||||
std::string resource_path_;
|
||||
int socket_fd_;
|
||||
bool timeout_enabled_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(HttpSocket);
|
||||
};
|
||||
|
||||
}; // namespace wvcdm
|
||||
|
||||
#endif // CDM_TEST_HTTP_SOCKET_H_
|
||||
194
libwvdrmengine/cdm/core/test/http_socket_test.cpp
Normal file
194
libwvdrmengine/cdm/core/test/http_socket_test.cpp
Normal file
@@ -0,0 +1,194 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include <errno.h>
|
||||
#include "gtest/gtest.h"
|
||||
#include "http_socket.h"
|
||||
#include "log.h"
|
||||
#include "string_conversions.h"
|
||||
|
||||
namespace {
|
||||
std::string gTestServer("https://www.google.com");
|
||||
std::string gTestData("Hello");
|
||||
const int kHttpBufferSize = 4096;
|
||||
char gBuffer[kHttpBufferSize];
|
||||
}
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class HttpSocketTest : public testing::Test {
|
||||
public:
|
||||
HttpSocketTest() {}
|
||||
~HttpSocketTest() { socket_.CloseSocket(); }
|
||||
|
||||
protected:
|
||||
bool Connect(const std::string& server_url) {
|
||||
|
||||
if (socket_.Connect(server_url.c_str(), "80", true)) {
|
||||
LOGD("connected to %s", socket_.domain_name().c_str());
|
||||
} else {
|
||||
LOGE("failed to connect to %s", socket_.domain_name().c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PostRequest(const std::string& data) {
|
||||
std::string request("POST ");
|
||||
if (socket_.resource_path().empty())
|
||||
request.append(socket_.domain_name());
|
||||
else
|
||||
request.append(socket_.resource_path());
|
||||
request.append(" HTTP/1.1\r\n");
|
||||
request.append("Host: ");
|
||||
request.append(socket_.domain_name());
|
||||
request.append("\r\nUser-Agent: httpSocketTest/1.0\r\n");
|
||||
request.append("Content-Length: ");
|
||||
memset(gBuffer, 0, kHttpBufferSize);
|
||||
snprintf(gBuffer, kHttpBufferSize, "%d\r\n", static_cast<int>(data.size()));
|
||||
request.append(gBuffer);
|
||||
request.append("Content-Type: multipart/form-data\r\n");
|
||||
|
||||
// newline terminates header
|
||||
request.append("\r\n");
|
||||
|
||||
// append data
|
||||
request.append(data);
|
||||
socket_.Write(request.c_str(), request.size());
|
||||
LOGD("request: %s", request.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetResponse() {
|
||||
int bytes = socket_.Read(gBuffer, kHttpBufferSize, 1000);
|
||||
if (bytes < 0) {
|
||||
LOGE("read error = ", errno);
|
||||
return false;
|
||||
} else {
|
||||
LOGD("read %d bytes", bytes);
|
||||
std::string response(gBuffer, bytes);
|
||||
LOGD("response: %s", response.c_str());
|
||||
LOGD("end response dump");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
HttpSocket socket_;
|
||||
std::string domain_name_;
|
||||
std::string resource_path_;
|
||||
};
|
||||
|
||||
TEST_F(HttpSocketTest, GetDomainNameAndPathFromUrlTest)
|
||||
{
|
||||
socket_.GetDomainNameAndPathFromUrl("http://code.google.com/p/googletest/wiki/Primer",
|
||||
domain_name_,
|
||||
resource_path_);
|
||||
EXPECT_STREQ("code.google.com", domain_name_.c_str());
|
||||
EXPECT_STREQ("p/googletest/wiki/Primer", resource_path_.c_str());
|
||||
|
||||
socket_.GetDomainNameAndPathFromUrl("http://code.google.com/p/googletest/wiki/Primer/",
|
||||
domain_name_,
|
||||
resource_path_);
|
||||
EXPECT_STREQ("code.google.com", domain_name_.c_str());
|
||||
EXPECT_STREQ("p/googletest/wiki/Primer/", resource_path_.c_str());
|
||||
|
||||
socket_.GetDomainNameAndPathFromUrl("http://code.google.com/",
|
||||
domain_name_,
|
||||
resource_path_);
|
||||
EXPECT_STREQ("code.google.com", domain_name_.c_str());
|
||||
EXPECT_STREQ("", resource_path_.c_str());
|
||||
|
||||
socket_.GetDomainNameAndPathFromUrl("http://code.google.com",
|
||||
domain_name_,
|
||||
resource_path_);
|
||||
EXPECT_STREQ("code.google.com", domain_name_.c_str());
|
||||
EXPECT_STREQ("", resource_path_.c_str());
|
||||
|
||||
socket_.GetDomainNameAndPathFromUrl("code.google.com/p/googletest/wiki/Primer",
|
||||
domain_name_,
|
||||
resource_path_);
|
||||
EXPECT_STREQ("code.google.com", domain_name_.c_str());
|
||||
EXPECT_STREQ("p/googletest/wiki/Primer", resource_path_.c_str());
|
||||
|
||||
socket_.GetDomainNameAndPathFromUrl("code.google.com",
|
||||
domain_name_,
|
||||
resource_path_);
|
||||
EXPECT_STREQ("code.google.com", domain_name_.c_str());
|
||||
EXPECT_STREQ("", resource_path_.c_str());
|
||||
|
||||
socket_.GetDomainNameAndPathFromUrl("code.google.com/",
|
||||
domain_name_,
|
||||
resource_path_);
|
||||
EXPECT_STREQ("code.google.com", domain_name_.c_str());
|
||||
EXPECT_STREQ("", resource_path_.c_str());
|
||||
|
||||
socket_.GetDomainNameAndPathFromUrl("",
|
||||
domain_name_,
|
||||
resource_path_);
|
||||
EXPECT_TRUE(domain_name_.empty());
|
||||
EXPECT_TRUE(resource_path_.empty());
|
||||
|
||||
socket_.GetDomainNameAndPathFromUrl("http://10.21.200.68:8888/drm",
|
||||
domain_name_,
|
||||
resource_path_);
|
||||
EXPECT_STREQ("10.21.200.68", domain_name_.c_str());
|
||||
EXPECT_STREQ("drm", resource_path_.c_str());
|
||||
|
||||
socket_.GetDomainNameAndPathFromUrl("http://10.21.200.68:8888",
|
||||
domain_name_,
|
||||
resource_path_);
|
||||
EXPECT_STREQ("10.21.200.68", domain_name_.c_str());
|
||||
EXPECT_TRUE(resource_path_.empty());
|
||||
}
|
||||
|
||||
TEST_F(HttpSocketTest, ConnectTest)
|
||||
{
|
||||
EXPECT_TRUE(Connect(gTestServer));
|
||||
socket_.CloseSocket();
|
||||
EXPECT_FALSE(Connect("ww.g.c"));
|
||||
socket_.CloseSocket();
|
||||
}
|
||||
|
||||
TEST_F(HttpSocketTest, RoundTripTest)
|
||||
{
|
||||
ASSERT_TRUE(Connect(gTestServer));
|
||||
EXPECT_TRUE(PostRequest(gTestData));
|
||||
GetResponse();
|
||||
socket_.CloseSocket();
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
|
||||
std::string temp;
|
||||
std::string test_server(gTestServer);
|
||||
std::string test_data(gTestData);
|
||||
for (int i=1; i<argc; i++) {
|
||||
temp.assign(argv[i]);
|
||||
if (temp.find("--server=") == 0) {
|
||||
gTestServer.assign(temp.substr(strlen("--server=")));
|
||||
} else if (temp.find("--data=") == 0) {
|
||||
gTestData.assign(temp.substr(strlen("--data=")));
|
||||
}
|
||||
else {
|
||||
std::cout << "error: unknown option '" << argv[i] << "'" << std::endl;
|
||||
std::cout << "usage: http_socket_test [options]" << std::endl << std::endl;
|
||||
std::cout << std::setw(30) << std::left << " --server=<server_url>";
|
||||
std::cout << "configure the test server url, please include http[s] in the url" << std::endl;
|
||||
std::cout << std::setw(30) << std::left << " ";
|
||||
std::cout << "default: " << test_server << std::endl;
|
||||
std::cout << std::setw(30) << std::left << " --data=<data>";
|
||||
std::cout << "configure data to send, in ascii string format" << std::endl;
|
||||
std::cout << std::setw(30) << std::left << " ";
|
||||
std::cout << "default: " << test_data << std::endl << std::endl;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
std::cout << "Server: " << gTestServer << std::endl;
|
||||
std::cout << "Data: " << gTestData << std::endl;
|
||||
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
82
libwvdrmengine/cdm/core/test/license_request.cpp
Normal file
82
libwvdrmengine/cdm/core/test/license_request.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "license_request.h"
|
||||
#include "log.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
static const std::string kTwoBlankLines("\r\n\r\n");
|
||||
|
||||
size_t LicenseRequest::FindHeaderEndPosition(
|
||||
const std::string& response) const {
|
||||
return(response.find(kTwoBlankLines));
|
||||
}
|
||||
|
||||
// Returns drm message in drm_msg.
|
||||
// The drm message is at the end of the response message.
|
||||
void LicenseRequest::GetDrmMessage(const std::string& response,
|
||||
std::string& drm_msg) {
|
||||
if (response.empty()) {
|
||||
drm_msg.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// Extracts DRM message.
|
||||
// Content-Length = GLS line + Header(s) + empty line + drm message;
|
||||
// we use the empty line to locate the drm message, and compute
|
||||
// the drm message length as below instead of using Content-Length
|
||||
size_t header_end_pos = FindHeaderEndPosition(response);
|
||||
if (header_end_pos != std::string::npos) {
|
||||
header_end_pos += kTwoBlankLines.size(); // points to response body
|
||||
|
||||
drm_msg.clear();
|
||||
size_t drm_msg_pos = response.find(kTwoBlankLines, header_end_pos);
|
||||
if (drm_msg_pos != std::string::npos) {
|
||||
drm_msg_pos += kTwoBlankLines.size(); // points to drm message
|
||||
} else {
|
||||
// For backward compatibility, no blank line after error code
|
||||
drm_msg_pos = response.find("\r\n", header_end_pos);
|
||||
if (drm_msg_pos != std::string::npos) {
|
||||
drm_msg_pos += 2; // points to drm message
|
||||
}
|
||||
}
|
||||
|
||||
if (drm_msg_pos != std::string::npos) {
|
||||
drm_msg = response.substr(drm_msg_pos);
|
||||
} else {
|
||||
LOGE("drm msg not found");
|
||||
}
|
||||
} else {
|
||||
LOGE("response body not found");
|
||||
}
|
||||
}
|
||||
|
||||
// Returns heartbeat url in heartbeat_url.
|
||||
// The heartbeat url is stored as meta data in the response message.
|
||||
void LicenseRequest::GetHeartbeatUrl(const std::string& response,
|
||||
std::string& heartbeat_url) {
|
||||
if (response.empty()) {
|
||||
heartbeat_url.clear(); // TODO: assign default heartbeat url
|
||||
return;
|
||||
}
|
||||
|
||||
size_t header_end_pos = FindHeaderEndPosition(response);
|
||||
if (header_end_pos != std::string::npos) {
|
||||
header_end_pos += kTwoBlankLines.size(); // points to response body
|
||||
|
||||
heartbeat_url.clear();
|
||||
size_t heartbeat_url_pos = response.find("Heartbeat-Url: ",
|
||||
header_end_pos);
|
||||
if (heartbeat_url_pos != std::string::npos) {
|
||||
heartbeat_url_pos += sizeof("Heartbeat-Url: ");
|
||||
heartbeat_url.assign(response.substr(heartbeat_url_pos));
|
||||
} else {
|
||||
LOGE("heartbeat url not found");
|
||||
}
|
||||
} else {
|
||||
LOGE("response body not found");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace wvcdm
|
||||
30
libwvdrmengine/cdm/core/test/license_request.h
Normal file
30
libwvdrmengine/cdm/core/test/license_request.h
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef CDM_TEST_LICENSE_REQUEST_H_
|
||||
#define CDM_TEST_LICENSE_REQUEST_H_
|
||||
|
||||
#include <string>
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// Parses response from a license request.
|
||||
// This class assumes a particular response format defined by
|
||||
// Google license servers.
|
||||
class LicenseRequest {
|
||||
public:
|
||||
LicenseRequest() {};
|
||||
~LicenseRequest() {};
|
||||
|
||||
void GetDrmMessage(const std::string& response, std::string& drm_msg);
|
||||
void GetHeartbeatUrl(const std::string& response, std::string& heartbeat_url);
|
||||
|
||||
private:
|
||||
size_t FindHeaderEndPosition(const std::string& response) const;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(LicenseRequest);
|
||||
};
|
||||
|
||||
}; // namespace wvcdm
|
||||
|
||||
#endif // CDM_TEST_LICENSE_REQUEST_H_
|
||||
100
libwvdrmengine/cdm/core/test/license_unittest.cpp
Normal file
100
libwvdrmengine/cdm/core/test/license_unittest.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "crypto_engine.h"
|
||||
#include "crypto_session.h"
|
||||
#include "license.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "string_conversions.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// The test data is based on key box Eureka-Dev-G1-0001520
|
||||
// This unit test should run on oemcrypto mock with the same key box
|
||||
static const char* kInitData = "0801121093789920E8D6520098577DF8F2DD5546";
|
||||
static const char* kSignedRequest =
|
||||
"080112790A4C0800124800000002000001241F344DB9DFF087F01D917910F39B"
|
||||
"60DC7797CD97789EE82516DC07A478CB70B8C08C299293150AA8E01D2DC9808C"
|
||||
"98DAA16E40A0E55DFE3618C7584DD3C7BE4212250A230A140801121093789920"
|
||||
"E8D6520098577DF8F2DD554610011A09393837363534333231180120001A20FA"
|
||||
"E2DDCD7F1ACA4B728EC957FEE802F8A5541557ACA784EE0D05BFCC0E65FEA1";
|
||||
static const char* kValidResponse =
|
||||
"080212D9020A190A093938373635343332311208C434AB9240A9EF2420012800"
|
||||
"120E0801180120809A9E0128809A9E011A461210B72EEBF582B04BDB15C2E0E3"
|
||||
"20B21C351A30E51FC1D27F70DB8E0DDF8C051BD6E251A44599DBCE4E1BE663FD"
|
||||
"3AFAB191A7DD5736841FB04CE558E7F17BD9812A2DBA20011A6E0A1093789920"
|
||||
"E8D6520098577DF8F2DD55461210367E8714B6F10087AFDE542EDC5C91541A20"
|
||||
"ED51D4E84D81C8CBD8E2046EE079F8A2016268A2F192B902FDA241FEEB10C014"
|
||||
"200242240A109209D46191B8752147C9F6A1CE2BEE6E12107910F39B60DC7797"
|
||||
"CD97789EE82516DC1A6E0A107B1328EB61B554E293F75B1E3E94CC3B1210676F"
|
||||
"69BBDA35EE972B77BC1328A087391A20D2B9FA92B164F5F6362CAD9200A11661"
|
||||
"B8F71E9CE671A3A252D34586526B68FA200242240A109D7B13420FD6217666CC"
|
||||
"CD43860FAA3A1210DBCE4E1BE663FD3AFAB191A7DD57368420E9FDCE86051A20"
|
||||
"C6279E32FD2CB9067229E87AFF4B2DE14A077CDF8F061DAEE2CC2D1BCDEF62D0";
|
||||
static const char* kInvalidResponse =
|
||||
"0802128D020A190A093938373635343332311208BA68C949396C438C20012800"
|
||||
"120E0801180120809A9E0128809A9E011A4612105021EB9AEDC1F73E96DE7DCC"
|
||||
"6D7D72401A300A82E118C0BF0DB230FCADE3F49A9777DDD392322240FEF32C97"
|
||||
"F85428E2F6CCFA638B5481464ADBCF199CEC2FCF3AFB20011A480A1093789920"
|
||||
"E8D6520098577DF8F2DD55461210EE52C59B99050A36E10569AFB34D1DA41A20"
|
||||
"C61FCB8019AC9ADE99FF8FCA99ED35E2331B6488A35102F9379AA42C87A22DC7"
|
||||
"20021A480A107B1328EB61B554E293F75B1E3E94CC3B12101BBF5286B859E349"
|
||||
"2E4A47A24C06AC1B1A2061F21836A04E558BEE0244EF41C165F60CF23C580275"
|
||||
"3175D48BAF1C6CA5759F200220A2BCCA86051A203FD4671075D9DEC6486A9317"
|
||||
"70669993306831EDD57D77F34EFEB467470BA364";
|
||||
}
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class LicenseTest : public ::testing::Test {
|
||||
protected:
|
||||
virtual void SetUp() {
|
||||
CryptoEngine* crypto_engine = CryptoEngine::GetInstance();
|
||||
EXPECT_TRUE(crypto_engine != NULL);
|
||||
session_ = crypto_engine->CreateSession("Dummy");
|
||||
EXPECT_TRUE(session_ != NULL);
|
||||
|
||||
std::string token;
|
||||
EXPECT_TRUE(crypto_engine->GetToken(&token));
|
||||
|
||||
EXPECT_TRUE(session_->IsOpen());
|
||||
EXPECT_TRUE(license_.Init(token, session_));
|
||||
}
|
||||
|
||||
virtual void TearDown() {
|
||||
session_->Close();
|
||||
delete session_;
|
||||
}
|
||||
|
||||
CryptoSession* session_;
|
||||
CdmLicense license_;
|
||||
};
|
||||
|
||||
TEST(LicenseTestSession, InitNullSession) {
|
||||
CdmLicense license;
|
||||
EXPECT_FALSE(license.Init("Dummy", NULL));
|
||||
}
|
||||
|
||||
TEST_F(LicenseTest, PrepareKeyRequest) {
|
||||
std::string signed_request;
|
||||
license_.PrepareKeyRequest(a2bs_hex(kInitData), &signed_request);
|
||||
EXPECT_EQ(signed_request, a2bs_hex(kSignedRequest));
|
||||
}
|
||||
|
||||
TEST_F(LicenseTest, HandleKeyResponseValid) {
|
||||
std::string signed_request;
|
||||
license_.PrepareKeyRequest(a2bs_hex(kInitData), &signed_request);
|
||||
EXPECT_EQ(signed_request, a2bs_hex(kSignedRequest));
|
||||
EXPECT_TRUE(license_.HandleKeyResponse(a2bs_hex(kValidResponse)));
|
||||
}
|
||||
|
||||
TEST_F(LicenseTest, HandleKeyResponseInvalid) {
|
||||
std::string signed_request;
|
||||
license_.PrepareKeyRequest(a2bs_hex(kInitData), &signed_request);
|
||||
EXPECT_EQ(signed_request, a2bs_hex(kSignedRequest));
|
||||
EXPECT_FALSE(license_.HandleKeyResponse(a2bs_hex(kInvalidResponse)));
|
||||
}
|
||||
|
||||
// TODO(kqyang): add unit test cases for PrepareKeyRenewalRequest
|
||||
// and HandleRenewalKeyResponse
|
||||
|
||||
}
|
||||
106
libwvdrmengine/cdm/core/test/url_request.cpp
Normal file
106
libwvdrmengine/cdm/core/test/url_request.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "url_request.h"
|
||||
|
||||
#include "http_socket.h"
|
||||
#include "log.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
UrlRequest::UrlRequest(const std::string& url, const std::string& port)
|
||||
: is_connected_(false),
|
||||
port_("80"),
|
||||
request_(""),
|
||||
server_url_(url)
|
||||
{
|
||||
if (!port.empty()) {
|
||||
port_.assign(port);
|
||||
}
|
||||
if (socket_.Connect((server_url_).c_str(), port_, true)) {
|
||||
LOGD("connected to %s", socket_.domain_name().c_str());
|
||||
is_connected_ = true;
|
||||
} else {
|
||||
LOGE("failed to connect to %s, port=%s",
|
||||
socket_.domain_name().c_str(), port.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
UrlRequest::~UrlRequest()
|
||||
{
|
||||
socket_.CloseSocket();
|
||||
}
|
||||
|
||||
void UrlRequest::AppendChunkToUpload(const std::string& data) {
|
||||
// format of chunk:
|
||||
// size of chunk in hex\r\n
|
||||
// data\r\n
|
||||
// . . .
|
||||
// 0\r\n
|
||||
|
||||
// buffer to store length of chunk
|
||||
memset(buffer_, 0, kHttpBufferSize);
|
||||
snprintf(buffer_, kHttpBufferSize, "%x\r\n", data.size());
|
||||
request_.append(buffer_); // appends size of chunk
|
||||
LOGD("...\r\n%s", request_.c_str());
|
||||
request_.append(data);
|
||||
request_.append("\r\n"); // marks end of data
|
||||
}
|
||||
|
||||
int UrlRequest::GetResponse(std::string& response) {
|
||||
response.clear();
|
||||
|
||||
const int kTimeoutInMs = 1000;
|
||||
int bytes = 0;
|
||||
int total_bytes = 0;
|
||||
do {
|
||||
memset(buffer_, 0, kHttpBufferSize);
|
||||
bytes = socket_.Read(buffer_, kHttpBufferSize, kTimeoutInMs);
|
||||
if (bytes > 0) {
|
||||
response.append(buffer_, bytes);
|
||||
total_bytes += bytes;
|
||||
} else {
|
||||
if (bytes < 0) LOGE("read error = ", errno);
|
||||
// bytes == 0 indicates nothing to read
|
||||
}
|
||||
} while (bytes > 0);
|
||||
return total_bytes;
|
||||
}
|
||||
|
||||
int UrlRequest::GetStatusCode(const std::string& response) {
|
||||
const std::string kHttpVersion("HTTP/1.1");
|
||||
|
||||
int status_code = -1;
|
||||
size_t pos = response.find(kHttpVersion);
|
||||
if (pos != std::string::npos) {
|
||||
pos += kHttpVersion.size();
|
||||
sscanf(response.substr(pos).c_str(), "%d", &status_code);
|
||||
}
|
||||
return status_code;
|
||||
}
|
||||
|
||||
bool UrlRequest::PostRequest(const std::string& data) {
|
||||
request_.assign("POST /");
|
||||
request_.append(socket_.resource_path());
|
||||
request_.append(" HTTP/1.1\r\n");
|
||||
request_.append("Host: ");
|
||||
request_.append(socket_.domain_name());
|
||||
request_.append("\r\nConnection: Keep-Alive\r\n");
|
||||
request_.append("Transfer-Encoding: chunked\r\n");
|
||||
request_.append("User-Agent: Widevine CDM v1.0\r\n");
|
||||
request_.append("Accept-Encoding: gzip,deflate\r\n");
|
||||
request_.append("Accept-Language: en-us,fr\r\n");
|
||||
request_.append("Accept-Charset: iso-8859-1,*,utf-8\r\n");
|
||||
request_.append("\r\n"); // empty line to terminate header
|
||||
|
||||
// calls AppendChunkToUpload repeatedly for multiple chunks
|
||||
AppendChunkToUpload(data);
|
||||
|
||||
// terminates last chunk with 0\r\n, then ends header with an empty line
|
||||
request_.append("0\r\n\r\n");
|
||||
|
||||
socket_.Write(request_.c_str(), request_.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
} // namespace wvcdm
|
||||
39
libwvdrmengine/cdm/core/test/url_request.h
Normal file
39
libwvdrmengine/cdm/core/test/url_request.h
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef CDM_TEST_URL_REQUEST_H_
|
||||
#define CDM_TEST_URL_REQUEST_H_
|
||||
|
||||
#include <string>
|
||||
#include "http_socket.h"
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// Provides simple HTTP request and response service.
|
||||
// Only POST request method is implemented.
|
||||
class UrlRequest {
|
||||
public:
|
||||
UrlRequest(const std::string& url, const std::string& port);
|
||||
~UrlRequest();
|
||||
|
||||
void AppendChunkToUpload(const std::string& data);
|
||||
int GetResponse(std::string& response);
|
||||
int GetStatusCode(const std::string& response);
|
||||
bool is_connected() const { return is_connected_; }
|
||||
bool PostRequest(const std::string& data);
|
||||
|
||||
private:
|
||||
static const unsigned int kHttpBufferSize = 4096;
|
||||
char buffer_[kHttpBufferSize];
|
||||
bool is_connected_;
|
||||
std::string port_;
|
||||
std::string request_;
|
||||
HttpSocket socket_;
|
||||
std::string server_url_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(UrlRequest);
|
||||
};
|
||||
|
||||
}; // namespace wvcdm
|
||||
|
||||
#endif // CDM_TEST_URL_REQUEST_H_
|
||||
861
libwvdrmengine/cdm/core/test/wvcdm_decryptor_unittest.cpp
Normal file
861
libwvdrmengine/cdm/core/test/wvcdm_decryptor_unittest.cpp
Normal file
@@ -0,0 +1,861 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
|
||||
#include "base/at_exit.h"
|
||||
#include "base/basictypes.h"
|
||||
#include "base/bind.h"
|
||||
#include "base/message_loop.h"
|
||||
#include "base/sys_byteorder.h"
|
||||
#include "crypto/encryptor.h"
|
||||
#include "crypto/hmac.h"
|
||||
#include "crypto/symmetric_key.h"
|
||||
#include "license_protocol.pb.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
#include "eureka/widevine_cdm/oemcrypto/client/oemcrypto_client.h"
|
||||
#include "eureka/widevine_cdm/oemcrypto/mock/src/cmac.h"
|
||||
#include "eureka/widevine_cdm/oemcrypto/mock/src/oemcrypto_keybox_mock.h"
|
||||
#include "wv_cdm_types.h"
|
||||
#include "wv_content_decryption_module.h"
|
||||
|
||||
namespace {
|
||||
|
||||
namespace wv_license_protocol = video_widevine_server::sdk;
|
||||
|
||||
using wv_license_protocol::License;
|
||||
using wv_license_protocol::LicenseIdentification;
|
||||
using wv_license_protocol::LicenseRequest;
|
||||
using wv_license_protocol::SessionState;
|
||||
using wv_license_protocol::SignedMessage;
|
||||
|
||||
enum PolicyType {
|
||||
kDefault = 0,
|
||||
kNoPlay,
|
||||
kShortDuration
|
||||
};
|
||||
|
||||
struct PolicyItem {
|
||||
PolicyType type;
|
||||
bool can_play;
|
||||
bool can_renew;
|
||||
int duration_seconds;
|
||||
int renewal_delay_seconds;
|
||||
int renewal_retry_interval_seconds;
|
||||
};
|
||||
|
||||
struct PolicyItem PolicyItems[] = {
|
||||
{
|
||||
kDefault,
|
||||
true,
|
||||
true,
|
||||
1000,
|
||||
100,
|
||||
0
|
||||
},
|
||||
{
|
||||
kShortDuration,
|
||||
true,
|
||||
true,
|
||||
12,
|
||||
2,
|
||||
2
|
||||
},
|
||||
{
|
||||
kNoPlay,
|
||||
false,
|
||||
false,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
}
|
||||
};
|
||||
|
||||
// TODO(jfore): Move this into the test class.
|
||||
/*const*/ char kTestSigningKey[] = {
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
|
||||
};
|
||||
const int kTestSigningKeySize = arraysize(kTestSigningKey);
|
||||
|
||||
const char* kEncryptionKeyLabel = "ENCRYPTION";
|
||||
const uint32_t kEncryptionKeySizeBits = 128;
|
||||
const char* kSigningKeyLabel = "AUTHENTICATION";
|
||||
const uint32_t kSigningKeySizeBits = 256;
|
||||
|
||||
// This is a container to hold the info for an encrypted frame.
|
||||
struct WvCdmEncryptedFrameInfo {
|
||||
char plain_text[32];
|
||||
int plain_text_size;
|
||||
uint8_t key_id[32];
|
||||
int key_id_size;
|
||||
uint8_t content_key[32];
|
||||
int content_key_size;
|
||||
uint8_t encrypted_data[64];
|
||||
int encrypted_data_size;
|
||||
PolicyType policy_type;
|
||||
};
|
||||
|
||||
const WvCdmEncryptedFrameInfo kWvCdmEncryptedFrames[] = {
|
||||
{
|
||||
"Original data.", 14,
|
||||
{ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
|
||||
0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35
|
||||
}, 16,
|
||||
{ 0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b,
|
||||
0x68, 0xef, 0x12, 0x2a, 0xfc, 0xe4, 0xae, 0x3c
|
||||
}, 16,
|
||||
{ 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0x82, 0x54, 0xab, 0x89, 0xa2, 0x75, 0xcd,
|
||||
0x85, 0x83, 0x61, 0x3f, 0xfe, 0x13, 0x58
|
||||
}, 23,
|
||||
kShortDuration
|
||||
},
|
||||
{
|
||||
"Original data.", 14,
|
||||
{ 0x08, 0x01, 0x12, 0x10, 0x6f, 0x13, 0x33, 0xe7,
|
||||
0x6e, 0x59, 0x5e, 0xb5, 0x8c, 0x04, 0x30, 0x72,
|
||||
0xcb, 0xb2, 0x50, 0x68
|
||||
}, 20,
|
||||
{ 0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b,
|
||||
0x68, 0xef, 0x12, 0x2a, 0xfc, 0xe4, 0xae, 0x3c
|
||||
}, 16,
|
||||
{ 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0x82, 0x54, 0xab, 0x89, 0xa2, 0x75, 0xcd,
|
||||
0x85, 0x83, 0x61, 0x3f, 0xfe, 0x13, 0x58
|
||||
}, 23,
|
||||
kShortDuration
|
||||
},
|
||||
{
|
||||
"Changed Original data.", 22,
|
||||
{ 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
|
||||
0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02
|
||||
}, 16,
|
||||
{ 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
|
||||
0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23
|
||||
}, 16,
|
||||
{ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x57, 0x66, 0xf4, 0x12, 0x1a, 0xed, 0xb5,
|
||||
0x79, 0x1c, 0x8e, 0x25, 0xd7, 0x17, 0xe7, 0x5e,
|
||||
0x16, 0xe3, 0x40, 0x08, 0x27, 0x11, 0xe9
|
||||
}, 31,
|
||||
kShortDuration
|
||||
},
|
||||
{
|
||||
"Original data.", 14,
|
||||
{ 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
|
||||
0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f
|
||||
}, 16,
|
||||
{ 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
|
||||
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40
|
||||
}, 16,
|
||||
{ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x9c, 0x71, 0x26, 0x57, 0x3e, 0x25, 0x37,
|
||||
0xf7, 0x31, 0x81, 0x19, 0x64, 0xce, 0xbc
|
||||
}, 23,
|
||||
kShortDuration
|
||||
},
|
||||
// For license renewal test. This has kNoPlay.
|
||||
{
|
||||
// Differnent key and key id.
|
||||
"Original data.", 14,
|
||||
{ 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
|
||||
0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33
|
||||
}, 16,
|
||||
{ 0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b,
|
||||
0x68, 0xef, 0x12, 0x2a, 0xfc, 0xe4, 0xae, 0x3c
|
||||
}, 16,
|
||||
{ 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0x82, 0x54, 0xab, 0x89, 0xa2, 0x75, 0xcd,
|
||||
0x85, 0x83, 0x61, 0x3f, 0xfe, 0x13, 0x58
|
||||
}, 23,
|
||||
kNoPlay
|
||||
}
|
||||
};
|
||||
|
||||
bool GetPolicy(PolicyType policy_type, License::Policy* policy) {
|
||||
DCHECK(policy);
|
||||
PolicyItem policy_item;
|
||||
switch (policy_type) {
|
||||
case kDefault:
|
||||
policy_item = PolicyItems[0];
|
||||
DCHECK_EQ(policy_item.type, kDefault);
|
||||
break;
|
||||
case kShortDuration:
|
||||
policy_item = PolicyItems[1];
|
||||
DCHECK_EQ(policy_item.type, kShortDuration);
|
||||
break;
|
||||
case kNoPlay:
|
||||
policy_item = PolicyItems[2];
|
||||
DCHECK_EQ(policy_item.type, kNoPlay);
|
||||
break;
|
||||
default:
|
||||
NOTREACHED();
|
||||
return false;
|
||||
}
|
||||
policy->set_can_play(policy_item.can_play);
|
||||
policy->set_can_renew(policy_item.can_renew);
|
||||
policy->set_license_duration_seconds(policy_item.duration_seconds);
|
||||
policy->set_renewal_delay_seconds(policy_item.renewal_delay_seconds);
|
||||
policy->set_renewal_retry_interval_seconds(
|
||||
policy_item.renewal_retry_interval_seconds);
|
||||
return true;
|
||||
}
|
||||
|
||||
SessionState GetTestSessionState() {
|
||||
static const std::string kTestSessionId = "SomeSessionId";
|
||||
SessionState session_cache;
|
||||
session_cache.mutable_license_id()->set_session_id(kTestSessionId);
|
||||
session_cache.mutable_license_id()->set_version(0);
|
||||
session_cache.set_signing_key(kTestSigningKey, kTestSigningKeySize);
|
||||
return session_cache;
|
||||
}
|
||||
|
||||
// Since "GetTime" is used in this test where some functions cannot reach
|
||||
// Host instance, this will be used as a universal clock for this test.
|
||||
double GetCurrentTestTime() {
|
||||
return base::Time::Now().ToDoubleT();
|
||||
}
|
||||
|
||||
// This encrypts the content key using the device key in cdm/base/device_key.h.
|
||||
std::string EncryptUsingKey(const std::string& input,
|
||||
const std::string& iv,
|
||||
const std::string& encryption_key) {
|
||||
//static const int kAesBlockSize = 16;
|
||||
crypto::Encryptor aes_ecryptor;
|
||||
scoped_ptr<crypto::SymmetricKey> device_key(
|
||||
crypto::SymmetricKey::Import(crypto::SymmetricKey::AES,
|
||||
encryption_key));
|
||||
if (!aes_ecryptor.Init(device_key.get(), crypto::Encryptor::CBC, iv))
|
||||
return "";
|
||||
|
||||
std::string encrypted_data;
|
||||
if (!aes_ecryptor.Encrypt(input, &encrypted_data))
|
||||
return "";
|
||||
|
||||
return encrypted_data;
|
||||
}
|
||||
|
||||
// Takes the license and the session state and generates a SignedMessage. The
|
||||
// return value is the serialized version of the SignedMessage object.
|
||||
std::string GenerateSignedLicenseResponse(const License& license,
|
||||
const std::string& signing_key) {
|
||||
SignedMessage signed_message;
|
||||
bool success = license.SerializeToString(
|
||||
signed_message.mutable_msg());
|
||||
DCHECK(success);
|
||||
|
||||
crypto::HMAC hmacer(crypto::HMAC::SHA256);
|
||||
if (!hmacer.Init(signing_key))
|
||||
return "";
|
||||
|
||||
static const int kDigestSize = 32;
|
||||
uint8_t digest[kDigestSize] = { 0 };
|
||||
if (!hmacer.Sign(signed_message.msg(), digest, kDigestSize))
|
||||
return "";
|
||||
|
||||
signed_message.set_signature(digest, kDigestSize);
|
||||
|
||||
std::string signed_message_bytes;
|
||||
success = signed_message.SerializeToString(&signed_message_bytes);
|
||||
DCHECK(success);
|
||||
|
||||
return signed_message_bytes;
|
||||
}
|
||||
|
||||
// Note: We only use one session. So there aren't any list of sessions stored
|
||||
// anywhere.
|
||||
std::string GenerateNewSignedLicense(
|
||||
const LicenseRequest& license_request,
|
||||
const License::Policy& policies,
|
||||
const License::KeyContainer& content_key,
|
||||
const std::string& encryption_key,
|
||||
const std::string& signing_key) {
|
||||
DCHECK(license_request.content_id().has_cenc_id());
|
||||
SessionState session_cache = GetTestSessionState();
|
||||
DCHECK(session_cache.has_signing_key());
|
||||
|
||||
session_cache.mutable_license_id()->set_request_id(
|
||||
license_request.content_id().webm_id().request_id());
|
||||
session_cache.mutable_license_id()->set_type(
|
||||
license_request.content_id().webm_id().license_type());
|
||||
|
||||
License license;
|
||||
license.mutable_id()->CopyFrom(session_cache.license_id());
|
||||
license.mutable_policy()->CopyFrom(policies);
|
||||
license.set_license_start_time(GetCurrentTestTime());
|
||||
|
||||
License::KeyContainer* renewal_signing_key = license.add_key();
|
||||
renewal_signing_key->set_key(session_cache.signing_key());
|
||||
renewal_signing_key->set_type(License::KeyContainer::SIGNING);
|
||||
|
||||
license.add_key()->CopyFrom(content_key);
|
||||
for (int i = 0; i < license.key_size(); ++i) {
|
||||
license.mutable_key(i)->set_iv("0123456789012345");
|
||||
license.mutable_key(i)->set_key(
|
||||
EncryptUsingKey(license.key(i).key(),
|
||||
license.key(i).iv(),
|
||||
encryption_key));
|
||||
if (license.key(i).key().empty())
|
||||
return "";
|
||||
}
|
||||
|
||||
return GenerateSignedLicenseResponse(license, signing_key);
|
||||
}
|
||||
|
||||
bool GetContentKeyFromKeyId(const std::string& key_id,
|
||||
std::string* content_key) {
|
||||
DCHECK(content_key);
|
||||
for (unsigned int i = 0; i < arraysize(kWvCdmEncryptedFrames); ++i) {
|
||||
const WvCdmEncryptedFrameInfo& frame = kWvCdmEncryptedFrames[i];
|
||||
if (frame.key_id_size != static_cast<int>(key_id.size()))
|
||||
continue;
|
||||
if (!memcmp(frame.key_id, key_id.data(), frame.key_id_size)) {
|
||||
content_key->assign(frame.content_key,
|
||||
frame.content_key + frame.content_key_size);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string DeriveKey(const std::string& key,
|
||||
const std::string& purpose,
|
||||
const std::string& context,
|
||||
const uint32_t size_bits) {
|
||||
if (key.size() != 16)
|
||||
return "";
|
||||
|
||||
// We only handle even multiples of 16 bytes (128 bits) right now.
|
||||
if ((size_bits % 128) || (size_bits > (128 * 255))) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string result;
|
||||
|
||||
const EVP_CIPHER *cipher = EVP_aes_128_cbc();
|
||||
CMAC_CTX* cmac_ctx = CMAC_CTX_new();
|
||||
|
||||
size_t reslen;
|
||||
unsigned char res[128];
|
||||
unsigned char counter;
|
||||
for (counter = 1; counter <= (size_bits / 128); counter++) {
|
||||
if (!CMAC_Init(cmac_ctx, key.data(), key.size(), cipher, 0))
|
||||
break;
|
||||
|
||||
std::string message;
|
||||
message.append(1, counter);
|
||||
message.append(purpose);
|
||||
message.append(1, '\0');
|
||||
message.append(context);
|
||||
uint32_t size_l = htonl(size_bits);
|
||||
message.append(reinterpret_cast<char*>(&size_l), sizeof(size_l));
|
||||
if (!CMAC_Update(cmac_ctx, message.data(), message.size()))
|
||||
break;
|
||||
|
||||
if (!CMAC_Final(cmac_ctx, res, &reslen))
|
||||
break;
|
||||
|
||||
result.append((const char*)res, reslen);
|
||||
}
|
||||
|
||||
CMAC_CTX_free(cmac_ctx);
|
||||
|
||||
if (counter <= (size_bits / 128))
|
||||
return "";
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool VerifyTestSignature(const std::string& message,
|
||||
const std::string& signature,
|
||||
const std::string& key) {
|
||||
crypto::HMAC hmacer(crypto::HMAC::SHA256);
|
||||
if (!hmacer.Init(key))
|
||||
return false;
|
||||
|
||||
if (!hmacer.Verify(message, signature))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string GenerateNewLicenseResponse(const LicenseRequest& license_request,
|
||||
const PolicyType& policy_type) {
|
||||
if (!license_request.content_id().has_cenc_id())
|
||||
return "";
|
||||
|
||||
std::string content_key_id = license_request.content_id().cenc_id().pssh(0);
|
||||
|
||||
std::string content_key;
|
||||
if (!GetContentKeyFromKeyId(content_key_id,
|
||||
&content_key)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (content_key_id.size() > 16)
|
||||
content_key_id.resize(16);
|
||||
|
||||
License::Policy policies;
|
||||
if (!GetPolicy(policy_type, &policies))
|
||||
return "";
|
||||
|
||||
std::string context;
|
||||
if (!license_request.SerializeToString(&context))
|
||||
return "";
|
||||
|
||||
wvoec_mock::WvKeybox keybox;
|
||||
|
||||
// TODO(): Fix this to use a constant for key length.
|
||||
std::string widevine_device_key(keybox.device_key().value());
|
||||
std::string encryption_key = DeriveKey(widevine_device_key,
|
||||
std::string(kEncryptionKeyLabel),
|
||||
context,
|
||||
kEncryptionKeySizeBits);
|
||||
std::string signing_key = DeriveKey(widevine_device_key,
|
||||
std::string(kSigningKeyLabel),
|
||||
context,
|
||||
kSigningKeySizeBits);
|
||||
|
||||
memcpy(kTestSigningKey, &signing_key[0], 32);
|
||||
|
||||
License::KeyContainer key_container;
|
||||
key_container.set_id(content_key_id);
|
||||
key_container.set_key(content_key);
|
||||
key_container.set_type(License::KeyContainer::CONTENT);
|
||||
return GenerateNewSignedLicense(license_request,
|
||||
policies,
|
||||
key_container,
|
||||
encryption_key,
|
||||
signing_key);
|
||||
}
|
||||
|
||||
std::string GenerateLicenseRenewalResponse(
|
||||
const SignedMessage& signed_message,
|
||||
const PolicyType& policy_type) {
|
||||
SessionState session_cache = GetTestSessionState();
|
||||
|
||||
LicenseRequest license_request;
|
||||
if (!license_request.ParseFromString(signed_message.msg()))
|
||||
return "";
|
||||
|
||||
std::string session_id = license_request.content_id().license().
|
||||
license_id().session_id();
|
||||
if (session_id.compare(session_cache.license_id().session_id()))
|
||||
return "";
|
||||
|
||||
if (!VerifyTestSignature(signed_message.msg(),
|
||||
signed_message.signature(),
|
||||
session_cache.signing_key())) {
|
||||
return "";
|
||||
}
|
||||
session_cache.mutable_license_id()->set_version(
|
||||
session_cache.license_id().version() + 1);
|
||||
|
||||
License license;
|
||||
license.mutable_id()->CopyFrom(session_cache.license_id());
|
||||
|
||||
// Always get Policy object with kDefault for renewal.
|
||||
License::Policy policy;
|
||||
GetPolicy(policy_type, &policy);
|
||||
license.mutable_policy()->Swap(&policy);
|
||||
license.set_license_start_time(GetCurrentTestTime());
|
||||
|
||||
return GenerateSignedLicenseResponse(license, session_cache.signing_key());
|
||||
}
|
||||
|
||||
std::string GenerateLicenseResponse(const std::string& signed_request,
|
||||
const PolicyType& policy) {
|
||||
SignedMessage signed_message;
|
||||
if (!signed_message.ParseFromString(signed_request))
|
||||
return "";
|
||||
|
||||
LicenseRequest license_request;
|
||||
if (!license_request.ParseFromString(signed_message.msg()))
|
||||
return "";
|
||||
|
||||
if (license_request.type() == LicenseRequest::NEW) {
|
||||
return GenerateNewLicenseResponse(license_request, policy);
|
||||
} else if (license_request.type() == LicenseRequest::RENEWAL) {
|
||||
return GenerateLicenseRenewalResponse(signed_message, policy);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
struct WvCdmEncryptedData {
|
||||
char plain_text[32];
|
||||
int plain_text_size;
|
||||
uint8_t key_id[32];
|
||||
int key_id_size;
|
||||
uint8_t content_key[32];
|
||||
int content_key_size;
|
||||
uint8_t encrypted_data[64];
|
||||
int encrypted_data_size;
|
||||
const char* license_response;
|
||||
int license_response_size;
|
||||
};
|
||||
|
||||
// Container used to pass data from GenerateLicenseRequest to Decrypt.
|
||||
// TODO(rkuroiwa): This class was made before KeyMessage existed; this
|
||||
// should be removed.
|
||||
class LicenseRequestParameter {
|
||||
public:
|
||||
explicit LicenseRequestParameter(const WvCdmEncryptedData& frame)
|
||||
: init_data(new uint8_t[frame.key_id_size]),
|
||||
init_data_size(frame.key_id_size),
|
||||
session_id(NULL),
|
||||
session_id_size(0),
|
||||
key_request(NULL),
|
||||
key_request_size(0),
|
||||
default_url(NULL),
|
||||
default_url_size(0) {
|
||||
memcpy(init_data.get(), frame.key_id, frame.key_id_size);
|
||||
}
|
||||
|
||||
~LicenseRequestParameter() {
|
||||
}
|
||||
|
||||
scoped_array<uint8_t> init_data;
|
||||
int init_data_size;
|
||||
scoped_array<char> session_id;
|
||||
int session_id_size;
|
||||
scoped_array<uint8_t> key_request;
|
||||
int key_request_size;
|
||||
scoped_array<char> default_url;
|
||||
int default_url_size;
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(LicenseRequestParameter);
|
||||
};
|
||||
|
||||
// |encrypted_data| is encrypted from |plain_text| using |key|. |key_id| is
|
||||
// used to distinguish |key|.
|
||||
struct WebmEncryptedData {
|
||||
uint8 plain_text[32];
|
||||
int plain_text_size;
|
||||
uint8 key_id[32];
|
||||
int key_id_size;
|
||||
uint8 key[32];
|
||||
int key_size;
|
||||
uint8 encrypted_data[64];
|
||||
int encrypted_data_size;
|
||||
};
|
||||
|
||||
// Frames 0 & 1 are encrypted with the same key. Frame 2 is encrypted with a
|
||||
// different key. Frame 3 is unencrypted.
|
||||
const WebmEncryptedData kWebmEncryptedFrames[] = {
|
||||
{
|
||||
// plaintext
|
||||
"Original data.", 14,
|
||||
// key_id
|
||||
{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13
|
||||
}, 20,
|
||||
// key
|
||||
{ 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
|
||||
0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23
|
||||
}, 16,
|
||||
// encrypted_data
|
||||
{ 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xf0, 0xd1, 0x12, 0xd5, 0x24, 0x81, 0x96,
|
||||
0x55, 0x1b, 0x68, 0x9f, 0x38, 0x91, 0x85
|
||||
}, 23
|
||||
},
|
||||
{
|
||||
// plaintext
|
||||
"Changed Original data.", 22,
|
||||
// key_id
|
||||
{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13
|
||||
}, 20,
|
||||
// key
|
||||
{ 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
|
||||
0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23
|
||||
}, 16,
|
||||
// encrypted_data
|
||||
{ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x57, 0x66, 0xf4, 0x12, 0x1a, 0xed, 0xb5,
|
||||
0x79, 0x1c, 0x8e, 0x25, 0xd7, 0x17, 0xe7, 0x5e,
|
||||
0x16, 0xe3, 0x40, 0x08, 0x27, 0x11, 0xe9
|
||||
}, 31
|
||||
},
|
||||
{
|
||||
// plaintext
|
||||
"Original data.", 14,
|
||||
// key_id
|
||||
{ 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
|
||||
0x2c, 0x2d, 0x2e, 0x2f, 0x30
|
||||
}, 13,
|
||||
// key
|
||||
{ 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
|
||||
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40
|
||||
}, 16,
|
||||
// encrypted_data
|
||||
{ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x01, 0x9c, 0x71, 0x26, 0x57, 0x3e, 0x25, 0x37,
|
||||
0xf7, 0x31, 0x81, 0x19, 0x64, 0xce, 0xbc
|
||||
}, 23
|
||||
},
|
||||
{
|
||||
// plaintext
|
||||
"Changed Original data.", 22,
|
||||
// key_id
|
||||
{ 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
|
||||
0x2c, 0x2d, 0x2e, 0x2f, 0x30
|
||||
}, 13,
|
||||
// key
|
||||
{ 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
|
||||
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40
|
||||
}, 16,
|
||||
// encrypted_data
|
||||
{ 0x00, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64,
|
||||
0x20, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61,
|
||||
0x6c, 0x20, 0x64, 0x61, 0x74, 0x61, 0x2e
|
||||
}, 23
|
||||
}
|
||||
};
|
||||
|
||||
static const uint8 kWebmWrongSizedKey[] = { 0x20, 0x20 };
|
||||
|
||||
static const char kClearKeySystem[] = "org.w3.clearkey";
|
||||
static const char *kWidevineKeySystem = "com.widevine.alpha";
|
||||
static const char kKeyType[] = "any";
|
||||
|
||||
// All three ContentIdentification are supported but cenc_id only supports
|
||||
// one key for now.
|
||||
// Using key_id kCencTestRequest will return the content_key kCencTestRequest
|
||||
//
|
||||
static const char kCencTestRequest[] = "0123456789ABCDEF";
|
||||
static const char kCencTestContentKey[] = {
|
||||
0x16, 0x23, 0xa3, 0x16, 0x67, 0x6d, 0xb7, 0x70,
|
||||
0xfe, 0x78, 0xf6, 0x58, 0x42, 0xb8, 0x16, 0x3c
|
||||
};
|
||||
|
||||
// System Id of the Widevine DRM system for identification in pssh
|
||||
static const uint8 kWidevineSystemId[] = {
|
||||
0xED, 0xEF, 0x8B, 0xA9, 0x79, 0xD6, 0x4A, 0xCE,
|
||||
0xA3, 0xC8, 0x27, 0xDC, 0xD5, 0x1D, 0x21, 0xED,
|
||||
};
|
||||
|
||||
static std::string EncodeInt32(int i) {
|
||||
std::string s;
|
||||
s.resize(sizeof(int));
|
||||
memcpy(&*s.begin(), &i, sizeof(int));
|
||||
return s;
|
||||
}
|
||||
|
||||
// Generate PSSH blob from init data
|
||||
static std::string GeneratePSSHBlob(const uint8* init_data,
|
||||
int init_data_length) {
|
||||
std::string output;
|
||||
|
||||
// 4 byte size of the PSSH atom, inclusive
|
||||
int size = 4 + 4 + 4 + sizeof(kWidevineSystemId) + 4 + init_data_length;
|
||||
output.append(EncodeInt32(base::HostToNet32(size)));
|
||||
|
||||
// "pssh"
|
||||
output.append("pssh");
|
||||
|
||||
// 4 byte flags, value 0
|
||||
int flag = 0;
|
||||
output.append(EncodeInt32(base::HostToNet32(flag)));
|
||||
|
||||
// 16 byte system id
|
||||
output.append(reinterpret_cast<const char*>(kWidevineSystemId),
|
||||
sizeof(kWidevineSystemId));
|
||||
|
||||
// 4 byte size of PSSH data, exclusive
|
||||
output.append(EncodeInt32(base::HostToNet32(init_data_length)));
|
||||
|
||||
// pssh data
|
||||
output.append(reinterpret_cast<const char*>(init_data),
|
||||
init_data_length);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
}; // anonymous
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class WvCdmDecryptorTest : public testing::Test {
|
||||
|
||||
public:
|
||||
WvCdmDecryptorTest() {}
|
||||
|
||||
~WvCdmDecryptorTest() {}
|
||||
|
||||
protected:
|
||||
void GenerateKeyRequest(const uint8* key_id, int key_id_size,
|
||||
std::string& message_buffer) {
|
||||
std::string init_data = GeneratePSSHBlob(key_id, key_id_size);
|
||||
wvcdm::CdmResponseType res = decryptor_.GenerateKeyRequest(
|
||||
kWidevineKeySystem, init_data, &message_buffer, &session_id_string_);
|
||||
EXPECT_TRUE(res == wvcdm::KEY_MESSAGE);
|
||||
}
|
||||
|
||||
void PrepareForRenewalRequest(int i) {
|
||||
}
|
||||
|
||||
void GetRenewalMessage(std::string& message_buffer) {
|
||||
}
|
||||
|
||||
void GetFailedRenewalMessage(std::string& message_buffer) {
|
||||
}
|
||||
|
||||
public:
|
||||
void AddKeyAndExpectToSucceed(const uint8* key_id, int key_id_size,
|
||||
const uint8* key, int key_size) {
|
||||
std::string cma_key((const char*)key, key_size);
|
||||
std::string init_data = GeneratePSSHBlob(key_id, key_id_size);
|
||||
wvcdm::CdmResponseType res = decryptor_.AddKey(
|
||||
kWidevineKeySystem, init_data, cma_key, session_id_string_);
|
||||
EXPECT_TRUE(res == wvcdm::KEY_ADDED);
|
||||
}
|
||||
|
||||
void AddKeyAndExpectToFail(const uint8* key_id, int key_id_size,
|
||||
const uint8* key, int key_size) {
|
||||
std::string cma_key((const char*)key, key_size);
|
||||
std::string init_data = GeneratePSSHBlob(key_id, key_id_size);
|
||||
wvcdm::CdmResponseType res = decryptor_.AddKey(
|
||||
kWidevineKeySystem, init_data, cma_key, session_id_string_);
|
||||
EXPECT_TRUE(res == wvcdm::KEY_ADDED);
|
||||
}
|
||||
protected:
|
||||
|
||||
wvcdm::WvContentDecryptionModule decryptor_;
|
||||
std::string session_id_string_;
|
||||
};
|
||||
|
||||
TEST_F(WvCdmDecryptorTest, RenewalTest) {
|
||||
std::string response;
|
||||
std::string message;
|
||||
const WvCdmEncryptedFrameInfo& frame = kWvCdmEncryptedFrames[3];
|
||||
License::Policy policies;
|
||||
DCHECK(GetPolicy(frame.policy_type, &policies));
|
||||
|
||||
GenerateKeyRequest(frame.key_id, frame.key_id_size, message);
|
||||
response = GenerateLicenseResponse(message, kShortDuration);
|
||||
AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size,
|
||||
reinterpret_cast<const uint8*>(response.data()),
|
||||
response.size());
|
||||
MessageLoop::current()->RunUntilIdle();
|
||||
PrepareForRenewalRequest(1);
|
||||
sleep(policies.renewal_delay_seconds());
|
||||
MessageLoop::current()->RunUntilIdle();
|
||||
GetRenewalMessage(message);
|
||||
response = GenerateLicenseResponse(message, frame.policy_type);
|
||||
AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size,
|
||||
reinterpret_cast<const uint8*>(response.data()),
|
||||
response.size());
|
||||
}
|
||||
|
||||
TEST_F(WvCdmDecryptorTest, MultiRenewalTest) {
|
||||
std::string response;
|
||||
std::string message;
|
||||
const WvCdmEncryptedFrameInfo& frame = kWvCdmEncryptedFrames[3];
|
||||
License::Policy policies;
|
||||
DCHECK(GetPolicy(frame.policy_type, &policies));
|
||||
|
||||
GenerateKeyRequest(frame.key_id, frame.key_id_size, message);
|
||||
response = GenerateLicenseResponse(message, kShortDuration);
|
||||
AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size,
|
||||
reinterpret_cast<const uint8*>(response.data()),
|
||||
response.size());
|
||||
MessageLoop::current()->RunUntilIdle();
|
||||
PrepareForRenewalRequest(1);
|
||||
sleep(policies.renewal_delay_seconds());
|
||||
MessageLoop::current()->RunUntilIdle();
|
||||
GetRenewalMessage(message);
|
||||
response = GenerateLicenseResponse(message, frame.policy_type);
|
||||
AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size,
|
||||
reinterpret_cast<const uint8*>(response.data()),
|
||||
response.size());
|
||||
|
||||
PrepareForRenewalRequest(2);
|
||||
sleep(policies.renewal_delay_seconds());
|
||||
MessageLoop::current()->RunUntilIdle();
|
||||
GetRenewalMessage(message);
|
||||
}
|
||||
|
||||
TEST_F(WvCdmDecryptorTest, RenewalRetryTest_ExpectSuccess) {
|
||||
std::string response;
|
||||
std::string message;
|
||||
const WvCdmEncryptedFrameInfo& frame = kWvCdmEncryptedFrames[3];
|
||||
License::Policy policies;
|
||||
DCHECK(GetPolicy(frame.policy_type, &policies));
|
||||
|
||||
GenerateKeyRequest(frame.key_id, frame.key_id_size, message);
|
||||
response = GenerateLicenseResponse(message, kShortDuration);
|
||||
AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size,
|
||||
reinterpret_cast<const uint8*>(response.data()),
|
||||
response.size());
|
||||
MessageLoop::current()->RunUntilIdle();
|
||||
|
||||
int loop_seconds =
|
||||
policies.license_duration_seconds() - policies.renewal_delay_seconds();
|
||||
int loop_count = loop_seconds / policies.renewal_retry_interval_seconds();
|
||||
if (loop_seconds % policies.renewal_retry_interval_seconds())
|
||||
++loop_count;
|
||||
for (int i = 1; i <= loop_count; ++i) {
|
||||
PrepareForRenewalRequest(i);
|
||||
sleep(policies.renewal_delay_seconds());
|
||||
MessageLoop::current()->RunUntilIdle();
|
||||
GetRenewalMessage(message);
|
||||
}
|
||||
|
||||
response = GenerateLicenseResponse(message, frame.policy_type);
|
||||
AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size,
|
||||
reinterpret_cast<const uint8*>(response.data()),
|
||||
response.size());
|
||||
}
|
||||
|
||||
TEST_F(WvCdmDecryptorTest, RenewalRetryTest_ExpectLicenseExpiration) {
|
||||
std::string response;
|
||||
std::string message;
|
||||
const WvCdmEncryptedFrameInfo& frame = kWvCdmEncryptedFrames[3];
|
||||
License::Policy policies;
|
||||
DCHECK(GetPolicy(frame.policy_type, &policies));
|
||||
|
||||
GenerateKeyRequest(frame.key_id, frame.key_id_size, message);
|
||||
response = GenerateLicenseResponse(message, kShortDuration);
|
||||
AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size,
|
||||
reinterpret_cast<const uint8*>(response.data()),
|
||||
response.size());
|
||||
MessageLoop::current()->RunUntilIdle();
|
||||
|
||||
int loop_seconds =
|
||||
policies.license_duration_seconds() - policies.renewal_delay_seconds();
|
||||
int loop_count = loop_seconds / policies.renewal_retry_interval_seconds() + 1;
|
||||
if (loop_seconds % policies.renewal_retry_interval_seconds())
|
||||
++loop_count;
|
||||
|
||||
for (int i = 1; i <= loop_count; ++i) {
|
||||
PrepareForRenewalRequest(i);
|
||||
sleep(i > 1 ?
|
||||
policies.renewal_retry_interval_seconds() :
|
||||
policies.renewal_delay_seconds());
|
||||
MessageLoop::current()->RunUntilIdle();
|
||||
if (i < loop_count)
|
||||
GetRenewalMessage(message);
|
||||
}
|
||||
GetFailedRenewalMessage(message);
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
// TODO(rkuroiwa): Find where to put this main function just for Widevine CDM
|
||||
// unit tests.
|
||||
int main(int argc, char **argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
base::AtExitManager exit;
|
||||
MessageLoop ttr(MessageLoop::TYPE_IO);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
201
libwvdrmengine/cdm/core/test/wvcdm_test.cpp
Normal file
201
libwvdrmengine/cdm/core/test/wvcdm_test.cpp
Normal file
@@ -0,0 +1,201 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/at_exit.h"
|
||||
#include "net/url_request/url_request.h"
|
||||
#include "net/url_request/url_request_test_util.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
#include "string_conversions.h"
|
||||
#include "wv_content_decryption_module.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// Default license server to play server, can be configured using --server command line option
|
||||
std::string gGpLicenseServer =
|
||||
"https://jmt17.google.com/video-dev/license/GetCencLicense";
|
||||
std::string gYtLicenseServer =
|
||||
"https://www.youtube.com/api/drm/widevine?video_id=03681262dc412c06&source=YOUTUBE";
|
||||
|
||||
std::string gLicenseServer(gYtLicenseServer);
|
||||
|
||||
// Default key id (pssh), can be configured using --keyid command line option
|
||||
std::string gKeyID = "000000347073736800000000" // blob size and pssh
|
||||
"EDEF8BA979D64ACEA3C827DCD51D21ED00000014" // Widevine system id
|
||||
"08011210e02562e04cd55351b14b3d748d36ed8e"; // pssh data
|
||||
|
||||
// An invalid key id, expected to fail
|
||||
std::string kWrongKeyID = "000000347073736800000000" // blob size and psshb
|
||||
"EDEF8BA979D64ACEA3C827DCD51D21ED00000014" // Widevine system id
|
||||
"0901121094889920E8D6520098577DF8F2DD5546"; // pssh data
|
||||
|
||||
static const char kWidevineKeySystem[] = "com.widevine.alpha";
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace wvcdm_test {
|
||||
|
||||
class WvCdmDecryptorTest : public testing::Test {
|
||||
public:
|
||||
WvCdmDecryptorTest() {}
|
||||
~WvCdmDecryptorTest() {}
|
||||
|
||||
protected:
|
||||
void GenerateKeyRequest(const std::string& key_system,
|
||||
const std::string& init_data) {
|
||||
EXPECT_EQ(decryptor_.GenerateKeyRequest(key_system,
|
||||
init_data,
|
||||
&key_msg_,
|
||||
&session_id_), wvcdm::KEY_MESSAGE);
|
||||
}
|
||||
|
||||
void GenerateRenewalRequest(const std::string& key_system,
|
||||
const std::string& init_data) {
|
||||
EXPECT_EQ(decryptor_.GenerateRenewalRequest(key_system,
|
||||
init_data,
|
||||
session_id_, &key_msg_),
|
||||
wvcdm::KEY_MESSAGE);
|
||||
}
|
||||
|
||||
std::string GetKeyRequestResponse(const std::string& server_url,
|
||||
int expected_response) {
|
||||
net::TestDelegate d;
|
||||
net::TestNetworkDelegate network_delegate;
|
||||
net::TestURLRequestContext context(true);
|
||||
context.set_network_delegate(&network_delegate);
|
||||
scoped_ptr<net::HostResolver> resolver(
|
||||
net::HostResolver::CreateDefaultResolver(NULL));
|
||||
context.set_host_resolver(resolver.get());
|
||||
context.Init();
|
||||
net::URLRequest r(GURL(server_url), &d, &context);
|
||||
r.EnableChunkedUpload();
|
||||
r.set_method("POST");
|
||||
r.AppendChunkToUpload(key_msg_.data(), key_msg_.size(), true);
|
||||
r.Start();
|
||||
EXPECT_TRUE(r.is_pending());
|
||||
|
||||
MessageLoop::current()->Run();
|
||||
|
||||
std::string data = d.data_received();
|
||||
// Youtube server returns 400 for invalid message while play server returns
|
||||
// 500, so just test inequity here for invalid message
|
||||
if (expected_response == 200) {
|
||||
EXPECT_EQ(200, r.GetResponseCode()) << data;
|
||||
} else {
|
||||
EXPECT_NE(200, r.GetResponseCode()) << data;
|
||||
}
|
||||
EXPECT_EQ(net::URLRequestStatus::SUCCESS, r.status().status());
|
||||
EXPECT_TRUE(d.bytes_received() > 0);
|
||||
// Extract DRM message:
|
||||
// https://docs.google.com/a/google.com/document/d/1Xue3bgwv2qIAnuFIZ-HCcix43dvH2UxsOEA_8FCBO3I/edit#
|
||||
if (r.status().status() == net::URLRequestStatus::SUCCESS) {
|
||||
size_t pos = data.find("\r\n");
|
||||
if (pos != data.npos) data = data.substr(pos+2);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
void VerifyKeyRequestResponse(const std::string& server_url,
|
||||
std::string& init_data,
|
||||
bool is_renewal) {
|
||||
std::string resp = GetKeyRequestResponse(server_url, 200);
|
||||
|
||||
std::cout << "Message: " << wvcdm::b2a_hex(key_msg_) << std::endl;
|
||||
std::cout << "Response: " << wvcdm::b2a_hex(resp) << std::endl;
|
||||
|
||||
if (is_renewal) {
|
||||
EXPECT_EQ(decryptor_.RenewKey(kWidevineKeySystem,
|
||||
init_data, resp,
|
||||
session_id_), wvcdm::KEY_ADDED);
|
||||
}
|
||||
else {
|
||||
EXPECT_EQ(decryptor_.AddKey(kWidevineKeySystem,
|
||||
init_data, resp,
|
||||
session_id_), wvcdm::KEY_ADDED);
|
||||
}
|
||||
|
||||
std::cout << "back from AddKey" << std::endl;
|
||||
|
||||
}
|
||||
|
||||
wvcdm::WvContentDecryptionModule decryptor_;
|
||||
|
||||
std::string key_msg_;
|
||||
std::string session_id_;
|
||||
};
|
||||
|
||||
TEST_F(WvCdmDecryptorTest, BaseMessageTest)
|
||||
{
|
||||
GenerateKeyRequest(kWidevineKeySystem, gKeyID);
|
||||
|
||||
GetKeyRequestResponse(gLicenseServer, 200);
|
||||
}
|
||||
|
||||
TEST_F(WvCdmDecryptorTest, WrongMessageTest)
|
||||
{
|
||||
std::string wrong_message = wvcdm::a2b_hex(kWrongKeyID);
|
||||
|
||||
GenerateKeyRequest(kWidevineKeySystem, wrong_message);
|
||||
|
||||
GetKeyRequestResponse(gLicenseServer, 500);
|
||||
}
|
||||
|
||||
TEST_F(WvCdmDecryptorTest, NormalWebMDecryption) {
|
||||
GenerateKeyRequest(kWidevineKeySystem, gKeyID);
|
||||
|
||||
VerifyKeyRequestResponse(gLicenseServer, gKeyID, false);
|
||||
}
|
||||
|
||||
TEST_F(WvCdmDecryptorTest, LicenseRenewal) {
|
||||
GenerateKeyRequest(kWidevineKeySystem, gKeyID);
|
||||
|
||||
VerifyKeyRequestResponse(gLicenseServer, gKeyID, false);
|
||||
|
||||
GenerateRenewalRequest(kWidevineKeySystem, gKeyID);
|
||||
|
||||
VerifyKeyRequestResponse(gLicenseServer, gKeyID, true);
|
||||
}
|
||||
|
||||
} // namespace wvcdm_test
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
|
||||
std::string temp;
|
||||
std::string license_server(gLicenseServer);
|
||||
std::string key_id(gKeyID);
|
||||
for (int i=1; i<argc; i++) {
|
||||
temp.assign(argv[i]);
|
||||
if (temp.find("--server=") == 0) {
|
||||
gLicenseServer.assign(temp.substr(strlen("--server=")));
|
||||
}
|
||||
else if (temp.find("--keyid=") == 0) {
|
||||
gKeyID.assign(temp.substr(strlen("--keyid=")));
|
||||
}
|
||||
else {
|
||||
std::cout << "error: unknown option '" << argv[i] << "'" << std::endl;
|
||||
std::cout << "usage: wvcdm_test [options]" << std::endl << std::endl;
|
||||
std::cout << std::setw(30) << std::left << " --server=<server_url>";
|
||||
std::cout << "configure the license server url, please include http[s] in the url" << std::endl;
|
||||
std::cout << std::setw(30) << std::left << " ";
|
||||
std::cout << "default: " << license_server << std::endl;
|
||||
std::cout << std::setw(30) << std::left << " --keyid=<key_id>";
|
||||
std::cout << "configure the key id or pssh, in hex format" << std::endl;
|
||||
std::cout << std::setw(30) << std::left << " ";
|
||||
std::cout << "default: " << key_id << std::endl << std::endl;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
std::cout << "Server: " << gLicenseServer << std::endl;
|
||||
std::cout << "KeyID: " << gKeyID << std::endl << std::endl;
|
||||
|
||||
gKeyID = wvcdm::a2b_hex(gKeyID);
|
||||
|
||||
base::AtExitManager exit;
|
||||
MessageLoop ttr(MessageLoop::TYPE_IO);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
Reference in New Issue
Block a user