From 54104c7a22c8fbc2ab5d49903cfcf5457b3b3660 Mon Sep 17 00:00:00 2001 From: Edwin Wong Date: Mon, 21 Jan 2019 17:07:43 -0800 Subject: [PATCH] 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 --- libwvdrmengine/cdm/core/include/cdm_engine.h | 11 ++ libwvdrmengine/cdm/core/include/cdm_session.h | 2 +- .../cdm/core/include/device_files.h | 1 + .../cdm/core/include/wv_cdm_types.h | 11 ++ libwvdrmengine/cdm/core/src/cdm_engine.cpp | 104 ++++++++++ .../cdm/core/test/test_printers.cpp | 14 ++ .../include/wv_content_decryption_module.h | 20 ++ .../cdm/src/wv_content_decryption_module.cpp | 28 +++ libwvdrmengine/include/mapErrors-inl.h | 10 + libwvdrmengine/include_hidl/mapErrors-inl.h | 5 + .../mediadrm/src_hidl/WVDrmPlugin.cpp | 108 ++++++++++- .../mediadrm/test/WVDrmPlugin_test.cpp | 178 +++++++++++++++--- 12 files changed, 462 insertions(+), 30 deletions(-) diff --git a/libwvdrmengine/cdm/core/include/cdm_engine.h b/libwvdrmengine/cdm/core/include/cdm_engine.h index 0c2783ad..cde40e0a 100644 --- a/libwvdrmengine/cdm/core/include/cdm_engine.h +++ b/libwvdrmengine/cdm/core/include/cdm_engine.h @@ -212,6 +212,17 @@ class CdmEngine { CdmSecurityLevel security_level, 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 // Retrieve a random usage info from the list of all usage infos for this app // id. diff --git a/libwvdrmengine/cdm/core/include/cdm_session.h b/libwvdrmengine/cdm/core/include/cdm_session.h index 7c6b23fb..98032520 100644 --- a/libwvdrmengine/cdm/core/include/cdm_session.h +++ b/libwvdrmengine/cdm/core/include/cdm_session.h @@ -194,7 +194,7 @@ class CdmSession { const std::string& signature); virtual CdmResponseType SetDecryptHash(uint32_t frame_number, - const std::string& hash); + const std::string& hash); virtual CdmResponseType GetDecryptHashError(std::string* hash_error_string); diff --git a/libwvdrmengine/cdm/core/include/device_files.h b/libwvdrmengine/cdm/core/include/device_files.h index 3f5313bf..0ffdc77f 100644 --- a/libwvdrmengine/cdm/core/include/device_files.h +++ b/libwvdrmengine/cdm/core/include/device_files.h @@ -91,6 +91,7 @@ class DeviceFiles { const CdmUsageEntry& usage_entry, uint32_t usage_entry_number, ResponseType* result); + virtual bool RetrieveLicense( const std::string& key_set_id, LicenseState* state, CdmInitData* pssh_data, CdmKeyMessage* key_request, diff --git a/libwvdrmengine/cdm/core/include/wv_cdm_types.h b/libwvdrmengine/cdm/core/include/wv_cdm_types.h index 34d37bda..2a6dbeb1 100644 --- a/libwvdrmengine/cdm/core/include/wv_cdm_types.h +++ b/libwvdrmengine/cdm/core/include/wv_cdm_types.h @@ -40,6 +40,12 @@ enum CdmKeyRequestType { kKeyRequestTypeRelease, }; +enum CdmOfflineLicenseState { + kLicenseStateActive, + kLicenseStateReleasing, + kLicenseStateUnknown, +}; + enum CdmResponseType { NO_ERROR = 0, UNKNOWN_ERROR = 1, @@ -355,6 +361,11 @@ enum CdmResponseType { INVALID_LICENSE_TYPE_2 = 310, SIGNATURE_NOT_FOUND_2 = 311, 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. }; diff --git a/libwvdrmengine/cdm/core/src/cdm_engine.cpp b/libwvdrmengine/cdm/core/src/cdm_engine.cpp index 964596d6..3dec9c9e 100644 --- a/libwvdrmengine/cdm/core/src/cdm_engine.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_engine.cpp @@ -27,6 +27,18 @@ namespace { const uint64_t kReleaseSessionTimeToLive = 60; // seconds const uint32_t kUpdateUsageInformationPeriod = 60; // seconds 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 wvcdm { @@ -1100,6 +1112,98 @@ CdmResponseType CdmEngine::DeleteUsageRecord(const std::string& app_id, 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 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, const CdmSecureStopId& ssid, CdmUsageInfo* usage_info) { diff --git a/libwvdrmengine/cdm/core/test/test_printers.cpp b/libwvdrmengine/cdm/core/test/test_printers.cpp index 359c4397..8ad974d6 100644 --- a/libwvdrmengine/cdm/core/test/test_printers.cpp +++ b/libwvdrmengine/cdm/core/test/test_printers.cpp @@ -154,6 +154,12 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { case GET_LICENSE_ERROR: *os << "GET_LICENSE_ERROR"; 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: *os << "GET_RELEASED_LICENSE_ERROR"; break; @@ -784,6 +790,9 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { case SESSION_NOT_FOUND_20: *os << "SESSION_NOT_FOUND_20"; break; + case SESSION_NOT_FOUND_21: + *os << "SESSION_NOT_FOUND_21"; + break; case INVALID_DECRYPT_HASH_FORMAT: *os << "INVALID_DECRYPT_HASH_FORMAT"; break; @@ -825,6 +834,11 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) { break; case 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; default: *os << "Unknown CdmResponseType"; diff --git a/libwvdrmengine/cdm/include/wv_content_decryption_module.h b/libwvdrmengine/cdm/include/wv_content_decryption_module.h index d5ade737..848b702e 100644 --- a/libwvdrmengine/cdm/include/wv_content_decryption_module.h +++ b/libwvdrmengine/cdm/include/wv_content_decryption_module.h @@ -147,6 +147,26 @@ class WvContentDecryptionModule : public android::RefBase, public TimerHandler { virtual CdmResponseType GetDecryptHashError(const CdmSessionId& session_id, 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* 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: struct CdmInfo { CdmInfo(); diff --git a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp index 2cb15525..dfd6ac57 100644 --- a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp +++ b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp @@ -525,4 +525,32 @@ uint32_t WvContentDecryptionModule::GenerateSessionSharingId() { return ++next_session_sharing_id; } +CdmResponseType WvContentDecryptionModule::ListStoredLicenses( + CdmSecurityLevel security_level, + const CdmIdentifier& identifier, + std::vector* 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 diff --git a/libwvdrmengine/include/mapErrors-inl.h b/libwvdrmengine/include/mapErrors-inl.h index bc9a129e..327d5a80 100644 --- a/libwvdrmengine/include/mapErrors-inl.h +++ b/libwvdrmengine/include/mapErrors-inl.h @@ -235,6 +235,7 @@ static android::status_t mapCdmResponseType(wvcdm::CdmResponseType res) { case wvcdm::SESSION_NOT_FOUND_18: case wvcdm::SESSION_NOT_FOUND_19: case wvcdm::SESSION_NOT_FOUND_20: + case wvcdm::SESSION_NOT_FOUND_21: return android::ERROR_DRM_SESSION_NOT_OPENED; case wvcdm::SESSION_KEYS_NOT_FOUND: return kSessionKeysNotFound; @@ -557,6 +558,15 @@ static android::status_t mapCdmResponseType(wvcdm::CdmResponseType res) { return kSignatureNotFound2; case wvcdm::SESSION_KEYS_NOT_FOUND_2: 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 diff --git a/libwvdrmengine/include_hidl/mapErrors-inl.h b/libwvdrmengine/include_hidl/mapErrors-inl.h index 67dc8a03..31cc509f 100644 --- a/libwvdrmengine/include_hidl/mapErrors-inl.h +++ b/libwvdrmengine/include_hidl/mapErrors-inl.h @@ -63,6 +63,7 @@ static Status mapCdmResponseType(wvcdm::CdmResponseType res) { case wvcdm::SESSION_NOT_FOUND_18: case wvcdm::SESSION_NOT_FOUND_19: case wvcdm::SESSION_NOT_FOUND_20: + case wvcdm::SESSION_NOT_FOUND_21: return Status::ERROR_DRM_SESSION_NOT_OPENED; case wvcdm::DECRYPT_ERROR: @@ -321,6 +322,10 @@ static Status mapCdmResponseType(wvcdm::CdmResponseType res) { case wvcdm::INVALID_LICENSE_TYPE_2: case wvcdm::SIGNATURE_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); return Status::ERROR_DRM_UNKNOWN; diff --git a/libwvdrmengine/mediadrm/src_hidl/WVDrmPlugin.cpp b/libwvdrmengine/mediadrm/src_hidl/WVDrmPlugin.cpp index c690d9b7..67280ea9 100644 --- a/libwvdrmengine/mediadrm/src_hidl/WVDrmPlugin.cpp +++ b/libwvdrmengine/mediadrm/src_hidl/WVDrmPlugin.cpp @@ -55,6 +55,7 @@ using wvcdm::CdmProvisioningRequest; using wvcdm::CdmProvisioningResponse; using wvcdm::CdmQueryMap; using wvcdm::CdmSecureStopId; +using wvcdm::CdmSecurityLevel; using wvcdm::CdmUsageInfo; using wvcdm::CdmUsageInfoReleaseMessage; 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) return HdcpLevel::HDCP_V1; else if (level == wvcdm::QUERY_VALUE_HDCP_V2_0) @@ -1048,20 +1049,91 @@ Return WVDrmPlugin::getSecurityLevel( Return WVDrmPlugin::getOfflineLicenseKeySetIds( getOfflineLicenseKeySetIds_cb _hidl_cb) { - std::vector keySetIds; + std::vector > keySetIds; + std::vector 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 levels = { + wvcdm::kSecurityLevelL1, wvcdm::kSecurityLevelL3 }; + + CdmResponseType res = wvcdm::UNKNOWN_ERROR; + + for (auto level : levels) { + std::vector 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 WVDrmPlugin::getOfflineLicenseState(const KeySetId &keySetId, getOfflineLicenseState_cb _hidl_cb) { + OfflineLicenseState licenseState = OfflineLicenseState::UNKNOWN; + if (!keySetId.size()) { - _hidl_cb(Status::BAD_VALUE, OfflineLicenseState::UNKNOWN); + _hidl_cb(Status::BAD_VALUE, licenseState); 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(); } @@ -1070,7 +1142,31 @@ Return WVDrmPlugin::removeOfflineLicense(const KeySetId &keySetId) { 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 WVDrmPlugin::getPropertyString(const hidl_string& propertyName, diff --git a/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp b/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp index 2967f715..197a4bda 100644 --- a/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp +++ b/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp @@ -78,6 +78,7 @@ using wvcdm::CdmKeyRequest; using wvcdm::CdmKeySetId; using wvcdm::CdmKeySystem; using wvcdm::CdmLicenseType; +using wvcdm::CdmOfflineLicenseState; using wvcdm::CdmProvisioningRequest; using wvcdm::CdmProvisioningResponse; using wvcdm::CdmQueryMap; @@ -159,7 +160,8 @@ class MockCDM : public WvContentDecryptionModule { MOCK_METHOD2(RestoreKey, CdmResponseType(const CdmSessionId&, const CdmKeySetId&)); - MOCK_METHOD3(QueryStatus, CdmResponseType(wvcdm::SecurityLevel, const std::string&, + MOCK_METHOD3(QueryStatus, CdmResponseType(wvcdm::SecurityLevel, + const std::string&, std::string*)); MOCK_METHOD2(QueryKeyStatus, CdmResponseType(const CdmSessionId&, @@ -202,7 +204,24 @@ class MockCDM : public WvContentDecryptionModule { MOCK_METHOD2(GetMetrics, CdmResponseType(const CdmIdentifier&, 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*)); + + 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 { @@ -260,8 +279,11 @@ MATCHER_P(HasOrigin, origin, "") { class WVDrmPluginTest : public Test { protected: + static const uint32_t kKeySetIdSize = 32; static const uint32_t kSessionIdSize = 16; + uint8_t keySetIdRaw[kKeySetIdSize]; uint8_t sessionIdRaw[kSessionIdSize]; + std::vector keySetId; std::vector sessionId; CdmSessionId cdmSessionId; @@ -269,12 +291,19 @@ class WVDrmPluginTest : public Test { // Fill the session ID FILE* fp = fopen("/dev/urandom", "r"); fread(sessionIdRaw, sizeof(uint8_t), kSessionIdSize, fp); - fclose(fp); memcpy(sessionIdRaw, SESSION_ID_PREFIX, sizeof(SESSION_ID_PREFIX) - 1); sessionId.assign(sessionIdRaw, sessionIdRaw + kSessionIdSize); 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(keySetIdRaw), + kKeySetIdSize); + keySetId.assign(keySetIdRaw, keySetIdRaw + kKeySetIdSize); + // Set default return values for gMock DefaultValue::Set(wvcdm::NO_ERROR); DefaultValue::Set(OEMCrypto_SUCCESS); @@ -728,15 +757,6 @@ TEST_F(WVDrmPluginTest, RestoresKeys) { StrictMock crypto; 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 keySetId; - keySetId.assign(keySetIdRaw, keySetIdRaw + kKeySetIdSize); - EXPECT_CALL(*cdm, RestoreKey(cdmSessionId, ElementsAreArray(keySetIdRaw, kKeySetIdSize))) .Times(1); @@ -1039,7 +1059,7 @@ TEST_F(WVDrmPluginTest, GetsSecureStops) { .WillOnce(DoAll(SetArgPointee<2>(cdmStops), testing::Return(wvcdm::NO_ERROR))); - std::list > stops; + std::vector > stops; WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false); Status status = plugin.setPropertyString(hidl_string("appId"), @@ -1058,17 +1078,11 @@ TEST_F(WVDrmPluginTest, GetsSecureStops) { } }); - std::list >::iterator iter = stops.begin(); - uint32_t rawIter = 0; - while (rawIter < kStopCount && iter != stops.end()) { - EXPECT_THAT(*iter, ElementsAreArray(stopsRaw[rawIter], kStopSize)); - - ++iter; - ++rawIter; + size_t index = 0; + for (auto stop : stops) { + EXPECT_THAT(stop, ElementsAreArray(stopsRaw[index++], kStopSize)); } - // Assert that both lists are the same length - EXPECT_EQ(kStopCount, rawIter); - EXPECT_EQ(stops.end(), iter); + EXPECT_EQ(kStopCount, index); } TEST_F(WVDrmPluginTest, ReleasesAllSecureStops) { @@ -2715,6 +2729,124 @@ TEST_F(WVDrmPluginTest, DoesNotSetDecryptHashProperties) { ASSERT_EQ(Status::OK, status); } +TEST_F(WVDrmPluginTest, GetOfflineLicenseIds) { + android::sp> cdm = new StrictMock(); + StrictMock 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 mockIdsL1; + for (uint32_t i = 0; i < kLicenseCount; ++i) { + mockIdsL1.push_back(std::string(mockIdsRaw[i], + mockIdsRaw[i] + kKeySetIdSize)); + } + + std::vector 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 > offlineIds; + offlineIds.clear(); + plugin.getOfflineLicenseKeySetIds( + [&](Status status, hidl_vec hKeySetIds) { + ASSERT_EQ(Status::OK, status); + + std::vector 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> cdm = new StrictMock(); + StrictMock 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> cdm = new StrictMock(); + StrictMock 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 V1_2 } // namespace drm