Source release v2.1.2-0-773 + third_party libs
Change-Id: Ia07608577b65b301c22a8ff4bf7f743c2d3f9274
This commit is contained in:
@@ -88,10 +88,11 @@ class CdmEngine {
|
||||
std::string* cert,
|
||||
std::string* wrapped_key);
|
||||
|
||||
// Secure stop related methods
|
||||
CdmResponseType GetSecureStops(CdmSecureStops* secure_stops);
|
||||
CdmResponseType ReleaseSecureStops(
|
||||
const CdmSecureStopReleaseMessage& message);
|
||||
CdmResponseType Unprovision(CdmSecurityLevel security_level);
|
||||
|
||||
// Usage related methods for streaming licenses
|
||||
CdmResponseType GetUsageInfo(CdmUsageInfo* usage_info);
|
||||
CdmResponseType ReleaseUsageInfo(const CdmUsageInfoReleaseMessage& message);
|
||||
|
||||
// Decryption and key related methods
|
||||
// Accept encrypted buffer and return decrypted data.
|
||||
@@ -124,6 +125,9 @@ class CdmEngine {
|
||||
CdmReleaseKeySetMap release_key_sets_;
|
||||
CertificateProvisioning cert_provisioning_;
|
||||
SecurityLevel cert_provisioning_requested_security_level_;
|
||||
CdmSession* usage_session_;
|
||||
|
||||
int64_t last_usage_information_update_time;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(CdmEngine);
|
||||
};
|
||||
|
||||
@@ -28,6 +28,8 @@ class CdmSession {
|
||||
|
||||
CdmResponseType RestoreOfflineSession(const CdmKeySetId& key_set_id,
|
||||
const CdmLicenseType license_type);
|
||||
CdmResponseType RestoreUsageSession(const CdmKeyMessage& key_request,
|
||||
const CdmKeyResponse& key_response);
|
||||
|
||||
void set_key_system(const CdmKeySystem& ksystem) { key_system_ = ksystem; }
|
||||
const CdmKeySystem& key_system() { return key_system_; }
|
||||
@@ -86,6 +88,12 @@ class CdmSession {
|
||||
void OnKeyReleaseEvent(const CdmKeySetId& key_set_id);
|
||||
|
||||
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:
|
||||
|
||||
@@ -93,7 +101,9 @@ class CdmSession {
|
||||
CdmSessionId GenerateSessionId();
|
||||
bool GenerateKeySetId(CdmKeySetId* key_set_id);
|
||||
|
||||
CdmResponseType StoreLicense();
|
||||
bool StoreLicense(DeviceFiles::LicenseState state);
|
||||
bool DeleteLicense();
|
||||
|
||||
// instance variables
|
||||
const CdmSessionId session_id_;
|
||||
@@ -103,13 +113,16 @@ class CdmSession {
|
||||
PolicyEngine policy_engine_;
|
||||
bool license_received_;
|
||||
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
|
||||
CdmInitData offline_init_data_;
|
||||
CdmKeyMessage offline_key_request_;
|
||||
CdmKeyResponse offline_key_response_;
|
||||
CdmKeyMessage offline_key_renewal_request_;
|
||||
CdmKeyResponse offline_key_renewal_response_;
|
||||
std::string offline_release_server_url_;
|
||||
|
||||
@@ -45,7 +45,8 @@ class CryptoSession {
|
||||
const std::string& signature,
|
||||
const std::string& mac_key_iv,
|
||||
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 RefreshKeys(const std::string& message, const std::string& signature,
|
||||
int num_keys, const CryptoKey* key_array);
|
||||
@@ -63,6 +64,15 @@ class CryptoSession {
|
||||
// Media data path
|
||||
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);
|
||||
|
||||
private:
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
#if defined(UNIT_TEST)
|
||||
#include <gtest/gtest_prod.h>
|
||||
#endif
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class File;
|
||||
@@ -18,10 +22,10 @@ class DeviceFiles {
|
||||
} LicenseState;
|
||||
|
||||
DeviceFiles(): file_(NULL), security_level_(kSecurityLevelUninitialized),
|
||||
initialized_(false) {}
|
||||
virtual ~DeviceFiles() {}
|
||||
initialized_(false), test_file_(false) {}
|
||||
virtual ~DeviceFiles();
|
||||
|
||||
virtual bool Init(const File* handle, CdmSecurityLevel security_level);
|
||||
virtual bool Init(CdmSecurityLevel security_level);
|
||||
|
||||
virtual bool StoreCertificate(const std::string& certificate,
|
||||
const std::string& wrapped_private_key);
|
||||
@@ -49,25 +53,52 @@ class DeviceFiles {
|
||||
virtual bool DeleteAllLicenses();
|
||||
virtual bool LicenseExists(const std::string& key_set_id);
|
||||
|
||||
// For testing only
|
||||
static std::string GetCertificateFileName();
|
||||
static std::string GetLicenseFileNameExtension();
|
||||
|
||||
protected:
|
||||
bool Hash(const std::string& data, std::string* hash);
|
||||
bool StoreFile(const char* name, const std::string& data);
|
||||
bool RetrieveFile(const char* name, std::string* data);
|
||||
virtual bool StoreUsageInfo(const std::string& provider_session_token,
|
||||
const CdmKeyMessage& key_request,
|
||||
const CdmKeyResponse& key_response);
|
||||
virtual bool DeleteUsageInfo(const std::string& provider_session_token);
|
||||
virtual bool DeleteUsageInfo();
|
||||
virtual bool RetrieveUsageInfo(
|
||||
std::vector<std::pair<CdmKeyMessage, CdmKeyResponse> >* usage_info);
|
||||
|
||||
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
|
||||
// level specific directories. In an earlier version they were
|
||||
// stored in a common directory and need to be copied over.
|
||||
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_;
|
||||
CdmSecurityLevel security_level_;
|
||||
bool initialized_;
|
||||
|
||||
bool test_file_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(DeviceFiles);
|
||||
};
|
||||
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// File class. The implementation is platform dependent.
|
||||
|
||||
@@ -40,12 +40,16 @@ class CdmLicense {
|
||||
CdmResponseType HandleKeyUpdateResponse(
|
||||
bool is_renewal, const CdmKeyResponse& license_response);
|
||||
|
||||
bool RestoreOfflineLicense(CdmKeyMessage& license_request,
|
||||
CdmKeyResponse& license_response,
|
||||
CdmKeyResponse& license_renewal_response);
|
||||
bool RestoreOfflineLicense(const CdmKeyMessage& license_request,
|
||||
const CdmKeyResponse& license_response,
|
||||
const CdmKeyResponse& license_renewal_response);
|
||||
bool RestoreUsageLicense(const CdmKeyMessage& license_request,
|
||||
const CdmKeyResponse& license_response);
|
||||
bool HasInitData() { return !stored_init_data_.empty(); }
|
||||
bool IsKeyLoaded(const KeyId& key_id);
|
||||
|
||||
std::string provider_session_token() { return provider_session_token_; }
|
||||
|
||||
private:
|
||||
bool PrepareServiceCertificateRequest(CdmKeyMessage* signed_request,
|
||||
std::string* server_url);
|
||||
@@ -67,6 +71,7 @@ class CdmLicense {
|
||||
std::string stored_init_data_;
|
||||
bool initialized_;
|
||||
std::set<KeyId> loaded_keys_;
|
||||
std::string provider_session_token_;
|
||||
|
||||
// Used for certificate based licensing
|
||||
CdmKeyMessage key_request_;
|
||||
|
||||
@@ -17,6 +17,8 @@ typedef enum {
|
||||
LOG_VERBOSE
|
||||
} LogPriority;
|
||||
|
||||
extern LogPriority g_cutoff;
|
||||
|
||||
// Enable/disable verbose logging (LOGV).
|
||||
// This function is supplied for cases where the system layer does not
|
||||
// initialize logging. This is also needed to initialize logging in
|
||||
|
||||
@@ -24,30 +24,27 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "openssl/evp.h"
|
||||
#include "openssl/rsa.h"
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class AesCbcKey {
|
||||
public:
|
||||
AesCbcKey() : initialized_(false) {};
|
||||
~AesCbcKey() {};
|
||||
AesCbcKey();
|
||||
~AesCbcKey();
|
||||
|
||||
bool Init(const std::string& key);
|
||||
bool Encrypt(const std::string& in, std::string* out, std::string* iv);
|
||||
|
||||
private:
|
||||
EVP_CIPHER_CTX ctx_;
|
||||
bool initialized_;
|
||||
std::string key_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(AesCbcKey);
|
||||
};
|
||||
|
||||
class RsaPublicKey {
|
||||
public:
|
||||
RsaPublicKey() : key_(NULL) {}
|
||||
RsaPublicKey();
|
||||
~RsaPublicKey();
|
||||
|
||||
// Initializes an RsaPublicKey object using a DER encoded PKCS#1 RSAPublicKey
|
||||
@@ -64,7 +61,7 @@ class RsaPublicKey {
|
||||
const std::string& signature);
|
||||
|
||||
private:
|
||||
RSA* key_;
|
||||
std::string serialized_key_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(RsaPublicKey);
|
||||
};
|
||||
|
||||
@@ -23,15 +23,15 @@ typedef uint32_t CryptoSessionId;
|
||||
typedef std::string CryptoKeyId;
|
||||
typedef std::map<std::string, std::string> CdmAppParameterMap;
|
||||
typedef std::map<std::string, std::string> CdmQueryMap;
|
||||
typedef std::vector<std::string> CdmSecureStops;
|
||||
typedef std::vector<uint8_t> CdmSecureStopReleaseMessage;
|
||||
typedef std::vector<std::string> CdmUsageInfo;
|
||||
typedef std::string CdmUsageInfoReleaseMessage;
|
||||
typedef std::string CdmProvisioningRequest;
|
||||
typedef std::string CdmProvisioningResponse;
|
||||
|
||||
// Types for shared host/cdm interface pairs used to shared vendor data.
|
||||
typedef std::pair<std::string, std::string> kStringPairs;
|
||||
typedef std::vector<uint8_t> kVectorBytes;
|
||||
typedef std::pair<std::string, kVectorBytes> kVectorPairs;
|
||||
typedef std::pair<std::string, std::string> StringPairs;
|
||||
typedef std::vector<uint8_t> VectorBytes;
|
||||
typedef std::pair<std::string, VectorBytes> VectorPairs;
|
||||
|
||||
enum CdmResponseType {
|
||||
NO_ERROR,
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include <sstream>
|
||||
|
||||
#include "cdm_session.h"
|
||||
#include "clock.h"
|
||||
#include "device_files.h"
|
||||
#include "license_protocol.pb.h"
|
||||
#include "log.h"
|
||||
#include "properties.h"
|
||||
@@ -14,14 +16,24 @@
|
||||
#include "wv_cdm_constants.h"
|
||||
#include "wv_cdm_event_listener.h"
|
||||
|
||||
namespace {
|
||||
const uint32_t kUpdateUsageInformationPeriod = 60; // seconds
|
||||
const size_t kMinNoncesPerSession = 4;
|
||||
} // unnamed namespace
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
CdmEngine::~CdmEngine() {
|
||||
if (NULL != usage_session_)
|
||||
delete usage_session_;
|
||||
|
||||
CdmSessionMap::iterator i(sessions_.begin());
|
||||
for (; i != sessions_.end(); ++i) {
|
||||
delete i->second;
|
||||
@@ -478,13 +490,87 @@ CdmResponseType CdmEngine::HandleProvisioningResponse(
|
||||
wrapped_key);
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::GetSecureStops(
|
||||
CdmSecureStops* secure_stops) {
|
||||
CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level) {
|
||||
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;
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::ReleaseSecureStops(
|
||||
const CdmSecureStopReleaseMessage& message) {
|
||||
CdmResponseType CdmEngine::GetUsageInfo(CdmUsageInfo* usage_info) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -527,8 +613,8 @@ CdmResponseType CdmEngine::Decrypt(
|
||||
}
|
||||
if (iter == sessions_.end()) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -604,9 +690,31 @@ bool CdmEngine::ValidateKeySystem(const CdmKeySystem& key_system) {
|
||||
}
|
||||
|
||||
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();
|
||||
iter != sessions_.end(); ++iter) {
|
||||
iter != sessions_.end(); ++iter) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,9 @@ CdmSession::CdmSession(const CdmClientPropertySet* cdm_client_property_set)
|
||||
crypto_session_(NULL),
|
||||
license_received_(false),
|
||||
reinitialize_session_(false),
|
||||
license_type_(kLicenseTypeStreaming),
|
||||
is_offline_(false),
|
||||
is_release_(false),
|
||||
is_usage_update_needed_(false),
|
||||
is_certificate_loaded_(false) {
|
||||
if (cdm_client_property_set) {
|
||||
Properties::AddSessionPropertySet(session_id_, cdm_client_property_set);
|
||||
@@ -47,9 +49,8 @@ CdmResponseType CdmSession::Init() {
|
||||
|
||||
std::string token;
|
||||
if (Properties::use_certificates_as_identification()) {
|
||||
File file;
|
||||
DeviceFiles handle;
|
||||
if (!handle.Init(&file, session.get()->GetSecurityLevel()) ||
|
||||
if (!handle.Init(session.get()->GetSecurityLevel()) ||
|
||||
!handle.RetrieveCertificate(&token, &wrapped_key_)) {
|
||||
return NEED_PROVISIONING;
|
||||
}
|
||||
@@ -71,15 +72,14 @@ CdmResponseType CdmSession::RestoreOfflineSession(
|
||||
key_set_id_ = key_set_id;
|
||||
|
||||
// Retrieve license information from persistent store
|
||||
File file;
|
||||
DeviceFiles handle;
|
||||
if (!handle.Init(&file, crypto_session_->GetSecurityLevel()))
|
||||
if (!handle.Init(crypto_session_->GetSecurityLevel()))
|
||||
return UNKNOWN_ERROR;
|
||||
|
||||
DeviceFiles::LicenseState license_state;
|
||||
|
||||
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_response_,
|
||||
&offline_release_server_url_)) {
|
||||
@@ -102,14 +102,39 @@ CdmResponseType CdmSession::RestoreOfflineSession(
|
||||
}
|
||||
}
|
||||
|
||||
if (!license_parser_.RestoreOfflineLicense(offline_key_request_,
|
||||
offline_key_response_,
|
||||
if (!license_parser_.RestoreOfflineLicense(key_request_, key_response_,
|
||||
offline_key_renewal_response_)) {
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -135,9 +160,17 @@ CdmResponseType CdmSession::GenerateKeyRequest(
|
||||
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);
|
||||
} else if (license_received_) { // renewal
|
||||
return GenerateRenewalRequest(key_request, server_url);
|
||||
@@ -168,9 +201,9 @@ CdmResponseType CdmSession::GenerateKeyRequest(
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (license_type_ == kLicenseTypeOffline) {
|
||||
key_request_ = *key_request;
|
||||
if (is_offline_) {
|
||||
offline_init_data_ = init_data.data();
|
||||
offline_key_request_ = *key_request;
|
||||
offline_release_server_url_ = *server_url;
|
||||
}
|
||||
|
||||
@@ -191,7 +224,7 @@ CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response,
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
if (license_type_ == kLicenseTypeRelease) {
|
||||
if (is_release_) {
|
||||
return ReleaseKey(key_response);
|
||||
} else if (license_received_) { // renewal
|
||||
return RenewKey(key_response);
|
||||
@@ -201,25 +234,11 @@ CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response,
|
||||
if (sts != KEY_ADDED) return sts;
|
||||
|
||||
license_received_ = true;
|
||||
key_response_ = key_response;
|
||||
|
||||
if (license_type_ == kLicenseTypeOffline) {
|
||||
offline_key_response_ = key_response;
|
||||
if (!GenerateKeySetId(&key_set_id_)) {
|
||||
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;
|
||||
}
|
||||
if (is_offline_ || !license_parser_.provider_session_token().empty()) {
|
||||
sts = StoreLicense();
|
||||
if (sts != NO_ERROR) return sts;
|
||||
}
|
||||
|
||||
*key_set_id = key_set_id_;
|
||||
@@ -290,7 +309,17 @@ CdmResponseType CdmSession::CancelKeyRequest() {
|
||||
CdmResponseType CdmSession::Decrypt(const CdmDecryptionParameters& params) {
|
||||
if (crypto_session_.get() == NULL || !crypto_session_->IsOpen())
|
||||
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
|
||||
@@ -303,7 +332,7 @@ CdmResponseType CdmSession::GenerateRenewalRequest(CdmKeyMessage* key_request,
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (license_type_ == kLicenseTypeOffline) {
|
||||
if (is_offline_) {
|
||||
offline_key_renewal_request_ = *key_request;
|
||||
}
|
||||
return KEY_MESSAGE;
|
||||
@@ -315,7 +344,7 @@ CdmResponseType CdmSession::RenewKey(const CdmKeyResponse& key_response) {
|
||||
license_parser_.HandleKeyUpdateResponse(true, key_response);
|
||||
if (sts != KEY_ADDED) return sts;
|
||||
|
||||
if (license_type_ == kLicenseTypeOffline) {
|
||||
if (is_offline_) {
|
||||
offline_key_renewal_response_ = key_response;
|
||||
if (!StoreLicense(DeviceFiles::kLicenseStateActive)) return UNKNOWN_ERROR;
|
||||
}
|
||||
@@ -324,23 +353,28 @@ CdmResponseType CdmSession::RenewKey(const CdmKeyResponse& key_response) {
|
||||
|
||||
CdmResponseType CdmSession::GenerateReleaseRequest(CdmKeyMessage* key_request,
|
||||
std::string* server_url) {
|
||||
if (license_parser_.PrepareKeyUpdateRequest(false, key_request, server_url)) {
|
||||
// Mark license as being released
|
||||
if (StoreLicense(DeviceFiles::kLicenseStateReleasing)) return KEY_MESSAGE;
|
||||
is_release_ = true;
|
||||
if (!license_parser_.PrepareKeyUpdateRequest(false, key_request, server_url))
|
||||
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.
|
||||
CdmResponseType CdmSession::ReleaseKey(const CdmKeyResponse& key_response) {
|
||||
CdmResponseType sts =
|
||||
license_parser_.HandleKeyUpdateResponse(false, key_response);
|
||||
File file;
|
||||
DeviceFiles handle;
|
||||
if (handle.Init(&file, crypto_session_->GetSecurityLevel()))
|
||||
handle.DeleteLicense(key_set_id_);
|
||||
CdmResponseType sts = license_parser_.HandleKeyUpdateResponse(false,
|
||||
key_response);
|
||||
if (NO_ERROR != sts)
|
||||
return sts;
|
||||
|
||||
return sts;
|
||||
if (is_offline_ || !license_parser_.provider_session_token().empty()) {
|
||||
DeleteLicense();
|
||||
}
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
bool CdmSession::IsKeyLoaded(const KeyId& key_id) {
|
||||
@@ -361,9 +395,8 @@ bool CdmSession::GenerateKeySetId(CdmKeySetId* key_set_id) {
|
||||
std::vector<uint8_t> random_data(
|
||||
(kKeySetIdLength - sizeof(KEY_SET_ID_PREFIX)) / 2, 0);
|
||||
|
||||
File file;
|
||||
DeviceFiles handle;
|
||||
if (!handle.Init(&file, crypto_session_->GetSecurityLevel()))
|
||||
if (!handle.Init(crypto_session_->GetSecurityLevel()))
|
||||
return false;
|
||||
|
||||
while (key_set_id->empty()) {
|
||||
@@ -380,18 +413,75 @@ bool CdmSession::GenerateKeySetId(CdmKeySetId* key_set_id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CdmSession::StoreLicense(DeviceFiles::LicenseState state) {
|
||||
File file;
|
||||
CdmResponseType CdmSession::StoreLicense() {
|
||||
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;
|
||||
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 handle.StoreLicense(
|
||||
key_set_id_, state, offline_init_data_, offline_key_request_,
|
||||
offline_key_response_, offline_key_renewal_request_,
|
||||
key_set_id_, state, offline_init_data_, key_request_,
|
||||
key_response_, offline_key_renewal_request_,
|
||||
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) {
|
||||
std::pair<CdmEventListenerIter, bool> result = listeners_.insert(listener);
|
||||
return result.second;
|
||||
@@ -433,4 +523,15 @@ SecurityLevel CdmSession::GetRequestedSecurityLevel() {
|
||||
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
|
||||
|
||||
@@ -28,10 +28,9 @@ using video_widevine_server::sdk::ProvisioningResponse;
|
||||
using video_widevine_server::sdk::SignedProvisioningMessage;
|
||||
|
||||
/*
|
||||
* This function converts SignedProvisioningRequest into base64 string.
|
||||
* It then wraps it in JSON format expected by the Apiary frontend.
|
||||
* Apiary requires the base64 encoding to replace '+' with minus '-',
|
||||
* and '/' with underscore '_'; opposite to stubby's.
|
||||
* This function converts SignedProvisioningRequest into base64 string. It then
|
||||
* wraps it in JSON format expected by the frontend. This server requires a
|
||||
* "web-safe" base 64 encoding, where '+' becomes '-' and '/' becomes '_'.
|
||||
*
|
||||
* Returns the JSON formated string in *request. The JSON string will be
|
||||
* appended as a query parameter, i.e. signedRequest=<base 64 encoded
|
||||
@@ -103,7 +102,8 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequest(
|
||||
switch (cert_type) {
|
||||
case kCertificateWidevine:
|
||||
options->set_certificate_type(
|
||||
video_widevine_server::sdk::ProvisioningOptions_CertificateType_RSA_WIDEVINE);
|
||||
video_widevine_server::sdk::
|
||||
ProvisioningOptions_CertificateType_WIDEVINE_DRM);
|
||||
break;
|
||||
case kCertificateX509:
|
||||
options->set_certificate_type(
|
||||
@@ -262,9 +262,8 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
|
||||
const std::string& device_certificate =
|
||||
provisioning_response.device_certificate();
|
||||
|
||||
File file;
|
||||
DeviceFiles handle;
|
||||
if (!handle.Init(&file, crypto_session_.GetSecurityLevel())) {
|
||||
if (!handle.Init(crypto_session_.GetSecurityLevel())) {
|
||||
LOGE("HandleProvisioningResponse: failed to init DeviceFiles");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ bool CryptoSession::GetDeviceUniqueId(std::string* device_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
device_id->assign(reinterpret_cast<char *>(&id[0]), id_length);
|
||||
device_id->assign(reinterpret_cast<char*>(&id[0]), id_length);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -340,12 +340,11 @@ size_t CryptoSession::GetOffset(std::string message, std::string field) {
|
||||
return pos;
|
||||
}
|
||||
|
||||
CdmResponseType CryptoSession::LoadKeys(const std::string& message,
|
||||
const std::string& signature,
|
||||
const std::string& mac_key_iv,
|
||||
const std::string& mac_key,
|
||||
int num_keys,
|
||||
const CryptoKey* key_array) {
|
||||
CdmResponseType CryptoSession::LoadKeys(
|
||||
const std::string& message, const std::string& signature,
|
||||
const std::string& mac_key_iv, const std::string& mac_key,
|
||||
const std::vector<CryptoKey>& keys,
|
||||
const std::string& provider_session_token) {
|
||||
LOGV("CryptoSession::LoadKeys: Lock");
|
||||
AutoLock auto_lock(crypto_lock_);
|
||||
|
||||
@@ -358,10 +357,10 @@ CdmResponseType CryptoSession::LoadKeys(const std::string& message,
|
||||
} else {
|
||||
LOGV("CryptoSession::LoadKeys: enc_mac_key not set");
|
||||
}
|
||||
std::vector<OEMCrypto_KeyObject> load_key_array(num_keys);
|
||||
for (int i = 0; i < num_keys; ++i) {
|
||||
const CryptoKey* ki = &key_array[i];
|
||||
OEMCrypto_KeyObject* ko = &load_key_array[i];
|
||||
std::vector<OEMCrypto_KeyObject> load_keys(keys.size());
|
||||
for (size_t i = 0; i < keys.size(); ++i) {
|
||||
const CryptoKey* ki = &keys[i];
|
||||
OEMCrypto_KeyObject* ko = &load_keys[i];
|
||||
ko->key_id = msg + GetOffset(message, ki->key_id());
|
||||
ko->key_id_length = ki->key_id().length();
|
||||
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;
|
||||
}
|
||||
}
|
||||
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_);
|
||||
OEMCryptoResult sts = OEMCrypto_LoadKeys(
|
||||
oec_session_id_, msg, message.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) {
|
||||
return KEY_ADDED;
|
||||
@@ -605,7 +610,8 @@ CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) {
|
||||
buffer_descriptor.buffer.clear.address =
|
||||
static_cast<uint8_t*>(params.decrypt_buffer) +
|
||||
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;
|
||||
case OEMCrypto_BufferType_Secure:
|
||||
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) {
|
||||
if (!nonce) {
|
||||
LOGE("input parameter is null");
|
||||
|
||||
@@ -2,14 +2,22 @@
|
||||
|
||||
#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 <string>
|
||||
|
||||
#include "device_files.pb.h"
|
||||
#include "file_store.h"
|
||||
#include "log.h"
|
||||
#include "openssl/sha.h"
|
||||
#include "properties.h"
|
||||
#include "string_conversions.h"
|
||||
|
||||
// Protobuf generated classes.
|
||||
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_LicenseState_ACTIVE;
|
||||
using video_widevine_client::sdk::License_LicenseState_RELEASING;
|
||||
using video_widevine_client::sdk::UsageInfo;
|
||||
using video_widevine_client::sdk::UsageInfo_ProviderSession;
|
||||
|
||||
namespace {
|
||||
|
||||
const char kCertificateFileName[] = "cert.bin";
|
||||
const char kUsageInfoFileName[] = "usage.bin";
|
||||
const char kLicenseFileNameExt[] = ".lic";
|
||||
const char kWildcard[] = "*";
|
||||
const char kDirectoryDelimiter = '/';
|
||||
@@ -27,15 +39,27 @@ const char* kSecurityLevelPathCompatibilityExclusionList[] = {"ay64.dat"};
|
||||
size_t kSecurityLevelPathCompatibilityExclusionListSize =
|
||||
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 {
|
||||
|
||||
bool DeviceFiles::Init(const File* handle, CdmSecurityLevel security_level) {
|
||||
if (handle == NULL) {
|
||||
LOGW("DeviceFiles::Init: Invalid file handle parameter");
|
||||
return false;
|
||||
}
|
||||
DeviceFiles::~DeviceFiles() {
|
||||
if (!file_ && !test_file_) delete file_;
|
||||
}
|
||||
|
||||
bool DeviceFiles::Init(CdmSecurityLevel security_level) {
|
||||
switch (security_level) {
|
||||
case kSecurityLevelL1:
|
||||
case kSecurityLevelL2:
|
||||
@@ -45,7 +69,7 @@ bool DeviceFiles::Init(const File* handle, CdmSecurityLevel security_level) {
|
||||
LOGW("DeviceFiles::Init: Unsupported security level %d", security_level);
|
||||
return false;
|
||||
}
|
||||
file_ = const_cast<File*>(handle);
|
||||
file_ = new File();
|
||||
security_level_ = security_level;
|
||||
initialized_ = true;
|
||||
return true;
|
||||
@@ -68,24 +92,10 @@ bool DeviceFiles::StoreCertificate(const std::string& certificate,
|
||||
device_certificate->set_certificate(certificate);
|
||||
device_certificate->set_wrapped_private_key(wrapped_private_key);
|
||||
|
||||
std::string serialized_string;
|
||||
file.SerializeToString(&serialized_string);
|
||||
std::string serialized_file;
|
||||
file.SerializeToString(&serialized_file);
|
||||
|
||||
// calculate SHA hash
|
||||
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);
|
||||
return StoreFile(kCertificateFileName, serialized_file);
|
||||
}
|
||||
|
||||
bool DeviceFiles::RetrieveCertificate(std::string* certificate,
|
||||
@@ -99,29 +109,12 @@ bool DeviceFiles::RetrieveCertificate(std::string* certificate,
|
||||
SecurityLevelPathBackwardCompatibility();
|
||||
}
|
||||
|
||||
std::string serialized_hashed_file;
|
||||
if (!RetrieveFile(kCertificateFileName, &serialized_hashed_file))
|
||||
std::string serialized_file;
|
||||
if (!RetrieveFile(kCertificateFileName, &serialized_file))
|
||||
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;
|
||||
if (!file.ParseFromString(hashed_file.file())) {
|
||||
if (!file.ParseFromString(serialized_file)) {
|
||||
LOGW("DeviceFiles::RetrieveCertificate: Unable to parse file");
|
||||
return false;
|
||||
}
|
||||
@@ -187,25 +180,11 @@ bool DeviceFiles::StoreLicense(const std::string& key_set_id,
|
||||
license->set_renewal(license_renewal);
|
||||
license->set_release_server_url(release_server_url);
|
||||
|
||||
std::string serialized_string;
|
||||
file.SerializeToString(&serialized_string);
|
||||
|
||||
// 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 serialized_file;
|
||||
file.SerializeToString(&serialized_file);
|
||||
|
||||
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,
|
||||
@@ -220,29 +199,12 @@ bool DeviceFiles::RetrieveLicense(const std::string& key_set_id,
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string serialized_hashed_file;
|
||||
std::string serialized_file;
|
||||
std::string file_name = key_set_id + kLicenseFileNameExt;
|
||||
if (!RetrieveFile(file_name.c_str(), &serialized_hashed_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;
|
||||
}
|
||||
if (!RetrieveFile(file_name.c_str(), &serialized_file)) return false;
|
||||
|
||||
video_widevine_client::sdk::File file;
|
||||
if (!file.ParseFromString(hashed_file.file())) {
|
||||
if (!file.ParseFromString(serialized_file)) {
|
||||
LOGW("DeviceFiles::RetrieveLicense: Unable to parse file");
|
||||
return false;
|
||||
}
|
||||
@@ -352,18 +314,144 @@ bool DeviceFiles::LicenseExists(const std::string& key_set_id) {
|
||||
return file_->Exists(path);
|
||||
}
|
||||
|
||||
bool DeviceFiles::Hash(const std::string& data, std::string* hash) {
|
||||
if (!hash) return false;
|
||||
bool DeviceFiles::StoreUsageInfo(const std::string& provider_session_token,
|
||||
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;
|
||||
}
|
||||
|
||||
bool DeviceFiles::StoreFile(const char* name, const std::string& data) {
|
||||
bool DeviceFiles::StoreFile(const char* name,
|
||||
const std::string& serialized_file) {
|
||||
if (!file_) {
|
||||
LOGW("DeviceFiles::StoreFile: Invalid file handle");
|
||||
return false;
|
||||
@@ -374,6 +462,21 @@ bool DeviceFiles::StoreFile(const char* name, const std::string& data) {
|
||||
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;
|
||||
if (!Properties::GetDeviceFilesBasePath(security_level_, &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;
|
||||
}
|
||||
|
||||
ssize_t bytes = file_->Write(data.data(), data.size());
|
||||
ssize_t bytes = file_->Write(serialized_hash_file.data(),
|
||||
serialized_hash_file.size());
|
||||
file_->Close();
|
||||
|
||||
if (bytes != static_cast<ssize_t>(data.size())) {
|
||||
LOGW("DeviceFiles::StoreFile: write failed: %d %d", data.size(), bytes);
|
||||
if (bytes != static_cast<ssize_t>(serialized_hash_file.size())) {
|
||||
LOGW("DeviceFiles::StoreFile: write failed: (actual: %d, expected: %d)",
|
||||
bytes,
|
||||
serialized_hash_file.size());
|
||||
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;
|
||||
}
|
||||
|
||||
bool DeviceFiles::RetrieveFile(const char* name, std::string* data) {
|
||||
bool DeviceFiles::RetrieveFile(const char* name, std::string* serialized_file) {
|
||||
if (!file_) {
|
||||
LOGW("DeviceFiles::RetrieveFile: Invalid file handle");
|
||||
return false;
|
||||
@@ -414,8 +522,8 @@ bool DeviceFiles::RetrieveFile(const char* name, std::string* data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
LOGW("DeviceFiles::RetrieveFile: Unspecified data parameter");
|
||||
if (!serialized_file) {
|
||||
LOGW("DeviceFiles::RetrieveFile: Unspecified serialized_file parameter");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -434,7 +542,7 @@ bool DeviceFiles::RetrieveFile(const char* name, std::string* data) {
|
||||
|
||||
ssize_t bytes = file_->FileSize(path);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -442,17 +550,37 @@ bool DeviceFiles::RetrieveFile(const char* name, std::string* data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
data->resize(bytes);
|
||||
bytes = file_->Read(&(*data)[0], data->size());
|
||||
std::string serialized_hash_file;
|
||||
serialized_hash_file.resize(bytes);
|
||||
bytes = file_->Read(&serialized_hash_file[0], serialized_hash_file.size());
|
||||
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");
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -528,4 +656,14 @@ std::string DeviceFiles::GetLicenseFileNameExtension() {
|
||||
return kLicenseFileNameExt;
|
||||
}
|
||||
|
||||
std::string DeviceFiles::GetUsageInfoFileName() {
|
||||
return kUsageInfoFileName;
|
||||
}
|
||||
|
||||
void DeviceFiles::SetTestFile(File* file) {
|
||||
if (file_) delete file_;
|
||||
file_ = file;
|
||||
test_file_ = true;
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
@@ -33,10 +33,21 @@ message License {
|
||||
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 {
|
||||
enum FileType {
|
||||
DEVICE_CERTIFICATE = 1;
|
||||
LICENSE = 2;
|
||||
USAGE_INFO = 3;
|
||||
}
|
||||
|
||||
enum FileVersion {
|
||||
@@ -47,9 +58,11 @@ message File {
|
||||
optional FileVersion version = 2 [default = VERSION_1];
|
||||
optional DeviceCertificate device_certificate = 3;
|
||||
optional License license = 4;
|
||||
optional UsageInfo usage_info = 5;
|
||||
}
|
||||
|
||||
message HashedFile {
|
||||
optional bytes file = 1;
|
||||
// A raw (not hex-encoded) SHA256, taken over the bytes of 'file'.
|
||||
optional bytes hash = 2;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "crypto_key.h"
|
||||
#include "crypto_session.h"
|
||||
#include "device_files.h"
|
||||
#include "log.h"
|
||||
#include "policy_engine.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::DeviceCertificate;
|
||||
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_ContentIdentification;
|
||||
using video_widevine_server::sdk::LicenseRequest_ContentIdentification_CENC;
|
||||
using video_widevine_server::sdk::LicenseRequest_ContentIdentification_WebM;
|
||||
using video_widevine_server::sdk::
|
||||
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::SignedMessage;
|
||||
|
||||
@@ -424,7 +426,19 @@ bool CdmLicense::PrepareKeyUpdateRequest(bool is_renewal,
|
||||
|
||||
LicenseRequest_ContentIdentification_ExistingLicense* current_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
|
||||
// of the license response.
|
||||
@@ -488,7 +502,7 @@ CdmResponseType CdmLicense::HandleKeyResponse(
|
||||
break;
|
||||
case SignedMessage::SERVICE_CERTIFICATE:
|
||||
return CdmLicense::HandleServiceCertificateResponse(signed_response);
|
||||
case SignedMessage::ERROR:
|
||||
case SignedMessage::ERROR_RESPONSE:
|
||||
return HandleKeyErrorResponse(signed_response);
|
||||
default:
|
||||
LOGE("CdmLicense::HandleKeyResponse: unrecognized signed message type: %d"
|
||||
@@ -546,6 +560,10 @@ CdmResponseType CdmLicense::HandleKeyResponse(
|
||||
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()) {
|
||||
server_url_ = license.policy().renewal_server_url();
|
||||
}
|
||||
@@ -556,8 +574,8 @@ CdmResponseType CdmLicense::HandleKeyResponse(
|
||||
signed_response.signature(),
|
||||
mac_key_iv,
|
||||
mac_key,
|
||||
key_array.size(),
|
||||
&key_array[0]);
|
||||
key_array,
|
||||
provider_session_token);
|
||||
|
||||
if (KEY_ADDED == resp) {
|
||||
loaded_keys_.clear();
|
||||
@@ -587,7 +605,7 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse(
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (signed_response.type() == SignedMessage::ERROR) {
|
||||
if (signed_response.type() == SignedMessage::ERROR_RESPONSE) {
|
||||
return HandleKeyErrorResponse(signed_response);
|
||||
}
|
||||
|
||||
@@ -615,6 +633,14 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse(
|
||||
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);
|
||||
|
||||
@@ -631,8 +657,9 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse(
|
||||
}
|
||||
|
||||
bool CdmLicense::RestoreOfflineLicense(
|
||||
CdmKeyMessage& license_request, CdmKeyResponse& license_response,
|
||||
CdmKeyResponse& license_renewal_response) {
|
||||
const CdmKeyMessage& license_request,
|
||||
const CdmKeyResponse& license_response,
|
||||
const CdmKeyResponse& license_renewal_response) {
|
||||
|
||||
if (license_request.empty() || license_response.empty()) {
|
||||
LOGE(
|
||||
@@ -675,6 +702,77 @@ bool CdmLicense::RestoreOfflineLicense(
|
||||
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,
|
||||
std::string* server_url) {
|
||||
if (!initialized_) {
|
||||
@@ -798,8 +896,4 @@ bool CdmLicense::PrepareContentId(const CdmLicenseType license_type,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CdmLicense::IsKeyLoaded(const KeyId& key_id) {
|
||||
return loaded_keys_.find(key_id) != loaded_keys_.end();
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
@@ -27,6 +27,7 @@ message LicenseIdentification {
|
||||
optional bytes purchase_id = 3;
|
||||
optional LicenseType type = 4;
|
||||
optional int32 version = 5;
|
||||
optional bytes provider_session_token = 6;
|
||||
}
|
||||
|
||||
message License {
|
||||
@@ -126,6 +127,8 @@ message License {
|
||||
HDCP_NONE = 0;
|
||||
HDCP_V1 = 1;
|
||||
HDCP_V2 = 2;
|
||||
HDCP_V2_1 = 3;
|
||||
HDCP_V2_2 = 4;
|
||||
}
|
||||
optional HDCP hdcp = 1 [default = HDCP_NONE];
|
||||
|
||||
@@ -139,6 +142,15 @@ message License {
|
||||
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 {
|
||||
// Permissions/key usage flags for operator service keys
|
||||
// (type = OPERATOR_SESSION).
|
||||
@@ -157,12 +169,20 @@ message License {
|
||||
optional OutputProtection requested_protection = 7;
|
||||
optional KeyControl key_control = 8;
|
||||
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 Policy policy = 2;
|
||||
repeated KeyContainer key = 3;
|
||||
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 {
|
||||
@@ -187,6 +207,8 @@ message LicenseRequest {
|
||||
message ExistingLicense {
|
||||
optional LicenseIdentification license_id = 1;
|
||||
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.
|
||||
@@ -233,11 +255,22 @@ message LicenseError {
|
||||
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 {
|
||||
enum MessageType {
|
||||
LICENSE_REQUEST = 1;
|
||||
LICENSE = 2;
|
||||
ERROR = 3;
|
||||
ERROR_RESPONSE = 3;
|
||||
SERVICE_CERTIFICATE_REQUEST = 4;
|
||||
SERVICE_CERTIFICATE = 5;
|
||||
}
|
||||
@@ -246,10 +279,20 @@ message SignedMessage {
|
||||
optional bytes msg = 2;
|
||||
optional bytes signature = 3;
|
||||
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.
|
||||
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 purchase_id = 2;
|
||||
// master_signing_key should be 128 bits in length.
|
||||
@@ -258,6 +301,19 @@ message SessionInit {
|
||||
// (server || client) HMAC-SHA256 keys.
|
||||
optional bytes signing_key = 4;
|
||||
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.
|
||||
@@ -280,7 +336,7 @@ message SessionState {
|
||||
// in the case of X509 certificates, the certificate authority to use.
|
||||
message ProvisioningOptions {
|
||||
enum CertificateType {
|
||||
RSA_WIDEVINE = 0; // Default. The original certificate type.
|
||||
WIDEVINE_DRM = 0; // Default. The original certificate type.
|
||||
X509 = 1; // X.509 certificate.
|
||||
}
|
||||
|
||||
@@ -336,6 +392,7 @@ message ClientIdentification {
|
||||
enum TokenType {
|
||||
KEYBOX = 0;
|
||||
DEVICE_CERTIFICATE = 1;
|
||||
REMOTE_ATTESTATION_CERTIFICATE = 2;
|
||||
}
|
||||
|
||||
message NameValue {
|
||||
@@ -343,12 +400,36 @@ message ClientIdentification {
|
||||
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.
|
||||
optional TokenType type = 1 [default = KEYBOX];
|
||||
// Factory-provisioned device root of trust. Required.
|
||||
optional bytes token = 2;
|
||||
// Optional client information name/value pairs.
|
||||
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
|
||||
@@ -359,16 +440,16 @@ message EncryptedClientIdentification {
|
||||
optional string service_id = 1;
|
||||
// Serial number for the service certificate for which ClientIdentification is
|
||||
// encrypted.
|
||||
optional string service_certificate_serial_number = 2;
|
||||
// Serialized ClientIdentification message, encrypted with the privacy key
|
||||
// using AES-128-CBC with PKCS#5 padding.
|
||||
optional bytes service_certificate_serial_number = 2;
|
||||
// Serialized ClientIdentification message, encrypted with the privacy key using
|
||||
// AES-128-CBC with PKCS#5 padding.
|
||||
optional bytes encrypted_client_id = 3;
|
||||
// Initialization vector needed to decrypt encrypted_client_id.
|
||||
optional bytes encrypted_client_id_iv = 4;
|
||||
// AES-128 privacy key, encrytped with the service public public key using
|
||||
// RSA-OAEP.
|
||||
optional bytes encrypted_privacy_key = 5;
|
||||
};
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// device_certificate.proto
|
||||
@@ -400,9 +481,10 @@ message DeviceCertificate {
|
||||
// Widevine system ID for the device. Required for intermediate and
|
||||
// user device certificates.
|
||||
optional uint32 system_id = 5;
|
||||
// True if the certificate corresponds to a test (non production) device or
|
||||
// service. Optional.
|
||||
optional bool test_device = 6 [default = false];
|
||||
// Deprecated field, which used to indicate whether the device was a test
|
||||
// (non-production) device. The test_device field in ProvisionedDeviceInfo
|
||||
// below should be observed instead.
|
||||
optional bool test_device_deprecated = 6 [deprecated = true];
|
||||
// Service identifier (web origin) for the service which owns the certificate.
|
||||
// Required for service certificates.
|
||||
optional string service_id = 7;
|
||||
|
||||
@@ -7,38 +7,60 @@
|
||||
|
||||
#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 "openssl/aes.h"
|
||||
#include "openssl/bio.h"
|
||||
#include "openssl/err.h"
|
||||
#include "openssl/pem.h"
|
||||
#include "openssl/sha.h"
|
||||
|
||||
namespace {
|
||||
const int kPssSaltLength = 20;
|
||||
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 wvcdm {
|
||||
|
||||
AesCbcKey::AesCbcKey() {}
|
||||
|
||||
AesCbcKey::~AesCbcKey() {}
|
||||
|
||||
bool AesCbcKey::Init(const std::string& key) {
|
||||
if (key.empty()) {
|
||||
LOGE("AesCbcKey::Init: no key provided");
|
||||
return false;
|
||||
}
|
||||
if (key.size() != AES_BLOCK_SIZE) {
|
||||
LOGE("AesCbcKey::Init: unexpected key size: %d", key.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_init(&ctx_);
|
||||
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;
|
||||
key_ = key;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -60,14 +82,16 @@ bool AesCbcKey::Encrypt(const std::string& in, std::string* out,
|
||||
LOGE("AesCbcKey::Encrypt: crypttext destination not provided");
|
||||
return false;
|
||||
}
|
||||
if (!initialized_) {
|
||||
if (key_.empty()) {
|
||||
LOGE("AesCbcKey::Encrypt: AES key not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (EVP_EncryptInit(&ctx_, NULL, NULL,
|
||||
reinterpret_cast<const uint8_t*>(iv->data())) == 0) {
|
||||
LOGE("AesCbcKey::Encrypt: AES CBC iv setup failure: %s",
|
||||
EVP_CIPHER_CTX ctx;
|
||||
if (EVP_EncryptInit(&ctx, EVP_aes_128_cbc(),
|
||||
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));
|
||||
return false;
|
||||
}
|
||||
@@ -75,7 +99,7 @@ bool AesCbcKey::Encrypt(const std::string& in, std::string* out,
|
||||
out->resize(in.size() + AES_BLOCK_SIZE);
|
||||
int out_length = out->size();
|
||||
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())),
|
||||
in.size()) == 0) {
|
||||
LOGE("AesCbcKey::Encrypt: encryption failure: %s",
|
||||
@@ -84,7 +108,7 @@ bool AesCbcKey::Encrypt(const std::string& in, std::string* out,
|
||||
}
|
||||
|
||||
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) {
|
||||
LOGE("AesCbcKey::Encrypt: PKCS7 padding failure: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
@@ -95,34 +119,17 @@ bool AesCbcKey::Encrypt(const std::string& in, std::string* out,
|
||||
return true;
|
||||
}
|
||||
|
||||
RsaPublicKey::~RsaPublicKey() {
|
||||
if (key_ != NULL) {
|
||||
RSA_free(key_);
|
||||
}
|
||||
}
|
||||
RsaPublicKey::RsaPublicKey() {}
|
||||
|
||||
RsaPublicKey::~RsaPublicKey() {}
|
||||
|
||||
bool RsaPublicKey::Init(const std::string& serialized_key) {
|
||||
|
||||
if (serialized_key.empty()) {
|
||||
LOGE("RsaPublicKey::Init: no serialized key provided");
|
||||
return false;
|
||||
}
|
||||
|
||||
BIO* bio = BIO_new_mem_buf(const_cast<char*>(serialized_key.data()),
|
||||
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;
|
||||
}
|
||||
|
||||
serialized_key_ = serialized_key;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -136,36 +143,46 @@ bool RsaPublicKey::Encrypt(const std::string& clear_message,
|
||||
LOGE("RsaPublicKey::Encrypt: no encrypt message buffer provided");
|
||||
return false;
|
||||
}
|
||||
if (key_ == NULL) {
|
||||
if (serialized_key_.empty()) {
|
||||
LOGE("RsaPublicKey::Encrypt: RSA key not initialized");
|
||||
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()) >
|
||||
rsa_size - kRsaPkcs1OaepPaddingLength) {
|
||||
LOGE("RsaPublicKey::Encrypt: message too large to be encrypted (actual %d",
|
||||
" max allowed %d)", clear_message.size(),
|
||||
rsa_size - kRsaPkcs1OaepPaddingLength);
|
||||
FreeKey(key);
|
||||
return false;
|
||||
}
|
||||
|
||||
encrypted_message->assign(rsa_size, 0);
|
||||
if (RSA_public_encrypt(
|
||||
clear_message.size(),
|
||||
const_cast<unsigned char*>(
|
||||
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) {
|
||||
LOGE("RsaPublicKey::Encrypt: encrypt failure: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
FreeKey(key);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RsaPublicKey::VerifySignature(const std::string& message,
|
||||
const std::string& signature) {
|
||||
if (key_ == NULL) {
|
||||
if (serialized_key_.empty()) {
|
||||
LOGE("RsaPublicKey::VerifySignature: RSA key not initialized");
|
||||
return false;
|
||||
}
|
||||
@@ -173,25 +190,33 @@ bool RsaPublicKey::VerifySignature(const std::string& message,
|
||||
LOGE("RsaPublicKey::VerifySignature: signed message is empty");
|
||||
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) {
|
||||
LOGE(
|
||||
"RsaPublicKey::VerifySignature: message signature is of the wrong "
|
||||
"size (expected %d, actual %d)",
|
||||
rsa_size, signature.size());
|
||||
FreeKey(key);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Decrypt the signature.
|
||||
std::string padded_digest(signature.size(), 0);
|
||||
if (RSA_public_decrypt(
|
||||
signature.size(),
|
||||
const_cast<unsigned char*>(
|
||||
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) {
|
||||
LOGE("RsaPublicKey::VerifySignature: RSA public decrypt failure: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
FreeKey(key);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -202,12 +227,13 @@ bool RsaPublicKey::VerifySignature(const std::string& message,
|
||||
|
||||
// Verify PSS padding.
|
||||
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(),
|
||||
reinterpret_cast<const unsigned char*>(padded_digest.data()),
|
||||
kPssSaltLength) == 0) {
|
||||
LOGE("RsaPublicKey::VerifySignature: RSA verify failure: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
FreeKey(key);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
43
core/src/privacy_crypto_dummy.cpp
Normal file
43
core/src/privacy_crypto_dummy.cpp
Normal 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
|
||||
@@ -77,11 +77,11 @@ std::string b2a_hex(const std::string& byte) {
|
||||
|
||||
// Filename-friendly base64 encoding (RFC4648), commonly referred to
|
||||
// as Base64WebSafeEncode.
|
||||
// This is the encoding required to interface with the provisioning
|
||||
// server's Apiary interface as well as for certain license server
|
||||
// transactions. It is also used for logging certain strings.
|
||||
// The difference between web safe encoding vs regular encoding is that
|
||||
// the web safe version replaces '+' with '-' and '/' with '_'.
|
||||
//
|
||||
// This is the encoding required to interface with the provisioning server, as
|
||||
// well as for certain license server transactions. It is also used for logging
|
||||
// certain strings. The difference between web safe encoding vs regular encoding
|
||||
// is that the web safe version replaces '+' with '-' and '/' with '_'.
|
||||
std::string Base64SafeEncode(const std::vector<uint8_t>& bin_input) {
|
||||
if (bin_input.empty()) {
|
||||
return std::string();
|
||||
|
||||
@@ -33,7 +33,6 @@ wvcdm::KeyId g_key_id_pssh;
|
||||
wvcdm::KeyId g_key_id_unwrapped;
|
||||
wvcdm::CdmKeySystem g_key_system;
|
||||
std::string g_license_server;
|
||||
std::string g_port;
|
||||
wvcdm::KeyId g_wrong_key_id;
|
||||
int g_use_full_path = 0; // cannot use boolean in getopt_long
|
||||
|
||||
@@ -138,16 +137,16 @@ class WvCdmEngineTest : public testing::Test {
|
||||
std::string GetKeyRequestResponse(const std::string& server_url,
|
||||
const std::string& client_auth) {
|
||||
// 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()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
url_request.PostRequest(key_msg_);
|
||||
std::string response;
|
||||
int resp_bytes = url_request.GetResponse(&response);
|
||||
LOGD("response:\r\n%s", response.c_str());
|
||||
LOGD("end %d bytes response dump", resp_bytes);
|
||||
bool ok = url_request.GetResponse(&response);
|
||||
LOGD("response: %s\n", response.c_str());
|
||||
EXPECT_TRUE(ok);
|
||||
|
||||
int status_code = url_request.GetStatusCode(response);
|
||||
EXPECT_EQ(kHttpOk, status_code);
|
||||
@@ -254,14 +253,12 @@ int main(int argc, char **argv) {
|
||||
// The following variables are configurable through command line options.
|
||||
g_license_server.assign(config.license_server());
|
||||
g_key_id_pssh.assign(config.key_id());
|
||||
g_port.assign(config.port());
|
||||
std::string license_server(g_license_server);
|
||||
|
||||
int show_usage = 0;
|
||||
static const struct option long_options[] = {
|
||||
{ "use_full_path", no_argument, &g_use_full_path, 0 },
|
||||
{ "keyid", required_argument, NULL, 'k' },
|
||||
{ "port", required_argument, NULL, 'p' },
|
||||
{ "server", required_argument, NULL, 's' },
|
||||
{ "vmodule", 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);
|
||||
break;
|
||||
}
|
||||
case 'p': {
|
||||
g_port.clear();
|
||||
g_port.assign(optarg);
|
||||
break;
|
||||
}
|
||||
case 's': {
|
||||
g_license_server.clear();
|
||||
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 << " e.g. adb shell '" << argv[0] << " --server=\"url\"'" << std::endl << std::endl;
|
||||
|
||||
std::cout << std::setw(30) << std::left << " --port=<connection port>";
|
||||
std::cout << "specifies the port number, in decimal format" << std::endl;
|
||||
std::cout << std::setw(30) << std::left << " ";
|
||||
std::cout << "default: " << g_port << std::endl;
|
||||
|
||||
std::cout << std::setw(30) << std::left << " --server=<server_url>";
|
||||
std::cout << "configure the license server url, please include http[s] in the url" << std::endl;
|
||||
std::cout << std::setw(30) << std::left << " ";
|
||||
@@ -327,12 +314,10 @@ int main(int argc, char **argv) {
|
||||
|
||||
std::cout << std::endl;
|
||||
std::cout << "Server: " << g_license_server << std::endl;
|
||||
std::cout << "Port: " << g_port << std::endl;
|
||||
std::cout << "KeyID: " << g_key_id_pssh << std::endl << std::endl;
|
||||
|
||||
g_key_id_pssh = wvcdm::a2bs_hex(g_key_id_pssh);
|
||||
config.set_license_server(g_license_server);
|
||||
config.set_port(g_port);
|
||||
config.set_key_id(g_key_id_pssh);
|
||||
|
||||
// Extract the key ID from the PSSH box.
|
||||
|
||||
@@ -3,14 +3,23 @@
|
||||
#include "config_test_env.h"
|
||||
|
||||
namespace {
|
||||
const std::string kWidevineKeySystem = "com.widevine.alpha";
|
||||
|
||||
// Youtube Content Protection license server data
|
||||
const std::string kYtCpLicenseServer =
|
||||
"http://wv-ref-eme-player.appspot.com/proxy";
|
||||
const std::string kYtCpClientAuth = "";
|
||||
const std::string kYtCpKeyId =
|
||||
"000000347073736800000000" // blob size and pssh
|
||||
"EDEF8BA979D64ACEA3C827DCD51D21ED00000014" // Widevine system id
|
||||
"0801121030313233343536373839616263646566"; // pssh data
|
||||
"000000427073736800000000" // blob size and pssh
|
||||
"EDEF8BA979D64ACEA3C827DCD51D21ED00000022" // Widevine system id
|
||||
"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
|
||||
const std::string kYtLicenseServer =
|
||||
@@ -34,6 +43,13 @@ const std::string kGpLicenseServer =
|
||||
const std::string kGpClientAuth =
|
||||
"?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 =
|
||||
"000000347073736800000000" // blob size and pssh
|
||||
"edef8ba979d64acea3c827dcd51d21ed00000014" // Widevine system id
|
||||
@@ -63,23 +79,49 @@ const std::string kServerSdkLicenseServer =
|
||||
|
||||
const wvcdm::ConfigTestEnv::LicenseServerConfiguration license_servers[] = {
|
||||
{ wvcdm::kGooglePlayServer, kGpLicenseServer, kGpClientAuth, kGpKeyId,
|
||||
kDefaultHttpsPort, true, true },
|
||||
kGpKeyId },
|
||||
{ wvcdm::kYouTubeContentProtectionServer, kYtCpLicenseServer,
|
||||
kYtCpClientAuth, kYtCpKeyId, kDefaultHttpPort, false, false }
|
||||
kYtCpClientAuth, kYtCpKeyId, kYtCpOfflineKeyId }
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
ConfigTestEnv::ConfigTestEnv(LicenseServerId server_id)
|
||||
: client_auth_(license_servers[server_id].client_tag),
|
||||
key_id_(license_servers[server_id].key_id),
|
||||
key_system_("com.widevine.alpha"),
|
||||
license_server_(license_servers[server_id].url),
|
||||
port_(license_servers[server_id].port),
|
||||
provisioning_server_url_(kProductionProvisioningServerUrl),
|
||||
use_chunked_transfer_(license_servers[server_id].use_chunked_transfer),
|
||||
use_secure_transfer_(license_servers[server_id].use_secure_transfer),
|
||||
wrong_key_id_(kWrongKeyId) {}
|
||||
ConfigTestEnv::ConfigTestEnv(LicenseServerId server_id) {
|
||||
Init(server_id);
|
||||
}
|
||||
|
||||
ConfigTestEnv::ConfigTestEnv(LicenseServerId server_id, bool streaming) {
|
||||
Init(server_id);
|
||||
if (!streaming)
|
||||
key_id_ = license_servers[server_id].offline_key_id;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -6,11 +6,6 @@
|
||||
#include <string>
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace {
|
||||
const std::string kDefaultHttpsPort = "443";
|
||||
const std::string kDefaultHttpPort = "80";
|
||||
}
|
||||
|
||||
namespace wvcdm {
|
||||
typedef enum {
|
||||
kGooglePlayServer,
|
||||
@@ -25,24 +20,22 @@ class ConfigTestEnv {
|
||||
std::string url;
|
||||
std::string client_tag;
|
||||
std::string key_id;
|
||||
std::string port;
|
||||
bool use_chunked_transfer;
|
||||
bool use_secure_transfer;
|
||||
std::string offline_key_id;
|
||||
} LicenseServerConfiguration;
|
||||
|
||||
explicit ConfigTestEnv(LicenseServerId server_id);
|
||||
ConfigTestEnv(LicenseServerId server_id, bool streaming);
|
||||
ConfigTestEnv(LicenseServerId server_id, bool streaming, bool renew,
|
||||
bool release);
|
||||
~ConfigTestEnv() {};
|
||||
|
||||
const std::string& client_auth() const { return client_auth_; }
|
||||
const KeyId& key_id() const { return key_id_; }
|
||||
const CdmKeySystem& key_system() const { return key_system_; }
|
||||
const std::string& license_server() const { return license_server_; }
|
||||
const std::string& port() const { return port_; }
|
||||
const std::string& provisioning_server_url() const {
|
||||
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_; }
|
||||
|
||||
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) {
|
||||
license_server_.assign(license_server);
|
||||
}
|
||||
void set_port(std::string& port) { port_.assign(port); }
|
||||
|
||||
private:
|
||||
void Init(LicenseServerId server_id);
|
||||
|
||||
std::string client_auth_;
|
||||
KeyId key_id_;
|
||||
CdmKeySystem key_system_;
|
||||
std::string license_server_;
|
||||
std::string port_;
|
||||
std::string provisioning_server_url_;
|
||||
bool use_chunked_transfer_;
|
||||
bool use_secure_transfer_;
|
||||
KeyId wrong_key_id_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(ConfigTestEnv);
|
||||
|
||||
@@ -26,7 +26,11 @@ using ::testing::StrEq;
|
||||
namespace {
|
||||
const uint32_t kCertificateLen = 700;
|
||||
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.
|
||||
// The data elements in this module are used to test the storage and
|
||||
@@ -901,8 +905,7 @@ LicenseInfo license_update_test_data[] = {
|
||||
"D30B08B2C4673551293A2E68747470733A2F2F746573742E676F6F676C65"
|
||||
"2E636F6D2F6C6963656E73652F47657443656E634C6963656E736512200A"
|
||||
"1C78D0E574D0827C3AE78A05EEC90BAC31D10686EC19EB0599F75B2D1AB4"
|
||||
"C5"
|
||||
)},
|
||||
"C5")},
|
||||
// license being released. all fields are identical except for license
|
||||
// state and hashed file data
|
||||
{"", DeviceFiles::kLicenseStateReleasing, "", "", "", "", "", "",
|
||||
@@ -1001,6 +1004,287 @@ LicenseInfo license_update_test_data[] = {
|
||||
"7186A244EF561E3B07DC459BC681A0798B180667EA448327F6BBBD30212A"
|
||||
"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
|
||||
|
||||
class MockFile : public File {
|
||||
@@ -1053,6 +1337,9 @@ class DeviceFilesSecurityLevelTest
|
||||
: public DeviceFilesTest,
|
||||
public ::testing::WithParamInterface<CdmSecurityLevel> {};
|
||||
|
||||
class DeviceFilesUsageInfoTest : public DeviceFilesTest,
|
||||
public ::testing::WithParamInterface<int> {};
|
||||
|
||||
MATCHER(IsCreateFileFlagSet, "") { return File::kCreate & arg; }
|
||||
MATCHER(IsBinaryFileFlagSet, "") { return File::kBinary & arg; }
|
||||
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
|
||||
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
|
||||
// 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 &&
|
||||
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, "") {
|
||||
// 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, 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 &&
|
||||
data.find(str2) != 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),
|
||||
AllOf(IsCreateFileFlagSet(), IsBinaryFileFlagSet())))
|
||||
.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())))
|
||||
.WillOnce(ReturnArg<1>());
|
||||
EXPECT_CALL(file, Close()).Times(1);
|
||||
EXPECT_CALL(file, Read(_, _)).Times(0);
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -1132,7 +1431,8 @@ TEST_F(DeviceFilesTest, ReadCertificate) {
|
||||
EXPECT_CALL(file, Write(_, _)).Times(0);
|
||||
|
||||
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;
|
||||
ASSERT_TRUE(
|
||||
@@ -1161,14 +1461,15 @@ TEST_P(DeviceFilesSecurityLevelTest, SecurityLevel) {
|
||||
EXPECT_CALL(file, Open(StrEq(device_certificate_path),
|
||||
AllOf(IsCreateFileFlagSet(), IsBinaryFileFlagSet())))
|
||||
.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())))
|
||||
.WillOnce(ReturnArg<1>());
|
||||
EXPECT_CALL(file, Close()).Times(1);
|
||||
EXPECT_CALL(file, Read(_, _)).Times(0);
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -1208,7 +1509,8 @@ TEST_P(DeviceFilesStoreTest, StoreLicense) {
|
||||
EXPECT_CALL(file, Read(_, _)).Times(0);
|
||||
|
||||
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(
|
||||
license_test_data[license_num].key_set_id,
|
||||
license_test_data[license_num].license_state,
|
||||
@@ -1226,7 +1528,8 @@ INSTANTIATE_TEST_CASE_P(StoreLicense, DeviceFilesStoreTest,
|
||||
TEST_F(DeviceFilesTest, StoreLicenses) {
|
||||
MockFile file;
|
||||
EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_)))
|
||||
.Times(kNumberOfLicenses).WillRepeatedly(Return(true));
|
||||
.Times(kNumberOfLicenses)
|
||||
.WillRepeatedly(Return(true));
|
||||
EXPECT_CALL(file, CreateDirectory(_)).Times(0);
|
||||
|
||||
for (size_t i = 0; i < kNumberOfLicenses; ++i) {
|
||||
@@ -1251,7 +1554,8 @@ TEST_F(DeviceFilesTest, StoreLicenses) {
|
||||
EXPECT_CALL(file, Read(_, _)).Times(0);
|
||||
|
||||
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++) {
|
||||
EXPECT_TRUE(device_files.StoreLicense(
|
||||
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);
|
||||
|
||||
DeviceFiles device_files;
|
||||
EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1));
|
||||
EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
|
||||
device_files.SetTestFile(&file);
|
||||
DeviceFiles::LicenseState license_state;
|
||||
CdmInitData pssh_data;
|
||||
CdmKeyMessage key_request;
|
||||
@@ -1338,12 +1643,11 @@ TEST_F(DeviceFilesTest, SecurityLevelPathBackwardCompatibility) {
|
||||
|
||||
std::string old_path = base_path + DeviceFiles::GetCertificateFileName();
|
||||
old_files.push_back(DeviceFiles::GetCertificateFileName());
|
||||
EXPECT_CALL(file, IsRegularFile(StrEq(old_path)))
|
||||
.WillOnce(Return(true));
|
||||
EXPECT_CALL(file, IsRegularFile(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) {
|
||||
new_path = base_path + security_dirs[i] +
|
||||
DeviceFiles::GetCertificateFileName();
|
||||
new_path =
|
||||
base_path + security_dirs[i] + DeviceFiles::GetCertificateFileName();
|
||||
EXPECT_CALL(file, Copy(StrEq(old_path), StrEq(new_path)))
|
||||
.WillOnce(Return(true));
|
||||
}
|
||||
@@ -1377,7 +1681,8 @@ TEST_F(DeviceFilesTest, SecurityLevelPathBackwardCompatibility) {
|
||||
EXPECT_CALL(file, Write(_, _)).Times(0);
|
||||
|
||||
DeviceFiles device_files;
|
||||
EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1));
|
||||
EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
|
||||
device_files.SetTestFile(&file);
|
||||
|
||||
Properties::Init();
|
||||
std::string certificate, wrapped_private_key;
|
||||
@@ -1391,12 +1696,14 @@ TEST_F(DeviceFilesTest, UpdateLicenseState) {
|
||||
license_update_test_data[0].key_set_id +
|
||||
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));
|
||||
EXPECT_CALL(file, CreateDirectory(_)).Times(0);
|
||||
EXPECT_CALL(file, Open(StrEq(license_path),
|
||||
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),
|
||||
Eq(license_update_test_data[0].file_data.size())))
|
||||
.WillOnce(ReturnArg<1>());
|
||||
@@ -1407,7 +1714,8 @@ TEST_F(DeviceFilesTest, UpdateLicenseState) {
|
||||
EXPECT_CALL(file, Read(_, _)).Times(0);
|
||||
|
||||
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(
|
||||
license_update_test_data[0].key_set_id,
|
||||
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();
|
||||
|
||||
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));
|
||||
EXPECT_CALL(file, FileSize(StrEq(license_path))).WillOnce(Return(size));
|
||||
EXPECT_CALL(file, Open(StrEq(license_path), IsBinaryFileFlagSet()))
|
||||
@@ -1451,7 +1761,8 @@ TEST_F(DeviceFilesTest, DeleteLicense) {
|
||||
EXPECT_CALL(file, Write(_, _)).Times(0);
|
||||
|
||||
DeviceFiles device_files;
|
||||
EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1));
|
||||
EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
|
||||
device_files.SetTestFile(&file);
|
||||
DeviceFiles::LicenseState license_state;
|
||||
CdmInitData pssh_data;
|
||||
CdmKeyMessage key_request;
|
||||
@@ -1475,4 +1786,154 @@ TEST_F(DeviceFilesTest, DeleteLicense) {
|
||||
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
|
||||
|
||||
@@ -5,17 +5,35 @@
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <netdb.h>
|
||||
#include <string.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/x509.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;
|
||||
SSL_CTX* ctx;
|
||||
|
||||
@@ -23,21 +41,18 @@ SSL_CTX* HttpSocket::InitSslContext(void) {
|
||||
SSL_load_error_strings();
|
||||
method = SSLv3_client_method();
|
||||
ctx = SSL_CTX_new(method);
|
||||
if (NULL == ctx) {
|
||||
if (!ctx)
|
||||
LOGE("failed to create SSL context");
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void HttpSocket::ShowServerCertificate(const SSL* ssl) {
|
||||
X509* cert;
|
||||
char* line;
|
||||
|
||||
// unused, may be useful for debugging SSL-related issues.
|
||||
void ShowServerCertificate(const SSL* ssl) {
|
||||
// gets the server certificate
|
||||
cert = SSL_get_peer_certificate(ssl);
|
||||
if (cert != NULL) {
|
||||
X509* cert = SSL_get_peer_certificate(ssl);
|
||||
if (cert) {
|
||||
char* line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
|
||||
LOGV("server certificate:");
|
||||
line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
|
||||
LOGV("subject: %s", line);
|
||||
free(line);
|
||||
line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
|
||||
@@ -49,183 +64,252 @@ void HttpSocket::ShowServerCertificate(const SSL* ssl) {
|
||||
}
|
||||
}
|
||||
|
||||
HttpSocket::HttpSocket()
|
||||
: secure_connect_(true),
|
||||
socket_fd_(-1),
|
||||
// Wait for a socket to be ready for reading or writing.
|
||||
// Establishing a connection counts as "ready for write".
|
||||
// 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_ctx_(NULL),
|
||||
timeout_enabled_(false) {
|
||||
ssl_ctx_(NULL) {
|
||||
valid_url_ = ParseUrl(url, &scheme_, &secure_connect_, &domain_name_, &port_,
|
||||
&resource_path_);
|
||||
SSL_library_init();
|
||||
}
|
||||
|
||||
HttpSocket::~HttpSocket() { CloseSocket(); }
|
||||
HttpSocket::~HttpSocket() {
|
||||
CloseSocket();
|
||||
}
|
||||
|
||||
void HttpSocket::CloseSocket() {
|
||||
if (socket_fd_ != -1) {
|
||||
close(socket_fd_);
|
||||
socket_fd_ = -1;
|
||||
}
|
||||
if (secure_connect_) {
|
||||
if (ssl_) {
|
||||
SSL_free(ssl_);
|
||||
ssl_ = NULL;
|
||||
}
|
||||
if (ssl_ctx_) {
|
||||
CloseSslContext(ssl_ctx_);
|
||||
ssl_ctx_ = NULL;
|
||||
}
|
||||
if (ssl_) {
|
||||
SSL_free(ssl_);
|
||||
ssl_ = NULL;
|
||||
}
|
||||
if (ssl_ctx_) {
|
||||
SSL_CTX_free(ssl_ctx_);
|
||||
ssl_ctx_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Extracts the domain name and resource path from the input url parameter.
|
||||
// The results are put in domain_name and resource_path respectively.
|
||||
// The format of the url can begin with <protocol/scheme>:://domain server/...
|
||||
// or dowmain server/resource_path
|
||||
void HttpSocket::GetDomainNameAndPathFromUrl(const std::string& url,
|
||||
std::string& domain_name,
|
||||
std::string& resource_path) {
|
||||
domain_name.clear();
|
||||
resource_path.clear();
|
||||
|
||||
size_t start = url.find("//");
|
||||
size_t end = url.npos;
|
||||
if (start != url.npos) {
|
||||
end = url.find("/", start + 2);
|
||||
if (end != url.npos) {
|
||||
domain_name.assign(url, start + 2, end - start - 2);
|
||||
resource_path.assign(url, end + 1, url.npos);
|
||||
} else {
|
||||
domain_name.assign(url, start + 2, url.npos);
|
||||
}
|
||||
} else {
|
||||
// no scheme/protocol in url
|
||||
end = url.find("/");
|
||||
if (end != url.npos) {
|
||||
domain_name.assign(url, 0, end);
|
||||
resource_path.assign(url, end + 1, url.npos);
|
||||
} else {
|
||||
domain_name.assign(url);
|
||||
}
|
||||
bool HttpSocket::Connect(int timeout_in_ms) {
|
||||
if (!valid_url_) {
|
||||
return false;
|
||||
}
|
||||
// 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);
|
||||
if (socket_fd_ < 0) {
|
||||
LOGE("cannot open socket %d", errno);
|
||||
LOGE("cannot open socket, errno = %d", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
int reuse = 1;
|
||||
if (setsockopt(socket_fd_, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) ==
|
||||
-1) {
|
||||
// set the socket in non-blocking mode
|
||||
int original_flags = fcntl(socket_fd_, F_GETFL, 0);
|
||||
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();
|
||||
LOGE("setsockopt error %d", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
// lookup the server IP
|
||||
struct addrinfo hints;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
struct addrinfo* addr_info = NULL;
|
||||
bool status = true;
|
||||
int ret = getaddrinfo(domain_name_.c_str(), port.c_str(), &hints, &addr_info);
|
||||
int ret = getaddrinfo(domain_name_.c_str(), NULL, &hints, &addr_info);
|
||||
if (ret != 0) {
|
||||
CloseSocket();
|
||||
LOGE("getaddrinfo failed with %d", ret);
|
||||
status = false;
|
||||
LOGE("getaddrinfo failed, errno = %d", ret);
|
||||
return 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 {
|
||||
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();
|
||||
LOGE("cannot connect socket to %s, error=%d", domain_name_.c_str(),
|
||||
errno);
|
||||
status = false;
|
||||
return false;
|
||||
} else {
|
||||
// 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_);
|
||||
if (!ssl_) {
|
||||
LOGE("failed SSL_new");
|
||||
CloseSocket();
|
||||
return false;
|
||||
}
|
||||
|
||||
BIO* a_bio = BIO_new_socket(socket_fd_, BIO_NOCLOSE);
|
||||
if (!a_bio) {
|
||||
LOGE("BIO_new_socket error");
|
||||
CloseSocket();
|
||||
return false;
|
||||
}
|
||||
|
||||
SSL_set_bio(ssl_, a_bio, a_bio);
|
||||
int ret = SSL_connect(ssl_);
|
||||
if (1 != ret) {
|
||||
char buf[256];
|
||||
LOGE("SSL_connect error:%s", ERR_error_string(ERR_get_error(), buf));
|
||||
return false;
|
||||
}
|
||||
do {
|
||||
ret = SSL_connect(ssl_);
|
||||
if (ret != 1) {
|
||||
int ssl_err = SSL_get_error(ssl_, ret);
|
||||
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;
|
||||
}
|
||||
|
||||
int HttpSocket::Read(char* data, int len) { return (Read(data, len, 0)); }
|
||||
|
||||
// makes non-blocking mode only during read, it supports timeout for read
|
||||
// returns -1 for error, number of bytes read for success
|
||||
// Returns -1 for error, number of bytes read for success.
|
||||
// The timeout here only applies to the span between packets of data, for the
|
||||
// sake of simplicity.
|
||||
int HttpSocket::Read(char* data, int len, int timeout_in_ms) {
|
||||
bool use_timeout = (timeout_enabled_ && (timeout_in_ms > 0));
|
||||
int original_flags = 0;
|
||||
if (use_timeout) {
|
||||
original_flags = fcntl(socket_fd_, F_GETFL, 0);
|
||||
if (original_flags == -1) {
|
||||
LOGE("fcntl error %d", errno);
|
||||
return -1;
|
||||
}
|
||||
if (fcntl(socket_fd_, F_SETFL, original_flags | O_NONBLOCK) == -1) {
|
||||
LOGE("fcntl error %d", errno);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int total_read = 0;
|
||||
int read = 0;
|
||||
int to_read = len;
|
||||
|
||||
while (to_read > 0) {
|
||||
if (use_timeout) {
|
||||
fd_set read_fds;
|
||||
struct timeval tv;
|
||||
tv.tv_sec = timeout_in_ms / 1000;
|
||||
tv.tv_usec = (timeout_in_ms % 1000) * 1000;
|
||||
FD_ZERO(&read_fds);
|
||||
FD_SET(socket_fd_, &read_fds);
|
||||
if (select(socket_fd_ + 1, &read_fds, NULL, NULL, &tv) == -1) {
|
||||
LOGE("select failed");
|
||||
break;
|
||||
}
|
||||
if (!FD_ISSET(socket_fd_, &read_fds)) {
|
||||
LOGD("socket read timeout");
|
||||
break;
|
||||
}
|
||||
if (!SocketWait(socket_fd_, /* for_read */ true, timeout_in_ms)) {
|
||||
LOGE("unable to read from %s", domain_name_.c_str());
|
||||
return -1;
|
||||
}
|
||||
|
||||
int read;
|
||||
if (secure_connect_)
|
||||
read = SSL_read(ssl_, data, to_read);
|
||||
else
|
||||
@@ -236,27 +320,26 @@ int HttpSocket::Read(char* data, int len, int timeout_in_ms) {
|
||||
data += read;
|
||||
total_read += read;
|
||||
} else if (read == 0) {
|
||||
// in blocking mode, zero read mean's peer closed.
|
||||
// in non-blocking mode, select said that there is data. so it should not
|
||||
// happen
|
||||
// The connection has been closed. No more data.
|
||||
break;
|
||||
} else {
|
||||
LOGE("recv returned %d, error = %d", read, errno);
|
||||
break;
|
||||
LOGE("recv returned %d, errno = %d", read, errno);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (use_timeout) {
|
||||
fcntl(socket_fd_, F_SETFL, original_flags); // now blocking again
|
||||
}
|
||||
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 sent = 0;
|
||||
int to_send = len;
|
||||
|
||||
while (to_send > 0) {
|
||||
int sent;
|
||||
if (secure_connect_)
|
||||
sent = SSL_write(ssl_, data, to_send);
|
||||
else
|
||||
@@ -267,11 +350,17 @@ int HttpSocket::Write(const char* data, int len) {
|
||||
data += sent;
|
||||
total_sent += sent;
|
||||
} 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 {
|
||||
LOGE("send returned error %d", errno);
|
||||
LOGE("send returned %d, errno = %d", sent, errno);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return total_sent;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,43 +4,52 @@
|
||||
#define CDM_TEST_HTTP_SOCKET_H_
|
||||
|
||||
#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 {
|
||||
|
||||
// Provides basic Linux based TCP socket interface.
|
||||
class HttpSocket {
|
||||
public:
|
||||
HttpSocket();
|
||||
// A scheme (http:// or https://) is required for the URL.
|
||||
explicit HttpSocket(const std::string& url);
|
||||
~HttpSocket();
|
||||
|
||||
bool Connect(int timeout_in_ms);
|
||||
void CloseSocket();
|
||||
bool Connect(const char* url, const std::string& port, bool enable_timeout,
|
||||
bool secure_connection);
|
||||
void GetDomainNameAndPathFromUrl(const std::string& url,
|
||||
std::string& domain_name,
|
||||
std::string& resource_path);
|
||||
const std::string& domain_name() const { return domain_name_; };
|
||||
const std::string& resource_path() const { return resource_path_; };
|
||||
int Read(char* data, int len);
|
||||
|
||||
const std::string& scheme() const { return scheme_; }
|
||||
bool secure_connect() const { return secure_connect_; }
|
||||
const std::string& domain_name() const { return domain_name_; }
|
||||
int port() const { return port_; }
|
||||
const std::string& resource_path() const { return resource_path_; }
|
||||
|
||||
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:
|
||||
void CloseSslContext(SSL_CTX* ctx) const {
|
||||
if (ctx) SSL_CTX_free(ctx);
|
||||
}
|
||||
SSL_CTX* InitSslContext(void);
|
||||
void ShowServerCertificate(const SSL* ssl);
|
||||
static bool ParseUrl(const std::string& url,
|
||||
std::string* scheme,
|
||||
bool* secure_connect,
|
||||
std::string* domain_name,
|
||||
int* port,
|
||||
std::string* path);
|
||||
FRIEND_TEST(HttpSocketTest, ParseUrlTest);
|
||||
|
||||
std::string domain_name_;
|
||||
std::string scheme_;
|
||||
bool secure_connect_;
|
||||
std::string domain_name_;
|
||||
int port_;
|
||||
std::string resource_path_;
|
||||
bool valid_url_;
|
||||
|
||||
int socket_fd_;
|
||||
SSL* ssl_;
|
||||
SSL_CTX* ssl_ctx_;
|
||||
bool timeout_enabled_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(HttpSocket);
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <errno.h>
|
||||
#include "gtest/gtest.h"
|
||||
#include "http_socket.h"
|
||||
#include "scoped_ptr.h"
|
||||
#include "log.h"
|
||||
#include "string_conversions.h"
|
||||
#include "url_request.h"
|
||||
@@ -10,10 +11,14 @@
|
||||
namespace {
|
||||
// Arbitrary URL for tests.
|
||||
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 gTestData("Hello");
|
||||
// Arbitrary buffer size and timeout settings.
|
||||
const int kHttpBufferSize = 4096;
|
||||
char gBuffer[kHttpBufferSize];
|
||||
const int kTimeout = 3000;
|
||||
}
|
||||
|
||||
namespace wvcdm {
|
||||
@@ -21,153 +26,179 @@ namespace wvcdm {
|
||||
class HttpSocketTest : public testing::Test {
|
||||
public:
|
||||
HttpSocketTest() {}
|
||||
~HttpSocketTest() { socket_.CloseSocket(); }
|
||||
~HttpSocketTest() {}
|
||||
|
||||
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(server_url.c_str(), port, true, secure_connection)) {
|
||||
LOGD("connected to %s", socket_.domain_name().c_str());
|
||||
if (socket_->Connect(kTimeout)) {
|
||||
LOGD("connected to %s", socket_->domain_name().c_str());
|
||||
return true;
|
||||
} 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 true;
|
||||
}
|
||||
|
||||
bool PostRequest(const std::string& data) {
|
||||
std::string request("POST ");
|
||||
if (socket_.resource_path().empty())
|
||||
request.append(socket_.domain_name());
|
||||
else
|
||||
request.append(socket_.resource_path());
|
||||
request.append(socket_->resource_path());
|
||||
request.append(" HTTP/1.1\r\n");
|
||||
|
||||
request.append("Host: ");
|
||||
request.append(socket_.domain_name());
|
||||
request.append("\r\nUser-Agent: httpSocketTest/1.0\r\n");
|
||||
request.append(socket_->domain_name());
|
||||
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: ");
|
||||
memset(gBuffer, 0, kHttpBufferSize);
|
||||
snprintf(gBuffer, kHttpBufferSize, "%d\r\n", static_cast<int>(data.size()));
|
||||
request.append(gBuffer);
|
||||
request.append(buffer);
|
||||
request.append("\r\n");
|
||||
|
||||
request.append("Content-Type: multipart/form-data\r\n");
|
||||
|
||||
// newline terminates header
|
||||
// an extra newline terminates HTTP headers.
|
||||
request.append("\r\n");
|
||||
|
||||
// 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());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetResponse() {
|
||||
int bytes = socket_.Read(gBuffer, kHttpBufferSize, 1000);
|
||||
bool GetResponse(std::string* response) {
|
||||
char buffer[kHttpBufferSize];
|
||||
int bytes = socket_->Read(buffer, sizeof(buffer), kTimeout);
|
||||
if (bytes < 0) {
|
||||
LOGE("read error = ", errno);
|
||||
LOGE("read error, errno = %d", errno);
|
||||
return false;
|
||||
} else {
|
||||
LOGD("read %d bytes", bytes);
|
||||
std::string response(gBuffer, bytes);
|
||||
LOGD("response: %s", response.c_str());
|
||||
LOGD("end response dump");
|
||||
return true;
|
||||
}
|
||||
|
||||
LOGD("read %d bytes", bytes);
|
||||
response->assign(buffer, bytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
HttpSocket socket_;
|
||||
scoped_ptr<HttpSocket> socket_;
|
||||
std::string domain_name_;
|
||||
std::string resource_path_;
|
||||
};
|
||||
|
||||
TEST_F(HttpSocketTest, GetDomainNameAndPathFromUrlTest) {
|
||||
socket_.GetDomainNameAndPathFromUrl(
|
||||
"https://code.google.com/p/googletest/wiki/Primer", domain_name_,
|
||||
resource_path_);
|
||||
EXPECT_STREQ("code.google.com", domain_name_.c_str());
|
||||
EXPECT_STREQ("p/googletest/wiki/Primer", resource_path_.c_str());
|
||||
struct ParseUrlTests {
|
||||
const char* url;
|
||||
const char* scheme;
|
||||
bool secure_connect;
|
||||
const char* domain_name;
|
||||
int port;
|
||||
const char* path;
|
||||
};
|
||||
|
||||
socket_.GetDomainNameAndPathFromUrl(
|
||||
"http://code.google.com/p/googletest/wiki/Primer/", domain_name_,
|
||||
resource_path_);
|
||||
EXPECT_STREQ("code.google.com", domain_name_.c_str());
|
||||
EXPECT_STREQ("p/googletest/wiki/Primer/", resource_path_.c_str());
|
||||
ParseUrlTests parse_url_tests[] = {
|
||||
{
|
||||
"https://code.google.com/p/googletest/wiki/Primer", // url
|
||||
"https", // scheme
|
||||
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_,
|
||||
resource_path_);
|
||||
EXPECT_STREQ("code.google.com", domain_name_.c_str());
|
||||
EXPECT_STREQ("", resource_path_.c_str());
|
||||
TEST_F(HttpSocketTest, ParseUrlTest) {
|
||||
std::string scheme;
|
||||
bool secure_connect;
|
||||
std::string domain_name;
|
||||
int port;
|
||||
std::string path;
|
||||
ParseUrlTests* test = NULL;
|
||||
|
||||
socket_.GetDomainNameAndPathFromUrl("http://code.google.com", domain_name_,
|
||||
resource_path_);
|
||||
EXPECT_STREQ("code.google.com", domain_name_.c_str());
|
||||
EXPECT_STREQ("", resource_path_.c_str());
|
||||
|
||||
socket_.GetDomainNameAndPathFromUrl(
|
||||
"code.google.com/p/googletest/wiki/Primer", domain_name_, resource_path_);
|
||||
EXPECT_STREQ("code.google.com", domain_name_.c_str());
|
||||
EXPECT_STREQ("p/googletest/wiki/Primer", resource_path_.c_str());
|
||||
|
||||
socket_.GetDomainNameAndPathFromUrl("code.google.com", domain_name_,
|
||||
resource_path_);
|
||||
EXPECT_STREQ("code.google.com", domain_name_.c_str());
|
||||
EXPECT_STREQ("", resource_path_.c_str());
|
||||
|
||||
socket_.GetDomainNameAndPathFromUrl("code.google.com/", domain_name_,
|
||||
resource_path_);
|
||||
EXPECT_STREQ("code.google.com", domain_name_.c_str());
|
||||
EXPECT_STREQ("", resource_path_.c_str());
|
||||
|
||||
socket_.GetDomainNameAndPathFromUrl("", domain_name_, resource_path_);
|
||||
EXPECT_TRUE(domain_name_.empty());
|
||||
EXPECT_TRUE(resource_path_.empty());
|
||||
|
||||
// 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());
|
||||
for (test = &parse_url_tests[0]; test->url != NULL; ++test) {
|
||||
bool ok = HttpSocket::ParseUrl(test->url, &scheme, &secure_connect,
|
||||
&domain_name, &port, &path);
|
||||
EXPECT_TRUE(ok);
|
||||
if (ok) {
|
||||
EXPECT_EQ(test->scheme, scheme);
|
||||
EXPECT_EQ(test->secure_connect, secure_connect);
|
||||
EXPECT_EQ(test->domain_name, domain_name);
|
||||
EXPECT_EQ(test->port, port);
|
||||
EXPECT_EQ(test->path, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(HttpSocketTest, ConnectTest) {
|
||||
const bool kUseSecureConnection = true;
|
||||
|
||||
if (gTestServer.find("https") != std::string::npos) {
|
||||
EXPECT_TRUE(Connect(gTestServer, kUseSecureConnection));
|
||||
socket_.CloseSocket();
|
||||
|
||||
// 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();
|
||||
EXPECT_TRUE(Connect(kHttpsTestServer));
|
||||
EXPECT_TRUE(Connect(kHttpTestServer));
|
||||
EXPECT_FALSE(Connect("ww.g.c"));
|
||||
EXPECT_FALSE(Connect("http://ww.g.c"));
|
||||
EXPECT_FALSE(Connect("https://ww.g.c"));
|
||||
}
|
||||
|
||||
TEST_F(HttpSocketTest, RoundTripTest) {
|
||||
int secure_connection =
|
||||
(gTestServer.find("https") != std::string::npos) ? true : false;
|
||||
ASSERT_TRUE(Connect(gTestServer, secure_connection));
|
||||
ASSERT_TRUE(Connect(gTestServer));
|
||||
EXPECT_TRUE(PostRequest(gTestData));
|
||||
GetResponse();
|
||||
socket_.CloseSocket();
|
||||
|
||||
std::string response;
|
||||
EXPECT_TRUE(GetResponse(&response));
|
||||
LOGD("response: %s", response.c_str());
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
@@ -10,52 +10,16 @@
|
||||
#include "string_conversions.h"
|
||||
|
||||
namespace {
|
||||
const int kMaxReadAttempts = 4;
|
||||
const int kSingleReadAttempt = 1;
|
||||
} // namespace
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
UrlRequest::UrlRequest(const std::string& url, const std::string& port,
|
||||
bool secure_connection, bool chunk_transfer_mode)
|
||||
: 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
|
||||
}
|
||||
const int kReadBufferSize = 1024;
|
||||
const int kConnectTimeoutMs = 5000;
|
||||
const int kWriteTimeoutMs = 3000;
|
||||
const int kReadTimeoutMs = 3000;
|
||||
|
||||
// Concatenate all chunks into one blob and returns the response with
|
||||
// header information.
|
||||
void UrlRequest::ConcatenateChunkedResponse(const std::string http_response,
|
||||
std::string* modified_response) {
|
||||
void ConcatenateChunkedResponse(const std::string http_response,
|
||||
std::string* modified_response) {
|
||||
if (http_response.empty()) return;
|
||||
|
||||
modified_response->clear();
|
||||
@@ -103,33 +67,52 @@ void UrlRequest::ConcatenateChunkedResponse(const std::string http_response,
|
||||
}
|
||||
}
|
||||
|
||||
int UrlRequest::GetResponse(std::string* message) {
|
||||
message->clear();
|
||||
} // namespace
|
||||
|
||||
|
||||
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;
|
||||
const int kTimeoutInMs = 3000;
|
||||
int bytes = 0;
|
||||
for (int attempts = kMaxReadAttempts; attempts > 0; --attempts) {
|
||||
memset(buffer_, 0, kHttpBufferSize);
|
||||
bytes = socket_.Read(buffer_, kHttpBufferSize, kTimeoutInMs);
|
||||
|
||||
// Keep reading until end of stream (0 bytes read) or timeout. Partial
|
||||
// buffers worth of data can and do happen, especially with OpenSSL in
|
||||
// non-blocking mode.
|
||||
while (true) {
|
||||
char read_buffer[kReadBufferSize];
|
||||
int bytes = socket_.Read(read_buffer, sizeof(read_buffer), kReadTimeoutMs);
|
||||
if (bytes > 0) {
|
||||
response.append(buffer_, bytes);
|
||||
if (bytes < static_cast<int>(kHttpBufferSize)) {
|
||||
attempts = kSingleReadAttempt;
|
||||
}
|
||||
response.append(read_buffer, bytes);
|
||||
} else if (bytes < 0) {
|
||||
LOGE("read error, errno = %d", errno);
|
||||
return false;
|
||||
} else {
|
||||
if (bytes < 0) LOGE("read error = ", errno);
|
||||
// bytes == 0 indicates nothing to read
|
||||
// end of stream.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ConcatenateChunkedResponse(response, message);
|
||||
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) {
|
||||
const std::string kHttpVersion("HTTP/1.1");
|
||||
const std::string kHttpVersion("HTTP/1.1 ");
|
||||
|
||||
int status_code = -1;
|
||||
size_t pos = response.find(kHttpVersion);
|
||||
@@ -140,79 +123,48 @@ int UrlRequest::GetStatusCode(const std::string& response) {
|
||||
return status_code;
|
||||
}
|
||||
|
||||
bool UrlRequest::PostRequestChunk(const std::string& data) {
|
||||
request_.assign("POST /");
|
||||
request_.append(socket_.resource_path());
|
||||
request_.append(" HTTP/1.1\r\n");
|
||||
request_.append("Host: ");
|
||||
request_.append(socket_.domain_name());
|
||||
request_.append("\r\nConnection: Keep-Alive\r\n");
|
||||
request_.append("Transfer-Encoding: chunked\r\n");
|
||||
request_.append("User-Agent: Widevine CDM v1.0\r\n");
|
||||
request_.append("Accept-Encoding: gzip,deflate\r\n");
|
||||
request_.append("Accept-Language: en-us,fr\r\n");
|
||||
request_.append("Accept-Charset: iso-8859-1,*,utf-8\r\n");
|
||||
request_.append("\r\n"); // empty line to terminate header
|
||||
bool UrlRequest::PostRequestWithPath(const std::string& path,
|
||||
const std::string& data) {
|
||||
std::string request;
|
||||
|
||||
// calls AppendChunkToUpload repeatedly for multiple chunks
|
||||
AppendChunkToUpload(data);
|
||||
request.append("POST ");
|
||||
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("0\r\n\r\n");
|
||||
request.append("Host: ");
|
||||
request.append(socket_.domain_name());
|
||||
request.append("\r\n");
|
||||
|
||||
socket_.Write(request_.c_str(), request_.size());
|
||||
return true;
|
||||
request.append("Connection: close\r\n");
|
||||
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) {
|
||||
if (chunk_transfer_mode_) {
|
||||
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;
|
||||
return PostRequestWithPath(socket_.resource_path(), data);
|
||||
}
|
||||
|
||||
bool UrlRequest::PostCertRequestInQueryString(const std::string& data) {
|
||||
request_.assign("POST /");
|
||||
request_.append(socket_.resource_path());
|
||||
request_.append("&signedRequest=");
|
||||
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
|
||||
std::string path = socket_.resource_path();
|
||||
path.append("&signedRequest=");
|
||||
path.append(data);
|
||||
|
||||
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;
|
||||
return PostRequestWithPath(path, "");
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
@@ -13,29 +13,22 @@ namespace wvcdm {
|
||||
// Only POST request method is implemented.
|
||||
class UrlRequest {
|
||||
public:
|
||||
UrlRequest(const std::string& url, const std::string& port,
|
||||
bool secure_connect, bool chunk_transfer_mode);
|
||||
explicit UrlRequest(const std::string& url);
|
||||
~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 PostRequest(const std::string& data);
|
||||
bool PostRequestChunk(const std::string& data);
|
||||
bool PostCertRequestInQueryString(const std::string& data);
|
||||
|
||||
bool GetResponse(std::string* message);
|
||||
static int GetStatusCode(const std::string& response);
|
||||
|
||||
private:
|
||||
static const unsigned int kHttpBufferSize = 4096;
|
||||
char buffer_[kHttpBufferSize];
|
||||
bool chunk_transfer_mode_;
|
||||
bool PostRequestWithPath(const std::string& path, const std::string& data);
|
||||
|
||||
bool is_connected_;
|
||||
std::string port_;
|
||||
std::string request_;
|
||||
HttpSocket socket_;
|
||||
std::string server_url_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(UrlRequest);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user