Source release v3.0.4
This commit is contained in:
101
CHANGELOG.md
Normal file
101
CHANGELOG.md
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
## 3.0.4 (2015-12-14)
|
||||||
|
|
||||||
|
Features:
|
||||||
|
- Enforce storage restrictions based on the license type and policy
|
||||||
|
- Updated to EME spec 2015-11-20
|
||||||
|
- Updated kPersistent to kPersistentLicense
|
||||||
|
- Updated kInvalidAccess with kTypeError and kRangeError
|
||||||
|
- Updated kOutputNotAllowed to kOutputRestricted
|
||||||
|
- Added key status kReleased
|
||||||
|
- Added new session type (kPersistentUsageRecord) used for "secure stop"
|
||||||
|
- Enabled WebM-related tests for CdmEngine
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
- Fixed OEMCrypto test bugs regarding nonce-enabled and nonce-or-entry flags
|
||||||
|
- Fixed build system bug to allow adding the static CDM library as a
|
||||||
|
dependency of another gyp static library target
|
||||||
|
- Fixed message type for service cert requests
|
||||||
|
- Fixed reporting of expiration for sessions which do not expire
|
||||||
|
- Fixed test bugs in which changing execution order caused test failures
|
||||||
|
- Fixed bug in OEMCrypto\_DeleteUsageTable in which the empty table was not
|
||||||
|
written to disk
|
||||||
|
- Fixed bug in CE CDM tests in which OEMCrypto usage table data was not
|
||||||
|
cleared between test runs, causing issues with duplicate PSTs
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.3 (2015-11-09)
|
||||||
|
|
||||||
|
Features:
|
||||||
|
- Added x86-32 build settings
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
- Fix buffer overflow in mock OEMCrypto on 32-bit systems
|
||||||
|
- Fixed OEMCrypto\_RefreshKeys return value
|
||||||
|
- Fixed OEMCrypto\_GenerateRSASignature return value
|
||||||
|
- Fixed assertions during server certificate provisioning, triggered by a
|
||||||
|
race condition
|
||||||
|
- Removed spurious error messages from CdmEngine::AddKey()
|
||||||
|
- Fixed PSS verification in iOS privacy crypto implementation
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.2 (2015-09-18)
|
||||||
|
|
||||||
|
Features:
|
||||||
|
- Updated OEMCrypto docs
|
||||||
|
- Privacy crypto implementation for iOS
|
||||||
|
- Now builds with strict warnings and warnings as errors
|
||||||
|
- Added an extra method to IEventListener to allow integration with older
|
||||||
|
versions of Chromium using prefixed EME
|
||||||
|
- *NOTE: This is temporary and will be removed in a future release*
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
- Fixed support for C++11 and clang
|
||||||
|
- Prevent renewal license when can\_renew is false
|
||||||
|
- Fixed variable-length key ID tests
|
||||||
|
- Fixed enforcement of secure buffer types for decrypt
|
||||||
|
- Fix type-casting issues with various versions of OpenSSL and BoringSSL
|
||||||
|
- Return kNotSupported when generateRequest called with non-Widevine initdata
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.1 (2015-09-11)
|
||||||
|
|
||||||
|
Features:
|
||||||
|
- Added new methods to access app parameters available on Android
|
||||||
|
- Test suite is now IPv6-ready
|
||||||
|
- Exposed IClient inheritance for Cdm interface
|
||||||
|
- Added baked-in cert support to the mock OEMCrypto
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
- Made improvements to tests for OEMCrypto and core
|
||||||
|
- Return client ID information in secure stop
|
||||||
|
- Fix multiple deletions of OEMCrypto usage table entries
|
||||||
|
- Don't delete offline licenses when a new device cert is provisioned
|
||||||
|
- Hardened BufferReader class
|
||||||
|
- Removed excess logging in PSSH parser
|
||||||
|
- Fixed iOS build issues with MD5 in DeviceFiles
|
||||||
|
- Fixed iOS build issues with protobuf\_config==target
|
||||||
|
- Fixed bugs in OEMCrypto v9 and v10 adapters
|
||||||
|
- Fixed inclusion of unit test gypis from external projects
|
||||||
|
|
||||||
|
Broken compatibility:
|
||||||
|
- Added a cancel() method to ITimer, needed for some timer implementations
|
||||||
|
|
||||||
|
|
||||||
|
## 3.0.0 (2015-06-19)
|
||||||
|
|
||||||
|
v3.0 introduced a completely new interface which is not backward compatible
|
||||||
|
with v2.x.
|
||||||
|
|
||||||
|
Features:
|
||||||
|
- Simplified, synchronous interface which mimics EME APIs
|
||||||
|
- Support for key statuses and session expiration times
|
||||||
|
- Simplified build system with fewer build-time flags
|
||||||
|
- Simplified initialization with runtime settings for client info,
|
||||||
|
log levels, and secure output modes
|
||||||
|
- Secure output modes are explicit, and individual decrypt requests can
|
||||||
|
be done in the clear (for example, for platforms with L3 audio)
|
||||||
|
- Device certificates are now required for all platforms and must be
|
||||||
|
provisioned during initialization if not present
|
||||||
|
- Simplified storage interface with more explicit methods
|
||||||
|
- New integration guide which replaces several older documents
|
||||||
|
|
||||||
BIN
README.pdf
BIN
README.pdf
Binary file not shown.
@@ -161,6 +161,8 @@
|
|||||||
'include_dirs': [
|
'include_dirs': [
|
||||||
'include',
|
'include',
|
||||||
],
|
],
|
||||||
|
},
|
||||||
|
'link_settings': {
|
||||||
'libraries': [
|
'libraries': [
|
||||||
'-lpthread',
|
'-lpthread',
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// Copyright 2015 Google Inc. All Rights Reserved.
|
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||||
// Based on the EME draft spec from 2015 June 01.
|
// Based on the EME draft spec from 2015 November 20.
|
||||||
// https://rawgit.com/w3c/encrypted-media/1cbedad/index.html
|
// https://rawgit.com/w3c/encrypted-media/1dab9e5/index.html
|
||||||
// TODO: Verify behavior and update to June 12 draft.
|
|
||||||
#ifndef WVCDM_CDM_CDM_H_
|
#ifndef WVCDM_CDM_CDM_H_
|
||||||
#define WVCDM_CDM_CDM_H_
|
#define WVCDM_CDM_CDM_H_
|
||||||
|
|
||||||
@@ -49,7 +48,9 @@ class CDM_EXPORT Cdm : public ITimerClient {
|
|||||||
// Session types defined by EME.
|
// Session types defined by EME.
|
||||||
typedef enum {
|
typedef enum {
|
||||||
kTemporary = 0,
|
kTemporary = 0,
|
||||||
kPersistent = 1,
|
kPersistentLicense = 1,
|
||||||
|
kPersistent = kPersistentLicense, // deprecated name from June 1 draft
|
||||||
|
kPersistentUsageRecord = 2,
|
||||||
} SessionType;
|
} SessionType;
|
||||||
|
|
||||||
// Message types defined by EME.
|
// Message types defined by EME.
|
||||||
@@ -67,11 +68,15 @@ class CDM_EXPORT Cdm : public ITimerClient {
|
|||||||
kSessionNotFound = 2,
|
kSessionNotFound = 2,
|
||||||
kDecryptError = 3,
|
kDecryptError = 3,
|
||||||
kNoKey = 4,
|
kNoKey = 4,
|
||||||
|
|
||||||
// These are analogous to the errors used by EME:
|
// These are analogous to the errors used by EME:
|
||||||
kInvalidAccess = 5,
|
kTypeError = 5,
|
||||||
|
kInvalidAccess = kTypeError, // deprecated name from June 1 draft
|
||||||
kNotSupported = 6,
|
kNotSupported = 6,
|
||||||
kInvalidState = 7,
|
kInvalidState = 7,
|
||||||
kQuotaExceeded = 8,
|
kQuotaExceeded = 8,
|
||||||
|
kRangeError = 9,
|
||||||
|
|
||||||
// This covers errors that we do not expect (see logs for details):
|
// This covers errors that we do not expect (see logs for details):
|
||||||
kUnexpectedError = 99999,
|
kUnexpectedError = 99999,
|
||||||
} Status;
|
} Status;
|
||||||
@@ -87,9 +92,11 @@ class CDM_EXPORT Cdm : public ITimerClient {
|
|||||||
typedef enum {
|
typedef enum {
|
||||||
kUsable = 0,
|
kUsable = 0,
|
||||||
kExpired = 1,
|
kExpired = 1,
|
||||||
kOutputNotAllowed = 2,
|
kOutputRestricted = 2,
|
||||||
|
kOutputNotAllowed = kOutputRestricted, // deprecated name from June 1 draft
|
||||||
kStatusPending = 3,
|
kStatusPending = 3,
|
||||||
kInternalError = 4,
|
kInternalError = 4,
|
||||||
|
kReleased = 5,
|
||||||
} KeyStatus;
|
} KeyStatus;
|
||||||
|
|
||||||
// These are defined by Widevine. The CDM can be configured to decrypt in
|
// These are defined by Widevine. The CDM can be configured to decrypt in
|
||||||
@@ -316,7 +323,7 @@ class CDM_EXPORT Cdm : public ITimerClient {
|
|||||||
// license server.
|
// license server.
|
||||||
// If |privacy_mode| was true in create() and setServerCertificate() is not
|
// If |privacy_mode| was true in create() and setServerCertificate() is not
|
||||||
// called, the CDM will attempt to provision a server certificate through
|
// called, the CDM will attempt to provision a server certificate through
|
||||||
// IEventListener::onMessage() with messageType == kIndividualizationRequest.
|
// IEventListener::onMessage() with messageType == kLicenseRequest.
|
||||||
// May not be called if |privacy_mode| was false.
|
// May not be called if |privacy_mode| was false.
|
||||||
virtual Status setServerCertificate(const std::string& certificate) = 0;
|
virtual Status setServerCertificate(const std::string& certificate) = 0;
|
||||||
|
|
||||||
@@ -453,13 +460,13 @@ class CDM_EXPORT Cdm : public ITimerClient {
|
|||||||
const std::string& value) = 0;
|
const std::string& value) = 0;
|
||||||
|
|
||||||
// Gets the current value in the custom app settings. If the key is
|
// Gets the current value in the custom app settings. If the key is
|
||||||
// not present, then kInvalidAccess is returned. The |key| cannot be
|
// not present, then kTypeError is returned. The |key| cannot be
|
||||||
// empty. |result| cannot be null. See setAppParameter().
|
// empty. |result| cannot be null. See setAppParameter().
|
||||||
virtual Status getAppParameter(const std::string& key,
|
virtual Status getAppParameter(const std::string& key,
|
||||||
std::string* result) = 0;
|
std::string* result) = 0;
|
||||||
|
|
||||||
// Removes the value in the custom app settings. If the key is not
|
// Removes the value in the custom app settings. If the key is not
|
||||||
// present, then kInvalidAccess is returned. The |key| cannot be empty.
|
// present, then kTypeError is returned. The |key| cannot be empty.
|
||||||
// See setAppParameter().
|
// See setAppParameter().
|
||||||
virtual Status removeAppParameter(const std::string& key) = 0;
|
virtual Status removeAppParameter(const std::string& key) = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
// Widevine CE CDM Version
|
// Widevine CE CDM Version
|
||||||
#define CDM_VERSION "v3.0.3-0-g226db8b-ce"
|
#define CDM_VERSION "v3.0.4-0-g4dee2a8-ce"
|
||||||
|
|||||||
170
cdm/src/cdm.cpp
170
cdm/src/cdm.cpp
@@ -2,6 +2,7 @@
|
|||||||
#include "cdm.h"
|
#include "cdm.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <limits.h> // LLONG_MAX
|
||||||
#include <string.h> // memcpy
|
#include <string.h> // memcpy
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -11,6 +12,7 @@
|
|||||||
#include "cdm_engine.h"
|
#include "cdm_engine.h"
|
||||||
#include "clock.h"
|
#include "clock.h"
|
||||||
#include "crypto_session.h"
|
#include "crypto_session.h"
|
||||||
|
#include "device_files.h"
|
||||||
#include "file_store.h"
|
#include "file_store.h"
|
||||||
#include "license.h"
|
#include "license.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
@@ -171,9 +173,18 @@ class CdmImpl : public Cdm,
|
|||||||
PropertySet property_set_;
|
PropertySet property_set_;
|
||||||
CdmAppParameterMap app_parameters_;
|
CdmAppParameterMap app_parameters_;
|
||||||
|
|
||||||
std::map<std::string, SessionType> new_session_types_;
|
struct SessionMetadata {
|
||||||
std::map<std::string, int64_t> session_expirations_;
|
bool callable; // EME terminology: request generated or session loaded
|
||||||
std::map<std::string, KeyStatusMap> session_key_statuses_;
|
SessionType type;
|
||||||
|
int64_t expiration;
|
||||||
|
KeyStatusMap key_statuses;
|
||||||
|
|
||||||
|
SessionMetadata()
|
||||||
|
: callable(false),
|
||||||
|
type((SessionType)-1),
|
||||||
|
expiration(0) {}
|
||||||
|
};
|
||||||
|
std::map<std::string, SessionMetadata> sessions_;
|
||||||
};
|
};
|
||||||
|
|
||||||
CdmImpl::CdmImpl(IEventListener* listener,
|
CdmImpl::CdmImpl(IEventListener* listener,
|
||||||
@@ -195,12 +206,12 @@ Cdm::Status CdmImpl::setServerCertificate(const std::string& certificate) {
|
|||||||
|
|
||||||
if (certificate.empty()) {
|
if (certificate.empty()) {
|
||||||
LOGE("An empty server certificate is invalid.");
|
LOGE("An empty server certificate is invalid.");
|
||||||
return kInvalidAccess;
|
return kTypeError;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CdmLicense::VerifySignedServiceCertificate(certificate) != NO_ERROR) {
|
if (CdmLicense::VerifySignedServiceCertificate(certificate) != NO_ERROR) {
|
||||||
LOGE("Invalid server certificate!");
|
LOGE("Invalid server certificate!");
|
||||||
return kInvalidAccess;
|
return kTypeError;
|
||||||
}
|
}
|
||||||
|
|
||||||
property_set_.set_service_certificate(certificate);
|
property_set_.set_service_certificate(certificate);
|
||||||
@@ -211,7 +222,7 @@ Cdm::Status CdmImpl::createSession(SessionType session_type,
|
|||||||
std::string* session_id) {
|
std::string* session_id) {
|
||||||
if (!session_id) {
|
if (!session_id) {
|
||||||
LOGE("Missing session ID pointer.");
|
LOGE("Missing session ID pointer.");
|
||||||
return kInvalidAccess;
|
return kTypeError;
|
||||||
}
|
}
|
||||||
// Important! The caller may pass a pre-filled string, which must be cleared
|
// Important! The caller may pass a pre-filled string, which must be cleared
|
||||||
// before being given to CdmEngine.
|
// before being given to CdmEngine.
|
||||||
@@ -219,7 +230,8 @@ Cdm::Status CdmImpl::createSession(SessionType session_type,
|
|||||||
|
|
||||||
switch (session_type) {
|
switch (session_type) {
|
||||||
case kTemporary:
|
case kTemporary:
|
||||||
case kPersistent:
|
case kPersistentLicense:
|
||||||
|
case kPersistentUsageRecord:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
LOGE("Unsupported session type: %d", session_type);
|
LOGE("Unsupported session type: %d", session_type);
|
||||||
@@ -232,7 +244,7 @@ Cdm::Status CdmImpl::createSession(SessionType session_type,
|
|||||||
NULL, session_id);
|
NULL, session_id);
|
||||||
switch (result) {
|
switch (result) {
|
||||||
case NO_ERROR:
|
case NO_ERROR:
|
||||||
new_session_types_[*session_id] = session_type;
|
sessions_[*session_id].type = session_type;
|
||||||
return kSuccess;
|
return kSuccess;
|
||||||
case NEED_PROVISIONING:
|
case NEED_PROVISIONING:
|
||||||
LOGE("A device certificate is needed.");
|
LOGE("A device certificate is needed.");
|
||||||
@@ -251,22 +263,23 @@ Cdm::Status CdmImpl::generateRequest(const std::string& session_id,
|
|||||||
return kSessionNotFound;
|
return kSessionNotFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::map<std::string, SessionType>::iterator it =
|
if (sessions_[session_id].callable) {
|
||||||
new_session_types_.find(session_id);
|
|
||||||
if (it == new_session_types_.end()) {
|
|
||||||
LOGE("Request already generated: %s", session_id.c_str());
|
LOGE("Request already generated: %s", session_id.c_str());
|
||||||
return kInvalidState;
|
return kInvalidState;
|
||||||
}
|
}
|
||||||
|
|
||||||
SessionType session_type = it->second;
|
SessionType session_type = sessions_[session_id].type;
|
||||||
CdmLicenseType license_type;
|
CdmLicenseType license_type;
|
||||||
switch (session_type) {
|
switch (session_type) {
|
||||||
case kTemporary:
|
case kTemporary:
|
||||||
license_type = kLicenseTypeStreaming;
|
license_type = kLicenseTypeTemporary;
|
||||||
break;
|
break;
|
||||||
case kPersistent:
|
case kPersistentLicense:
|
||||||
license_type = kLicenseTypeOffline;
|
license_type = kLicenseTypeOffline;
|
||||||
break;
|
break;
|
||||||
|
case kPersistentUsageRecord:
|
||||||
|
license_type = kLicenseTypeStreaming;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
LOGE("Unexpected session type: %d", session_type);
|
LOGE("Unexpected session type: %d", session_type);
|
||||||
return kUnexpectedError;
|
return kUnexpectedError;
|
||||||
@@ -285,12 +298,12 @@ Cdm::Status CdmImpl::generateRequest(const std::string& session_id,
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
LOGE("Invalid init data type: %d", init_data_type);
|
LOGE("Invalid init data type: %d", init_data_type);
|
||||||
return kInvalidAccess;
|
return kTypeError;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (init_data.empty()) {
|
if (init_data.empty()) {
|
||||||
LOGE("Empty init data is not valid.");
|
LOGE("Empty init data is not valid.");
|
||||||
return kInvalidAccess;
|
return kTypeError;
|
||||||
}
|
}
|
||||||
|
|
||||||
InitializationData init_data_obj(init_data_type_name, init_data);
|
InitializationData init_data_obj(init_data_type_name, init_data);
|
||||||
@@ -316,26 +329,27 @@ Cdm::Status CdmImpl::generateRequest(const std::string& session_id,
|
|||||||
return kUnexpectedError;
|
return kUnexpectedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
new_session_types_.erase(it);
|
sessions_[session_id].callable = true;
|
||||||
assert(key_request_type == kKeyRequestTypeInitial);
|
assert(key_request_type == kKeyRequestTypeInitial);
|
||||||
MessageType message_type = kLicenseRequest;
|
|
||||||
if (property_set_.use_privacy_mode() &&
|
if (property_set_.use_privacy_mode() &&
|
||||||
property_set_.service_certificate().empty()) {
|
property_set_.service_certificate().empty()) {
|
||||||
// We can deduce that this is a server cert request, even though CdmEgine
|
// We can deduce that this is a server cert request, even though CdmEgine
|
||||||
// cannot currently inform us of this.
|
// cannot currently inform us of this.
|
||||||
message_type = kIndividualizationRequest;
|
// Previously, we used message type kIndividiualizationRequest for this.
|
||||||
|
// The EME editor has clarified that this was a misinterpretation of the
|
||||||
|
// spec, and that this should also be kLicenseRequest.
|
||||||
LOGI("A server certificate request has been generated.");
|
LOGI("A server certificate request has been generated.");
|
||||||
} else {
|
} else {
|
||||||
LOGI("A license request has been generated.");
|
LOGI("A license request has been generated.");
|
||||||
}
|
}
|
||||||
listener_->onMessage(session_id, message_type, key_request);
|
listener_->onMessage(session_id, kLicenseRequest, key_request);
|
||||||
return kSuccess;
|
return kSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
Cdm::Status CdmImpl::load(const std::string& session_id) {
|
Cdm::Status CdmImpl::load(const std::string& session_id) {
|
||||||
if (session_id.empty()) {
|
if (session_id.empty()) {
|
||||||
LOGE("Empty session ID.");
|
LOGE("Empty session ID.");
|
||||||
return kInvalidAccess;
|
return kTypeError;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cdm_engine_.IsOpenSession(session_id)) {
|
if (cdm_engine_.IsOpenSession(session_id)) {
|
||||||
@@ -359,11 +373,37 @@ Cdm::Status CdmImpl::load(const std::string& session_id) {
|
|||||||
return kUnexpectedError;
|
return kUnexpectedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DeviceFiles f;
|
||||||
|
if (!f.Init(kSecurityLevelUnknown)) {
|
||||||
|
LOGE("Unexpected error, failed to init DeviceFiles");
|
||||||
|
return kUnexpectedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!f.LicenseExists(session_id)) {
|
||||||
|
// This might be a usage record session which needs to be loaded.
|
||||||
|
CdmKeyMessage release_message;
|
||||||
|
result = cdm_engine_.LoadUsageSession(session_id, &release_message);
|
||||||
|
if (result == LOAD_USAGE_INFO_MISSING) {
|
||||||
|
LOGE("Unable to load license: %s", session_id.c_str());
|
||||||
|
cdm_engine_.CloseSession(session_id);
|
||||||
|
return kSessionNotFound;
|
||||||
|
} else if (result != KEY_MESSAGE) {
|
||||||
|
LOGE("Unexpected error %d", result);
|
||||||
|
cdm_engine_.CloseSession(session_id);
|
||||||
|
return kUnexpectedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGI("A usage record release has been generated.");
|
||||||
|
MessageType message_type = kLicenseRelease;
|
||||||
|
listener_->onMessage(session_id, message_type, release_message);
|
||||||
|
|
||||||
|
sessions_[session_id].type = kPersistentUsageRecord;
|
||||||
|
sessions_[session_id].callable = true;
|
||||||
|
return kSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
result = cdm_engine_.RestoreKey(session_id, session_id);
|
result = cdm_engine_.RestoreKey(session_id, session_id);
|
||||||
if (result == GET_LICENSE_ERROR) {
|
if (result == GET_RELEASED_LICENSE_ERROR) {
|
||||||
LOGE("Unable to load license: %s", session_id.c_str());
|
|
||||||
return kSessionNotFound;
|
|
||||||
} else if (result == GET_RELEASED_LICENSE_ERROR) {
|
|
||||||
// This was partially removed already.
|
// This was partially removed already.
|
||||||
// The EME spec states that we should send a release message right away.
|
// The EME spec states that we should send a release message right away.
|
||||||
InitializationData empty_initialization_data;
|
InitializationData empty_initialization_data;
|
||||||
@@ -388,6 +428,8 @@ Cdm::Status CdmImpl::load(const std::string& session_id) {
|
|||||||
return kUnexpectedError;
|
return kUnexpectedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sessions_[session_id].type = kPersistentLicense;
|
||||||
|
sessions_[session_id].callable = true;
|
||||||
return kSuccess;
|
return kSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,14 +440,14 @@ Cdm::Status CdmImpl::update(const std::string& session_id,
|
|||||||
return kSessionNotFound;
|
return kSessionNotFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (new_session_types_.find(session_id) != new_session_types_.end()) {
|
if (!sessions_[session_id].callable) {
|
||||||
LOGE("Request not yet generated: %s", session_id.c_str());
|
LOGE("Request not yet generated: %s", session_id.c_str());
|
||||||
return kInvalidState;
|
return kInvalidState;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.empty()) {
|
if (response.empty()) {
|
||||||
LOGE("Empty response.");
|
LOGE("Empty response.");
|
||||||
return kInvalidAccess;
|
return kTypeError;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: If the CdmSession object recognizes that this is not the first
|
// NOTE: If the CdmSession object recognizes that this is not the first
|
||||||
@@ -444,6 +486,12 @@ Cdm::Status CdmImpl::update(const std::string& session_id,
|
|||||||
MessageType message_type = kLicenseRequest;
|
MessageType message_type = kLicenseRequest;
|
||||||
listener_->onMessage(session_id, message_type, key_request);
|
listener_->onMessage(session_id, message_type, key_request);
|
||||||
return kSuccess;
|
return kSuccess;
|
||||||
|
} else if (result == OFFLINE_LICENSE_PROHIBITED) {
|
||||||
|
LOGE("A temporary session cannot be used for a persistent license.");
|
||||||
|
return kRangeError;
|
||||||
|
} else if (result == STORAGE_PROHIBITED) {
|
||||||
|
LOGE("A temporary session cannot be used for a persistent usage records.");
|
||||||
|
return kRangeError;
|
||||||
} else if (result != KEY_ADDED) {
|
} else if (result != KEY_ADDED) {
|
||||||
LOGE("Unexpected error %d", result);
|
LOGE("Unexpected error %d", result);
|
||||||
return kUnexpectedError;
|
return kUnexpectedError;
|
||||||
@@ -457,6 +505,7 @@ Cdm::Status CdmImpl::update(const std::string& session_id,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cdm_engine_.IsReleaseSession(session_id)) {
|
if (cdm_engine_.IsReleaseSession(session_id)) {
|
||||||
|
sessions_.erase(session_id);
|
||||||
cdm_engine_.CloseSession(session_id);
|
cdm_engine_.CloseSession(session_id);
|
||||||
listener_->onRemoveComplete(session_id);
|
listener_->onRemoveComplete(session_id);
|
||||||
}
|
}
|
||||||
@@ -470,7 +519,7 @@ Cdm::Status CdmImpl::getExpiration(const std::string& session_id,
|
|||||||
return kSessionNotFound;
|
return kSessionNotFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
*expiration = session_expirations_[session_id];
|
*expiration = sessions_[session_id].expiration;
|
||||||
return kSuccess;
|
return kSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,14 +530,14 @@ Cdm::Status CdmImpl::getKeyStatuses(const std::string& session_id,
|
|||||||
return kSessionNotFound;
|
return kSessionNotFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
*key_statuses = session_key_statuses_[session_id];
|
*key_statuses = sessions_[session_id].key_statuses;
|
||||||
return kSuccess;
|
return kSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
Cdm::Status CdmImpl::setAppParameter(const std::string& key,
|
Cdm::Status CdmImpl::setAppParameter(const std::string& key,
|
||||||
const std::string& value) {
|
const std::string& value) {
|
||||||
if (key.empty()) {
|
if (key.empty()) {
|
||||||
return kInvalidAccess;
|
return kTypeError;
|
||||||
}
|
}
|
||||||
app_parameters_[key] = value;
|
app_parameters_[key] = value;
|
||||||
return kSuccess;
|
return kSuccess;
|
||||||
@@ -498,7 +547,7 @@ Cdm::Status CdmImpl::getAppParameter(const std::string& key,
|
|||||||
std::string* result) {
|
std::string* result) {
|
||||||
if (NULL == result || key.empty() ||
|
if (NULL == result || key.empty() ||
|
||||||
app_parameters_.find(key) == app_parameters_.end()) {
|
app_parameters_.find(key) == app_parameters_.end()) {
|
||||||
return kInvalidAccess;
|
return kTypeError;
|
||||||
}
|
}
|
||||||
*result = app_parameters_[key];
|
*result = app_parameters_[key];
|
||||||
return kSuccess;
|
return kSuccess;
|
||||||
@@ -506,11 +555,11 @@ Cdm::Status CdmImpl::getAppParameter(const std::string& key,
|
|||||||
|
|
||||||
Cdm::Status CdmImpl::removeAppParameter(const std::string& key) {
|
Cdm::Status CdmImpl::removeAppParameter(const std::string& key) {
|
||||||
if (key.empty()) {
|
if (key.empty()) {
|
||||||
return kInvalidAccess;
|
return kTypeError;
|
||||||
}
|
}
|
||||||
CdmAppParameterMap::iterator it = app_parameters_.find(key);
|
CdmAppParameterMap::iterator it = app_parameters_.find(key);
|
||||||
if (it == app_parameters_.end()) {
|
if (it == app_parameters_.end()) {
|
||||||
return kInvalidAccess;
|
return kTypeError;
|
||||||
}
|
}
|
||||||
app_parameters_.erase(it);
|
app_parameters_.erase(it);
|
||||||
return kSuccess;
|
return kSuccess;
|
||||||
@@ -532,6 +581,7 @@ Cdm::Status CdmImpl::close(const std::string& session_id) {
|
|||||||
LOGE("Unexpected error %d", result);
|
LOGE("Unexpected error %d", result);
|
||||||
return kUnexpectedError;
|
return kUnexpectedError;
|
||||||
}
|
}
|
||||||
|
sessions_.erase(session_id);
|
||||||
return kSuccess;
|
return kSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -541,20 +591,28 @@ Cdm::Status CdmImpl::remove(const std::string& session_id) {
|
|||||||
return kSessionNotFound;
|
return kSessionNotFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (new_session_types_.find(session_id) != new_session_types_.end()) {
|
if (!sessions_[session_id].callable) {
|
||||||
LOGE("Request not yet generated: %s", session_id.c_str());
|
LOGE("Request not yet generated: %s", session_id.c_str());
|
||||||
return kInvalidState;
|
return kInvalidState;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cdm_engine_.IsOfflineSession(session_id)) {
|
if (sessions_[session_id].type == kTemporary) {
|
||||||
LOGE("Not a persistent session: %s", session_id.c_str());
|
LOGE("Not a persistent session: %s", session_id.c_str());
|
||||||
return kInvalidAccess;
|
return kRangeError;
|
||||||
}
|
}
|
||||||
|
|
||||||
InitializationData empty_initialization_data;
|
InitializationData empty_initialization_data;
|
||||||
CdmKeyMessage key_request;
|
CdmKeyMessage key_request;
|
||||||
std::string ignored_server_url;
|
std::string ignored_server_url;
|
||||||
|
|
||||||
|
// Mark all keys as released ahead of generating the release request.
|
||||||
|
// When released, cdm_engine_ will mark all keys as expired, which we will
|
||||||
|
// ignore in this interface.
|
||||||
|
KeyStatusMap& map = sessions_[session_id].key_statuses;
|
||||||
|
for (KeyStatusMap::iterator it = map.begin(); it != map.end(); ++it) {
|
||||||
|
it->second = kReleased;
|
||||||
|
}
|
||||||
|
|
||||||
CdmResponseType result = cdm_engine_.GenerateKeyRequest(
|
CdmResponseType result = cdm_engine_.GenerateKeyRequest(
|
||||||
session_id, session_id, empty_initialization_data,
|
session_id, session_id, empty_initialization_data,
|
||||||
kLicenseTypeRelease, app_parameters_, &key_request, NULL,
|
kLicenseTypeRelease, app_parameters_, &key_request, NULL,
|
||||||
@@ -575,7 +633,7 @@ Cdm::Status CdmImpl::decrypt(const InputBuffer& input,
|
|||||||
const OutputBuffer& output) {
|
const OutputBuffer& output) {
|
||||||
if (input.is_encrypted && input.iv_length != 16) {
|
if (input.is_encrypted && input.iv_length != 16) {
|
||||||
LOGE("The IV must be 16 bytes long.");
|
LOGE("The IV must be 16 bytes long.");
|
||||||
return kInvalidAccess;
|
return kTypeError;
|
||||||
}
|
}
|
||||||
if (PropertiesCE::GetSecureOutputType() == kNoSecureOutput &&
|
if (PropertiesCE::GetSecureOutputType() == kNoSecureOutput &&
|
||||||
output.is_secure) {
|
output.is_secure) {
|
||||||
@@ -651,7 +709,7 @@ void CdmImpl::OnSessionRenewalNeeded(const CdmSessionId& session_id) {
|
|||||||
void CdmImpl::OnSessionKeysChange(const CdmSessionId& session_id,
|
void CdmImpl::OnSessionKeysChange(const CdmSessionId& session_id,
|
||||||
const CdmKeyStatusMap& keys_status,
|
const CdmKeyStatusMap& keys_status,
|
||||||
bool has_new_usable_key) {
|
bool has_new_usable_key) {
|
||||||
KeyStatusMap& map = session_key_statuses_[session_id];
|
KeyStatusMap& map = sessions_[session_id].key_statuses;
|
||||||
|
|
||||||
CdmKeyStatusMap::const_iterator it;
|
CdmKeyStatusMap::const_iterator it;
|
||||||
for (it = keys_status.begin(); it != keys_status.end(); ++it) {
|
for (it = keys_status.begin(); it != keys_status.end(); ++it) {
|
||||||
@@ -659,11 +717,18 @@ void CdmImpl::OnSessionKeysChange(const CdmSessionId& session_id,
|
|||||||
case kKeyStatusUsable:
|
case kKeyStatusUsable:
|
||||||
map[it->first] = kUsable;
|
map[it->first] = kUsable;
|
||||||
break;
|
break;
|
||||||
case kKeyStatusExpired:
|
case kKeyStatusExpired: {
|
||||||
map[it->first] = kExpired;
|
KeyStatusMap::const_iterator it_old = map.find(it->first);
|
||||||
|
if (it_old != map.end() && it_old->second == kReleased) {
|
||||||
|
// This key has already been marked as "released".
|
||||||
|
// Ignore the internal "expired" status.
|
||||||
|
} else {
|
||||||
|
map[it->first] = kExpired;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case kKeyStatusOutputNotAllowed:
|
case kKeyStatusOutputNotAllowed:
|
||||||
map[it->first] = kOutputNotAllowed;
|
map[it->first] = kOutputRestricted;
|
||||||
break;
|
break;
|
||||||
case kKeyStatusPending:
|
case kKeyStatusPending:
|
||||||
map[it->first] = kStatusPending;
|
map[it->first] = kStatusPending;
|
||||||
@@ -683,7 +748,12 @@ void CdmImpl::OnSessionKeysChange(const CdmSessionId& session_id,
|
|||||||
|
|
||||||
void CdmImpl::OnExpirationUpdate(const CdmSessionId& session_id,
|
void CdmImpl::OnExpirationUpdate(const CdmSessionId& session_id,
|
||||||
int64_t new_expiry_time_seconds) {
|
int64_t new_expiry_time_seconds) {
|
||||||
session_expirations_[session_id] = new_expiry_time_seconds * 1000;
|
// "Never expires" in core is LLONG_MAX. In the CDM API, it's -1.
|
||||||
|
if (new_expiry_time_seconds == LLONG_MAX) {
|
||||||
|
sessions_[session_id].expiration = -1;
|
||||||
|
} else {
|
||||||
|
sessions_[session_id].expiration = new_expiry_time_seconds * 1000;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -728,24 +798,24 @@ Cdm::Status Cdm::initialize(
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
LOGE("Invalid output type!");
|
LOGE("Invalid output type!");
|
||||||
return kInvalidAccess;
|
return kTypeError;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client_info.product_name.empty() ||
|
if (client_info.product_name.empty() ||
|
||||||
client_info.company_name.empty() ||
|
client_info.company_name.empty() ||
|
||||||
client_info.model_name.empty()) {
|
client_info.model_name.empty()) {
|
||||||
LOGE("Client info requires product_name, company_name, model_name!");
|
LOGE("Client info requires product_name, company_name, model_name!");
|
||||||
return kInvalidAccess;
|
return kTypeError;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!storage || !clock || !timer) {
|
if (!storage || !clock || !timer) {
|
||||||
LOGE("All interfaces are required!");
|
LOGE("All interfaces are required!");
|
||||||
return kInvalidAccess;
|
return kTypeError;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!device_certificate_request) {
|
if (!device_certificate_request) {
|
||||||
LOGE("Device certificate request pointer is required!");
|
LOGE("Device certificate request pointer is required!");
|
||||||
return kInvalidAccess;
|
return kTypeError;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Our enum values match those in core/include/log.h
|
// Our enum values match those in core/include/log.h
|
||||||
@@ -811,7 +881,7 @@ Cdm::Status Cdm::DeviceCertificateRequest::acceptReply(
|
|||||||
const std::string& reply) {
|
const std::string& reply) {
|
||||||
if (!host.provisioning_engine) {
|
if (!host.provisioning_engine) {
|
||||||
LOGE("Provisioning reply received while not in a provisioning state!");
|
LOGE("Provisioning reply received while not in a provisioning state!");
|
||||||
return kInvalidAccess;
|
return kTypeError;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string empty_origin;
|
std::string empty_origin;
|
||||||
@@ -901,7 +971,11 @@ void File::Close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool File::Exists(const std::string& file_path) {
|
bool File::Exists(const std::string& file_path) {
|
||||||
return host.storage->exists(file_path);
|
// An empty path is the "base directory" for CE CDM's file storage.
|
||||||
|
// Therefore, it should always be seen as existing.
|
||||||
|
// If it ever does not exist, CdmEngine detects this as a "factory reset"
|
||||||
|
// and wipes out all usage table data.
|
||||||
|
return file_path.empty() || host.storage->exists(file_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool File::Remove(const std::string& file_path) {
|
bool File::Remove(const std::string& file_path) {
|
||||||
|
|||||||
@@ -142,6 +142,11 @@ class CdmTest : public Test,
|
|||||||
// Clear anything stored, load default device cert.
|
// Clear anything stored, load default device cert.
|
||||||
g_host->Reset();
|
g_host->Reset();
|
||||||
|
|
||||||
|
// Clear anything stored by OEMCrypto.
|
||||||
|
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Initialize());
|
||||||
|
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_DeleteUsageTable());
|
||||||
|
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Terminate());
|
||||||
|
|
||||||
// Reinit the library.
|
// Reinit the library.
|
||||||
Cdm::DeviceCertificateRequest cert_request;
|
Cdm::DeviceCertificateRequest cert_request;
|
||||||
Cdm::Status status = Cdm::initialize(
|
Cdm::Status status = Cdm::initialize(
|
||||||
@@ -230,61 +235,40 @@ class CdmTest : public Test,
|
|||||||
if (ok) ASSERT_EQ(expected_status_code, status_code);
|
if (ok) ASSERT_EQ(expected_status_code, status_code);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CreateTemporarySessionAndGenerateRequest(std::string* session_id,
|
void CreateSessionAndGenerateRequest(Cdm::SessionType session_type,
|
||||||
std::string* message) {
|
std::string* session_id,
|
||||||
Cdm::Status status = cdm_->createSession(Cdm::kTemporary, session_id);
|
std::string* message) {
|
||||||
|
Cdm::Status status = cdm_->createSession(session_type, session_id);
|
||||||
ASSERT_EQ(Cdm::kSuccess, status);
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
|
|
||||||
|
std::string init_data;
|
||||||
|
if (session_type == Cdm::kTemporary) {
|
||||||
|
init_data = kCencInitData;
|
||||||
|
} else {
|
||||||
|
init_data = kCencPersistentInitData;
|
||||||
|
}
|
||||||
|
|
||||||
EXPECT_CALL(*this, onMessage(*session_id, Cdm::kLicenseRequest, _)).
|
EXPECT_CALL(*this, onMessage(*session_id, Cdm::kLicenseRequest, _)).
|
||||||
WillOnce(SaveArg<2>(message));
|
WillOnce(SaveArg<2>(message));
|
||||||
status = cdm_->generateRequest(*session_id, Cdm::kCenc, kCencInitData);
|
status = cdm_->generateRequest(*session_id, Cdm::kCenc, init_data);
|
||||||
ASSERT_EQ(Cdm::kSuccess, status);
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
Mock::VerifyAndClear(this);
|
Mock::VerifyAndClear(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CreatePersistentSessionAndGenerateRequest(std::string* session_id,
|
void CreateSessionAndFetchLicense(Cdm::SessionType session_type,
|
||||||
std::string* message) {
|
std::string* session_id,
|
||||||
Cdm::Status status = cdm_->createSession(Cdm::kPersistent, session_id);
|
std::string* response) {
|
||||||
ASSERT_EQ(Cdm::kSuccess, status);
|
|
||||||
|
|
||||||
EXPECT_CALL(*this, onMessage(*session_id, Cdm::kLicenseRequest, _)).
|
|
||||||
WillOnce(SaveArg<2>(message));
|
|
||||||
status = cdm_->generateRequest(*session_id, Cdm::kCenc,
|
|
||||||
kCencPersistentInitData);
|
|
||||||
ASSERT_EQ(Cdm::kSuccess, status);
|
|
||||||
Mock::VerifyAndClear(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CreateTemporarySessionAndFetchLicense(std::string* session_id,
|
|
||||||
std::string* response) {
|
|
||||||
std::string message;
|
std::string message;
|
||||||
ASSERT_NO_FATAL_FAILURE(CreateTemporarySessionAndGenerateRequest(
|
ASSERT_NO_FATAL_FAILURE(CreateSessionAndGenerateRequest(
|
||||||
session_id, &message));
|
session_type, session_id, &message));
|
||||||
FetchLicense(message, response);
|
FetchLicense(message, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CreatePersistentSessionAndFetchLicense(std::string* session_id,
|
void CreateSessionAndUpdate(Cdm::SessionType session_type,
|
||||||
std::string* response) {
|
std::string* session_id) {
|
||||||
std::string message;
|
|
||||||
ASSERT_NO_FATAL_FAILURE(CreatePersistentSessionAndGenerateRequest(
|
|
||||||
session_id, &message));
|
|
||||||
FetchLicense(message, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CreateTemporarySessionAndUpdate(std::string* session_id) {
|
|
||||||
std::string response;
|
std::string response;
|
||||||
ASSERT_NO_FATAL_FAILURE(CreateTemporarySessionAndFetchLicense(
|
ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense(
|
||||||
session_id, &response));
|
session_type, session_id, &response));
|
||||||
EXPECT_CALL(*this, onKeyStatusesChange(*session_id));
|
|
||||||
Cdm::Status status = cdm_->update(*session_id, response);
|
|
||||||
ASSERT_EQ(Cdm::kSuccess, status);
|
|
||||||
Mock::VerifyAndClear(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CreatePersistentSessionAndUpdate(std::string* session_id) {
|
|
||||||
std::string response;
|
|
||||||
ASSERT_NO_FATAL_FAILURE(CreatePersistentSessionAndFetchLicense(
|
|
||||||
session_id, &response));
|
|
||||||
EXPECT_CALL(*this, onKeyStatusesChange(*session_id));
|
EXPECT_CALL(*this, onKeyStatusesChange(*session_id));
|
||||||
Cdm::Status status = cdm_->update(*session_id, response);
|
Cdm::Status status = cdm_->update(*session_id, response);
|
||||||
ASSERT_EQ(Cdm::kSuccess, status);
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
@@ -352,7 +336,7 @@ TEST_F(CdmTest, Initialize) {
|
|||||||
static_cast<Cdm::SecureOutputType>(-1), PropertiesCE::GetClientInfo(),
|
static_cast<Cdm::SecureOutputType>(-1), PropertiesCE::GetClientInfo(),
|
||||||
g_host, g_host, g_host, &cert_request,
|
g_host, g_host, g_host, &cert_request,
|
||||||
static_cast<Cdm::LogLevel>(g_cutoff));
|
static_cast<Cdm::LogLevel>(g_cutoff));
|
||||||
EXPECT_EQ(Cdm::kInvalidAccess, status);
|
EXPECT_EQ(Cdm::kTypeError, status);
|
||||||
|
|
||||||
// Try with various client info properties missing.
|
// Try with various client info properties missing.
|
||||||
Cdm::ClientInfo working_client_info = PropertiesCE::GetClientInfo();
|
Cdm::ClientInfo working_client_info = PropertiesCE::GetClientInfo();
|
||||||
@@ -364,7 +348,7 @@ TEST_F(CdmTest, Initialize) {
|
|||||||
Cdm::kNoSecureOutput, broken_client_info,
|
Cdm::kNoSecureOutput, broken_client_info,
|
||||||
g_host, g_host, g_host, &cert_request,
|
g_host, g_host, g_host, &cert_request,
|
||||||
static_cast<Cdm::LogLevel>(g_cutoff));
|
static_cast<Cdm::LogLevel>(g_cutoff));
|
||||||
EXPECT_EQ(Cdm::kInvalidAccess, status);
|
EXPECT_EQ(Cdm::kTypeError, status);
|
||||||
|
|
||||||
broken_client_info = working_client_info;
|
broken_client_info = working_client_info;
|
||||||
broken_client_info.company_name.clear();
|
broken_client_info.company_name.clear();
|
||||||
@@ -372,7 +356,7 @@ TEST_F(CdmTest, Initialize) {
|
|||||||
Cdm::kNoSecureOutput, broken_client_info,
|
Cdm::kNoSecureOutput, broken_client_info,
|
||||||
g_host, g_host, g_host, &cert_request,
|
g_host, g_host, g_host, &cert_request,
|
||||||
static_cast<Cdm::LogLevel>(g_cutoff));
|
static_cast<Cdm::LogLevel>(g_cutoff));
|
||||||
EXPECT_EQ(Cdm::kInvalidAccess, status);
|
EXPECT_EQ(Cdm::kTypeError, status);
|
||||||
|
|
||||||
broken_client_info = working_client_info;
|
broken_client_info = working_client_info;
|
||||||
broken_client_info.device_name.clear(); // Not required
|
broken_client_info.device_name.clear(); // Not required
|
||||||
@@ -388,7 +372,7 @@ TEST_F(CdmTest, Initialize) {
|
|||||||
Cdm::kNoSecureOutput, broken_client_info,
|
Cdm::kNoSecureOutput, broken_client_info,
|
||||||
g_host, g_host, g_host, &cert_request,
|
g_host, g_host, g_host, &cert_request,
|
||||||
static_cast<Cdm::LogLevel>(g_cutoff));
|
static_cast<Cdm::LogLevel>(g_cutoff));
|
||||||
EXPECT_EQ(Cdm::kInvalidAccess, status);
|
EXPECT_EQ(Cdm::kTypeError, status);
|
||||||
|
|
||||||
broken_client_info = working_client_info;
|
broken_client_info = working_client_info;
|
||||||
broken_client_info.arch_name.clear(); // Not required
|
broken_client_info.arch_name.clear(); // Not required
|
||||||
@@ -411,25 +395,25 @@ TEST_F(CdmTest, Initialize) {
|
|||||||
Cdm::kNoSecureOutput, working_client_info,
|
Cdm::kNoSecureOutput, working_client_info,
|
||||||
NULL, g_host, g_host, &cert_request,
|
NULL, g_host, g_host, &cert_request,
|
||||||
static_cast<Cdm::LogLevel>(g_cutoff));
|
static_cast<Cdm::LogLevel>(g_cutoff));
|
||||||
EXPECT_EQ(Cdm::kInvalidAccess, status);
|
EXPECT_EQ(Cdm::kTypeError, status);
|
||||||
|
|
||||||
status = Cdm::initialize(
|
status = Cdm::initialize(
|
||||||
Cdm::kNoSecureOutput, working_client_info,
|
Cdm::kNoSecureOutput, working_client_info,
|
||||||
g_host, NULL, g_host, &cert_request,
|
g_host, NULL, g_host, &cert_request,
|
||||||
static_cast<Cdm::LogLevel>(g_cutoff));
|
static_cast<Cdm::LogLevel>(g_cutoff));
|
||||||
EXPECT_EQ(Cdm::kInvalidAccess, status);
|
EXPECT_EQ(Cdm::kTypeError, status);
|
||||||
|
|
||||||
status = Cdm::initialize(
|
status = Cdm::initialize(
|
||||||
Cdm::kNoSecureOutput, working_client_info,
|
Cdm::kNoSecureOutput, working_client_info,
|
||||||
g_host, g_host, NULL, &cert_request,
|
g_host, g_host, NULL, &cert_request,
|
||||||
static_cast<Cdm::LogLevel>(g_cutoff));
|
static_cast<Cdm::LogLevel>(g_cutoff));
|
||||||
EXPECT_EQ(Cdm::kInvalidAccess, status);
|
EXPECT_EQ(Cdm::kTypeError, status);
|
||||||
|
|
||||||
status = Cdm::initialize(
|
status = Cdm::initialize(
|
||||||
Cdm::kNoSecureOutput, working_client_info,
|
Cdm::kNoSecureOutput, working_client_info,
|
||||||
g_host, g_host, g_host, NULL,
|
g_host, g_host, g_host, NULL,
|
||||||
static_cast<Cdm::LogLevel>(g_cutoff));
|
static_cast<Cdm::LogLevel>(g_cutoff));
|
||||||
EXPECT_EQ(Cdm::kInvalidAccess, status);
|
EXPECT_EQ(Cdm::kTypeError, status);
|
||||||
|
|
||||||
// One last init with everything correct and working.
|
// One last init with everything correct and working.
|
||||||
status = Cdm::initialize(
|
status = Cdm::initialize(
|
||||||
@@ -502,11 +486,11 @@ TEST_F(CdmTest, SetServerCertificate) {
|
|||||||
|
|
||||||
// It is invalid to set an empty cert.
|
// It is invalid to set an empty cert.
|
||||||
status = cdm_->setServerCertificate("");
|
status = cdm_->setServerCertificate("");
|
||||||
EXPECT_EQ(Cdm::kInvalidAccess, status);
|
EXPECT_EQ(Cdm::kTypeError, status);
|
||||||
|
|
||||||
// It is invalid to set a malformed cert.
|
// It is invalid to set a malformed cert.
|
||||||
status = cdm_->setServerCertificate("asdf");
|
status = cdm_->setServerCertificate("asdf");
|
||||||
EXPECT_EQ(Cdm::kInvalidAccess, status);
|
EXPECT_EQ(Cdm::kTypeError, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(CdmTest, CreateSession) {
|
TEST_F(CdmTest, CreateSession) {
|
||||||
@@ -524,12 +508,12 @@ TEST_F(CdmTest, CreateSession) {
|
|||||||
EXPECT_NE(original_session_id, session_id);
|
EXPECT_NE(original_session_id, session_id);
|
||||||
|
|
||||||
// Create a persistent session.
|
// Create a persistent session.
|
||||||
status = cdm_->createSession(Cdm::kPersistent, &session_id);
|
status = cdm_->createSession(Cdm::kPersistentLicense, &session_id);
|
||||||
EXPECT_EQ(Cdm::kSuccess, status);
|
EXPECT_EQ(Cdm::kSuccess, status);
|
||||||
|
|
||||||
// Try a NULL pointer for session ID.
|
// Try a NULL pointer for session ID.
|
||||||
status = cdm_->createSession(Cdm::kTemporary, NULL);
|
status = cdm_->createSession(Cdm::kTemporary, NULL);
|
||||||
EXPECT_EQ(Cdm::kInvalidAccess, status);
|
EXPECT_EQ(Cdm::kTypeError, status);
|
||||||
|
|
||||||
// Try a bogus session type.
|
// Try a bogus session type.
|
||||||
status = cdm_->createSession(kBogusSessionType, &session_id);
|
status = cdm_->createSession(kBogusSessionType, &session_id);
|
||||||
@@ -574,7 +558,7 @@ TEST_F(CdmTest, GenerateRequest) {
|
|||||||
ASSERT_EQ(Cdm::kSuccess, status);
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(0);
|
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(0);
|
||||||
status = cdm_->generateRequest(session_id, kBogusInitDataType, "asdf");
|
status = cdm_->generateRequest(session_id, kBogusInitDataType, "asdf");
|
||||||
EXPECT_EQ(Cdm::kInvalidAccess, status);
|
EXPECT_EQ(Cdm::kTypeError, status);
|
||||||
Mock::VerifyAndClear(this);
|
Mock::VerifyAndClear(this);
|
||||||
|
|
||||||
// This same session should still be usable with a supported init data type
|
// This same session should still be usable with a supported init data type
|
||||||
@@ -589,7 +573,7 @@ TEST_F(CdmTest, GenerateRequest) {
|
|||||||
ASSERT_EQ(Cdm::kSuccess, status);
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(0);
|
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(0);
|
||||||
status = cdm_->generateRequest(session_id, Cdm::kCenc, "");
|
status = cdm_->generateRequest(session_id, Cdm::kCenc, "");
|
||||||
EXPECT_EQ(Cdm::kInvalidAccess, status);
|
EXPECT_EQ(Cdm::kTypeError, status);
|
||||||
Mock::VerifyAndClear(this);
|
Mock::VerifyAndClear(this);
|
||||||
|
|
||||||
// Try to pass invalid CENC init data.
|
// Try to pass invalid CENC init data.
|
||||||
@@ -619,8 +603,8 @@ TEST_F(CdmTest, GenerateRequest) {
|
|||||||
TEST_F(CdmTest, Update) {
|
TEST_F(CdmTest, Update) {
|
||||||
std::string session_id;
|
std::string session_id;
|
||||||
std::string message;
|
std::string message;
|
||||||
ASSERT_NO_FATAL_FAILURE(CreateTemporarySessionAndGenerateRequest(
|
ASSERT_NO_FATAL_FAILURE(CreateSessionAndGenerateRequest(
|
||||||
&session_id, &message));
|
Cdm::kTemporary, &session_id, &message));
|
||||||
|
|
||||||
// Acquire a license.
|
// Acquire a license.
|
||||||
std::string response;
|
std::string response;
|
||||||
@@ -638,7 +622,7 @@ TEST_F(CdmTest, Update) {
|
|||||||
|
|
||||||
// Try updating with an empty response.
|
// Try updating with an empty response.
|
||||||
status = cdm_->update(session_id, "");
|
status = cdm_->update(session_id, "");
|
||||||
EXPECT_EQ(Cdm::kInvalidAccess, status);
|
EXPECT_EQ(Cdm::kTypeError, status);
|
||||||
|
|
||||||
// Create a new session and try updating before generating a request.
|
// Create a new session and try updating before generating a request.
|
||||||
status = cdm_->createSession(Cdm::kTemporary, &session_id);
|
status = cdm_->createSession(Cdm::kTemporary, &session_id);
|
||||||
@@ -675,8 +659,8 @@ TEST_F(CdmTest, Close) {
|
|||||||
TEST_F(CdmTest, LoadTemporary) {
|
TEST_F(CdmTest, LoadTemporary) {
|
||||||
std::string session_id;
|
std::string session_id;
|
||||||
std::string response;
|
std::string response;
|
||||||
ASSERT_NO_FATAL_FAILURE(CreateTemporarySessionAndFetchLicense(
|
ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense(
|
||||||
&session_id, &response));
|
Cdm::kTemporary, &session_id, &response));
|
||||||
|
|
||||||
// Update the temporary session.
|
// Update the temporary session.
|
||||||
EXPECT_CALL(*this, onKeyStatusesChange(session_id));
|
EXPECT_CALL(*this, onKeyStatusesChange(session_id));
|
||||||
@@ -696,8 +680,8 @@ TEST_F(CdmTest, LoadTemporary) {
|
|||||||
TEST_F(CdmTest, LoadPersistent) {
|
TEST_F(CdmTest, LoadPersistent) {
|
||||||
std::string session_id;
|
std::string session_id;
|
||||||
std::string response;
|
std::string response;
|
||||||
ASSERT_NO_FATAL_FAILURE(CreatePersistentSessionAndFetchLicense(
|
ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense(
|
||||||
&session_id, &response));
|
Cdm::kPersistentLicense, &session_id, &response));
|
||||||
|
|
||||||
// Update the persistent session.
|
// Update the persistent session.
|
||||||
EXPECT_CALL(*this, onKeyStatusesChange(session_id));
|
EXPECT_CALL(*this, onKeyStatusesChange(session_id));
|
||||||
@@ -731,6 +715,49 @@ TEST_F(CdmTest, LoadPersistent) {
|
|||||||
Mock::VerifyAndClear(this);
|
Mock::VerifyAndClear(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(CdmTest, LoadUsageRecord) {
|
||||||
|
std::string session_id;
|
||||||
|
std::string response;
|
||||||
|
ASSERT_NO_FATAL_FAILURE(CreateSessionAndFetchLicense(
|
||||||
|
Cdm::kPersistentUsageRecord, &session_id, &response));
|
||||||
|
|
||||||
|
// Update the session.
|
||||||
|
EXPECT_CALL(*this, onKeyStatusesChange(session_id));
|
||||||
|
Cdm::Status status = cdm_->update(session_id, response);
|
||||||
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
|
Mock::VerifyAndClear(this);
|
||||||
|
|
||||||
|
// Should be able to load the session again after closing it.
|
||||||
|
status = cdm_->close(session_id);
|
||||||
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
|
|
||||||
|
// There should be no usable keys after loading this session.
|
||||||
|
EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0);
|
||||||
|
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _));
|
||||||
|
status = cdm_->load(session_id);
|
||||||
|
EXPECT_EQ(Cdm::kSuccess, status);
|
||||||
|
Mock::VerifyAndClear(this);
|
||||||
|
|
||||||
|
// Should be able to load the session again after recreating the CDM.
|
||||||
|
ASSERT_NO_FATAL_FAILURE(RecreateCdm(true /* privacy_mode */));
|
||||||
|
ASSERT_NO_FATAL_FAILURE(SetDefaultServerCertificate());
|
||||||
|
EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0);
|
||||||
|
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _));
|
||||||
|
status = cdm_->load(session_id);
|
||||||
|
EXPECT_EQ(Cdm::kSuccess, status);
|
||||||
|
Mock::VerifyAndClear(this);
|
||||||
|
|
||||||
|
// Should not be able to load the session again clearing storage.
|
||||||
|
status = cdm_->close(session_id);
|
||||||
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
|
g_host->Reset();
|
||||||
|
EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0);
|
||||||
|
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).Times(0);
|
||||||
|
status = cdm_->load(session_id);
|
||||||
|
EXPECT_EQ(Cdm::kSessionNotFound, status);
|
||||||
|
Mock::VerifyAndClear(this);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(CdmTest, LoadBogus) {
|
TEST_F(CdmTest, LoadBogus) {
|
||||||
EXPECT_CALL(*this, onKeyStatusesChange(_)).Times(0);
|
EXPECT_CALL(*this, onKeyStatusesChange(_)).Times(0);
|
||||||
Cdm::Status status = cdm_->load(kBogusSessionId);
|
Cdm::Status status = cdm_->load(kBogusSessionId);
|
||||||
@@ -739,7 +766,7 @@ TEST_F(CdmTest, LoadBogus) {
|
|||||||
|
|
||||||
TEST_F(CdmTest, GetKeyStatuses) {
|
TEST_F(CdmTest, GetKeyStatuses) {
|
||||||
std::string session_id;
|
std::string session_id;
|
||||||
ASSERT_NO_FATAL_FAILURE(CreateTemporarySessionAndUpdate(&session_id));
|
ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(Cdm::kTemporary, &session_id));
|
||||||
|
|
||||||
// We should be able to query status and see a usable key.
|
// We should be able to query status and see a usable key.
|
||||||
Cdm::KeyStatusMap map;
|
Cdm::KeyStatusMap map;
|
||||||
@@ -773,7 +800,7 @@ TEST_F(CdmTest, GetKeyStatuses) {
|
|||||||
|
|
||||||
TEST_F(CdmTest, GetExpiration) {
|
TEST_F(CdmTest, GetExpiration) {
|
||||||
std::string session_id;
|
std::string session_id;
|
||||||
ASSERT_NO_FATAL_FAILURE(CreateTemporarySessionAndUpdate(&session_id));
|
ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(Cdm::kTemporary, &session_id));
|
||||||
|
|
||||||
// We should be able to query expiration and get a value in the future.
|
// We should be able to query expiration and get a value in the future.
|
||||||
int64_t expiration;
|
int64_t expiration;
|
||||||
@@ -805,7 +832,8 @@ TEST_F(CdmTest, GetExpiration) {
|
|||||||
|
|
||||||
TEST_F(CdmTest, Remove) {
|
TEST_F(CdmTest, Remove) {
|
||||||
std::string session_id;
|
std::string session_id;
|
||||||
ASSERT_NO_FATAL_FAILURE(CreatePersistentSessionAndUpdate(&session_id));
|
ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(
|
||||||
|
Cdm::kPersistentLicense, &session_id));
|
||||||
|
|
||||||
// Remove the session. This causes a release message to be generated.
|
// Remove the session. This causes a release message to be generated.
|
||||||
std::string message;
|
std::string message;
|
||||||
@@ -820,7 +848,7 @@ TEST_F(CdmTest, Remove) {
|
|||||||
Cdm::KeyStatusMap map;
|
Cdm::KeyStatusMap map;
|
||||||
status = cdm_->getKeyStatuses(session_id, &map);
|
status = cdm_->getKeyStatuses(session_id, &map);
|
||||||
ASSERT_EQ(Cdm::kSuccess, status);
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
EXPECT_EQ(Cdm::kExpired, map.begin()->second);
|
EXPECT_EQ(Cdm::kReleased, map.begin()->second);
|
||||||
|
|
||||||
// Post the release message to the license server.
|
// Post the release message to the license server.
|
||||||
std::string response;
|
std::string response;
|
||||||
@@ -843,20 +871,56 @@ TEST_F(CdmTest, Remove) {
|
|||||||
EXPECT_EQ(Cdm::kSessionNotFound, status);
|
EXPECT_EQ(Cdm::kSessionNotFound, status);
|
||||||
|
|
||||||
// Try a new session.
|
// Try a new session.
|
||||||
status = cdm_->createSession(Cdm::kPersistent, &session_id);
|
status = cdm_->createSession(Cdm::kPersistentLicense, &session_id);
|
||||||
ASSERT_EQ(Cdm::kSuccess, status);
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
status = cdm_->remove(session_id);
|
status = cdm_->remove(session_id);
|
||||||
EXPECT_EQ(Cdm::kInvalidState, status);
|
EXPECT_EQ(Cdm::kInvalidState, status);
|
||||||
|
|
||||||
// Try a temporary session.
|
// Try a temporary session.
|
||||||
ASSERT_NO_FATAL_FAILURE(CreateTemporarySessionAndUpdate(&session_id));
|
ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(Cdm::kTemporary, &session_id));
|
||||||
status = cdm_->remove(session_id);
|
status = cdm_->remove(session_id);
|
||||||
EXPECT_EQ(Cdm::kInvalidAccess, status);
|
EXPECT_EQ(Cdm::kRangeError, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CdmTest, RemoveUsageRecord) {
|
||||||
|
std::string session_id;
|
||||||
|
ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(
|
||||||
|
Cdm::kPersistentUsageRecord, &session_id));
|
||||||
|
|
||||||
|
// Remove the session. This causes a release message to be generated.
|
||||||
|
std::string message;
|
||||||
|
EXPECT_CALL(*this, onKeyStatusesChange(session_id));
|
||||||
|
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).WillOnce(
|
||||||
|
SaveArg<2>(&message));
|
||||||
|
Cdm::Status status = cdm_->remove(session_id);
|
||||||
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
|
Mock::VerifyAndClear(this);
|
||||||
|
|
||||||
|
// The keys should already be unusable.
|
||||||
|
Cdm::KeyStatusMap map;
|
||||||
|
status = cdm_->getKeyStatuses(session_id, &map);
|
||||||
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
|
EXPECT_EQ(Cdm::kReleased, map.begin()->second);
|
||||||
|
|
||||||
|
// Post the release message to the license server.
|
||||||
|
std::string response;
|
||||||
|
ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response));
|
||||||
|
|
||||||
|
// Update the session.
|
||||||
|
EXPECT_CALL(*this, onRemoveComplete(session_id));
|
||||||
|
status = cdm_->update(session_id, response);
|
||||||
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
|
Mock::VerifyAndClear(this);
|
||||||
|
|
||||||
|
// The session is now completely gone.
|
||||||
|
status = cdm_->close(session_id);
|
||||||
|
ASSERT_EQ(Cdm::kSessionNotFound, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(CdmTest, RemoveIncomplete) {
|
TEST_F(CdmTest, RemoveIncomplete) {
|
||||||
std::string session_id;
|
std::string session_id;
|
||||||
ASSERT_NO_FATAL_FAILURE(CreatePersistentSessionAndUpdate(&session_id));
|
ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(
|
||||||
|
Cdm::kPersistentLicense, &session_id));
|
||||||
|
|
||||||
// Remove the session. This causes a release message to be generated.
|
// Remove the session. This causes a release message to be generated.
|
||||||
std::string message;
|
std::string message;
|
||||||
@@ -872,7 +936,64 @@ TEST_F(CdmTest, RemoveIncomplete) {
|
|||||||
status = cdm_->getKeyStatuses(session_id, &map);
|
status = cdm_->getKeyStatuses(session_id, &map);
|
||||||
ASSERT_EQ(Cdm::kSuccess, status);
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
ASSERT_FALSE(map.empty());
|
ASSERT_FALSE(map.empty());
|
||||||
EXPECT_EQ(Cdm::kExpired, map.begin()->second);
|
EXPECT_EQ(Cdm::kReleased, map.begin()->second);
|
||||||
|
|
||||||
|
// Recreate the CDM.
|
||||||
|
ASSERT_NO_FATAL_FAILURE(RecreateCdm(true /* privacy_mode */));
|
||||||
|
ASSERT_NO_FATAL_FAILURE(SetDefaultServerCertificate());
|
||||||
|
|
||||||
|
// Load the partially removed session, which will immediately generate a
|
||||||
|
// release message.
|
||||||
|
message.clear();
|
||||||
|
EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0);
|
||||||
|
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).WillOnce(
|
||||||
|
SaveArg<2>(&message));
|
||||||
|
status = cdm_->load(session_id);
|
||||||
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
|
ASSERT_FALSE(message.empty());
|
||||||
|
Mock::VerifyAndClear(this);
|
||||||
|
|
||||||
|
// This session has no keys.
|
||||||
|
status = cdm_->getKeyStatuses(session_id, &map);
|
||||||
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
|
EXPECT_TRUE(map.empty());
|
||||||
|
|
||||||
|
// Post the release message to the license server.
|
||||||
|
std::string response;
|
||||||
|
ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response));
|
||||||
|
|
||||||
|
// Update the session.
|
||||||
|
EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0);
|
||||||
|
EXPECT_CALL(*this, onRemoveComplete(session_id));
|
||||||
|
status = cdm_->update(session_id, response);
|
||||||
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
|
Mock::VerifyAndClear(this);
|
||||||
|
|
||||||
|
// The session is now completely gone.
|
||||||
|
status = cdm_->load(session_id);
|
||||||
|
ASSERT_EQ(Cdm::kSessionNotFound, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CdmTest, RemoveUsageRecordIncomplete) {
|
||||||
|
std::string session_id;
|
||||||
|
ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(
|
||||||
|
Cdm::kPersistentUsageRecord, &session_id));
|
||||||
|
|
||||||
|
// Remove the session. This causes a release message to be generated.
|
||||||
|
std::string message;
|
||||||
|
EXPECT_CALL(*this, onKeyStatusesChange(session_id));
|
||||||
|
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRelease, _)).WillOnce(
|
||||||
|
SaveArg<2>(&message));
|
||||||
|
Cdm::Status status = cdm_->remove(session_id);
|
||||||
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
|
Mock::VerifyAndClear(this);
|
||||||
|
|
||||||
|
// The keys should already be unusable, but they should still exist.
|
||||||
|
Cdm::KeyStatusMap map;
|
||||||
|
status = cdm_->getKeyStatuses(session_id, &map);
|
||||||
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
|
ASSERT_FALSE(map.empty());
|
||||||
|
EXPECT_EQ(Cdm::kReleased, map.begin()->second);
|
||||||
|
|
||||||
// Recreate the CDM.
|
// Recreate the CDM.
|
||||||
ASSERT_NO_FATAL_FAILURE(RecreateCdm(true /* privacy_mode */));
|
ASSERT_NO_FATAL_FAILURE(RecreateCdm(true /* privacy_mode */));
|
||||||
@@ -913,7 +1034,8 @@ TEST_F(CdmTest, RemoveIncomplete) {
|
|||||||
TEST_F(CdmTest, RemoveNotLoaded) {
|
TEST_F(CdmTest, RemoveNotLoaded) {
|
||||||
// Create a persistent session and then close it.
|
// Create a persistent session and then close it.
|
||||||
std::string session_id;
|
std::string session_id;
|
||||||
ASSERT_NO_FATAL_FAILURE(CreatePersistentSessionAndUpdate(&session_id));
|
ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(
|
||||||
|
Cdm::kPersistentLicense, &session_id));
|
||||||
Cdm::Status status = cdm_->close(session_id);
|
Cdm::Status status = cdm_->close(session_id);
|
||||||
ASSERT_EQ(Cdm::kSuccess, status);
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
|
|
||||||
@@ -945,7 +1067,7 @@ TEST_F(CdmTest, DecryptClear) {
|
|||||||
|
|
||||||
// Create a session with the right keys.
|
// Create a session with the right keys.
|
||||||
std::string session_id;
|
std::string session_id;
|
||||||
ASSERT_NO_FATAL_FAILURE(CreateTemporarySessionAndUpdate(&session_id));
|
ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(Cdm::kTemporary, &session_id));
|
||||||
|
|
||||||
// Decrypt should now succeed.
|
// Decrypt should now succeed.
|
||||||
status = cdm_->decrypt(input, output);
|
status = cdm_->decrypt(input, output);
|
||||||
@@ -958,7 +1080,8 @@ TEST_F(CdmTest, RequestPersistentLicenseWithWrongInitData) {
|
|||||||
// Generate a request for a persistent license without using the correct
|
// Generate a request for a persistent license without using the correct
|
||||||
// persistent content init data.
|
// persistent content init data.
|
||||||
std::string session_id;
|
std::string session_id;
|
||||||
Cdm::Status status = cdm_->createSession(Cdm::kPersistent, &session_id);
|
Cdm::Status status = cdm_->createSession(Cdm::kPersistentLicense,
|
||||||
|
&session_id);
|
||||||
ASSERT_EQ(Cdm::kSuccess, status);
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
|
|
||||||
std::string message;
|
std::string message;
|
||||||
@@ -990,24 +1113,16 @@ TEST_F(CdmTest, RequestTemporaryLicenseWithWrongInitData) {
|
|||||||
std::string response;
|
std::string response;
|
||||||
ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response));
|
ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response));
|
||||||
|
|
||||||
// This license should be accepted.
|
// This license should not be accepted.
|
||||||
EXPECT_CALL(*this, onKeyStatusesChange(session_id));
|
EXPECT_CALL(*this, onKeyStatusesChange(session_id));
|
||||||
status = cdm_->update(session_id, response);
|
status = cdm_->update(session_id, response);
|
||||||
EXPECT_EQ(Cdm::kSuccess, status);
|
EXPECT_EQ(Cdm::kRangeError, status);
|
||||||
Mock::VerifyAndClear(this);
|
|
||||||
|
|
||||||
// Should not be able to load the session again after closing it.
|
|
||||||
status = cdm_->close(session_id);
|
|
||||||
ASSERT_EQ(Cdm::kSuccess, status);
|
|
||||||
EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0);
|
|
||||||
status = cdm_->load(session_id);
|
|
||||||
EXPECT_EQ(Cdm::kSessionNotFound, status);
|
|
||||||
Mock::VerifyAndClear(this);
|
Mock::VerifyAndClear(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(CdmTest, Renewal) {
|
TEST_F(CdmTest, Renewal) {
|
||||||
std::string session_id;
|
std::string session_id;
|
||||||
ASSERT_NO_FATAL_FAILURE(CreateTemporarySessionAndUpdate(&session_id));
|
ASSERT_NO_FATAL_FAILURE(CreateSessionAndUpdate(Cdm::kTemporary, &session_id));
|
||||||
|
|
||||||
// We should have a timer.
|
// We should have a timer.
|
||||||
EXPECT_NE(0, g_host->NumTimers());
|
EXPECT_NE(0, g_host->NumTimers());
|
||||||
@@ -1044,9 +1159,10 @@ TEST_F(CdmTest, ServerCertificateProvisioning) {
|
|||||||
Cdm::Status status = cdm_->createSession(Cdm::kTemporary, &session_id);
|
Cdm::Status status = cdm_->createSession(Cdm::kTemporary, &session_id);
|
||||||
ASSERT_EQ(Cdm::kSuccess, status);
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
|
|
||||||
// Expect an individualization request.
|
// Expect a license request type message, but this is actually a server cert
|
||||||
|
// provisioning request.
|
||||||
std::string message;
|
std::string message;
|
||||||
EXPECT_CALL(*this, onMessage(session_id, Cdm::kIndividualizationRequest, _)).
|
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).
|
||||||
WillOnce(SaveArg<2>(&message));
|
WillOnce(SaveArg<2>(&message));
|
||||||
status = cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData);
|
status = cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData);
|
||||||
ASSERT_EQ(Cdm::kSuccess, status);
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
@@ -1058,7 +1174,7 @@ TEST_F(CdmTest, ServerCertificateProvisioning) {
|
|||||||
|
|
||||||
// No keys will change, since this wasn't a license.
|
// No keys will change, since this wasn't a license.
|
||||||
EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0);
|
EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0);
|
||||||
// We should get an actual license request generated during update.
|
// We should get another license request generated during update.
|
||||||
message.clear();
|
message.clear();
|
||||||
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).WillOnce(
|
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).WillOnce(
|
||||||
SaveArg<2>(&message));
|
SaveArg<2>(&message));
|
||||||
@@ -1076,7 +1192,7 @@ TEST_F(CdmTest, ServerCertificateProvisioning) {
|
|||||||
ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response));
|
ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response));
|
||||||
|
|
||||||
// Update the session. The keys will change now.
|
// Update the session. The keys will change now.
|
||||||
EXPECT_CALL(*this, onKeyStatusesChange(session_id));
|
EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(1);
|
||||||
status = cdm_->update(session_id, response);
|
status = cdm_->update(session_id, response);
|
||||||
ASSERT_EQ(Cdm::kSuccess, status);
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
Mock::VerifyAndClear(this);
|
Mock::VerifyAndClear(this);
|
||||||
@@ -1087,17 +1203,30 @@ TEST_F(CdmTest, ServerCertificateProvisioning) {
|
|||||||
ASSERT_FALSE(map.empty());
|
ASSERT_FALSE(map.empty());
|
||||||
EXPECT_EQ(Cdm::kUsable, map.begin()->second);
|
EXPECT_EQ(Cdm::kUsable, map.begin()->second);
|
||||||
|
|
||||||
|
|
||||||
// Create another session. This one should not require server certificate
|
// Create another session. This one should not require server certificate
|
||||||
// provisioning.
|
// provisioning.
|
||||||
status = cdm_->createSession(Cdm::kTemporary, &session_id);
|
status = cdm_->createSession(Cdm::kTemporary, &session_id);
|
||||||
ASSERT_EQ(Cdm::kSuccess, status);
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
|
|
||||||
// Expect a license request, not an individualization request.
|
// Expect a license request.
|
||||||
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _));
|
message.clear();
|
||||||
|
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).WillOnce(
|
||||||
|
SaveArg<2>(&message));
|
||||||
status = cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData);
|
status = cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData);
|
||||||
ASSERT_EQ(Cdm::kSuccess, status);
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
Mock::VerifyAndClear(this);
|
Mock::VerifyAndClear(this);
|
||||||
|
|
||||||
|
// Relay it to the server.
|
||||||
|
ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response));
|
||||||
|
|
||||||
|
// Keys will change, since this was an actual license.
|
||||||
|
EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(1);
|
||||||
|
status = cdm_->update(session_id, response);
|
||||||
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
|
Mock::VerifyAndClear(this);
|
||||||
|
|
||||||
|
|
||||||
// Create a second CDM instance.
|
// Create a second CDM instance.
|
||||||
scoped_ptr<Cdm> cdm2;
|
scoped_ptr<Cdm> cdm2;
|
||||||
CreateAdditionalCdm(true /* privacy_mode */, &cdm2);
|
CreateAdditionalCdm(true /* privacy_mode */, &cdm2);
|
||||||
@@ -1107,20 +1236,45 @@ TEST_F(CdmTest, ServerCertificateProvisioning) {
|
|||||||
// instances.
|
// instances.
|
||||||
status = cdm2->createSession(Cdm::kTemporary, &session_id);
|
status = cdm2->createSession(Cdm::kTemporary, &session_id);
|
||||||
ASSERT_EQ(Cdm::kSuccess, status);
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
EXPECT_CALL(*this, onMessage(session_id, Cdm::kIndividualizationRequest, _));
|
message.clear();
|
||||||
|
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).WillOnce(
|
||||||
|
SaveArg<2>(&message));
|
||||||
status = cdm2->generateRequest(session_id, Cdm::kCenc, kCencInitData);
|
status = cdm2->generateRequest(session_id, Cdm::kCenc, kCencInitData);
|
||||||
ASSERT_EQ(Cdm::kSuccess, status);
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
Mock::VerifyAndClear(this);
|
Mock::VerifyAndClear(this);
|
||||||
|
|
||||||
|
// Relay it to the server.
|
||||||
|
ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response));
|
||||||
|
|
||||||
|
// No keys will change, since this wasn't a license.
|
||||||
|
EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(0);
|
||||||
|
// We should get another license request generated during update.
|
||||||
|
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _));
|
||||||
|
status = cdm2->update(session_id, response);
|
||||||
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
|
Mock::VerifyAndClear(this);
|
||||||
|
|
||||||
|
|
||||||
// Create another session on the first CDM. This one should not require
|
// Create another session on the first CDM. This one should not require
|
||||||
// server certificate provisioning. This proves that the creation of the
|
// server certificate provisioning. This proves that the creation of the
|
||||||
// second CDM instance did not affect the state of the first.
|
// second CDM instance did not affect the state of the first.
|
||||||
status = cdm_->createSession(Cdm::kTemporary, &session_id);
|
status = cdm_->createSession(Cdm::kTemporary, &session_id);
|
||||||
ASSERT_EQ(Cdm::kSuccess, status);
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _));
|
message.clear();
|
||||||
|
EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).WillOnce(
|
||||||
|
SaveArg<2>(&message));
|
||||||
status = cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData);
|
status = cdm_->generateRequest(session_id, Cdm::kCenc, kCencInitData);
|
||||||
ASSERT_EQ(Cdm::kSuccess, status);
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
Mock::VerifyAndClear(this);
|
Mock::VerifyAndClear(this);
|
||||||
|
|
||||||
|
// Relay it to the server.
|
||||||
|
ASSERT_NO_FATAL_FAILURE(FetchLicense(message, &response));
|
||||||
|
|
||||||
|
// Keys will change, since this was an actual license.
|
||||||
|
EXPECT_CALL(*this, onKeyStatusesChange(session_id)).Times(1);
|
||||||
|
status = cdm_->update(session_id, response);
|
||||||
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
|
Mock::VerifyAndClear(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(CdmTest, SetAppParameters) {
|
TEST_F(CdmTest, SetAppParameters) {
|
||||||
@@ -1140,19 +1294,19 @@ TEST_F(CdmTest, SetAppParameters) {
|
|||||||
|
|
||||||
// Try to get using a null result.
|
// Try to get using a null result.
|
||||||
status = cdm_->getAppParameter(kParamName, NULL);
|
status = cdm_->getAppParameter(kParamName, NULL);
|
||||||
ASSERT_EQ(Cdm::kInvalidAccess, status);
|
ASSERT_EQ(Cdm::kTypeError, status);
|
||||||
|
|
||||||
// Try to get using an empty key.
|
// Try to get using an empty key.
|
||||||
status = cdm_->getAppParameter("", &result);
|
status = cdm_->getAppParameter("", &result);
|
||||||
ASSERT_EQ(Cdm::kInvalidAccess, status);
|
ASSERT_EQ(Cdm::kTypeError, status);
|
||||||
|
|
||||||
// Try to set using an empty key.
|
// Try to set using an empty key.
|
||||||
status = cdm_->setAppParameter("", kValue);
|
status = cdm_->setAppParameter("", kValue);
|
||||||
ASSERT_EQ(Cdm::kInvalidAccess, status);
|
ASSERT_EQ(Cdm::kTypeError, status);
|
||||||
|
|
||||||
// Try to remove using an empty key.
|
// Try to remove using an empty key.
|
||||||
status = cdm_->removeAppParameter("");
|
status = cdm_->removeAppParameter("");
|
||||||
ASSERT_EQ(Cdm::kInvalidAccess, status);
|
ASSERT_EQ(Cdm::kTypeError, status);
|
||||||
|
|
||||||
// Change an existing app parameter.
|
// Change an existing app parameter.
|
||||||
status = cdm_->setAppParameter(kParamName, kNewValue);
|
status = cdm_->setAppParameter(kParamName, kNewValue);
|
||||||
@@ -1165,11 +1319,11 @@ TEST_F(CdmTest, SetAppParameters) {
|
|||||||
status = cdm_->removeAppParameter(kParamName);
|
status = cdm_->removeAppParameter(kParamName);
|
||||||
ASSERT_EQ(Cdm::kSuccess, status);
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
status = cdm_->getAppParameter(kParamName, &result);
|
status = cdm_->getAppParameter(kParamName, &result);
|
||||||
ASSERT_EQ(Cdm::kInvalidAccess, status);
|
ASSERT_EQ(Cdm::kTypeError, status);
|
||||||
|
|
||||||
// Try to remove an absent value.
|
// Try to remove an absent value.
|
||||||
status = cdm_->removeAppParameter(kParamName2);
|
status = cdm_->removeAppParameter(kParamName2);
|
||||||
ASSERT_EQ(Cdm::kInvalidAccess, status);
|
ASSERT_EQ(Cdm::kTypeError, status);
|
||||||
|
|
||||||
// Set some values to check for.
|
// Set some values to check for.
|
||||||
status = cdm_->setAppParameter(kParamName, kValue);
|
status = cdm_->setAppParameter(kParamName, kValue);
|
||||||
@@ -1198,9 +1352,9 @@ TEST_F(CdmTest, SetAppParameters) {
|
|||||||
status = cdm_->clearAppParameters();
|
status = cdm_->clearAppParameters();
|
||||||
ASSERT_EQ(Cdm::kSuccess, status);
|
ASSERT_EQ(Cdm::kSuccess, status);
|
||||||
status = cdm_->getAppParameter(kParamName, &result);
|
status = cdm_->getAppParameter(kParamName, &result);
|
||||||
ASSERT_EQ(Cdm::kInvalidAccess, status);
|
ASSERT_EQ(Cdm::kTypeError, status);
|
||||||
status = cdm_->getAppParameter(kParamName2, &result);
|
status = cdm_->getAppParameter(kParamName2, &result);
|
||||||
ASSERT_EQ(Cdm::kInvalidAccess, status);
|
ASSERT_EQ(Cdm::kTypeError, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace widevine
|
} // namespace widevine
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ void PrintTo(const Cdm::Status& value, ::std::ostream* os) {
|
|||||||
break;
|
break;
|
||||||
case Cdm::kNoKey: *os << "Cdm::kNoKey";
|
case Cdm::kNoKey: *os << "Cdm::kNoKey";
|
||||||
break;
|
break;
|
||||||
case Cdm::kInvalidAccess: *os << "Cdm::kInvalidAccess";
|
case Cdm::kTypeError: *os << "Cdm::kTypeError";
|
||||||
break;
|
break;
|
||||||
case Cdm::kNotSupported: *os << "Cdm::kNotSupported";
|
case Cdm::kNotSupported: *os << "Cdm::kNotSupported";
|
||||||
break;
|
break;
|
||||||
@@ -40,6 +40,8 @@ void PrintTo(const Cdm::Status& value, ::std::ostream* os) {
|
|||||||
break;
|
break;
|
||||||
case Cdm::kQuotaExceeded: *os << "Cdm::kQuotaExceeded";
|
case Cdm::kQuotaExceeded: *os << "Cdm::kQuotaExceeded";
|
||||||
break;
|
break;
|
||||||
|
case Cdm::kRangeError: *os << "Cdm::kRangeError";
|
||||||
|
break;
|
||||||
case Cdm::kUnexpectedError: *os << "Cdm::kUnexpectedError";
|
case Cdm::kUnexpectedError: *os << "Cdm::kUnexpectedError";
|
||||||
break;
|
break;
|
||||||
default: *os << "Unknown Cdm::Status value " << value;
|
default: *os << "Unknown Cdm::Status value " << value;
|
||||||
@@ -53,7 +55,7 @@ void PrintTo(const Cdm::KeyStatus& value, ::std::ostream* os) {
|
|||||||
break;
|
break;
|
||||||
case Cdm::kExpired: *os << "Cdm::kExpired";
|
case Cdm::kExpired: *os << "Cdm::kExpired";
|
||||||
break;
|
break;
|
||||||
case Cdm::kOutputNotAllowed: *os << "Cdm::kOutputNotAllowed";
|
case Cdm::kOutputRestricted: *os << "Cdm::kOutputRestricted";
|
||||||
break;
|
break;
|
||||||
case Cdm::kStatusPending: *os << "Cdm::kStatusPending";
|
case Cdm::kStatusPending: *os << "Cdm::kStatusPending";
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -146,6 +146,8 @@ class CdmEngine {
|
|||||||
virtual CdmResponseType ReleaseAllUsageInfo(const std::string& app_id);
|
virtual CdmResponseType ReleaseAllUsageInfo(const std::string& app_id);
|
||||||
virtual CdmResponseType ReleaseUsageInfo(
|
virtual CdmResponseType ReleaseUsageInfo(
|
||||||
const CdmUsageInfoReleaseMessage& message);
|
const CdmUsageInfoReleaseMessage& message);
|
||||||
|
virtual CdmResponseType LoadUsageSession(const CdmKeySetId& key_set_id,
|
||||||
|
CdmKeyMessage* release_message);
|
||||||
|
|
||||||
// Decryption and key related methods
|
// Decryption and key related methods
|
||||||
// Accept encrypted buffer and return decrypted data.
|
// Accept encrypted buffer and return decrypted data.
|
||||||
@@ -173,6 +175,7 @@ class CdmEngine {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
// private methods
|
// private methods
|
||||||
|
void DeleteAllUsageReportsUponFactoryReset();
|
||||||
bool ValidateKeySystem(const CdmKeySystem& key_system);
|
bool ValidateKeySystem(const CdmKeySystem& key_system);
|
||||||
CdmResponseType GetUsageInfo(const std::string& app_id,
|
CdmResponseType GetUsageInfo(const std::string& app_id,
|
||||||
SecurityLevel requested_security_level,
|
SecurityLevel requested_security_level,
|
||||||
|
|||||||
@@ -104,6 +104,8 @@ class CdmSession {
|
|||||||
|
|
||||||
virtual bool is_release() { return is_release_; }
|
virtual bool is_release() { return is_release_; }
|
||||||
virtual bool is_offline() { return is_offline_; }
|
virtual bool is_offline() { return is_offline_; }
|
||||||
|
virtual bool is_temporary() { return is_temporary_; }
|
||||||
|
virtual bool license_received() { return license_received_; }
|
||||||
|
|
||||||
// ReleaseCrypto() - Closes the underlying crypto session but leaves this
|
// ReleaseCrypto() - Closes the underlying crypto session but leaves this
|
||||||
// object alive. It is invalid to call any method that requires a crypto
|
// object alive. It is invalid to call any method that requires a crypto
|
||||||
@@ -141,6 +143,7 @@ class CdmSession {
|
|||||||
bool license_received_;
|
bool license_received_;
|
||||||
bool is_offline_;
|
bool is_offline_;
|
||||||
bool is_release_;
|
bool is_release_;
|
||||||
|
bool is_temporary_;
|
||||||
CdmSecurityLevel security_level_;
|
CdmSecurityLevel security_level_;
|
||||||
SecurityLevel requested_security_level_;
|
SecurityLevel requested_security_level_;
|
||||||
CdmAppParameterMap app_parameters_;
|
CdmAppParameterMap app_parameters_;
|
||||||
|
|||||||
@@ -67,11 +67,13 @@ class DeviceFiles {
|
|||||||
virtual bool DeleteAllLicenses();
|
virtual bool DeleteAllLicenses();
|
||||||
virtual bool LicenseExists(const std::string& key_set_id);
|
virtual bool LicenseExists(const std::string& key_set_id);
|
||||||
virtual bool ReserveLicenseId(const std::string& key_set_id);
|
virtual bool ReserveLicenseId(const std::string& key_set_id);
|
||||||
|
virtual bool UnreserveLicenseId(const std::string& key_set_id);
|
||||||
|
|
||||||
virtual bool StoreUsageInfo(const std::string& provider_session_token,
|
virtual bool StoreUsageInfo(const std::string& provider_session_token,
|
||||||
const CdmKeyMessage& key_request,
|
const CdmKeyMessage& key_request,
|
||||||
const CdmKeyResponse& key_response,
|
const CdmKeyResponse& key_response,
|
||||||
const std::string& app_id);
|
const std::string& app_id,
|
||||||
|
const std::string& key_set_id);
|
||||||
virtual bool DeleteUsageInfo(const std::string& app_id,
|
virtual bool DeleteUsageInfo(const std::string& app_id,
|
||||||
const std::string& provider_session_token);
|
const std::string& provider_session_token);
|
||||||
// Delete usage information from the file system. Puts a list of all the
|
// Delete usage information from the file system. Puts a list of all the
|
||||||
@@ -90,6 +92,12 @@ class DeviceFiles {
|
|||||||
const std::string& provider_session_token,
|
const std::string& provider_session_token,
|
||||||
CdmKeyMessage* license_request,
|
CdmKeyMessage* license_request,
|
||||||
CdmKeyResponse* license_response);
|
CdmKeyResponse* license_response);
|
||||||
|
// Retrieve the usage info entry specified by |key_set_id|.
|
||||||
|
// Returns false if the entry could not be found.
|
||||||
|
virtual bool RetrieveUsageInfoByKeySetId(const std::string& app_id,
|
||||||
|
const std::string& key_set_id,
|
||||||
|
CdmKeyMessage* license_request,
|
||||||
|
CdmKeyResponse* license_response);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Helpers that wrap the File interface and automatically handle hashing, as
|
// Helpers that wrap the File interface and automatically handle hashing, as
|
||||||
|
|||||||
@@ -56,6 +56,10 @@ class CdmLicense {
|
|||||||
return provider_session_token_;
|
return provider_session_token_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual bool is_offline() {
|
||||||
|
return is_offline_;
|
||||||
|
}
|
||||||
|
|
||||||
static CdmResponseType VerifySignedServiceCertificate(
|
static CdmResponseType VerifySignedServiceCertificate(
|
||||||
const std::string& signed_service_certificate);
|
const std::string& signed_service_certificate);
|
||||||
|
|
||||||
@@ -89,6 +93,7 @@ class CdmLicense {
|
|||||||
std::set<KeyId> loaded_keys_;
|
std::set<KeyId> loaded_keys_;
|
||||||
std::string provider_session_token_;
|
std::string provider_session_token_;
|
||||||
bool renew_with_client_id_;
|
bool renew_with_client_id_;
|
||||||
|
bool is_offline_;
|
||||||
|
|
||||||
// Used for certificate based licensing
|
// Used for certificate based licensing
|
||||||
CdmKeyMessage key_request_;
|
CdmKeyMessage key_request_;
|
||||||
|
|||||||
@@ -207,6 +207,12 @@ enum CdmResponseType {
|
|||||||
DUPLICATE_SESSION_ID_SPECIFIED,
|
DUPLICATE_SESSION_ID_SPECIFIED,
|
||||||
LICENSE_RENEWAL_PROHIBITED,
|
LICENSE_RENEWAL_PROHIBITED,
|
||||||
EMPTY_PROVISIONING_CERTIFICATE_2,
|
EMPTY_PROVISIONING_CERTIFICATE_2,
|
||||||
|
OFFLINE_LICENSE_PROHIBITED,
|
||||||
|
STORAGE_PROHIBITED,
|
||||||
|
EMPTY_KEYSET_ID_ENG_5,
|
||||||
|
SESSION_NOT_FOUND_11,
|
||||||
|
LOAD_USAGE_INFO_FILE_ERROR,
|
||||||
|
LOAD_USAGE_INFO_MISSING,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum CdmKeyStatus {
|
enum CdmKeyStatus {
|
||||||
@@ -229,6 +235,9 @@ enum CdmLicenseType {
|
|||||||
// If the original request was saved to make a service certificate request,
|
// If the original request was saved to make a service certificate request,
|
||||||
// use Deferred for the license type in the subsequent request.
|
// use Deferred for the license type in the subsequent request.
|
||||||
kLicenseTypeDeferred,
|
kLicenseTypeDeferred,
|
||||||
|
// Like Streaming, but stricter. Does not permit storage of any kind.
|
||||||
|
// Named after the 'temporary' session type in EME, which has this behavior.
|
||||||
|
kLicenseTypeTemporary,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum SecurityLevel {
|
enum SecurityLevel {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "cdm_engine.h"
|
#include "cdm_engine.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@@ -10,6 +11,7 @@
|
|||||||
#include "cdm_session.h"
|
#include "cdm_session.h"
|
||||||
#include "clock.h"
|
#include "clock.h"
|
||||||
#include "device_files.h"
|
#include "device_files.h"
|
||||||
|
#include "file_store.h"
|
||||||
#include "license_protocol.pb.h"
|
#include "license_protocol.pb.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "properties.h"
|
#include "properties.h"
|
||||||
@@ -227,7 +229,8 @@ CdmResponseType CdmEngine::GenerateKeyRequest(
|
|||||||
|
|
||||||
key_request->clear();
|
key_request->clear();
|
||||||
|
|
||||||
if (license_type == kLicenseTypeRelease) {
|
if (license_type == kLicenseTypeRelease &&
|
||||||
|
!iter->second->license_received()) {
|
||||||
sts = iter->second->RestoreOfflineSession(key_set_id, kLicenseTypeRelease);
|
sts = iter->second->RestoreOfflineSession(key_set_id, kLicenseTypeRelease);
|
||||||
if (sts != KEY_ADDED) {
|
if (sts != KEY_ADDED) {
|
||||||
LOGE("CdmEngine::GenerateKeyRequest: key release restoration failed,"
|
LOGE("CdmEngine::GenerateKeyRequest: key release restoration failed,"
|
||||||
@@ -338,7 +341,7 @@ CdmResponseType CdmEngine::RestoreKey(const CdmSessionId& session_id,
|
|||||||
if (sts != KEY_ADDED && sts != GET_RELEASED_LICENSE_ERROR) {
|
if (sts != KEY_ADDED && sts != GET_RELEASED_LICENSE_ERROR) {
|
||||||
LOGE("CdmEngine::RestoreKey: restore offline session failed = %d", sts);
|
LOGE("CdmEngine::RestoreKey: restore offline session failed = %d", sts);
|
||||||
}
|
}
|
||||||
return sts; // TODO ewew
|
return sts;
|
||||||
}
|
}
|
||||||
|
|
||||||
CdmResponseType CdmEngine::RemoveKeys(const CdmSessionId& session_id) {
|
CdmResponseType CdmEngine::RemoveKeys(const CdmSessionId& session_id) {
|
||||||
@@ -602,6 +605,9 @@ CdmResponseType CdmEngine::GetProvisioningRequest(
|
|||||||
LOGE("CdmEngine::GetProvisioningRequest: invalid output parameters");
|
LOGE("CdmEngine::GetProvisioningRequest: invalid output parameters");
|
||||||
return INVALID_PROVISIONING_REQUEST_PARAM_2;
|
return INVALID_PROVISIONING_REQUEST_PARAM_2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DeleteAllUsageReportsUponFactoryReset();
|
||||||
|
|
||||||
if (NULL == cert_provisioning_.get()) {
|
if (NULL == cert_provisioning_.get()) {
|
||||||
cert_provisioning_.reset(new CertificateProvisioning());
|
cert_provisioning_.reset(new CertificateProvisioning());
|
||||||
}
|
}
|
||||||
@@ -663,6 +669,7 @@ CdmResponseType CdmEngine::HandleProvisioningResponse(
|
|||||||
}
|
}
|
||||||
return NO_ERROR;
|
return NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
CdmResponseType ret = cert_provisioning_->HandleProvisioningResponse(
|
CdmResponseType ret = cert_provisioning_->HandleProvisioningResponse(
|
||||||
origin, response, cert, wrapped_key);
|
origin, response, cert, wrapped_key);
|
||||||
// Release resources only on success. It is possible that a provisioning
|
// Release resources only on success. It is possible that a provisioning
|
||||||
@@ -928,6 +935,66 @@ CdmResponseType CdmEngine::ReleaseUsageInfo(
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CdmResponseType CdmEngine::LoadUsageSession(const CdmKeySetId& key_set_id,
|
||||||
|
CdmKeyMessage* release_message) {
|
||||||
|
LOGI("CdmEngine::LoadUsageSession");
|
||||||
|
// This method is currently only used by the CE CDM, in which all session IDs
|
||||||
|
// are key set IDs.
|
||||||
|
assert(Properties::AlwaysUseKeySetIds());
|
||||||
|
|
||||||
|
if (key_set_id.empty()) {
|
||||||
|
LOGE("CdmEngine::LoadUsageSession: invalid key set id");
|
||||||
|
return EMPTY_KEYSET_ID_ENG_5;
|
||||||
|
}
|
||||||
|
|
||||||
|
CdmSessionMap::iterator iter = sessions_.find(key_set_id);
|
||||||
|
if (iter == sessions_.end()) {
|
||||||
|
LOGE("CdmEngine::LoadUsageSession: session_id not found = %s ",
|
||||||
|
key_set_id.c_str());
|
||||||
|
return SESSION_NOT_FOUND_11;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceFiles handle;
|
||||||
|
if (!handle.Init(iter->second->GetSecurityLevel())) {
|
||||||
|
LOGE("CdmEngine::LoadUsageSession: unable to initialize device files");
|
||||||
|
return LOAD_USAGE_INFO_FILE_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string app_id;
|
||||||
|
iter->second->GetApplicationId(&app_id);
|
||||||
|
|
||||||
|
CdmKeyMessage key_message;
|
||||||
|
CdmKeyResponse key_response;
|
||||||
|
if (!handle.RetrieveUsageInfoByKeySetId(app_id, key_set_id, &key_message,
|
||||||
|
&key_response)) {
|
||||||
|
LOGE("CdmEngine::LoadUsageSession: unable to find usage information");
|
||||||
|
return LOAD_USAGE_INFO_MISSING;
|
||||||
|
}
|
||||||
|
|
||||||
|
CdmResponseType status =
|
||||||
|
iter->second->RestoreUsageSession(key_message, key_response);
|
||||||
|
if (KEY_ADDED != status) {
|
||||||
|
LOGE("CdmEngine::LoadUsageSession: usage session error %ld", status);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string server_url;
|
||||||
|
status = iter->second->GenerateReleaseRequest(release_message, &server_url);
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case KEY_MESSAGE:
|
||||||
|
break;
|
||||||
|
case KEY_CANCELED: // usage information not present in
|
||||||
|
iter->second->DeleteLicense(); // OEMCrypto, delete and try again
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOGE("CdmEngine::LoadUsageSession: generate release request error: %d",
|
||||||
|
status);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
CdmResponseType CdmEngine::Decrypt(const CdmSessionId& session_id,
|
CdmResponseType CdmEngine::Decrypt(const CdmSessionId& session_id,
|
||||||
const CdmDecryptionParameters& parameters) {
|
const CdmDecryptionParameters& parameters) {
|
||||||
if (parameters.key_id == NULL) {
|
if (parameters.key_id == NULL) {
|
||||||
@@ -1097,4 +1164,34 @@ std::string CdmEngine::MapHdcpVersion(
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CdmEngine::DeleteAllUsageReportsUponFactoryReset() {
|
||||||
|
std::string device_base_path_level1 = "";
|
||||||
|
std::string device_base_path_level3 = "";
|
||||||
|
Properties::GetDeviceFilesBasePath(kSecurityLevelL1,
|
||||||
|
&device_base_path_level1);
|
||||||
|
Properties::GetDeviceFilesBasePath(kSecurityLevelL3,
|
||||||
|
&device_base_path_level3);
|
||||||
|
|
||||||
|
File file;
|
||||||
|
if (!file.Exists(device_base_path_level1) &&
|
||||||
|
!file.Exists(device_base_path_level3)) {
|
||||||
|
scoped_ptr<CryptoSession> crypto_session(new CryptoSession());
|
||||||
|
CdmResponseType status = crypto_session->Open(
|
||||||
|
cert_provisioning_requested_security_level_);
|
||||||
|
if (NO_ERROR == status) {
|
||||||
|
status = crypto_session->DeleteAllUsageReports();
|
||||||
|
if (NO_ERROR != status) {
|
||||||
|
LOGW(
|
||||||
|
"CdmEngine::GetProvisioningRequest: "
|
||||||
|
"Fails to delete usage reports: %d", status);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOGW(
|
||||||
|
"CdmEngine::GetProvisioningRequest: "
|
||||||
|
"Fails to open crypto session: error=%d.\n"
|
||||||
|
"Usage reports are not removed after factory reset.", status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace wvcdm
|
} // namespace wvcdm
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ CdmSession::CdmSession(CdmClientPropertySet* cdm_client_property_set,
|
|||||||
license_received_(false),
|
license_received_(false),
|
||||||
is_offline_(false),
|
is_offline_(false),
|
||||||
is_release_(false),
|
is_release_(false),
|
||||||
|
is_temporary_(false),
|
||||||
security_level_(kSecurityLevelUninitialized),
|
security_level_(kSecurityLevelUninitialized),
|
||||||
requested_security_level_(kLevelDefault),
|
requested_security_level_(kLevelDefault),
|
||||||
is_initial_decryption_(true),
|
is_initial_decryption_(true),
|
||||||
@@ -66,7 +67,13 @@ CdmSession::CdmSession(CdmClientPropertySet* cdm_client_property_set,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CdmSession::~CdmSession() { Properties::RemoveSessionPropertySet(session_id_); }
|
CdmSession::~CdmSession() {
|
||||||
|
if (!key_set_id_.empty()) {
|
||||||
|
// Unreserve the license ID.
|
||||||
|
file_handle_->UnreserveLicenseId(key_set_id_);
|
||||||
|
}
|
||||||
|
Properties::RemoveSessionPropertySet(session_id_);
|
||||||
|
}
|
||||||
|
|
||||||
CdmResponseType CdmSession::Init() {
|
CdmResponseType CdmSession::Init() {
|
||||||
if (session_id_.empty()) {
|
if (session_id_.empty()) {
|
||||||
@@ -184,6 +191,9 @@ CdmResponseType CdmSession::GenerateKeyRequest(
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (license_type) {
|
switch (license_type) {
|
||||||
|
case kLicenseTypeTemporary:
|
||||||
|
is_temporary_ = true;
|
||||||
|
break;
|
||||||
case kLicenseTypeStreaming:
|
case kLicenseTypeStreaming:
|
||||||
is_offline_ = false;
|
is_offline_ = false;
|
||||||
break;
|
break;
|
||||||
@@ -207,6 +217,8 @@ CdmResponseType CdmSession::GenerateKeyRequest(
|
|||||||
license_type = kLicenseTypeRelease;
|
license_type = kLicenseTypeRelease;
|
||||||
} else if (is_offline_) {
|
} else if (is_offline_) {
|
||||||
license_type = kLicenseTypeOffline;
|
license_type = kLicenseTypeOffline;
|
||||||
|
} else if (is_temporary_) {
|
||||||
|
license_type = kLicenseTypeTemporary;
|
||||||
} else {
|
} else {
|
||||||
license_type = kLicenseTypeStreaming;
|
license_type = kLicenseTypeStreaming;
|
||||||
}
|
}
|
||||||
@@ -484,12 +496,22 @@ bool CdmSession::GenerateKeySetId(CdmKeySetId* key_set_id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CdmResponseType CdmSession::StoreLicense() {
|
CdmResponseType CdmSession::StoreLicense() {
|
||||||
|
if (is_temporary_) {
|
||||||
|
LOGE("CdmSession::StoreLicense: Session type prohibits storage.");
|
||||||
|
return STORAGE_PROHIBITED;
|
||||||
|
}
|
||||||
|
|
||||||
if (is_offline_) {
|
if (is_offline_) {
|
||||||
if (key_set_id_.empty()) {
|
if (key_set_id_.empty()) {
|
||||||
LOGE("CdmSession::StoreLicense: No key set ID");
|
LOGE("CdmSession::StoreLicense: No key set ID");
|
||||||
return EMPTY_KEYSET_ID;
|
return EMPTY_KEYSET_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!license_parser_->is_offline()) {
|
||||||
|
LOGE("CdmSession::StoreLicense: License policy prohibits storage.");
|
||||||
|
return OFFLINE_LICENSE_PROHIBITED;
|
||||||
|
}
|
||||||
|
|
||||||
if (!StoreLicense(DeviceFiles::kLicenseStateActive)) {
|
if (!StoreLicense(DeviceFiles::kLicenseStateActive)) {
|
||||||
LOGE("CdmSession::StoreLicense: Unable to store license");
|
LOGE("CdmSession::StoreLicense: Unable to store license");
|
||||||
CdmResponseType sts = Init();
|
CdmResponseType sts = Init();
|
||||||
@@ -502,7 +524,7 @@ CdmResponseType CdmSession::StoreLicense() {
|
|||||||
return STORE_LICENSE_ERROR_1;
|
return STORE_LICENSE_ERROR_1;
|
||||||
}
|
}
|
||||||
return NO_ERROR;
|
return NO_ERROR;
|
||||||
}
|
} // if (is_offline_)
|
||||||
|
|
||||||
std::string provider_session_token =
|
std::string provider_session_token =
|
||||||
license_parser_->provider_session_token();
|
license_parser_->provider_session_token();
|
||||||
@@ -519,7 +541,7 @@ CdmResponseType CdmSession::StoreLicense() {
|
|||||||
std::string app_id;
|
std::string app_id;
|
||||||
GetApplicationId(&app_id);
|
GetApplicationId(&app_id);
|
||||||
if (!file_handle_->StoreUsageInfo(provider_session_token, key_request_,
|
if (!file_handle_->StoreUsageInfo(provider_session_token, key_request_,
|
||||||
key_response_, app_id)) {
|
key_response_, app_id, key_set_id_)) {
|
||||||
LOGE("CdmSession::StoreLicense: Unable to store usage info");
|
LOGE("CdmSession::StoreLicense: Unable to store usage info");
|
||||||
return STORE_USAGE_INFO_ERROR;
|
return STORE_USAGE_INFO_ERROR;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -343,10 +343,20 @@ bool DeviceFiles::ReserveLicenseId(const std::string& key_set_id) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DeviceFiles::UnreserveLicenseId(const std::string& key_set_id) {
|
||||||
|
if (!initialized_) {
|
||||||
|
LOGW("DeviceFiles::UnreserveLicenseId: not initialized");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
reserved_license_ids_.erase(key_set_id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool DeviceFiles::StoreUsageInfo(const std::string& provider_session_token,
|
bool DeviceFiles::StoreUsageInfo(const std::string& provider_session_token,
|
||||||
const CdmKeyMessage& key_request,
|
const CdmKeyMessage& key_request,
|
||||||
const CdmKeyResponse& key_response,
|
const CdmKeyResponse& key_response,
|
||||||
const std::string& app_id) {
|
const std::string& app_id,
|
||||||
|
const std::string& key_set_id) {
|
||||||
if (!initialized_) {
|
if (!initialized_) {
|
||||||
LOGW("DeviceFiles::StoreUsageInfo: not initialized");
|
LOGW("DeviceFiles::StoreUsageInfo: not initialized");
|
||||||
return false;
|
return false;
|
||||||
@@ -372,6 +382,7 @@ bool DeviceFiles::StoreUsageInfo(const std::string& provider_session_token,
|
|||||||
provider_session_token.size());
|
provider_session_token.size());
|
||||||
provider_session->set_license_request(key_request.data(), key_request.size());
|
provider_session->set_license_request(key_request.data(), key_request.size());
|
||||||
provider_session->set_license(key_response.data(), key_response.size());
|
provider_session->set_license(key_response.data(), key_response.size());
|
||||||
|
provider_session->set_key_set_id(key_set_id.data(), key_set_id.size());
|
||||||
|
|
||||||
file.SerializeToString(&serialized_file);
|
file.SerializeToString(&serialized_file);
|
||||||
return StoreFileWithHash(file_name, serialized_file);
|
return StoreFileWithHash(file_name, serialized_file);
|
||||||
@@ -513,22 +524,48 @@ bool DeviceFiles::RetrieveUsageInfo(const std::string& app_id,
|
|||||||
LOGW("DeviceFiles::RetrieveUsageInfo: Unable to parse file");
|
LOGW("DeviceFiles::RetrieveUsageInfo: Unable to parse file");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int index = 0;
|
int index = 0;
|
||||||
bool found = false;
|
|
||||||
for (; index < file.usage_info().sessions_size(); ++index) {
|
for (; index < file.usage_info().sessions_size(); ++index) {
|
||||||
if (file.usage_info().sessions(index).token() == provider_session_token) {
|
if (file.usage_info().sessions(index).token() == provider_session_token) {
|
||||||
found = true;
|
*license_request = file.usage_info().sessions(index).license_request();
|
||||||
break;
|
*license_response = file.usage_info().sessions(index).license();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!found) {
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeviceFiles::RetrieveUsageInfoByKeySetId(
|
||||||
|
const std::string& app_id,
|
||||||
|
const std::string& key_set_id,
|
||||||
|
CdmKeyMessage* license_request,
|
||||||
|
CdmKeyResponse* license_response) {
|
||||||
|
if (!initialized_) {
|
||||||
|
LOGW("DeviceFiles::RetrieveUsageInfoByKeySetId: not initialized");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::string serialized_file;
|
||||||
|
std::string file_name = GetUsageInfoFileName(app_id);
|
||||||
|
if (!RetrieveHashedFile(file_name, &serialized_file)) return false;
|
||||||
|
|
||||||
|
video_widevine_client::sdk::File file;
|
||||||
|
if (!file.ParseFromString(serialized_file)) {
|
||||||
|
LOGW("DeviceFiles::RetrieveUsageInfoByKeySetId: Unable to parse file");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
*license_request = file.usage_info().sessions(index).license_request();
|
int index = 0;
|
||||||
*license_response = file.usage_info().sessions(index).license();
|
for (; index < file.usage_info().sessions_size(); ++index) {
|
||||||
return true;
|
if (file.usage_info().sessions(index).key_set_id() == key_set_id) {
|
||||||
|
*license_request = file.usage_info().sessions(index).license_request();
|
||||||
|
*license_response = file.usage_info().sessions(index).license();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DeviceFiles::StoreFileWithHash(const std::string& name,
|
bool DeviceFiles::StoreFileWithHash(const std::string& name,
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ message UsageInfo {
|
|||||||
optional bytes token = 1;
|
optional bytes token = 1;
|
||||||
optional bytes license_request = 2;
|
optional bytes license_request = 2;
|
||||||
optional bytes license = 3;
|
optional bytes license = 3;
|
||||||
|
optional bytes key_set_id = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
repeated ProviderSession sessions = 1;
|
repeated ProviderSession sessions = 1;
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ CdmLicense::CdmLicense(const CdmSessionId& session_id)
|
|||||||
session_id_(session_id),
|
session_id_(session_id),
|
||||||
initialized_(false),
|
initialized_(false),
|
||||||
renew_with_client_id_(false),
|
renew_with_client_id_(false),
|
||||||
|
is_offline_(false),
|
||||||
clock_(new Clock()) {}
|
clock_(new Clock()) {}
|
||||||
|
|
||||||
CdmLicense::CdmLicense(const CdmSessionId& session_id, Clock* clock)
|
CdmLicense::CdmLicense(const CdmSessionId& session_id, Clock* clock)
|
||||||
@@ -139,7 +140,8 @@ CdmLicense::CdmLicense(const CdmSessionId& session_id, Clock* clock)
|
|||||||
policy_engine_(NULL),
|
policy_engine_(NULL),
|
||||||
session_id_(session_id),
|
session_id_(session_id),
|
||||||
initialized_(false),
|
initialized_(false),
|
||||||
renew_with_client_id_(false) {
|
renew_with_client_id_(false),
|
||||||
|
is_offline_(false) {
|
||||||
clock_.reset(clock);
|
clock_.reset(clock);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -522,6 +524,10 @@ CdmResponseType CdmLicense::HandleKeyResponse(
|
|||||||
return NO_CONTENT_KEY;
|
return NO_CONTENT_KEY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (license.id().type() == video_widevine_server::sdk::OFFLINE &&
|
||||||
|
license.policy().can_persist())
|
||||||
|
is_offline_ = true;
|
||||||
|
|
||||||
if (license.id().has_provider_session_token())
|
if (license.id().has_provider_session_token())
|
||||||
provider_session_token_ = license.id().provider_session_token();
|
provider_session_token_ = license.id().provider_session_token();
|
||||||
|
|
||||||
@@ -1102,6 +1108,7 @@ bool CdmLicense::PrepareContentId(const CdmLicenseType license_type,
|
|||||||
content_id->set_license_type(video_widevine_server::sdk::OFFLINE);
|
content_id->set_license_type(video_widevine_server::sdk::OFFLINE);
|
||||||
break;
|
break;
|
||||||
case kLicenseTypeStreaming:
|
case kLicenseTypeStreaming:
|
||||||
|
case kLicenseTypeTemporary:
|
||||||
content_id->set_license_type(video_widevine_server::sdk::STREAMING);
|
content_id->set_license_type(video_widevine_server::sdk::STREAMING);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ TEST_F(WvCdmEngineTest, BaseIsoBmffMessageTest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO(juce): Set up with correct test data.
|
// TODO(juce): Set up with correct test data.
|
||||||
TEST_F(WvCdmEngineTest, DISABLED_BaseWebmMessageTest) {
|
TEST_F(WvCdmEngineTest, BaseWebmMessageTest) {
|
||||||
GenerateKeyRequest(g_key_id_unwrapped, kWebmMimeType);
|
GenerateKeyRequest(g_key_id_unwrapped, kWebmMimeType);
|
||||||
GetKeyRequestResponse(g_license_server, g_client_auth);
|
GetKeyRequestResponse(g_license_server, g_client_auth);
|
||||||
}
|
}
|
||||||
@@ -220,7 +220,7 @@ TEST_F(WvCdmEngineTest, NormalDecryptionIsoBmff) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO(juce): Set up with correct test data.
|
// TODO(juce): Set up with correct test data.
|
||||||
TEST_F(WvCdmEngineTest, DISABLED_NormalDecryptionWebm) {
|
TEST_F(WvCdmEngineTest, NormalDecryptionWebm) {
|
||||||
GenerateKeyRequest(g_key_id_unwrapped, kWebmMimeType);
|
GenerateKeyRequest(g_key_id_unwrapped, kWebmMimeType);
|
||||||
VerifyNewKeyResponse(g_license_server, g_client_auth);
|
VerifyNewKeyResponse(g_license_server, g_client_auth);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const uint32_t kProtobufEstimatedOverhead = 75;
|
|||||||
const uint32_t kLicenseRequestLen = 300;
|
const uint32_t kLicenseRequestLen = 300;
|
||||||
const uint32_t kLicenseLen = 500;
|
const uint32_t kLicenseLen = 500;
|
||||||
const uint32_t kProviderSessionTokenLen = 128;
|
const uint32_t kProviderSessionTokenLen = 128;
|
||||||
|
const uint32_t kKeySetIdLen = 20;
|
||||||
|
|
||||||
// Structurally valid test certificate.
|
// Structurally valid test certificate.
|
||||||
// The data elements in this module are used to test the storage and
|
// The data elements in this module are used to test the storage and
|
||||||
@@ -1536,6 +1537,16 @@ MATCHER_P4(Contains, str1, str2, str3, size, "") {
|
|||||||
data.find(str2) != std::string::npos &&
|
data.find(str2) != std::string::npos &&
|
||||||
data.find(str3) != std::string::npos);
|
data.find(str3) != std::string::npos);
|
||||||
}
|
}
|
||||||
|
MATCHER_P5(Contains, str1, str2, str3, str4, 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() +
|
||||||
|
str4.size() + kProtobufEstimatedOverhead);
|
||||||
|
return (data.find(str1) != std::string::npos &&
|
||||||
|
data.find(str2) != std::string::npos &&
|
||||||
|
data.find(str3) != std::string::npos &&
|
||||||
|
data.find(str4) != std::string::npos);
|
||||||
|
}
|
||||||
MATCHER_P6(Contains, str1, str2, str3, str4, str5, str6, "") {
|
MATCHER_P6(Contains, str1, str2, str3, str4, str5, str6, "") {
|
||||||
// Estimating the length of data. We can have gmock provide length
|
// Estimating the length of data. We can have gmock provide length
|
||||||
// as well as pointer to data but that will introduce a dependency on tr1
|
// as well as pointer to data but that will introduce a dependency on tr1
|
||||||
@@ -1549,7 +1560,6 @@ MATCHER_P6(Contains, str1, str2, str3, str4, str5, str6, "") {
|
|||||||
data.find(str5) != std::string::npos &&
|
data.find(str5) != std::string::npos &&
|
||||||
data.find(str6) != std::string::npos);
|
data.find(str6) != std::string::npos);
|
||||||
}
|
}
|
||||||
|
|
||||||
MATCHER_P7(Contains, str1, str2, str3, str4, str5, str6, map7, "") {
|
MATCHER_P7(Contains, str1, str2, str3, str4, str5, str6, map7, "") {
|
||||||
// Estimating the length of data. We can have gmock provide length
|
// Estimating the length of data. We can have gmock provide length
|
||||||
// as well as pointer to data but that will introduce a dependency on tr1
|
// as well as pointer to data but that will introduce a dependency on tr1
|
||||||
@@ -2140,6 +2150,8 @@ TEST_F(DeviceFilesTest, ReserveLicenseIdsDoesNotUseFileSystem) {
|
|||||||
EXPECT_TRUE(device_files.ReserveLicenseId(license_test_data[i].key_set_id));
|
EXPECT_TRUE(device_files.ReserveLicenseId(license_test_data[i].key_set_id));
|
||||||
// Validate that the license IDs are actually reserved.
|
// Validate that the license IDs are actually reserved.
|
||||||
EXPECT_TRUE(device_files.LicenseExists(license_test_data[i].key_set_id));
|
EXPECT_TRUE(device_files.LicenseExists(license_test_data[i].key_set_id));
|
||||||
|
// Unreserve these IDs to avoid polluting other tests.
|
||||||
|
EXPECT_TRUE(device_files.UnreserveLicenseId(license_test_data[i].key_set_id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2205,6 +2217,7 @@ TEST_P(DeviceFilesUsageInfoTest, Store) {
|
|||||||
std::string pst(GenerateRandomData(kProviderSessionTokenLen));
|
std::string pst(GenerateRandomData(kProviderSessionTokenLen));
|
||||||
std::string license_request(GenerateRandomData(kLicenseRequestLen));
|
std::string license_request(GenerateRandomData(kLicenseRequestLen));
|
||||||
std::string license(GenerateRandomData(kLicenseLen));
|
std::string license(GenerateRandomData(kLicenseLen));
|
||||||
|
std::string key_set_id(GenerateRandomData(kKeySetIdLen));
|
||||||
std::string path =
|
std::string path =
|
||||||
device_base_path_ + DeviceFiles::GetUsageInfoFileName(app_id);
|
device_base_path_ + DeviceFiles::GetUsageInfoFileName(app_id);
|
||||||
|
|
||||||
@@ -2234,8 +2247,10 @@ TEST_P(DeviceFilesUsageInfoTest, Store) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
EXPECT_CALL(file,
|
EXPECT_CALL(file,
|
||||||
Write(Contains(pst, license_request, license, data.size()),
|
Write(Contains(pst, license_request, license, key_set_id,
|
||||||
Gt(pst.size() + license_request.size() + license.size())))
|
data.size()),
|
||||||
|
Gt(pst.size() + license_request.size() + license.size() +
|
||||||
|
key_set_id.size())))
|
||||||
.WillOnce(ReturnArg<1>());
|
.WillOnce(ReturnArg<1>());
|
||||||
|
|
||||||
DeviceFiles device_files;
|
DeviceFiles device_files;
|
||||||
@@ -2243,7 +2258,8 @@ TEST_P(DeviceFilesUsageInfoTest, Store) {
|
|||||||
device_files.SetTestFile(&file);
|
device_files.SetTestFile(&file);
|
||||||
|
|
||||||
ASSERT_TRUE(
|
ASSERT_TRUE(
|
||||||
device_files.StoreUsageInfo(pst, license_request, license, app_id));
|
device_files.StoreUsageInfo(pst, license_request, license, app_id,
|
||||||
|
key_set_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_P(DeviceFilesUsageInfoTest, Delete) {
|
TEST_P(DeviceFilesUsageInfoTest, Delete) {
|
||||||
|
|||||||
@@ -5285,9 +5285,7 @@ TEST_P(UsageTableTestWithMAC, OfflineBadNonce) {
|
|||||||
Session s;
|
Session s;
|
||||||
s.open();
|
s.open();
|
||||||
s.GenerateTestSessionKeys();
|
s.GenerateTestSessionKeys();
|
||||||
s.FillSimpleMessage(
|
s.FillSimpleMessage(0, wvoec_mock::kControlNonceOrEntry, 42, pst);
|
||||||
0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceOrEntry,
|
|
||||||
42, pst);
|
|
||||||
s.EncryptAndSign();
|
s.EncryptAndSign();
|
||||||
uint8_t* pst_ptr = s.encrypted_license().pst;
|
uint8_t* pst_ptr = s.encrypted_license().pst;
|
||||||
OEMCryptoResult sts = OEMCrypto_LoadKeys(
|
OEMCryptoResult sts = OEMCrypto_LoadKeys(
|
||||||
@@ -5305,9 +5303,7 @@ TEST_P(UsageTableTestWithMAC, OfflineEmptyPST) {
|
|||||||
Session s;
|
Session s;
|
||||||
s.open();
|
s.open();
|
||||||
s.GenerateTestSessionKeys();
|
s.GenerateTestSessionKeys();
|
||||||
s.FillSimpleMessage(
|
s.FillSimpleMessage(0, wvoec_mock::kControlNonceOrEntry, s.get_nonce());
|
||||||
0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceOrEntry,
|
|
||||||
s.get_nonce());
|
|
||||||
s.EncryptAndSign();
|
s.EncryptAndSign();
|
||||||
OEMCryptoResult sts = OEMCrypto_LoadKeys(
|
OEMCryptoResult sts = OEMCrypto_LoadKeys(
|
||||||
s.session_id(), s.message_ptr(), sizeof(MessageData), &s.signature()[0],
|
s.session_id(), s.message_ptr(), sizeof(MessageData), &s.signature()[0],
|
||||||
@@ -5367,8 +5363,7 @@ TEST_P(UsageTableTestWithMAC, BadRange) {
|
|||||||
Session s;
|
Session s;
|
||||||
s.open();
|
s.open();
|
||||||
s.GenerateTestSessionKeys();
|
s.GenerateTestSessionKeys();
|
||||||
s.FillSimpleMessage(0, wvoec_mock::kControlNonceOrEntry,
|
s.FillSimpleMessage(0, wvoec_mock::kControlNonceOrEntry,s.get_nonce(), pst);
|
||||||
s.get_nonce(), pst);
|
|
||||||
s.EncryptAndSign();
|
s.EncryptAndSign();
|
||||||
uint8_t* pst_ptr = s.license().pst; // Bad: not in encrypted_license.
|
uint8_t* pst_ptr = s.license().pst; // Bad: not in encrypted_license.
|
||||||
ASSERT_NE(
|
ASSERT_NE(
|
||||||
|
|||||||
Reference in New Issue
Block a user