Source release v2.1.2-0-773 + third_party libs

Change-Id: Ia07608577b65b301c22a8ff4bf7f743c2d3f9274
This commit is contained in:
Joey Parrish
2014-06-10 13:36:59 -07:00
parent 557c42130a
commit aaa3c6192a
42 changed files with 2425 additions and 918 deletions

5
README
View File

@@ -1,5 +1,5 @@
README for Widevine CDM Partner Kit v2.1 README for Widevine CDM Partner Kit v2.1
Date: 5/30/2014 Date: 6/03/2014
This document provides additional details on installation, system This document provides additional details on installation, system
setup, building, and testing components of the Widevine Content setup, building, and testing components of the Widevine Content
@@ -223,11 +223,12 @@ above the test directory. The test-releated GYP targets will have "test" or
Here's a current list of GYP files and test-related targets: Here's a current list of GYP files and test-related targets:
File - Targets Purpose File - Targets Purpose
cdm/cdm_api_internal.gyp cdm/cdm.gyp
license_protocol Create protobuf sources for license protocol license_protocol Create protobuf sources for license protocol
device_files Create protobuf sources for license storage device_files Create protobuf sources for license storage
wvcdm_sysdep Build system-dependent layer of CDM wvcdm_sysdep Build system-dependent layer of CDM
wvcdm_shared Build CDM shared library wvcdm_shared Build CDM shared library
cdm/cdm_unittests.gyp
wvcdm_shared_api_unittest Build CDM unit tests wvcdm_shared_api_unittest Build CDM unit tests
cdm/test/gtest.gyp cdm/test/gtest.gyp
gtest Provides gtest modules for unit tests gtest Provides gtest modules for unit tests

View File

@@ -11,19 +11,20 @@ import gyp
cdm_top = os.path.abspath(os.path.dirname(__file__)) cdm_top = os.path.abspath(os.path.dirname(__file__))
parser = argparse.ArgumentParser()
parser.add_argument('platform', def IsNinjaInstalled():
help='The platform configuration to use (x86-64, ...). ' """Determine if ninja is installed."""
'Should be one of the folder names inside platforms/') try:
parser.add_argument('-r', '--release', bit_bucket = open(os.devnull, 'w')
dest='build_config', default='Debug', subprocess.check_call(['ninja', '--version'], stdout=bit_bucket,
action='store_const', const='Release', stderr=bit_bucket)
help='Builds a release build (equivalent to -c Release)') return True
parser.add_argument('-c', '--config', except subprocess.CalledProcessError:
dest='build_config', default='Debug', # Error code returned, probably not the ninja we're looking for.
help='Select a build config (Debug, Release)') return False
parser.add_argument('-g', '--generator', default='make', except OSError:
help='Which build system to use (make, ninja, ...)') # No such command found.
return False
def VerboseSubprocess(args): def VerboseSubprocess(args):
@@ -93,12 +94,33 @@ def ImportPlatform(name, gyp_args):
def main(args): def main(args):
if IsNinjaInstalled():
default_generator = 'ninja'
else:
default_generator = 'make'
parser = argparse.ArgumentParser()
parser.add_argument('platform',
help='The platform configuration to use (x86-64, ...). '
'Should be one of the folder names inside platforms/')
parser.add_argument('-r', '--release',
dest='build_config', default='Debug',
action='store_const', const='Release',
help='Builds a release build (equivalent to -c Release)')
parser.add_argument('-c', '--config',
dest='build_config', default='Debug',
help='Select a build config (Debug, Release). '
'Defaults to Debug.')
parser.add_argument('-g', '--generator', default=default_generator,
help='Which build system to use (make, ninja, ...). '
'Defaults to ninja when available, make otherwise.')
options = parser.parse_args(args) options = parser.parse_args(args)
gyp_args = [ gyp_args = [
'--format=%s' % options.generator, '--format=%s' % options.generator,
'--depth=%s' % cdm_top, '--depth=%s' % cdm_top,
'%s/cdm/cdm_api_external.gyp' % cdm_top, '%s/cdm/cdm_unittests.gyp' % cdm_top,
] ]
output_path = ImportPlatform(options.platform, gyp_args) output_path = ImportPlatform(options.platform, gyp_args)
@@ -116,4 +138,4 @@ def main(args):
if __name__ == '__main__': if __name__ == '__main__':
main(sys.argv[1:]) sys.exit(main(sys.argv[1:]))

View File

@@ -48,13 +48,13 @@
], ],
}, },
{ {
'target_name': '<(cdm_target_name)', 'target_name': 'wvcdm_static',
'type': '<(cdm_target_type)', 'type': 'static_library',
'defines': ['CDM_IMPLEMENTATION'], 'defines': ['CDM_IMPLEMENTATION'],
'dependencies': [ 'dependencies': [
'device_files',
'license_protocol', 'license_protocol',
'wvcdm_sysdep', 'wvcdm_sysdep',
'device_files',
'<(oemcrypto_target)', '<(oemcrypto_target)',
], ],
'include_dirs': [ 'include_dirs': [
@@ -82,9 +82,36 @@
'../core/src/privacy_crypto.cpp', '../core/src/privacy_crypto.cpp',
'../core/src/properties.cpp', '../core/src/properties.cpp',
], ],
'conditions': [
['disable_privacy_crypto=="true"', {
'sources!': [ # exclude
'../core/src/privacy_crypto.cpp',
],
'sources': [ # include
'../core/src/privacy_crypto_dummy.cpp',
],
}],
],
'direct_dependencies': [ 'direct_dependencies': [
'device_files',
'license_protocol', 'license_protocol',
], ],
'conditions': [
['use_system_protobuf=="true"', {
'direct_dependent_settings': {
'libraries': [
'-lprotobuf',
],
},
}],
],
},
{
'target_name': 'wvcdm_shared',
'type': 'shared_library',
'dependencies': [
'wvcdm_static',
],
}, },
], ],
} }

View File

