diff --git a/libwvdrmengine/cdm/include/cdm_identifier.h b/libwvdrmengine/cdm/include/cdm_identifier.h index 618c4e5b..820b93e9 100644 --- a/libwvdrmengine/cdm/include/cdm_identifier.h +++ b/libwvdrmengine/cdm/include/cdm_identifier.h @@ -30,12 +30,28 @@ struct CdmIdentifier { // provide a friendly name of the application package for the purposes of // logging and metrics. std::string app_package_name; + + // The unique identifier guarantees that no two identifiers share the same + // CdmEngine instance. We're moving to a model where a plugin maps 1 to 1 + // with a CdmEngine instance. This is a simple way to implement that. + uint32_t unique_id; + + // This method is needed to check to see if the identifier is equivalent + // to the default cdm. E.g. no spoid, origin or app package name. Use this + // comparison in lieu of the == operator when checking to see if the + // identifier would cause the default provisioned certificate to be used. + bool IsEquivalentToDefault() { + return spoid == EMPTY_SPOID + && origin == EMPTY_ORIGIN + && app_package_name == EMPTY_APP_PACKAGE_NAME; + } }; // Provide comparison operators inline bool operator==(const CdmIdentifier& lhs, const CdmIdentifier& rhs) { return lhs.spoid == rhs.spoid && lhs.origin == rhs.origin - && lhs.app_package_name == rhs.app_package_name; + && lhs.app_package_name == rhs.app_package_name + && lhs.unique_id == rhs.unique_id; } inline bool operator!=(const CdmIdentifier& lhs, const CdmIdentifier& rhs) { @@ -47,7 +63,9 @@ inline bool operator<(const CdmIdentifier& lhs, const CdmIdentifier& rhs) { || ((lhs.spoid == rhs.spoid) && (lhs.origin < rhs.origin || (lhs.origin == rhs.origin - && lhs.app_package_name < rhs.app_package_name))); + && (lhs.app_package_name < rhs.app_package_name + || (lhs.app_package_name == rhs.app_package_name + && lhs.unique_id < rhs.unique_id))))); } inline bool operator>(const CdmIdentifier& lhs, const CdmIdentifier& rhs) { @@ -66,7 +84,8 @@ inline bool operator>=(const CdmIdentifier& lhs, const CdmIdentifier& rhs) { static const CdmIdentifier kDefaultCdmIdentifier = { EMPTY_SPOID, EMPTY_ORIGIN, - EMPTY_APP_PACKAGE_NAME + EMPTY_APP_PACKAGE_NAME, + 0 }; } // namespace wvcdm diff --git a/libwvdrmengine/cdm/include/wv_content_decryption_module.h b/libwvdrmengine/cdm/include/wv_content_decryption_module.h index 85198525..88fedfcb 100644 --- a/libwvdrmengine/cdm/include/wv_content_decryption_module.h +++ b/libwvdrmengine/cdm/include/wv_content_decryption_module.h @@ -127,9 +127,14 @@ class WvContentDecryptionModule : public android::RefBase, public TimerHandler { // Validate a passed-in service certificate virtual bool IsValidServiceCertificate(const std::string& certificate); - // Retrieve the serialized metrics from CdmEngine and CdmSession instances - // that have been closed. - virtual void GetSerializedMetrics(std::string* serialized_metrics); + // Fill the metrics parameter with the metrics data for the CdmEngine + // associated with the given CdmIdentifier. If the CdmEngine instance does + // not exist, this will return an error. + virtual CdmResponseType GetMetrics(const CdmIdentifier& identifier, + drm_metrics::WvCdmMetrics* metrics); + + // Closes the CdmEngine and sessions associated with the given CdmIdentifier. + virtual CdmResponseType CloseCdm(const CdmIdentifier& identifier); private: struct CdmInfo { @@ -145,10 +150,10 @@ class WvContentDecryptionModule : public android::RefBase, public TimerHandler { // Finds the CdmEngine instance for the given session id, returning NULL if // not found. CdmEngine* GetCdmForSessionId(const std::string& session_id); - // Closes CdmEngine instances that don't have any open sessions. Also stores - // metrics data for closed CdmEngine instances. - // Callers must acquire the cdms_lock_ before calling this method. - void CloseCdmsWithoutSessions(); + + // Close all of the open CdmEngine instances. This is used when ready to close + // the WvContentDecryptionModule instance. + void CloseAllCdms(); uint32_t GenerateSessionSharingId(); @@ -170,9 +175,6 @@ class WvContentDecryptionModule : public android::RefBase, public TimerHandler { // This contains weak pointers to the CDM instances contained in |cdms_|. std::map cdm_by_session_id_; - // The metrics for cdm engines and sessions that have been closed. - drm_metrics::WvCdmMetricsGroup metrics_group_; - CORE_DISALLOW_COPY_AND_ASSIGN(WvContentDecryptionModule); }; diff --git a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp index 8d50d172..1b071953 100644 --- a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp +++ b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp @@ -24,6 +24,7 @@ Lock WvContentDecryptionModule::session_sharing_id_generation_lock_; WvContentDecryptionModule::WvContentDecryptionModule() {} WvContentDecryptionModule::~WvContentDecryptionModule() { + CloseAllCdms(); ForceDisablePolicyTimer(); } @@ -75,8 +76,6 @@ CdmResponseType WvContentDecryptionModule::CloseSession( cdm_by_session_id_.erase(session_id); } - DisablePolicyTimer(); - return sts; } @@ -330,12 +329,21 @@ bool WvContentDecryptionModule::IsValidServiceCertificate( return cert.has_certificate(); } -void WvContentDecryptionModule::GetSerializedMetrics( - std::string* serialized_metrics) { + +CdmResponseType WvContentDecryptionModule::GetMetrics( + const CdmIdentifier& identifier, drm_metrics::WvCdmMetrics* metrics) { + if (!metrics) { + return PARAMETER_NULL; + } AutoLock auto_lock(cdms_lock_); - CloseCdmsWithoutSessions(); - metrics_group_.SerializeToString(serialized_metrics); - metrics_group_.Clear(); + auto it = cdms_.find(identifier); + if (it == cdms_.end()) { + LOGE("WVContentDecryptionModule::Close. cdm_identifier not found"); + // TODO(blueeyes): Add a better error. + return UNKNOWN_ERROR; + } + it->second.cdm_engine->GetMetrics()->Serialize(metrics); + return NO_ERROR; } WvContentDecryptionModule::CdmInfo::CdmInfo() @@ -371,60 +379,44 @@ CdmEngine* WvContentDecryptionModule::GetCdmForSessionId( return it->second; } -// This method requires that the caller first acquire cdms_lock_. -void WvContentDecryptionModule::CloseCdmsWithoutSessions() { +void WvContentDecryptionModule::CloseAllCdms() { + AutoLock auto_lock(cdms_lock_); for (auto it = cdms_.begin(); it != cdms_.end();) { - if (it->second.cdm_engine->SessionSize() != 0) { - ++it; - } else { - // Retrieve the metrics from the engine and any completed - // sessions. Clear the metrics from any completed sessions. - metrics::EngineMetrics* engine_metrics = - it->second.cdm_engine->GetMetrics(); - // engine_metrics should never be null. - if (engine_metrics != NULL) { - engine_metrics->Serialize(metrics_group_.add_metrics()); - } else { - // Engine metrics should never be null. - LOGI( - "WvContentDecryptionModule::CloseCdmsWithoutSessions." - "engine_metrics was unexpectedly NULL."); - } - - // The CDM is no longer used for this identifier, delete it. - it = cdms_.erase(it); - } + it = cdms_.erase(it); } } +CdmResponseType WvContentDecryptionModule::CloseCdm( + const CdmIdentifier& cdm_identifier) { + AutoLock auto_lock(cdms_lock_); + auto it = cdms_.find(cdm_identifier); + if (it == cdms_.end()) { + LOGE("WVContentDecryptionModule::Close. cdm_identifier not found"); + // TODO(blueeyes): Create a better error. + return UNKNOWN_ERROR; + } + cdms_.erase(it); + + DisablePolicyTimer(); + return NO_ERROR; +} + void WvContentDecryptionModule::EnablePolicyTimer() { AutoLock auto_lock(policy_timer_lock_); if (!policy_timer_.IsRunning()) policy_timer_.Start(this, kCdmPolicyTimerDurationSeconds); } +// This implementation assumes that the caller has already acquired the +// cdms_lock_. void WvContentDecryptionModule::DisablePolicyTimer() { - bool cdms_is_empty = false; - { - AutoLock auto_lock(cdms_lock_); - CloseCdmsWithoutSessions(); - cdms_is_empty = cdms_.empty(); - } - AutoLock auto_lock(policy_timer_lock_); - if (cdms_is_empty) { - if (policy_timer_.IsRunning()) { - policy_timer_.Stop(); - } + if (cdms_.empty() && policy_timer_.IsRunning()) { + policy_timer_.Stop(); } } void WvContentDecryptionModule::ForceDisablePolicyTimer() { - { - AutoLock auto_lock(cdms_lock_); - CloseCdmsWithoutSessions(); - } - AutoLock auto_lock(policy_timer_lock_); if (policy_timer_.IsRunning()) { policy_timer_.Stop(); diff --git a/libwvdrmengine/cdm/test/cdm_extended_duration_test.cpp b/libwvdrmengine/cdm/test/cdm_extended_duration_test.cpp index 0d4332c1..b96e3a7f 100644 --- a/libwvdrmengine/cdm/test/cdm_extended_duration_test.cpp +++ b/libwvdrmengine/cdm/test/cdm_extended_duration_test.cpp @@ -605,24 +605,21 @@ class WvCdmExtendedDurationTest : public WvCdmTestBase { EXPECT_TRUE(license_renewal.has_key_control_nonce()); } - void ValidateHasUpdateUsageEntry(const std::string& serialized_metrics) + void ValidateHasUpdateUsageEntry(const drm_metrics::WvCdmMetrics& metrics) const { - drm_metrics::WvCdmMetricsGroup group; - ASSERT_TRUE(group.ParseFromString(serialized_metrics)); bool has_update_usage_entry_metrics = false; - for (const auto& metrics : group.metrics()) { - for (const auto& session : metrics.session_metrics()) { - has_update_usage_entry_metrics |= - session.crypto_metrics() - .crypto_session_update_usage_entry_time_us().size() > 0; - has_update_usage_entry_metrics |= - session.crypto_metrics().oemcrypto_update_usage_entry().size() > 0; - } + for (const auto& session : metrics.session_metrics()) { + has_update_usage_entry_metrics |= + session.crypto_metrics() + .crypto_session_update_usage_entry_time_us().size() > 0; + has_update_usage_entry_metrics |= + session.crypto_metrics().oemcrypto_update_usage_entry().size() > 0; } + std::string serialized_metrics; + ASSERT_TRUE(metrics.SerializeToString(&serialized_metrics)); EXPECT_TRUE(has_update_usage_entry_metrics) << "metrics: " << wvcdm::b2a_hex(serialized_metrics); - } void QueryKeyStatus(bool streaming, bool expect_renewal, @@ -1452,9 +1449,9 @@ TEST_P(WvCdmStreamingUsageReportTest, UsageTest) { } // Validate that update usage table entry is exercised. - std::string serialized_metrics; - decryptor_.GetSerializedMetrics(&serialized_metrics); - ValidateHasUpdateUsageEntry(serialized_metrics); + drm_metrics::WvCdmMetrics metrics; + ASSERT_EQ(NO_ERROR, decryptor_.GetMetrics(kDefaultCdmIdentifier, &metrics)); + ValidateHasUpdateUsageEntry(metrics); } INSTANTIATE_TEST_CASE_P(Cdm, WvCdmStreamingUsageReportTest, diff --git a/libwvdrmengine/cdm/test/request_license_test.cpp b/libwvdrmengine/cdm/test/request_license_test.cpp index 9af2eea2..50ede011 100644 --- a/libwvdrmengine/cdm/test/request_license_test.cpp +++ b/libwvdrmengine/cdm/test/request_license_test.cpp @@ -51,7 +51,8 @@ const int kHttpInternalServerError = 500; const wvcdm::CdmIdentifier kExampleIdentifier = { wvcdm::EMPTY_SPOID, "com.example", - "com.example" + "com.example", + 7 }; // Protobuf generated classes @@ -392,15 +393,19 @@ const uint32_t kSingleEncryptedSubSampleIcpLicenseExpirationWindow = 2; struct SessionSharingSubSampleInfo { SubSampleInfo* sub_sample; bool session_sharing_enabled; + wvcdm::CdmIdentifier cdm_identifier; }; SessionSharingSubSampleInfo session_sharing_sub_samples[] = { - {&clear_sub_sample, false}, - {&clear_sub_sample, true}, - {&clear_sub_sample_no_key, false}, - {&clear_sub_sample_no_key, true}, - {&single_encrypted_sub_sample, false}, - {&single_encrypted_sub_sample, true}}; + {&clear_sub_sample, false, wvcdm::kDefaultCdmIdentifier}, + {&clear_sub_sample, true, wvcdm::kDefaultCdmIdentifier}, + {&clear_sub_sample_no_key, false, wvcdm::kDefaultCdmIdentifier}, + {&clear_sub_sample_no_key, true, wvcdm::kDefaultCdmIdentifier}, + {&single_encrypted_sub_sample, false, wvcdm::kDefaultCdmIdentifier}, + {&single_encrypted_sub_sample, true, wvcdm::kDefaultCdmIdentifier}, + // The last entry simulates session sharing using the non default + // identifier. + {&single_encrypted_sub_sample, true, kExampleIdentifier}}; struct UsageInfoSubSampleInfo { SubSampleInfo* sub_sample; @@ -1301,6 +1306,14 @@ class WvCdmRequestLicenseTest : public WvCdmTestBase { GenerateKeyRequest(init_data, license_type, NULL); } + void GenerateKeyRequest(const std::string& init_data, + CdmLicenseType license_type, + const CdmIdentifier& identifier) { + CdmAppParameterMap app_parameters; + GenerateKeyRequest(wvcdm::KEY_MESSAGE, "video/mp4", init_data, + app_parameters, license_type, identifier, NULL); + } + void GenerateKeyRequest(const std::string& init_data, CdmLicenseType license_type, CdmClientPropertySet* property_set) { @@ -1342,7 +1355,13 @@ class WvCdmRequestLicenseTest : public WvCdmTestBase { decryptor_.GenerateKeyRequest( session_id_, key_set_id, init_data_type, init_data, license_type, app_parameters, property_set, - cdm_identifier, &key_request)); + cdm_identifier, &key_request)) + << "session_id_ " << session_id_ << std::endl + << "init_data (hex) " << wvcdm::b2a_hex(init_data) << std::endl + << "key_set_id " << key_set_id << std::endl + << "cdm_identifier.origin " << cdm_identifier.origin << std::endl + << "cdm_identifier.app_package_name " << cdm_identifier.app_package_name << std::endl + << "cdm_identifier.unique_id " << cdm_identifier.unique_id << std::endl; key_msg_ = key_request.message; EXPECT_EQ(0u, key_request.url.size()); } @@ -3862,14 +3881,20 @@ class WvCdmSessionSharingTest TEST_P(WvCdmSessionSharingTest, SessionSharingTest) { SessionSharingSubSampleInfo* session_sharing_info = GetParam(); + CdmIdentifier cdm_identifier = session_sharing_info->cdm_identifier; + if (!cdm_identifier.IsEquivalentToDefault()) { + Provision(session_sharing_info->cdm_identifier, kLevelDefault); + } + TestWvCdmClientPropertySet property_set; property_set.set_session_sharing_mode( session_sharing_info->session_sharing_enabled); - decryptor_.OpenSession(g_key_system, &property_set, kDefaultCdmIdentifier, - NULL, &session_id_); + ASSERT_EQ(NO_ERROR, decryptor_.OpenSession(g_key_system, &property_set, + cdm_identifier, NULL, + &session_id_)); CdmSessionId gp_session_id_1 = session_id_; - GenerateKeyRequest(g_key_id, kLicenseTypeStreaming); + GenerateKeyRequest(g_key_id, kLicenseTypeStreaming, cdm_identifier); VerifyKeyRequestResponse(g_license_server, g_client_auth); // TODO(rfrias): Move content information to ConfigTestEnv @@ -3880,10 +3905,11 @@ TEST_P(WvCdmSessionSharingTest, SessionSharingTest) { "edef8ba979d64acea3c827dcd51d21ed00000014" // Widevine system id "08011210bdf1cb4fffc6506b8b7945b0bd2917fb"); // pssh data - decryptor_.OpenSession(g_key_system, &property_set, kDefaultCdmIdentifier, - NULL, &session_id_); + ASSERT_EQ(NO_ERROR, decryptor_.OpenSession(g_key_system, &property_set, + cdm_identifier, NULL, + &session_id_)); CdmSessionId gp_session_id_2 = session_id_; - GenerateKeyRequest(gp_key_id2, kLicenseTypeStreaming); + GenerateKeyRequest(gp_key_id2, kLicenseTypeStreaming, cdm_identifier); VerifyKeyRequestResponse(g_license_server, gp_client_auth2); SubSampleInfo* data = session_sharing_info->sub_sample; @@ -3898,7 +3924,10 @@ TEST_P(WvCdmSessionSharingTest, SessionSharingTest) { if (session_sharing_info->session_sharing_enabled || !data->is_encrypted) { EXPECT_EQ(NO_ERROR, decryptor_.Decrypt(gp_session_id_2, data->validate_key_id, - decryption_parameters)); + decryption_parameters)) + << "session_sharing_info->session_sharing_enabled " + << session_sharing_info->session_sharing_enabled << std::endl + << "data->is_encrypted " << data->is_encrypted << std::endl; EXPECT_TRUE(std::equal(data->decrypt_data.begin(), data->decrypt_data.end(), decrypt_buffer.begin())); } else { @@ -3907,13 +3936,19 @@ TEST_P(WvCdmSessionSharingTest, SessionSharingTest) { decryption_parameters)); } + if (!cdm_identifier.IsEquivalentToDefault()) { + // Unprovision both security level certs. + decryptor_.Unprovision(kSecurityLevelL1, cdm_identifier); + decryptor_.Unprovision(kSecurityLevelL3, cdm_identifier); + } + decryptor_.CloseSession(gp_session_id_1); decryptor_.CloseSession(gp_session_id_2); } INSTANTIATE_TEST_CASE_P(Cdm, WvCdmSessionSharingTest, ::testing::Range(&session_sharing_sub_samples[0], - &session_sharing_sub_samples[6])); + &session_sharing_sub_samples[7])); TEST_F(WvCdmRequestLicenseTest, SessionSharingTest) { TestWvCdmClientPropertySet property_set; diff --git a/libwvdrmengine/cdm/test/wv_cdm_metrics_test.cpp b/libwvdrmengine/cdm/test/wv_cdm_metrics_test.cpp index e620a9cd..3963d036 100644 --- a/libwvdrmengine/cdm/test/wv_cdm_metrics_test.cpp +++ b/libwvdrmengine/cdm/test/wv_cdm_metrics_test.cpp @@ -30,11 +30,10 @@ class WvContentDecryptionModuleMetricsTest : public ::testing::Test { wvcdm::WvContentDecryptionModule decryptor_; }; -TEST_F(WvContentDecryptionModuleMetricsTest, NoMetrics) { - // Get metrics before any operations are performed. - std::string serialized_metrics; - decryptor_.GetSerializedMetrics(&serialized_metrics); - EXPECT_TRUE(serialized_metrics.empty()); +TEST_F(WvContentDecryptionModuleMetricsTest, IdentifierNotFound) { + drm_metrics::WvCdmMetrics metrics; + ASSERT_EQ(wvcdm::UNKNOWN_ERROR, + decryptor_.GetMetrics(kDefaultCdmIdentifier, &metrics)); } TEST_F(WvContentDecryptionModuleMetricsTest, EngineOnlyMetrics) { @@ -48,27 +47,21 @@ TEST_F(WvContentDecryptionModuleMetricsTest, EngineOnlyMetrics) { decryptor_.GetProvisioningRequest(cert_type, cert_authority, kDefaultCdmIdentifier, &request, &provisioning_server_url)); - std::string serialized_metrics; - decryptor_.GetSerializedMetrics(&serialized_metrics); - drm_metrics::WvCdmMetricsGroup metrics_group; - ASSERT_TRUE(metrics_group.ParseFromString(serialized_metrics)) - << "Unexpected failure deserializing metrics: " - << wvcdm::b2a_hex(serialized_metrics); - ASSERT_THAT(metrics_group.metrics().size(), Eq(1)); + drm_metrics::WvCdmMetrics metrics; + ASSERT_EQ(wvcdm::NO_ERROR, + decryptor_.GetMetrics(kDefaultCdmIdentifier, &metrics)); // 100 is an arbitrary high value that shouldn't ever occur. EXPECT_THAT( - metrics_group.metrics(0).engine_metrics() - .oemcrypto_initialization_mode().int_value(), + metrics.engine_metrics().oemcrypto_initialization_mode().int_value(), Lt(100)); EXPECT_THAT( - metrics_group.metrics(0).engine_metrics() - .oemcrypto_initialization_mode().int_value(), + metrics.engine_metrics().oemcrypto_initialization_mode().int_value(), Ge(0)); - ASSERT_THAT(metrics_group.metrics(0).engine_metrics() + ASSERT_THAT(metrics.engine_metrics() .cdm_engine_get_provisioning_request_time_us().size(), Eq(1)); - EXPECT_THAT(metrics_group.metrics(0).engine_metrics() + EXPECT_THAT(metrics.engine_metrics() .cdm_engine_get_provisioning_request_time_us(0) .operation_count(), Eq(1u)); @@ -85,81 +78,80 @@ TEST_F(WvContentDecryptionModuleMetricsTest, EngineAndSessionMetrics) { decryptor_.OpenSession(key_system, NULL, kDefaultCdmIdentifier, NULL, &session_id)); - // The metrics will have a single engine and single session stats. + drm_metrics::WvCdmMetrics metrics; + ASSERT_EQ(wvcdm::NO_ERROR, + decryptor_.GetMetrics(kDefaultCdmIdentifier, &metrics)); std::string serialized_metrics; - decryptor_.GetSerializedMetrics(&serialized_metrics); + ASSERT_TRUE(metrics.SerializeToString(&serialized_metrics)); // Spot check some metric values. - drm_metrics::WvCdmMetricsGroup metrics_group; - ASSERT_TRUE(metrics_group.ParseFromString(serialized_metrics)) - << "Unexpected failure deserializing metrics: " - << wvcdm::b2a_hex(serialized_metrics); - ASSERT_THAT(metrics_group.metrics().size(), Eq(1)); - // Validate engine-level metrics. - EXPECT_TRUE(metrics_group.metrics(0).engine_metrics() - .has_oemcrypto_initialization_mode()); - ASSERT_THAT( - metrics_group.metrics(0).engine_metrics().cdm_engine_open_session().size(), - Eq(1)); - EXPECT_THAT(metrics_group.metrics(0).engine_metrics() - .cdm_engine_open_session(0).count(), Eq(1)); - EXPECT_THAT(metrics_group.metrics(0).engine_metrics() + EXPECT_TRUE(metrics.engine_metrics().has_oemcrypto_initialization_mode()); + ASSERT_THAT(metrics.engine_metrics().cdm_engine_open_session().size(), Eq(1)); + EXPECT_THAT(metrics.engine_metrics().cdm_engine_open_session(0).count(), + Eq(1)); + EXPECT_THAT(metrics.engine_metrics() .cdm_engine_open_session(0).attributes().error_code(), Eq(CdmResponseType::NEED_PROVISIONING)); // Validate a session-level metric. - ASSERT_THAT(metrics_group.metrics(0).session_metrics().size(), Eq(1)); + ASSERT_THAT(metrics.session_metrics().size(), Eq(1)); EXPECT_THAT( - metrics_group.metrics(0).session_metrics(0) - .cdm_session_life_span_ms().double_value(), + metrics.session_metrics(0).cdm_session_life_span_ms().double_value(), Gt(0.0)) << "Unexpected failure with session_metrics: " << wvcdm::b2a_hex(serialized_metrics); } -TEST_F(WvContentDecryptionModuleMetricsTest, MultipleEngineMetric) { +TEST_F(WvContentDecryptionModuleMetricsTest, + DifferentCdmIdentifiersHaveDifferentMetrics) { CdmSessionId session_id; wvcdm::CdmKeySystem key_system("com.widevine"); - CdmIdentifier identifier = { "foo", "bar", "baz" }; + CdmIdentifier identifiers[] = { kDefaultCdmIdentifier, + { "foo", "bar", "baz", 7 }, + // Note that this has all the same parameters + // as the one above except for the unique_id. + { "foo", "bar", "baz", 8 }}; + const int cdm_engine_count = 3; + for (int i = 0; i < cdm_engine_count; i++) { + // To make sure we can detect different engine metrics, + // make the open session call a different number of times for + // each identifier. + for (int j = 0; j <= i; j ++) { + EXPECT_EQ(CdmResponseType::NEED_PROVISIONING, + decryptor_.OpenSession(key_system, NULL, + identifiers[i], NULL, &session_id)); + } + } - // Openning the session will fail with NEEDS_PROVISIONING error. But it will - // still create some session-level stats. - EXPECT_EQ(CdmResponseType::NEED_PROVISIONING, - decryptor_.OpenSession(key_system, NULL, - kDefaultCdmIdentifier, NULL, &session_id)); - // Open a second engine with a custom identifier. - EXPECT_EQ(CdmResponseType::NEED_PROVISIONING, - decryptor_.OpenSession(key_system, NULL, - identifier, NULL, &session_id)); + for (int i = 0; i < cdm_engine_count; i++) { + drm_metrics::WvCdmMetrics metrics; + metrics.Clear(); + ASSERT_EQ(wvcdm::NO_ERROR, + decryptor_.GetMetrics(identifiers[i], &metrics)); + std::string serialized_metrics; + ASSERT_TRUE(metrics.SerializeToString(&serialized_metrics)); - // The metrics will now have two engines with single session stats each. - std::string serialized_metrics; - decryptor_.GetSerializedMetrics(&serialized_metrics); - - // Spot check some metric values. - drm_metrics::WvCdmMetricsGroup metrics_group; - ASSERT_TRUE(metrics_group.ParseFromString(serialized_metrics)); - - // Two engine-level metrics are expected. - ASSERT_THAT(metrics_group.metrics().size(), Eq(2)); - - for (int i = 0; i < metrics_group.metrics().size(); i++) { - drm_metrics::WvCdmMetrics::EngineMetrics engine_metrics = - metrics_group.metrics(i).engine_metrics(); - // Validate an engine-level metric. - EXPECT_TRUE(engine_metrics.has_oemcrypto_initialization_mode()); - ASSERT_THAT(engine_metrics.cdm_engine_open_session().size(), Eq(1)); - EXPECT_THAT(engine_metrics.cdm_engine_open_session(0).count(), Eq(1)); + ASSERT_THAT(metrics.engine_metrics().cdm_engine_open_session().size(), + Eq(1)); + // The number of times open session was called should match the index + // of the identifier + EXPECT_THAT(metrics.engine_metrics().cdm_engine_open_session(0).count(), + Eq(i + 1)); EXPECT_THAT( - engine_metrics.cdm_engine_open_session(0).attributes().error_code(), + metrics.engine_metrics() + .cdm_engine_open_session(0).attributes().error_code(), Eq(CdmResponseType::NEED_PROVISIONING)); - // Validate a session-level metric. - ASSERT_THAT(metrics_group.metrics(i).session_metrics().size(), Eq(1)); - EXPECT_THAT(metrics_group.metrics(i).session_metrics(0) - .cdm_session_life_span_ms().double_value(), Gt(0.0)); + // Spot check a session-level metric. + ASSERT_THAT(metrics.session_metrics().size(), Eq(i + 1)) + << "Unexpected failure with session_metrics: " + << wvcdm::b2a_hex(serialized_metrics); + EXPECT_THAT(metrics.session_metrics(0) + .cdm_session_life_span_ms().double_value(), Gt(0.0)) + << "Unexpected failure with session_metrics: " + << wvcdm::b2a_hex(serialized_metrics); } } -} +} // wvcdm namespace diff --git a/libwvdrmengine/mediadrm/include_hidl/WVDrmPlugin.h b/libwvdrmengine/mediadrm/include_hidl/WVDrmPlugin.h index 34f68cad..8b6db1e3 100644 --- a/libwvdrmengine/mediadrm/include_hidl/WVDrmPlugin.h +++ b/libwvdrmengine/mediadrm/include_hidl/WVDrmPlugin.h @@ -365,6 +365,10 @@ struct WVDrmPlugin : public IDrmPlugin, IDrmPluginListener, const std::string& origin() const { return mCdmIdentifier.origin; } bool set_origin(const std::string& id); + // Indicates whether the builder can still be modified. This returns false + // until a call to getCdmIdentifier. + bool is_sealed() { return mIsIdentifierSealed; } + private: WVDRM_DISALLOW_COPY_AND_ASSIGN(CdmIdentifierBuilder); @@ -382,6 +386,13 @@ struct WVDrmPlugin : public IDrmPlugin, IDrmPluginListener, // outside this class should use getDeviceUniqueId() to get the // application-safe device-unique ID. Status getOemcryptoDeviceId(std::string* id); + + // The unique identifier is meant to ensure that two clients with the + // same spoid, origin and app package name still get different cdm engine + // instances. This is a stepping stone to simplifying the implementation. + // Note that we do not have a lock or mutex around this object. We assume + // that locking is handled external to this object. + uint32_t getNextUniqueId(); } mCdmIdentifierBuilder; sp const mCDM; diff --git a/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp b/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp index 6782fdd1..cc2eb2ab 100644 --- a/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp +++ b/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp @@ -392,7 +392,7 @@ status_t WVDrmPlugin::provideProvisionResponse( } CdmProvisioningResponse cdmResponse(response.begin(), response.end()); if (cdmResponse == kSpecialUnprovisionResponse) { - if (mCdmIdentifier == kDefaultCdmIdentifier) { + if (mCdmIdentifier.IsEquivalentToDefault()) { return kErrorNoOriginSpecified; } return unprovision(mCdmIdentifier); @@ -540,9 +540,14 @@ status_t WVDrmPlugin::getPropertyByteArray(const String8& name, } else if (name == "serviceCertificate") { value = ToVector(mPropertySet.service_certificate()); } else if (name == "metrics") { - std::string metrics_value; - mCDM->GetSerializedMetrics(&metrics_value); - value = ToVector(metrics_value); + std::string serialized_metrics; + drm_metrics::WvCdmMetrics metrics; + mCDM->GetMetrics(mCdmIdentifier, &metrics); + if (!metrics.SerializeToString(&serialized_metrics)) { + return android::ERROR_DRM_UNKNOWN; + } else { + value = ToVector(serialized_metrics); + } } else { ALOGE("App requested unknown byte array property %s", name.string()); return android::ERROR_DRM_CANNOT_HANDLE; diff --git a/libwvdrmengine/mediadrm/src_hidl/WVDrmPlugin.cpp b/libwvdrmengine/mediadrm/src_hidl/WVDrmPlugin.cpp index 2d909850..0cd85fd2 100644 --- a/libwvdrmengine/mediadrm/src_hidl/WVDrmPlugin.cpp +++ b/libwvdrmengine/mediadrm/src_hidl/WVDrmPlugin.cpp @@ -14,6 +14,7 @@ #include "mapErrors-inl.h" #include "media/stagefright/MediaErrors.h" +#include "metrics.pb.h" #include "openssl/sha.h" #include "wv_cdm_constants.h" @@ -139,6 +140,16 @@ WVDrmPlugin::~WVDrmPlugin() { } } mCryptoSessions.clear(); + CdmIdentifier identifier; + Status status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier); + if (status != Status::OK) { + ALOGE("Failed to get cdm identifier %d", status); + } else { + status = mapCdmResponseType(mCDM->CloseCdm(identifier)); + if (status != Status::OK) { + ALOGE("Failed to get close cdm %d", status); + } + } } Status WVDrmPlugin::openSessionCommon(std::vector& sessionId) { @@ -616,7 +627,7 @@ Return WVDrmPlugin::provideProvisionResponse( CdmProvisioningResponse cdmResponse(resp.begin(), resp.end()); if (cdmResponse == kSpecialUnprovisionResponse) { - if (identifier == kDefaultCdmIdentifier) { + if (identifier.IsEquivalentToDefault()) { ALOGW("Returns UNKNOWN error for legacy status kErrorNoOriginSpecified"); _hidl_cb(Status::ERROR_DRM_UNKNOWN, toHidlVec(certificate), toHidlVec(wrappedKey)); @@ -984,9 +995,27 @@ Return WVDrmPlugin::getPropertyByteArray( } else if (name == "serviceCertificate") { value = StrToVector(mPropertySet.service_certificate()); } else if (name == "metrics") { - std::string metrics_value; - mCDM->GetSerializedMetrics(&metrics_value); - value = StrToVector(metrics_value); + drm_metrics::WvCdmMetrics metrics; + // If the cdm identifier is not yet sealed, then there are no metrics + // for that cdm engine. Avoid calling getCdmIdentifier and sealing + // the identifier builder. + if (mCdmIdentifierBuilder.is_sealed()) { + CdmIdentifier identifier; + status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier); + if (status != Status::OK) { + ALOGE("Unexpected error retrieving cdm identifier: %d", status); + } else { + status = mapCdmResponseType(mCDM->GetMetrics(identifier, &metrics)); + } + } + if (status == Status::OK) { + std::string serialized_metrics; + if (!metrics.SerializeToString(&serialized_metrics)) { + status = Status::ERROR_DRM_UNKNOWN; + } else { + value = StrToVector(serialized_metrics); + } + } } else { ALOGE("App requested unknown byte array property %s", name.c_str()); status = Status::ERROR_DRM_CANNOT_HANDLE; @@ -1641,6 +1670,7 @@ WVDrmPlugin::CdmIdentifierBuilder::CdmIdentifierBuilder( mAppPackageName(appPackageName), mParent(parent) { mCdmIdentifier.app_package_name = mAppPackageName; + mCdmIdentifier.unique_id = getNextUniqueId(); } Status WVDrmPlugin::CdmIdentifierBuilder::getCdmIdentifier( @@ -1648,7 +1678,6 @@ Status WVDrmPlugin::CdmIdentifierBuilder::getCdmIdentifier( if (!mIsIdentifierSealed) { Status res = calculateSpoid(); if (res != Status::OK) return res; - mIsIdentifierSealed = true; } *identifier = mCdmIdentifier; @@ -1717,6 +1746,12 @@ Status WVDrmPlugin::CdmIdentifierBuilder::getOemcryptoDeviceId( return mParent.queryProperty(wvcdm::QUERY_KEY_DEVICE_ID, *id); } +uint32_t WVDrmPlugin::CdmIdentifierBuilder::getNextUniqueId() { + // Start with 1. 0 is reserved for the default cdm identifier. + static uint32_t unique_id = 1; + return ++unique_id; +} + } // namespace widevine } // namespace V1_1 } // namespace drm diff --git a/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp b/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp index 3876f1f0..fd36288a 100644 --- a/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp +++ b/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp @@ -20,6 +20,7 @@ #include "gtest/gtest.h" #include "media/stagefright/foundation/ABase.h" #include "media/stagefright/MediaErrors.h" +#include "string_conversions.h" #include "wv_cdm_constants.h" #include "wv_cdm_types.h" #include "wv_content_decryption_module.h" @@ -120,15 +121,17 @@ const uint8_t* const kUnprovisionResponse = const size_t kUnprovisionResponseSize = 11; const std::string kDeviceId("0123456789\0ABCDEF", 17); -// This is a serialized MetricsGroup message containing a small amount of +// This is a serialized WvCdmMetrics message containing a small amount of // sample data. This ensures we're able to extract it via a property. -const char kSerializedMetrics[] = { - 0x0a, 0x0a, 0x0a, 0x04, 0x74, 0x65, 0x73, 0x74, 0x12, 0x02, 0x08, 0x00, - 0x0a, 0x12, 0x0a, 0x05, 0x74, 0x65, 0x73, 0x74, 0x32, 0x12, 0x09, 0x11, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x15, 0x0a, 0x05, - 0x74, 0x65, 0x73, 0x74, 0x33, 0x12, 0x0c, 0x1a, 0x0a, 0x74, 0x65, 0x73, - 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65 -}; +const char kSerializedMetricsHex[] = + "0a580a001a0210072202100d2a02100832182216636f6d2e676f6f676c652e616e64726f69" + "642e676d734208220631342e302e304a06080112020800520610d5f3fad5056a0b1d00fd4c" + "47280132020804a2010608011202080012cb010a0622047369643412b5010a021001520919" + "1d5a643bdfff50405a0b1d00d01945280132021001620d1d00f8e84528013204080020006a" + "0310b739820102100d8a01060801120248009a010310ff01da0106080112024800e2010e1d" + "005243472801320528800248009a020d1d00b016452801320428404800a202060801120248" + "19aa0206080212024800b2020b1d8098f047280132024800ba02021001ca020b1d00101945" + "280132024800e202021004fa02021002a203021000b2030210021a09196891ed7c3f355040"; #define N_ELEM(a) (sizeof(a)/sizeof(a[0])) } // anonymous namespace @@ -198,7 +201,8 @@ class MockCDM : public WvContentDecryptionModule { MOCK_METHOD1(IsValidServiceCertificate, bool(const std::string&)); - MOCK_METHOD1(GetSerializedMetrics, void(std::string*)); + MOCK_METHOD2(GetMetrics, CdmResponseType(const CdmIdentifier&, + drm_metrics::WvCdmMetrics*)); }; class MockCrypto : public WVGenericCryptoInterface { @@ -1126,8 +1130,9 @@ TEST_F(WVDrmPluginTest, ReturnsExpectedPropertyValues) { static const std::string oemCryptoApiVersion = "13"; static const std::string currentSRMVersion = "1"; static const std::string cdmVersion = "Infinity Minus 1"; - std::string serializedMetrics( - kSerializedMetrics, kSerializedMetrics + sizeof(kSerializedMetrics)); + drm_metrics::WvCdmMetrics expected_metrics; + std::string serialized_metrics = wvcdm::a2bs_hex(kSerializedMetricsHex); + ASSERT_TRUE(expected_metrics.ParseFromString(serialized_metrics)); EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_SECURITY_LEVEL, _)) .WillOnce(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L1), @@ -1171,8 +1176,9 @@ TEST_F(WVDrmPluginTest, ReturnsExpectedPropertyValues) { .WillOnce(DoAll(SetArgPointee<2>(cdmVersion), testing::Return(wvcdm::NO_ERROR))); - EXPECT_CALL(*cdm, GetSerializedMetrics(_)) - .WillOnce(SetArgPointee<0>(serializedMetrics)); + EXPECT_CALL(*cdm, GetMetrics(_, _)) + .WillOnce(DoAll(SetArgPointee<1>(expected_metrics), + testing::Return(wvcdm::NO_ERROR))); WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false); std::string stringResult; @@ -1275,14 +1281,51 @@ TEST_F(WVDrmPluginTest, ReturnsExpectedPropertyValues) { EXPECT_STREQ(currentSRMVersion.c_str(), stringResult.c_str()); }); + // This call occurs before any open session or other call. This means + // that the cdm identifer is not yet sealed, and metrics return empty + // metrics data. plugin.getPropertyByteArray( hidl_string("metrics"), [&](Status status, hidl_vec vectorResult) { ASSERT_EQ(Status::OK, status); std::vector id(vectorResult); - EXPECT_THAT(id, ElementsAreArray(serializedMetrics.data(), - serializedMetrics.size())); + const char empty[] = {}; + EXPECT_THAT(id, ElementsAreArray(empty, sizeof(empty))); }); + + // Set expectations for the OpenSession call and a CloseSession call. + EXPECT_CALL(*cdm, + OpenSession(StrEq("com.widevine"), _, HasOrigin(EMPTY_ORIGIN), _, _)) + .WillOnce(DoAll(SetArgPointee<4>(cdmSessionId), + testing::Return(wvcdm::NO_ERROR))); + // Provide expected behavior when plugin requests session control info + EXPECT_CALL(*cdm, QueryOemCryptoSessionId(cdmSessionId, _)) + .Times(AtLeast(1)) + .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); + EXPECT_CALL(*cdm, CloseSession(_)) + .Times(AtLeast(0)); + + // This call causes the cdm identifier to become sealed. + std::vector sessionId; + plugin.openSession([&](Status status, hidl_vec hSessionId) { + ASSERT_EQ(Status::OK, status); + sessionId.clear(); + sessionId.assign(hSessionId.data(), + hSessionId.data() + hSessionId.size()); + }); + + // This call occurs after open session. The CDM identifer should be sealed. + // And the call should populate the mock metrics data. + plugin.getPropertyByteArray( + hidl_string("metrics"), + [&](Status status, hidl_vec vectorResult) { + ASSERT_EQ(Status::OK, status); + std::vector id(vectorResult); + EXPECT_THAT(id, ElementsAreArray(serialized_metrics.data(), + serialized_metrics.size())); + }); + + ASSERT_EQ(Status::OK, plugin.closeSession(toHidlVec(sessionId))); } TEST_F(WVDrmPluginTest, DoesNotGetUnknownProperties) { diff --git a/libwvdrmengine/mediadrm/test/legacy_src/WVDrmPlugin_test.cpp b/libwvdrmengine/mediadrm/test/legacy_src/WVDrmPlugin_test.cpp index 50fddc43..f9df36fb 100644 --- a/libwvdrmengine/mediadrm/test/legacy_src/WVDrmPlugin_test.cpp +++ b/libwvdrmengine/mediadrm/test/legacy_src/WVDrmPlugin_test.cpp @@ -13,6 +13,7 @@ #include "media/stagefright/foundation/ABase.h" #include "media/stagefright/foundation/AString.h" #include "media/stagefright/MediaErrors.h" +#include "string_conversions.h" #include "wv_cdm_constants.h" #include "wv_cdm_types.h" #include "wv_content_decryption_module.h" @@ -33,14 +34,18 @@ const uint8_t* const kUnprovisionResponse = reinterpret_cast("unprovision"); const size_t kUnprovisionResponseSize = 11; -// This is a serialized MetricsGroup message containing a small amount of +// This is a serialized WvCdmMetrics message containing a small amount of // sample data. This ensures we're able to extract it via a property. -const char kSerializedMetrics[] = { - 0x0a, 0x0a, 0x0a, 0x04, 0x74, 0x65, 0x73, 0x74, 0x12, 0x02, 0x08, 0x00, - 0x0a, 0x12, 0x0a, 0x05, 0x74, 0x65, 0x73, 0x74, 0x32, 0x12, 0x09, 0x11, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x15, 0x0a, 0x05, - 0x74, 0x65, 0x73, 0x74, 0x33, 0x12, 0x0c, 0x1a, 0x0a, 0x74, 0x65, 0x73, - 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65}; +const char kSerializedMetricsHex[] = + "0a580a001a0210072202100d2a02100832182216636f6d2e676f6f676c652e616e64726f69" + "642e676d734208220631342e302e304a06080112020800520610d5f3fad5056a0b1d00fd4c" + "47280132020804a2010608011202080012cb010a0622047369643412b5010a021001520919" + "1d5a643bdfff50405a0b1d00d01945280132021001620d1d00f8e84528013204080020006a" + "0310b739820102100d8a01060801120248009a010310ff01da0106080112024800e2010e1d" + "005243472801320528800248009a020d1d00b016452801320428404800a202060801120248" + "19aa0206080212024800b2020b1d8098f047280132024800ba02021001ca020b1d00101945" + "280132024800e202021004fa02021002a203021000b2030210021a09196891ed7c3f355040"; + } // anonymous namespace class MockCDM : public WvContentDecryptionModule { @@ -108,7 +113,8 @@ class MockCDM : public WvContentDecryptionModule { MOCK_METHOD1(IsValidServiceCertificate, bool(const std::string&)); - MOCK_METHOD1(GetSerializedMetrics, void(std::string*)); + MOCK_METHOD2(GetMetrics, CdmResponseType(const CdmIdentifier&, + drm_metrics::WvCdmMetrics*)); }; class MockCrypto : public WVGenericCryptoInterface { @@ -850,8 +856,10 @@ TEST_F(WVDrmPluginTest, ReturnsExpectedPropertyValues) { static const string oemCryptoApiVersion = "10"; static const string currentSRMVersion = "1"; static const string cdmVersion = "Infinity Minus 1"; - string serializedMetrics(kSerializedMetrics, - kSerializedMetrics + sizeof(kSerializedMetrics)); + + drm_metrics::WvCdmMetrics expected_metrics; + std::string serialized_metrics = wvcdm::a2bs_hex(kSerializedMetricsHex); + ASSERT_TRUE(expected_metrics.ParseFromString(serialized_metrics)); EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_SECURITY_LEVEL, _)) .WillOnce(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L1), @@ -895,8 +903,9 @@ TEST_F(WVDrmPluginTest, ReturnsExpectedPropertyValues) { .WillOnce(DoAll(SetArgPointee<2>(cdmVersion), Return(wvcdm::NO_ERROR))); - EXPECT_CALL(*cdm, GetSerializedMetrics(_)) - .WillOnce(SetArgPointee<0>(serializedMetrics)); + EXPECT_CALL(*cdm, GetMetrics(_, _)) + .WillOnce(DoAll(SetArgPointee<1>(expected_metrics), + testing::Return(wvcdm::NO_ERROR))); String8 stringResult; Vector vectorResult; @@ -962,8 +971,8 @@ TEST_F(WVDrmPluginTest, ReturnsExpectedPropertyValues) { vectorResult.clear(); res = plugin.getPropertyByteArray(String8("metrics"), vectorResult); ASSERT_EQ(OK, res); - EXPECT_THAT(vectorResult, ElementsAreArray(serializedMetrics.data(), - serializedMetrics.size())); + EXPECT_THAT(vectorResult, ElementsAreArray(serialized_metrics.data(), + serialized_metrics.size())); } TEST_F(WVDrmPluginTest, DoesNotGetUnknownProperties) {