Implement MediaDrm offline license support in Widevine hidl service.

Merged from http://go/wvgerrit/69723.

The new APIs are getOfflineLicenseIds, getOfflineLicenseState and
removeOfflineLicense. These methods are currently stubbed out in
Widevine hidl service. This CL completes the implementation.

Test: unit tests - libwvdrmdrmplugin_hidl_test

Test: GTS
  --test com.google.android.media.gts.MediaDrmTest#testWidevineApi29

bug: 117570686
Change-Id: I96ffb75f453e36e931effefd3664b5faa8d69d30
This commit is contained in:
Edwin Wong
2019-01-21 17:07:43 -08:00
parent 19c4996b3c
commit 54104c7a22
12 changed files with 462 additions and 30 deletions

View File

@@ -212,6 +212,17 @@ class CdmEngine {
CdmSecurityLevel security_level, CdmSecurityLevel security_level,
const std::string& key_set_id); const std::string& key_set_id);
// Get offline license status: active, release or unknown
virtual CdmResponseType GetOfflineLicenseState(
const std::string& key_set_id,
CdmSecurityLevel security_level,
CdmOfflineLicenseState* license_state);
// Remove offline license with the given key_set_id
virtual CdmResponseType RemoveOfflineLicense(
const std::string& key_set_id,
CdmSecurityLevel security_level);
// Usage related methods for streaming licenses // Usage related methods for streaming licenses
// Retrieve a random usage info from the list of all usage infos for this app // Retrieve a random usage info from the list of all usage infos for this app
// id. // id.

View File

@@ -194,7 +194,7 @@ class CdmSession {
const std::string& signature); const std::string& signature);
virtual CdmResponseType SetDecryptHash(uint32_t frame_number, virtual CdmResponseType SetDecryptHash(uint32_t frame_number,
const std::string& hash); const std::string& hash);
virtual CdmResponseType GetDecryptHashError(std::string* hash_error_string); virtual CdmResponseType GetDecryptHashError(std::string* hash_error_string);

View File