@@ -26,10 +26,9 @@
'-lssl', '-lssl',
'-lcrypto', '-lcrypto',
'-lpthread', '-lpthread',
'-lprotobuf',
], ],
'dependencies': [ 'dependencies': [
'cdm_api_internal.gyp:<(cdm_target_name)', 'cdm.gyp:wvcdm_shared',
'test/gmock.gyp:gmock', 'test/gmock.gyp:gmock',
'test/gmock.gyp:gmock_main', 'test/gmock.gyp:gmock_main',
'test/gtest.gyp:gtest', 'test/gtest.gyp:gtest',

View File

@@ -14,6 +14,9 @@ typedef __int64 int64_t;
#include <stdint.h> #include <stdint.h>
#endif #endif
#include <string>
#include <vector>
// Define CDM_EXPORT so that functionality implemented by the CDM module // Define CDM_EXPORT so that functionality implemented by the CDM module
// can be exported to consumers. // can be exported to consumers.
#if defined(WIN32) #if defined(WIN32)

View File

@@ -1,3 +1,3 @@
// Widevine CDM Kit Version // Widevine CDM Kit Version
#define WV_CDM_VERSION "v2.1.1-0-738" #define WV_CDM_VERSION "v2.1.2-0-773"

View File

@@ -97,7 +97,7 @@ cdm::Status WvContentDecryptionModule::GenerateKeyRequest(
std::string security_level; std::string security_level;
std::string privacy_mode; std::string privacy_mode;
kVectorBytes service_certificate; VectorBytes service_certificate;
host_->GetPlatformString("SecurityLevel", &security_level); host_->GetPlatformString("SecurityLevel", &security_level);
host_->GetPlatformString("PrivacyOn", &privacy_mode); host_->GetPlatformString("PrivacyOn", &privacy_mode);
@@ -353,10 +353,22 @@ cdm::Status WvContentDecryptionModule::DoSubsampleDecrypt(
CdmSessionId session_id; // it's empty but cdm_engine will locate via key_id. CdmSessionId session_id; // it's empty but cdm_engine will locate via key_id.
CdmResponseType status = NO_ERROR; CdmResponseType status = NO_ERROR;
uint8_t* output_buffer = decrypted_block
? reinterpret_cast<uint8_t*>( size_t output_size = 0;
decrypted_block->DecryptedBuffer()->Data()) for (int i = 0; i < encrypted_buffer.num_subsamples; ++i) {
: NULL; const cdm::SubsampleEntry& subsample = encrypted_buffer.subsamples[i];
output_size += subsample.cipher_bytes + subsample.clear_bytes;
}
parameters.decrypt_buffer_length = output_size;
if (decrypted_block) {
cdm::Buffer* buffer = host_->Allocate(output_size);
decrypted_block->SetDecryptedBuffer(buffer);
parameters.decrypt_buffer = buffer->Data();
} else {
parameters.decrypt_buffer = NULL;
}
size_t offset = 0; size_t offset = 0;
size_t encrypted_offset = 0; size_t encrypted_offset = 0;
uint32_t block_ctr = 0; uint32_t block_ctr = 0;
@@ -377,13 +389,10 @@ cdm::Status WvContentDecryptionModule::DoSubsampleDecrypt(
block_ctr = counter; block_ctr = counter;
} }
parameters.encrypt_buffer = &encrypted_buffer.data[encrypted_buffer parameters.encrypt_buffer =
.data_offset + offset]; &encrypted_buffer.data[encrypted_buffer.data_offset + offset];
if (output_buffer) parameters.decrypt_buffer_offset = offset;
parameters.decrypt_buffer = &output_buffer[offset];
parameters.encrypt_length = bytes; parameters.encrypt_length = bytes;
parameters.decrypt_buffer_length = encrypted_buffer.data_size - offset;
parameters.block_offset = encrypted_offset % kIvSize; parameters.block_offset = encrypted_offset % kIvSize;
offset += bytes; offset += bytes;

View File

@@ -47,8 +47,8 @@ double GetCurrentTestTime() {
return tv.tv_sec; return tv.tv_sec;
} }
using wvcdm::kStringPairs; using wvcdm::StringPairs;
using wvcdm::kVectorPairs; using wvcdm::VectorPairs;
// These classes below are naive implementation of the abstract classes defined // These classes below are naive implementation of the abstract classes defined
// in the CDM interface (content_decryptiom_module.h), which are used for tests // in the CDM interface (content_decryptiom_module.h), which are used for tests
@@ -357,8 +357,8 @@ class TestHost : public cdm::Host, public IHostTime {
cdm::ContentDecryptionModule* host_cdm_ptr_; cdm::ContentDecryptionModule* host_cdm_ptr_;
// These are containers for the platform sharing data. // These are containers for the platform sharing data.
std::set<kStringPairs> platform_strings_set_; std::set<StringPairs> platform_strings_set_;
std::set<kVectorPairs> platform_vectors_set_; std::set<VectorPairs> platform_vectors_set_;
TestHostFile test_host_file_; TestHostFile test_host_file_;
void LoadPersistentValues(); void LoadPersistentValues();
SimplePThread simple_thread_; SimplePThread simple_thread_;
@@ -473,12 +473,12 @@ void TestHost::GetPrivateData(int32_t* instance,
int TestHost::GetPlatformString(const std::string& name, int TestHost::GetPlatformString(const std::string& name,
std::string* value) { std::string* value) {
std::set<kStringPairs>::iterator it; std::set<StringPairs>::iterator it;
for (it = platform_strings_set_.begin(); for (it = platform_strings_set_.begin();
it != platform_strings_set_.end(); ++it) { it != platform_strings_set_.end(); ++it) {
kStringPairs sp = *it; StringPairs sp = *it;
std::string x = sp.first; std::string x = sp.first;
std::string y = sp.second; std::string y = sp.second;
if (x == name) { if (x == name) {
@@ -492,14 +492,14 @@ int TestHost::GetPlatformString(const std::string& name,
int TestHost::SetPlatformString(const std::string& name, int TestHost::SetPlatformString(const std::string& name,
const std::string& value) { const std::string& value) {
kStringPairs sp(name, value); StringPairs sp(name, value);
platform_strings_set_.insert(sp); platform_strings_set_.insert(sp);
return 1; return 1;
} }
int TestHost::PersistPlatformString(const std::string& name, int TestHost::PersistPlatformString(const std::string& name,
const std::string& value) { const std::string& value) {
kStringPairs sp(name, value); StringPairs sp(name, value);
// Write the pairs to a file such that they can be retrieved by // Write the pairs to a file such that they can be retrieved by
// GetPlatformString() even after a power cycle. // GetPlatformString() even after a power cycle.
return 1; return 1;
@@ -508,17 +508,17 @@ int TestHost::PersistPlatformString(const std::string& name,
int TestHost::SetPlatformByteArray(const std::string& name, int TestHost::SetPlatformByteArray(const std::string& name,
const std::vector<uint8_t>& value) { const std::vector<uint8_t>& value) {
// A zero value pair is an erase only. // A zero value pair is an erase only.
std::set<kVectorPairs>::iterator it; std::set<VectorPairs>::iterator it;
for (it = platform_vectors_set_.begin(); for (it = platform_vectors_set_.begin();
it != platform_vectors_set_.end(); ++it) { it != platform_vectors_set_.end(); ++it) {
kVectorPairs vp = *it; VectorPairs vp = *it;
if (vp.first == name) { if (vp.first == name) {
platform_vectors_set_.erase(vp); platform_vectors_set_.erase(vp);
} }
} }
if (value.size() > 0) { if (value.size() > 0) {
kVectorPairs vp(name, value); VectorPairs vp(name, value);
platform_vectors_set_.insert(vp); platform_vectors_set_.insert(vp);
} }
@@ -540,7 +540,7 @@ int TestHost::PersistPlatformByteArray(const std::string& name,
memcpy(&string_buffer[0], &value[0], value.size()); memcpy(&string_buffer[0], &value[0], value.size());
test_host_file_.Write(&string_buffer[0], value.size()); test_host_file_.Write(&string_buffer[0], value.size());
} }
kVectorPairs vp(name, value); VectorPairs vp(name, value);
SetPlatformByteArray(name, value); SetPlatformByteArray(name, value);
// open file and persist this so that it can be retrieved as a string/vector // open file and persist this so that it can be retrieved as a string/vector
@@ -550,11 +550,11 @@ int TestHost::PersistPlatformByteArray(const std::string& name,
int TestHost::GetPlatformByteArray(const std::string& name, int TestHost::GetPlatformByteArray(const std::string& name,
std::vector<uint8_t>* value) { std::vector<uint8_t>* value) {
std::set<kVectorPairs>::iterator it; std::set<VectorPairs>::iterator it;
for (it = platform_vectors_set_.begin(); for (it = platform_vectors_set_.begin();
it != platform_vectors_set_.end(); ++it) { it != platform_vectors_set_.end(); ++it) {
kVectorPairs vp = *it; VectorPairs vp = *it;
std::string x = vp.first; std::string x = vp.first;
if (x == name) { if (x == name) {
*value = vp.second; *value = vp.second;
@@ -693,7 +693,6 @@ std::string g_client_auth;
wvcdm::KeyId g_key_id; wvcdm::KeyId g_key_id;
wvcdm::CdmKeySystem g_key_system; wvcdm::CdmKeySystem g_key_system;
std::string g_license_server; std::string g_license_server;
std::string g_port;
wvcdm::KeyId g_wrong_key_id; wvcdm::KeyId g_wrong_key_id;
int g_use_full_path = 0; // cannot use boolean in getopt_long int g_use_full_path = 0; // cannot use boolean in getopt_long
@@ -735,7 +734,7 @@ class WvCdmApiTest : public testing::Test {
// emulate pulling a certificate out of persistent storage. // emulate pulling a certificate out of persistent storage.
static const size_t kPrivacyCertSize = 256; static const size_t kPrivacyCertSize = 256;
kVectorBytes cert(kPrivacyCertSize); VectorBytes cert(kPrivacyCertSize);
for (size_t i = 0; i < cert.size(); i++) { for (size_t i = 0; i < cert.size(); i++) {
cert[i] = i; cert[i] = i;
} }
@@ -756,18 +755,19 @@ class WvCdmApiTest : public testing::Test {
if (status == cdm::kNeedsDeviceCertificate) { if (status == cdm::kNeedsDeviceCertificate) {
std::string provisioning_server_url; std::string provisioning_server_url;
std::string prov_request; std::string prov_request;
if (cdm_->GetProvisioningRequest(&prov_request, status = cdm_->GetProvisioningRequest(&prov_request,
&provisioning_server_url) == cdm::kSuccess) { &provisioning_server_url);
if (status == cdm::kSuccess) {
UrlRequest url_request(provisioning_server_url, UrlRequest url_request(provisioning_server_url);
kDefaultHttpsPort, true, true);
url_request.PostCertRequestInQueryString(prov_request); url_request.PostCertRequestInQueryString(prov_request);
std::string message;
int resp_bytes = url_request.GetResponse(&message);
if (resp_bytes) { std::string message;
if (cdm_->HandleProvisioningResponse(message) == cdm::kSuccess) { bool ok = url_request.GetResponse(&message);
EXPECT_TRUE(ok);
if (ok) {
status = cdm_->HandleProvisioningResponse(message);
if (status == cdm::kSuccess) {
status = cdm_->GenerateKeyRequest(NULL, 0, status = cdm_->GenerateKeyRequest(NULL, 0,
(const uint8_t*)init_data.data(), init_data.length()); (const uint8_t*)init_data.data(), init_data.length());
} }
@@ -782,7 +782,7 @@ class WvCdmApiTest : public testing::Test {
const std::string& client_auth, const std::string& client_auth,
int expected_response) { int expected_response) {
// Use secure connection and chunk transfer coding. // Use secure connection and chunk transfer coding.
UrlRequest url_request(server_url + client_auth, g_port, true, true); UrlRequest url_request(server_url + client_auth);
if (!url_request.is_connected()) { if (!url_request.is_connected()) {
return ""; return "";
} }
@@ -887,20 +887,12 @@ class WvCdmApiTest : public testing::Test {
buf.num_subsamples = 1; buf.num_subsamples = 1;
buf.timestamp = 10; buf.timestamp = 10;
TestBuffer* out_buf =
dynamic_cast<TestBuffer*>(host_->Allocate(buf.data_size));
memset(out_buf->Data(), 0, buf.data_size);
TestDecryptedBlock output; TestDecryptedBlock output;
output.SetDecryptedBuffer(out_buf); cdm::Status status = cdm_->Decrypt(buf, &output);
cdm::Status status;
status = cdm_->Decrypt(buf, &output);
EXPECT_EQ(cdm::kSuccess, status); EXPECT_EQ(cdm::kSuccess, status);
EXPECT_EQ(0, memcmp(output.DecryptedBuffer()->Data(), &data.decrypt_data[0], EXPECT_EQ(0, memcmp(output.DecryptedBuffer()->Data(), &data.decrypt_data[0],
buf.data_size)); buf.data_size));
LOGI("Verbose Acknowledgement of Test Pass!!!\n");
} }
// Level 1 / Level 2 payload comes back in the cpu memory as cleartext. // Level 1 / Level 2 payload comes back in the cpu memory as cleartext.
@@ -976,22 +968,14 @@ class WvCdmApiTest : public testing::Test {
buf.num_subsamples = sub.size(); buf.num_subsamples = sub.size();
buf.timestamp = 10; buf.timestamp = 10;
TestBuffer* out_buf = dynamic_cast<TestBuffer*>(host_->Allocate(
buf.data_size));
memset(out_buf->Data(), 0, buf.data_size);
TestDecryptedBlock output; TestDecryptedBlock output;
output.SetDecryptedBuffer(out_buf); cdm::Status status = cdm_->Decrypt(buf, &output);
cdm::Status status;
status = cdm_->Decrypt(buf, &output);
EXPECT_EQ(cdm::kSuccess, status); EXPECT_EQ(cdm::kSuccess, status);
EXPECT_EQ( EXPECT_EQ(
0, 0,
memcmp(output.DecryptedBuffer()->Data(), &data.decrypt_data[0], memcmp(output.DecryptedBuffer()->Data(), &data.decrypt_data[0],
buf.data_size)); buf.data_size));
LOGI("Verbose Acknowledgement of Test Pass!!!\n");
} }
void DecryptClearSubsampleTestWithMissingSubsampleInfo() { void DecryptClearSubsampleTestWithMissingSubsampleInfo() {
@@ -1064,15 +1048,9 @@ class WvCdmApiTest : public testing::Test {
//buf.num_subsamples = sub.size(); //buf.num_subsamples = sub.size();
buf.timestamp = 10; buf.timestamp = 10;
TestBuffer* out_buf =
dynamic_cast<TestBuffer*>(host_->Allocate(buf.data_size));
memset(out_buf->Data(), 0, buf.data_size);
TestDecryptedBlock output; TestDecryptedBlock output;
output.SetDecryptedBuffer(out_buf);
cdm::Status status; cdm::Status status = cdm_->Decrypt(buf, &output);
status = cdm_->Decrypt(buf, &output);
EXPECT_EQ(cdm::kDecryptError, status); EXPECT_EQ(cdm::kDecryptError, status);
buf.subsamples = &sub[0]; buf.subsamples = &sub[0];
@@ -1088,7 +1066,6 @@ class WvCdmApiTest : public testing::Test {
buf.subsamples = NULL; buf.subsamples = NULL;
status = cdm_->Decrypt(buf, &output); status = cdm_->Decrypt(buf, &output);
EXPECT_EQ(cdm::kDecryptError, status); EXPECT_EQ(cdm::kDecryptError, status);
LOGI("Verbose Acknowledgement of Test Pass!!!\n");
} }
// Level 1 passes encrypted payload straight through. By calling the // Level 1 passes encrypted payload straight through. By calling the
@@ -1157,8 +1134,6 @@ class WvCdmApiTest : public testing::Test {
EXPECT_EQ(cdm::kSuccess, status); EXPECT_EQ(cdm::kSuccess, status);
status = cdm_->DecryptDecodeAndRenderFrame(buf); status = cdm_->DecryptDecodeAndRenderFrame(buf);
EXPECT_EQ(cdm::kSuccess, status); EXPECT_EQ(cdm::kSuccess, status);
LOGI("Verbose Acknowledgement of Level 1 Test Pass!!!\n");
} }
// Level 1 passes encrypted payload straight through. By calling the // Level 1 passes encrypted payload straight through. By calling the
@@ -1240,8 +1215,6 @@ class WvCdmApiTest : public testing::Test {
EXPECT_EQ(cdm::kSuccess, status); EXPECT_EQ(cdm::kSuccess, status);
status = cdm_->DecryptDecodeAndRenderFrame(buf); status = cdm_->DecryptDecodeAndRenderFrame(buf);
EXPECT_EQ(cdm::kSuccess, status); EXPECT_EQ(cdm::kSuccess, status);
LOGI("Verbose Acknowledgement of Level 1 Test Pass!!!\n");
} }
void WithMissingSubsampleInfoTest() { void WithMissingSubsampleInfoTest() {
@@ -1335,8 +1308,6 @@ class WvCdmApiTest : public testing::Test {
EXPECT_EQ(cdm::kDecryptError, status); EXPECT_EQ(cdm::kDecryptError, status);
status = cdm_->DecryptDecodeAndRenderFrame(buf); status = cdm_->DecryptDecodeAndRenderFrame(buf);
EXPECT_EQ(cdm::kDecryptError, status); EXPECT_EQ(cdm::kDecryptError, status);
LOGI("Verbose Acknowledgement of Level 1 Test Pass!!!\n");
} }
void TimerTest(void* session_id) { host_->SetTimer(1000, session_id); } void TimerTest(void* session_id) { host_->SetTimer(1000, session_id); }
@@ -1505,14 +1476,12 @@ int main(int argc, char** argv) {
// The following variables are configurable through command line options. // The following variables are configurable through command line options.
g_license_server.assign(config.license_server()); g_license_server.assign(config.license_server());
g_key_id.assign(config.key_id()); g_key_id.assign(config.key_id());
g_port.assign(config.port());
std::string license_server(g_license_server); std::string license_server(g_license_server);
int show_usage = 0; int show_usage = 0;
static const struct option long_options[] = { static const struct option long_options[] = {
{"use_full_path", no_argument, &g_use_full_path, 0}, {"use_full_path", no_argument, &g_use_full_path, 0},
{"keyid", required_argument, NULL, 'k'}, {"keyid", required_argument, NULL, 'k'},
{"port", required_argument, NULL, 'p'},
{"server", required_argument, NULL, 's'}, {"server", required_argument, NULL, 's'},
{"vmodule", required_argument, NULL, 0}, {"vmodule", required_argument, NULL, 0},
{"v", required_argument, NULL, 0}, {"v", required_argument, NULL, 0},
@@ -1528,11 +1497,6 @@ int main(int argc, char** argv) {
g_key_id.assign(optarg); g_key_id.assign(optarg);
break; break;
} }
case 'p': {
g_port.clear();
g_port.assign(optarg);
break;
}
case 's': { case 's': {
g_license_server.clear(); g_license_server.clear();
g_license_server.assign(optarg); g_license_server.assign(optarg);
@@ -1559,11 +1523,6 @@ int main(int argc, char** argv) {
std::cout << " e.g. adb shell '" << argv[0] << " --server=\"url\"'" std::cout << " e.g. adb shell '" << argv[0] << " --server=\"url\"'"
<< std::endl << std::endl; << 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 << std::setw(30) << std::left << " --server=<server_url>";
std::cout std::cout
<< "configure the license server url, please include http[s] in the url" << "configure the license server url, please include http[s] in the url"
@@ -1584,12 +1543,10 @@ int main(int argc, char** argv) {
std::cout << std::endl; std::cout << std::endl;
std::cout << "Server: " << g_license_server << 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; std::cout << "KeyID: " << g_key_id << std::endl << std::endl;
g_key_id = wvcdm::a2bs_hex(g_key_id); g_key_id = wvcdm::a2bs_hex(g_key_id);
config.set_license_server(g_license_server); config.set_license_server(g_license_server);
config.set_port(g_port);
config.set_key_id(g_key_id); config.set_key_id(g_key_id);
return RUN_ALL_TESTS(); return RUN_ALL_TESTS();

View File

@@ -88,10 +88,11 @@ class CdmEngine {
std::string* cert, std::string* cert,
std::string* wrapped_key); std::string* wrapped_key);
// Secure stop related methods CdmResponseType Unprovision(CdmSecurityLevel security_level);
CdmResponseType GetSecureStops(CdmSecureStops* secure_stops);
CdmResponseType ReleaseSecureStops( // Usage related methods for streaming licenses
const CdmSecureStopReleaseMessage& message); CdmResponseType GetUsageInfo(CdmUsageInfo* usage_info);
CdmResponseType ReleaseUsageInfo(const CdmUsageInfoReleaseMessage& message);
// Decryption and key related methods // Decryption and key related methods
// Accept encrypted buffer and return decrypted data. // Accept encrypted buffer and return decrypted data.
@@ -124,6 +125,9 @@ class CdmEngine {
CdmReleaseKeySetMap release_key_sets_; CdmReleaseKeySetMap release_key_sets_;
CertificateProvisioning cert_provisioning_; CertificateProvisioning cert_provisioning_;
SecurityLevel cert_provisioning_requested_security_level_; SecurityLevel cert_provisioning_requested_security_level_;
CdmSession* usage_session_;
int64_t last_usage_information_update_time;
CORE_DISALLOW_COPY_AND_ASSIGN(CdmEngine); CORE_DISALLOW_COPY_AND_ASSIGN(CdmEngine);
}; };

View File

@@ -28,6 +28,8 @@ class CdmSession {
CdmResponseType RestoreOfflineSession(const CdmKeySetId& key_set_id, CdmResponseType RestoreOfflineSession(const CdmKeySetId& key_set_id,
const CdmLicenseType license_type); const CdmLicenseType license_type);
CdmResponseType RestoreUsageSession(const CdmKeyMessage& key_request,
const CdmKeyResponse& key_response);
void set_key_system(const CdmKeySystem& ksystem) { key_system_ = ksystem; } void set_key_system(const CdmKeySystem& ksystem) { key_system_ = ksystem; }
const CdmKeySystem& key_system() { return key_system_; } const CdmKeySystem& key_system() { return key_system_; }
@@ -86,6 +88,12 @@ class CdmSession {
void OnKeyReleaseEvent(const CdmKeySetId& key_set_id); void OnKeyReleaseEvent(const CdmKeySetId& key_set_id);
SecurityLevel GetRequestedSecurityLevel(); SecurityLevel GetRequestedSecurityLevel();
CdmSecurityLevel GetSecurityLevel();
CdmResponseType UpdateUsageInformation();
bool is_usage_update_needed() { return is_usage_update_needed_; }
void reset_is_usage_update_needed() { is_usage_update_needed_ = false; }
private: private:
@@ -93,7 +101,9 @@ class CdmSession {
CdmSessionId GenerateSessionId(); CdmSessionId GenerateSessionId();
bool GenerateKeySetId(CdmKeySetId* key_set_id); bool GenerateKeySetId(CdmKeySetId* key_set_id);
CdmResponseType StoreLicense();
bool StoreLicense(DeviceFiles::LicenseState state); bool StoreLicense(DeviceFiles::LicenseState state);
bool DeleteLicense();
// instance variables // instance variables
const CdmSessionId session_id_; const CdmSessionId session_id_;
@@ -103,13 +113,16 @@ class CdmSession {
PolicyEngine policy_engine_; PolicyEngine policy_engine_;
bool license_received_; bool license_received_;
bool reinitialize_session_; bool reinitialize_session_;
bool is_offline_;
bool is_release_;
bool is_usage_update_needed_;
CdmLicenseType license_type_; // information useful for offline and usage scenarios
CdmKeyMessage key_request_;
CdmKeyResponse key_response_;
// license type offline related information // license type offline related information
CdmInitData offline_init_data_; CdmInitData offline_init_data_;
CdmKeyMessage offline_key_request_;
CdmKeyResponse offline_key_response_;
CdmKeyMessage offline_key_renewal_request_; CdmKeyMessage offline_key_renewal_request_;
CdmKeyResponse offline_key_renewal_response_; CdmKeyResponse offline_key_renewal_response_;
std::string offline_release_server_url_; std::string offline_release_server_url_;

View File

@@ -45,7 +45,8 @@ class CryptoSession {
const std::string& signature, const std::string& signature,
const std::string& mac_key_iv, const std::string& mac_key_iv,
const std::string& mac_key, const std::string& mac_key,
int num_keys, const CryptoKey* key_array); const std::vector<CryptoKey>& key_array,
const std::string& provider_session_token);
bool LoadCertificatePrivateKey(std::string& wrapped_key); bool LoadCertificatePrivateKey(std::string& wrapped_key);
bool RefreshKeys(const std::string& message, const std::string& signature, bool RefreshKeys(const std::string& message, const std::string& signature,
int num_keys, const CryptoKey* key_array); int num_keys, const CryptoKey* key_array);
@@ -63,6 +64,15 @@ class CryptoSession {
// Media data path // Media data path
CdmResponseType Decrypt(const CdmDecryptionParameters& parameters); CdmResponseType Decrypt(const CdmDecryptionParameters& parameters);
CdmResponseType UpdateUsageInformation();
CdmResponseType GenerateUsageReport(
const std::string& provider_session_token,
std::string* usage_report);
CdmResponseType ReleaseUsageInformation(
const std::string& message,
const std::string& signature,
const std::string& provider_session_token);
bool GetRandom(size_t data_length, uint8_t* random_data); bool GetRandom(size_t data_length, uint8_t* random_data);
private: private:

View File

@@ -5,6 +5,10 @@
#include "wv_cdm_types.h" #include "wv_cdm_types.h"
#if defined(UNIT_TEST)
#include <gtest/gtest_prod.h>
#endif
namespace wvcdm { namespace wvcdm {
class File; class File;
@@ -18,10 +22,10 @@ class DeviceFiles {
} LicenseState; } LicenseState;
DeviceFiles(): file_(NULL), security_level_(kSecurityLevelUninitialized), DeviceFiles(): file_(NULL), security_level_(kSecurityLevelUninitialized),
initialized_(false) {} initialized_(false), test_file_(false) {}
virtual ~DeviceFiles() {} virtual ~DeviceFiles();
virtual bool Init(const File* handle, CdmSecurityLevel security_level); virtual bool Init(CdmSecurityLevel security_level);
virtual bool StoreCertificate(const std::string& certificate, virtual bool StoreCertificate(const std::string& certificate,
const std::string& wrapped_private_key); const std::string& wrapped_private_key);
@@ -49,25 +53,52 @@ class DeviceFiles {
virtual bool DeleteAllLicenses(); virtual bool DeleteAllLicenses();
virtual bool LicenseExists(const std::string& key_set_id); virtual bool LicenseExists(const std::string& key_set_id);
// For testing only virtual bool StoreUsageInfo(const std::string& provider_session_token,
static std::string GetCertificateFileName(); const CdmKeyMessage& key_request,
static std::string GetLicenseFileNameExtension(); const CdmKeyResponse& key_response);
virtual bool DeleteUsageInfo(const std::string& provider_session_token);
protected: virtual bool DeleteUsageInfo();
bool Hash(const std::string& data, std::string* hash); virtual bool RetrieveUsageInfo(
bool StoreFile(const char* name, const std::string& data); std::vector<std::pair<CdmKeyMessage, CdmKeyResponse> >* usage_info);
bool RetrieveFile(const char* name, std::string* data);
private: private:
bool StoreFile(const char* name, const std::string& serialized_file);
bool RetrieveFile(const char* name, std::string* serialized_file);
// Certificate and offline licenses are now stored in security // Certificate and offline licenses are now stored in security
// level specific directories. In an earlier version they were // level specific directories. In an earlier version they were
// stored in a common directory and need to be copied over. // stored in a common directory and need to be copied over.
virtual void SecurityLevelPathBackwardCompatibility(); virtual void SecurityLevelPathBackwardCompatibility();
// For testing only:
static std::string GetCertificateFileName();
static std::string GetLicenseFileNameExtension();
static std::string GetUsageInfoFileName();
void SetTestFile(File* file);
#if defined(UNIT_TEST)
FRIEND_TEST(DeviceFilesSecurityLevelTest, SecurityLevel);
FRIEND_TEST(DeviceFilesStoreTest, StoreCertificate);
FRIEND_TEST(DeviceFilesStoreTest, StoreLicense);
FRIEND_TEST(DeviceFilesTest, DeleteLicense);
FRIEND_TEST(DeviceFilesTest, ReadCertificate);
FRIEND_TEST(DeviceFilesTest, RetrieveLicenses);
FRIEND_TEST(DeviceFilesTest, SecurityLevelPathBackwardCompatibility);
FRIEND_TEST(DeviceFilesTest, StoreLicenses);
FRIEND_TEST(DeviceFilesTest, UpdateLicenseState);
FRIEND_TEST(DeviceFilesUsageInfoTest, Delete);
FRIEND_TEST(DeviceFilesUsageInfoTest, Read);
FRIEND_TEST(DeviceFilesUsageInfoTest, Store);
FRIEND_TEST(WvCdmRequestLicenseTest, UnprovisionTest);
FRIEND_TEST(WvCdmRequestLicenseTest, ForceL3Test);
FRIEND_TEST(WvCdmUsageInfoTest, DISABLED_UsageInfo);
#endif
File* file_; File* file_;
CdmSecurityLevel security_level_; CdmSecurityLevel security_level_;
bool initialized_; bool initialized_;
bool test_file_;
CORE_DISALLOW_COPY_AND_ASSIGN(DeviceFiles); CORE_DISALLOW_COPY_AND_ASSIGN(DeviceFiles);
}; };

View File

@@ -7,6 +7,9 @@
#include "wv_cdm_types.h" #include "wv_cdm_types.h"
#include <stddef.h>
#include <stdlib.h>
namespace wvcdm { namespace wvcdm {
// File class. The implementation is platform dependent. // File class. The implementation is platform dependent.

View File

@@ -40,12 +40,16 @@ class CdmLicense {
CdmResponseType HandleKeyUpdateResponse( CdmResponseType HandleKeyUpdateResponse(
bool is_renewal, const CdmKeyResponse& license_response); bool is_renewal, const CdmKeyResponse& license_response);
bool RestoreOfflineLicense(CdmKeyMessage& license_request, bool RestoreOfflineLicense(const CdmKeyMessage& license_request,
CdmKeyResponse& license_response, const CdmKeyResponse& license_response,
CdmKeyResponse& license_renewal_response); const CdmKeyResponse& license_renewal_response);
bool RestoreUsageLicense(const CdmKeyMessage& license_request,
const CdmKeyResponse& license_response);
bool HasInitData() { return !stored_init_data_.empty(); } bool HasInitData() { return !stored_init_data_.empty(); }
bool IsKeyLoaded(const KeyId& key_id); bool IsKeyLoaded(const KeyId& key_id);
std::string provider_session_token() { return provider_session_token_; }
private: private:
bool PrepareServiceCertificateRequest(CdmKeyMessage* signed_request, bool PrepareServiceCertificateRequest(CdmKeyMessage* signed_request,
std::string* server_url); std::string* server_url);
@@ -67,6 +71,7 @@ class CdmLicense {
std::string stored_init_data_; std::string stored_init_data_;
bool initialized_; bool initialized_;
std::set<KeyId> loaded_keys_; std::set<KeyId> loaded_keys_;
std::string provider_session_token_;
// Used for certificate based licensing // Used for certificate based licensing
CdmKeyMessage key_request_; CdmKeyMessage key_request_;

View File

@@ -17,6 +17,8 @@ typedef enum {
LOG_VERBOSE LOG_VERBOSE
} LogPriority; } LogPriority;
extern LogPriority g_cutoff;
// Enable/disable verbose logging (LOGV). // Enable/disable verbose logging (LOGV).
// This function is supplied for cases where the system layer does not // This function is supplied for cases where the system layer does not
// initialize logging. This is also needed to initialize logging in // initialize logging. This is also needed to initialize logging in

View File

@@ -24,30 +24,27 @@
#include <string> #include <string>
#include "openssl/evp.h"
#include "openssl/rsa.h"
#include "wv_cdm_types.h" #include "wv_cdm_types.h"
namespace wvcdm { namespace wvcdm {
class AesCbcKey { class AesCbcKey {
public: public:
AesCbcKey() : initialized_(false) {}; AesCbcKey();
~AesCbcKey() {}; ~AesCbcKey();
bool Init(const std::string& key); bool Init(const std::string& key);
bool Encrypt(const std::string& in, std::string* out, std::string* iv); bool Encrypt(const std::string& in, std::string* out, std::string* iv);
private: private:
EVP_CIPHER_CTX ctx_; std::string key_;
bool initialized_;
CORE_DISALLOW_COPY_AND_ASSIGN(AesCbcKey); CORE_DISALLOW_COPY_AND_ASSIGN(AesCbcKey);
}; };
class RsaPublicKey { class RsaPublicKey {
public: public:
RsaPublicKey() : key_(NULL) {} RsaPublicKey();
~RsaPublicKey(); ~RsaPublicKey();
// Initializes an RsaPublicKey object using a DER encoded PKCS#1 RSAPublicKey // Initializes an RsaPublicKey object using a DER encoded PKCS#1 RSAPublicKey
@@ -64,7 +61,7 @@ class RsaPublicKey {
const std::string& signature); const std::string& signature);
private: private:
RSA* key_; std::string serialized_key_;
CORE_DISALLOW_COPY_AND_ASSIGN(RsaPublicKey); CORE_DISALLOW_COPY_AND_ASSIGN(RsaPublicKey);
}; };

View File

@@ -23,15 +23,15 @@ typedef uint32_t CryptoSessionId;
typedef std::string CryptoKeyId; typedef std::string CryptoKeyId;
typedef std::map<std::string, std::string> CdmAppParameterMap; typedef std::map<std::string, std::string> CdmAppParameterMap;
typedef std::map<std::string, std::string> CdmQueryMap; typedef std::map<std::string, std::string> CdmQueryMap;
typedef std::vector<std::string> CdmSecureStops; typedef std::vector<std::string> CdmUsageInfo;
typedef std::vector<uint8_t> CdmSecureStopReleaseMessage; typedef std::string CdmUsageInfoReleaseMessage;
typedef std::string CdmProvisioningRequest; typedef std::string CdmProvisioningRequest;
typedef std::string CdmProvisioningResponse; typedef std::string CdmProvisioningResponse;
// Types for shared host/cdm interface pairs used to shared vendor data. // Types for shared host/cdm interface pairs used to shared vendor data.
typedef std::pair<std::string, std::string> kStringPairs; typedef std::pair<std::string, std::string> StringPairs;
typedef std::vector<uint8_t> kVectorBytes; typedef std::vector<uint8_t> VectorBytes;
typedef std::pair<std::string, kVectorBytes> kVectorPairs; typedef std::pair<std::string, VectorBytes> VectorPairs;
enum CdmResponseType { enum CdmResponseType {
NO_ERROR, NO_ERROR,

View File

@@ -6,6 +6,8 @@
#include <sstream> #include <sstream>
#include "cdm_session.h" #include "cdm_session.h"
#include "clock.h"
#include "device_files.h"
#include "license_protocol.pb.h" #include "license_protocol.pb.h"
#include "log.h" #include "log.h"
#include "properties.h" #include "properties.h"
@@ -14,14 +16,24 @@
#include "wv_cdm_constants.h" #include "wv_cdm_constants.h"
#include "wv_cdm_event_listener.h" #include "wv_cdm_event_listener.h"
namespace {
const uint32_t kUpdateUsageInformationPeriod = 60; // seconds
const size_t kMinNoncesPerSession = 4;
} // unnamed namespace
namespace wvcdm { namespace wvcdm {
CdmEngine::CdmEngine() CdmEngine::CdmEngine()
: cert_provisioning_requested_security_level_(kLevelDefault) { : cert_provisioning_requested_security_level_(kLevelDefault),
usage_session_(NULL),
last_usage_information_update_time(0) {
Properties::Init(); Properties::Init();
} }
CdmEngine::~CdmEngine() { CdmEngine::~CdmEngine() {
if (NULL != usage_session_)
delete usage_session_;
CdmSessionMap::iterator i(sessions_.begin()); CdmSessionMap::iterator i(sessions_.begin());
for (; i != sessions_.end(); ++i) { for (; i != sessions_.end(); ++i) {
delete i->second; delete i->second;
@@ -478,13 +490,87 @@ CdmResponseType CdmEngine::HandleProvisioningResponse(
wrapped_key); wrapped_key);
} }
CdmResponseType CdmEngine::GetSecureStops( CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level) {
CdmSecureStops* secure_stops) { DeviceFiles handle;
if (!handle.Init(security_level)) {
LOGE("CdmEngine::Unprovision: unable to initialize device files");
return UNKNOWN_ERROR;
}
if (!handle.DeleteAllFiles()) {
LOGE("CdmEngine::Unprovision: unable to delete files");
return UNKNOWN_ERROR;
}
return NO_ERROR; return NO_ERROR;
} }
CdmResponseType CdmEngine::ReleaseSecureStops( CdmResponseType CdmEngine::GetUsageInfo(CdmUsageInfo* usage_info) {
const CdmSecureStopReleaseMessage& message) { if (NULL == usage_session_) {
usage_session_ = new CdmSession(NULL);
}
CdmResponseType status = usage_session_->Init();
if (NO_ERROR != status) {
LOGE("CdmEngine::GetUsageInfo: session init error");
return status;
}
DeviceFiles handle;
if (!handle.Init(usage_session_->GetSecurityLevel())) {
LOGE("CdmEngine::GetUsageInfo: unable to initialize device files");
return status;
}
std::vector<std::pair<CdmKeyMessage, CdmKeyResponse> > license_info;
if (!handle.RetrieveUsageInfo(&license_info)) {
LOGE("CdmEngine::GetUsageInfo: unable to read usage information");
return UNKNOWN_ERROR;
}
if (0 == license_info.size()) {
usage_info->resize(0);
return NO_ERROR;
}
std::string server_url;
// rate limit secure stop messages based on minimum nonce
// table size per session
usage_info->resize(license_info.size() >= kMinNoncesPerSession - 1
? kMinNoncesPerSession - 1
: license_info.size());
for (size_t i = 0; i < usage_info->size(); ++i) {
status = usage_session_->RestoreUsageSession(license_info[i].first,
license_info[i].second);
if (KEY_ADDED != status) {
LOGE("CdmEngine::GetUsageInfo: restore usage session error: %ld",
status);
usage_info->clear();
return status;
}
status = usage_session_->GenerateReleaseRequest(&(*usage_info)[i],
&server_url);
if (KEY_MESSAGE != status) {
LOGE("CdmEngine::GetUsageInfo: generate release request error: %ld",
status);
usage_info->clear();
return status;
}
}
return KEY_MESSAGE;
}
CdmResponseType CdmEngine::ReleaseUsageInfo(
const CdmUsageInfoReleaseMessage& message) {
if (NULL == usage_session_) {
LOGE("CdmEngine::ReleaseUsageInfo: cdm session not initialized");
return UNKNOWN_ERROR;
}
CdmResponseType status = usage_session_->ReleaseKey(message);
if (NO_ERROR != status) {
LOGE("CdmEngine::ReleaseUsageInfo: release key error: %ld", status);
return UNKNOWN_ERROR;
}
return NO_ERROR; return NO_ERROR;
} }
@@ -527,8 +613,8 @@ CdmResponseType CdmEngine::Decrypt(
} }
if (iter == sessions_.end()) { if (iter == sessions_.end()) {
LOGE("CdmEngine::Decrypt: session not found: id=%s, id size=%d", LOGE("CdmEngine::Decrypt: session not found: id=%s, id size=%d",
session_id.size(), session_id.c_str(),
session_id.c_str()); session_id.size());
return KEY_ERROR; return KEY_ERROR;
} }
@@ -604,9 +690,31 @@ bool CdmEngine::ValidateKeySystem(const CdmKeySystem& key_system) {
} }
void CdmEngine::OnTimerEvent() { void CdmEngine::OnTimerEvent() {
Clock clock;
uint64_t current_time = clock.GetCurrentTime();
bool update_usage_information = false;
if (current_time - last_usage_information_update_time >
kUpdateUsageInformationPeriod) {
update_usage_information = true;
last_usage_information_update_time = current_time;
}
for (CdmSessionMap::iterator iter = sessions_.begin(); for (CdmSessionMap::iterator iter = sessions_.begin();
iter != sessions_.end(); ++iter) { iter != sessions_.end(); ++iter) {
iter->second->OnTimerEvent(); iter->second->OnTimerEvent();
if (update_usage_information && iter->second->is_usage_update_needed()) {
// usage is updated for all sessions so this needs to be
// called only once per update usage information period
CdmResponseType status = iter->second->UpdateUsageInformation();
if (NO_ERROR != status) {
LOGW("Update usage information failed: %u", status);
} else {
update_usage_information = false;
}
}
iter->second->reset_is_usage_update_needed();
} }
} }

View File

@@ -30,7 +30,9 @@ CdmSession::CdmSession(const CdmClientPropertySet* cdm_client_property_set)
crypto_session_(NULL), crypto_session_(NULL),
license_received_(false), license_received_(false),
reinitialize_session_(false), reinitialize_session_(false),
license_type_(kLicenseTypeStreaming), is_offline_(false),
is_release_(false),
is_usage_update_needed_(false),
is_certificate_loaded_(false) { is_certificate_loaded_(false) {
if (cdm_client_property_set) { if (cdm_client_property_set) {
Properties::AddSessionPropertySet(session_id_, cdm_client_property_set); Properties::AddSessionPropertySet(session_id_, cdm_client_property_set);
@@ -47,9 +49,8 @@ CdmResponseType CdmSession::Init() {
std::string token; std::string token;
if (Properties::use_certificates_as_identification()) { if (Properties::use_certificates_as_identification()) {
File file;
DeviceFiles handle; DeviceFiles handle;
if (!handle.Init(&file, session.get()->GetSecurityLevel()) || if (!handle.Init(session.get()->GetSecurityLevel()) ||
!handle.RetrieveCertificate(&token, &wrapped_key_)) { !handle.RetrieveCertificate(&token, &wrapped_key_)) {
return NEED_PROVISIONING; return NEED_PROVISIONING;
} }
@@ -71,15 +72,14 @@ CdmResponseType CdmSession::RestoreOfflineSession(
key_set_id_ = key_set_id; key_set_id_ = key_set_id;
// Retrieve license information from persistent store // Retrieve license information from persistent store
File file;
DeviceFiles handle; DeviceFiles handle;
if (!handle.Init(&file, crypto_session_->GetSecurityLevel())) if (!handle.Init(crypto_session_->GetSecurityLevel()))
return UNKNOWN_ERROR; return UNKNOWN_ERROR;
DeviceFiles::LicenseState license_state; DeviceFiles::LicenseState license_state;
if (!handle.RetrieveLicense(key_set_id, &license_state, &offline_init_data_, if (!handle.RetrieveLicense(key_set_id, &license_state, &offline_init_data_,
&offline_key_request_, &offline_key_response_, &key_request_, &key_response_,
&offline_key_renewal_request_, &offline_key_renewal_request_,
&offline_key_renewal_response_, &offline_key_renewal_response_,
&offline_release_server_url_)) { &offline_release_server_url_)) {
@@ -102,14 +102,39 @@ CdmResponseType CdmSession::RestoreOfflineSession(
} }
} }
if (!license_parser_.RestoreOfflineLicense(offline_key_request_, if (!license_parser_.RestoreOfflineLicense(key_request_, key_response_,
offline_key_response_,
offline_key_renewal_response_)) { offline_key_renewal_response_)) {
return UNKNOWN_ERROR; return UNKNOWN_ERROR;
} }
license_received_ = true; license_received_ = true;
license_type_ = license_type; is_offline_ = true;
is_release_ = license_type == kLicenseTypeRelease;
return KEY_ADDED;
}
CdmResponseType CdmSession::RestoreUsageSession(
const CdmKeyMessage& key_request,
const CdmKeyResponse& key_response) {
key_request_ = key_request;
key_response_ = key_response;
if (Properties::use_certificates_as_identification()) {
if (is_certificate_loaded_ ||
crypto_session_->LoadCertificatePrivateKey(wrapped_key_)) {
is_certificate_loaded_ = true;
} else {
return NEED_PROVISIONING;
}
}
if (!license_parser_.RestoreUsageLicense(key_request_, key_response_)) {
return UNKNOWN_ERROR;
}
license_received_ = true;
is_offline_ = false;
is_release_ = true;
return KEY_ADDED; return KEY_ADDED;
} }
@@ -135,9 +160,17 @@ CdmResponseType CdmSession::GenerateKeyRequest(
return UNKNOWN_ERROR; return UNKNOWN_ERROR;
} }
license_type_ = license_type; switch (license_type) {
case kLicenseTypeStreaming: is_offline_ = false; break;
case kLicenseTypeOffline: is_offline_ = true; break;
case kLicenseTypeRelease: is_release_ = true; break;
default:
LOGE("CdmSession::GenerateKeyRequest: unrecognized license type: %ld",
license_type);
return UNKNOWN_ERROR;
}
if (license_type_ == kLicenseTypeRelease) { if (is_release_) {
return GenerateReleaseRequest(key_request, server_url); return GenerateReleaseRequest(key_request, server_url);
} else if (license_received_) { // renewal } else if (license_received_) { // renewal
return GenerateRenewalRequest(key_request, server_url); return GenerateRenewalRequest(key_request, server_url);
@@ -168,9 +201,9 @@ CdmResponseType CdmSession::GenerateKeyRequest(
return KEY_ERROR; return KEY_ERROR;
} }
if (license_type_ == kLicenseTypeOffline) { key_request_ = *key_request;
if (is_offline_) {
offline_init_data_ = init_data.data(); offline_init_data_ = init_data.data();
offline_key_request_ = *key_request;
offline_release_server_url_ = *server_url; offline_release_server_url_ = *server_url;
} }
@@ -191,7 +224,7 @@ CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response,
return UNKNOWN_ERROR; return UNKNOWN_ERROR;
} }
if (license_type_ == kLicenseTypeRelease) { if (is_release_) {
return ReleaseKey(key_response); return ReleaseKey(key_response);
} else if (license_received_) { // renewal } else if (license_received_) { // renewal
return RenewKey(key_response); return RenewKey(key_response);
@@ -201,25 +234,11 @@ CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response,
if (sts != KEY_ADDED) return sts; if (sts != KEY_ADDED) return sts;
license_received_ = true; license_received_ = true;
key_response_ = key_response;
if (license_type_ == kLicenseTypeOffline) { if (is_offline_ || !license_parser_.provider_session_token().empty()) {
offline_key_response_ = key_response; sts = StoreLicense();
if (!GenerateKeySetId(&key_set_id_)) { if (sts != NO_ERROR) return sts;
LOGE("CdmSession::AddKey: Unable to generate key set Id");
return UNKNOWN_ERROR;
}
if (!StoreLicense(DeviceFiles::kLicenseStateActive)) {
LOGE("CdmSession::AddKey: Unable to store license");
CdmResponseType sts = Init();
if (sts != NO_ERROR) {
LOGW("CdmSession::AddKey: Reinitialization failed");
return sts;
}
key_set_id_.clear();
return UNKNOWN_ERROR;
}
} }
*key_set_id = key_set_id_; *key_set_id = key_set_id_;
@@ -290,7 +309,17 @@ CdmResponseType CdmSession::CancelKeyRequest() {
CdmResponseType CdmSession::Decrypt(const CdmDecryptionParameters& params) { CdmResponseType CdmSession::Decrypt(const CdmDecryptionParameters& params) {
if (crypto_session_.get() == NULL || !crypto_session_->IsOpen()) if (crypto_session_.get() == NULL || !crypto_session_->IsOpen())
return UNKNOWN_ERROR; return UNKNOWN_ERROR;
return crypto_session_->Decrypt(params);
CdmResponseType status = crypto_session_->Decrypt(params);
if (NO_ERROR == status) {
if (!is_usage_update_needed_) {
is_usage_update_needed_ =
!license_parser_.provider_session_token().empty();
}
}
return status;
} }
// License renewal // License renewal
@@ -303,7 +332,7 @@ CdmResponseType CdmSession::GenerateRenewalRequest(CdmKeyMessage* key_request,
return KEY_ERROR; return KEY_ERROR;
} }
if (license_type_ == kLicenseTypeOffline) { if (is_offline_) {
offline_key_renewal_request_ = *key_request; offline_key_renewal_request_ = *key_request;
} }
return KEY_MESSAGE; return KEY_MESSAGE;
@@ -315,7 +344,7 @@ CdmResponseType CdmSession::RenewKey(const CdmKeyResponse& key_response) {
license_parser_.HandleKeyUpdateResponse(true, key_response); license_parser_.HandleKeyUpdateResponse(true, key_response);
if (sts != KEY_ADDED) return sts; if (sts != KEY_ADDED) return sts;
if (license_type_ == kLicenseTypeOffline) { if (is_offline_) {
offline_key_renewal_response_ = key_response; offline_key_renewal_response_ = key_response;
if (!StoreLicense(DeviceFiles::kLicenseStateActive)) return UNKNOWN_ERROR; if (!StoreLicense(DeviceFiles::kLicenseStateActive)) return UNKNOWN_ERROR;
} }
@@ -324,23 +353,28 @@ CdmResponseType CdmSession::RenewKey(const CdmKeyResponse& key_response) {
CdmResponseType CdmSession::GenerateReleaseRequest(CdmKeyMessage* key_request, CdmResponseType CdmSession::GenerateReleaseRequest(CdmKeyMessage* key_request,
std::string* server_url) { std::string* server_url) {
if (license_parser_.PrepareKeyUpdateRequest(false, key_request, server_url)) { is_release_ = true;
// Mark license as being released if (!license_parser_.PrepareKeyUpdateRequest(false, key_request, server_url))
if (StoreLicense(DeviceFiles::kLicenseStateReleasing)) return KEY_MESSAGE; return UNKNOWN_ERROR;
if (is_offline_) { // Mark license as being released
if (!StoreLicense(DeviceFiles::kLicenseStateReleasing))
return UNKNOWN_ERROR;
} }
return UNKNOWN_ERROR; return KEY_MESSAGE;
} }
// ReleaseKey() - Accept release response and release license. // ReleaseKey() - Accept release response and release license.
CdmResponseType CdmSession::ReleaseKey(const CdmKeyResponse& key_response) { CdmResponseType CdmSession::ReleaseKey(const CdmKeyResponse& key_response) {
CdmResponseType sts = CdmResponseType sts = license_parser_.HandleKeyUpdateResponse(false,
license_parser_.HandleKeyUpdateResponse(false, key_response); key_response);
File file; if (NO_ERROR != sts)
DeviceFiles handle; return sts;
if (handle.Init(&file, crypto_session_->GetSecurityLevel()))
handle.DeleteLicense(key_set_id_);
return sts; if (is_offline_ || !license_parser_.provider_session_token().empty()) {
DeleteLicense();
}
return NO_ERROR;
} }
bool CdmSession::IsKeyLoaded(const KeyId& key_id) { bool CdmSession::IsKeyLoaded(const KeyId& key_id) {
@@ -361,9 +395,8 @@ bool CdmSession::GenerateKeySetId(CdmKeySetId* key_set_id) {
std::vector<uint8_t> random_data( std::vector<uint8_t> random_data(
(kKeySetIdLength - sizeof(KEY_SET_ID_PREFIX)) / 2, 0); (kKeySetIdLength - sizeof(KEY_SET_ID_PREFIX)) / 2, 0);
File file;
DeviceFiles handle; DeviceFiles handle;
if (!handle.Init(&file, crypto_session_->GetSecurityLevel())) if (!handle.Init(crypto_session_->GetSecurityLevel()))
return false; return false;
while (key_set_id->empty()) { while (key_set_id->empty()) {
@@ -380,18 +413,75 @@ bool CdmSession::GenerateKeySetId(CdmKeySetId* key_set_id) {
return true; return true;
} }
bool CdmSession::StoreLicense(DeviceFiles::LicenseState state) { CdmResponseType CdmSession::StoreLicense() {
File file; if (is_offline_) {
if (!GenerateKeySetId(&key_set_id_)) {
LOGE("CdmSession::StoreLicense: Unable to generate key set Id");
return UNKNOWN_ERROR;
}
if (!StoreLicense(DeviceFiles::kLicenseStateActive)) {
LOGE("CdmSession::StoreLicense: Unable to store license");
CdmResponseType sts = Init();
if (sts != NO_ERROR) {
LOGW("CdmSession::StoreLicense: Reinitialization failed");
return sts;
}
key_set_id_.clear();
return UNKNOWN_ERROR;
}
return NO_ERROR;
}
std::string provider_session_token = license_parser_.provider_session_token();
if (provider_session_token.empty()) {
LOGE("CdmSession::StoreLicense: No provider session token and not offline");
return UNKNOWN_ERROR;
}
DeviceFiles handle; DeviceFiles handle;
if (!handle.Init(&file, crypto_session_->GetSecurityLevel())) if (!handle.Init(crypto_session_->GetSecurityLevel())) {
LOGE("CdmSession::StoreLicense: Unable to initialize device files");
return UNKNOWN_ERROR;
}
if (!handle.StoreUsageInfo(provider_session_token, key_request_,
key_response_)) {
LOGE("CdmSession::StoreLicense: Unable to store usage info");
return UNKNOWN_ERROR;
}
return NO_ERROR;
}
bool CdmSession::StoreLicense(DeviceFiles::LicenseState state) {
DeviceFiles handle;
if (!handle.Init(crypto_session_->GetSecurityLevel()))
return false; return false;
return handle.StoreLicense( return handle.StoreLicense(
key_set_id_, state, offline_init_data_, offline_key_request_, key_set_id_, state, offline_init_data_, key_request_,
offline_key_response_, offline_key_renewal_request_, key_response_, offline_key_renewal_request_,
offline_key_renewal_response_, offline_release_server_url_); offline_key_renewal_response_, offline_release_server_url_);
} }
bool CdmSession::DeleteLicense() {
if (!is_offline_ && license_parser_.provider_session_token().empty())
return false;
DeviceFiles handle;
if (!handle.Init(crypto_session_->GetSecurityLevel())) {
LOGE("CdmSession::DeleteLicense: Unable to initialize device files");
return false;
}
if (is_offline_)
return handle.DeleteLicense(key_set_id_);
else
return handle.DeleteUsageInfo(
license_parser_.provider_session_token());
}
bool CdmSession::AttachEventListener(WvCdmEventListener* listener) { bool CdmSession::AttachEventListener(WvCdmEventListener* listener) {
std::pair<CdmEventListenerIter, bool> result = listeners_.insert(listener); std::pair<CdmEventListenerIter, bool> result = listeners_.insert(listener);
return result.second; return result.second;
@@ -433,4 +523,15 @@ SecurityLevel CdmSession::GetRequestedSecurityLevel() {
return kLevelDefault; return kLevelDefault;
} }
CdmSecurityLevel CdmSession::GetSecurityLevel() {
if (NULL == crypto_session_.get())
return kSecurityLevelUninitialized;
return crypto_session_.get()->GetSecurityLevel();
}
CdmResponseType CdmSession::UpdateUsageInformation() {
return crypto_session_->UpdateUsageInformation();
}
} // namespace wvcdm } // namespace wvcdm

View File

@@ -28,10 +28,9 @@ using video_widevine_server::sdk::ProvisioningResponse;
using video_widevine_server::sdk::SignedProvisioningMessage; using video_widevine_server::sdk::SignedProvisioningMessage;
/* /*
* This function converts SignedProvisioningRequest into base64 string. * This function converts SignedProvisioningRequest into base64 string. It then
* It then wraps it in JSON format expected by the Apiary frontend. * wraps it in JSON format expected by the frontend. This server requires a
* Apiary requires the base64 encoding to replace '+' with minus '-', * "web-safe" base 64 encoding, where '+' becomes '-' and '/' becomes '_'.
* and '/' with underscore '_'; opposite to stubby's.
* *
* Returns the JSON formated string in *request. The JSON string will be * Returns the JSON formated string in *request. The JSON string will be
* appended as a query parameter, i.e. signedRequest=<base 64 encoded * appended as a query parameter, i.e. signedRequest=<base 64 encoded
@@ -103,7 +102,8 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequest(
switch (cert_type) { switch (cert_type) {
case kCertificateWidevine: case kCertificateWidevine:
options->set_certificate_type( options->set_certificate_type(
video_widevine_server::sdk::ProvisioningOptions_CertificateType_RSA_WIDEVINE); video_widevine_server::sdk::
ProvisioningOptions_CertificateType_WIDEVINE_DRM);
break; break;
case kCertificateX509: case kCertificateX509:
options->set_certificate_type( options->set_certificate_type(
@@ -262,9 +262,8 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
const std::string& device_certificate = const std::string& device_certificate =
provisioning_response.device_certificate(); provisioning_response.device_certificate();
File file;
DeviceFiles handle; DeviceFiles handle;
if (!handle.Init(&file, crypto_session_.GetSecurityLevel())) { if (!handle.Init(crypto_session_.GetSecurityLevel())) {
LOGE("HandleProvisioningResponse: failed to init DeviceFiles"); LOGE("HandleProvisioningResponse: failed to init DeviceFiles");
return UNKNOWN_ERROR; return UNKNOWN_ERROR;
} }

View File

@@ -155,7 +155,7 @@ bool CryptoSession::GetDeviceUniqueId(std::string* device_id) {
return false; return false;
} }
device_id->assign(reinterpret_cast<char *>(&id[0]), id_length); device_id->assign(reinterpret_cast<char*>(&id[0]), id_length);
return true; return true;
} }
@@ -340,12 +340,11 @@ size_t CryptoSession::GetOffset(std::string message, std::string field) {
return pos; return pos;
} }
CdmResponseType CryptoSession::LoadKeys(const std::string& message, CdmResponseType CryptoSession::LoadKeys(
const std::string& signature, const std::string& message, const std::string& signature,
const std::string& mac_key_iv, const std::string& mac_key_iv, const std::string& mac_key,
const std::string& mac_key, const std::vector<CryptoKey>& keys,
int num_keys, const std::string& provider_session_token) {
const CryptoKey* key_array) {
LOGV("CryptoSession::LoadKeys: Lock"); LOGV("CryptoSession::LoadKeys: Lock");
AutoLock auto_lock(crypto_lock_); AutoLock auto_lock(crypto_lock_);
@@ -358,10 +357,10 @@ CdmResponseType CryptoSession::LoadKeys(const std::string& message,
} else { } else {
LOGV("CryptoSession::LoadKeys: enc_mac_key not set"); LOGV("CryptoSession::LoadKeys: enc_mac_key not set");
} }
std::vector<OEMCrypto_KeyObject> load_key_array(num_keys); std::vector<OEMCrypto_KeyObject> load_keys(keys.size());
for (int i = 0; i < num_keys; ++i) { for (size_t i = 0; i < keys.size(); ++i) {
const CryptoKey* ki = &key_array[i]; const CryptoKey* ki = &keys[i];
OEMCrypto_KeyObject* ko = &load_key_array[i]; OEMCrypto_KeyObject* ko = &load_keys[i];
ko->key_id = msg + GetOffset(message, ki->key_id()); ko->key_id = msg + GetOffset(message, ki->key_id());
ko->key_id_length = ki->key_id().length(); ko->key_id_length = ki->key_id().length();
ko->key_data_iv = msg + GetOffset(message, ki->key_data_iv()); ko->key_data_iv = msg + GetOffset(message, ki->key_data_iv());
@@ -377,11 +376,17 @@ CdmResponseType CryptoSession::LoadKeys(const std::string& message,
ko->key_control = NULL; ko->key_control = NULL;
} }
} }
uint8_t* pst = NULL;
if (!provider_session_token.empty()) {
pst =
const_cast<uint8_t*>(msg) + GetOffset(message, provider_session_token);
}
LOGV("LoadKeys: id=%ld", (uint32_t)oec_session_id_); LOGV("LoadKeys: id=%ld", (uint32_t)oec_session_id_);
OEMCryptoResult sts = OEMCrypto_LoadKeys( OEMCryptoResult sts = OEMCrypto_LoadKeys(
oec_session_id_, msg, message.size(), oec_session_id_, msg, message.size(),
reinterpret_cast<const uint8_t*>(signature.data()), signature.size(), reinterpret_cast<const uint8_t*>(signature.data()), signature.size(),
enc_mac_key_iv, enc_mac_key, num_keys, &load_key_array[0], NULL, 0); enc_mac_key_iv, enc_mac_key, keys.size(), &load_keys[0], pst,
provider_session_token.length());
if (OEMCrypto_SUCCESS == sts) { if (OEMCrypto_SUCCESS == sts) {
return KEY_ADDED; return KEY_ADDED;
@@ -605,7 +610,8 @@ CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) {
buffer_descriptor.buffer.clear.address = buffer_descriptor.buffer.clear.address =
static_cast<uint8_t*>(params.decrypt_buffer) + static_cast<uint8_t*>(params.decrypt_buffer) +
params.decrypt_buffer_offset; params.decrypt_buffer_offset;
buffer_descriptor.buffer.clear.max_length = params.decrypt_buffer_length; buffer_descriptor.buffer.clear.max_length =
params.decrypt_buffer_length - params.decrypt_buffer_offset;
break; break;
case OEMCrypto_BufferType_Secure: case OEMCrypto_BufferType_Secure:
buffer_descriptor.buffer.secure.handle = params.decrypt_buffer; buffer_descriptor.buffer.secure.handle = params.decrypt_buffer;
@@ -635,6 +641,84 @@ CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) {
} }
} }
CdmResponseType CryptoSession::UpdateUsageInformation() {
return (OEMCrypto_UpdateUsageTable() == OEMCrypto_SUCCESS) ? NO_ERROR
: UNKNOWN_ERROR;
}
CdmResponseType CryptoSession::GenerateUsageReport(
const std::string& provider_session_token, std::string* usage_report) {
LOGV("GenerateUsageReport: id=%ld", (uint32_t)oec_session_id_);
if (NULL == usage_report) {
LOGE("usage_report parameter is null");
return UNKNOWN_ERROR;
}
AutoLock auto_lock(crypto_lock_);
uint8_t* pst = reinterpret_cast<uint8_t*>(
const_cast<char*>(provider_session_token.data()));
OEMCryptoResult status =
OEMCrypto_DeactivateUsageEntry(pst, provider_session_token.length());
if (OEMCrypto_SUCCESS != status) {
LOGE("CryptoSession::GenerateUsageReport: Deactivate Usage Entry error=%ld",
status);
return UNKNOWN_ERROR;
}
size_t usage_length = 0;
status = OEMCrypto_ReportUsage(oec_session_id_, pst,
provider_session_token.length(), NULL,
&usage_length);
if (OEMCrypto_ERROR_SHORT_BUFFER != status) {
LOGE("CryptoSession::GenerateUsageReport: Report Usage error=%ld", status);
return UNKNOWN_ERROR;
}
usage_report->resize(usage_length);
OEMCrypto_PST_Report* report = reinterpret_cast<OEMCrypto_PST_Report*>(
const_cast<char*>(usage_report->data()));
status = OEMCrypto_ReportUsage(oec_session_id_, pst,
provider_session_token.length(), report,
&usage_length);
if (OEMCrypto_SUCCESS != status) {
LOGE("CryptoSession::GenerateUsageReport: Report Usage error=%ld", status);
return UNKNOWN_ERROR;
}
if (usage_length < usage_report->length()) {
usage_report->resize(usage_length);
}
return NO_ERROR;
}
CdmResponseType CryptoSession::ReleaseUsageInformation(
const std::string& message, const std::string& signature,
const std::string& provider_session_token) {
LOGV("ReleaseUsageInformation: id=%ld", (uint32_t)oec_session_id_);
AutoLock auto_lock(crypto_lock_);
const uint8_t* msg = reinterpret_cast<const uint8_t*>(message.data());
const uint8_t* sig = reinterpret_cast<const uint8_t*>(signature.data());
const uint8_t* pst = msg + GetOffset(message, provider_session_token);
OEMCryptoResult status = OEMCrypto_DeleteUsageEntry(
oec_session_id_, pst, provider_session_token.length(), msg,
message.length(), sig, signature.length());
if (OEMCrypto_SUCCESS != status) {
LOGE("CryptoSession::ReleaseUsageInformation: Report Usage error=%ld",
status);
return UNKNOWN_ERROR;
}
return NO_ERROR;
}
bool CryptoSession::GenerateNonce(uint32_t* nonce) { bool CryptoSession::GenerateNonce(uint32_t* nonce) {
if (!nonce) { if (!nonce) {
LOGE("input parameter is null"); LOGE("input parameter is null");

View File

@@ -2,14 +2,22 @@
#include "device_files.h" #include "device_files.h"
#if defined(__APPLE__)
# include <CommonCrypto/CommonDigest.h>
# define SHA256 CC_SHA256
# define SHA256_DIGEST_LENGTH CC_SHA256_DIGEST_LENGTH
#else
# include <openssl/sha.h>
#endif
#include <cstring> #include <cstring>
#include <string> #include <string>
#include "device_files.pb.h" #include "device_files.pb.h"
#include "file_store.h" #include "file_store.h"
#include "log.h" #include "log.h"
#include "openssl/sha.h"
#include "properties.h" #include "properties.h"
#include "string_conversions.h"
// Protobuf generated classes. // Protobuf generated classes.
using video_widevine_client::sdk::DeviceCertificate; using video_widevine_client::sdk::DeviceCertificate;
@@ -17,9 +25,13 @@ using video_widevine_client::sdk::HashedFile;
using video_widevine_client::sdk::License; using video_widevine_client::sdk::License;
using video_widevine_client::sdk::License_LicenseState_ACTIVE; using video_widevine_client::sdk::License_LicenseState_ACTIVE;
using video_widevine_client::sdk::License_LicenseState_RELEASING; using video_widevine_client::sdk::License_LicenseState_RELEASING;
using video_widevine_client::sdk::UsageInfo;
using video_widevine_client::sdk::UsageInfo_ProviderSession;
namespace { namespace {
const char kCertificateFileName[] = "cert.bin"; const char kCertificateFileName[] = "cert.bin";
const char kUsageInfoFileName[] = "usage.bin";
const char kLicenseFileNameExt[] = ".lic"; const char kLicenseFileNameExt[] = ".lic";
const char kWildcard[] = "*"; const char kWildcard[] = "*";
const char kDirectoryDelimiter = '/'; const char kDirectoryDelimiter = '/';
@@ -27,15 +39,27 @@ const char* kSecurityLevelPathCompatibilityExclusionList[] = {"ay64.dat"};
size_t kSecurityLevelPathCompatibilityExclusionListSize = size_t kSecurityLevelPathCompatibilityExclusionListSize =
sizeof(kSecurityLevelPathCompatibilityExclusionList) / sizeof(kSecurityLevelPathCompatibilityExclusionList) /
sizeof(*kSecurityLevelPathCompatibilityExclusionList); sizeof(*kSecurityLevelPathCompatibilityExclusionList);
} // namespace
bool Hash(const std::string& data, std::string* hash) {
if (!hash) return false;
hash->resize(SHA256_DIGEST_LENGTH);
const unsigned char* input =
reinterpret_cast<const unsigned char*>(data.data());
unsigned char* output = reinterpret_cast<unsigned char*>(&(*hash)[0]);
SHA256(input, data.size(), output);
return true;
}
} // unnamed namespace
namespace wvcdm { namespace wvcdm {
bool DeviceFiles::Init(const File* handle, CdmSecurityLevel security_level) { DeviceFiles::~DeviceFiles() {
if (handle == NULL) { if (!file_ && !test_file_) delete file_;
LOGW("DeviceFiles::Init: Invalid file handle parameter"); }
return false;
} bool DeviceFiles::Init(CdmSecurityLevel security_level) {
switch (security_level) { switch (security_level) {
case kSecurityLevelL1: case kSecurityLevelL1:
case kSecurityLevelL2: case kSecurityLevelL2:
@@ -45,7 +69,7 @@ bool DeviceFiles::Init(const File* handle, CdmSecurityLevel security_level) {
LOGW("DeviceFiles::Init: Unsupported security level %d", security_level); LOGW("DeviceFiles::Init: Unsupported security level %d", security_level);
return false; return false;
} }
file_ = const_cast<File*>(handle); file_ = new File();
security_level_ = security_level; security_level_ = security_level;
initialized_ = true; initialized_ = true;
return true; return true;
@@ -68,24 +92,10 @@ bool DeviceFiles::StoreCertificate(const std::string& certificate,
device_certificate->set_certificate(certificate); device_certificate->set_certificate(certificate);
device_certificate->set_wrapped_private_key(wrapped_private_key); device_certificate->set_wrapped_private_key(wrapped_private_key);
std::string serialized_string; std::string serialized_file;
file.SerializeToString(&serialized_string); file.SerializeToString(&serialized_file);
// calculate SHA hash return StoreFile(kCertificateFileName, serialized_file);
std::string hash;
if (!Hash(serialized_string, &hash)) {
LOGW("DeviceFiles::StoreCertificate: Hash computation failed");
return false;
}
// Fill in hashed file data
HashedFile hashed_file;
hashed_file.set_file(serialized_string);
hashed_file.set_hash(hash);
hashed_file.SerializeToString(&serialized_string);
return StoreFile(kCertificateFileName, serialized_string);
} }
bool DeviceFiles::RetrieveCertificate(std::string* certificate, bool DeviceFiles::RetrieveCertificate(std::string* certificate,
@@ -99,29 +109,12 @@ bool DeviceFiles::RetrieveCertificate(std::string* certificate,
SecurityLevelPathBackwardCompatibility(); SecurityLevelPathBackwardCompatibility();
} }
std::string serialized_hashed_file; std::string serialized_file;
if (!RetrieveFile(kCertificateFileName, &serialized_hashed_file)) if (!RetrieveFile(kCertificateFileName, &serialized_file))
return false; return false;
HashedFile hashed_file;
if (!hashed_file.ParseFromString(serialized_hashed_file)) {
LOGW("DeviceFiles::RetrieveCertificate: Unable to parse hash file");
return false;
}
std::string hash;
if (!Hash(hashed_file.file(), &hash)) {
LOGW("DeviceFiles::RetrieveCertificate: Hash computation failed");
return false;
}
if (hash.compare(hashed_file.hash())) {
LOGW("DeviceFiles::RetrieveCertificate: Hash mismatch");
return false;
}
video_widevine_client::sdk::File file; video_widevine_client::sdk::File file;
if (!file.ParseFromString(hashed_file.file())) { if (!file.ParseFromString(serialized_file)) {
LOGW("DeviceFiles::RetrieveCertificate: Unable to parse file"); LOGW("DeviceFiles::RetrieveCertificate: Unable to parse file");
return false; return false;
} }
@@ -187,25 +180,11 @@ bool DeviceFiles::StoreLicense(const std::string& key_set_id,
license->set_renewal(license_renewal); license->set_renewal(license_renewal);
license->set_release_server_url(release_server_url); license->set_release_server_url(release_server_url);
std::string serialized_string; std::string serialized_file;
file.SerializeToString(&serialized_string); file.SerializeToString(&serialized_file);
// calculate SHA hash
std::string hash;
if (!Hash(serialized_string, &hash)) {
LOGW("DeviceFiles::StoreLicense: Hash computation failed");
return false;
}
// File in hashed file data
HashedFile hashed_file;
hashed_file.set_file(serialized_string);
hashed_file.set_hash(hash);
hashed_file.SerializeToString(&serialized_string);
std::string file_name = key_set_id + kLicenseFileNameExt; std::string file_name = key_set_id + kLicenseFileNameExt;
return StoreFile(file_name.c_str(), serialized_string); return StoreFile(file_name.c_str(), serialized_file);
} }
bool DeviceFiles::RetrieveLicense(const std::string& key_set_id, bool DeviceFiles::RetrieveLicense(const std::string& key_set_id,
@@ -220,29 +199,12 @@ bool DeviceFiles::RetrieveLicense(const std::string& key_set_id,
return false; return false;
} }
std::string serialized_hashed_file; std::string serialized_file;
std::string file_name = key_set_id + kLicenseFileNameExt; std::string file_name = key_set_id + kLicenseFileNameExt;
if (!RetrieveFile(file_name.c_str(), &serialized_hashed_file)) return false; if (!RetrieveFile(file_name.c_str(), &serialized_file)) return false;
HashedFile hashed_file;
if (!hashed_file.ParseFromString(serialized_hashed_file)) {
LOGW("DeviceFiles::RetrieveLicense: Unable to parse hash file");
return false;
}
std::string hash;
if (!Hash(hashed_file.file(), &hash)) {
LOGW("DeviceFiles::RetrieveLicense: Hash computation failed");
return false;
}
if (hash.compare(hashed_file.hash())) {
LOGW("DeviceFiles::RetrieveLicense: Hash mismatch");
return false;
}
video_widevine_client::sdk::File file; video_widevine_client::sdk::File file;
if (!file.ParseFromString(hashed_file.file())) { if (!file.ParseFromString(serialized_file)) {
LOGW("DeviceFiles::RetrieveLicense: Unable to parse file"); LOGW("DeviceFiles::RetrieveLicense: Unable to parse file");
return false; return false;
} }
@@ -352,18 +314,144 @@ bool DeviceFiles::LicenseExists(const std::string& key_set_id) {
return file_->Exists(path); return file_->Exists(path);
} }
bool DeviceFiles::Hash(const std::string& data, std::string* hash) { bool DeviceFiles::StoreUsageInfo(const std::string& provider_session_token,
if (!hash) return false; const CdmKeyMessage& key_request,
const CdmKeyResponse& key_response) {
if (!initialized_) {
LOGW("DeviceFiles::StoreUsageInfo: not initialized");
return false;
}
std::string serialized_file;
video_widevine_client::sdk::File file;
if (!RetrieveFile(kUsageInfoFileName, &serialized_file)) {
file.set_type(video_widevine_client::sdk::File::USAGE_INFO);
file.set_version(video_widevine_client::sdk::File::VERSION_1);
} else {
if (!file.ParseFromString(serialized_file)) {
LOGW("DeviceFiles::StoreUsageInfo: Unable to parse file");
return false;
}
}
UsageInfo* usage_info = file.mutable_usage_info();
UsageInfo_ProviderSession* provider_session = usage_info->add_sessions();
provider_session->set_token(provider_session_token.data(),
provider_session_token.size());
provider_session->set_license_request(key_request.data(),
key_request.size());
provider_session->set_license(key_response.data(),
key_response.size());
file.SerializeToString(&serialized_file);
return StoreFile(kUsageInfoFileName, serialized_file);
}
bool DeviceFiles::DeleteUsageInfo(const std::string& provider_session_token) {
if (!initialized_) {
LOGW("DeviceFiles::DeleteUsageInfo: not initialized");
return false;
}
std::string serialized_file;
if (!RetrieveFile(kUsageInfoFileName, &serialized_file)) return false;
video_widevine_client::sdk::File file;
if (!file.ParseFromString(serialized_file)) {
LOGW("DeviceFiles::DeleteUsageInfo: Unable to parse file");
return false;
}
UsageInfo* updated_info = file.mutable_usage_info();
UsageInfo info(*(const_cast<const UsageInfo*>(updated_info)));
updated_info->clear_sessions();
bool found = false;
for (int i = 0; i < info.sessions_size(); ++i) {
if (info.sessions(i).token().compare(provider_session_token) == 0) {
found = true;
} else {
updated_info->add_sessions()->set_token(info.sessions(i).token());
updated_info->add_sessions()->set_license_request(
info.sessions(i).license_request());
updated_info->add_sessions()->set_license(info.sessions(i).license());
}
}
if (!found) {
LOGW("DeviceFiles::DeleteUsageInfo: Unable to find provider session "
"token: %s", b2a_hex(provider_session_token).c_str());
return false;
}
file.SerializeToString(&serialized_file);
return StoreFile(kUsageInfoFileName, serialized_file);
}
bool DeviceFiles::DeleteUsageInfo() {
if (!initialized_) {
LOGW("DeviceFiles::DeleteUsageInfo: not initialized");
return false;
}
std::string path;
if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) {
LOGW("DeviceFiles::DeleteUsageInfo: Unable to get base path");
return false;
}
path.append(kUsageInfoFileName);
return file_->Remove(path);
}
bool DeviceFiles::RetrieveUsageInfo(std::vector<
std::pair<CdmKeyMessage, CdmKeyResponse> >* usage_info) {
if (!initialized_) {
LOGW("DeviceFiles::RetrieveUsageInfo: not initialized");
return false;
}
if (NULL == usage_info) {
LOGW("DeviceFiles::RetrieveUsageInfo: license destination not "
"provided");
return false;
}
std::string serialized_file;
if (!RetrieveFile(kUsageInfoFileName, &serialized_file)) {
std::string path;
if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) {
return false;
}
path += kUsageInfoFileName;
if (!file_->Exists(path) || 0 == file_->FileSize(path)) {
usage_info->resize(0);
return true;
}
return false;
}
video_widevine_client::sdk::File file;
if (!file.ParseFromString(serialized_file)) {
LOGW("DeviceFiles::RetrieveUsageInfo: Unable to parse file");
return false;
}
usage_info->resize(file.usage_info().sessions_size());
for (int i = 0; i < file.usage_info().sessions_size(); ++i) {
(*usage_info)[i] =
std::make_pair(file.usage_info().sessions(i).license_request(),
file.usage_info().sessions(i).license());
}
hash->resize(SHA256_DIGEST_LENGTH);
SHA256_CTX sha256;
SHA256_Init(&sha256);
SHA256_Update(&sha256, data.data(), data.size());
SHA256_Final(reinterpret_cast<unsigned char*>(&(*hash)[0]), &sha256);
return true; return true;
} }
bool DeviceFiles::StoreFile(const char* name, const std::string& data) { bool DeviceFiles::StoreFile(const char* name,
const std::string& serialized_file) {
if (!file_) { if (!file_) {
LOGW("DeviceFiles::StoreFile: Invalid file handle"); LOGW("DeviceFiles::StoreFile: Invalid file handle");
return false; return false;
@@ -374,6 +462,21 @@ bool DeviceFiles::StoreFile(const char* name, const std::string& data) {
return false; return false;
} }
// calculate SHA hash
std::string hash;
if (!Hash(serialized_file, &hash)) {
LOGW("DeviceFiles::StoreFile: Hash computation failed");
return false;
}
// Fill in hashed file data
HashedFile hash_file;
hash_file.set_file(serialized_file);
hash_file.set_hash(hash);
std::string serialized_hash_file;
hash_file.SerializeToString(&serialized_hash_file);
std::string path; std::string path;
if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) {
LOGW("DeviceFiles::StoreFile: Unable to get base path"); LOGW("DeviceFiles::StoreFile: Unable to get base path");
@@ -391,19 +494,24 @@ bool DeviceFiles::StoreFile(const char* name, const std::string& data) {
return false; return false;
} }
ssize_t bytes = file_->Write(data.data(), data.size()); ssize_t bytes = file_->Write(serialized_hash_file.data(),
serialized_hash_file.size());
file_->Close(); file_->Close();
if (bytes != static_cast<ssize_t>(data.size())) { if (bytes != static_cast<ssize_t>(serialized_hash_file.size())) {
LOGW("DeviceFiles::StoreFile: write failed: %d %d", data.size(), bytes); LOGW("DeviceFiles::StoreFile: write failed: (actual: %d, expected: %d)",
bytes,
serialized_hash_file.size());
return false; return false;
} }
LOGV("DeviceFiles::StoreFile: success: %s (%db)", path.c_str(), data.size()); LOGV("DeviceFiles::StoreFile: success: %s (%db)",
path.c_str(),
serialized_hash_file.size());
return true; return true;
} }
bool DeviceFiles::RetrieveFile(const char* name, std::string* data) { bool DeviceFiles::RetrieveFile(const char* name, std::string* serialized_file) {
if (!file_) { if (!file_) {
LOGW("DeviceFiles::RetrieveFile: Invalid file handle"); LOGW("DeviceFiles::RetrieveFile: Invalid file handle");
return false; return false;
@@ -414,8 +522,8 @@ bool DeviceFiles::RetrieveFile(const char* name, std::string* data) {
return false; return false;
} }
if (!data) { if (!serialized_file) {
LOGW("DeviceFiles::RetrieveFile: Unspecified data parameter"); LOGW("DeviceFiles::RetrieveFile: Unspecified serialized_file parameter");
return false; return false;
} }
@@ -434,7 +542,7 @@ bool DeviceFiles::RetrieveFile(const char* name, std::string* data) {
ssize_t bytes = file_->FileSize(path); ssize_t bytes = file_->FileSize(path);
if (bytes <= 0) { if (bytes <= 0) {
LOGW("DeviceFiles::RetrieveFile: File size invalid: %d", path.c_str()); LOGW("DeviceFiles::RetrieveFile: File size invalid: %s", path.c_str());
return false; return false;
} }
@@ -442,17 +550,37 @@ bool DeviceFiles::RetrieveFile(const char* name, std::string* data) {
return false; return false;
} }
data->resize(bytes); std::string serialized_hash_file;
bytes = file_->Read(&(*data)[0], data->size()); serialized_hash_file.resize(bytes);
bytes = file_->Read(&serialized_hash_file[0], serialized_hash_file.size());
file_->Close(); file_->Close();
if (bytes != static_cast<ssize_t>(data->size())) { if (bytes != static_cast<ssize_t>(serialized_hash_file.size())) {
LOGW("DeviceFiles::RetrieveFile: read failed"); LOGW("DeviceFiles::RetrieveFile: read failed");
return false; return false;
} }
LOGV("DeviceFiles::RetrieveFile: success: %s (%db)", path.c_str(), LOGV("DeviceFiles::RetrieveFile: success: %s (%db)", path.c_str(),
data->size()); serialized_hash_file.size());
HashedFile hash_file;
if (!hash_file.ParseFromString(serialized_hash_file)) {
LOGW("DeviceFiles::RetrieveFile: Unable to parse hash file");
return false;
}
std::string hash;
if (!Hash(hash_file.file(), &hash)) {
LOGW("DeviceFiles::RetrieveFile: Hash computation failed");
return false;
}
if (hash.compare(hash_file.hash())) {
LOGW("DeviceFiles::RetrieveFile: Hash mismatch");
return false;
}
*serialized_file = hash_file.file();
return true; return true;
} }
@@ -528,4 +656,14 @@ std::string DeviceFiles::GetLicenseFileNameExtension() {
return kLicenseFileNameExt; return kLicenseFileNameExt;
} }
std::string DeviceFiles::GetUsageInfoFileName() {
return kUsageInfoFileName;
}
void DeviceFiles::SetTestFile(File* file) {
if (file_) delete file_;
file_ = file;
test_file_ = true;
}
} // namespace wvcdm } // namespace wvcdm

View File

@@ -33,10 +33,21 @@ message License {
optional bytes release_server_url = 7; optional bytes release_server_url = 7;
} }
message UsageInfo {
message ProviderSession {
optional bytes token = 1;
optional bytes license_request = 2;
optional bytes license = 3;
}
repeated ProviderSession sessions = 1;
}
message File { message File {
enum FileType { enum FileType {
DEVICE_CERTIFICATE = 1; DEVICE_CERTIFICATE = 1;
LICENSE = 2; LICENSE = 2;
USAGE_INFO = 3;
} }
enum FileVersion { enum FileVersion {
@@ -47,9 +58,11 @@ message File {
optional FileVersion version = 2 [default = VERSION_1]; optional FileVersion version = 2 [default = VERSION_1];
optional DeviceCertificate device_certificate = 3; optional DeviceCertificate device_certificate = 3;
optional License license = 4; optional License license = 4;
optional UsageInfo usage_info = 5;
} }
message HashedFile { message HashedFile {
optional bytes file = 1; optional bytes file = 1;
// A raw (not hex-encoded) SHA256, taken over the bytes of 'file'.
optional bytes hash = 2; optional bytes hash = 2;
} }

View File

@@ -6,6 +6,7 @@
#include "crypto_key.h" #include "crypto_key.h"
#include "crypto_session.h" #include "crypto_session.h"
#include "device_files.h"
#include "log.h" #include "log.h"
#include "policy_engine.h" #include "policy_engine.h"
#include "properties.h" #include "properties.h"
@@ -81,15 +82,16 @@ using video_widevine_server::sdk::ClientIdentification;
using video_widevine_server::sdk::ClientIdentification_NameValue; using video_widevine_server::sdk::ClientIdentification_NameValue;
using video_widevine_server::sdk::DeviceCertificate; using video_widevine_server::sdk::DeviceCertificate;
using video_widevine_server::sdk::EncryptedClientIdentification; using video_widevine_server::sdk::EncryptedClientIdentification;
using video_widevine_server::sdk::License;
using video_widevine_server::sdk::License_KeyContainer;
using video_widevine_server::sdk::LicenseError;
using video_widevine_server::sdk::LicenseIdentification;
using video_widevine_server::sdk::LicenseRequest; using video_widevine_server::sdk::LicenseRequest;
using video_widevine_server::sdk::LicenseRequest_ContentIdentification; using video_widevine_server::sdk::LicenseRequest_ContentIdentification;
using video_widevine_server::sdk::LicenseRequest_ContentIdentification_CENC; using video_widevine_server::sdk::LicenseRequest_ContentIdentification_CENC;
using video_widevine_server::sdk::LicenseRequest_ContentIdentification_WebM; using video_widevine_server::sdk::LicenseRequest_ContentIdentification_WebM;
using video_widevine_server::sdk:: using video_widevine_server::sdk::
LicenseRequest_ContentIdentification_ExistingLicense; LicenseRequest_ContentIdentification_ExistingLicense;
using video_widevine_server::sdk::License;
using video_widevine_server::sdk::License_KeyContainer;
using video_widevine_server::sdk::LicenseError;
using video_widevine_server::sdk::SignedDeviceCertificate; using video_widevine_server::sdk::SignedDeviceCertificate;
using video_widevine_server::sdk::SignedMessage; using video_widevine_server::sdk::SignedMessage;
@@ -424,7 +426,19 @@ bool CdmLicense::PrepareKeyUpdateRequest(bool is_renewal,
LicenseRequest_ContentIdentification_ExistingLicense* current_license = LicenseRequest_ContentIdentification_ExistingLicense* current_license =
license_request.mutable_content_id()->mutable_license(); license_request.mutable_content_id()->mutable_license();
current_license->mutable_license_id()->CopyFrom(policy_engine_->license_id()); LicenseIdentification license_id = policy_engine_->license_id();
current_license->mutable_license_id()->CopyFrom(license_id);
if (!is_renewal) {
if (license_id.has_provider_session_token()) {
std::string usage_report;
if (!session_->GenerateUsageReport(license_id.provider_session_token(),
&usage_report)) {
return false;
}
current_license->set_session_usage_table_entry(usage_report);
}
}
// Get/set the nonce. This value will be reflected in the Key Control Block // Get/set the nonce. This value will be reflected in the Key Control Block
// of the license response. // of the license response.
@@ -488,7 +502,7 @@ CdmResponseType CdmLicense::HandleKeyResponse(
break; break;
case SignedMessage::SERVICE_CERTIFICATE: case SignedMessage::SERVICE_CERTIFICATE:
return CdmLicense::HandleServiceCertificateResponse(signed_response); return CdmLicense::HandleServiceCertificateResponse(signed_response);
case SignedMessage::ERROR: case SignedMessage::ERROR_RESPONSE:
return HandleKeyErrorResponse(signed_response); return HandleKeyErrorResponse(signed_response);
default: default:
LOGE("CdmLicense::HandleKeyResponse: unrecognized signed message type: %d" LOGE("CdmLicense::HandleKeyResponse: unrecognized signed message type: %d"
@@ -546,6 +560,10 @@ CdmResponseType CdmLicense::HandleKeyResponse(
return KEY_ERROR; return KEY_ERROR;
} }
std::string provider_session_token;
if (license.id().has_provider_session_token())
provider_session_token_ = license.id().provider_session_token();
if (license.policy().has_renewal_server_url()) { if (license.policy().has_renewal_server_url()) {
server_url_ = license.policy().renewal_server_url(); server_url_ = license.policy().renewal_server_url();
} }
@@ -556,8 +574,8 @@ CdmResponseType CdmLicense::HandleKeyResponse(
signed_response.signature(), signed_response.signature(),
mac_key_iv, mac_key_iv,
mac_key, mac_key,
key_array.size(), key_array,
&key_array[0]); provider_session_token);
if (KEY_ADDED == resp) { if (KEY_ADDED == resp) {
loaded_keys_.clear(); loaded_keys_.clear();
@@ -587,7 +605,7 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse(
return KEY_ERROR; return KEY_ERROR;
} }
if (signed_response.type() == SignedMessage::ERROR) { if (signed_response.type() == SignedMessage::ERROR_RESPONSE) {
return HandleKeyErrorResponse(signed_response); return HandleKeyErrorResponse(signed_response);
} }
@@ -615,6 +633,14 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse(
server_url_ = license.policy().renewal_server_url(); server_url_ = license.policy().renewal_server_url();
} }
} }
else {
if (license.id().has_provider_session_token()) {
provider_session_token_ = license.id().provider_session_token();
session_->ReleaseUsageInformation(signed_response.msg(),
signed_response.signature(),
provider_session_token_);
}
}
policy_engine_->UpdateLicense(license); policy_engine_->UpdateLicense(license);
@@ -631,8 +657,9 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse(
} }
bool CdmLicense::RestoreOfflineLicense( bool CdmLicense::RestoreOfflineLicense(
CdmKeyMessage& license_request, CdmKeyResponse& license_response, const CdmKeyMessage& license_request,
CdmKeyResponse& license_renewal_response) { const CdmKeyResponse& license_response,
const CdmKeyResponse& license_renewal_response) {
if (license_request.empty() || license_response.empty()) { if (license_request.empty() || license_response.empty()) {
LOGE( LOGE(
@@ -675,6 +702,77 @@ bool CdmLicense::RestoreOfflineLicense(
return true; return true;
} }
bool CdmLicense::RestoreUsageLicense(const CdmKeyMessage& license_request,
const CdmKeyResponse& license_response) {
if (license_request.empty() || license_response.empty()) {
LOGE(
"CdmLicense::RestoreUsageLicense: key_request or response empty: %u %u",
license_request.size(), license_response.size());
return false;
}
SignedMessage signed_request;
if (!signed_request.ParseFromString(license_request)) {
LOGE("CdmLicense::RestoreUsageLicense: license_request parse failed");
return false;
}
if (signed_request.type() != SignedMessage::LICENSE_REQUEST) {
LOGE(
"CdmLicense::RestoreUsageLicense: license request type: expected = %d,"
" actual = %d",
SignedMessage::LICENSE_REQUEST, signed_request.type());
return false;
}
key_request_ = signed_request.msg();
SignedMessage signed_response;
if (!signed_response.ParseFromString(license_response)) {
LOGE("CdmLicense::RestoreUsageLicense: unable to parse signed license"
" response");
return false;
}
if (SignedMessage::LICENSE != signed_response.type()) {
LOGE("CdmLicense::RestoreUsageLicense: unrecognized signed message type: %d"
, signed_response.type());
return false;
}
if (Properties::use_certificates_as_identification()) {
if (!signed_response.has_session_key()) {
LOGE("CdmLicense::RestoreUsageLicense: no session keys present");
return false;
}
if (!session_->GenerateDerivedKeys(key_request_,
signed_response.session_key()))
return false;
} else {
if (!session_->GenerateDerivedKeys(key_request_)) return false;
}
if (!signed_response.has_signature()) {
LOGE("CdmLicense::RestoreUsageLicense: license response is not signed");
return false;
}
License license;
if (!license.ParseFromString(signed_response.msg())) {
LOGE("CdmLicense::RestoreUsageLicense: unable to parse license response");
return false;
}
policy_engine_->SetLicense(license);
return true;
}
bool CdmLicense::IsKeyLoaded(const KeyId& key_id) {
return loaded_keys_.find(key_id) != loaded_keys_.end();
}
bool CdmLicense::PrepareServiceCertificateRequest(CdmKeyMessage* signed_request, bool CdmLicense::PrepareServiceCertificateRequest(CdmKeyMessage* signed_request,
std::string* server_url) { std::string* server_url) {
if (!initialized_) { if (!initialized_) {
@@ -798,8 +896,4 @@ bool CdmLicense::PrepareContentId(const CdmLicenseType license_type,
return true; return true;
} }
bool CdmLicense::IsKeyLoaded(const KeyId& key_id) {
return loaded_keys_.find(key_id) != loaded_keys_.end();
}
} // namespace wvcdm } // namespace wvcdm

View File

@@ -27,6 +27,7 @@ message LicenseIdentification {
optional bytes purchase_id = 3; optional bytes purchase_id = 3;
optional LicenseType type = 4; optional LicenseType type = 4;
optional int32 version = 5; optional int32 version = 5;
optional bytes provider_session_token = 6;
} }
message License { message License {
@@ -126,6 +127,8 @@ message License {
HDCP_NONE = 0; HDCP_NONE = 0;
HDCP_V1 = 1; HDCP_V1 = 1;
HDCP_V2 = 2; HDCP_V2 = 2;
HDCP_V2_1 = 3;
HDCP_V2_2 = 4;
} }
optional HDCP hdcp = 1 [default = HDCP_NONE]; optional HDCP hdcp = 1 [default = HDCP_NONE];
@@ -139,6 +142,15 @@ message License {
optional CGMS cgms_flags = 2 [default = CGMS_NONE]; optional CGMS cgms_flags = 2 [default = CGMS_NONE];
} }
message VideoResolutionConstraint {
// Minimum and maximum video resolutions in the range (height x width).
optional uint32 min_resolution_pixels = 1;
optional uint32 max_resolution_pixels = 2;
// Optional output protection requirements for this range. If not
// specified, the OutputProtection in the KeyContainer applies.
optional OutputProtection required_protection = 3;
}
message OperatorSessionKeyPermissions { message OperatorSessionKeyPermissions {
// Permissions/key usage flags for operator service keys // Permissions/key usage flags for operator service keys
// (type = OPERATOR_SESSION). // (type = OPERATOR_SESSION).
@@ -157,12 +169,20 @@ message License {
optional OutputProtection requested_protection = 7; optional OutputProtection requested_protection = 7;
optional KeyControl key_control = 8; optional KeyControl key_control = 8;
optional OperatorSessionKeyPermissions operator_session_key_permissions = 9; optional OperatorSessionKeyPermissions operator_session_key_permissions = 9;
// Optional video resolution constraints. If the video resolution of the
// content being decrypted/decoded falls within one of the specified ranges,
// the optional required_protections may be applied. Otherwise an error will
// be reported.
repeated VideoResolutionConstraint video_resolution_constraints = 10;
} }
optional LicenseIdentification id = 1; optional LicenseIdentification id = 1;
optional Policy policy = 2; optional Policy policy = 2;
repeated KeyContainer key = 3; repeated KeyContainer key = 3;
optional int64 license_start_time = 4; optional int64 license_start_time = 4;
optional bool remote_attestation_verified = 5 [default = false];
// Client token generated by the content provider. Optional.
optional bytes provider_client_token = 6;
} }
enum ProtocolVersion { enum ProtocolVersion {
@@ -187,6 +207,8 @@ message LicenseRequest {
message ExistingLicense { message ExistingLicense {
optional LicenseIdentification license_id = 1; optional LicenseIdentification license_id = 1;
optional int64 seconds_since_started = 2; optional int64 seconds_since_started = 2;
optional int64 seconds_since_last_played = 3;
optional bytes session_usage_table_entry = 4;
} }
// Exactly one of these must be present. // Exactly one of these must be present.
@@ -233,11 +255,22 @@ message LicenseError {
optional Error error_code = 1; optional Error error_code = 1;
} }
message RemoteAttestation {
// Encrypted ClientIdentification message containing the device remote
// attestation certificate. Required.
optional EncryptedClientIdentification certificate = 1;
// Bytes of salt which were added to the remote attestation challenge prior to
// signing it. Required.
optional bytes salt = 2;
// Signed remote attestation challenge + salt. Required.
optional bytes signature = 3;
}
message SignedMessage { message SignedMessage {
enum MessageType { enum MessageType {
LICENSE_REQUEST = 1; LICENSE_REQUEST = 1;
LICENSE = 2; LICENSE = 2;
ERROR = 3; ERROR_RESPONSE = 3;
SERVICE_CERTIFICATE_REQUEST = 4; SERVICE_CERTIFICATE_REQUEST = 4;
SERVICE_CERTIFICATE = 5; SERVICE_CERTIFICATE = 5;
} }
@@ -246,10 +279,20 @@ message SignedMessage {
optional bytes msg = 2; optional bytes msg = 2;
optional bytes signature = 3; optional bytes signature = 3;
optional bytes session_key = 4; optional bytes session_key = 4;
// Remote attestation data which will be present in the initial license
// request for ChromeOS client devices operating in verified mode. Remote
// attestation challenge data is |msg| field above. Optional.
optional RemoteAttestation remote_attestation = 5;
} }
// This message is used to pass optional data on initial license issuance. // This message is used to pass optional data on initial license issuance.
message SessionInit { message SessionInit {
enum ReplayControl {
NO_SESSION_USAGE = 0;
NONCE_REQUIRED_AND_NEW_SESSION_USAGE = 1;
NONCE_REQUIRED_OR_EXISTING_SESSION_USAGE = 2;
}
optional bytes session_id = 1; optional bytes session_id = 1;
optional bytes purchase_id = 2; optional bytes purchase_id = 2;
// master_signing_key should be 128 bits in length. // master_signing_key should be 128 bits in length.
@@ -258,6 +301,19 @@ message SessionInit {
// (server || client) HMAC-SHA256 keys. // (server || client) HMAC-SHA256 keys.
optional bytes signing_key = 4; optional bytes signing_key = 4;
optional int64 license_start_time = 5; optional int64 license_start_time = 5;
// Client token for the session. This session is for use by the license
// provider, and is akin to a client cookie. It will be copied to
// License::provider_client_token, and sent back by the client in
// ClientIdentification::provider_client_token in all license requests
// thereafter.
optional bytes provider_client_token = 6;
// Session token for the session. This token is for use by the license
// provider, and is akin to a session cookie. It will be copied to
// LicenseIdentfication::provider_session_token, and sent back in all
// license renewal and release requests for the session thereafter.
optional bytes provider_session_token = 7;
// Replay control indicator which will be encoded into V9+ KeyControl blocks.
optional ReplayControl replay_control = 8 [default = NO_SESSION_USAGE];
} }
// This message is used by the server to preserve and restore session state. // This message is used by the server to preserve and restore session state.
@@ -280,7 +336,7 @@ message SessionState {
// in the case of X509 certificates, the certificate authority to use. // in the case of X509 certificates, the certificate authority to use.
message ProvisioningOptions { message ProvisioningOptions {
enum CertificateType { enum CertificateType {
RSA_WIDEVINE = 0; // Default. The original certificate type. WIDEVINE_DRM = 0; // Default. The original certificate type.
X509 = 1; // X.509 certificate. X509 = 1; // X.509 certificate.
} }
@@ -336,6 +392,7 @@ message ClientIdentification {
enum TokenType { enum TokenType {
KEYBOX = 0; KEYBOX = 0;
DEVICE_CERTIFICATE = 1; DEVICE_CERTIFICATE = 1;
REMOTE_ATTESTATION_CERTIFICATE = 2;
} }
message NameValue { message NameValue {
@@ -343,12 +400,36 @@ message ClientIdentification {
optional string value = 2; optional string value = 2;
} }
// Capabilities which not all clients may support. Used for the license
// exchange protocol only.
message ClientCapabilities {
enum HdcpVersion {
HDCP_NONE = 0;
HDCP_V1 = 1;
HDCP_V2 = 2;
HDCP_V2_1 = 3;
HDCP_V2_2 = 4;
}
optional bool client_token = 1 [default = false];
optional bool session_token = 2 [default = false];
optional bool video_resolution_constraints = 3 [default = false];
optional HdcpVersion max_hdcp_version = 4 [default = HDCP_NONE];
}
// Type of factory-provisioned device root of trust. Optional. // Type of factory-provisioned device root of trust. Optional.
optional TokenType type = 1 [default = KEYBOX]; optional TokenType type = 1 [default = KEYBOX];
// Factory-provisioned device root of trust. Required. // Factory-provisioned device root of trust. Required.
optional bytes token = 2; optional bytes token = 2;
// Optional client information name/value pairs. // Optional client information name/value pairs.
repeated NameValue client_info = 3; repeated NameValue client_info = 3;
// Client token generated by the content provider. Optional.
optional bytes provider_client_token = 4;
// Number of licenses received by the client to which the token above belongs.
// Only present if client_token is specified.
optional uint32 license_counter = 5;
// List of non-baseline client capabilities.
optional ClientCapabilities client_capabilities = 6;
} }
// EncryptedClientIdentification message used to hold ClientIdentification // EncryptedClientIdentification message used to hold ClientIdentification
@@ -359,16 +440,16 @@ message EncryptedClientIdentification {
optional string service_id = 1; optional string service_id = 1;
// Serial number for the service certificate for which ClientIdentification is // Serial number for the service certificate for which ClientIdentification is
// encrypted. // encrypted.
optional string service_certificate_serial_number = 2; optional bytes service_certificate_serial_number = 2;
// Serialized ClientIdentification message, encrypted with the privacy key // Serialized ClientIdentification message, encrypted with the privacy key using
// using AES-128-CBC with PKCS#5 padding. // AES-128-CBC with PKCS#5 padding.
optional bytes encrypted_client_id = 3; optional bytes encrypted_client_id = 3;
// Initialization vector needed to decrypt encrypted_client_id. // Initialization vector needed to decrypt encrypted_client_id.
optional bytes encrypted_client_id_iv = 4; optional bytes encrypted_client_id_iv = 4;
// AES-128 privacy key, encrytped with the service public public key using // AES-128 privacy key, encrytped with the service public public key using
// RSA-OAEP. // RSA-OAEP.
optional bytes encrypted_privacy_key = 5; optional bytes encrypted_privacy_key = 5;
}; }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// device_certificate.proto // device_certificate.proto
@@ -400,9 +481,10 @@ message DeviceCertificate {
// Widevine system ID for the device. Required for intermediate and // Widevine system ID for the device. Required for intermediate and
// user device certificates. // user device certificates.
optional uint32 system_id = 5; optional uint32 system_id = 5;
// True if the certificate corresponds to a test (non production) device or // Deprecated field, which used to indicate whether the device was a test
// service. Optional. // (non-production) device. The test_device field in ProvisionedDeviceInfo
optional bool test_device = 6 [default = false]; // below should be observed instead.
optional bool test_device_deprecated = 6 [deprecated = true];
// Service identifier (web origin) for the service which owns the certificate. // Service identifier (web origin) for the service which owns the certificate.
// Required for service certificates. // Required for service certificates.
optional string service_id = 7; optional string service_id = 7;

View File

@@ -7,38 +7,60 @@
#include "privacy_crypto.h" #include "privacy_crypto.h"
#include <openssl/aes.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/sha.h>
#include "log.h" #include "log.h"
#include "openssl/aes.h"
#include "openssl/bio.h"
#include "openssl/err.h"
#include "openssl/pem.h"
#include "openssl/sha.h"
namespace { namespace {
const int kPssSaltLength = 20; const int kPssSaltLength = 20;
const int kRsaPkcs1OaepPaddingLength = 41; const int kRsaPkcs1OaepPaddingLength = 41;
RSA* GetKey(const std::string& serialized_key) {
BIO* bio = BIO_new_mem_buf(const_cast<char*>(serialized_key.data()),
serialized_key.size());
if (bio == NULL) {
LOGE("GetKey: BIO_new_mem_buf returned NULL");
return NULL;
}
RSA* key = d2i_RSAPublicKey_bio(bio, NULL);
BIO_free(bio);
if (key == NULL) {
LOGE("GetKey: RSA key deserialization failure: %s",
ERR_error_string(ERR_get_error(), NULL));
return NULL;
}
return key;
}
void FreeKey(RSA* key) {
if (key != NULL) {
RSA_free(key);
}
}
} // namespace } // namespace
namespace wvcdm { namespace wvcdm {
AesCbcKey::AesCbcKey() {}
AesCbcKey::~AesCbcKey() {}
bool AesCbcKey::Init(const std::string& key) { bool AesCbcKey::Init(const std::string& key) {
if (key.empty()) {
LOGE("AesCbcKey::Init: no key provided");
return false;
}
if (key.size() != AES_BLOCK_SIZE) { if (key.size() != AES_BLOCK_SIZE) {
LOGE("AesCbcKey::Init: unexpected key size: %d", key.size()); LOGE("AesCbcKey::Init: unexpected key size: %d", key.size());
return false; return false;
} }
EVP_CIPHER_CTX_init(&ctx_); key_ = key;
if (EVP_EncryptInit(&ctx_, EVP_aes_128_cbc(),
reinterpret_cast<const uint8_t*>(&key[0]), NULL) == 0) {
LOGE("AesCbcKey::Init: AES CBC key setup failure: %s",
ERR_error_string(ERR_get_error(), NULL));
return false;
}
initialized_ = true;
return true; return true;
} }
@@ -60,14 +82,16 @@ bool AesCbcKey::Encrypt(const std::string& in, std::string* out,
LOGE("AesCbcKey::Encrypt: crypttext destination not provided"); LOGE("AesCbcKey::Encrypt: crypttext destination not provided");
return false; return false;
} }
if (!initialized_) { if (key_.empty()) {
LOGE("AesCbcKey::Encrypt: AES key not initialized"); LOGE("AesCbcKey::Encrypt: AES key not initialized");
return false; return false;
} }
if (EVP_EncryptInit(&ctx_, NULL, NULL, EVP_CIPHER_CTX ctx;
reinterpret_cast<const uint8_t*>(iv->data())) == 0) { if (EVP_EncryptInit(&ctx, EVP_aes_128_cbc(),
LOGE("AesCbcKey::Encrypt: AES CBC iv setup failure: %s", reinterpret_cast<uint8_t*>(&key_[0]),
reinterpret_cast<uint8_t*>(&(*iv)[0])) == 0) {
LOGE("AesCbcKey::Encrypt: AES CBC setup failure: %s",
ERR_error_string(ERR_get_error(), NULL)); ERR_error_string(ERR_get_error(), NULL));
return false; return false;
} }
@@ -75,7 +99,7 @@ bool AesCbcKey::Encrypt(const std::string& in, std::string* out,
out->resize(in.size() + AES_BLOCK_SIZE); out->resize(in.size() + AES_BLOCK_SIZE);
int out_length = out->size(); int out_length = out->size();
if (EVP_EncryptUpdate( if (EVP_EncryptUpdate(
&ctx_, reinterpret_cast<uint8_t*>(&(*out)[0]), &out_length, &ctx, reinterpret_cast<uint8_t*>(&(*out)[0]), &out_length,
reinterpret_cast<uint8_t*>(const_cast<char*>(in.data())), reinterpret_cast<uint8_t*>(const_cast<char*>(in.data())),
in.size()) == 0) { in.size()) == 0) {
LOGE("AesCbcKey::Encrypt: encryption failure: %s", LOGE("AesCbcKey::Encrypt: encryption failure: %s",
@@ -84,7 +108,7 @@ bool AesCbcKey::Encrypt(const std::string& in, std::string* out,
} }
int padding = 0; int padding = 0;
if (EVP_EncryptFinal(&ctx_, reinterpret_cast<uint8_t*>(&(*out)[out_length]), if (EVP_EncryptFinal(&ctx, reinterpret_cast<uint8_t*>(&(*out)[out_length]),
&padding) == 0) { &padding) == 0) {
LOGE("AesCbcKey::Encrypt: PKCS7 padding failure: %s", LOGE("AesCbcKey::Encrypt: PKCS7 padding failure: %s",
ERR_error_string(ERR_get_error(), NULL)); ERR_error_string(ERR_get_error(), NULL));
@@ -95,34 +119,17 @@ bool AesCbcKey::Encrypt(const std::string& in, std::string* out,
return true; return true;
} }
RsaPublicKey::~RsaPublicKey() { RsaPublicKey::RsaPublicKey() {}
if (key_ != NULL) {
RSA_free(key_); RsaPublicKey::~RsaPublicKey() {}
}
}
bool RsaPublicKey::Init(const std::string& serialized_key) { bool RsaPublicKey::Init(const std::string& serialized_key) {
if (serialized_key.empty()) { if (serialized_key.empty()) {
LOGE("RsaPublicKey::Init: no serialized key provided"); LOGE("RsaPublicKey::Init: no serialized key provided");
return false; return false;
} }
BIO* bio = BIO_new_mem_buf(const_cast<char*>(serialized_key.data()), serialized_key_ = serialized_key;
serialized_key.size());
if (bio == NULL) {
LOGE("RsaPublicKey::Init: BIO_new_mem_buf returned NULL");
return false;
}
key_ = d2i_RSAPublicKey_bio(bio, NULL);
BIO_free(bio);
if (key_ == NULL) {
LOGE("RsaPublicKey::Init: RSA key deserialization failure: %s",
ERR_error_string(ERR_get_error(), NULL));
return false;
}
return true; return true;
} }
@@ -136,36 +143,46 @@ bool RsaPublicKey::Encrypt(const std::string& clear_message,
LOGE("RsaPublicKey::Encrypt: no encrypt message buffer provided"); LOGE("RsaPublicKey::Encrypt: no encrypt message buffer provided");
return false; return false;
} }
if (key_ == NULL) { if (serialized_key_.empty()) {
LOGE("RsaPublicKey::Encrypt: RSA key not initialized"); LOGE("RsaPublicKey::Encrypt: RSA key not initialized");
return false; return false;
} }
int rsa_size = RSA_size(key_); RSA* key = GetKey(serialized_key_);
if (key == NULL) {
// Error already logged by GetKey.
return false;
}
int rsa_size = RSA_size(key);
if (static_cast<int>(clear_message.size()) > if (static_cast<int>(clear_message.size()) >
rsa_size - kRsaPkcs1OaepPaddingLength) { rsa_size - kRsaPkcs1OaepPaddingLength) {
LOGE("RsaPublicKey::Encrypt: message too large to be encrypted (actual %d", LOGE("RsaPublicKey::Encrypt: message too large to be encrypted (actual %d",
" max allowed %d)", clear_message.size(), " max allowed %d)", clear_message.size(),
rsa_size - kRsaPkcs1OaepPaddingLength); rsa_size - kRsaPkcs1OaepPaddingLength);
FreeKey(key);
return false; return false;
} }
encrypted_message->assign(rsa_size, 0); encrypted_message->assign(rsa_size, 0);
if (RSA_public_encrypt( if (RSA_public_encrypt(
clear_message.size(), clear_message.size(),
const_cast<unsigned char*>( const_cast<unsigned char*>(
reinterpret_cast<const unsigned char*>(clear_message.data())), reinterpret_cast<const unsigned char*>(clear_message.data())),
reinterpret_cast<unsigned char*>(&(*encrypted_message)[0]), key_, reinterpret_cast<unsigned char*>(&(*encrypted_message)[0]), key,
RSA_PKCS1_OAEP_PADDING) != rsa_size) { RSA_PKCS1_OAEP_PADDING) != rsa_size) {
LOGE("RsaPublicKey::Encrypt: encrypt failure: %s", LOGE("RsaPublicKey::Encrypt: encrypt failure: %s",
ERR_error_string(ERR_get_error(), NULL)); ERR_error_string(ERR_get_error(), NULL));
FreeKey(key);
return false; return false;
} }
return true; return true;
} }
bool RsaPublicKey::VerifySignature(const std::string& message, bool RsaPublicKey::VerifySignature(const std::string& message,
const std::string& signature) { const std::string& signature) {
if (key_ == NULL) { if (serialized_key_.empty()) {
LOGE("RsaPublicKey::VerifySignature: RSA key not initialized"); LOGE("RsaPublicKey::VerifySignature: RSA key not initialized");
return false; return false;
} }
@@ -173,25 +190,33 @@ bool RsaPublicKey::VerifySignature(const std::string& message,
LOGE("RsaPublicKey::VerifySignature: signed message is empty"); LOGE("RsaPublicKey::VerifySignature: signed message is empty");
return false; return false;
} }
RSA* key = GetKey(serialized_key_);
if (key == NULL) {
// Error already logged by GetKey.
return false;
}
int rsa_size = RSA_size(key_); int rsa_size = RSA_size(key);
if (static_cast<int>(signature.size()) != rsa_size) { if (static_cast<int>(signature.size()) != rsa_size) {
LOGE( LOGE(
"RsaPublicKey::VerifySignature: message signature is of the wrong " "RsaPublicKey::VerifySignature: message signature is of the wrong "
"size (expected %d, actual %d)", "size (expected %d, actual %d)",
rsa_size, signature.size()); rsa_size, signature.size());
FreeKey(key);
return false; return false;
} }
// Decrypt the signature. // Decrypt the signature.
std::string padded_digest(signature.size(), 0); std::string padded_digest(signature.size(), 0);
if (RSA_public_decrypt( if (RSA_public_decrypt(
signature.size(), signature.size(),
const_cast<unsigned char*>( const_cast<unsigned char*>(
reinterpret_cast<const unsigned char*>(signature.data())), reinterpret_cast<const unsigned char*>(signature.data())),
reinterpret_cast<unsigned char*>(&padded_digest[0]), key_, reinterpret_cast<unsigned char*>(&padded_digest[0]), key,
RSA_NO_PADDING) != rsa_size) { RSA_NO_PADDING) != rsa_size) {
LOGE("RsaPublicKey::VerifySignature: RSA public decrypt failure: %s", LOGE("RsaPublicKey::VerifySignature: RSA public decrypt failure: %s",
ERR_error_string(ERR_get_error(), NULL)); ERR_error_string(ERR_get_error(), NULL));
FreeKey(key);
return false; return false;
} }
@@ -202,12 +227,13 @@ bool RsaPublicKey::VerifySignature(const std::string& message,
// Verify PSS padding. // Verify PSS padding.
if (RSA_verify_PKCS1_PSS( if (RSA_verify_PKCS1_PSS(
key_, reinterpret_cast<const unsigned char*>(message_digest.data()), key, reinterpret_cast<const unsigned char*>(message_digest.data()),
EVP_sha1(), EVP_sha1(),
reinterpret_cast<const unsigned char*>(padded_digest.data()), reinterpret_cast<const unsigned char*>(padded_digest.data()),
kPssSaltLength) == 0) { kPssSaltLength) == 0) {
LOGE("RsaPublicKey::VerifySignature: RSA verify failure: %s", LOGE("RsaPublicKey::VerifySignature: RSA verify failure: %s",
ERR_error_string(ERR_get_error(), NULL)); ERR_error_string(ERR_get_error(), NULL));
FreeKey(key);
return false; return false;
} }

View File

@@ -0,0 +1,43 @@
// Copyright 2013 Google Inc. All Rights Reserved.
//
// Description:
// Dummy version of privacy crypto classes for systems which
// can't tolerate OpenSSL as a dependency.
//
#include "privacy_crypto.h"
namespace wvcdm {
AesCbcKey::AesCbcKey() {}
AesCbcKey::~AesCbcKey() {}
bool AesCbcKey::Init(const std::string& key) {
return false;
}
bool AesCbcKey::Encrypt(const std::string& in, std::string* out,
std::string* iv) {
return false;
}
RsaPublicKey::RsaPublicKey() {}
RsaPublicKey::~RsaPublicKey() {}
bool RsaPublicKey::Init(const std::string& serialized_key) {
return false;
}
bool RsaPublicKey::Encrypt(const std::string& clear_message,
std::string* encrypted_message) {
return false;
}
bool RsaPublicKey::VerifySignature(const std::string& message,
const std::string& signature) {
return false;
}
} // namespace wvcdm

View File

@@ -77,11 +77,11 @@ std::string b2a_hex(const std::string& byte) {
// Filename-friendly base64 encoding (RFC4648), commonly referred to // Filename-friendly base64 encoding (RFC4648), commonly referred to
// as Base64WebSafeEncode. // as Base64WebSafeEncode.
// This is the encoding required to interface with the provisioning //
// server's Apiary interface as well as for certain license server // This is the encoding required to interface with the provisioning server, as
// transactions. It is also used for logging certain strings. // well as for certain license server transactions. It is also used for logging
// The difference between web safe encoding vs regular encoding is that // certain strings. The difference between web safe encoding vs regular encoding
// the web safe version replaces '+' with '-' and '/' with '_'. // is that the web safe version replaces '+' with '-' and '/' with '_'.
std::string Base64SafeEncode(const std::vector<uint8_t>& bin_input) { std::string Base64SafeEncode(const std::vector<uint8_t>& bin_input) {
if (bin_input.empty()) { if (bin_input.empty()) {
return std::string(); return std::string();

View File

@@ -33,7 +33,6 @@ wvcdm::KeyId g_key_id_pssh;
wvcdm::KeyId g_key_id_unwrapped; wvcdm::KeyId g_key_id_unwrapped;
wvcdm::CdmKeySystem g_key_system; wvcdm::CdmKeySystem g_key_system;
std::string g_license_server; std::string g_license_server;
std::string g_port;
wvcdm::KeyId g_wrong_key_id; wvcdm::KeyId g_wrong_key_id;
int g_use_full_path = 0; // cannot use boolean in getopt_long int g_use_full_path = 0; // cannot use boolean in getopt_long
@@ -138,16 +137,16 @@ class WvCdmEngineTest : public testing::Test {
std::string GetKeyRequestResponse(const std::string& server_url, std::string GetKeyRequestResponse(const std::string& server_url,
const std::string& client_auth) { const std::string& client_auth) {
// Use secure connection and chunk transfer coding. // Use secure connection and chunk transfer coding.
UrlRequest url_request(server_url + client_auth, g_port, true, true); UrlRequest url_request(server_url + client_auth);
if (!url_request.is_connected()) { if (!url_request.is_connected()) {
return ""; return "";
} }
url_request.PostRequest(key_msg_); url_request.PostRequest(key_msg_);
std::string response; std::string response;
int resp_bytes = url_request.GetResponse(&response); bool ok = url_request.GetResponse(&response);
LOGD("response:\r\n%s", response.c_str()); LOGD("response: %s\n", response.c_str());
LOGD("end %d bytes response dump", resp_bytes); EXPECT_TRUE(ok);
int status_code = url_request.GetStatusCode(response); int status_code = url_request.GetStatusCode(response);
EXPECT_EQ(kHttpOk, status_code); EXPECT_EQ(kHttpOk, status_code);
@@ -254,14 +253,12 @@ int main(int argc, char **argv) {
// The following variables are configurable through command line options. // The following variables are configurable through command line options.
g_license_server.assign(config.license_server()); g_license_server.assign(config.license_server());
g_key_id_pssh.assign(config.key_id()); g_key_id_pssh.assign(config.key_id());
g_port.assign(config.port());
std::string license_server(g_license_server); std::string license_server(g_license_server);
int show_usage = 0; int show_usage = 0;
static const struct option long_options[] = { static const struct option long_options[] = {
{ "use_full_path", no_argument, &g_use_full_path, 0 }, { "use_full_path", no_argument, &g_use_full_path, 0 },
{ "keyid", required_argument, NULL, 'k' }, { "keyid", required_argument, NULL, 'k' },
{ "port", required_argument, NULL, 'p' },
{ "server", required_argument, NULL, 's' }, { "server", required_argument, NULL, 's' },
{ "vmodule", required_argument, NULL, 0 }, { "vmodule", required_argument, NULL, 0 },
{ "v", required_argument, NULL, 0 }, { "v", required_argument, NULL, 0 },
@@ -277,11 +274,6 @@ int main(int argc, char **argv) {
g_key_id_pssh.assign(optarg); g_key_id_pssh.assign(optarg);
break; break;
} }
case 'p': {
g_port.clear();
g_port.assign(optarg);
break;
}
case 's': { case 's': {
g_license_server.clear(); g_license_server.clear();
g_license_server.assign(optarg); g_license_server.assign(optarg);
@@ -304,11 +296,6 @@ int main(int argc, char **argv) {
std::cout << " enclose multiple arguments in '' when using adb shell" << 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 << " 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 << 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 << "configure the license server url, please include http[s] in the url" << std::endl;
std::cout << std::setw(30) << std::left << " "; std::cout << std::setw(30) << std::left << " ";
@@ -327,12 +314,10 @@ int main(int argc, char **argv) {
std::cout << std::endl; std::cout << std::endl;
std::cout << "Server: " << g_license_server << std::endl; std::cout << "Server: " << g_license_server << std::endl;
std::cout << "Port: " << g_port << std::endl;
std::cout << "KeyID: " << g_key_id_pssh << std::endl << std::endl; std::cout << "KeyID: " << g_key_id_pssh << std::endl << std::endl;
g_key_id_pssh = wvcdm::a2bs_hex(g_key_id_pssh); g_key_id_pssh = wvcdm::a2bs_hex(g_key_id_pssh);
config.set_license_server(g_license_server); config.set_license_server(g_license_server);
config.set_port(g_port);
config.set_key_id(g_key_id_pssh); config.set_key_id(g_key_id_pssh);
// Extract the key ID from the PSSH box. // Extract the key ID from the PSSH box.

View File

@@ -3,14 +3,23 @@
#include "config_test_env.h" #include "config_test_env.h"
namespace { namespace {
const std::string kWidevineKeySystem = "com.widevine.alpha";
// Youtube Content Protection license server data // Youtube Content Protection license server data
const std::string kYtCpLicenseServer = const std::string kYtCpLicenseServer =
"http://wv-ref-eme-player.appspot.com/proxy"; "http://wv-ref-eme-player.appspot.com/proxy";
const std::string kYtCpClientAuth = ""; const std::string kYtCpClientAuth = "";
const std::string kYtCpKeyId = const std::string kYtCpKeyId =
"000000347073736800000000" // blob size and pssh "000000427073736800000000" // blob size and pssh
"EDEF8BA979D64ACEA3C827DCD51D21ED00000014" // Widevine system id "EDEF8BA979D64ACEA3C827DCD51D21ED00000022" // Widevine system id
"0801121030313233343536373839616263646566"; // pssh data "08011a0d7769646576696e655f7465737422" // pssh data (streaming)
"0f73747265616d696e675f636c697031";
const std::string kYtCpOfflineKeyId =
"000000407073736800000000" // blob size and pssh
"EDEF8BA979D64ACEA3C827DCD51D21ED00000020" // Widevine system id
"08011a0d7769646576696e655f7465737422" //pssh data (offline)
"0d6f66666c696e655f636c697031";
// Youtube license server data // Youtube license server data
const std::string kYtLicenseServer = const std::string kYtLicenseServer =
@@ -34,6 +43,13 @@ const std::string kGpLicenseServer =
const std::string kGpClientAuth = const std::string kGpClientAuth =
"?source=YOUTUBE&video_id=EGHC6OHNbOo&oauth=ya.gtsqawidevine"; "?source=YOUTUBE&video_id=EGHC6OHNbOo&oauth=ya.gtsqawidevine";
const std::string kGpClientOfflineQueryParameters =
"&offline=true";
const std::string kGpClientOfflineRenewalQueryParameters =
"&offline=true&renewal=true";
const std::string kGpClientOfflineReleaseQueryParameters =
"&offline=true&release=true";
const std::string kGpKeyId = const std::string kGpKeyId =
"000000347073736800000000" // blob size and pssh "000000347073736800000000" // blob size and pssh
"edef8ba979d64acea3c827dcd51d21ed00000014" // Widevine system id "edef8ba979d64acea3c827dcd51d21ed00000014" // Widevine system id
@@ -63,23 +79,49 @@ const std::string kServerSdkLicenseServer =
const wvcdm::ConfigTestEnv::LicenseServerConfiguration license_servers[] = { const wvcdm::ConfigTestEnv::LicenseServerConfiguration license_servers[] = {
{ wvcdm::kGooglePlayServer, kGpLicenseServer, kGpClientAuth, kGpKeyId, { wvcdm::kGooglePlayServer, kGpLicenseServer, kGpClientAuth, kGpKeyId,
kDefaultHttpsPort, true, true }, kGpKeyId },
{ wvcdm::kYouTubeContentProtectionServer, kYtCpLicenseServer, { wvcdm::kYouTubeContentProtectionServer, kYtCpLicenseServer,
kYtCpClientAuth, kYtCpKeyId, kDefaultHttpPort, false, false } kYtCpClientAuth, kYtCpKeyId, kYtCpOfflineKeyId }
}; };
} // namespace } // namespace
namespace wvcdm { namespace wvcdm {
ConfigTestEnv::ConfigTestEnv(LicenseServerId server_id) ConfigTestEnv::ConfigTestEnv(LicenseServerId server_id) {
: client_auth_(license_servers[server_id].client_tag), Init(server_id);
key_id_(license_servers[server_id].key_id), }
key_system_("com.widevine.alpha"),
license_server_(license_servers[server_id].url), ConfigTestEnv::ConfigTestEnv(LicenseServerId server_id, bool streaming) {
port_(license_servers[server_id].port), Init(server_id);
provisioning_server_url_(kProductionProvisioningServerUrl), if (!streaming)
use_chunked_transfer_(license_servers[server_id].use_chunked_transfer), key_id_ = license_servers[server_id].offline_key_id;
use_secure_transfer_(license_servers[server_id].use_secure_transfer), }
wrong_key_id_(kWrongKeyId) {}
ConfigTestEnv::ConfigTestEnv(LicenseServerId server_id, bool streaming,
bool renew, bool release) {
Init(server_id);
if (!streaming) {
key_id_ = license_servers[server_id].offline_key_id;
if (wvcdm::kGooglePlayServer == server_id) {
if (renew) {
client_auth_.append(kGpClientOfflineRenewalQueryParameters);
} else if (release) {
client_auth_.append(kGpClientOfflineReleaseQueryParameters);
} else {
client_auth_.append(kGpClientOfflineQueryParameters);
}
}
}
}
void ConfigTestEnv::Init(LicenseServerId server_id) {
client_auth_ = license_servers[server_id].client_tag;
key_id_ = license_servers[server_id].key_id;
key_system_ = kWidevineKeySystem;
license_server_ = license_servers[server_id].url;
provisioning_server_url_ = kProductionProvisioningServerUrl;
wrong_key_id_= kWrongKeyId;
}
} // namespace wvcdm } // namespace wvcdm

View File

@@ -6,11 +6,6 @@
#include <string> #include <string>
#include "wv_cdm_types.h" #include "wv_cdm_types.h"
namespace {
const std::string kDefaultHttpsPort = "443";
const std::string kDefaultHttpPort = "80";
}
namespace wvcdm { namespace wvcdm {
typedef enum { typedef enum {
kGooglePlayServer, kGooglePlayServer,
@@ -25,24 +20,22 @@ class ConfigTestEnv {
std::string url; std::string url;
std::string client_tag; std::string client_tag;
std::string key_id; std::string key_id;
std::string port; std::string offline_key_id;
bool use_chunked_transfer;
bool use_secure_transfer;
} LicenseServerConfiguration; } LicenseServerConfiguration;
explicit ConfigTestEnv(LicenseServerId server_id); explicit ConfigTestEnv(LicenseServerId server_id);
ConfigTestEnv(LicenseServerId server_id, bool streaming);
ConfigTestEnv(LicenseServerId server_id, bool streaming, bool renew,
bool release);
~ConfigTestEnv() {}; ~ConfigTestEnv() {};
const std::string& client_auth() const { return client_auth_; } const std::string& client_auth() const { return client_auth_; }
const KeyId& key_id() const { return key_id_; } const KeyId& key_id() const { return key_id_; }
const CdmKeySystem& key_system() const { return key_system_; } const CdmKeySystem& key_system() const { return key_system_; }
const std::string& license_server() const { return license_server_; } const std::string& license_server() const { return license_server_; }
const std::string& port() const { return port_; }
const std::string& provisioning_server_url() const { const std::string& provisioning_server_url() const {
return provisioning_server_url_; return provisioning_server_url_;
} }
bool use_chunked_transfer() { return use_chunked_transfer_; }
bool use_secure_transfer() { return use_secure_transfer_; }
const KeyId& wrong_key_id() const { return wrong_key_id_; } 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_id(KeyId& key_id) { key_id_.assign(key_id); }
@@ -52,17 +45,15 @@ class ConfigTestEnv {
void set_license_server(std::string& license_server) { void set_license_server(std::string& license_server) {
license_server_.assign(license_server); license_server_.assign(license_server);
} }
void set_port(std::string& port) { port_.assign(port); }
private: private:
void Init(LicenseServerId server_id);
std::string client_auth_; std::string client_auth_;
KeyId key_id_; KeyId key_id_;
CdmKeySystem key_system_; CdmKeySystem key_system_;
std::string license_server_; std::string license_server_;
std::string port_;
std::string provisioning_server_url_; std::string provisioning_server_url_;
bool use_chunked_transfer_;
bool use_secure_transfer_;
KeyId wrong_key_id_; KeyId wrong_key_id_;
CORE_DISALLOW_COPY_AND_ASSIGN(ConfigTestEnv); CORE_DISALLOW_COPY_AND_ASSIGN(ConfigTestEnv);

View File

@@ -26,7 +26,11 @@ using ::testing::StrEq;
namespace { namespace {
const uint32_t kCertificateLen = 700; const uint32_t kCertificateLen = 700;
const uint32_t kWrappedKeyLen = 500; const uint32_t kWrappedKeyLen = 500;
const uint32_t kProtobufEstimatedLen = 75;
const uint32_t kProtobufEstimatedOverhead = 75;
const uint32_t kLicenseRequestLen = 300;
const uint32_t kLicenseLen = 500;
const uint32_t kProviderSessionTokenLen = 128;
// Structurally valid test certificate. // Structurally valid test certificate.
// The data elements in this module are used to test the storage and // The data elements in this module are used to test the storage and
@@ -901,8 +905,7 @@ LicenseInfo license_update_test_data[] = {
"D30B08B2C4673551293A2E68747470733A2F2F746573742E676F6F676C65" "D30B08B2C4673551293A2E68747470733A2F2F746573742E676F6F676C65"
"2E636F6D2F6C6963656E73652F47657443656E634C6963656E736512200A" "2E636F6D2F6C6963656E73652F47657443656E634C6963656E736512200A"
"1C78D0E574D0827C3AE78A05EEC90BAC31D10686EC19EB0599F75B2D1AB4" "1C78D0E574D0827C3AE78A05EEC90BAC31D10686EC19EB0599F75B2D1AB4"
"C5" "C5")},
)},
// license being released. all fields are identical except for license // license being released. all fields are identical except for license
// state and hashed file data // state and hashed file data
{"", DeviceFiles::kLicenseStateReleasing, "", "", "", "", "", "", {"", DeviceFiles::kLicenseStateReleasing, "", "", "", "", "", "",
@@ -1001,6 +1004,287 @@ LicenseInfo license_update_test_data[] = {
"7186A244EF561E3B07DC459BC681A0798B180667EA448327F6BBBD30212A" "7186A244EF561E3B07DC459BC681A0798B180667EA448327F6BBBD30212A"
"49")}}; "49")}};
struct UsageInfo {
std::string provider_session_token;
std::string license_request;
std::string license;
std::string file_data;
};
UsageInfo kUsageInfoTestData[] = {
{"", "", "", // 0 usage info records
wvcdm::a2bs_hex(
"0A06080210012A00122095053501C5FA405B7EF01DA94685C6B20CB36493"
"A9CF1653B720E2BEA3B77929")},
{// 1 usage info record
wvcdm::a2bs_hex(
"924B035FBDA56AE5EF0ED05A08DE7AECC8ABE1835E0C4A548F7803937F4C3B4520EB7"
"F3334FFCDFA00DE56408F09D5019FCE87072D0DC6789817468974B2EA51EE3944B8D7"
"E0A88E4F16EBB80F03BD845231A01E6146841CBAEF0134DCD9300DB2D92732992C0F2"
"310D8E386FB31C67B9477010DEF9D99C4272589572A26A17E"),
wvcdm::a2bs_hex(
"1E6FFBE66FC6153E7749906EC8F684E819467E16CAF317F315DB32B6D3FDD1A8E8A09"
"4174D92D063B88E4835EAB78BD09541EA7FE72F132EB7364E154BC1548FC40EC70927"
"75531508C95F9ED5D76F36BC0C198C3A33A1F9415B343905D6BE37645E6800F053B1D"
"A9A20286EFCBBC320424ADF7FB6E3D5D8E86C35E576A1A2A37D344A419C0F0034A1B5"
"F767D3C61D90DCA1119E5024C34EDE8FA7DD128696D8C435410F218E52A853AD214FD"
"05D0F8B3CB4832CFCD97FE159E6DEE64CE82CDAEC0321AE71B3BCBAE42DF9EA65E42E"
"151827086EADE71C138B972CC3992CF9ADA944C063816352ED8658D3FA07BE0F32239"
"E74A65932B069AAC4E8386DB59154AF9AEF71448128C66E510445294F44E511BD9B1A"
"F19D4D67E99363093BE888D4B2AB841CAFF252CAD13EDF8E"),
wvcdm::a2bs_hex(
"40FC62339728520E6C0C09907C26F3FB78287231661952A8B699E47AE241B999C029F"
"D2067836DC4BC64F66998A3ECD197DAE36F808A2E5A4C5BF25DD580E52B1C39A8B037"
"72BF82D58929766F2DA04F0E616F92B3A0EB75661B8FF5DE1EB807C990F9E6BA991C8"
"BAD5EB63B37E8663A4E22AA9DB2015D3DF8BED1C8313F85F13B9483C7A39C592436C8"
"B13C23F78F55CE812795335059F7F527CA580306A0AEE5A6D957A91F498F64AA2EFC6"
"780716400E17C7EEA30B2E6523B902986995E003C2D919A7DC7C0122CE9410037A660"
"2B59A63B5C89473D4E02DE35C1F01B12ADB48A3D94D43693F08268FECCC78DAF6F4C3"
"5FA32C538CD73FBF3CEA274B01179C02473486311956E5A0C78E44C59B2F34FF24B06"
"53A6379A2F5F6F51467CAE26D55CC5BBDCFC9BCFA7B8C5CBF82EBE7BD340C3DAE6374"
"D0692052C529AA33D7A6799C8F1F59C78575E51F707013026CC4F83F6B3328EE6FB1A"
"C91929A4491338E93D10EE6193014A73BA241A9A833EA835217894EB4FD4BDB8904A5"
"999928325D0AC31B6D58609EDD9D85E88F74B5BD6FA7BDD83C51EEB91633ED267ACA2"
"E103904BBE4C031A6483858FBAD74DACD01711F7B882749FFFBA0DB6C7D7109D82989"
"C7D4DB5A0F1E7506AC24C89CECAF231EFF99F96AD76E57DABDD3C2DFBA7BAA869A771"
"F561B165987E552824B0C914E708E425C3"),
wvcdm::a2bs_hex(
"0AB307080210012AAC070AA9070A8001924B035FBDA56AE5EF0ED05A08DE7AECC8ABE"
"1835E0C4A548F7803937F4C3B4520EB7F3334FFCDFA00DE56408F09D5019FCE87072D"
"0DC6789817468974B2EA51EE3944B8D7E0A88E4F16EBB80F03BD845231A01E6146841"
"CBAEF0134DCD9300DB2D92732992C0F2310D8E386FB31C67B9477010DEF9D99C42725"
"89572A26A17E12AC021E6FFBE66FC6153E7749906EC8F684E819467E16CAF317F315D"
"B32B6D3FDD1A8E8A094174D92D063B88E4835EAB78BD09541EA7FE72F132EB7364E15"
"4BC1548FC40EC7092775531508C95F9ED5D76F36BC0C198C3A33A1F9415B343905D6B"
"E37645E6800F053B1DA9A20286EFCBBC320424ADF7FB6E3D5D8E86C35E576A1A2A37D"
"344A419C0F0034A1B5F767D3C61D90DCA1119E5024C34EDE8FA7DD128696D8C435410"
"F218E52A853AD214FD05D0F8B3CB4832CFCD97FE159E6DEE64CE82CDAEC0321AE71B3"
"BCBAE42DF9EA65E42E151827086EADE71C138B972CC3992CF9ADA944C063816352ED8"
"658D3FA07BE0F32239E74A65932B069AAC4E8386DB59154AF9AEF71448128C66E5104"
"45294F44E511BD9B1AF19D4D67E99363093BE888D4B2AB841CAFF252CAD13EDF8E1AF"
"40340FC62339728520E6C0C09907C26F3FB78287231661952A8B699E47AE241B999C0"
"29FD2067836DC4BC64F66998A3ECD197DAE36F808A2E5A4C5BF25DD580E52B1C39A8B"
"03772BF82D58929766F2DA04F0E616F92B3A0EB75661B8FF5DE1EB807C990F9E6BA99"
"1C8BAD5EB63B37E8663A4E22AA9DB2015D3DF8BED1C8313F85F13B9483C7A39C59243"
"6C8B13C23F78F55CE812795335059F7F527CA580306A0AEE5A6D957A91F498F64AA2E"
"FC6780716400E17C7EEA30B2E6523B902986995E003C2D919A7DC7C0122CE9410037A"
"6602B59A63B5C89473D4E02DE35C1F01B12ADB48A3D94D43693F08268FECCC78DAF6F"
"4C35FA32C538CD73FBF3CEA274B01179C02473486311956E5A0C78E44C59B2F34FF24"
"B0653A6379A2F5F6F51467CAE26D55CC5BBDCFC9BCFA7B8C5CBF82EBE7BD340C3DAE6"
"374D0692052C529AA33D7A6799C8F1F59C78575E51F707013026CC4F83F6B3328EE6F"
"B1AC91929A4491338E93D10EE6193014A73BA241A9A833EA835217894EB4FD4BDB890"
"4A5999928325D0AC31B6D58609EDD9D85E88F74B5BD6FA7BDD83C51EEB91633ED267A"
"CA2E103904BBE4C031A6483858FBAD74DACD01711F7B882749FFFBA0DB6C7D7109D82"
"989C7D4DB5A0F1E7506AC24C89CECAF231EFF99F96AD76E57DABDD3C2DFBA7BAA869A"
"771F561B165987E552824B0C914E708E425C3122051C8F84C5713500997DC5B325BAE"
"D208B224DFAEB2B034E58046A62F503FED6E")},
{// 2 usage info records
wvcdm::a2bs_hex(
"7290396E183156BDF830B7BF31BA762CB2675528C9004FD24A61DAFB587ABCF1D36F8"
"7795EE0B3DA0B425616A66C82349B2E3BB8841C1335536865F919ED2AE671487B608B"
"21A362D888E0AB4F7AB7175B82F108617C3503F175435788AECAF7FFBFE76995D93CD"
"79424A843A247A8D8A6054A5B5404C9C057AACAD91A203229"),
wvcdm::a2bs_hex(
"3478A2D76DEB90BE713B03A11037EA7C305D1AF65099E3F2B92C4D4443A8F481C1177"
"DEF0A3CB49BA5F1448A10AF1207AD2D361B4A1F961B4B1F215B76A9A5005B414EF45E"
"AFBCF2636ABFC01413B27DD11871103579F8C041A799E22888D9ADB798E92A5E29BC4"
"6DECBC90991C65FE151C49F18068C1B65D0E90A9ECDA9248B87C120D5FD8EC81D4D36"
"B529FB2DAD39E0D39578B13B158E2B07C752D86F1A9D8160C93930C1F4F9E1D0D8E2C"
"5AB308732EB27722A6BF8BE852624C2BE3E4FE85819B89BEBA6535FCFBE85FA63A57B"
"D0FBAF284C64FFD97A146B76B3F37B576FC091C03E2222FBD24C2211344B7E2417EFC"
"36C4A54DCCC460CF810E7EA8AC6386D6AB567C819FED88A22CE55EF9BBE62C2CBC7AE"
"EDE5E5A69FF3472418CE2F4514496C59D26E72F3BFE0131F"),
wvcdm::a2bs_hex(
"C45FDCB3296A0EBE24FF381E027E6E2EF1AC289C67D3B858330669A81E8131583D2F1"
"40FD64615BDED0ED8316ABFD9C7E887433E1CAA6EA8E0C4F87ADB2A7FC3CF6FF87A7F"
"02AFF03BF5DB640AD8DDB572C41532E673618DCD8C33EF2BFE4E25EE821DF7D742B09"
"90398543B16EFCDBB03C6327B79D3664CED442E894020F4410ECC178C92AAEDFE39DC"
"563AC226FE9E0EF22E1C896C4F2835CDFDCD50B6C4DBA2B27A3B65DE3963D0A5F6E44"
"2A3C32008AB9D1ACBE4F366990EB43F8EE213B71E98DA090282680ABDD649BECA8970"
"0764561379F1DD23490CE967632ECA349AF8E1CBFA1F3A4F39F453614C8FFB5A17975"
"6243CB1FDB515834229BC64917C47A2F2E1116FAAC13368015312C31FD41215106469"
"BEE77D0EF2FE10CF645B3E82902EAF53A676933D0EC433949C1833BE52E76602CC3E4"
"E784C002E20624BCE0F38F9CBC478439899DA7F15554D0ACADEC140C00C8FA8FC9886"
"2D9933938781B30CB9C76899B3A48DBF170DDA0A18ED37D77F048ABBC85CB19469638"
"C2A32AA3180CF3943BD6B8C5CB26F2EA70868F18B0707C882054141086997A1AE5B70"
"9D4D0AA2B358990F244BA76C8E40791D29A0C63C9EF620B97FDFFA9B671E5A65AFCC1"
"C94CAACE0443E9D91F14028935BEA3988831BEBBFD3EB7C3A5AC9605B3534712A0912"
"4345ACB09665E357E58946871BC140D365"),
wvcdm::a2bs_hex(
"0ADF0E080210012AD80E0AA9070A8001924B035FBDA56AE5EF0ED05A08DE7AECC8ABE"
"1835E0C4A548F7803937F4C3B4520EB7F3334FFCDFA00DE56408F09D5019FCE87072D"
"0DC6789817468974B2EA51EE3944B8D7E0A88E4F16EBB80F03BD845231A01E6146841"
"CBAEF0134DCD9300DB2D92732992C0F2310D8E386FB31C67B9477010DEF9D99C42725"
"89572A26A17E12AC021E6FFBE66FC6153E7749906EC8F684E819467E16CAF317F315D"
"B32B6D3FDD1A8E8A094174D92D063B88E4835EAB78BD09541EA7FE72F132EB7364E15"
"4BC1548FC40EC7092775531508C95F9ED5D76F36BC0C198C3A33A1F9415B343905D6B"
"E37645E6800F053B1DA9A20286EFCBBC320424ADF7FB6E3D5D8E86C35E576A1A2A37D"
"344A419C0F0034A1B5F767D3C61D90DCA1119E5024C34EDE8FA7DD128696D8C435410"
"F218E52A853AD214FD05D0F8B3CB4832CFCD97FE159E6DEE64CE82CDAEC0321AE71B3"
"BCBAE42DF9EA65E42E151827086EADE71C138B972CC3992CF9ADA944C063816352ED8"
"658D3FA07BE0F32239E74A65932B069AAC4E8386DB59154AF9AEF71448128C66E5104"
"45294F44E511BD9B1AF19D4D67E99363093BE888D4B2AB841CAFF252CAD13EDF8E1AF"
"40340FC62339728520E6C0C09907C26F3FB78287231661952A8B699E47AE241B999C0"
"29FD2067836DC4BC64F66998A3ECD197DAE36F808A2E5A4C5BF25DD580E52B1C39A8B"
"03772BF82D58929766F2DA04F0E616F92B3A0EB75661B8FF5DE1EB807C990F9E6BA99"
"1C8BAD5EB63B37E8663A4E22AA9DB2015D3DF8BED1C8313F85F13B9483C7A39C59243"
"6C8B13C23F78F55CE812795335059F7F527CA580306A0AEE5A6D957A91F498F64AA2E"
"FC6780716400E17C7EEA30B2E6523B902986995E003C2D919A7DC7C0122CE9410037A"
"6602B59A63B5C89473D4E02DE35C1F01B12ADB48A3D94D43693F08268FECCC78DAF6F"
"4C35FA32C538CD73FBF3CEA274B01179C02473486311956E5A0C78E44C59B2F34FF24"
"B0653A6379A2F5F6F51467CAE26D55CC5BBDCFC9BCFA7B8C5CBF82EBE7BD340C3DAE6"
"374D0692052C529AA33D7A6799C8F1F59C78575E51F707013026CC4F83F6B3328EE6F"
"B1AC91929A4491338E93D10EE6193014A73BA241A9A833EA835217894EB4FD4BDB890"
"4A5999928325D0AC31B6D58609EDD9D85E88F74B5BD6FA7BDD83C51EEB91633ED267A"
"CA2E103904BBE4C031A6483858FBAD74DACD01711F7B882749FFFBA0DB6C7D7109D82"
"989C7D4DB5A0F1E7506AC24C89CECAF231EFF99F96AD76E57DABDD3C2DFBA7BAA869A"
"771F561B165987E552824B0C914E708E425C30AA9070A80017290396E183156BDF830"
"B7BF31BA762CB2675528C9004FD24A61DAFB587ABCF1D36F87795EE0B3DA0B425616A"
"66C82349B2E3BB8841C1335536865F919ED2AE671487B608B21A362D888E0AB4F7AB7"
"175B82F108617C3503F175435788AECAF7FFBFE76995D93CD79424A843A247A8D8A60"
"54A5B5404C9C057AACAD91A20322912AC023478A2D76DEB90BE713B03A11037EA7C30"
"5D1AF65099E3F2B92C4D4443A8F481C1177DEF0A3CB49BA5F1448A10AF1207AD2D361"
"B4A1F961B4B1F215B76A9A5005B414EF45EAFBCF2636ABFC01413B27DD11871103579"
"F8C041A799E22888D9ADB798E92A5E29BC46DECBC90991C65FE151C49F18068C1B65D"
"0E90A9ECDA9248B87C120D5FD8EC81D4D36B529FB2DAD39E0D39578B13B158E2B07C7"
"52D86F1A9D8160C93930C1F4F9E1D0D8E2C5AB308732EB27722A6BF8BE852624C2BE3"
"E4FE85819B89BEBA6535FCFBE85FA63A57BD0FBAF284C64FFD97A146B76B3F37B576F"
"C091C03E2222FBD24C2211344B7E2417EFC36C4A54DCCC460CF810E7EA8AC6386D6AB"
"567C819FED88A22CE55EF9BBE62C2CBC7AEEDE5E5A69FF3472418CE2F4514496C59D2"
"6E72F3BFE0131F1AF403C45FDCB3296A0EBE24FF381E027E6E2EF1AC289C67D3B8583"
"30669A81E8131583D2F140FD64615BDED0ED8316ABFD9C7E887433E1CAA6EA8E0C4F8"
"7ADB2A7FC3CF6FF87A7F02AFF03BF5DB640AD8DDB572C41532E673618DCD8C33EF2BF"
"E4E25EE821DF7D742B0990398543B16EFCDBB03C6327B79D3664CED442E894020F441"
"0ECC178C92AAEDFE39DC563AC226FE9E0EF22E1C896C4F2835CDFDCD50B6C4DBA2B27"
"A3B65DE3963D0A5F6E442A3C32008AB9D1ACBE4F366990EB43F8EE213B71E98DA0902"
"82680ABDD649BECA89700764561379F1DD23490CE967632ECA349AF8E1CBFA1F3A4F3"
"9F453614C8FFB5A179756243CB1FDB515834229BC64917C47A2F2E1116FAAC1336801"
"5312C31FD41215106469BEE77D0EF2FE10CF645B3E82902EAF53A676933D0EC433949"
"C1833BE52E76602CC3E4E784C002E20624BCE0F38F9CBC478439899DA7F15554D0ACA"
"DEC140C00C8FA8FC98862D9933938781B30CB9C76899B3A48DBF170DDA0A18ED37D77"
"F048ABBC85CB19469638C2A32AA3180CF3943BD6B8C5CB26F2EA70868F18B0707C882"
"054141086997A1AE5B709D4D0AA2B358990F244BA76C8E40791D29A0C63C9EF620B97"
"FDFFA9B671E5A65AFCC1C94CAACE0443E9D91F14028935BEA3988831BEBBFD3EB7C3A"
"5AC9605B3534712A09124345ACB09665E357E58946871BC140D3651220464E4A1BB23"
"1A5B0287888B34CA0A8CF5396EB2B8313377DC5ED5C41A9B389A9")},
{// 3 usage info records
wvcdm::a2bs_hex(
"983358221FB8DBF892047F00AA661F217EEC4E7A1626E8F98E025509E4D65A685E7D9"
"B169B98B16934F6E43E0E0E854A3FA9EB8E9A9D08E9D9B3A6C766AA44F7C655879BA2"
"DF5F38732FB7EDCA66D8C13A855B15E32CC9389B7DD119BA1F2417825FF1F52970F8E"
"985D34DD353D2AC8B24267353E5B8406C098427C4559A90CC"),
wvcdm::a2bs_hex(
"483EAC68243092009D06FAB41DB594ACB22E068C9524810758ECFF8BAB7E1B1ACA988"
"C3987023F01EFEC11529C7326279742E805E755A08EBBD9AA322F305805BE1166AB45"
"CB156FB0A9E6734371F4028707EE01CF2FB08465707E7E5613DD90D74B0D02536E26C"
"F1261CDDA8713943F3620ECC54095C76F8CD3CE31948C3CC0C9EB5582A4D087A54B39"
"1B4CDCBC98E35830B5932F6CF8D16427EF115CFF0A99499513702DD54C758E53248BB"
"5D195F2A2DD1DB18F97562F1F9034E223CEDB1E09ED1B0FE26089C20ED43B5D87B51F"
"6FC6C9F86255FBF70DF233F2665D604355BF9740A3B755521102E0B485C5CCCA607A9"
"A1BEB757BEDEF12327C637D17D6401E3756719F99BBE69B9CE4C8E47C2AC771F35A8E"
"E3FC4D58B2B2269CF85728E4DA7231BC8F0FD7C50E2A1EE9"),
wvcdm::a2bs_hex(
"5826D3A95F78879292612BCE06D845D64285CD45A7EAA6C87A9DBC3290B0B6AC95315"
"809F8CC7938768F9BD342C62CD4CE055866394489D955247CB0535001D50EFF4FEDF0"
"9501C58569B1EB9AA2305A113A5F4D4524AD34148A2DC48D2F522937F44A57FC76F57"
"EB1D4819C438EA42C7F8974FC7D2FE61CAAB3E1F27172FE6B8675DF4CCF1329A6EFB3"
"1F686FB0DC0F8B552D78970708D50C82ADBE333B585F6DE5A0D01D106F8232EB9ED45"
"42A2DC5AA031CC44652E8A42EDCA5AB08B0B5CA61A922E69A119E556F6014642522EA"
"1550F6D6E63EB25ACC03A4DD3F22F4686ED525F994FABA87629AF5939C16BA68C0F09"
"3EFE033CD319180BF69FCB72AC5123EBCB9DCF1AF00F0A68E31FF5B18FA8CFF3DFBB7"
"DA45413799105D67FA78217710D2F6C33394DD4088100013295FF43CF0598E6FE5C05"
"F03417CCD031F01CF63BECD444C750DF198345F155AB2B2AB94394A3C0C0AE05E386D"
"E6CC565AE82398BD0E377D6ABE103B9D5E84582C3772584B759891FC4B121A113370E"
"2DF5372DD81FB6358C64B0F6EB8F26193CA119E4D9D3D38036FA450EE2047CB2CE265"
"0FF37DF85BE23D58C17379FEC08DC0648236A107AE66178EEBF78F05F3B898424FA02"
"668B51F838AFA90D367B5CB425372D8CC3790BEA8AFB8795251FA09340D85A7F0B003"
"134C838F08BB1054D18404C3F69130700E"),
wvcdm::a2bs_hex(
"0A8B16080210012A84160AA9070A8001924B035FBDA56AE5EF0ED05A08DE7AECC8ABE"
"1835E0C4A548F7803937F4C3B4520EB7F3334FFCDFA00DE56408F09D5019FCE87072D"
"0DC6789817468974B2EA51EE3944B8D7E0A88E4F16EBB80F03BD845231A01E6146841"
"CBAEF0134DCD9300DB2D92732992C0F2310D8E386FB31C67B9477010DEF9D99C42725"
"89572A26A17E12AC021E6FFBE66FC6153E7749906EC8F684E819467E16CAF317F315D"
"B32B6D3FDD1A8E8A094174D92D063B88E4835EAB78BD09541EA7FE72F132EB7364E15"
"4BC1548FC40EC7092775531508C95F9ED5D76F36BC0C198C3A33A1F9415B343905D6B"
"E37645E6800F053B1DA9A20286EFCBBC320424ADF7FB6E3D5D8E86C35E576A1A2A37D"
"344A419C0F0034A1B5F767D3C61D90DCA1119E5024C34EDE8FA7DD128696D8C435410"
"F218E52A853AD214FD05D0F8B3CB4832CFCD97FE159E6DEE64CE82CDAEC0321AE71B3"
"BCBAE42DF9EA65E42E151827086EADE71C138B972CC3992CF9ADA944C063816352ED8"
"658D3FA07BE0F32239E74A65932B069AAC4E8386DB59154AF9AEF71448128C66E5104"
"45294F44E511BD9B1AF19D4D67E99363093BE888D4B2AB841CAFF252CAD13EDF8E1AF"
"40340FC62339728520E6C0C09907C26F3FB78287231661952A8B699E47AE241B999C0"
"29FD2067836DC4BC64F66998A3ECD197DAE36F808A2E5A4C5BF25DD580E52B1C39A8B"
"03772BF82D58929766F2DA04F0E616F92B3A0EB75661B8FF5DE1EB807C990F9E6BA99"
"1C8BAD5EB63B37E8663A4E22AA9DB2015D3DF8BED1C8313F85F13B9483C7A39C59243"
"6C8B13C23F78F55CE812795335059F7F527CA580306A0AEE5A6D957A91F498F64AA2E"
"FC6780716400E17C7EEA30B2E6523B902986995E003C2D919A7DC7C0122CE9410037A"
"6602B59A63B5C89473D4E02DE35C1F01B12ADB48A3D94D43693F08268FECCC78DAF6F"
"4C35FA32C538CD73FBF3CEA274B01179C02473486311956E5A0C78E44C59B2F34FF24"
"B0653A6379A2F5F6F51467CAE26D55CC5BBDCFC9BCFA7B8C5CBF82EBE7BD340C3DAE6"
"374D0692052C529AA33D7A6799C8F1F59C78575E51F707013026CC4F83F6B3328EE6F"
"B1AC91929A4491338E93D10EE6193014A73BA241A9A833EA835217894EB4FD4BDB890"
"4A5999928325D0AC31B6D58609EDD9D85E88F74B5BD6FA7BDD83C51EEB91633ED267A"
"CA2E103904BBE4C031A6483858FBAD74DACD01711F7B882749FFFBA0DB6C7D7109D82"
"989C7D4DB5A0F1E7506AC24C89CECAF231EFF99F96AD76E57DABDD3C2DFBA7BAA869A"
"771F561B165987E552824B0C914E708E425C30AA9070A80017290396E183156BDF830"
"B7BF31BA762CB2675528C9004FD24A61DAFB587ABCF1D36F87795EE0B3DA0B425616A"
"66C82349B2E3BB8841C1335536865F919ED2AE671487B608B21A362D888E0AB4F7AB7"
"175B82F108617C3503F175435788AECAF7FFBFE76995D93CD79424A843A247A8D8A60"
"54A5B5404C9C057AACAD91A20322912AC023478A2D76DEB90BE713B03A11037EA7C30"
"5D1AF65099E3F2B92C4D4443A8F481C1177DEF0A3CB49BA5F1448A10AF1207AD2D361"
"B4A1F961B4B1F215B76A9A5005B414EF45EAFBCF2636ABFC01413B27DD11871103579"
"F8C041A799E22888D9ADB798E92A5E29BC46DECBC90991C65FE151C49F18068C1B65D"
"0E90A9ECDA9248B87C120D5FD8EC81D4D36B529FB2DAD39E0D39578B13B158E2B07C7"
"52D86F1A9D8160C93930C1F4F9E1D0D8E2C5AB308732EB27722A6BF8BE852624C2BE3"
"E4FE85819B89BEBA6535FCFBE85FA63A57BD0FBAF284C64FFD97A146B76B3F37B576F"
"C091C03E2222FBD24C2211344B7E2417EFC36C4A54DCCC460CF810E7EA8AC6386D6AB"
"567C819FED88A22CE55EF9BBE62C2CBC7AEEDE5E5A69FF3472418CE2F4514496C59D2"
"6E72F3BFE0131F1AF403C45FDCB3296A0EBE24FF381E027E6E2EF1AC289C67D3B8583"
"30669A81E8131583D2F140FD64615BDED0ED8316ABFD9C7E887433E1CAA6EA8E0C4F8"
"7ADB2A7FC3CF6FF87A7F02AFF03BF5DB640AD8DDB572C41532E673618DCD8C33EF2BF"
"E4E25EE821DF7D742B0990398543B16EFCDBB03C6327B79D3664CED442E894020F441"
"0ECC178C92AAEDFE39DC563AC226FE9E0EF22E1C896C4F2835CDFDCD50B6C4DBA2B27"
"A3B65DE3963D0A5F6E442A3C32008AB9D1ACBE4F366990EB43F8EE213B71E98DA0902"
"82680ABDD649BECA89700764561379F1DD23490CE967632ECA349AF8E1CBFA1F3A4F3"
"9F453614C8FFB5A179756243CB1FDB515834229BC64917C47A2F2E1116FAAC1336801"
"5312C31FD41215106469BEE77D0EF2FE10CF645B3E82902EAF53A676933D0EC433949"
"C1833BE52E76602CC3E4E784C002E20624BCE0F38F9CBC478439899DA7F15554D0ACA"
"DEC140C00C8FA8FC98862D9933938781B30CB9C76899B3A48DBF170DDA0A18ED37D77"
"F048ABBC85CB19469638C2A32AA3180CF3943BD6B8C5CB26F2EA70868F18B0707C882"
"054141086997A1AE5B709D4D0AA2B358990F244BA76C8E40791D29A0C63C9EF620B97"
"FDFFA9B671E5A65AFCC1C94CAACE0443E9D91F14028935BEA3988831BEBBFD3EB7C3A"
"5AC9605B3534712A09124345ACB09665E357E58946871BC140D3650AA9070A8001983"
"358221FB8DBF892047F00AA661F217EEC4E7A1626E8F98E025509E4D65A685E7D9B16"
"9B98B16934F6E43E0E0E854A3FA9EB8E9A9D08E9D9B3A6C766AA44F7C655879BA2DF5"
"F38732FB7EDCA66D8C13A855B15E32CC9389B7DD119BA1F2417825FF1F52970F8E985"
"D34DD353D2AC8B24267353E5B8406C098427C4559A90CC12AC02483EAC68243092009"
"D06FAB41DB594ACB22E068C9524810758ECFF8BAB7E1B1ACA988C3987023F01EFEC11"
"529C7326279742E805E755A08EBBD9AA322F305805BE1166AB45CB156FB0A9E673437"
"1F4028707EE01CF2FB08465707E7E5613DD90D74B0D02536E26CF1261CDDA8713943F"
"3620ECC54095C76F8CD3CE31948C3CC0C9EB5582A4D087A54B391B4CDCBC98E35830B"
"5932F6CF8D16427EF115CFF0A99499513702DD54C758E53248BB5D195F2A2DD1DB18F"
"97562F1F9034E223CEDB1E09ED1B0FE26089C20ED43B5D87B51F6FC6C9F86255FBF70"
"DF233F2665D604355BF9740A3B755521102E0B485C5CCCA607A9A1BEB757BEDEF1232"
"7C637D17D6401E3756719F99BBE69B9CE4C8E47C2AC771F35A8EE3FC4D58B2B2269CF"
"85728E4DA7231BC8F0FD7C50E2A1EE91AF4035826D3A95F78879292612BCE06D845D6"
"4285CD45A7EAA6C87A9DBC3290B0B6AC95315809F8CC7938768F9BD342C62CD4CE055"
"866394489D955247CB0535001D50EFF4FEDF09501C58569B1EB9AA2305A113A5F4D45"
"24AD34148A2DC48D2F522937F44A57FC76F57EB1D4819C438EA42C7F8974FC7D2FE61"
"CAAB3E1F27172FE6B8675DF4CCF1329A6EFB31F686FB0DC0F8B552D78970708D50C82"
"ADBE333B585F6DE5A0D01D106F8232EB9ED4542A2DC5AA031CC44652E8A42EDCA5AB0"
"8B0B5CA61A922E69A119E556F6014642522EA1550F6D6E63EB25ACC03A4DD3F22F468"
"6ED525F994FABA87629AF5939C16BA68C0F093EFE033CD319180BF69FCB72AC5123EB"
"CB9DCF1AF00F0A68E31FF5B18FA8CFF3DFBB7DA45413799105D67FA78217710D2F6C3"
"3394DD4088100013295FF43CF0598E6FE5C05F03417CCD031F01CF63BECD444C750DF"
"198345F155AB2B2AB94394A3C0C0AE05E386DE6CC565AE82398BD0E377D6ABE103B9D"
"5E84582C3772584B759891FC4B121A113370E2DF5372DD81FB6358C64B0F6EB8F2619"
"3CA119E4D9D3D38036FA450EE2047CB2CE2650FF37DF85BE23D58C17379FEC08DC064"
"8236A107AE66178EEBF78F05F3B898424FA02668B51F838AFA90D367B5CB425372D8C"
"C3790BEA8AFB8795251FA09340D85A7F0B003134C838F08BB1054D18404C3F6913070"
"0E12202FF1FBA9926A24A1F79970EC427DDF87B4421488F7952499BC33CEB282D9E48"
"A")}};
} // namespace } // namespace
class MockFile : public File { class MockFile : public File {
@@ -1053,6 +1337,9 @@ class DeviceFilesSecurityLevelTest
: public DeviceFilesTest, : public DeviceFilesTest,
public ::testing::WithParamInterface<CdmSecurityLevel> {}; public ::testing::WithParamInterface<CdmSecurityLevel> {};
class DeviceFilesUsageInfoTest : public DeviceFilesTest,
public ::testing::WithParamInterface<int> {};
MATCHER(IsCreateFileFlagSet, "") { return File::kCreate & arg; } MATCHER(IsCreateFileFlagSet, "") { return File::kCreate & arg; }
MATCHER(IsBinaryFileFlagSet, "") { return File::kBinary & arg; } MATCHER(IsBinaryFileFlagSet, "") { return File::kBinary & arg; }
MATCHER_P(IsStrEq, str, "") { MATCHER_P(IsStrEq, str, "") {
@@ -1060,18 +1347,29 @@ MATCHER_P(IsStrEq, str, "") {
// as well as pointer to data but that will introduce a dependency on tr1 // as well as pointer to data but that will introduce a dependency on tr1
return memcmp(arg, str.c_str(), str.size()) == 0; return memcmp(arg, str.c_str(), str.size()) == 0;
} }
MATCHER_P2(Contains, str1, str2, "") { MATCHER_P3(Contains, str1, str2, size, "") {
// Estimating the length of data. We can have gmock provide length // Estimating the length of data. We can have gmock provide length
// as well as pointer to data but that will introduce a dependency on tr1 // as well as pointer to data but that will introduce a dependency on tr1
std::string data(arg, str1.size() + str2.size() + kProtobufEstimatedLen); std::string data(
arg, size + str1.size() + str2.size() + kProtobufEstimatedOverhead);
return (data.find(str1) != std::string::npos && return (data.find(str1) != std::string::npos &&
data.find(str2) != std::string::npos); data.find(str2) != std::string::npos);
} }
MATCHER_P4(Contains, str1, str2, str3, size, "") {
// Estimating the length of data. We can have gmock provide length
// as well as pointer to data but that will introduce a dependency on tr1
std::string data(arg, size + str1.size() + str2.size() + str3.size() +
kProtobufEstimatedOverhead);
return (data.find(str1) != std::string::npos &&
data.find(str2) != std::string::npos &&
data.find(str3) != std::string::npos);
}
MATCHER_P6(Contains, str1, str2, str3, str4, str5, str6, "") { MATCHER_P6(Contains, str1, str2, str3, str4, str5, str6, "") {
// Estimating the length of data. We can have gmock provide length // Estimating the length of data. We can have gmock provide length
// as well as pointer to data but that will introduce a dependency on tr1 // as well as pointer to data but that will introduce a dependency on tr1
std::string data(arg, str1.size() + str2.size() + str3.size() + str4.size() + std::string data(arg, str1.size() + str2.size() + str3.size() + str4.size() +
str5.size() + str6.size() + kProtobufEstimatedLen); str5.size() + str6.size() +
kProtobufEstimatedOverhead);
return (data.find(str1) != std::string::npos && return (data.find(str1) != std::string::npos &&
data.find(str2) != std::string::npos && data.find(str2) != std::string::npos &&
data.find(str3) != std::string::npos && data.find(str3) != std::string::npos &&
@@ -1100,14 +1398,15 @@ TEST_P(DeviceFilesStoreTest, StoreCertificate) {
EXPECT_CALL(file, Open(StrEq(device_certificate_path), EXPECT_CALL(file, Open(StrEq(device_certificate_path),
AllOf(IsCreateFileFlagSet(), IsBinaryFileFlagSet()))) AllOf(IsCreateFileFlagSet(), IsBinaryFileFlagSet())))
.WillOnce(Return(true)); .WillOnce(Return(true));
EXPECT_CALL(file, Write(Contains(certificate, wrapped_private_key), EXPECT_CALL(file, Write(Contains(certificate, wrapped_private_key, 0),
Gt(certificate.size() + wrapped_private_key.size()))) Gt(certificate.size() + wrapped_private_key.size())))
.WillOnce(ReturnArg<1>()); .WillOnce(ReturnArg<1>());
EXPECT_CALL(file, Close()).Times(1); EXPECT_CALL(file, Close()).Times(1);
EXPECT_CALL(file, Read(_, _)).Times(0); EXPECT_CALL(file, Read(_, _)).Times(0);
DeviceFiles device_files; DeviceFiles device_files;
EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1)); EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
device_files.SetTestFile(&file);
EXPECT_TRUE(device_files.StoreCertificate(certificate, wrapped_private_key)); EXPECT_TRUE(device_files.StoreCertificate(certificate, wrapped_private_key));
} }
@@ -1132,7 +1431,8 @@ TEST_F(DeviceFilesTest, ReadCertificate) {
EXPECT_CALL(file, Write(_, _)).Times(0); EXPECT_CALL(file, Write(_, _)).Times(0);
DeviceFiles device_files; DeviceFiles device_files;
EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1)); EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
device_files.SetTestFile(&file);
std::string certificate, wrapped_private_key; std::string certificate, wrapped_private_key;
ASSERT_TRUE( ASSERT_TRUE(
@@ -1161,14 +1461,15 @@ TEST_P(DeviceFilesSecurityLevelTest, SecurityLevel) {
EXPECT_CALL(file, Open(StrEq(device_certificate_path), EXPECT_CALL(file, Open(StrEq(device_certificate_path),
AllOf(IsCreateFileFlagSet(), IsBinaryFileFlagSet()))) AllOf(IsCreateFileFlagSet(), IsBinaryFileFlagSet())))
.WillOnce(Return(true)); .WillOnce(Return(true));
EXPECT_CALL(file, Write(Contains(certificate, wrapped_private_key), EXPECT_CALL(file, Write(Contains(certificate, wrapped_private_key, 0),
Gt(certificate.size() + wrapped_private_key.size()))) Gt(certificate.size() + wrapped_private_key.size())))
.WillOnce(ReturnArg<1>()); .WillOnce(ReturnArg<1>());
EXPECT_CALL(file, Close()).Times(1); EXPECT_CALL(file, Close()).Times(1);
EXPECT_CALL(file, Read(_, _)).Times(0); EXPECT_CALL(file, Read(_, _)).Times(0);
DeviceFiles device_files; DeviceFiles device_files;
EXPECT_TRUE(device_files.Init(&file, security_level)); EXPECT_TRUE(device_files.Init(security_level));
device_files.SetTestFile(&file);
EXPECT_TRUE(device_files.StoreCertificate(certificate, wrapped_private_key)); EXPECT_TRUE(device_files.StoreCertificate(certificate, wrapped_private_key));
} }
@@ -1208,7 +1509,8 @@ TEST_P(DeviceFilesStoreTest, StoreLicense) {
EXPECT_CALL(file, Read(_, _)).Times(0); EXPECT_CALL(file, Read(_, _)).Times(0);
DeviceFiles device_files; DeviceFiles device_files;
EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1)); EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
device_files.SetTestFile(&file);
EXPECT_TRUE(device_files.StoreLicense( EXPECT_TRUE(device_files.StoreLicense(
license_test_data[license_num].key_set_id, license_test_data[license_num].key_set_id,
license_test_data[license_num].license_state, license_test_data[license_num].license_state,
@@ -1226,7 +1528,8 @@ INSTANTIATE_TEST_CASE_P(StoreLicense, DeviceFilesStoreTest,
TEST_F(DeviceFilesTest, StoreLicenses) { TEST_F(DeviceFilesTest, StoreLicenses) {
MockFile file; MockFile file;
EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_))) EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_)))
.Times(kNumberOfLicenses).WillRepeatedly(Return(true)); .Times(kNumberOfLicenses)
.WillRepeatedly(Return(true));
EXPECT_CALL(file, CreateDirectory(_)).Times(0); EXPECT_CALL(file, CreateDirectory(_)).Times(0);
for (size_t i = 0; i < kNumberOfLicenses; ++i) { for (size_t i = 0; i < kNumberOfLicenses; ++i) {
@@ -1251,7 +1554,8 @@ TEST_F(DeviceFilesTest, StoreLicenses) {
EXPECT_CALL(file, Read(_, _)).Times(0); EXPECT_CALL(file, Read(_, _)).Times(0);
DeviceFiles device_files; DeviceFiles device_files;
EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1)); EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
device_files.SetTestFile(&file);
for (size_t i = 0; i < kNumberOfLicenses; i++) { for (size_t i = 0; i < kNumberOfLicenses; i++) {
EXPECT_TRUE(device_files.StoreLicense( EXPECT_TRUE(device_files.StoreLicense(
license_test_data[i].key_set_id, license_test_data[i].license_state, license_test_data[i].key_set_id, license_test_data[i].license_state,
@@ -1286,7 +1590,8 @@ TEST_F(DeviceFilesTest, RetrieveLicenses) {
EXPECT_CALL(file, Write(_, _)).Times(0); EXPECT_CALL(file, Write(_, _)).Times(0);
DeviceFiles device_files; DeviceFiles device_files;
EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1)); EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
device_files.SetTestFile(&file);
DeviceFiles::LicenseState license_state; DeviceFiles::LicenseState license_state;
CdmInitData pssh_data; CdmInitData pssh_data;
CdmKeyMessage key_request; CdmKeyMessage key_request;
@@ -1338,12 +1643,11 @@ TEST_F(DeviceFilesTest, SecurityLevelPathBackwardCompatibility) {
std::string old_path = base_path + DeviceFiles::GetCertificateFileName(); std::string old_path = base_path + DeviceFiles::GetCertificateFileName();
old_files.push_back(DeviceFiles::GetCertificateFileName()); old_files.push_back(DeviceFiles::GetCertificateFileName());
EXPECT_CALL(file, IsRegularFile(StrEq(old_path))) EXPECT_CALL(file, IsRegularFile(StrEq(old_path))).WillOnce(Return(true));
.WillOnce(Return(true));
EXPECT_CALL(file, Remove(StrEq(old_path))).WillOnce(Return(true)); EXPECT_CALL(file, Remove(StrEq(old_path))).WillOnce(Return(true));
for (size_t i = 0; i < security_dirs.size(); ++i) { for (size_t i = 0; i < security_dirs.size(); ++i) {
new_path = base_path + security_dirs[i] + new_path =
DeviceFiles::GetCertificateFileName(); base_path + security_dirs[i] + DeviceFiles::GetCertificateFileName();
EXPECT_CALL(file, Copy(StrEq(old_path), StrEq(new_path))) EXPECT_CALL(file, Copy(StrEq(old_path), StrEq(new_path)))
.WillOnce(Return(true)); .WillOnce(Return(true));
} }
@@ -1377,7 +1681,8 @@ TEST_F(DeviceFilesTest, SecurityLevelPathBackwardCompatibility) {
EXPECT_CALL(file, Write(_, _)).Times(0); EXPECT_CALL(file, Write(_, _)).Times(0);
DeviceFiles device_files; DeviceFiles device_files;
EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1)); EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
device_files.SetTestFile(&file);
Properties::Init(); Properties::Init();
std::string certificate, wrapped_private_key; std::string certificate, wrapped_private_key;
@@ -1391,12 +1696,14 @@ TEST_F(DeviceFilesTest, UpdateLicenseState) {
license_update_test_data[0].key_set_id + license_update_test_data[0].key_set_id +
DeviceFiles::GetLicenseFileNameExtension(); DeviceFiles::GetLicenseFileNameExtension();
EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_))).Times(2) EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_)))
.Times(2)
.WillRepeatedly(Return(true)); .WillRepeatedly(Return(true));
EXPECT_CALL(file, CreateDirectory(_)).Times(0); EXPECT_CALL(file, CreateDirectory(_)).Times(0);
EXPECT_CALL(file, Open(StrEq(license_path), EXPECT_CALL(file, Open(StrEq(license_path),
AllOf(IsCreateFileFlagSet(), IsBinaryFileFlagSet()))) AllOf(IsCreateFileFlagSet(), IsBinaryFileFlagSet())))
.Times(2).WillRepeatedly(Return(true)); .Times(2)
.WillRepeatedly(Return(true));
EXPECT_CALL(file, Write(IsStrEq(license_update_test_data[0].file_data), EXPECT_CALL(file, Write(IsStrEq(license_update_test_data[0].file_data),
Eq(license_update_test_data[0].file_data.size()))) Eq(license_update_test_data[0].file_data.size())))
.WillOnce(ReturnArg<1>()); .WillOnce(ReturnArg<1>());
@@ -1407,7 +1714,8 @@ TEST_F(DeviceFilesTest, UpdateLicenseState) {
EXPECT_CALL(file, Read(_, _)).Times(0); EXPECT_CALL(file, Read(_, _)).Times(0);
DeviceFiles device_files; DeviceFiles device_files;
EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1)); EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
device_files.SetTestFile(&file);
EXPECT_TRUE(device_files.StoreLicense( EXPECT_TRUE(device_files.StoreLicense(
license_update_test_data[0].key_set_id, license_update_test_data[0].key_set_id,
license_update_test_data[0].license_state, license_update_test_data[0].license_state,
@@ -1437,7 +1745,9 @@ TEST_F(DeviceFilesTest, DeleteLicense) {
size_t size = license_test_data[0].file_data.size(); size_t size = license_test_data[0].file_data.size();
EXPECT_CALL(file, Exists(StrEq(license_path))).Times(2).WillOnce(Return(true)) EXPECT_CALL(file, Exists(StrEq(license_path)))
.Times(2)
.WillOnce(Return(true))
.WillOnce(Return(false)); .WillOnce(Return(false));
EXPECT_CALL(file, FileSize(StrEq(license_path))).WillOnce(Return(size)); EXPECT_CALL(file, FileSize(StrEq(license_path))).WillOnce(Return(size));
EXPECT_CALL(file, Open(StrEq(license_path), IsBinaryFileFlagSet())) EXPECT_CALL(file, Open(StrEq(license_path), IsBinaryFileFlagSet()))
@@ -1451,7 +1761,8 @@ TEST_F(DeviceFilesTest, DeleteLicense) {
EXPECT_CALL(file, Write(_, _)).Times(0); EXPECT_CALL(file, Write(_, _)).Times(0);
DeviceFiles device_files; DeviceFiles device_files;
EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1)); EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
device_files.SetTestFile(&file);
DeviceFiles::LicenseState license_state; DeviceFiles::LicenseState license_state;
CdmInitData pssh_data; CdmInitData pssh_data;
CdmKeyMessage key_request; CdmKeyMessage key_request;
@@ -1475,4 +1786,154 @@ TEST_F(DeviceFilesTest, DeleteLicense) {
EXPECT_FALSE(device_files.LicenseExists(license_test_data[0].key_set_id)); EXPECT_FALSE(device_files.LicenseExists(license_test_data[0].key_set_id));
} }
TEST_P(DeviceFilesUsageInfoTest, Read) {
MockFile file;
std::string path = device_base_path_ + DeviceFiles::GetUsageInfoFileName();
int index = GetParam();
std::string data;
if (index >= 0) {
data = kUsageInfoTestData[index].file_data;
}
if (index >= 0) {
EXPECT_CALL(file, Exists(StrEq(path))).WillOnce(Return(true));
EXPECT_CALL(file, FileSize(StrEq(path))).WillOnce(Return(data.size()));
EXPECT_CALL(file, Open(StrEq(path), IsBinaryFileFlagSet()))
.WillOnce(Return(true));
EXPECT_CALL(file, Read(NotNull(), Eq(data.size()))).WillOnce(DoAll(
SetArrayArgument<0>(data.begin(), data.end()), Return(data.size())));
EXPECT_CALL(file, Close()).Times(1);
} else {
EXPECT_CALL(file, Exists(StrEq(path))).Times(2).WillRepeatedly(
Return(false));
EXPECT_CALL(file, FileSize(_)).Times(0);
EXPECT_CALL(file, Open(_, _)).Times(0);
EXPECT_CALL(file, Close()).Times(0);
}
EXPECT_CALL(file, Write(_, _)).Times(0);
DeviceFiles device_files;
EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
device_files.SetTestFile(&file);
std::vector<std::pair<CdmKeyMessage, CdmKeyResponse> > license_info;
ASSERT_TRUE(device_files.RetrieveUsageInfo(&license_info));
if (index >= 0) {
EXPECT_EQ(index, license_info.size());
for (size_t i = 0; i < license_info.size(); ++i) {
bool found = false;
for (size_t j = 0; j <= static_cast<size_t>(index); ++j) {
if ((license_info[i].first.compare(
kUsageInfoTestData[j].license_request) == 0) &&
(license_info[i].second.compare(kUsageInfoTestData[j].license) ==
0)) {
found = true;
}
}
EXPECT_TRUE(found);
}
} else {
EXPECT_EQ(0, license_info.size());
}
}
TEST_P(DeviceFilesUsageInfoTest, Store) {
MockFile file;
std::string pst(GenerateRandomData(kProviderSessionTokenLen));
std::string license_request(GenerateRandomData(kLicenseRequestLen));
std::string license(GenerateRandomData(kLicenseLen));
std::string path = device_base_path_ + DeviceFiles::GetUsageInfoFileName();
int index = GetParam();
std::string data;
if (index >= 0) {
data = kUsageInfoTestData[index].file_data;
}
EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_)))
.WillRepeatedly(Return(true));
EXPECT_CALL(file, CreateDirectory(_)).Times(0);
EXPECT_CALL(file, Exists(StrEq(path))).WillOnce(Return(index >= 0));
if (index >= 0) {
EXPECT_CALL(file, FileSize(StrEq(path))).WillOnce(Return(data.size()));
EXPECT_CALL(file, Open(StrEq(path), IsBinaryFileFlagSet()))
.Times(2)
.WillRepeatedly(Return(true));
EXPECT_CALL(file, Read(NotNull(), Eq(data.size()))).WillOnce(DoAll(
SetArrayArgument<0>(data.begin(), data.end()), Return(data.size())));
EXPECT_CALL(file, Close()).Times(2);
} else {
EXPECT_CALL(file, FileSize(_)).Times(0);
EXPECT_CALL(file, Open(_, _)).Times(1).WillOnce(Return(true));
EXPECT_CALL(file, Close()).Times(1);
}
EXPECT_CALL(file, Write(Contains(pst, license_request, license, data.size()),
Gt(pst.size() + license_request.size() +
license.size()))).WillOnce(ReturnArg<1>());
DeviceFiles device_files;
EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
device_files.SetTestFile(&file);
ASSERT_TRUE(device_files.StoreUsageInfo(pst, license_request, license));
}
TEST_P(DeviceFilesUsageInfoTest, Delete) {
MockFile file;
std::string path = device_base_path_ + DeviceFiles::GetUsageInfoFileName();
int index = GetParam();
if (index < 0) return;
std::string data, pst, prev_data, prev_pst, prev_license;
if (index >= 0) {
data = kUsageInfoTestData[index].file_data;
if (index >= 1) {
pst = kUsageInfoTestData[index].provider_session_token;
prev_data = kUsageInfoTestData[index - 1].file_data;
prev_pst = kUsageInfoTestData[index - 1].provider_session_token;
prev_license = kUsageInfoTestData[index - 1].license;
}
}
EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_)))
.WillRepeatedly(Return(true));
EXPECT_CALL(file, CreateDirectory(_)).Times(0);
EXPECT_CALL(file, Exists(StrEq(path))).WillOnce(Return(index >= 0));
EXPECT_CALL(file, FileSize(StrEq(path))).WillOnce(Return(data.size()));
if (index >= 1) {
EXPECT_CALL(file, Open(StrEq(path), IsBinaryFileFlagSet()))
.Times(2)
.WillRepeatedly(Return(true));
EXPECT_CALL(file, Write(Contains(prev_pst, prev_license, prev_data.size()),
Gt(prev_pst.size() + prev_license.size())))
.WillOnce(ReturnArg<1>());
EXPECT_CALL(file, Close()).Times(2);
} else {
EXPECT_CALL(file, Open(StrEq(path), IsBinaryFileFlagSet()))
.WillOnce(Return(true));
EXPECT_CALL(file, Write(_, _)).Times(0);
EXPECT_CALL(file, Close()).Times(1);
}
EXPECT_CALL(file, Read(NotNull(), Eq(data.size()))).WillOnce(DoAll(
SetArrayArgument<0>(data.begin(), data.end()), Return(data.size())));
DeviceFiles device_files;
EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
device_files.SetTestFile(&file);
if (index >= 1) {
ASSERT_TRUE(device_files.DeleteUsageInfo(pst));
} else {
ASSERT_FALSE(device_files.DeleteUsageInfo(pst));
}
}
INSTANTIATE_TEST_CASE_P(UsageInfo, DeviceFilesUsageInfoTest,
::testing::Values(-1, 0, 1, 2, 3));
} // namespace wvcdm } // namespace wvcdm

View File

@@ -5,17 +5,35 @@
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <netdb.h> #include <netdb.h>
#include <string.h> #include <netinet/in.h>
#include <stdlib.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <unistd.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/x509.h>
#include "log.h" #include "log.h"
#include "openssl/bio.h"
#include "openssl/err.h"
#include "openssl/x509.h"
namespace wvcdm { namespace {
SSL_CTX* HttpSocket::InitSslContext(void) { // Helper function to tokenize a string. This makes it easier to avoid silly
// parsing bugs that creep in easily when each part of the string is parsed
// with its own piece of code.
bool Tokenize(const std::string& source, const std::string& delim,
const size_t offset, std::string* substring_output,
size_t* next_offset) {
size_t start_of_delim = source.find(delim, offset);
if (start_of_delim == std::string::npos) {
return false;
}
substring_output->assign(source, offset, start_of_delim - offset);
*next_offset = start_of_delim + delim.size();
return true;
}
SSL_CTX* InitSslContext() {
const SSL_METHOD* method; const SSL_METHOD* method;
SSL_CTX* ctx; SSL_CTX* ctx;
@@ -23,21 +41,18 @@ SSL_CTX* HttpSocket::InitSslContext(void) {
SSL_load_error_strings(); SSL_load_error_strings();
method = SSLv3_client_method(); method = SSLv3_client_method();
ctx = SSL_CTX_new(method); ctx = SSL_CTX_new(method);
if (NULL == ctx) { if (!ctx)
LOGE("failed to create SSL context"); LOGE("failed to create SSL context");
}
return ctx; return ctx;
} }
void HttpSocket::ShowServerCertificate(const SSL* ssl) { // unused, may be useful for debugging SSL-related issues.
X509* cert; void ShowServerCertificate(const SSL* ssl) {
char* line;
// gets the server certificate // gets the server certificate
cert = SSL_get_peer_certificate(ssl); X509* cert = SSL_get_peer_certificate(ssl);
if (cert != NULL) { if (cert) {
char* line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
LOGV("server certificate:"); LOGV("server certificate:");
line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
LOGV("subject: %s", line); LOGV("subject: %s", line);
free(line); free(line);
line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0); line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
@@ -49,183 +64,252 @@ void HttpSocket::ShowServerCertificate(const SSL* ssl) {
} }
} }
HttpSocket::HttpSocket() // Wait for a socket to be ready for reading or writing.
: secure_connect_(true), // Establishing a connection counts as "ready for write".
socket_fd_(-1), // Returns false on select error or timeout.
// Returns true when the socket is ready.
bool SocketWait(int fd, bool for_read, int timeout_in_ms) {
fd_set fds;
FD_ZERO(&fds);
FD_SET(fd, &fds);
struct timeval tv;
tv.tv_sec = timeout_in_ms / 1000;
tv.tv_usec = (timeout_in_ms % 1000) * 1000;
fd_set *read_fds = NULL;
fd_set *write_fds = NULL;
if (for_read) {
read_fds = &fds;
} else {
write_fds = &fds;
}
int ret = select(fd + 1, read_fds, write_fds, NULL, &tv);
if (ret == 0) {
LOGE("socket timed out");
return false;
} else if (ret == -1) {
LOGE("select failed, errno = %d", errno);
return false;
}
// socket ready.
return true;
}
} // namespace
namespace wvcdm {
// Parses the URL and extracts all relevant information.
// static
bool HttpSocket::ParseUrl(const std::string& url,
std::string* scheme,
bool* secure_connect,
std::string* domain_name,
int* port,
std::string* path) {
size_t offset = 0;
if (!Tokenize(url, "://", offset, scheme, &offset)) {
LOGE("Invalid URL, scheme not found: %s", url.c_str());
return false;
}
// If the scheme is http or https, set secure_connect and port accordingly.
// Otherwise, consider the scheme unsupported and fail.
if (*scheme == "http") {
*secure_connect = false;
*port = 80;
} else if (*scheme == "https") {
*secure_connect = true;
*port = 443;
} else {
LOGE("Invalid URL, scheme not supported: %s", url.c_str());
return false;
}
if (!Tokenize(url, "/", offset, domain_name, &offset)) {
// The rest of the URL belongs to the domain name.
domain_name->assign(url, offset, std::string::npos);
// No explicit path after the domain name.
path->assign("/");
} else {
// The rest of the URL, including the preceding slash, belongs to the path.
path->assign(url, offset - 1, std::string::npos);
}
// The domain name may optionally contain a port which overrides the default.
std::string domain_name_without_port;
size_t port_offset;
if (Tokenize(*domain_name, ":", 0, &domain_name_without_port,
&port_offset)) {
*port = atoi(domain_name->c_str() + port_offset);
if (*port <= 0 || *port >= 65536) {
LOGE("Invalid URL, port not valid: %s", url.c_str());
return false;
}
domain_name->assign(domain_name_without_port);
}
return true;
}
HttpSocket::HttpSocket(const std::string& url)
: socket_fd_(-1),
ssl_(NULL), ssl_(NULL),
ssl_ctx_(NULL), ssl_ctx_(NULL) {
timeout_enabled_(false) { valid_url_ = ParseUrl(url, &scheme_, &secure_connect_, &domain_name_, &port_,
&resource_path_);
SSL_library_init(); SSL_library_init();
} }
HttpSocket::~HttpSocket() { CloseSocket(); } HttpSocket::~HttpSocket() {
CloseSocket();
}
void HttpSocket::CloseSocket() { void HttpSocket::CloseSocket() {
if (socket_fd_ != -1) { if (socket_fd_ != -1) {
close(socket_fd_); close(socket_fd_);
socket_fd_ = -1; socket_fd_ = -1;
} }
if (secure_connect_) { if (ssl_) {
if (ssl_) { SSL_free(ssl_);
SSL_free(ssl_); ssl_ = NULL;
ssl_ = NULL; }
} if (ssl_ctx_) {
if (ssl_ctx_) { SSL_CTX_free(ssl_ctx_);
CloseSslContext(ssl_ctx_); ssl_ctx_ = NULL;
ssl_ctx_ = NULL;
}
} }
} }
// Extracts the domain name and resource path from the input url parameter. bool HttpSocket::Connect(int timeout_in_ms) {
// The results are put in domain_name and resource_path respectively. if (!valid_url_) {
// The format of the url can begin with <protocol/scheme>:://domain server/... return false;
// 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, bool secure_connection) {
secure_connect_ = secure_connection;
if (secure_connect_) ssl_ctx_ = InitSslContext();
GetDomainNameAndPathFromUrl(url, domain_name_, resource_path_);
// get a socket
socket_fd_ = socket(AF_INET, SOCK_STREAM, 0); socket_fd_ = socket(AF_INET, SOCK_STREAM, 0);
if (socket_fd_ < 0) { if (socket_fd_ < 0) {
LOGE("cannot open socket %d", errno); LOGE("cannot open socket, errno = %d", errno);
return false; return false;
} }
int reuse = 1; // set the socket in non-blocking mode
if (setsockopt(socket_fd_, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == int original_flags = fcntl(socket_fd_, F_GETFL, 0);
-1) { if (original_flags == -1) {
LOGE("fcntl error, errno = %d", errno);
CloseSocket();
return false;
}
if (fcntl(socket_fd_, F_SETFL, original_flags | O_NONBLOCK) == -1) {
LOGE("fcntl error, errno = %d", errno);
CloseSocket(); CloseSocket();
LOGE("setsockopt error %d", errno);
return false; return false;
} }
// lookup the server IP
struct addrinfo hints; struct addrinfo hints;
memset(&hints, 0, sizeof(hints)); memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET; hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM; hints.ai_socktype = SOCK_STREAM;
struct addrinfo* addr_info = NULL; struct addrinfo* addr_info = NULL;
bool status = true; int ret = getaddrinfo(domain_name_.c_str(), NULL, &hints, &addr_info);
int ret = getaddrinfo(domain_name_.c_str(), port.c_str(), &hints, &addr_info);
if (ret != 0) { if (ret != 0) {
CloseSocket(); LOGE("getaddrinfo failed, errno = %d", ret);
LOGE("getaddrinfo failed with %d", ret); return false;
status = false; }
// set the port
struct sockaddr_in* addr_ipv4 = reinterpret_cast<struct sockaddr_in*>(
addr_info->ai_addr);
addr_ipv4->sin_port = htons(port_);
// connect to the server
ret = connect(socket_fd_, addr_info->ai_addr, addr_info->ai_addrlen);
freeaddrinfo(addr_info);
if (ret == 0) {
// connected right away.
} else { } else {
if (connect(socket_fd_, addr_info->ai_addr, addr_info->ai_addrlen) == -1) { if (errno != EINPROGRESS) {
// failed right away.
LOGE("cannot connect to %s, errno = %d", domain_name_.c_str(), errno);
CloseSocket(); CloseSocket();
LOGE("cannot connect socket to %s, error=%d", domain_name_.c_str(), return false;
errno); } else {
status = false; // in progress. block until timeout expired or connection established.
if (!SocketWait(socket_fd_, /* for_read */ false, timeout_in_ms)) {
LOGE("cannot connect to %s", domain_name_.c_str());
CloseSocket();
return false;
}
} }
} }
timeout_enabled_ = enable_timeout;
if (addr_info != NULL) {
freeaddrinfo(addr_info);
}
if (!status) return false; // set up SSL if needed
if (secure_connect_) {
ssl_ctx_ = InitSslContext();
if (!ssl_ctx_) {
CloseSocket();
return false;
}
// secures connection
if (secure_connect_ && ssl_ctx_) {
ssl_ = SSL_new(ssl_ctx_); ssl_ = SSL_new(ssl_ctx_);
if (!ssl_) { if (!ssl_) {
LOGE("failed SSL_new"); LOGE("failed SSL_new");
CloseSocket();
return false; return false;
} }
BIO* a_bio = BIO_new_socket(socket_fd_, BIO_NOCLOSE); BIO* a_bio = BIO_new_socket(socket_fd_, BIO_NOCLOSE);
if (!a_bio) { if (!a_bio) {
LOGE("BIO_new_socket error"); LOGE("BIO_new_socket error");
CloseSocket();
return false; return false;
} }
SSL_set_bio(ssl_, a_bio, a_bio); SSL_set_bio(ssl_, a_bio, a_bio);
int ret = SSL_connect(ssl_); do {
if (1 != ret) { ret = SSL_connect(ssl_);
char buf[256]; if (ret != 1) {
LOGE("SSL_connect error:%s", ERR_error_string(ERR_get_error(), buf)); int ssl_err = SSL_get_error(ssl_, ret);
return false; if (ssl_err != SSL_ERROR_WANT_READ &&
} ssl_err != SSL_ERROR_WANT_WRITE) {
char buf[256];
LOGE("SSL_connect error: %s", ERR_error_string(ERR_get_error(), buf));
CloseSocket();
return false;
}
bool for_read = ssl_err == SSL_ERROR_WANT_READ;
if (!SocketWait(socket_fd_, for_read, timeout_in_ms)) {
LOGE("cannot connect to %s", domain_name_.c_str());
CloseSocket();
return false;
}
}
} while (ret != 1);
} }
return true; return true;
} }
int HttpSocket::Read(char* data, int len) { return (Read(data, len, 0)); } // Returns -1 for error, number of bytes read for success.
// The timeout here only applies to the span between packets of data, for the
// makes non-blocking mode only during read, it supports timeout for read // sake of simplicity.
// returns -1 for error, number of bytes read for success
int HttpSocket::Read(char* data, int len, int timeout_in_ms) { 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 total_read = 0;
int read = 0;
int to_read = len; int to_read = len;
while (to_read > 0) { while (to_read > 0) {
if (use_timeout) { if (!SocketWait(socket_fd_, /* for_read */ true, timeout_in_ms)) {
fd_set read_fds; LOGE("unable to read from %s", domain_name_.c_str());
struct timeval tv; return -1;
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)) {
LOGD("socket read timeout");
break;
}
} }
int read;
if (secure_connect_) if (secure_connect_)
read = SSL_read(ssl_, data, to_read); read = SSL_read(ssl_, data, to_read);
else else
@@ -236,27 +320,26 @@ int HttpSocket::Read(char* data, int len, int timeout_in_ms) {
data += read; data += read;
total_read += read; total_read += read;
} else if (read == 0) { } else if (read == 0) {
// in blocking mode, zero read mean's peer closed. // The connection has been closed. No more data.
// in non-blocking mode, select said that there is data. so it should not
// happen
break; break;
} else { } else {
LOGE("recv returned %d, error = %d", read, errno); LOGE("recv returned %d, errno = %d", read, errno);
break; return -1;
} }
} }
if (use_timeout) {
fcntl(socket_fd_, F_SETFL, original_flags); // now blocking again
}
return total_read; return total_read;
} }
int HttpSocket::Write(const char* data, int len) { // Returns -1 for error, number of bytes written for success.
// The timeout here only applies to the span between packets of data, for the
// sake of simplicity.
int HttpSocket::Write(const char* data, int len, int timeout_in_ms) {
int total_sent = 0; int total_sent = 0;
int sent = 0;
int to_send = len; int to_send = len;
while (to_send > 0) { while (to_send > 0) {
int sent;
if (secure_connect_) if (secure_connect_)
sent = SSL_write(ssl_, data, to_send); sent = SSL_write(ssl_, data, to_send);
else else
@@ -267,11 +350,17 @@ int HttpSocket::Write(const char* data, int len) {
data += sent; data += sent;
total_sent += sent; total_sent += sent;
} else if (sent == 0) { } else if (sent == 0) {
usleep(10); // retry later // We filled up the pipe. Wait for room to write.
if (!SocketWait(socket_fd_, /* for_read */ false, timeout_in_ms)) {
LOGE("unable to write to %s", domain_name_.c_str());
return -1;
}
} else { } else {
LOGE("send returned error %d", errno); LOGE("send returned %d, errno = %d", sent, errno);
return -1;
} }
} }
return total_sent; return total_sent;
} }

View File

@@ -4,43 +4,52 @@
#define CDM_TEST_HTTP_SOCKET_H_ #define CDM_TEST_HTTP_SOCKET_H_
#include <string> #include <string>
#include "openssl/ssl.h"
#include "wv_cdm_types.h" #include <gtest/gtest_prod.h>
#include <openssl/ssl.h>
#include "wv_cdm_types.h" // CORE_DISALLOW_COPY_AND_ASSIGN
namespace wvcdm { namespace wvcdm {
// Provides basic Linux based TCP socket interface. // Provides basic Linux based TCP socket interface.
class HttpSocket { class HttpSocket {
public: public:
HttpSocket(); // A scheme (http:// or https://) is required for the URL.
explicit HttpSocket(const std::string& url);
~HttpSocket(); ~HttpSocket();
bool Connect(int timeout_in_ms);
void CloseSocket(); void CloseSocket();
bool Connect(const char* url, const std::string& port, bool enable_timeout,
bool secure_connection); const std::string& scheme() const { return scheme_; }
void GetDomainNameAndPathFromUrl(const std::string& url, bool secure_connect() const { return secure_connect_; }
std::string& domain_name, const std::string& domain_name() const { return domain_name_; }
std::string& resource_path); int port() const { return port_; }
const std::string& domain_name() const { return domain_name_; }; const std::string& resource_path() const { return resource_path_; }
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 Read(char* data, int len, int timeout_in_ms);
int Write(const char* data, int len); int Write(const char* data, int len, int timeout_in_ms);
private: private:
void CloseSslContext(SSL_CTX* ctx) const { static bool ParseUrl(const std::string& url,
if (ctx) SSL_CTX_free(ctx); std::string* scheme,
} bool* secure_connect,
SSL_CTX* InitSslContext(void); std::string* domain_name,
void ShowServerCertificate(const SSL* ssl); int* port,
std::string* path);
FRIEND_TEST(HttpSocketTest, ParseUrlTest);
std::string domain_name_; std::string scheme_;
bool secure_connect_; bool secure_connect_;
std::string domain_name_;
int port_;
std::string resource_path_; std::string resource_path_;
bool valid_url_;
int socket_fd_; int socket_fd_;
SSL* ssl_; SSL* ssl_;
SSL_CTX* ssl_ctx_; SSL_CTX* ssl_ctx_;
bool timeout_enabled_;
CORE_DISALLOW_COPY_AND_ASSIGN(HttpSocket); CORE_DISALLOW_COPY_AND_ASSIGN(HttpSocket);
}; };

View File

@@ -3,6 +3,7 @@
#include <errno.h> #include <errno.h>
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include "http_socket.h" #include "http_socket.h"
#include "scoped_ptr.h"
#include "log.h" #include "log.h"
#include "string_conversions.h" #include "string_conversions.h"
#include "url_request.h" #include "url_request.h"
@@ -10,10 +11,14 @@
namespace { namespace {
// Arbitrary URL for tests. // Arbitrary URL for tests.
const std::string kHttpsTestServer("https://www.google.com"); const std::string kHttpsTestServer("https://www.google.com");
const std::string kHttpTestServer("http://www.google.com");
// This URL and data are used by RoundTripTest, and can be overridden on the
// command line.
std::string gTestServer(kHttpsTestServer); std::string gTestServer(kHttpsTestServer);
std::string gTestData("Hello"); std::string gTestData("Hello");
// Arbitrary buffer size and timeout settings.
const int kHttpBufferSize = 4096; const int kHttpBufferSize = 4096;
char gBuffer[kHttpBufferSize]; const int kTimeout = 3000;
} }
namespace wvcdm { namespace wvcdm {
@@ -21,153 +26,179 @@ namespace wvcdm {
class HttpSocketTest : public testing::Test { class HttpSocketTest : public testing::Test {
public: public:
HttpSocketTest() {} HttpSocketTest() {}
~HttpSocketTest() { socket_.CloseSocket(); } ~HttpSocketTest() {}
protected: protected:
bool Connect(const std::string& server_url, bool secure_connection) { bool Connect(const std::string& server_url) {
socket_.reset(new HttpSocket(server_url));
std::string port = secure_connection ? "443" : "80"; if (socket_->Connect(kTimeout)) {
if (socket_.Connect(server_url.c_str(), port, true, secure_connection)) { LOGD("connected to %s", socket_->domain_name().c_str());
LOGD("connected to %s", socket_.domain_name().c_str()); return true;
} else { } else {
LOGE("failed to connect to %s", socket_.domain_name().c_str()); LOGE("failed to connect to %s", server_url.c_str());
return false; return false;
} }
return true;
} }
bool PostRequest(const std::string& data) { bool PostRequest(const std::string& data) {
std::string request("POST "); std::string request("POST ");
if (socket_.resource_path().empty()) request.append(socket_->resource_path());
request.append(socket_.domain_name());
else
request.append(socket_.resource_path());
request.append(" HTTP/1.1\r\n"); request.append(" HTTP/1.1\r\n");
request.append("Host: "); request.append("Host: ");
request.append(socket_.domain_name()); request.append(socket_->domain_name());
request.append("\r\nUser-Agent: httpSocketTest/1.0\r\n"); request.append("\r\n");
// Important! Otherwise, the HTTP 1.1 default behavior for a server is to
// keep the connection open for a subsequent request.
request.append("Connection: close\r\n");
request.append("User-Agent: httpSocketTest/1.0\r\n");
char buffer[32] = {0};
snprintf(buffer, sizeof(buffer), "%d", static_cast<int>(data.size()));
request.append("Content-Length: "); request.append("Content-Length: ");
memset(gBuffer, 0, kHttpBufferSize); request.append(buffer);
snprintf(gBuffer, kHttpBufferSize, "%d\r\n", static_cast<int>(data.size())); request.append("\r\n");
request.append(gBuffer);
request.append("Content-Type: multipart/form-data\r\n"); request.append("Content-Type: multipart/form-data\r\n");
// newline terminates header // an extra newline terminates HTTP headers.
request.append("\r\n"); request.append("\r\n");
// append data // append data
request.append(data); request.append(data);
socket_.Write(request.c_str(), request.size()); socket_->Write(request.c_str(), request.size(), kTimeout);
LOGD("request: %s", request.c_str()); LOGD("request: %s", request.c_str());
return true; return true;
} }
bool GetResponse() { bool GetResponse(std::string* response) {
int bytes = socket_.Read(gBuffer, kHttpBufferSize, 1000); char buffer[kHttpBufferSize];
int bytes = socket_->Read(buffer, sizeof(buffer), kTimeout);
if (bytes < 0) { if (bytes < 0) {
LOGE("read error = ", errno); LOGE("read error, errno = %d", errno);
return false; 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;
} }
LOGD("read %d bytes", bytes);
response->assign(buffer, bytes);
return true;
} }
HttpSocket socket_; scoped_ptr<HttpSocket> socket_;
std::string domain_name_; std::string domain_name_;
std::string resource_path_; std::string resource_path_;
}; };
TEST_F(HttpSocketTest, GetDomainNameAndPathFromUrlTest) { struct ParseUrlTests {
socket_.GetDomainNameAndPathFromUrl( const char* url;
"https://code.google.com/p/googletest/wiki/Primer", domain_name_, const char* scheme;
resource_path_); bool secure_connect;
EXPECT_STREQ("code.google.com", domain_name_.c_str()); const char* domain_name;
EXPECT_STREQ("p/googletest/wiki/Primer", resource_path_.c_str()); int port;
const char* path;
};
socket_.GetDomainNameAndPathFromUrl( ParseUrlTests parse_url_tests[] = {
"http://code.google.com/p/googletest/wiki/Primer/", domain_name_, {
resource_path_); "https://code.google.com/p/googletest/wiki/Primer", // url
EXPECT_STREQ("code.google.com", domain_name_.c_str()); "https", // scheme
EXPECT_STREQ("p/googletest/wiki/Primer/", resource_path_.c_str()); true, // secure_connect
"code.google.com", // domain_name
443, // port
"/p/googletest/wiki/Primer", // path
},
{
"http://code.google.com/p/googletest/wiki/Primer/", // url
"http", // scheme
false, // secure_connect
"code.google.com", // domain_name
80, // port
"/p/googletest/wiki/Primer/", // path
},
{
"http://code.google.com/", // url
"http", // scheme
false, // secure_connect
"code.google.com", // domain_name
80, // port
"/", // path
},
{
"http://code.google.com", // url
"http", // scheme
false, // secure_connect
"code.google.com", // domain_name
80, // port
"/", // path
},
{
"http://10.11.12.13:8888/drm", // url
"http", // scheme
false, // secure_connect
"10.11.12.13", // domain_name
8888, // port
"/drm", // path
},
{
"http://10.11.12.13:8888", // url
"http", // scheme
false, // secure_connect
"10.11.12.13", // domain_name
8888, // port
"/", // path
},
{
"https://10.11.12.13:8888", // url
"https", // scheme
true, // secure_connect
"10.11.12.13", // domain_name
8888, // port
"/", // path
},
{ NULL } // list terminator
};
socket_.GetDomainNameAndPathFromUrl("http://code.google.com/", domain_name_, TEST_F(HttpSocketTest, ParseUrlTest) {
resource_path_); std::string scheme;
EXPECT_STREQ("code.google.com", domain_name_.c_str()); bool secure_connect;
EXPECT_STREQ("", resource_path_.c_str()); std::string domain_name;
int port;
std::string path;
ParseUrlTests* test = NULL;
socket_.GetDomainNameAndPathFromUrl("http://code.google.com", domain_name_, for (test = &parse_url_tests[0]; test->url != NULL; ++test) {
resource_path_); bool ok = HttpSocket::ParseUrl(test->url, &scheme, &secure_connect,
EXPECT_STREQ("code.google.com", domain_name_.c_str()); &domain_name, &port, &path);
EXPECT_STREQ("", resource_path_.c_str()); EXPECT_TRUE(ok);
if (ok) {
socket_.GetDomainNameAndPathFromUrl( EXPECT_EQ(test->scheme, scheme);
"code.google.com/p/googletest/wiki/Primer", domain_name_, resource_path_); EXPECT_EQ(test->secure_connect, secure_connect);
EXPECT_STREQ("code.google.com", domain_name_.c_str()); EXPECT_EQ(test->domain_name, domain_name);
EXPECT_STREQ("p/googletest/wiki/Primer", resource_path_.c_str()); EXPECT_EQ(test->port, port);
EXPECT_EQ(test->path, path);
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());
// Test with arbitrary numeric URL
socket_.GetDomainNameAndPathFromUrl("http://10.11.12.13:8888/drm",
domain_name_, resource_path_);
EXPECT_STREQ("10.11.12.13", domain_name_.c_str());
EXPECT_STREQ("drm", resource_path_.c_str());
socket_.GetDomainNameAndPathFromUrl("http://10.11.12.13:8888", domain_name_,
resource_path_);
EXPECT_STREQ("10.11.12.13", domain_name_.c_str());
EXPECT_TRUE(resource_path_.empty());
} }
TEST_F(HttpSocketTest, ConnectTest) { TEST_F(HttpSocketTest, ConnectTest) {
const bool kUseSecureConnection = true; EXPECT_TRUE(Connect(kHttpsTestServer));
EXPECT_TRUE(Connect(kHttpTestServer));
if (gTestServer.find("https") != std::string::npos) { EXPECT_FALSE(Connect("ww.g.c"));
EXPECT_TRUE(Connect(gTestServer, kUseSecureConnection)); EXPECT_FALSE(Connect("http://ww.g.c"));
socket_.CloseSocket(); EXPECT_FALSE(Connect("https://ww.g.c"));
// https connection allows insecure connection through port 80 as well
EXPECT_TRUE(Connect(gTestServer, !kUseSecureConnection));
socket_.CloseSocket();
} else {
EXPECT_TRUE(Connect(gTestServer, !kUseSecureConnection));
socket_.CloseSocket();
// Test for the case that non-https connection must not use port 443
EXPECT_FALSE(Connect(gTestServer, kUseSecureConnection));
socket_.CloseSocket();
}
EXPECT_FALSE(Connect("ww.g.c", kUseSecureConnection));
socket_.CloseSocket();
EXPECT_FALSE(Connect("ww.g.c", !kUseSecureConnection));
socket_.CloseSocket();
} }
TEST_F(HttpSocketTest, RoundTripTest) { TEST_F(HttpSocketTest, RoundTripTest) {
int secure_connection = ASSERT_TRUE(Connect(gTestServer));
(gTestServer.find("https") != std::string::npos) ? true : false;
ASSERT_TRUE(Connect(gTestServer, secure_connection));
EXPECT_TRUE(PostRequest(gTestData)); EXPECT_TRUE(PostRequest(gTestData));
GetResponse();
socket_.CloseSocket(); std::string response;
EXPECT_TRUE(GetResponse(&response));
LOGD("response: %s", response.c_str());
} }
} // namespace wvcdm } // namespace wvcdm

View File

@@ -10,52 +10,16 @@
#include "string_conversions.h" #include "string_conversions.h"
namespace { namespace {
const int kMaxReadAttempts = 4;
const int kSingleReadAttempt = 1;
} // namespace
namespace wvcdm { const int kReadBufferSize = 1024;
const int kConnectTimeoutMs = 5000;
UrlRequest::UrlRequest(const std::string& url, const std::string& port, const int kWriteTimeoutMs = 3000;
bool secure_connection, bool chunk_transfer_mode) const int kReadTimeoutMs = 3000;
: chunk_transfer_mode_(chunk_transfer_mode),
is_connected_(false),
port_("80"),
request_(""),
server_url_(url) {
if (!port.empty()) {
port_.assign(port);
}
if (socket_.Connect((server_url_).c_str(), port_, true, secure_connection)) {
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, "%zx\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
}
// Concatenate all chunks into one blob and returns the response with // Concatenate all chunks into one blob and returns the response with
// header information. // header information.
void UrlRequest::ConcatenateChunkedResponse(const std::string http_response, void ConcatenateChunkedResponse(const std::string http_response,
std::string* modified_response) { std::string* modified_response) {
if (http_response.empty()) return; if (http_response.empty()) return;
modified_response->clear(); modified_response->clear();
@@ -103,33 +67,52 @@ void UrlRequest::ConcatenateChunkedResponse(const std::string http_response,
} }
} }
int UrlRequest::GetResponse(std::string* message) { } // namespace
message->clear();
namespace wvcdm {
UrlRequest::UrlRequest(const std::string& url)
: is_connected_(false),
socket_(url) {
if (socket_.Connect(kConnectTimeoutMs)) {
is_connected_ = true;
} else {
LOGE("failed to connect to %s, port=%d", socket_.domain_name().c_str(),
socket_.port());
}
}
UrlRequest::~UrlRequest() {}
bool UrlRequest::GetResponse(std::string* message) {
std::string response; std::string response;
const int kTimeoutInMs = 3000;
int bytes = 0; // Keep reading until end of stream (0 bytes read) or timeout. Partial
for (int attempts = kMaxReadAttempts; attempts > 0; --attempts) { // buffers worth of data can and do happen, especially with OpenSSL in
memset(buffer_, 0, kHttpBufferSize); // non-blocking mode.
bytes = socket_.Read(buffer_, kHttpBufferSize, kTimeoutInMs); while (true) {
char read_buffer[kReadBufferSize];
int bytes = socket_.Read(read_buffer, sizeof(read_buffer), kReadTimeoutMs);
if (bytes > 0) { if (bytes > 0) {
response.append(buffer_, bytes); response.append(read_buffer, bytes);
if (bytes < static_cast<int>(kHttpBufferSize)) { } else if (bytes < 0) {
attempts = kSingleReadAttempt; LOGE("read error, errno = %d", errno);
} return false;
} else { } else {
if (bytes < 0) LOGE("read error = ", errno); // end of stream.
// bytes == 0 indicates nothing to read break;
} }
} }
ConcatenateChunkedResponse(response, message); ConcatenateChunkedResponse(response, message);
LOGD("HTTP response: (%d): %s", message->size(), b2a_hex(*message).c_str()); LOGD("HTTP response: (%d): %s", message->size(), b2a_hex(*message).c_str());
return message->size(); return true;
} }
// static
int UrlRequest::GetStatusCode(const std::string& response) { int UrlRequest::GetStatusCode(const std::string& response) {
const std::string kHttpVersion("HTTP/1.1"); const std::string kHttpVersion("HTTP/1.1 ");
int status_code = -1; int status_code = -1;
size_t pos = response.find(kHttpVersion); size_t pos = response.find(kHttpVersion);
@@ -140,79 +123,48 @@ int UrlRequest::GetStatusCode(const std::string& response) {
return status_code; return status_code;
} }
bool UrlRequest::PostRequestChunk(const std::string& data) { bool UrlRequest::PostRequestWithPath(const std::string& path,
request_.assign("POST /"); const std::string& data) {
request_.append(socket_.resource_path()); std::string request;
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 request.append("POST ");
AppendChunkToUpload(data); request.append(path);
request.append(" HTTP/1.1\r\n");
// terminates last chunk with 0\r\n, then ends header with an empty line request.append("Host: ");
request_.append("0\r\n\r\n"); request.append(socket_.domain_name());
request.append("\r\n");
socket_.Write(request_.c_str(), request_.size()); request.append("Connection: close\r\n");
return true; request.append("User-Agent: Widevine CDM v1.0\r\n");
// buffer to store length of data as a string
char data_size_buffer[32] = {0};
snprintf(data_size_buffer, sizeof(data_size_buffer), "%zd", data.size());
request.append("Content-Length: ");
request.append(data_size_buffer); // appends size of data
request.append("\r\n");
request.append("\r\n"); // empty line to terminate headers
request.append(data);
int ret = socket_.Write(request.c_str(), request.size(), kWriteTimeoutMs);
LOGD("HTTP request: (%d): %s", request.size(), b2a_hex(request).c_str());
return ret != -1;
} }
bool UrlRequest::PostRequest(const std::string& data) { bool UrlRequest::PostRequest(const std::string& data) {
if (chunk_transfer_mode_) { return PostRequestWithPath(socket_.resource_path(), data);
return PostRequestChunk(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("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");
std::ostringstream ss;
ss << data.size();
request_.append("Content-Length: ");
request_.append(ss.str());
request_.append("\r\n\r\n");
request_.append(data);
// terminates with \r\n, then ends with an empty line
request_.append("\r\n\r\n");
socket_.Write(request_.c_str(), request_.size());
LOGD("HTTP request: (%d): %s", request_.size(), request_.c_str());
LOGD("HTTP request: (%d): %s", request_.size(), b2a_hex(request_).c_str());
return true;
} }
bool UrlRequest::PostCertRequestInQueryString(const std::string& data) { bool UrlRequest::PostCertRequestInQueryString(const std::string& data) {
request_.assign("POST /"); std::string path = socket_.resource_path();
request_.append(socket_.resource_path()); path.append("&signedRequest=");
request_.append("&signedRequest="); path.append(data);
request_.append(data);
request_.append(" HTTP/1.1\r\n");
request_.append("User-Agent: Widevine CDM v1.0\r\n");
request_.append("Host: ");
request_.append(socket_.domain_name());
request_.append("\r\nAccept: */*");
request_.append("\r\nContent-Type: application/json");
request_.append("\r\nContent-Length: 0");
request_.append("\r\n"); // empty line to terminate header
request_.append("\r\n"); // terminates the request
socket_.Write(request_.c_str(), request_.size()); return PostRequestWithPath(path, "");
LOGD("HTTP request: (%d): %s", request_.size(), request_.c_str());
LOGD("HTTP request: (%d): %s", request_.size(), b2a_hex(request_).c_str());
return true;
} }
} // namespace wvcdm } // namespace wvcdm

View File

@@ -13,29 +13,22 @@ namespace wvcdm {
// Only POST request method is implemented. // Only POST request method is implemented.
class UrlRequest { class UrlRequest {
public: public:
UrlRequest(const std::string& url, const std::string& port, explicit UrlRequest(const std::string& url);
bool secure_connect, bool chunk_transfer_mode);
~UrlRequest(); ~UrlRequest();
void AppendChunkToUpload(const std::string& data);
void ConcatenateChunkedResponse(const std::string http_response,
std::string* modified_response);
int GetResponse(std::string* message);
int GetStatusCode(const std::string& response);
bool is_connected() const { return is_connected_; } bool is_connected() const { return is_connected_; }
bool PostRequest(const std::string& data); bool PostRequest(const std::string& data);
bool PostRequestChunk(const std::string& data);
bool PostCertRequestInQueryString(const std::string& data); bool PostCertRequestInQueryString(const std::string& data);
bool GetResponse(std::string* message);
static int GetStatusCode(const std::string& response);
private: private:
static const unsigned int kHttpBufferSize = 4096; bool PostRequestWithPath(const std::string& path, const std::string& data);
char buffer_[kHttpBufferSize];
bool chunk_transfer_mode_;
bool is_connected_; bool is_connected_;
std::string port_;
std::string request_;
HttpSocket socket_; HttpSocket socket_;
std::string server_url_;
CORE_DISALLOW_COPY_AND_ASSIGN(UrlRequest); CORE_DISALLOW_COPY_AND_ASSIGN(UrlRequest);
}; };

View File

@@ -12,7 +12,7 @@
namespace wvcdm { namespace wvcdm {
static LogPriority g_cutoff = LOG_WARN; LogPriority g_cutoff = LOG_WARN;
void InitLogging(int argc, const char* const* argv) { void InitLogging(int argc, const char* const* argv) {
for (int i = 1; i < argc; i++) { for (int i = 1; i < argc; i++) {

View File

@@ -0,0 +1,65 @@
// Copyright 2014 Google Inc. All Rights Reserved.
#ifndef WVOEC_OEMCRYPTO_LOGGING_H_
#define WVOEC_OEMCRYPTO_LOGGING_H_
#include "OEMCryptoCENC.h"
#include "log.h"
#include <string>
#include <iostream>
namespace wvoec_mock {
// The constants below represent integers with a single "on" bit that
// represents categories of logging This allows users to specify with
// more precision what they want to log. LogCategoryEnabled(category)
// is used to see if the category passed in the parameters is to
// be logged based on the current settings. Categories can be combines
// using the | (or) bitwise operator. For example
// LogCategoryEnabled(category1 | category2) will return true if
// category1 and/or category2 are set to logging.
const int kLoggingTraceOEMCryptoCalls = 0x01;
const int kLoggingDumpContentKeys = 0x02;
const int kLoggingDumpKeyControlBlocks = 0x04;
const int kLoggingDumpDerivedKeys = 0x08;
const int kLoggingTraceNonce = 0x10;
const int kLoggingTraceDecryption = 0x20;
const int kLoggingTraceUsageTable = 0x40;
const int kLoggingDumpTraceAll = 0xFF;
void SetLoggingSettings(int level, int categories);
// set level of logging
void SetLoggingLevel(int level);
void TurnOffLoggingForAllCategories();
// Returns true if the category passed is set to logging.
// Returns false otherwise. The category constant declared
// above are passed.
bool LogCategoryEnabled(int category);
// Turn on logging for the categories passed.
void AddLoggingForCategories(int categories);
// Turn off logging for the categories passed.
void RemoveLoggingForCategories(int categories);
void dump_hex_helper(std::string& buffer, std::string name,
const uint8_t* vector, size_t length);
void dump_hex(std::string name, const uint8_t* vector, size_t length);
void dump_array_part_helper(std::string& buffer, std::string array,
size_t index, std::string name,
const uint8_t* vector, size_t length);
void dump_array_part(std::string array, size_t index,
std::string name, const uint8_t* vector, size_t length);
} // namespace wvoec_mock
#endif

View File

@@ -1837,7 +1837,7 @@ class DISABLED_TestKeybox : public OEMCryptoClientTest {
} }
}; };
TEST_F(DISABLED_TestKeybox, CheckSystemID) { TEST_F(OEMCryptoClientTest, DISABLED_CheckSystemID) {
OEMCryptoResult sts; OEMCryptoResult sts;
uint8_t key_data[256]; uint8_t key_data[256];
size_t key_data_len = sizeof(key_data); size_t key_data_len = sizeof(key_data);
@@ -1884,6 +1884,7 @@ TEST_F(DISABLED_TestKeybox, BadCRCKeybox) {
InstallKeybox(keybox, false); InstallKeybox(keybox, false);
sts = OEMCrypto_IsKeyboxValid(); sts = OEMCrypto_IsKeyboxValid();
ASSERT_EQ(OEMCrypto_ERROR_BAD_CRC, sts); ASSERT_EQ(OEMCrypto_ERROR_BAD_CRC, sts);
InstallKeybox(kDefaultKeybox, true);
} }
TEST_F(DISABLED_TestKeybox, BadMagicKeybox) { TEST_F(DISABLED_TestKeybox, BadMagicKeybox) {
@@ -1893,6 +1894,7 @@ TEST_F(DISABLED_TestKeybox, BadMagicKeybox) {
InstallKeybox(keybox, false); InstallKeybox(keybox, false);
sts = OEMCrypto_IsKeyboxValid(); sts = OEMCrypto_IsKeyboxValid();
ASSERT_EQ(OEMCrypto_ERROR_BAD_MAGIC, sts); ASSERT_EQ(OEMCrypto_ERROR_BAD_MAGIC, sts);
InstallKeybox(kDefaultKeybox, true);
} }
TEST_F(DISABLED_TestKeybox, BadDataKeybox) { TEST_F(DISABLED_TestKeybox, BadDataKeybox) {
@@ -1902,6 +1904,7 @@ TEST_F(DISABLED_TestKeybox, BadDataKeybox) {
InstallKeybox(keybox, false); InstallKeybox(keybox, false);
sts = OEMCrypto_IsKeyboxValid(); sts = OEMCrypto_IsKeyboxValid();
ASSERT_EQ(OEMCrypto_ERROR_BAD_CRC, sts); ASSERT_EQ(OEMCrypto_ERROR_BAD_CRC, sts);
InstallKeybox(kDefaultKeybox, true);
} }
TEST_F(DISABLED_TestKeybox, GenerateSignature) { TEST_F(DISABLED_TestKeybox, GenerateSignature) {
@@ -2148,6 +2151,30 @@ TEST_F(DISABLED_TestKeybox, LoadKeyWithBadNonce) {
ASSERT_NE(OEMCrypto_SUCCESS, sts); ASSERT_NE(OEMCrypto_SUCCESS, sts);
} }
TEST_F(DISABLED_TestKeybox, LoadKeyWithRepeatNonce) {
InstallKeybox(kDefaultKeybox, true);
Session s;
s.open();
s.GenerateDerivedKeys();
uint32_t nonce = s.get_nonce();
s.FillSimpleMessage(0, wvoec_mock::kControlNonceEnabled, nonce);
s.EncryptAndSign();
s.LoadTestKeys();
s.close();
s.open();
s.GenerateDerivedKeys();
s.FillSimpleMessage(0, wvoec_mock::kControlNonceEnabled,
nonce); // same old nonce.
s.EncryptAndSign();
OEMCryptoResult sts = OEMCrypto_LoadKeys(
s.session_id(), s.message_ptr(), sizeof(MessageData), &s.signature()[0],
s.signature().size(), s.encrypted_license().mac_key_iv,
s.encrypted_license().mac_keys, kNumKeys, s.key_array(), NULL, 0);
ASSERT_NE(OEMCrypto_SUCCESS, sts);
}
TEST_F(DISABLED_TestKeybox, LoadKeyWithBadVerification) { TEST_F(DISABLED_TestKeybox, LoadKeyWithBadVerification) {
InstallKeybox(kDefaultKeybox, true); InstallKeybox(kDefaultKeybox, true);
Session s; Session s;
@@ -4601,6 +4628,85 @@ TEST_P(DISABLED_UsageTableTest, EmptyTable) {
} }
} }
TEST_P(DISABLED_UsageTableTest, FiftyEntries) {
if (OEMCrypto_SupportsUsageTable()) {
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable());
Session s1;
s1.open();
s1.GenerateDerivedKeys();
std::string pst1 = "pst saved";
s1.FillSimpleMessage(
0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired,
s1.get_nonce(), pst1);
s1.EncryptAndSign();
s1.LoadTestKeys(pst1, new_mac_keys_);
sleep(kShortSleep);
cout << "Making 49\n";
const size_t ENTRY_COUNT = 49;// API says should hold at least 50 entries.
Session sessions[ENTRY_COUNT];
for (int i=0; i<ENTRY_COUNT; i++) {
sessions[i].open();
sessions[i].GenerateDerivedKeys();
std::string pst = "pst ";
char c = 'A' + i;
pst = pst + c;
sessions[i].FillSimpleMessage(
0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired,
sessions[i].get_nonce(), pst);
sessions[i].EncryptAndSign();
sessions[i].LoadTestKeys(pst, new_mac_keys_);
sessions[i].GenerateReport(pst);
sessions[i].close();
}
cout << "Checking 49\n";
for (int i=0; i<ENTRY_COUNT; i++) {
Session s;
s.open();
std::string pst = "pst ";
char c = 'A' + i;
pst = pst + c;
s.GenerateReport(pst, true, &sessions[i]);
EXPECT_EQ(kUnused, s.pst_report()->status);
s.close();
}
sleep(kShortSleep);
cout << "Making another 49\n";
// If I add too many entries, it can delete the older ones first, except
// it shouldn't delete the one attached to an open session. (s1)
for (int i=0; i<ENTRY_COUNT; i++) {
sessions[i].open();
sessions[i].GenerateDerivedKeys();
std::string pst = "newer pst ";
char c = 'A' + i;
pst = pst + c;
sessions[i].FillSimpleMessage(
0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired,
sessions[i].get_nonce(), pst);
sessions[i].EncryptAndSign();
sessions[i].LoadTestKeys(pst, new_mac_keys_);
sessions[i].GenerateReport(pst);
sessions[i].close();
}
cout << "Checking another 49\n";
for (int i=0; i<49; i++) {
Session s;
s.open();
std::string pst = "newer pst ";
char c = 'A' + i;
pst = pst + c;
s.GenerateReport(pst, true, &sessions[i]);
EXPECT_EQ(kUnused, s.pst_report()->status);
s.close();
}
s1.close();
s1.open(); // Make sure s1's entry is still in the table.
s1.GenerateReport(pst1);
EXPECT_EQ(kUnused, s1.pst_report()->status);
s1.close();
}
}
TEST_P(DISABLED_UsageTableTest, DeleteUnusedEntry) { TEST_P(DISABLED_UsageTableTest, DeleteUnusedEntry) {
if (OEMCrypto_SupportsUsageTable()) { if (OEMCrypto_SupportsUsageTable()) {
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable()); ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable());
@@ -4826,9 +4932,7 @@ TEST_P(DISABLED_UsageTableTest, GenericEncrypt) {
wvoec_mock::kControlNonceRequired, wvoec_mock::kControlNonceRequired,
nonce, pst); nonce, pst);
s.EncryptAndSign(); s.EncryptAndSign();
printf("Getting ready to load keys.\n");
s.LoadTestKeys(pst, new_mac_keys_); s.LoadTestKeys(pst, new_mac_keys_);
printf("done to load keys.\n");
OEMCryptoResult sts; OEMCryptoResult sts;
unsigned int key_index = 0; unsigned int key_index = 0;
uint8_t expected_encrypted[kBufferSize]; uint8_t expected_encrypted[kBufferSize];
@@ -4842,7 +4946,6 @@ TEST_P(DISABLED_UsageTableTest, GenericEncrypt) {
encrypted); encrypted);
ASSERT_EQ(OEMCrypto_SUCCESS, sts); ASSERT_EQ(OEMCrypto_SUCCESS, sts);
EXPECT_EQ(0, memcmp(encrypted, expected_encrypted, kBufferSize)); EXPECT_EQ(0, memcmp(encrypted, expected_encrypted, kBufferSize));
printf("doing the generate report");
s.GenerateReport(pst); s.GenerateReport(pst);
EXPECT_EQ(kActive, s.pst_report()->status); EXPECT_EQ(kActive, s.pst_report()->status);
EXPECT_ALMOST( EXPECT_ALMOST(
@@ -5164,21 +5267,24 @@ TEST_P(DISABLED_UsageTableTest, TimingTest) {
Session s1; Session s1;
Session s2; Session s2;
Session s3; Session s3;
time_t loaded = time(NULL);
LoadOfflineLicense(s1, pst1); LoadOfflineLicense(s1, pst1);
time_t loaded1 = time(NULL);
LoadOfflineLicense(s2, pst2); LoadOfflineLicense(s2, pst2);
time_t loaded2 = time(NULL);
LoadOfflineLicense(s3, pst3); LoadOfflineLicense(s3, pst3);
time_t loaded3 = time(NULL);
sleep(kLongSleep); sleep(kLongSleep);
time_t first_decrypt = time(NULL);
s1.open(); s1.open();
s1.GenerateDerivedKeys(); s1.GenerateDerivedKeys();
s1.LoadTestKeys(pst1, new_mac_keys_); s1.LoadTestKeys(pst1, new_mac_keys_);
time_t first_decrypt1 = time(NULL);
s1.TestDecryptCTR(); s1.TestDecryptCTR();
s2.open(); s2.open();
s2.GenerateDerivedKeys(); s2.GenerateDerivedKeys();
s2.LoadTestKeys(pst2, new_mac_keys_); s2.LoadTestKeys(pst2, new_mac_keys_);
time_t first_decrypt2 = time(NULL);
s2.TestDecryptCTR(); s2.TestDecryptCTR();
sleep(kLongSleep); sleep(kLongSleep);
@@ -5196,6 +5302,7 @@ TEST_P(DISABLED_UsageTableTest, TimingTest) {
OEMCrypto_Terminate(); OEMCrypto_Terminate();
sleep(kShortSleep); sleep(kShortSleep);
OEMCrypto_Initialize(); OEMCrypto_Initialize();
InstallKeybox(kDefaultKeybox, true);
// After a reboot, we should be able to reload keys, and generate reports. // After a reboot, we should be able to reload keys, and generate reports.
sleep(kLongSleep); sleep(kLongSleep);
@@ -5210,34 +5317,36 @@ TEST_P(DISABLED_UsageTableTest, TimingTest) {
s2.open(); s2.open();
s3.open(); s3.open();
sleep(kLongSleep); sleep(kLongSleep);
time_t report_generated = time(NULL); time_t report_generated1 = time(NULL);
s1.GenerateReport(pst1); s1.GenerateReport(pst1);
time_t report_generated2 = time(NULL);
s2.GenerateReport(pst2); s2.GenerateReport(pst2);
time_t report_generated3 = time(NULL);
s3.GenerateReport(pst3); s3.GenerateReport(pst3);
EXPECT_EQ(kInactive, s1.pst_report()->status); EXPECT_EQ(kInactive, s1.pst_report()->status);
EXPECT_ALMOST( EXPECT_ALMOST(
report_generated - loaded, report_generated1 - loaded1,
wvcdm::htonll64(s1.pst_report()->seconds_since_license_received)); wvcdm::htonll64(s1.pst_report()->seconds_since_license_received));
EXPECT_ALMOST( EXPECT_ALMOST(
report_generated - first_decrypt, report_generated1 - first_decrypt1,
wvcdm::htonll64(s1.pst_report()->seconds_since_first_decrypt)); wvcdm::htonll64(s1.pst_report()->seconds_since_first_decrypt));
EXPECT_ALMOST(report_generated - second_decrypt, EXPECT_ALMOST(report_generated1 - second_decrypt,
wvcdm::htonll64(s1.pst_report()->seconds_since_last_decrypt)); wvcdm::htonll64(s1.pst_report()->seconds_since_last_decrypt));
EXPECT_EQ(kActive, s2.pst_report()->status); EXPECT_EQ(kActive, s2.pst_report()->status);
EXPECT_ALMOST( EXPECT_ALMOST(
report_generated - loaded, report_generated2 - loaded2,
wvcdm::htonll64(s2.pst_report()->seconds_since_license_received)); wvcdm::htonll64(s2.pst_report()->seconds_since_license_received));
EXPECT_ALMOST( EXPECT_ALMOST(
report_generated - first_decrypt, report_generated2 - first_decrypt2,
wvcdm::htonll64(s2.pst_report()->seconds_since_first_decrypt)); wvcdm::htonll64(s2.pst_report()->seconds_since_first_decrypt));
EXPECT_ALMOST(report_generated - third_decrypt, EXPECT_ALMOST(report_generated2 - third_decrypt,
wvcdm::htonll64(s2.pst_report()->seconds_since_last_decrypt)); wvcdm::htonll64(s2.pst_report()->seconds_since_last_decrypt));
EXPECT_EQ(kUnused, s3.pst_report()->status); EXPECT_EQ(kUnused, s3.pst_report()->status);
EXPECT_ALMOST( EXPECT_ALMOST(
report_generated - loaded, report_generated3 - loaded3,
wvcdm::htonll64(s3.pst_report()->seconds_since_license_received)); wvcdm::htonll64(s3.pst_report()->seconds_since_license_received));
// We don't expect first or last decrypt for unused report. // We don't expect first or last decrypt for unused report.
} }

View File

@@ -16,8 +16,7 @@
'protoc_dir%': '/usr/bin', 'protoc_dir%': '/usr/bin',
'certificate_provision%': 'false', 'certificate_provision%': 'false',
'force_use_of_secure_buffers%': 'false', 'force_use_of_secure_buffers%': 'false',
'cdm_target_name%': 'wvcdm_shared', 'disable_privacy_crypto%': 'false',
'cdm_target_type%': 'shared_library',
}, # end variables }, # end variables
'target_defaults': { 'target_defaults': {

View File

@@ -5,27 +5,38 @@
# use_system_protobuf to false. # use_system_protobuf to false.
{ {
# Some sources are used by both libprotobuf_lite and libprotobuf.
# Representing this with dependencies means that protobuf_lite must be built
# with both the target and host toolsets. Although gyp allows for this,
# this causes bugs in the generated output for both xcode and ninja.
# Therefore we include this variable in the sources of both libraries
# instead.
'variables': {
'protobuf_lite_sources': [
'<(protobuf_source_dir)/src/google/protobuf/extension_set.cc',
'<(protobuf_source_dir)/src/google/protobuf/generated_message_util.cc',
'<(protobuf_source_dir)/src/google/protobuf/message_lite.cc',
'<(protobuf_source_dir)/src/google/protobuf/repeated_field.cc',
'<(protobuf_source_dir)/src/google/protobuf/wire_format_lite.cc',
'<(protobuf_source_dir)/src/google/protobuf/io/coded_stream.cc',
'<(protobuf_source_dir)/src/google/protobuf/io/zero_copy_stream.cc',
'<(protobuf_source_dir)/src/google/protobuf/io/zero_copy_stream_impl_lite.cc',
'<(protobuf_source_dir)/src/google/protobuf/stubs/atomicops_internals_x86_gcc.cc',
'<(protobuf_source_dir)/src/google/protobuf/stubs/atomicops_internals_x86_msvc.cc',
'<(protobuf_source_dir)/src/google/protobuf/stubs/common.cc',
'<(protobuf_source_dir)/src/google/protobuf/stubs/once.cc',
'<(protobuf_source_dir)/src/google/protobuf/stubs/stringprintf.cc',
],
},
'targets': [ 'targets': [
{ {
'target_name': 'protobuf_lite', 'target_name': 'protobuf_lite',
'type': 'static_library', 'type': 'static_library',
'toolsets': ['host', 'target'], 'toolsets': ['target'],
'sources': [ 'sources': [
'<(protobuf_source_dir)/src/google/protobuf/extension_set.cc', '<@(protobuf_lite_sources)',
'<(protobuf_source_dir)/src/google/protobuf/generated_message_util.cc',
'<(protobuf_source_dir)/src/google/protobuf/message_lite.cc',
'<(protobuf_source_dir)/src/google/protobuf/repeated_field.cc',
'<(protobuf_source_dir)/src/google/protobuf/wire_format_lite.cc',
'<(protobuf_source_dir)/src/google/protobuf/io/coded_stream.cc',
'<(protobuf_source_dir)/src/google/protobuf/io/zero_copy_stream.cc',
'<(protobuf_source_dir)/src/google/protobuf/io/zero_copy_stream_impl_lite.cc',
'<(protobuf_source_dir)/src/google/protobuf/stubs/atomicops_internals_x86_gcc.cc',
'<(protobuf_source_dir)/src/google/protobuf/stubs/atomicops_internals_x86_msvc.cc',
'<(protobuf_source_dir)/src/google/protobuf/stubs/common.cc',
'<(protobuf_source_dir)/src/google/protobuf/stubs/once.cc',
'<(protobuf_source_dir)/src/google/protobuf/stubs/stringprintf.cc',
], ],
'include_dirs': [ 'include_dirs': [
'<(protobuf_source_dir)', '<(protobuf_source_dir)',
@@ -42,6 +53,8 @@
'type': 'static_library', 'type': 'static_library',
'toolsets': ['host'], 'toolsets': ['host'],
'sources': [ 'sources': [
'<@(protobuf_lite_sources)',
'<(protobuf_source_dir)/src/google/protobuf/descriptor.cc', '<(protobuf_source_dir)/src/google/protobuf/descriptor.cc',
'<(protobuf_source_dir)/src/google/protobuf/descriptor.pb.cc', '<(protobuf_source_dir)/src/google/protobuf/descriptor.pb.cc',
'<(protobuf_source_dir)/src/google/protobuf/descriptor_database.cc', '<(protobuf_source_dir)/src/google/protobuf/descriptor_database.cc',
@@ -71,9 +84,6 @@
'<(protobuf_source_dir)', '<(protobuf_source_dir)',
'<(protobuf_source_dir)/src', '<(protobuf_source_dir)/src',
], ],
'dependencies': [
'protobuf_lite',
],
}, },
{ {
'target_name': 'protoc', 'target_name': 'protoc',