diff --git a/libwvdrmengine/cdm/core/include/cdm_client_property_set.h b/libwvdrmengine/cdm/core/include/cdm_client_property_set.h index 7945859f..40687f4a 100644 --- a/libwvdrmengine/cdm/core/include/cdm_client_property_set.h +++ b/libwvdrmengine/cdm/core/include/cdm_client_property_set.h @@ -16,6 +16,9 @@ class CdmClientPropertySet { virtual std::string security_level() const = 0; virtual bool use_privacy_mode() const = 0; virtual std::vector service_certificate() const = 0; + virtual bool is_session_sharing_enabled() const = 0; + virtual uint32_t session_sharing_id() const = 0; + virtual void set_session_sharing_id(uint32_t id) = 0; }; } // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/include/cdm_engine.h b/libwvdrmengine/cdm/core/include/cdm_engine.h index c7814751..ca5487b9 100644 --- a/libwvdrmengine/cdm/core/include/cdm_engine.h +++ b/libwvdrmengine/cdm/core/include/cdm_engine.h @@ -93,6 +93,7 @@ class CdmEngine : public TimerHandler { // Is the key known to any session? virtual bool IsKeyValid(const KeyId& key_id); + virtual bool FindSessionForKey(const KeyId& key_id, CdmSessionId* sessionId); // Event listener related methods virtual bool AttachEventListener(const CdmSessionId& session_id, diff --git a/libwvdrmengine/cdm/core/include/license.h b/libwvdrmengine/cdm/core/include/license.h index d5990b6c..6e4534df 100644 --- a/libwvdrmengine/cdm/core/include/license.h +++ b/libwvdrmengine/cdm/core/include/license.h @@ -3,6 +3,8 @@ #ifndef CDM_BASE_LICENSE_H_ #define CDM_BASE_LICENSE_H_ +#include + #include "wv_cdm_types.h" namespace video_widevine_server { @@ -41,6 +43,7 @@ class CdmLicense { CdmKeyResponse& license_response, CdmKeyResponse& license_renewal_response); bool HasInitData() { return !init_data_.empty(); } + bool IsKeyLoaded(const KeyId& key_id); private: bool PrepareServiceCertificateRequest(CdmKeyMessage* signed_request, @@ -58,6 +61,7 @@ class CdmLicense { std::string service_certificate_; std::string init_data_; bool initialized_; + std::set loaded_keys_; // Used for certificate based licensing CdmKeyMessage key_request_; diff --git a/libwvdrmengine/cdm/core/include/properties.h b/libwvdrmengine/cdm/core/include/properties.h index 3a08055f..726491d6 100644 --- a/libwvdrmengine/cdm/core/include/properties.h +++ b/libwvdrmengine/cdm/core/include/properties.h @@ -66,6 +66,7 @@ class Properties { static const std::vector GetServiceCertificate( const CdmSessionId& session_id); static bool UsePrivacyMode(const CdmSessionId& session_id); + static uint32_t GetSessionSharingId(const CdmSessionId& session_id); static bool AddSessionPropertySet( const CdmSessionId& session_id, diff --git a/libwvdrmengine/cdm/core/src/cdm_engine.cpp b/libwvdrmengine/cdm/core/src/cdm_engine.cpp index 0089b969..935fa4ff 100644 --- a/libwvdrmengine/cdm/core/src/cdm_engine.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_engine.cpp @@ -525,6 +525,29 @@ bool CdmEngine::IsKeyValid(const KeyId& key_id) { return false; } +bool CdmEngine::FindSessionForKey( + const KeyId& key_id, + CdmSessionId* session_id) { + if (NULL == session_id) { + LOGE("CdmEngine::FindSessionForKey: session id not provided"); + return false; + } + + uint32_t session_sharing_id = Properties::GetSessionSharingId(*session_id); + + for (CdmSessionMap::iterator iter = sessions_.begin(); + iter != sessions_.end(); ++iter) { + CdmSessionId id = iter->second->session_id(); + if (Properties::GetSessionSharingId(id) == session_sharing_id) { + if (iter->second->IsKeyValid(key_id)) { + *session_id = id; + return true; + } + } + } + return false; +} + bool CdmEngine::AttachEventListener( const CdmSessionId& session_id, WvCdmEventListener* listener) { diff --git a/libwvdrmengine/cdm/core/src/cdm_session.cpp b/libwvdrmengine/cdm/core/src/cdm_session.cpp index 4ecce4a5..bb7acfc5 100644 --- a/libwvdrmengine/cdm/core/src/cdm_session.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_session.cpp @@ -335,22 +335,7 @@ CdmResponseType CdmSession::ReleaseKey(const CdmKeyResponse& key_response) { } bool CdmSession::IsKeyValid(const KeyId& key_id) { - if (crypto_session_.get() == NULL || !crypto_session_->IsOpen()) - return false; - - if (key_id_ != key_id) { - // OEMCrypto does not provide a way to query the existence/validity of a - // key. SelectKey can be used to check whether a key is valid, but there - // is also a side effect - the key is selected for decryption, which might - // be undesirable and it posts restriction on the use of IsKeyValid API. - // TODO(kqyang, gmorgan): consider adding a function in OEMCrypto to check - // if a key is valid. - if (!crypto_session_->SelectKey(key_id)) { - return false; - } - key_id_ = key_id; - } - return true; + return license_parser_.IsKeyLoaded(key_id); } CdmSessionId CdmSession::GenerateSessionId() { diff --git a/libwvdrmengine/cdm/core/src/license.cpp b/libwvdrmengine/cdm/core/src/license.cpp index df1009fb..a0d99c75 100644 --- a/libwvdrmengine/cdm/core/src/license.cpp +++ b/libwvdrmengine/cdm/core/src/license.cpp @@ -539,9 +539,22 @@ CdmResponseType CdmLicense::HandleKeyResponse( // merge from Eureka) policy_engine_->SetLicense(license); - return session_->LoadKeys(signed_response.msg(), signed_response.signature(), - mac_key_iv, mac_key, key_array.size(), - &key_array[0]); + CdmResponseType resp = session_->LoadKeys(signed_response.msg(), + signed_response.signature(), + mac_key_iv, + mac_key, + key_array.size(), + &key_array[0]); + + if (KEY_ADDED == resp) { + loaded_keys_.clear(); + for (std::vector::iterator it = key_array.begin(); + it != key_array.end(); + ++it) { + loaded_keys_.insert(it->key_id()); + } + } + return resp; } CdmResponseType CdmLicense::HandleKeyUpdateResponse( @@ -753,4 +766,8 @@ CdmResponseType CdmLicense::HandleKeyErrorResponse( } } +bool CdmLicense::IsKeyLoaded(const KeyId& key_id) { + return loaded_keys_.find(key_id) != loaded_keys_.end(); +} + } // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/src/properties.cpp b/libwvdrmengine/cdm/core/src/properties.cpp index 352c0951..14cfc8b2 100644 --- a/libwvdrmengine/cdm/core/src/properties.cpp +++ b/libwvdrmengine/cdm/core/src/properties.cpp @@ -100,6 +100,17 @@ bool Properties::UsePrivacyMode(const CdmSessionId& session_id) { return property_set->use_privacy_mode(); } +uint32_t Properties::GetSessionSharingId(const CdmSessionId& session_id) { + const CdmClientPropertySet* property_set = + GetCdmClientPropertySet(session_id); + if (NULL == property_set) { + LOGE("Properties::GetSessionSharingId: cannot find property set for %s", + session_id.c_str()); + return 0; + } + return property_set->session_sharing_id(); +} + bool Properties::GetSecurityLevelDirectories(std::vector* dirs) { dirs->resize(sizeof(kSecurityLevelDirs)/sizeof(const char*)); for (size_t i = 0; i < dirs->size(); ++i) { diff --git a/libwvdrmengine/cdm/include/wv_content_decryption_module.h b/libwvdrmengine/cdm/include/wv_content_decryption_module.h index d89c4c98..0613e507 100644 --- a/libwvdrmengine/cdm/include/wv_content_decryption_module.h +++ b/libwvdrmengine/cdm/include/wv_content_decryption_module.h @@ -21,7 +21,7 @@ class WvContentDecryptionModule { // Session related methods virtual CdmResponseType OpenSession( const CdmKeySystem& key_system, - const CdmClientPropertySet* property_set, + CdmClientPropertySet* property_set, CdmSessionId* session_id); virtual CdmResponseType CloseSession(const CdmSessionId& session_id); @@ -84,6 +84,7 @@ class WvContentDecryptionModule { WvCdmEventListener* listener); private: + uint32_t GenerateSessionSharingId(); // instance variables UniquePtr cdm_engine_; diff --git a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp index ae8a1ada..258d75ac 100644 --- a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp +++ b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp @@ -4,8 +4,10 @@ #include +#include "cdm_client_property_set.h" #include "cdm_engine.h" #include "log.h" +#include "properties.h" #include "wv_cdm_constants.h" #include "wv_cdm_event_listener.h" @@ -18,8 +20,13 @@ WvContentDecryptionModule::~WvContentDecryptionModule() {} CdmResponseType WvContentDecryptionModule::OpenSession( const CdmKeySystem& key_system, - const CdmClientPropertySet* property_set, + CdmClientPropertySet* property_set, CdmSessionId* session_id) { + if (property_set && property_set->is_session_sharing_enabled()) { + if (property_set->session_sharing_id() == 0) + property_set->set_session_sharing_id(GenerateSessionSharingId()); + } + return cdm_engine_->OpenSession(key_system, property_set, session_id); } @@ -111,7 +118,15 @@ CdmResponseType WvContentDecryptionModule::ReleaseSecureStops( CdmResponseType WvContentDecryptionModule::Decrypt( const CdmSessionId& session_id, const CdmDecryptionParameters& parameters) { - return cdm_engine_->Decrypt(session_id, parameters); + CdmSessionId id = session_id; + if (Properties::GetSessionSharingId(session_id) != 0) { + bool status = cdm_engine_->FindSessionForKey(*parameters.key_id, &id); + if (!status) { + LOGE("WvContentDecryptionModule::Decrypt: unable to find session"); + return KEY_ERROR; + } + } + return cdm_engine_->Decrypt(id, parameters); } bool WvContentDecryptionModule::AttachEventListener( @@ -124,4 +139,9 @@ bool WvContentDecryptionModule::DetachEventListener( return cdm_engine_->DetachEventListener(session_id, listener); } +uint32_t WvContentDecryptionModule::GenerateSessionSharingId() { + static int next_session_sharing_id = 0; + return ++next_session_sharing_id; +} + } // namespace wvcdm diff --git a/libwvdrmengine/cdm/test/request_license_test.cpp b/libwvdrmengine/cdm/test/request_license_test.cpp index 22025d60..e028249e 100644 --- a/libwvdrmengine/cdm/test/request_license_test.cpp +++ b/libwvdrmengine/cdm/test/request_license_test.cpp @@ -205,7 +205,10 @@ SubSampleInfo partial_offset_single_encrypted_sub_sample = { namespace wvcdm { class TestWvCdmClientPropertySet : public CdmClientPropertySet { public: - TestWvCdmClientPropertySet() : use_privacy_mode_(false) {} + TestWvCdmClientPropertySet() + : use_privacy_mode_(false), + is_session_sharing_enabled_(false), + session_sharing_id_(0) {} virtual ~TestWvCdmClientPropertySet() {} virtual std::string security_level() const { return security_level_; } @@ -213,6 +216,10 @@ class TestWvCdmClientPropertySet : public CdmClientPropertySet { return service_certificate_; } virtual bool use_privacy_mode() const { return use_privacy_mode_; } + bool is_session_sharing_enabled() const { + return is_session_sharing_enabled_; + } + uint32_t session_sharing_id() const { return session_sharing_id_; } void set_security_level(const std::string& security_level) { if (!security_level.compare(QUERY_VALUE_SECURITY_LEVEL_L1) || @@ -227,11 +234,17 @@ class TestWvCdmClientPropertySet : public CdmClientPropertySet { void set_use_privacy_mode(bool use_privacy_mode) { use_privacy_mode_ = use_privacy_mode; } + void set_session_sharing_mode(bool enable) { + is_session_sharing_enabled_ = enable; + } + void set_session_sharing_id(uint32_t id) { session_sharing_id_ = id; } private: std::string security_level_; std::vector service_certificate_; bool use_privacy_mode_; + bool is_session_sharing_enabled_; + uint32_t session_sharing_id_; }; class TestWvCdmEventListener : public WvCdmEventListener { @@ -377,6 +390,10 @@ class WvCdmDecryptionTest : public WvCdmRequestLicenseTest, public ::testing::WithParamInterface {}; +class WvCdmSessionSharingTest + : public WvCdmRequestLicenseTest, + public ::testing::WithParamInterface {}; + TEST_F(WvCdmRequestLicenseTest, ProvisioningTest) { decryptor_.OpenSession(g_key_system, NULL, &session_id_); std::string provisioning_server_url; @@ -523,6 +540,54 @@ TEST_F(WvCdmRequestLicenseTest, DISABLED_PrivacyModeWithServiceCertificateTest) decryptor_.CloseSession(session_id_); } +TEST_P(WvCdmSessionSharingTest, SessionSharingTest) { + bool enable_session_sharing = GetParam(); + + TestWvCdmClientPropertySet property_set; + property_set.set_session_sharing_mode(enable_session_sharing); + + decryptor_.OpenSession(g_key_system, &property_set, &session_id_); + CdmSessionId gp_session_id_1 = session_id_; + GenerateKeyRequest(g_key_system, g_key_id, kLicenseTypeStreaming); + VerifyKeyRequestResponse(g_license_server, g_client_auth, g_key_id, false); + + // TODO(rfrias): Move content information to ConfigTestEnv + std::string gp_client_auth2 = + "?source=YOUTUBE&video_id=z3S_NhwueaM&oauth=ya.gtsqawidevine"; + std::string gp_key_id2 = + wvcdm::a2bs_hex( + "000000347073736800000000" // blob size and pssh + "edef8ba979d64acea3c827dcd51d21ed00000014" // Widevine system id + "08011210bdf1cb4fffc6506b8b7945b0bd2917fb"); // pssh data + + decryptor_.OpenSession(g_key_system, &property_set, &session_id_); + CdmSessionId gp_session_id_2 = session_id_; + GenerateKeyRequest(g_key_system, gp_key_id2, kLicenseTypeStreaming); + VerifyKeyRequestResponse(g_license_server, gp_client_auth2, gp_key_id2, false); + + SubSampleInfo* data = &single_encrypted_sub_sample; + std::vector decrypt_buffer(data->encrypt_data.size()); + CdmDecryptionParameters decryption_parameters(&data->key_id, + &data->encrypt_data.front(), + data->encrypt_data.size(), + &data->iv, + data->block_offset, + &decrypt_buffer[0]); + decryption_parameters.is_encrypted = data->is_encrypted; + decryption_parameters.is_secure = data->is_secure; + EXPECT_EQ( + NO_ERROR == decryptor_.Decrypt(gp_session_id_2, decryption_parameters), + enable_session_sharing); + EXPECT_EQ(std::equal(data->decrypt_data.begin(), data->decrypt_data.end(), + decrypt_buffer.begin()), + enable_session_sharing); + + decryptor_.CloseSession(gp_session_id_1); + decryptor_.CloseSession(gp_session_id_2); +} + +INSTANTIATE_TEST_CASE_P(Cdm, WvCdmSessionSharingTest, ::testing::Bool()); + TEST_F(WvCdmRequestLicenseTest, BaseMessageTest) { decryptor_.OpenSession(g_key_system, NULL, &session_id_); GenerateKeyRequest(g_key_system, g_key_id, kLicenseTypeStreaming); diff --git a/libwvdrmengine/mediadrm/include/WVDrmPlugin.h b/libwvdrmengine/mediadrm/include/WVDrmPlugin.h index 292d6153..e33350fc 100644 --- a/libwvdrmengine/mediadrm/include/WVDrmPlugin.h +++ b/libwvdrmengine/mediadrm/include/WVDrmPlugin.h @@ -161,40 +161,58 @@ class WVDrmPlugin : public android::DrmPlugin, class WVClientPropertySet : public wvcdm::CdmClientPropertySet { public: WVClientPropertySet() - : mUsePrivacyMode(false) {} + : mUsePrivacyMode(false), mShareKeys(false), mSessionSharingId(0) {} virtual ~WVClientPropertySet() {} - void set_security_level(const std::string& securityLevel) { - mSecurityLevel = securityLevel; - } - virtual std::string security_level() const { return mSecurityLevel; } - void set_use_privacy_mode(bool usePrivacyMode) { - mUsePrivacyMode = usePrivacyMode; + void set_security_level(const std::string& securityLevel) { + mSecurityLevel = securityLevel; } virtual bool use_privacy_mode() const { return mUsePrivacyMode; } - void set_service_certificate(const std::vector& serviceCertificate) { - mServiceCertificate = serviceCertificate; + void set_use_privacy_mode(bool usePrivacyMode) { + mUsePrivacyMode = usePrivacyMode; } virtual std::vector service_certificate() const { return mServiceCertificate; } + void set_service_certificate(const std::vector& serviceCertificate) { + mServiceCertificate = serviceCertificate; + } + + virtual bool is_session_sharing_enabled() const { + return mShareKeys; + } + + void set_is_session_sharing_enabled(bool shareKeys) { + mShareKeys = shareKeys; + } + + virtual uint32_t session_sharing_id() const { + return mSessionSharingId; + } + + virtual void set_session_sharing_id(uint32_t id) { + mSessionSharingId = id; + } + private: DISALLOW_EVIL_CONSTRUCTORS(WVClientPropertySet); std::string mSecurityLevel; bool mUsePrivacyMode; std::vector mServiceCertificate; + bool mShareKeys; + uint32_t mSessionSharingId; } mPropertySet; WvContentDecryptionModule* mCDM; diff --git a/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp b/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp index bf09a930..83ea7855 100644 --- a/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp +++ b/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp @@ -392,6 +392,12 @@ status_t WVDrmPlugin::getPropertyString(const String8& name, } else { value = kDisable; } + } else if (name == "sessionSharing") { + if (mPropertySet.is_session_sharing_enabled()) { + value = kEnable; + } else { + value = kDisable; + } } else { ALOGE("App requested unknown string property %s", name.string()); return android::ERROR_DRM_CANNOT_HANDLE; @@ -475,6 +481,20 @@ status_t WVDrmPlugin::setPropertyString(const String8& name, ALOGE("App requested unknown privacy mode %s", value.string()); return android::BAD_VALUE; } + } else if (name == "sessionSharing") { + if (mCryptoSessions.size() == 0) { + if (value == kEnable) { + mPropertySet.set_is_session_sharing_enabled(true); + } else if (value == kDisable) { + mPropertySet.set_is_session_sharing_enabled(false); + } else { + ALOGE("App requested unknown sharing type %s", value.string()); + return android::BAD_VALUE; + } + } else { + ALOGE("App tried to change key sharing while sessions are open."); + return kErrorSessionIsOpen; + } } else { ALOGE("App set unknown string property %s", name.string()); return android::ERROR_DRM_CANNOT_HANDLE; diff --git a/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp b/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp index e42a1ffb..ac2efb23 100644 --- a/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp +++ b/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp @@ -26,7 +26,7 @@ using namespace wvdrm; class MockCDM : public WvContentDecryptionModule { public: MOCK_METHOD3(OpenSession, CdmResponseType(const CdmKeySystem&, - const CdmClientPropertySet*, + CdmClientPropertySet*, CdmSessionId*)); MOCK_METHOD1(CloseSession, CdmResponseType(const CdmSessionId&)); @@ -1181,7 +1181,6 @@ TEST_F(WVDrmPluginTest, GeneratesProvisioningNeededEvent) { ASSERT_EQ(ERROR_DRM_NOT_PROVISIONED, res); } - TEST_F(WVDrmPluginTest, ProvidesExpectedDefaultPropertiesToCdm) { StrictMock cdm; StrictMock crypto; @@ -1219,6 +1218,8 @@ TEST_F(WVDrmPluginTest, ProvidesExpectedDefaultPropertiesToCdm) { EXPECT_STREQ("", propertySet->security_level().c_str()); EXPECT_FALSE(propertySet->use_privacy_mode()); EXPECT_EQ(0u, propertySet->service_certificate().size()); + EXPECT_FALSE(propertySet->is_session_sharing_enabled()); + EXPECT_EQ(0u, propertySet->session_sharing_id()); } TEST_F(WVDrmPluginTest, CanSetSecurityLevel) { @@ -1395,3 +1396,107 @@ TEST_F(WVDrmPluginTest, CanSetServiceCertificate) { ASSERT_EQ(OK, res); EXPECT_EQ(0u, propertySet->service_certificate().size()); } + +TEST_F(WVDrmPluginTest, CanSetSessionSharing) { + StrictMock cdm; + StrictMock crypto; + WVDrmPlugin plugin(&cdm, &crypto); + + const CdmClientPropertySet* propertySet = NULL; + + // Provide expected mock behavior + { + // Provide expected behavior in response to OpenSession and store the + // property set + EXPECT_CALL(cdm, OpenSession(_, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<2>(cdmSessionId), + SaveArg<1>(&propertySet), + Return(wvcdm::NO_ERROR))); + + // Provide expected behavior when plugin requests session control info + EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _)) + .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); + + // Let gMock know these calls will happen but we aren't interested in them. + EXPECT_CALL(cdm, AttachEventListener(_, _)) + .Times(AtLeast(0)); + + EXPECT_CALL(cdm, DetachEventListener(_, _)) + .Times(AtLeast(0)); + + EXPECT_CALL(cdm, CloseSession(_)) + .Times(AtLeast(0)); + } + + status_t res; + + // Test turning on session sharing + res = plugin.setPropertyString(String8("sessionSharing"), String8("enable")); + ASSERT_EQ(OK, res); + + plugin.openSession(sessionId); + ASSERT_THAT(propertySet, NotNull()); + EXPECT_TRUE(propertySet->is_session_sharing_enabled()); + plugin.closeSession(sessionId); + + // Test turning off session sharing + res = plugin.setPropertyString(String8("sessionSharing"), String8("disable")); + ASSERT_EQ(OK, res); + + plugin.openSession(sessionId); + ASSERT_THAT(propertySet, NotNull()); + EXPECT_FALSE(propertySet->is_session_sharing_enabled()); + plugin.closeSession(sessionId); + + // Test nonsense (Should Fail) + res = plugin.setPropertyString(String8("sessionSharing"), String8("nonsense")); + ASSERT_NE(OK, res); + + // Test changing sharing with a session open (Should Fail) + plugin.openSession(sessionId); + res = plugin.setPropertyString(String8("sessionSharing"), String8("enable")); + ASSERT_NE(OK, res); +} + +TEST_F(WVDrmPluginTest, AllowsStoringOfSessionSharingId) { + StrictMock cdm; + StrictMock crypto; + WVDrmPlugin plugin(&cdm, &crypto); + + CdmClientPropertySet* propertySet = NULL; + + uint32_t sharingId; + FILE* fp = fopen("/dev/urandom", "r"); + fread(&sharingId, sizeof(uint32_t), 1, fp); + fclose(fp); + + // Provide expected mock behavior + { + // Provide expected behavior in response to OpenSession and store the + // property set + EXPECT_CALL(cdm, OpenSession(_, _, _)) + .WillRepeatedly(DoAll(SetArgPointee<2>(cdmSessionId), + SaveArg<1>(&propertySet), + Return(wvcdm::NO_ERROR))); + + // Provide expected behavior when plugin requests session control info + EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _)) + .WillRepeatedly(Invoke(setSessionIdOnMap<4>)); + + // Let gMock know these calls will happen but we aren't interested in them. + EXPECT_CALL(cdm, AttachEventListener(_, _)) + .Times(AtLeast(0)); + + EXPECT_CALL(cdm, DetachEventListener(_, _)) + .Times(AtLeast(0)); + + EXPECT_CALL(cdm, CloseSession(_)) + .Times(AtLeast(0)); + } + + plugin.openSession(sessionId); + + ASSERT_THAT(propertySet, NotNull()); + propertySet->set_session_sharing_id(sharingId); + EXPECT_EQ(sharingId, propertySet->session_sharing_id()); +}