@@ -91,6 +91,7 @@ class DeviceFiles {
const CdmUsageEntry& usage_entry, const CdmUsageEntry& usage_entry,
uint32_t usage_entry_number, uint32_t usage_entry_number,
ResponseType* result); ResponseType* result);
virtual bool RetrieveLicense( virtual bool RetrieveLicense(
const std::string& key_set_id, LicenseState* state, const std::string& key_set_id, LicenseState* state,
CdmInitData* pssh_data, CdmKeyMessage* key_request, CdmInitData* pssh_data, CdmKeyMessage* key_request,

View File

@@ -40,6 +40,12 @@ enum CdmKeyRequestType {
kKeyRequestTypeRelease, kKeyRequestTypeRelease,
}; };
enum CdmOfflineLicenseState {
kLicenseStateActive,
kLicenseStateReleasing,
kLicenseStateUnknown,
};
enum CdmResponseType { enum CdmResponseType {
NO_ERROR = 0, NO_ERROR = 0,
UNKNOWN_ERROR = 1, UNKNOWN_ERROR = 1,
@@ -355,6 +361,11 @@ enum CdmResponseType {
INVALID_LICENSE_TYPE_2 = 310, INVALID_LICENSE_TYPE_2 = 310,
SIGNATURE_NOT_FOUND_2 = 311, SIGNATURE_NOT_FOUND_2 = 311,
SESSION_KEYS_NOT_FOUND_2 = 312, SESSION_KEYS_NOT_FOUND_2 = 312,
GET_OFFLINE_LICENSE_STATE_ERROR_1 = 313,
GET_OFFLINE_LICENSE_STATE_ERROR_2 = 314,
REMOVE_OFFLINE_LICENSE_ERROR_1 = 315,
REMOVE_OFFLINE_LICENSE_ERROR_2 = 316,
SESSION_NOT_FOUND_21 = 317,
// Don't forget to add new values to ../test/test_printers.cpp. // Don't forget to add new values to ../test/test_printers.cpp.
}; };

View File

@@ -27,6 +27,18 @@ namespace {
const uint64_t kReleaseSessionTimeToLive = 60; // seconds const uint64_t kReleaseSessionTimeToLive = 60; // seconds
const uint32_t kUpdateUsageInformationPeriod = 60; // seconds const uint32_t kUpdateUsageInformationPeriod = 60; // seconds
const size_t kUsageReportsPerRequest = 1; const size_t kUsageReportsPerRequest = 1;
wvcdm::CdmOfflineLicenseState MapDeviceFilesLicenseState(
wvcdm::DeviceFiles::LicenseState state) {
switch (state) {
case wvcdm::DeviceFiles::LicenseState::kLicenseStateActive:
return wvcdm::kLicenseStateActive;
case wvcdm::DeviceFiles::LicenseState::kLicenseStateReleasing:
return wvcdm::kLicenseStateReleasing;
default:
return wvcdm::kLicenseStateUnknown;
}
}
} // namespace } // namespace
namespace wvcdm { namespace wvcdm {
@@ -1100,6 +1112,98 @@ CdmResponseType CdmEngine::DeleteUsageRecord(const std::string& app_id,
return status; return status;
} }
CdmResponseType CdmEngine::GetOfflineLicenseState(
const CdmKeySetId &key_set_id,
CdmSecurityLevel security_level,
CdmOfflineLicenseState* license_state) {
DeviceFiles handle(file_system_);
if (!handle.Init(security_level)) {
LOGE("CdmEngine::GetOfflineLicenseState: cannot initialize device files");
return GET_OFFLINE_LICENSE_STATE_ERROR_1;
}
DeviceFiles::LicenseState state;
// Dummy arguments to make the RetrieveLicense call,
// do not care about the return value except for license state.
CdmAppParameterMap app_parameters;
CdmKeyMessage key_request;
CdmKeyResponse key_response;
CdmInitData offline_init_data;
CdmKeyMessage offline_key_renewal_request;
CdmKeyResponse offline_key_renewal_response;
CdmUsageEntry usage_entry;
int64_t grace_period_end_time;
int64_t last_playback_time;
std::string offline_release_server_url;
int64_t playback_start_time;
uint32_t usage_entry_number;
DeviceFiles::ResponseType sub_error_code = DeviceFiles::kNoError;
if (handle.RetrieveLicense(key_set_id, &state, &offline_init_data,
&key_request, &key_response,
&offline_key_renewal_request, &offline_key_renewal_response,
&offline_release_server_url,
&playback_start_time, &last_playback_time, &grace_period_end_time,
&app_parameters, &usage_entry, &usage_entry_number,
&sub_error_code)) {
*license_state = MapDeviceFilesLicenseState(state);
} else {
LOGE("CdmEngine::GetOfflineLicenseState:: failed to retrieve license state "
"key set id = %s",
key_set_id.c_str());
return GET_OFFLINE_LICENSE_STATE_ERROR_2;
}
return NO_ERROR;
}
CdmResponseType CdmEngine::RemoveOfflineLicense(
const CdmKeySetId &key_set_id,
CdmSecurityLevel security_level) {
UsagePropertySet property_set;
property_set.set_security_level(
security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault);
DeviceFiles handle(file_system_);
CdmResponseType sts = OpenKeySetSession(key_set_id,
&property_set, nullptr /* event listener */);
if (sts != NO_ERROR) {
if (!handle.Init(security_level)) {
LOGE("CdmEngine::RemoveOfflineLicense: cannot initialize device files");
}
handle.DeleteLicense(key_set_id);
return REMOVE_OFFLINE_LICENSE_ERROR_1;
}
CdmSessionId session_id;
CdmAppParameterMap dummy_app_params;
InitializationData dummy_init_data("", "", "");
CdmKeyRequest key_request;
// Calling with no session_id is okay
sts = GenerateKeyRequest(session_id, key_set_id, dummy_init_data,
kLicenseTypeRelease, dummy_app_params, &key_request);
if (sts == KEY_MESSAGE) {
std::unique_lock<std::mutex> lock(release_key_sets_lock_);
CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(key_set_id);
if (iter == release_key_sets_.end()) {
LOGE("CdmEngine::RemoveOfflineLicense: key set id not found = %s",
key_set_id.c_str());
sts = REMOVE_OFFLINE_LICENSE_ERROR_2;
} else {
session_id = iter->second.first;
sts = RemoveLicense(session_id);
}
}
if (sts != NO_ERROR) {
LOGE("CdmEngine: GenerateKeyRequest error=%d", sts);
handle.DeleteLicense(key_set_id);
}
CloseKeySetSession(key_set_id);
return sts;
}
CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id,
const CdmSecureStopId& ssid, const CdmSecureStopId& ssid,
CdmUsageInfo* usage_info) { CdmUsageInfo* usage_info) {

View File

@@ -154,6 +154,12 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) {
case GET_LICENSE_ERROR: case GET_LICENSE_ERROR:
*os << "GET_LICENSE_ERROR"; *os << "GET_LICENSE_ERROR";
break; break;
case GET_OFFLINE_LICENSE_STATE_ERROR_1:
*os << "GET_OFFLINE_LICENSE_STATE_ERROR_1";
break;
case GET_OFFLINE_LICENSE_STATE_ERROR_2:
*os << "GET_OFFLINE_LICENSE_STATE_ERROR_2";
break;
case GET_RELEASED_LICENSE_ERROR: case GET_RELEASED_LICENSE_ERROR:
*os << "GET_RELEASED_LICENSE_ERROR"; *os << "GET_RELEASED_LICENSE_ERROR";
break; break;
@@ -784,6 +790,9 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) {
case SESSION_NOT_FOUND_20: case SESSION_NOT_FOUND_20:
*os << "SESSION_NOT_FOUND_20"; *os << "SESSION_NOT_FOUND_20";
break; break;
case SESSION_NOT_FOUND_21:
*os << "SESSION_NOT_FOUND_21";
break;
case INVALID_DECRYPT_HASH_FORMAT: case INVALID_DECRYPT_HASH_FORMAT:
*os << "INVALID_DECRYPT_HASH_FORMAT"; *os << "INVALID_DECRYPT_HASH_FORMAT";
break; break;
@@ -825,6 +834,11 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) {
break; break;
case SESSION_KEYS_NOT_FOUND_2: case SESSION_KEYS_NOT_FOUND_2:
*os << "SESSION_KEYS_NOT_FOUND_2"; *os << "SESSION_KEYS_NOT_FOUND_2";
case REMOVE_OFFLINE_LICENSE_ERROR_1:
*os << "REMOVE_OFFLINE_LICENSE_ERROR_1";
break;
case REMOVE_OFFLINE_LICENSE_ERROR_2:
*os << "REMOVE_OFFLINE_LICENSE_ERROR_2";
break; break;
default: default:
*os << "Unknown CdmResponseType"; *os << "Unknown CdmResponseType";

View File

@@ -147,6 +147,26 @@ class WvContentDecryptionModule : public android::RefBase, public TimerHandler {
virtual CdmResponseType GetDecryptHashError(const CdmSessionId& session_id, virtual CdmResponseType GetDecryptHashError(const CdmSessionId& session_id,
std::string* hash_error_string); std::string* hash_error_string);
// Return the list of key_set_ids stored on the current (origin-specific)
// file system.
virtual CdmResponseType ListStoredLicenses(
CdmSecurityLevel security_level,
const CdmIdentifier& identifier,
std::vector<CdmKeySetId>* key_set_ids);
// Retrieve offline license state using key_set_id.
virtual CdmResponseType GetOfflineLicenseState(
const CdmKeySetId& key_set_id,
CdmSecurityLevel security_level,
const CdmIdentifier& identifier,
CdmOfflineLicenseState* licenseState);
// Remove offline license using key_set_id.
virtual CdmResponseType RemoveOfflineLicense(
const CdmKeySetId& key_set_id,
CdmSecurityLevel security_level,
const CdmIdentifier& identifier);
private: private:
struct CdmInfo { struct CdmInfo {
CdmInfo(); CdmInfo();

View File

@@ -525,4 +525,32 @@ uint32_t WvContentDecryptionModule::GenerateSessionSharingId() {
return ++next_session_sharing_id; return ++next_session_sharing_id;
} }
CdmResponseType WvContentDecryptionModule::ListStoredLicenses(
CdmSecurityLevel security_level,
const CdmIdentifier& identifier,
std::vector<CdmKeySetId>* key_set_ids) {
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
return cdm_engine->ListStoredLicenses(
security_level, key_set_ids);
}
CdmResponseType WvContentDecryptionModule::GetOfflineLicenseState(
const CdmKeySetId& key_set_id,
CdmSecurityLevel security_level,
const CdmIdentifier& identifier,
CdmOfflineLicenseState* license_state) {
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
return cdm_engine->GetOfflineLicenseState(
key_set_id, security_level, license_state);
}
CdmResponseType WvContentDecryptionModule::RemoveOfflineLicense(
const CdmKeySetId& key_set_id,
CdmSecurityLevel security_level,
const CdmIdentifier& identifier) {
CdmEngine* cdm_engine = EnsureCdmForIdentifier(identifier);
return cdm_engine->RemoveOfflineLicense(
key_set_id, security_level);
}
} // namespace wvcdm } // namespace wvcdm

View File

@@ -235,6 +235,7 @@ static android::status_t mapCdmResponseType(wvcdm::CdmResponseType res) {
case wvcdm::SESSION_NOT_FOUND_18: case wvcdm::SESSION_NOT_FOUND_18:
case wvcdm::SESSION_NOT_FOUND_19: case wvcdm::SESSION_NOT_FOUND_19:
case wvcdm::SESSION_NOT_FOUND_20: case wvcdm::SESSION_NOT_FOUND_20:
case wvcdm::SESSION_NOT_FOUND_21:
return android::ERROR_DRM_SESSION_NOT_OPENED; return android::ERROR_DRM_SESSION_NOT_OPENED;
case wvcdm::SESSION_KEYS_NOT_FOUND: case wvcdm::SESSION_KEYS_NOT_FOUND:
return kSessionKeysNotFound; return kSessionKeysNotFound;
@@ -557,6 +558,15 @@ static android::status_t mapCdmResponseType(wvcdm::CdmResponseType res) {
return kSignatureNotFound2; return kSignatureNotFound2;
case wvcdm::SESSION_KEYS_NOT_FOUND_2: case wvcdm::SESSION_KEYS_NOT_FOUND_2:
return kSessionKeysNotFound2; return kSessionKeysNotFound2;
// This error is only returned in API 29 by the hidl service.
// It should never be used in the legacy plugin.
// It is mapped here to clear the compiler warning.
case wvcdm::GET_OFFLINE_LICENSE_STATE_ERROR_1:
case wvcdm::GET_OFFLINE_LICENSE_STATE_ERROR_2:
case wvcdm::REMOVE_OFFLINE_LICENSE_ERROR_1:
case wvcdm::REMOVE_OFFLINE_LICENSE_ERROR_2:
return android::ERROR_DRM_UNKNOWN;
} }
// Return here instead of as a default case so that the compiler will warn // Return here instead of as a default case so that the compiler will warn

View File

@@ -63,6 +63,7 @@ static Status mapCdmResponseType(wvcdm::CdmResponseType res) {
case wvcdm::SESSION_NOT_FOUND_18: case wvcdm::SESSION_NOT_FOUND_18:
case wvcdm::SESSION_NOT_FOUND_19: case wvcdm::SESSION_NOT_FOUND_19:
case wvcdm::SESSION_NOT_FOUND_20: case wvcdm::SESSION_NOT_FOUND_20:
case wvcdm::SESSION_NOT_FOUND_21:
return Status::ERROR_DRM_SESSION_NOT_OPENED; return Status::ERROR_DRM_SESSION_NOT_OPENED;
case wvcdm::DECRYPT_ERROR: case wvcdm::DECRYPT_ERROR:
@@ -321,6 +322,10 @@ static Status mapCdmResponseType(wvcdm::CdmResponseType res) {
case wvcdm::INVALID_LICENSE_TYPE_2: case wvcdm::INVALID_LICENSE_TYPE_2:
case wvcdm::SIGNATURE_NOT_FOUND_2: case wvcdm::SIGNATURE_NOT_FOUND_2:
case wvcdm::SESSION_KEYS_NOT_FOUND_2: case wvcdm::SESSION_KEYS_NOT_FOUND_2:
case wvcdm::GET_OFFLINE_LICENSE_STATE_ERROR_1:
case wvcdm::GET_OFFLINE_LICENSE_STATE_ERROR_2:
case wvcdm::REMOVE_OFFLINE_LICENSE_ERROR_1:
case wvcdm::REMOVE_OFFLINE_LICENSE_ERROR_2:
ALOGW("Returns UNKNOWN error for legacy status: %d", res); ALOGW("Returns UNKNOWN error for legacy status: %d", res);
return Status::ERROR_DRM_UNKNOWN; return Status::ERROR_DRM_UNKNOWN;

View File

@@ -55,6 +55,7 @@ using wvcdm::CdmProvisioningRequest;
using wvcdm::CdmProvisioningResponse; using wvcdm::CdmProvisioningResponse;
using wvcdm::CdmQueryMap; using wvcdm::CdmQueryMap;
using wvcdm::CdmSecureStopId; using wvcdm::CdmSecureStopId;
using wvcdm::CdmSecurityLevel;
using wvcdm::CdmUsageInfo; using wvcdm::CdmUsageInfo;
using wvcdm::CdmUsageInfoReleaseMessage; using wvcdm::CdmUsageInfoReleaseMessage;
using wvcdm::KeyId; using wvcdm::KeyId;
@@ -132,7 +133,7 @@ KeyStatusType ConvertFromCdmKeyStatus(CdmKeyStatus keyStatus) {
} }
} }
HdcpLevel mapHdcpLevel(const std::string level) { HdcpLevel mapHdcpLevel(const std::string& level) {
if (level == wvcdm::QUERY_VALUE_HDCP_V1) if (level == wvcdm::QUERY_VALUE_HDCP_V1)
return HdcpLevel::HDCP_V1; return HdcpLevel::HDCP_V1;
else if (level == wvcdm::QUERY_VALUE_HDCP_V2_0) else if (level == wvcdm::QUERY_VALUE_HDCP_V2_0)
@@ -1048,20 +1049,91 @@ Return<void> WVDrmPlugin::getSecurityLevel(
Return<void> WVDrmPlugin::getOfflineLicenseKeySetIds( Return<void> WVDrmPlugin::getOfflineLicenseKeySetIds(
getOfflineLicenseKeySetIds_cb _hidl_cb) { getOfflineLicenseKeySetIds_cb _hidl_cb) {
std::vector<KeySetId> keySetIds; std::vector<std::vector<uint8_t> > keySetIds;
std::vector<KeySetId> keySetIdsVec;
_hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, keySetIds); CdmIdentifier identifier;
Status status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier);
if (status != Status::OK) {
_hidl_cb(status, toHidlVec(keySetIdsVec));
return Void();
}
std::vector<CdmSecurityLevel> levels = {
wvcdm::kSecurityLevelL1, wvcdm::kSecurityLevelL3 };
CdmResponseType res = wvcdm::UNKNOWN_ERROR;
for (auto level : levels) {
std::vector<CdmKeySetId> cdmKeySetIds;
res = mCDM->ListStoredLicenses(level, identifier, &cdmKeySetIds);
if (isCdmResponseTypeSuccess(res)) {
keySetIds.clear();
for (auto id : cdmKeySetIds) {
keySetIds.push_back(StrToVector(id));
}
for (auto id : keySetIds) {
keySetIdsVec.push_back(id);
}
}
}
_hidl_cb(mapCdmResponseType(res), toHidlVec(keySetIdsVec));
return Void(); return Void();
} }
Return<void> WVDrmPlugin::getOfflineLicenseState(const KeySetId &keySetId, Return<void> WVDrmPlugin::getOfflineLicenseState(const KeySetId &keySetId,
getOfflineLicenseState_cb _hidl_cb) { getOfflineLicenseState_cb _hidl_cb) {
OfflineLicenseState licenseState = OfflineLicenseState::UNKNOWN;
if (!keySetId.size()) { if (!keySetId.size()) {
_hidl_cb(Status::BAD_VALUE, OfflineLicenseState::UNKNOWN); _hidl_cb(Status::BAD_VALUE, licenseState);
return Void(); return Void();
} }
_hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, OfflineLicenseState::UNKNOWN); CdmIdentifier identifier;
Status status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier);
if (status != Status::OK) {
_hidl_cb(status, licenseState);
return Void();
}
CdmResponseType res = wvcdm::UNKNOWN_ERROR;
CdmKeySetId keySetIdStr(keySetId.begin(), keySetId.end());
wvcdm::CdmOfflineLicenseState state = wvcdm::kLicenseStateUnknown;
res = mCDM->GetOfflineLicenseState(
keySetIdStr,
wvcdm::kSecurityLevelL1,
identifier,
&state);
if (!isCdmResponseTypeSuccess(res)) {
// try L3
res = mCDM->GetOfflineLicenseState(
keySetIdStr,
wvcdm::kSecurityLevelL3,
identifier,
&state);
if (!isCdmResponseTypeSuccess(res)) {
_hidl_cb(Status::BAD_VALUE, licenseState);
return Void();
}
}
switch(state) {
case wvcdm::kLicenseStateActive:
licenseState = OfflineLicenseState::USABLE;
break;
case wvcdm::kLicenseStateReleasing:
licenseState = OfflineLicenseState::INACTIVE;
break;
default:
licenseState = OfflineLicenseState::UNKNOWN;
ALOGE("Return unknown offline license state for %s", keySetIdStr.c_str());
break;
}
_hidl_cb(mapCdmResponseType(res), licenseState);
return Void(); return Void();
} }
@@ -1070,7 +1142,31 @@ Return<Status> WVDrmPlugin::removeOfflineLicense(const KeySetId &keySetId) {
return Status::BAD_VALUE; return Status::BAD_VALUE;
} }
return Status::ERROR_DRM_CANNOT_HANDLE; CdmIdentifier identifier;
Status status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier);
if (status != Status::OK) {
return status;
}
CdmResponseType res = wvcdm::UNKNOWN_ERROR;
res = mCDM->RemoveOfflineLicense(
std::string(keySetId.begin(), keySetId.end()),
wvcdm::kSecurityLevelL1,
identifier);
if (!isCdmResponseTypeSuccess(res)) {
CdmResponseType res = mCDM->RemoveOfflineLicense(
std::string(keySetId.begin(), keySetId.end()),
wvcdm::kSecurityLevelL3,
identifier);
if (isCdmResponseTypeSuccess(res)) {
status = Status::OK;
} else {
status = Status::ERROR_DRM_INVALID_STATE;
}
}
return status;
} }
Return<void> WVDrmPlugin::getPropertyString(const hidl_string& propertyName, Return<void> WVDrmPlugin::getPropertyString(const hidl_string& propertyName,

View File

@@ -78,6 +78,7 @@ using wvcdm::CdmKeyRequest;
using wvcdm::CdmKeySetId; using wvcdm::CdmKeySetId;
using wvcdm::CdmKeySystem; using wvcdm::CdmKeySystem;
using wvcdm::CdmLicenseType; using wvcdm::CdmLicenseType;
using wvcdm::CdmOfflineLicenseState;
using wvcdm::CdmProvisioningRequest; using wvcdm::CdmProvisioningRequest;
using wvcdm::CdmProvisioningResponse; using wvcdm::CdmProvisioningResponse;
using wvcdm::CdmQueryMap; using wvcdm::CdmQueryMap;
@@ -159,7 +160,8 @@ class MockCDM : public WvContentDecryptionModule {
MOCK_METHOD2(RestoreKey, CdmResponseType(const CdmSessionId&, MOCK_METHOD2(RestoreKey, CdmResponseType(const CdmSessionId&,
const CdmKeySetId&)); const CdmKeySetId&));
MOCK_METHOD3(QueryStatus, CdmResponseType(wvcdm::SecurityLevel, const std::string&, MOCK_METHOD3(QueryStatus, CdmResponseType(wvcdm::SecurityLevel,
const std::string&,
std::string*)); std::string*));
MOCK_METHOD2(QueryKeyStatus, CdmResponseType(const CdmSessionId&, MOCK_METHOD2(QueryKeyStatus, CdmResponseType(const CdmSessionId&,
@@ -202,7 +204,24 @@ class MockCDM : public WvContentDecryptionModule {
MOCK_METHOD2(GetMetrics, CdmResponseType(const CdmIdentifier&, MOCK_METHOD2(GetMetrics, CdmResponseType(const CdmIdentifier&,
drm_metrics::WvCdmMetrics*)); drm_metrics::WvCdmMetrics*));
MOCK_METHOD2(GetDecryptHashError, CdmResponseType(const CdmSessionId&, std::string*)); MOCK_METHOD2(GetDecryptHashError,
CdmResponseType(const CdmSessionId&, std::string*));
MOCK_METHOD3(ListStoredLicenses,
CdmResponseType(CdmSecurityLevel,
const CdmIdentifier&,
std::vector<std::string>*));
MOCK_METHOD4(GetOfflineLicenseState,
CdmResponseType(const std::string&,
CdmSecurityLevel,
const CdmIdentifier&,
CdmOfflineLicenseState*));
MOCK_METHOD3(RemoveOfflineLicense,
CdmResponseType(const std::string&,
CdmSecurityLevel,
const CdmIdentifier&));
}; };
class MockCrypto : public WVGenericCryptoInterface { class MockCrypto : public WVGenericCryptoInterface {
@@ -260,8 +279,11 @@ MATCHER_P(HasOrigin, origin, "") {
class WVDrmPluginTest : public Test { class WVDrmPluginTest : public Test {
protected: protected:
static const uint32_t kKeySetIdSize = 32;
static const uint32_t kSessionIdSize = 16; static const uint32_t kSessionIdSize = 16;
uint8_t keySetIdRaw[kKeySetIdSize];
uint8_t sessionIdRaw[kSessionIdSize]; uint8_t sessionIdRaw[kSessionIdSize];
std::vector<uint8_t> keySetId;
std::vector<uint8_t> sessionId; std::vector<uint8_t> sessionId;
CdmSessionId cdmSessionId; CdmSessionId cdmSessionId;
@@ -269,12 +291,19 @@ class WVDrmPluginTest : public Test {
// Fill the session ID // Fill the session ID
FILE* fp = fopen("/dev/urandom", "r"); FILE* fp = fopen("/dev/urandom", "r");
fread(sessionIdRaw, sizeof(uint8_t), kSessionIdSize, fp); fread(sessionIdRaw, sizeof(uint8_t), kSessionIdSize, fp);
fclose(fp);
memcpy(sessionIdRaw, SESSION_ID_PREFIX, sizeof(SESSION_ID_PREFIX) - 1); memcpy(sessionIdRaw, SESSION_ID_PREFIX, sizeof(SESSION_ID_PREFIX) - 1);
sessionId.assign(sessionIdRaw, sessionIdRaw + kSessionIdSize); sessionId.assign(sessionIdRaw, sessionIdRaw + kSessionIdSize);
cdmSessionId.assign(sessionId.begin(), sessionId.end()); cdmSessionId.assign(sessionId.begin(), sessionId.end());
fread(keySetIdRaw, sizeof(uint8_t), kKeySetIdSize, fp);
fclose(fp);
memcpy(keySetIdRaw, KEY_SET_ID_PREFIX, sizeof(KEY_SET_ID_PREFIX));
CdmKeySetId cdmKeySetId(reinterpret_cast<char*>(keySetIdRaw),
kKeySetIdSize);
keySetId.assign(keySetIdRaw, keySetIdRaw + kKeySetIdSize);
// Set default return values for gMock // Set default return values for gMock
DefaultValue<CdmResponseType>::Set(wvcdm::NO_ERROR); DefaultValue<CdmResponseType>::Set(wvcdm::NO_ERROR);
DefaultValue<OEMCryptoResult>::Set(OEMCrypto_SUCCESS); DefaultValue<OEMCryptoResult>::Set(OEMCrypto_SUCCESS);
@@ -728,15 +757,6 @@ TEST_F(WVDrmPluginTest, RestoresKeys) {
StrictMock<MockCrypto> crypto; StrictMock<MockCrypto> crypto;
std::string appPackageName; std::string appPackageName;
static const size_t kKeySetIdSize = 32;
uint8_t keySetIdRaw[kKeySetIdSize];
FILE* fp = fopen("/dev/urandom", "r");
fread(keySetIdRaw, sizeof(uint8_t), kKeySetIdSize, fp);
fclose(fp);
std::vector<uint8_t> keySetId;
keySetId.assign(keySetIdRaw, keySetIdRaw + kKeySetIdSize);
EXPECT_CALL(*cdm, RestoreKey(cdmSessionId, EXPECT_CALL(*cdm, RestoreKey(cdmSessionId,
ElementsAreArray(keySetIdRaw, kKeySetIdSize))) ElementsAreArray(keySetIdRaw, kKeySetIdSize)))
.Times(1); .Times(1);
@@ -1039,7 +1059,7 @@ TEST_F(WVDrmPluginTest, GetsSecureStops) {
.WillOnce(DoAll(SetArgPointee<2>(cdmStops), .WillOnce(DoAll(SetArgPointee<2>(cdmStops),
testing::Return(wvcdm::NO_ERROR))); testing::Return(wvcdm::NO_ERROR)));
std::list<std::vector<uint8_t> > stops; std::vector<std::vector<uint8_t> > stops;
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false); WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
Status status = plugin.setPropertyString(hidl_string("appId"), Status status = plugin.setPropertyString(hidl_string("appId"),
@@ -1058,17 +1078,11 @@ TEST_F(WVDrmPluginTest, GetsSecureStops) {
} }
}); });
std::list<std::vector<uint8_t> >::iterator iter = stops.begin(); size_t index = 0;
uint32_t rawIter = 0; for (auto stop : stops) {
while (rawIter < kStopCount && iter != stops.end()) { EXPECT_THAT(stop, ElementsAreArray(stopsRaw[index++], kStopSize));
EXPECT_THAT(*iter, ElementsAreArray(stopsRaw[rawIter], kStopSize));
++iter;
++rawIter;
} }
// Assert that both lists are the same length EXPECT_EQ(kStopCount, index);
EXPECT_EQ(kStopCount, rawIter);
EXPECT_EQ(stops.end(), iter);
} }
TEST_F(WVDrmPluginTest, ReleasesAllSecureStops) { TEST_F(WVDrmPluginTest, ReleasesAllSecureStops) {
@@ -2715,6 +2729,124 @@ TEST_F(WVDrmPluginTest, DoesNotSetDecryptHashProperties) {
ASSERT_EQ(Status::OK, status); ASSERT_EQ(Status::OK, status);
} }
TEST_F(WVDrmPluginTest, GetOfflineLicenseIds) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
std::string appPackageName;
const uint32_t kLicenseCount = 5;
uint8_t mockIdsRaw[kLicenseCount * 2][kKeySetIdSize];
FILE* fp = fopen("/dev/urandom", "r");
for (uint32_t i = 0; i < kLicenseCount * 2; ++i) {
fread(mockIdsRaw[i], sizeof(uint8_t), kKeySetIdSize, fp);
}
fclose(fp);
std::vector<std::string> mockIdsL1;
for (uint32_t i = 0; i < kLicenseCount; ++i) {
mockIdsL1.push_back(std::string(mockIdsRaw[i],
mockIdsRaw[i] + kKeySetIdSize));
}
std::vector<std::string> mockIdsL3;
for (uint32_t i = 0; i < kLicenseCount; ++i) {
mockIdsL3.push_back(std::string(mockIdsRaw[i+5],
mockIdsRaw[i+5] + kKeySetIdSize));
}
EXPECT_CALL(*cdm,
ListStoredLicenses(kSecurityLevelL1, HasOrigin(EMPTY_ORIGIN), _))
.WillOnce(DoAll(SetArgPointee<2>(mockIdsL1),
testing::Return(wvcdm::NO_ERROR)));
EXPECT_CALL(*cdm,
ListStoredLicenses(kSecurityLevelL3, HasOrigin(EMPTY_ORIGIN), _))
.WillOnce(DoAll(SetArgPointee<2>(mockIdsL3),
testing::Return(wvcdm::NO_ERROR)));
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
std::vector<std::vector<uint8_t> > offlineIds;
offlineIds.clear();
plugin.getOfflineLicenseKeySetIds(
[&](Status status, hidl_vec<KeySetId> hKeySetIds) {
ASSERT_EQ(Status::OK, status);
std::vector<KeySetId> ids(hKeySetIds);
for (auto id : ids) {
offlineIds.push_back(id);
}
});
size_t index = 0;
for (auto id : offlineIds) {
EXPECT_THAT(id, ElementsAreArray(mockIdsRaw[index++], kKeySetIdSize));
}
EXPECT_EQ(kLicenseCount * 2, index);
}
TEST_F(WVDrmPluginTest, GetOfflineLicenseState) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
std::string appPackageName;
EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_SECURITY_LEVEL, _))
.WillRepeatedly(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L1),
testing::Return(wvcdm::NO_ERROR)));
EXPECT_CALL(*cdm,
GetOfflineLicenseState(_, kSecurityLevelL1,
HasOrigin(EMPTY_ORIGIN), _))
.WillOnce(DoAll(SetArgPointee<3>(wvcdm::kLicenseStateActive),
testing::Return(wvcdm::NO_ERROR)))
.WillOnce(DoAll(SetArgPointee<3>(wvcdm::kLicenseStateReleasing),
testing::Return(wvcdm::NO_ERROR)))
.WillOnce(DoAll(SetArgPointee<3>(wvcdm::kLicenseStateUnknown),
testing::Return(wvcdm::NO_ERROR)));
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
Status status = plugin.setPropertyString(
hidl_string("securityLevel"), hidl_string("L1"));
ASSERT_EQ(Status::OK, status);
plugin.getOfflineLicenseState(toHidlVec(keySetId),
[&](Status status, OfflineLicenseState hLicenseState) {
ASSERT_EQ(Status::OK, status);
ASSERT_EQ(OfflineLicenseState::USABLE, hLicenseState);
});
plugin.getOfflineLicenseState(toHidlVec(keySetId),
[&](Status status, OfflineLicenseState hLicenseState) {
ASSERT_EQ(Status::OK, status);
ASSERT_EQ(OfflineLicenseState::INACTIVE, hLicenseState);
});
plugin.getOfflineLicenseState(toHidlVec(keySetId),
[&](Status status, OfflineLicenseState hLicenseState) {
ASSERT_EQ(Status::OK, status);
ASSERT_EQ(OfflineLicenseState::UNKNOWN, hLicenseState);
});
}
TEST_F(WVDrmPluginTest, RemoveOfflineLicense) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
StrictMock<MockCrypto> crypto;
std::string appPackageName;
EXPECT_CALL(*cdm,
RemoveOfflineLicense(_, kSecurityLevelL1,
HasOrigin(EMPTY_ORIGIN)))
.Times(1);
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
Status status = plugin.removeOfflineLicense(toHidlVec(keySetId));
ASSERT_EQ(Status::OK, status);
}
} // namespace widevine } // namespace widevine
} // namespace V1_2 } // namespace V1_2
} // namespace drm } // namespace drm