diff options
Diffstat (limited to 'media/cdm')
-rw-r--r-- | media/cdm/aes_decryptor.cc | 140 | ||||
-rw-r--r-- | media/cdm/aes_decryptor.h | 39 | ||||
-rw-r--r-- | media/cdm/aes_decryptor_unittest.cc | 277 | ||||
-rw-r--r-- | media/cdm/ppapi/cdm_adapter.cc | 345 | ||||
-rw-r--r-- | media/cdm/ppapi/cdm_adapter.h | 108 | ||||
-rw-r--r-- | media/cdm/ppapi/cdm_wrapper.h | 198 | ||||
-rw-r--r-- | media/cdm/ppapi/external_clear_key/clear_key_cdm.cc | 259 | ||||
-rw-r--r-- | media/cdm/ppapi/external_clear_key/clear_key_cdm.h | 59 | ||||
-rw-r--r-- | media/cdm/ppapi/external_clear_key/clear_key_cdm_common.h | 2 | ||||
-rw-r--r-- | media/cdm/ppapi/supported_cdm_versions.h | 6 |
10 files changed, 1019 insertions, 414 deletions
diff --git a/media/cdm/aes_decryptor.cc b/media/cdm/aes_decryptor.cc index 51661fd..9416707 100644 --- a/media/cdm/aes_decryptor.cc +++ b/media/cdm/aes_decryptor.cc @@ -13,6 +13,7 @@ #include "crypto/encryptor.h" #include "crypto/symmetric_key.h" #include "media/base/audio_decoder_config.h" +#include "media/base/cdm_promise.h" #include "media/base/decoder_buffer.h" #include "media/base/decrypt_config.h" #include "media/base/video_decoder_config.h" @@ -28,7 +29,7 @@ class AesDecryptor::SessionIdDecryptionKeyMap { // Use a std::list to actually hold the data. Insertion is always done // at the front, so the "latest" decryption key is always the first one // in the list. - typedef std::list<std::pair<uint32, DecryptionKey*> > KeyList; + typedef std::list<std::pair<std::string, DecryptionKey*> > KeyList; public: SessionIdDecryptionKeyMap() {} @@ -37,10 +38,11 @@ class AesDecryptor::SessionIdDecryptionKeyMap { // Replaces value if |session_id| is already present, or adds it if not. // This |decryption_key| becomes the latest until another insertion or // |session_id| is erased. - void Insert(uint32 session_id, scoped_ptr<DecryptionKey> decryption_key); + void Insert(const std::string& web_session_id, + scoped_ptr<DecryptionKey> decryption_key); // Deletes the entry for |session_id| if present. - void Erase(const uint32 session_id); + void Erase(const std::string& web_session_id); // Returns whether the list is empty bool Empty() const { return key_list_.empty(); } @@ -52,8 +54,8 @@ class AesDecryptor::SessionIdDecryptionKeyMap { } private: - // Searches the list for an element with |session_id|. - KeyList::iterator Find(const uint32 session_id); + // Searches the list for an element with |web_session_id|. + KeyList::iterator Find(const std::string& web_session_id); // Deletes the entry pointed to by |position|. void Erase(KeyList::iterator position); @@ -64,26 +66,28 @@ class AesDecryptor::SessionIdDecryptionKeyMap { }; void AesDecryptor::SessionIdDecryptionKeyMap::Insert( - uint32 session_id, + const std::string& web_session_id, scoped_ptr<DecryptionKey> decryption_key) { - KeyList::iterator it = Find(session_id); + KeyList::iterator it = Find(web_session_id); if (it != key_list_.end()) Erase(it); DecryptionKey* raw_ptr = decryption_key.release(); - key_list_.push_front(std::make_pair(session_id, raw_ptr)); + key_list_.push_front(std::make_pair(web_session_id, raw_ptr)); } -void AesDecryptor::SessionIdDecryptionKeyMap::Erase(const uint32 session_id) { - KeyList::iterator it = Find(session_id); +void AesDecryptor::SessionIdDecryptionKeyMap::Erase( + const std::string& web_session_id) { + KeyList::iterator it = Find(web_session_id); if (it == key_list_.end()) return; Erase(it); } AesDecryptor::SessionIdDecryptionKeyMap::KeyList::iterator -AesDecryptor::SessionIdDecryptionKeyMap::Find(const uint32 session_id) { +AesDecryptor::SessionIdDecryptionKeyMap::Find( + const std::string& web_session_id) { for (KeyList::iterator it = key_list_.begin(); it != key_list_.end(); ++it) { - if (it->first == session_id) + if (it->first == web_session_id) return it; } return key_list_.end(); @@ -215,67 +219,70 @@ static scoped_refptr<DecoderBuffer> DecryptData(const DecoderBuffer& input, return output; } -AesDecryptor::AesDecryptor(const SessionCreatedCB& session_created_cb, - const SessionMessageCB& session_message_cb, - const SessionReadyCB& session_ready_cb, - const SessionClosedCB& session_closed_cb, - const SessionErrorCB& session_error_cb) - : session_created_cb_(session_created_cb), - session_message_cb_(session_message_cb), - session_ready_cb_(session_ready_cb), - session_closed_cb_(session_closed_cb), - session_error_cb_(session_error_cb) {} +AesDecryptor::AesDecryptor(const SessionMessageCB& session_message_cb) + : session_message_cb_(session_message_cb) { + DCHECK(!session_message_cb_.is_null()); +} AesDecryptor::~AesDecryptor() { key_map_.clear(); } -bool AesDecryptor::CreateSession(uint32 session_id, - const std::string& content_type, +void AesDecryptor::CreateSession(const std::string& init_data_type, const uint8* init_data, - int init_data_length) { - // Validate that this is a new session. - DCHECK(valid_sessions_.find(session_id) == valid_sessions_.end()); - valid_sessions_.insert(session_id); - - std::string web_session_id_string(base::UintToString(next_web_session_id_++)); - - // For now, the AesDecryptor does not care about |content_type|; - // just fire the event with the |init_data| as the request. + int init_data_length, + SessionType session_type, + scoped_ptr<NewSessionCdmPromise> promise) { + std::string web_session_id(base::UintToString(next_web_session_id_++)); + valid_sessions_.insert(web_session_id); + + // For now, the AesDecryptor does not care about |init_data_type| or + // |session_type|; just resolve the promise and then fire a message event + // with the |init_data| as the request. + // TODO(jrummell): Validate |init_data_type| and |session_type|. std::vector<uint8> message; if (init_data && init_data_length) message.assign(init_data, init_data + init_data_length); - session_created_cb_.Run(session_id, web_session_id_string); - session_message_cb_.Run(session_id, message, GURL()); - return true; + promise->resolve(web_session_id); + + session_message_cb_.Run(web_session_id, message, GURL()); } -void AesDecryptor::LoadSession(uint32 session_id, - const std::string& web_session_id) { +void AesDecryptor::LoadSession(const std::string& web_session_id, + scoped_ptr<NewSessionCdmPromise> promise) { // TODO(xhwang): Change this to NOTREACHED() when blink checks for key systems // that do not support loadSession. See http://crbug.com/342481 - session_error_cb_.Run(session_id, MediaKeys::kUnknownError, 0); + promise->reject(NOT_SUPPORTED_ERROR, 0, "LoadSession() is not supported."); } -void AesDecryptor::UpdateSession(uint32 session_id, +void AesDecryptor::UpdateSession(const std::string& web_session_id, const uint8* response, - int response_length) { + int response_length, + scoped_ptr<SimpleCdmPromise> promise) { CHECK(response); CHECK_GT(response_length, 0); - DCHECK(valid_sessions_.find(session_id) != valid_sessions_.end()); + + // TODO(jrummell): Convert back to a DCHECK once prefixed EME is removed. + if (valid_sessions_.find(web_session_id) == valid_sessions_.end()) { + promise->reject(INVALID_ACCESS_ERROR, 0, "Session does not exist."); + return; + } std::string key_string(reinterpret_cast<const char*>(response), response_length); + KeyIdAndKeyPairs keys; if (!ExtractKeysFromJWKSet(key_string, &keys)) { - session_error_cb_.Run(session_id, MediaKeys::kUnknownError, 0); + promise->reject( + INVALID_ACCESS_ERROR, 0, "response is not a valid JSON Web Key Set."); return; } // Make sure that at least one key was extracted. if (keys.empty()) { - session_error_cb_.Run(session_id, MediaKeys::kUnknownError, 0); + promise->reject( + INVALID_ACCESS_ERROR, 0, "response does not contain any keys."); return; } @@ -283,11 +290,11 @@ void AesDecryptor::UpdateSession(uint32 session_id, if (it->second.length() != static_cast<size_t>(DecryptConfig::kDecryptionKeySize)) { DVLOG(1) << "Invalid key length: " << key_string.length(); - session_error_cb_.Run(session_id, MediaKeys::kUnknownError, 0); + promise->reject(INVALID_ACCESS_ERROR, 0, "Invalid key length."); return; } - if (!AddDecryptionKey(session_id, it->first, it->second)) { - session_error_cb_.Run(session_id, MediaKeys::kUnknownError, 0); + if (!AddDecryptionKey(web_session_id, it->first, it->second)) { + promise->reject(INVALID_ACCESS_ERROR, 0, "Unable to add key."); return; } } @@ -302,17 +309,24 @@ void AesDecryptor::UpdateSession(uint32 session_id, new_video_key_cb_.Run(); } - session_ready_cb_.Run(session_id); + promise->resolve(); } -void AesDecryptor::ReleaseSession(uint32 session_id) { +void AesDecryptor::ReleaseSession(const std::string& web_session_id, + scoped_ptr<SimpleCdmPromise> promise) { // Validate that this is a reference to an active session and then forget it. - std::set<uint32>::iterator it = valid_sessions_.find(session_id); - DCHECK(it != valid_sessions_.end()); + std::set<std::string>::iterator it = valid_sessions_.find(web_session_id); + // TODO(jrummell): Convert back to a DCHECK once prefixed EME is removed. + if (it == valid_sessions_.end()) { + promise->reject(INVALID_ACCESS_ERROR, 0, "Session does not exist."); + return; + } + valid_sessions_.erase(it); - DeleteKeysForSession(session_id); - session_closed_cb_.Run(session_id); + // Close the session. + DeleteKeysForSession(web_session_id); + promise->resolve(); } Decryptor* AesDecryptor::GetDecryptor() { @@ -404,15 +418,10 @@ void AesDecryptor::DeinitializeDecoder(StreamType stream_type) { NOTREACHED() << "AesDecryptor does not support audio/video decoding"; } -bool AesDecryptor::AddDecryptionKey(const uint32 session_id, +bool AesDecryptor::AddDecryptionKey(const std::string& web_session_id, const std::string& key_id, const std::string& key_string) { scoped_ptr<DecryptionKey> decryption_key(new DecryptionKey(key_string)); - if (!decryption_key) { - DVLOG(1) << "Could not create key."; - return false; - } - if (!decryption_key->Init()) { DVLOG(1) << "Could not initialize decryption key."; return false; @@ -421,14 +430,14 @@ bool AesDecryptor::AddDecryptionKey(const uint32 session_id, base::AutoLock auto_lock(key_map_lock_); KeyIdToSessionKeysMap::iterator key_id_entry = key_map_.find(key_id); if (key_id_entry != key_map_.end()) { - key_id_entry->second->Insert(session_id, decryption_key.Pass()); + key_id_entry->second->Insert(web_session_id, decryption_key.Pass()); return true; } // |key_id| not found, so need to create new entry. scoped_ptr<SessionIdDecryptionKeyMap> inner_map( new SessionIdDecryptionKeyMap()); - inner_map->Insert(session_id, decryption_key.Pass()); + inner_map->Insert(web_session_id, decryption_key.Pass()); key_map_.add(key_id, inner_map.Pass()); return true; } @@ -444,14 +453,15 @@ AesDecryptor::DecryptionKey* AesDecryptor::GetKey( return key_id_found->second->LatestDecryptionKey(); } -void AesDecryptor::DeleteKeysForSession(const uint32 session_id) { +void AesDecryptor::DeleteKeysForSession(const std::string& web_session_id) { base::AutoLock auto_lock(key_map_lock_); - // Remove all keys associated with |session_id|. Since the data is optimized - // for access in GetKey(), we need to look at each entry in |key_map_|. + // Remove all keys associated with |web_session_id|. Since the data is + // optimized for access in GetKey(), we need to look at each entry in + // |key_map_|. KeyIdToSessionKeysMap::iterator it = key_map_.begin(); while (it != key_map_.end()) { - it->second->Erase(session_id); + it->second->Erase(web_session_id); if (it->second->Empty()) { // Need to get rid of the entry for this key_id. This will mess up the // iterator, so we need to increment it first. diff --git a/media/cdm/aes_decryptor.h b/media/cdm/aes_decryptor.h index e2462ca..ab7c980 100644 --- a/media/cdm/aes_decryptor.h +++ b/media/cdm/aes_decryptor.h @@ -27,24 +27,23 @@ namespace media { // encryption must be CTR with a key size of 128bits. class MEDIA_EXPORT AesDecryptor : public MediaKeys, public Decryptor { public: - AesDecryptor(const SessionCreatedCB& session_created_cb, - const SessionMessageCB& session_message_cb, - const SessionReadyCB& session_ready_cb, - const SessionClosedCB& session_closed_cb, - const SessionErrorCB& session_error_cb); + explicit AesDecryptor(const SessionMessageCB& session_message_cb); virtual ~AesDecryptor(); // MediaKeys implementation. - virtual bool CreateSession(uint32 session_id, - const std::string& content_type, + virtual void CreateSession(const std::string& init_data_type, const uint8* init_data, - int init_data_length) OVERRIDE; - virtual void LoadSession(uint32 session_id, - const std::string& web_session_id) OVERRIDE; - virtual void UpdateSession(uint32 session_id, + int init_data_length, + SessionType session_type, + scoped_ptr<NewSessionCdmPromise> promise) OVERRIDE; + virtual void LoadSession(const std::string& web_session_id, + scoped_ptr<NewSessionCdmPromise> promise) OVERRIDE; + virtual void UpdateSession(const std::string& web_session_id, const uint8* response, - int response_length) OVERRIDE; - virtual void ReleaseSession(uint32 session_id) OVERRIDE; + int response_length, + scoped_ptr<SimpleCdmPromise> promise) OVERRIDE; + virtual void ReleaseSession(const std::string& web_session_id, + scoped_ptr<SimpleCdmPromise> promise) OVERRIDE; virtual Decryptor* GetDecryptor() OVERRIDE; // Decryptor implementation. @@ -103,7 +102,7 @@ class MEDIA_EXPORT AesDecryptor : public MediaKeys, public Decryptor { // Creates a DecryptionKey using |key_string| and associates it with |key_id|. // Returns true if successful. - bool AddDecryptionKey(const uint32 session_id, + bool AddDecryptionKey(const std::string& web_session_id, const std::string& key_id, const std::string& key_string); @@ -111,15 +110,11 @@ class MEDIA_EXPORT AesDecryptor : public MediaKeys, public Decryptor { // the key. Returns NULL if no key is associated with |key_id|. DecryptionKey* GetKey(const std::string& key_id) const; - // Deletes all keys associated with |session_id|. - void DeleteKeysForSession(const uint32 session_id); + // Deletes all keys associated with |web_session_id|. + void DeleteKeysForSession(const std::string& web_session_id); // Callbacks for firing session events. - SessionCreatedCB session_created_cb_; SessionMessageCB session_message_cb_; - SessionReadyCB session_ready_cb_; - SessionClosedCB session_closed_cb_; - SessionErrorCB session_error_cb_; // Since only Decrypt() is called off the renderer thread, we only need to // protect |key_map_|, the only member variable that is shared between @@ -127,8 +122,8 @@ class MEDIA_EXPORT AesDecryptor : public MediaKeys, public Decryptor { KeyIdToSessionKeysMap key_map_; // Protected by |key_map_lock_|. mutable base::Lock key_map_lock_; // Protects the |key_map_|. - // Keeps track of current valid session IDs. - std::set<uint32> valid_sessions_; + // Keeps track of current valid sessions. + std::set<std::string> valid_sessions_; // Make web session ID unique per renderer by making it static. Web session // IDs seen by the app will be "1", "2", etc. diff --git a/media/cdm/aes_decryptor_unittest.cc b/media/cdm/aes_decryptor_unittest.cc index 4ab4fec..8452527 100644 --- a/media/cdm/aes_decryptor_unittest.cc +++ b/media/cdm/aes_decryptor_unittest.cc @@ -7,6 +7,7 @@ #include "base/basictypes.h" #include "base/bind.h" +#include "media/base/cdm_promise.h" #include "media/base/decoder_buffer.h" #include "media/base/decrypt_config.h" #include "media/base/mock_filters.h" @@ -22,6 +23,7 @@ using ::testing::SaveArg; using ::testing::StrNe; MATCHER(IsEmpty, "") { return arg.empty(); } +MATCHER(IsNotEmpty, "") { return !arg.empty(); } class GURL; @@ -193,18 +195,12 @@ static scoped_refptr<DecoderBuffer> CreateEncryptedBuffer( return encrypted_buffer; } +enum PromiseResult { RESOLVED, REJECTED }; + class AesDecryptorTest : public testing::Test { public: AesDecryptorTest() - : decryptor_(base::Bind(&AesDecryptorTest::OnSessionCreated, - base::Unretained(this)), - base::Bind(&AesDecryptorTest::OnSessionMessage, - base::Unretained(this)), - base::Bind(&AesDecryptorTest::OnSessionReady, - base::Unretained(this)), - base::Bind(&AesDecryptorTest::OnSessionClosed, - base::Unretained(this)), - base::Bind(&AesDecryptorTest::OnSessionError, + : decryptor_(base::Bind(&AesDecryptorTest::OnSessionMessage, base::Unretained(this))), decrypt_cb_(base::Bind(&AesDecryptorTest::BufferDecrypted, base::Unretained(this))), @@ -218,52 +214,79 @@ class AesDecryptorTest : public testing::Test { iv_(kIv, kIv + arraysize(kIv)), normal_subsample_entries_( kSubsampleEntriesNormal, - kSubsampleEntriesNormal + arraysize(kSubsampleEntriesNormal)), - next_session_id_(1) { + kSubsampleEntriesNormal + arraysize(kSubsampleEntriesNormal)) { } protected: + void OnResolveWithSession(PromiseResult expected, + const std::string& web_session_id) { + EXPECT_EQ(expected, RESOLVED); + EXPECT_GT(web_session_id.length(), 0ul); + web_session_id_ = web_session_id; + } + + void OnResolve(PromiseResult expected) { + EXPECT_EQ(expected, RESOLVED); + } + + void OnReject(PromiseResult expected, + MediaKeys::Exception exception_code, + uint32 system_code, + const std::string& error_message) { + EXPECT_EQ(expected, REJECTED); + } + + scoped_ptr<SimpleCdmPromise> CreatePromise(PromiseResult expected) { + scoped_ptr<SimpleCdmPromise> promise(new SimpleCdmPromise( + base::Bind( + &AesDecryptorTest::OnResolve, base::Unretained(this), expected), + base::Bind( + &AesDecryptorTest::OnReject, base::Unretained(this), expected))); + return promise.Pass(); + } + + scoped_ptr<NewSessionCdmPromise> CreateSessionPromise( + PromiseResult expected) { + scoped_ptr<NewSessionCdmPromise> promise(new NewSessionCdmPromise( + base::Bind(&AesDecryptorTest::OnResolveWithSession, + base::Unretained(this), + expected), + base::Bind( + &AesDecryptorTest::OnReject, base::Unretained(this), expected))); + return promise.Pass(); + } + // Creates a new session using |key_id|. Returns the session ID. - uint32 CreateSession(const std::vector<uint8>& key_id) { + std::string CreateSession(const std::vector<uint8>& key_id) { DCHECK(!key_id.empty()); - uint32 session_id = next_session_id_++; - EXPECT_CALL(*this, OnSessionCreated(session_id, StrNe(std::string()))); - EXPECT_CALL(*this, OnSessionMessage(session_id, key_id, GURL::EmptyGURL())); - EXPECT_TRUE(decryptor_.CreateSession( - session_id, std::string(), &key_id[0], key_id.size())); - return session_id; + EXPECT_CALL(*this, + OnSessionMessage(IsNotEmpty(), key_id, GURL::EmptyGURL())); + decryptor_.CreateSession(std::string(), + &key_id[0], + key_id.size(), + MediaKeys::TEMPORARY_SESSION, + CreateSessionPromise(RESOLVED)); + // This expects the promise to be called synchronously, which is the case + // for AesDecryptor. + return web_session_id_; } // Releases the session specified by |session_id|. - void ReleaseSession(uint32 session_id) { - EXPECT_CALL(*this, OnSessionClosed(session_id)); - decryptor_.ReleaseSession(session_id); + void ReleaseSession(const std::string& session_id) { + decryptor_.ReleaseSession(session_id, CreatePromise(RESOLVED)); } - enum UpdateSessionExpectation { - SESSION_READY, - SESSION_ERROR - }; - // Updates the session specified by |session_id| with |key|. |result| // tests that the update succeeds or generates an error. - void UpdateSessionAndExpect(uint32 session_id, + void UpdateSessionAndExpect(std::string session_id, const std::string& key, - UpdateSessionExpectation result) { + PromiseResult result) { DCHECK(!key.empty()); - switch (result) { - case SESSION_READY: - EXPECT_CALL(*this, OnSessionReady(session_id)); - break; - case SESSION_ERROR: - EXPECT_CALL(*this, - OnSessionError(session_id, MediaKeys::kUnknownError, 0)); - break; - } - - decryptor_.UpdateSession( - session_id, reinterpret_cast<const uint8*>(key.c_str()), key.length()); + decryptor_.UpdateSession(session_id, + reinterpret_cast<const uint8*>(key.c_str()), + key.length(), + CreatePromise(result)); } MOCK_METHOD2(BufferDecrypted, void(Decryptor::Status, @@ -325,21 +348,14 @@ class AesDecryptorTest : public testing::Test { } } - MOCK_METHOD2(OnSessionCreated, - void(uint32 session_id, const std::string& web_session_id)); MOCK_METHOD3(OnSessionMessage, - void(uint32 session_id, + void(const std::string& web_session_id, const std::vector<uint8>& message, const GURL& destination_url)); - MOCK_METHOD1(OnSessionReady, void(uint32 session_id)); - MOCK_METHOD1(OnSessionClosed, void(uint32 session_id)); - MOCK_METHOD3(OnSessionError, - void(uint32 session_id, - MediaKeys::KeyError, - uint32 system_code)); AesDecryptor decryptor_; AesDecryptor::DecryptCB decrypt_cb_; + std::string web_session_id_; // Constants for testing. const std::vector<uint8> original_data_; @@ -349,42 +365,47 @@ class AesDecryptorTest : public testing::Test { const std::vector<uint8> iv_; const std::vector<SubsampleEntry> normal_subsample_entries_; const std::vector<SubsampleEntry> no_subsample_entries_; - - // Generate new session ID every time - uint32 next_session_id_; }; TEST_F(AesDecryptorTest, CreateSessionWithNullInitData) { - uint32 session_id = 8; EXPECT_CALL(*this, - OnSessionMessage(session_id, IsEmpty(), GURL::EmptyGURL())); - EXPECT_CALL(*this, OnSessionCreated(session_id, StrNe(std::string()))); - EXPECT_TRUE(decryptor_.CreateSession(session_id, std::string(), NULL, 0)); + OnSessionMessage(IsNotEmpty(), IsEmpty(), GURL::EmptyGURL())); + decryptor_.CreateSession(std::string(), + NULL, + 0, + MediaKeys::TEMPORARY_SESSION, + CreateSessionPromise(RESOLVED)); } TEST_F(AesDecryptorTest, MultipleCreateSession) { - uint32 session_id1 = 10; EXPECT_CALL(*this, - OnSessionMessage(session_id1, IsEmpty(), GURL::EmptyGURL())); - EXPECT_CALL(*this, OnSessionCreated(session_id1, StrNe(std::string()))); - EXPECT_TRUE(decryptor_.CreateSession(session_id1, std::string(), NULL, 0)); + OnSessionMessage(IsNotEmpty(), IsEmpty(), GURL::EmptyGURL())); + decryptor_.CreateSession(std::string(), + NULL, + 0, + MediaKeys::TEMPORARY_SESSION, + CreateSessionPromise(RESOLVED)); - uint32 session_id2 = 11; EXPECT_CALL(*this, - OnSessionMessage(session_id2, IsEmpty(), GURL::EmptyGURL())); - EXPECT_CALL(*this, OnSessionCreated(session_id2, StrNe(std::string()))); - EXPECT_TRUE(decryptor_.CreateSession(session_id2, std::string(), NULL, 0)); + OnSessionMessage(IsNotEmpty(), IsEmpty(), GURL::EmptyGURL())); + decryptor_.CreateSession(std::string(), + NULL, + 0, + MediaKeys::TEMPORARY_SESSION, + CreateSessionPromise(RESOLVED)); - uint32 session_id3 = 23; EXPECT_CALL(*this, - OnSessionMessage(session_id3, IsEmpty(), GURL::EmptyGURL())); - EXPECT_CALL(*this, OnSessionCreated(session_id3, StrNe(std::string()))); - EXPECT_TRUE(decryptor_.CreateSession(session_id3, std::string(), NULL, 0)); + OnSessionMessage(IsNotEmpty(), IsEmpty(), GURL::EmptyGURL())); + decryptor_.CreateSession(std::string(), + NULL, + 0, + MediaKeys::TEMPORARY_SESSION, + CreateSessionPromise(RESOLVED)); } TEST_F(AesDecryptorTest, NormalDecryption) { - uint32 session_id = CreateSession(key_id_); - UpdateSessionAndExpect(session_id, kKeyAsJWK, SESSION_READY); + std::string session_id = CreateSession(key_id_); + UpdateSessionAndExpect(session_id, kKeyAsJWK, RESOLVED); scoped_refptr<DecoderBuffer> encrypted_buffer = CreateEncryptedBuffer( encrypted_data_, key_id_, iv_, no_subsample_entries_); DecryptAndExpect(encrypted_buffer, original_data_, SUCCESS); @@ -398,8 +419,8 @@ TEST_F(AesDecryptorTest, UnencryptedFrame) { } TEST_F(AesDecryptorTest, WrongKey) { - uint32 session_id = CreateSession(key_id_); - UpdateSessionAndExpect(session_id, kWrongKeyAsJWK, SESSION_READY); + std::string session_id = CreateSession(key_id_); + UpdateSessionAndExpect(session_id, kWrongKeyAsJWK, RESOLVED); scoped_refptr<DecoderBuffer> encrypted_buffer = CreateEncryptedBuffer( encrypted_data_, key_id_, iv_, no_subsample_entries_); DecryptAndExpect(encrypted_buffer, original_data_, DATA_MISMATCH); @@ -413,33 +434,33 @@ TEST_F(AesDecryptorTest, NoKey) { } TEST_F(AesDecryptorTest, KeyReplacement) { - uint32 session_id = CreateSession(key_id_); + std::string session_id = CreateSession(key_id_); scoped_refptr<DecoderBuffer> encrypted_buffer = CreateEncryptedBuffer( encrypted_data_, key_id_, iv_, no_subsample_entries_); - UpdateSessionAndExpect(session_id, kWrongKeyAsJWK, SESSION_READY); + UpdateSessionAndExpect(session_id, kWrongKeyAsJWK, RESOLVED); ASSERT_NO_FATAL_FAILURE(DecryptAndExpect( encrypted_buffer, original_data_, DATA_MISMATCH)); - UpdateSessionAndExpect(session_id, kKeyAsJWK, SESSION_READY); + UpdateSessionAndExpect(session_id, kKeyAsJWK, RESOLVED); ASSERT_NO_FATAL_FAILURE( DecryptAndExpect(encrypted_buffer, original_data_, SUCCESS)); } TEST_F(AesDecryptorTest, WrongSizedKey) { - uint32 session_id = CreateSession(key_id_); - UpdateSessionAndExpect(session_id, kWrongSizedKeyAsJWK, SESSION_ERROR); + std::string session_id = CreateSession(key_id_); + UpdateSessionAndExpect(session_id, kWrongSizedKeyAsJWK, REJECTED); } TEST_F(AesDecryptorTest, MultipleKeysAndFrames) { - uint32 session_id = CreateSession(key_id_); - UpdateSessionAndExpect(session_id, kKeyAsJWK, SESSION_READY); + std::string session_id = CreateSession(key_id_); + UpdateSessionAndExpect(session_id, kKeyAsJWK, RESOLVED); scoped_refptr<DecoderBuffer> encrypted_buffer = CreateEncryptedBuffer( encrypted_data_, key_id_, iv_, no_subsample_entries_); ASSERT_NO_FATAL_FAILURE( DecryptAndExpect(encrypted_buffer, original_data_, SUCCESS)); - UpdateSessionAndExpect(session_id, kKey2AsJWK, SESSION_READY); + UpdateSessionAndExpect(session_id, kKey2AsJWK, RESOLVED); // The first key is still available after we added a second key. ASSERT_NO_FATAL_FAILURE( @@ -460,8 +481,8 @@ TEST_F(AesDecryptorTest, MultipleKeysAndFrames) { } TEST_F(AesDecryptorTest, CorruptedIv) { - uint32 session_id = CreateSession(key_id_); - UpdateSessionAndExpect(session_id, kKeyAsJWK, SESSION_READY); + std::string session_id = CreateSession(key_id_); + UpdateSessionAndExpect(session_id, kKeyAsJWK, RESOLVED); std::vector<uint8> bad_iv = iv_; bad_iv[1]++; @@ -473,8 +494,8 @@ TEST_F(AesDecryptorTest, CorruptedIv) { } TEST_F(AesDecryptorTest, CorruptedData) { - uint32 session_id = CreateSession(key_id_); - UpdateSessionAndExpect(session_id, kKeyAsJWK, SESSION_READY); + std::string session_id = CreateSession(key_id_); + UpdateSessionAndExpect(session_id, kKeyAsJWK, RESOLVED); std::vector<uint8> bad_data = encrypted_data_; bad_data[1]++; @@ -485,16 +506,16 @@ TEST_F(AesDecryptorTest, CorruptedData) { } TEST_F(AesDecryptorTest, EncryptedAsUnencryptedFailure) { - uint32 session_id = CreateSession(key_id_); - UpdateSessionAndExpect(session_id, kKeyAsJWK, SESSION_READY); + std::string session_id = CreateSession(key_id_); + UpdateSessionAndExpect(session_id, kKeyAsJWK, RESOLVED); scoped_refptr<DecoderBuffer> encrypted_buffer = CreateEncryptedBuffer( encrypted_data_, key_id_, std::vector<uint8>(), no_subsample_entries_); DecryptAndExpect(encrypted_buffer, original_data_, DATA_MISMATCH); } TEST_F(AesDecryptorTest, SubsampleDecryption) { - uint32 session_id = CreateSession(key_id_); - UpdateSessionAndExpect(session_id, kKeyAsJWK, SESSION_READY); + std::string session_id = CreateSession(key_id_); + UpdateSessionAndExpect(session_id, kKeyAsJWK, RESOLVED); scoped_refptr<DecoderBuffer> encrypted_buffer = CreateEncryptedBuffer( subsample_encrypted_data_, key_id_, iv_, normal_subsample_entries_); DecryptAndExpect(encrypted_buffer, original_data_, SUCCESS); @@ -504,16 +525,16 @@ TEST_F(AesDecryptorTest, SubsampleDecryption) { // expect to encounter this in the wild, but since the DecryptConfig doesn't // disallow such a configuration, it should be covered. TEST_F(AesDecryptorTest, SubsampleDecryptionWithOffset) { - uint32 session_id = CreateSession(key_id_); - UpdateSessionAndExpect(session_id, kKeyAsJWK, SESSION_READY); + std::string session_id = CreateSession(key_id_); + UpdateSessionAndExpect(session_id, kKeyAsJWK, RESOLVED); scoped_refptr<DecoderBuffer> encrypted_buffer = CreateEncryptedBuffer( subsample_encrypted_data_, key_id_, iv_, normal_subsample_entries_); DecryptAndExpect(encrypted_buffer, original_data_, SUCCESS); } TEST_F(AesDecryptorTest, SubsampleWrongSize) { - uint32 session_id = CreateSession(key_id_); - UpdateSessionAndExpect(session_id, kKeyAsJWK, SESSION_READY); + std::string session_id = CreateSession(key_id_); + UpdateSessionAndExpect(session_id, kKeyAsJWK, RESOLVED); std::vector<SubsampleEntry> subsample_entries_wrong_size( kSubsampleEntriesWrongSize, @@ -525,8 +546,8 @@ TEST_F(AesDecryptorTest, SubsampleWrongSize) { } TEST_F(AesDecryptorTest, SubsampleInvalidTotalSize) { - uint32 session_id = CreateSession(key_id_); - UpdateSessionAndExpect(session_id, kKeyAsJWK, SESSION_READY); + std::string session_id = CreateSession(key_id_); + UpdateSessionAndExpect(session_id, kKeyAsJWK, RESOLVED); std::vector<SubsampleEntry> subsample_entries_invalid_total_size( kSubsampleEntriesInvalidTotalSize, @@ -541,8 +562,8 @@ TEST_F(AesDecryptorTest, SubsampleInvalidTotalSize) { // No cypher bytes in any of the subsamples. TEST_F(AesDecryptorTest, SubsampleClearBytesOnly) { - uint32 session_id = CreateSession(key_id_); - UpdateSessionAndExpect(session_id, kKeyAsJWK, SESSION_READY); + std::string session_id = CreateSession(key_id_); + UpdateSessionAndExpect(session_id, kKeyAsJWK, RESOLVED); std::vector<SubsampleEntry> clear_only_subsample_entries( kSubsampleEntriesClearOnly, @@ -555,8 +576,8 @@ TEST_F(AesDecryptorTest, SubsampleClearBytesOnly) { // No clear bytes in any of the subsamples. TEST_F(AesDecryptorTest, SubsampleCypherBytesOnly) { - uint32 session_id = CreateSession(key_id_); - UpdateSessionAndExpect(session_id, kKeyAsJWK, SESSION_READY); + std::string session_id = CreateSession(key_id_); + UpdateSessionAndExpect(session_id, kKeyAsJWK, RESOLVED); std::vector<SubsampleEntry> cypher_only_subsample_entries( kSubsampleEntriesCypherOnly, @@ -568,11 +589,11 @@ TEST_F(AesDecryptorTest, SubsampleCypherBytesOnly) { } TEST_F(AesDecryptorTest, ReleaseSession) { - uint32 session_id = CreateSession(key_id_); + std::string session_id = CreateSession(key_id_); scoped_refptr<DecoderBuffer> encrypted_buffer = CreateEncryptedBuffer( encrypted_data_, key_id_, iv_, no_subsample_entries_); - UpdateSessionAndExpect(session_id, kKeyAsJWK, SESSION_READY); + UpdateSessionAndExpect(session_id, kKeyAsJWK, RESOLVED); ASSERT_NO_FATAL_FAILURE( DecryptAndExpect(encrypted_buffer, original_data_, SUCCESS)); @@ -580,11 +601,11 @@ TEST_F(AesDecryptorTest, ReleaseSession) { } TEST_F(AesDecryptorTest, NoKeyAfterReleaseSession) { - uint32 session_id = CreateSession(key_id_); + std::string session_id = CreateSession(key_id_); scoped_refptr<DecoderBuffer> encrypted_buffer = CreateEncryptedBuffer( encrypted_data_, key_id_, iv_, no_subsample_entries_); - UpdateSessionAndExpect(session_id, kKeyAsJWK, SESSION_READY); + UpdateSessionAndExpect(session_id, kKeyAsJWK, RESOLVED); ASSERT_NO_FATAL_FAILURE( DecryptAndExpect(encrypted_buffer, original_data_, SUCCESS)); @@ -594,18 +615,18 @@ TEST_F(AesDecryptorTest, NoKeyAfterReleaseSession) { } TEST_F(AesDecryptorTest, LatestKeyUsed) { - uint32 session_id1 = CreateSession(key_id_); + std::string session_id1 = CreateSession(key_id_); scoped_refptr<DecoderBuffer> encrypted_buffer = CreateEncryptedBuffer( encrypted_data_, key_id_, iv_, no_subsample_entries_); // Add alternate key, buffer should not be decoded properly. - UpdateSessionAndExpect(session_id1, kKeyAlternateAsJWK, SESSION_READY); + UpdateSessionAndExpect(session_id1, kKeyAlternateAsJWK, RESOLVED); ASSERT_NO_FATAL_FAILURE( DecryptAndExpect(encrypted_buffer, original_data_, DATA_MISMATCH)); // Create a second session with a correct key value for key_id_. - uint32 session_id2 = CreateSession(key_id_); - UpdateSessionAndExpect(session_id2, kKeyAsJWK, SESSION_READY); + std::string session_id2 = CreateSession(key_id_); + UpdateSessionAndExpect(session_id2, kKeyAsJWK, RESOLVED); // Should be able to decode with latest key. ASSERT_NO_FATAL_FAILURE( @@ -613,16 +634,16 @@ TEST_F(AesDecryptorTest, LatestKeyUsed) { } TEST_F(AesDecryptorTest, LatestKeyUsedAfterReleaseSession) { - uint32 session_id1 = CreateSession(key_id_); + std::string session_id1 = CreateSession(key_id_); scoped_refptr<DecoderBuffer> encrypted_buffer = CreateEncryptedBuffer( encrypted_data_, key_id_, iv_, no_subsample_entries_); - UpdateSessionAndExpect(session_id1, kKeyAsJWK, SESSION_READY); + UpdateSessionAndExpect(session_id1, kKeyAsJWK, RESOLVED); ASSERT_NO_FATAL_FAILURE( DecryptAndExpect(encrypted_buffer, original_data_, SUCCESS)); // Create a second session with a different key value for key_id_. - uint32 session_id2 = CreateSession(key_id_); - UpdateSessionAndExpect(session_id2, kKeyAlternateAsJWK, SESSION_READY); + std::string session_id2 = CreateSession(key_id_); + UpdateSessionAndExpect(session_id2, kKeyAlternateAsJWK, RESOLVED); // Should not be able to decode with new key. ASSERT_NO_FATAL_FAILURE( @@ -635,7 +656,7 @@ TEST_F(AesDecryptorTest, LatestKeyUsedAfterReleaseSession) { } TEST_F(AesDecryptorTest, JWKKey) { - uint32 session_id = CreateSession(key_id_); + std::string session_id = CreateSession(key_id_); // Try a simple JWK key (i.e. not in a set) const std::string kJwkSimple = @@ -644,7 +665,7 @@ TEST_F(AesDecryptorTest, JWKKey) { " \"kid\": \"AAECAwQFBgcICQoLDA0ODxAREhM\"," " \"k\": \"FBUWFxgZGhscHR4fICEiIw\"" "}"; - UpdateSessionAndExpect(session_id, kJwkSimple, SESSION_ERROR); + UpdateSessionAndExpect(session_id, kJwkSimple, REJECTED); // Try a key list with multiple entries. const std::string kJwksMultipleEntries = @@ -662,40 +683,38 @@ TEST_F(AesDecryptorTest, JWKKey) { " }" " ]" "}"; - UpdateSessionAndExpect(session_id, kJwksMultipleEntries, SESSION_READY); + UpdateSessionAndExpect(session_id, kJwksMultipleEntries, RESOLVED); // Try a key with no spaces and some \n plus additional fields. const std::string kJwksNoSpaces = "\n\n{\"something\":1,\"keys\":[{\n\n\"kty\":\"oct\",\"alg\":\"A128KW\"," "\"kid\":\"AAECAwQFBgcICQoLDA0ODxAREhM\",\"k\":\"GawgguFyGrWKav7AX4VKUg" "\",\"foo\":\"bar\"}]}\n\n"; - UpdateSessionAndExpect(session_id, kJwksNoSpaces, SESSION_READY); + UpdateSessionAndExpect(session_id, kJwksNoSpaces, RESOLVED); // Try some non-ASCII characters. - UpdateSessionAndExpect(session_id, - "This is not ASCII due to \xff\xfe\xfd in it.", - SESSION_ERROR); + UpdateSessionAndExpect( + session_id, "This is not ASCII due to \xff\xfe\xfd in it.", REJECTED); // Try a badly formatted key. Assume that the JSON parser is fully tested, // so we won't try a lot of combinations. However, need a test to ensure // that the code doesn't crash if invalid JSON received. - UpdateSessionAndExpect(session_id, "This is not a JSON key.", SESSION_ERROR); + UpdateSessionAndExpect(session_id, "This is not a JSON key.", REJECTED); // Try passing some valid JSON that is not a dictionary at the top level. - UpdateSessionAndExpect(session_id, "40", SESSION_ERROR); + UpdateSessionAndExpect(session_id, "40", REJECTED); // Try an empty dictionary. - UpdateSessionAndExpect(session_id, "{ }", SESSION_ERROR); + UpdateSessionAndExpect(session_id, "{ }", REJECTED); // Try an empty 'keys' dictionary. - UpdateSessionAndExpect(session_id, "{ \"keys\": [] }", SESSION_ERROR); + UpdateSessionAndExpect(session_id, "{ \"keys\": [] }", REJECTED); // Try with 'keys' not a dictionary. - UpdateSessionAndExpect(session_id, "{ \"keys\":\"1\" }", SESSION_ERROR); + UpdateSessionAndExpect(session_id, "{ \"keys\":\"1\" }", REJECTED); // Try with 'keys' a list of integers. - UpdateSessionAndExpect( - session_id, "{ \"keys\": [ 1, 2, 3 ] }", SESSION_ERROR); + UpdateSessionAndExpect(session_id, "{ \"keys\": [ 1, 2, 3 ] }", REJECTED); // Try padding(=) at end of 'k' base64 string. const std::string kJwksWithPaddedKey = @@ -708,7 +727,7 @@ TEST_F(AesDecryptorTest, JWKKey) { " }" " ]" "}"; - UpdateSessionAndExpect(session_id, kJwksWithPaddedKey, SESSION_ERROR); + UpdateSessionAndExpect(session_id, kJwksWithPaddedKey, REJECTED); // Try padding(=) at end of 'kid' base64 string. const std::string kJwksWithPaddedKeyId = @@ -721,7 +740,7 @@ TEST_F(AesDecryptorTest, JWKKey) { " }" " ]" "}"; - UpdateSessionAndExpect(session_id, kJwksWithPaddedKeyId, SESSION_ERROR); + UpdateSessionAndExpect(session_id, kJwksWithPaddedKeyId, REJECTED); // Try a key with invalid base64 encoding. const std::string kJwksWithInvalidBase64 = @@ -734,7 +753,7 @@ TEST_F(AesDecryptorTest, JWKKey) { " }" " ]" "}"; - UpdateSessionAndExpect(session_id, kJwksWithInvalidBase64, SESSION_ERROR); + UpdateSessionAndExpect(session_id, kJwksWithInvalidBase64, REJECTED); // Try a 3-byte 'kid' where no base64 padding is required. // |kJwksMultipleEntries| above has 2 'kid's that require 1 and 2 padding @@ -749,7 +768,7 @@ TEST_F(AesDecryptorTest, JWKKey) { " }" " ]" "}"; - UpdateSessionAndExpect(session_id, kJwksWithNoPadding, SESSION_READY); + UpdateSessionAndExpect(session_id, kJwksWithNoPadding, RESOLVED); // Empty key id. const std::string kJwksWithEmptyKeyId = @@ -762,7 +781,7 @@ TEST_F(AesDecryptorTest, JWKKey) { " }" " ]" "}"; - UpdateSessionAndExpect(session_id, kJwksWithEmptyKeyId, SESSION_ERROR); + UpdateSessionAndExpect(session_id, kJwksWithEmptyKeyId, REJECTED); ReleaseSession(session_id); } diff --git a/media/cdm/ppapi/cdm_adapter.cc b/media/cdm/ppapi/cdm_adapter.cc index 3c4d698..10feef7 100644 --- a/media/cdm/ppapi/cdm_adapter.cc +++ b/media/cdm/ppapi/cdm_adapter.cc @@ -211,6 +211,40 @@ cdm::StreamType PpDecryptorStreamTypeToCdmStreamType( return cdm::kStreamTypeVideo; } +cdm::SessionType PpSessionTypeToCdmSessionType(PP_SessionType session_type) { + switch (session_type) { + case PP_SESSIONTYPE_TEMPORARY: + return cdm::kTemporary; + case PP_SESSIONTYPE_PERSISTENT: + return cdm::kPersistent; + default: + PP_NOTREACHED(); + return cdm::kTemporary; + } +} + +PP_CdmExceptionCode CdmExceptionTypeToPpCdmExceptionType(cdm::Error error) { + switch (error) { + case cdm::kNotSupportedError: + return PP_CDMEXCEPTIONCODE_NOTSUPPORTEDERROR; + case cdm::kInvalidStateError: + return PP_CDMEXCEPTIONCODE_INVALIDSTATEERROR; + case cdm::kInvalidAccessError: + return PP_CDMEXCEPTIONCODE_INVALIDACCESSERROR; + case cdm::kQuotaExceededError: + return PP_CDMEXCEPTIONCODE_QUOTAEXCEEDEDERROR; + case cdm::kUnknownError: + return PP_CDMEXCEPTIONCODE_UNKNOWNERROR; + case cdm::kClientError: + return PP_CDMEXCEPTIONCODE_CLIENTERROR; + case cdm::kOutputError: + return PP_CDMEXCEPTIONCODE_OUTPUTERROR; + default: + PP_NOTREACHED(); + return PP_CDMEXCEPTIONCODE_UNKNOWNERROR; + } +} + } // namespace namespace media { @@ -290,49 +324,70 @@ void CdmAdapter::Initialize(const std::string& key_system) { key_system_ = key_system; } -void CdmAdapter::CreateSession(uint32_t session_id, - const std::string& content_type, - pp::VarArrayBuffer init_data) { +void CdmAdapter::CreateSession(uint32_t promise_id, + const std::string& init_data_type, + pp::VarArrayBuffer init_data, + PP_SessionType session_type) { // Initialize() doesn't report an error, so CreateSession() can be called // even if Initialize() failed. + // TODO(jrummell): Remove this code when prefixed EME gets removed. + // TODO(jrummell): Verify that Initialize() failing does not resolve the + // MediaKeys.create() promise. if (!cdm_) { - OnSessionError(session_id, cdm::kUnknownError, 0); + RejectPromise(promise_id, + cdm::kInvalidStateError, + 0, + "CDM has not been initialized."); return; } - cdm_->CreateSession(session_id, - content_type.data(), - content_type.size(), + cdm_->CreateSession(promise_id, + init_data_type.data(), + init_data_type.size(), static_cast<const uint8_t*>(init_data.Map()), - init_data.ByteLength()); + init_data.ByteLength(), + PpSessionTypeToCdmSessionType(session_type)); } -void CdmAdapter::LoadSession(uint32_t session_id, +void CdmAdapter::LoadSession(uint32_t promise_id, const std::string& web_session_id) { // Initialize() doesn't report an error, so LoadSession() can be called // even if Initialize() failed. + // TODO(jrummell): Remove this code when prefixed EME gets removed. + // TODO(jrummell): Verify that Initialize() failing does not resolve the + // MediaKeys.create() promise. if (!cdm_) { - OnSessionError(session_id, cdm::kUnknownError, 0); + RejectPromise(promise_id, + cdm::kInvalidStateError, + 0, + "CDM has not been initialized."); return; } - cdm_->LoadSession(session_id, web_session_id.data(), web_session_id.size()); + cdm_->LoadSession(promise_id, web_session_id.data(), web_session_id.size()); } -void CdmAdapter::UpdateSession(uint32_t session_id, +void CdmAdapter::UpdateSession(uint32_t promise_id, + const std::string& web_session_id, pp::VarArrayBuffer response) { const uint8_t* response_ptr = static_cast<const uint8_t*>(response.Map()); const uint32_t response_size = response.ByteLength(); - if (!response_ptr || response_size <= 0) { - OnSessionError(session_id, cdm::kUnknownError, 0); - return; - } - cdm_->UpdateSession(session_id, response_ptr, response_size); + PP_DCHECK(!web_session_id.empty()); + PP_DCHECK(response_ptr); + PP_DCHECK(response_size > 0); + + cdm_->UpdateSession(promise_id, + web_session_id.data(), + web_session_id.length(), + response_ptr, + response_size); } -void CdmAdapter::ReleaseSession(uint32_t session_id) { - cdm_->ReleaseSession(session_id); +void CdmAdapter::ReleaseSession(uint32_t promise_id, + const std::string& web_session_id) { + cdm_->ReleaseSession( + promise_id, web_session_id.data(), web_session_id.length()); } // Note: In the following decryption/decoding related functions, errors are NOT @@ -529,61 +584,219 @@ void CdmAdapter::TimerExpired(int32_t result, void* context) { cdm_->TimerExpired(context); } +// cdm::Host_4 methods + double CdmAdapter::GetCurrentWallTimeInSeconds() { - return pp::Module::Get()->core()->GetTime(); + return GetCurrentTime(); } void CdmAdapter::OnSessionCreated(uint32_t session_id, const char* web_session_id, uint32_t web_session_id_length) { + uint32_t promise_id = cdm_->LookupPromiseId(session_id); + cdm_->AssignWebSessionId(session_id, web_session_id, web_session_id_length); + OnResolveNewSessionPromise(promise_id, web_session_id, web_session_id_length); +} + +void CdmAdapter::OnSessionMessage(uint32_t session_id, + const char* message, + uint32_t message_length, + const char* destination_url, + uint32_t destination_url_length) { + std::string web_session_id = cdm_->LookupWebSessionId(session_id); + OnSessionMessage(web_session_id.data(), + web_session_id.length(), + message, + message_length, + destination_url, + destination_url_length); +} + +void CdmAdapter::OnSessionReady(uint32_t session_id) { + uint32_t promise_id = cdm_->LookupPromiseId(session_id); + if (promise_id) { + OnResolvePromise(promise_id); + } else { + std::string web_session_id = cdm_->LookupWebSessionId(session_id); + OnSessionReady(web_session_id.data(), web_session_id.length()); + } +} + +void CdmAdapter::OnSessionClosed(uint32_t session_id) { + uint32_t promise_id = cdm_->LookupPromiseId(session_id); + std::string web_session_id = cdm_->LookupWebSessionId(session_id); + cdm_->DropWebSessionId(web_session_id); + if (promise_id) { + OnResolvePromise(promise_id); + } else { + OnSessionClosed(web_session_id.data(), web_session_id.length()); + } +} + +void CdmAdapter::OnSessionError(uint32_t session_id, + cdm::MediaKeyError error_code, + uint32_t system_code) { + uint32_t promise_id = cdm_->LookupPromiseId(session_id); + + // Existing cdm::MediaKeyError don't map to DOM error names. Convert them + // into non-standard names so that the prefixed API can extract them. + // TODO(jrummell): Remove this conversion and the inverse when CDM4 is gone. + cdm::Error error; + switch (error_code) { + case cdm::kPrefixedClientError: + error = cdm::kClientError; + break; + case cdm::kPrefixedOutputError: + error = cdm::kOutputError; + break; + case cdm::kPrefixedUnknownError: + default: + error = cdm::kUnknownError; + break; + } + + if (promise_id) { + RejectPromise(promise_id, error, system_code, std::string()); + } else { + std::string web_session_id = cdm_->LookupWebSessionId(session_id); + OnSessionError(web_session_id.data(), + web_session_id.length(), + error, + system_code, + NULL, + 0); + } +} + +// cdm::Host_5 methods + +cdm::Time CdmAdapter::GetCurrentTime() { + return pp::Module::Get()->core()->GetTime(); +} + +void CdmAdapter::OnResolvePromise(uint32_t promise_id) { PostOnMain(callback_factory_.NewCallback( - &CdmAdapter::SendSessionCreatedInternal, - session_id, + &CdmAdapter::SendPromiseResolvedInternal, promise_id)); +} + +void CdmAdapter::OnResolveNewSessionPromise(uint32_t promise_id, + const char* web_session_id, + uint32_t web_session_id_length) { + PostOnMain(callback_factory_.NewCallback( + &CdmAdapter::SendPromiseResolvedWithSessionInternal, + promise_id, std::string(web_session_id, web_session_id_length))); } -void CdmAdapter::OnSessionMessage(uint32_t session_id, +void CdmAdapter::OnRejectPromise(uint32_t promise_id, + cdm::Error error, + uint32_t system_code, + const char* error_message, + uint32_t error_message_length) { + RejectPromise(promise_id, + error, + system_code, + std::string(error_message, error_message_length)); +} + +void CdmAdapter::RejectPromise(uint32_t promise_id, + cdm::Error error, + uint32_t system_code, + const std::string& error_message) { + PostOnMain(callback_factory_.NewCallback( + &CdmAdapter::SendPromiseRejectedInternal, + promise_id, + SessionError(error, system_code, error_message))); +} + +void CdmAdapter::OnSessionMessage(const char* web_session_id, + uint32_t web_session_id_length, const char* message, uint32_t message_length, const char* destination_url, uint32_t destination_url_length) { PostOnMain(callback_factory_.NewCallback( &CdmAdapter::SendSessionMessageInternal, - session_id, + std::string(web_session_id, web_session_id_length), std::vector<uint8>(message, message + message_length), std::string(destination_url, destination_url_length))); } -void CdmAdapter::OnSessionReady(uint32_t session_id) { +void CdmAdapter::OnSessionKeysChange(const char* web_session_id, + uint32_t web_session_id_length, + bool has_additional_usable_key) { + // TODO(jrummell): Implement this event in subsequent CL + // (http://crbug.com/370251). + PP_NOTREACHED(); +} + +void CdmAdapter::OnExpirationChange(const char* web_session_id, + uint32_t web_session_id_length, + cdm::Time new_expiry_time) { + // TODO(jrummell): Implement this event in subsequent CL + // (http://crbug.com/370251). + PP_NOTREACHED(); +} + +void CdmAdapter::OnSessionReady(const char* web_session_id, + uint32_t web_session_id_length) { PostOnMain(callback_factory_.NewCallback( - &CdmAdapter::SendSessionReadyInternal, session_id)); + &CdmAdapter::SendSessionReadyInternal, + std::string(web_session_id, web_session_id_length))); } -void CdmAdapter::OnSessionClosed(uint32_t session_id) { +void CdmAdapter::OnSessionClosed(const char* web_session_id, + uint32_t web_session_id_length) { PostOnMain(callback_factory_.NewCallback( - &CdmAdapter::SendSessionClosedInternal, session_id)); + &CdmAdapter::SendSessionClosedInternal, + std::string(web_session_id, web_session_id_length))); } -void CdmAdapter::OnSessionError(uint32_t session_id, - cdm::MediaKeyError error_code, - uint32_t system_code) { +void CdmAdapter::OnSessionError(const char* web_session_id, + uint32_t web_session_id_length, + cdm::Error error, + uint32_t system_code, + const char* error_message, + uint32_t error_message_length) { PostOnMain(callback_factory_.NewCallback( &CdmAdapter::SendSessionErrorInternal, - session_id, - error_code, - system_code)); + std::string(web_session_id, web_session_id_length), + SessionError(error, + system_code, + std::string(error_message, error_message_length)))); } -void CdmAdapter::SendSessionCreatedInternal(int32_t result, - uint32_t session_id, - const std::string& web_session_id) { +// Helpers to pass the event to Pepper. + +void CdmAdapter::SendPromiseResolvedInternal(int32_t result, + uint32_t promise_id) { PP_DCHECK(result == PP_OK); - pp::ContentDecryptor_Private::SessionCreated(session_id, web_session_id); + pp::ContentDecryptor_Private::PromiseResolved(promise_id); +} + +void CdmAdapter::SendPromiseResolvedWithSessionInternal( + int32_t result, + uint32_t promise_id, + const std::string& web_session_id) { + PP_DCHECK(result == PP_OK); + pp::ContentDecryptor_Private::PromiseResolvedWithSession(promise_id, + web_session_id); +} + +void CdmAdapter::SendPromiseRejectedInternal(int32_t result, + uint32_t promise_id, + const SessionError& error) { + PP_DCHECK(result == PP_OK); + pp::ContentDecryptor_Private::PromiseRejected( + promise_id, + CdmExceptionTypeToPpCdmExceptionType(error.error), + error.system_code, + error.error_description); } void CdmAdapter::SendSessionMessageInternal( int32_t result, - uint32_t session_id, + const std::string& web_session_id, const std::vector<uint8>& message, const std::string& destination_url) { PP_DCHECK(result == PP_OK); @@ -594,27 +807,30 @@ void CdmAdapter::SendSessionMessageInternal( } pp::ContentDecryptor_Private::SessionMessage( - session_id, message_array_buffer, destination_url); + web_session_id, message_array_buffer, destination_url); } -void CdmAdapter::SendSessionReadyInternal(int32_t result, uint32_t session_id) { +void CdmAdapter::SendSessionReadyInternal(int32_t result, + const std::string& web_session_id) { PP_DCHECK(result == PP_OK); - pp::ContentDecryptor_Private::SessionReady(session_id); + pp::ContentDecryptor_Private::SessionReady(web_session_id); } void CdmAdapter::SendSessionClosedInternal(int32_t result, - uint32_t session_id) { + const std::string& web_session_id) { PP_DCHECK(result == PP_OK); - pp::ContentDecryptor_Private::SessionClosed(session_id); + pp::ContentDecryptor_Private::SessionClosed(web_session_id); } void CdmAdapter::SendSessionErrorInternal(int32_t result, - uint32_t session_id, - cdm::MediaKeyError error_code, - uint32_t system_code) { + const std::string& web_session_id, + const SessionError& error) { PP_DCHECK(result == PP_OK); pp::ContentDecryptor_Private::SessionError( - session_id, error_code, system_code); + web_session_id, + CdmExceptionTypeToPpCdmExceptionType(error.error), + error.system_code, + error.error_description); } void CdmAdapter::DeliverBlock(int32_t result, @@ -943,15 +1159,12 @@ void CdmAdapter::SendPlatformChallengeDone(int32_t result) { platform_key_certificate_output_.AsString(); cdm::PlatformChallengeResponse response = { - static_cast<uint8_t*>(signed_data_var.Map()), - signed_data_var.ByteLength(), - - static_cast<uint8_t*>(signed_data_signature_var.Map()), - signed_data_signature_var.ByteLength(), - - reinterpret_cast<const uint8_t*>(platform_key_certificate_string.c_str()), - static_cast<uint32_t>(platform_key_certificate_string.length()) - }; + static_cast<uint8_t*>(signed_data_var.Map()), + signed_data_var.ByteLength(), + static_cast<uint8_t*>(signed_data_signature_var.Map()), + signed_data_signature_var.ByteLength(), + reinterpret_cast<const uint8_t*>(platform_key_certificate_string.data()), + static_cast<uint32_t>(platform_key_certificate_string.length())}; cdm_->OnPlatformChallengeResponse(response); signed_data_var.Unmap(); @@ -979,12 +1192,20 @@ void CdmAdapter::QueryOutputProtectionStatusDone(int32_t result) { } #endif +CdmAdapter::SessionError::SessionError(cdm::Error error, + uint32_t system_code, + std::string error_description) + : error(error), + system_code(system_code), + error_description(error_description) { +} + void* GetCdmHost(int host_interface_version, void* user_data) { if (!host_interface_version || !user_data) return NULL; COMPILE_ASSERT( - cdm::ContentDecryptionModule::Host::kVersion == cdm::Host_4::kVersion, + cdm::ContentDecryptionModule::Host::kVersion == cdm::Host_5::kVersion, update_code_below); // Ensure IsSupportedCdmHostVersion matches implementation of this function. @@ -994,11 +1215,11 @@ void* GetCdmHost(int host_interface_version, void* user_data) { PP_DCHECK( // Future version is not supported. - !IsSupportedCdmHostVersion(cdm::Host_4::kVersion + 1) && + !IsSupportedCdmHostVersion(cdm::Host_5::kVersion + 1) && // Current version is supported. - IsSupportedCdmHostVersion(cdm::Host_4::kVersion) && + IsSupportedCdmHostVersion(cdm::Host_5::kVersion) && // Include all previous supported versions (if any) here. - // No supported previous versions. + IsSupportedCdmHostVersion(cdm::Host_4::kVersion) && // One older than the oldest supported version is not supported. !IsSupportedCdmHostVersion(cdm::Host_4::kVersion - 1)); PP_DCHECK(IsSupportedCdmHostVersion(host_interface_version)); @@ -1008,6 +1229,8 @@ void* GetCdmHost(int host_interface_version, void* user_data) { switch (host_interface_version) { case cdm::Host_4::kVersion: return static_cast<cdm::Host_4*>(cdm_adapter); + case cdm::Host_5::kVersion: + return static_cast<cdm::Host_5*>(cdm_adapter); default: PP_NOTREACHED(); return NULL; diff --git a/media/cdm/ppapi/cdm_adapter.h b/media/cdm/ppapi/cdm_adapter.h index daedf9e..cd65b18 100644 --- a/media/cdm/ppapi/cdm_adapter.h +++ b/media/cdm/ppapi/cdm_adapter.h @@ -28,6 +28,11 @@ #include "ppapi/cpp/private/platform_verification.h" #endif +#if defined(GetCurrentTime) +// winbase.h defines this which messes up calls to Host_5::GetCurrentTime. +#undef GetCurrentTime +#endif + namespace media { // GetCdmHostFunc implementation. @@ -37,7 +42,8 @@ void* GetCdmHost(int host_interface_version, void* user_data); // Content Decryption Module (CDM). class CdmAdapter : public pp::Instance, public pp::ContentDecryptor_Private, - public cdm::Host_4 { + public cdm::Host_4, + public cdm::Host_5 { public: CdmAdapter(PP_Instance instance, pp::Module* module); virtual ~CdmAdapter(); @@ -51,14 +57,17 @@ class CdmAdapter : public pp::Instance, // Note: Results of calls to these methods must be reported through the // PPB_ContentDecryptor_Private interface. virtual void Initialize(const std::string& key_system) OVERRIDE; - virtual void CreateSession(uint32_t session_id, - const std::string& content_type, - pp::VarArrayBuffer init_data) OVERRIDE; - virtual void LoadSession(uint32_t session_id, + virtual void CreateSession(uint32_t promise_id, + const std::string& init_data_type, + pp::VarArrayBuffer init_data, + PP_SessionType session_type) OVERRIDE; + virtual void LoadSession(uint32_t promise_id, const std::string& web_session_id) OVERRIDE; - virtual void UpdateSession(uint32_t session_id, + virtual void UpdateSession(uint32_t promise_id, + const std::string& web_session_id, pp::VarArrayBuffer response) OVERRIDE; - virtual void ReleaseSession(uint32_t session_id) OVERRIDE; + virtual void ReleaseSession(uint32_t promise_id, + const std::string& web_session_id) OVERRIDE; virtual void Decrypt( pp::Buffer_Dev encrypted_buffer, const PP_EncryptedBlockInfo& encrypted_block_info) OVERRIDE; @@ -77,9 +86,11 @@ class CdmAdapter : public pp::Instance, pp::Buffer_Dev encrypted_buffer, const PP_EncryptedBlockInfo& encrypted_block_info) OVERRIDE; - // cdm::Host implementation. + // cdm::Host_4 and cdm::Host_5 implementation. virtual cdm::Buffer* Allocate(uint32_t capacity) OVERRIDE; virtual void SetTimer(int64_t delay_ms, void* context) OVERRIDE; + + // cdm::Host_4 implementation. virtual double GetCurrentWallTimeInSeconds() OVERRIDE; virtual void OnSessionCreated(uint32_t session_id, const char* web_session_id, @@ -94,9 +105,47 @@ class CdmAdapter : public pp::Instance, virtual void OnSessionError(uint32_t session_id, cdm::MediaKeyError error_code, uint32_t system_code) OVERRIDE; - virtual void SendPlatformChallenge( - const char* service_id, uint32_t service_id_length, - const char* challenge, uint32_t challenge_length) OVERRIDE; + + // cdm::Host_5 implementation. + virtual cdm::Time GetCurrentTime() OVERRIDE; + virtual void OnResolveNewSessionPromise( + uint32_t promise_id, + const char* web_session_id, + uint32_t web_session_id_length) OVERRIDE; + virtual void OnResolvePromise(uint32_t promise_id) OVERRIDE; + virtual void OnRejectPromise(uint32_t promise_id, + cdm::Error error, + uint32_t system_code, + const char* error_message, + uint32_t error_message_length) OVERRIDE; + virtual void OnSessionMessage(const char* web_session_id, + uint32_t web_session_id_length, + const char* message, + uint32_t message_length, + const char* destination_url, + uint32_t destination_url_length) OVERRIDE; + virtual void OnSessionKeysChange(const char* web_session_id, + uint32_t web_session_id_length, + bool has_additional_usable_key); + virtual void OnExpirationChange(const char* web_session_id, + uint32_t web_session_id_length, + cdm::Time new_expiry_time); + virtual void OnSessionReady(const char* web_session_id, + uint32_t web_session_id_length) OVERRIDE; + virtual void OnSessionClosed(const char* web_session_id, + uint32_t web_session_id_length) OVERRIDE; + virtual void OnSessionError(const char* web_session_id, + uint32_t web_session_id_length, + cdm::Error error, + uint32_t system_code, + const char* error_message, + uint32_t error_message_length) OVERRIDE; + + // cdm::Host_4 and cdm::Host_5 implementation. + virtual void SendPlatformChallenge(const char* service_id, + uint32_t service_id_length, + const char* challenge, + uint32_t challenge_length) OVERRIDE; virtual void EnableOutputProtection( uint32_t desired_protection_mask) OVERRIDE; virtual void QueryOutputProtectionStatus() OVERRIDE; @@ -118,24 +167,43 @@ class CdmAdapter : public pp::Instance, typedef linked_ptr<VideoFrameImpl> LinkedVideoFrame; typedef linked_ptr<AudioFramesImpl> LinkedAudioFrames; + struct SessionError { + SessionError(cdm::Error error, + uint32_t system_code, + std::string error_description); + cdm::Error error; + uint32_t system_code; + std::string error_description; + }; + bool CreateCdmInstance(const std::string& key_system); // <code>PPB_ContentDecryptor_Private</code> dispatchers. These are passed to // <code>callback_factory_</code> to ensure that calls into // <code>PPP_ContentDecryptor_Private</code> are asynchronous. - void SendSessionCreatedInternal(int32_t result, - uint32_t session_id, - const std::string& web_session_id); + void SendPromiseResolvedInternal(int32_t result, uint32_t promise_id); + void SendPromiseResolvedWithSessionInternal( + int32_t result, + uint32_t promise_id, + const std::string& web_session_id); + void SendPromiseRejectedInternal(int32_t result, + uint32_t promise_id, + const SessionError& error); void SendSessionMessageInternal(int32_t result, - uint32_t session_id, + const std::string& web_session_id, const std::vector<uint8>& message, const std::string& destination_url); - void SendSessionReadyInternal(int32_t result, uint32_t session_id); - void SendSessionClosedInternal(int32_t result, uint32_t session_id); + void SendSessionReadyInternal(int32_t result, + const std::string& web_session_id); + void SendSessionClosedInternal(int32_t result, + const std::string& web_session_id); void SendSessionErrorInternal(int32_t result, - uint32_t session_id, - cdm::MediaKeyError error_code, - uint32_t system_code); + const std::string& web_session_id, + const SessionError& error); + void RejectPromise(uint32_t promise_id, + cdm::Error error, + uint32_t system_code, + const std::string& error_message); void DeliverBlock(int32_t result, const cdm::Status& status, diff --git a/media/cdm/ppapi/cdm_wrapper.h b/media/cdm/ppapi/cdm_wrapper.h index cf8e88b..665b6b6 100644 --- a/media/cdm/ppapi/cdm_wrapper.h +++ b/media/cdm/ppapi/cdm_wrapper.h @@ -42,18 +42,23 @@ class CdmWrapper { virtual ~CdmWrapper() {}; - virtual void CreateSession(uint32_t session_id, - const char* content_type, - uint32_t content_type_size, + virtual void CreateSession(uint32_t promise_id, + const char* init_data_type, + uint32_t init_data_type_size, const uint8_t* init_data, - uint32_t init_data_size) = 0; - virtual void LoadSession(uint32_t session_id, + uint32_t init_data_size, + cdm::SessionType session_type) = 0; + virtual void LoadSession(uint32_t promise_id, const char* web_session_id, uint32_t web_session_id_size) = 0; - virtual void UpdateSession(uint32_t session_id, + virtual void UpdateSession(uint32_t promise_id, + const char* web_session_id, + uint32_t web_session_id_size, const uint8_t* response, uint32_t response_size) = 0; - virtual void ReleaseSession(uint32_t session_id) = 0; + virtual void ReleaseSession(uint32_t promise_id, + const char* web_session_id, + uint32_t web_session_id_size) = 0; virtual void TimerExpired(void* context) = 0; virtual cdm::Status Decrypt(const cdm::InputBuffer& encrypted_buffer, cdm::DecryptedBlock* decrypted_buffer) = 0; @@ -75,6 +80,21 @@ class CdmWrapper { uint32_t link_mask, uint32_t output_protection_mask) = 0; + // Helper function for the cdm::Host_4 methods. Calls to CreateSession(), + // LoadSession(), UpdateSession(), and ReleaseSession() pass in promise ids, + // but the CDM interface needs session ids. For create and load, we need to + // create a new session_id to pass to the CDM. For update and release, we need + // to look up |web_session_id| and convert it into the existing |session_id|. + // Since the callbacks don't come through this interface, cdm_adapter needs to + // create the mapping (and delete it on release). + // TODO(jrummell): Remove these once Host_4 interface is removed. + virtual uint32_t LookupPromiseId(uint32_t session_id) = 0; + virtual void AssignWebSessionId(uint32_t session_id, + const char* web_session_id, + uint32_t web_session_id_size) = 0; + virtual std::string LookupWebSessionId(uint32_t session_id) = 0; + virtual void DropWebSessionId(std::string web_session_id) = 0; + protected: CdmWrapper() {} @@ -106,29 +126,42 @@ class CdmWrapperImpl : public CdmWrapper { cdm_->Destroy(); } - virtual void CreateSession(uint32_t session_id, - const char* content_type, - uint32_t content_type_size, + virtual void CreateSession(uint32_t promise_id, + const char* init_data_type, + uint32_t init_data_type_size, const uint8_t* init_data, - uint32_t init_data_size) OVERRIDE { - cdm_->CreateSession( - session_id, content_type, content_type_size, init_data, init_data_size); + uint32_t init_data_size, + cdm::SessionType session_type) OVERRIDE { + cdm_->CreateSession(promise_id, + init_data_type, + init_data_type_size, + init_data, + init_data_size, + session_type); } - virtual void LoadSession(uint32_t session_id, + virtual void LoadSession(uint32_t promise_id, const char* web_session_id, uint32_t web_session_id_size) OVERRIDE { - cdm_->LoadSession(session_id, web_session_id, web_session_id_size); + cdm_->LoadSession(promise_id, web_session_id, web_session_id_size); } - virtual void UpdateSession(uint32_t session_id, + virtual void UpdateSession(uint32_t promise_id, + const char* web_session_id, + uint32_t web_session_id_size, const uint8_t* response, uint32_t response_size) OVERRIDE { - cdm_->UpdateSession(session_id, response, response_size); + cdm_->UpdateSession(promise_id, + web_session_id, + web_session_id_size, + response, + response_size); } - virtual void ReleaseSession(uint32_t session_id) OVERRIDE { - cdm_->ReleaseSession(session_id); + virtual void ReleaseSession(uint32_t promise_id, + const char* web_session_id, + uint32_t web_session_id_size) OVERRIDE { + cdm_->ReleaseSession(promise_id, web_session_id, web_session_id_size); } virtual void TimerExpired(void* context) OVERRIDE { @@ -181,22 +214,133 @@ class CdmWrapperImpl : public CdmWrapper { cdm_->OnQueryOutputProtectionStatus(link_mask, output_protection_mask); } + uint32_t CreateSessionId() { + return next_session_id_++; + } + + void RegisterPromise(uint32_t session_id, uint32_t promise_id) { + PP_DCHECK(promise_to_session_id_map_.find(session_id) == + promise_to_session_id_map_.end()); + promise_to_session_id_map_.insert(std::make_pair(session_id, promise_id)); + } + + virtual uint32_t LookupPromiseId(uint32_t session_id) { + std::map<uint32_t, uint32_t>::iterator it = + promise_to_session_id_map_.find(session_id); + if (it == promise_to_session_id_map_.end()) + return 0; + uint32_t promise_id = it->second; + promise_to_session_id_map_.erase(it); + return promise_id; + } + + virtual void AssignWebSessionId(uint32_t session_id, + const char* web_session_id, + uint32_t web_session_id_size) { + web_session_to_session_id_map_.insert(std::make_pair( + std::string(web_session_id, web_session_id_size), session_id)); + } + + uint32_t LookupSessionId(std::string web_session_id) { + return web_session_to_session_id_map_.find(web_session_id)->second; + } + + virtual std::string LookupWebSessionId(uint32_t session_id) { + std::map<std::string, uint32_t>::iterator it; + for (it = web_session_to_session_id_map_.begin(); + it != web_session_to_session_id_map_.end(); + ++it) { + if (it->second == session_id) + return it->first; + } + PP_NOTREACHED(); + return std::string(); + } + + virtual void DropWebSessionId(std::string web_session_id) { + web_session_to_session_id_map_.erase(web_session_id); + } + private: - CdmWrapperImpl(CdmInterface* cdm) : cdm_(cdm) { + CdmWrapperImpl(CdmInterface* cdm) : cdm_(cdm), next_session_id_(100) { PP_DCHECK(cdm_); } CdmInterface* cdm_; + std::map<uint32_t, uint32_t> promise_to_session_id_map_; + uint32_t next_session_id_; + std::map<std::string, uint32_t> web_session_to_session_id_map_; + DISALLOW_COPY_AND_ASSIGN(CdmWrapperImpl); }; +// Overrides for the cdm::Host_4 methods. Calls to CreateSession(), +// LoadSession(), UpdateSession(), and ReleaseSession() pass in promise ids, +// but the CDM interface needs session ids. For create and load, we need to +// create a new session_id to pass to the CDM. For update and release, we need +// to look up |web_session_id| and convert it into the existing |session_id|. +// Since the callbacks don't come through this interface, cdm_adapter needs to +// create the mapping (and delete it on release). +// TODO(jrummell): Remove these once Host_4 interface is removed. + +template <> +void CdmWrapperImpl<cdm::ContentDecryptionModule_4>::CreateSession( + uint32_t promise_id, + const char* init_data_type, + uint32_t init_data_type_size, + const uint8_t* init_data, + uint32_t init_data_size, + cdm::SessionType session_type) { + uint32_t session_id = CreateSessionId(); + RegisterPromise(session_id, promise_id); + cdm_->CreateSession(session_id, + init_data_type, + init_data_type_size, + init_data, + init_data_size); +} + +template <> +void CdmWrapperImpl<cdm::ContentDecryptionModule_4>::LoadSession( + uint32_t promise_id, + const char* web_session_id, + uint32_t web_session_id_size) { + uint32_t session_id = CreateSessionId(); + RegisterPromise(session_id, promise_id); + cdm_->LoadSession(session_id, web_session_id, web_session_id_size); +} + +template <> +void CdmWrapperImpl<cdm::ContentDecryptionModule_4>::UpdateSession( + uint32_t promise_id, + const char* web_session_id, + uint32_t web_session_id_size, + const uint8_t* response, + uint32_t response_size) { + std::string web_session_str(web_session_id, web_session_id_size); + uint32_t session_id = LookupSessionId(web_session_str); + RegisterPromise(session_id, promise_id); + cdm_->UpdateSession(session_id, response, response_size); +} + +template <> +void CdmWrapperImpl<cdm::ContentDecryptionModule_4>::ReleaseSession( + uint32_t promise_id, + const char* web_session_id, + uint32_t web_session_id_size) { + std::string web_session_str(web_session_id, web_session_id_size); + uint32_t session_id = LookupSessionId(web_session_str); + RegisterPromise(session_id, promise_id); + cdm_->ReleaseSession(session_id); +} + CdmWrapper* CdmWrapper::Create(const char* key_system, uint32_t key_system_size, GetCdmHostFunc get_cdm_host_func, void* user_data) { COMPILE_ASSERT(cdm::ContentDecryptionModule::kVersion == - cdm::ContentDecryptionModule_4::kVersion, + cdm::ContentDecryptionModule_5::kVersion, update_code_below); // Ensure IsSupportedCdmInterfaceVersion() matches this implementation. @@ -207,18 +351,22 @@ CdmWrapper* CdmWrapper::Create(const char* key_system, !IsSupportedCdmInterfaceVersion(cdm::ContentDecryptionModule::kVersion + 1) && IsSupportedCdmInterfaceVersion(cdm::ContentDecryptionModule::kVersion) && - !IsSupportedCdmInterfaceVersion(cdm::ContentDecryptionModule::kVersion - + IsSupportedCdmInterfaceVersion( + cdm::ContentDecryptionModule_4::kVersion) && + !IsSupportedCdmInterfaceVersion(cdm::ContentDecryptionModule_4::kVersion - 1)); // Try to create the CDM using the latest CDM interface version. CdmWrapper* cdm_wrapper = CdmWrapperImpl<cdm::ContentDecryptionModule>::Create( key_system, key_system_size, get_cdm_host_func, user_data); + if (cdm_wrapper) + return cdm_wrapper; // If |cdm_wrapper| is NULL, try to create the CDM using older supported // versions of the CDM interface. - // No older versions of CDM interface supported. - + cdm_wrapper = CdmWrapperImpl<cdm::ContentDecryptionModule_4>::Create( + key_system, key_system_size, get_cdm_host_func, user_data); return cdm_wrapper; } @@ -227,7 +375,7 @@ CdmWrapper* CdmWrapper::Create(const char* key_system, // does not have. // Also update supported_cdm_versions.h. COMPILE_ASSERT(cdm::ContentDecryptionModule::kVersion == - cdm::ContentDecryptionModule_4::kVersion, + cdm::ContentDecryptionModule_5::kVersion, ensure_cdm_wrapper_templates_have_old_version_support); } // namespace media diff --git a/media/cdm/ppapi/external_clear_key/clear_key_cdm.cc b/media/cdm/ppapi/external_clear_key/clear_key_cdm.cc index 82e3b63..b10111e 100644 --- a/media/cdm/ppapi/external_clear_key/clear_key_cdm.cc +++ b/media/cdm/ppapi/external_clear_key/clear_key_cdm.cc @@ -14,6 +14,7 @@ #include "base/debug/trace_event.h" #include "base/logging.h" #include "base/time/time.h" +#include "media/base/cdm_promise.h" #include "media/base/decoder_buffer.h" #include "media/base/decrypt_config.h" #include "media/cdm/json_web_key.h" @@ -133,6 +134,39 @@ static std::string GetFileIOTestResultMessage(bool success) { return message; } +static cdm::Error ConvertException(media::MediaKeys::Exception exception_code) { + switch (exception_code) { + case media::MediaKeys::NOT_SUPPORTED_ERROR: + return cdm::kNotSupportedError; + case media::MediaKeys::INVALID_STATE_ERROR: + return cdm::kInvalidStateError; + case media::MediaKeys::INVALID_ACCESS_ERROR: + return cdm::kInvalidAccessError; + case media::MediaKeys::QUOTA_EXCEEDED_ERROR: + return cdm::kQuotaExceededError; + case media::MediaKeys::UNKNOWN_ERROR: + return cdm::kUnknownError; + case media::MediaKeys::CLIENT_ERROR: + return cdm::kClientError; + case media::MediaKeys::OUTPUT_ERROR: + return cdm::kOutputError; + } + NOTIMPLEMENTED(); + return cdm::kUnknownError; +} + +static media::MediaKeys::SessionType ConvertSessionType( + cdm::SessionType session_type) { + switch (session_type) { + case cdm::kPersistent: + return media::MediaKeys::PERSISTENT_SESSION; + case cdm::kTemporary: + return media::MediaKeys::TEMPORARY_SESSION; + } + NOTIMPLEMENTED(); + return media::MediaKeys::TEMPORARY_SESSION; +} + template<typename Type> class ScopedResetter { public: @@ -187,15 +221,9 @@ namespace media { ClearKeyCdm::ClearKeyCdm(ClearKeyCdmHost* host, const std::string& key_system) : decryptor_( - base::Bind(&ClearKeyCdm::OnSessionCreated, base::Unretained(this)), - base::Bind(&ClearKeyCdm::OnSessionMessage, base::Unretained(this)), - base::Bind(&ClearKeyCdm::OnSessionReady, base::Unretained(this)), - base::Bind(&ClearKeyCdm::OnSessionClosed, base::Unretained(this)), - base::Bind(&ClearKeyCdm::OnSessionError, base::Unretained(this))), + base::Bind(&ClearKeyCdm::OnSessionMessage, base::Unretained(this))), host_(host), key_system_(key_system), - last_session_id_(MediaKeys::kInvalidSessionId), - session_id_for_emulated_loadsession_(MediaKeys::kInvalidSessionId), timer_delay_ms_(kInitialTimerDelayMs), heartbeat_timer_set_(false) { #if defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER) @@ -209,17 +237,26 @@ ClearKeyCdm::ClearKeyCdm(ClearKeyCdmHost* host, const std::string& key_system) ClearKeyCdm::~ClearKeyCdm() {} -void ClearKeyCdm::CreateSession(uint32 session_id, - const char* type, - uint32 type_size, +void ClearKeyCdm::CreateSession(uint32 promise_id, + const char* init_data_type, + uint32 init_data_type_size, const uint8* init_data, - uint32 init_data_size) { + uint32 init_data_size, + cdm::SessionType session_type) { DVLOG(1) << __FUNCTION__; - decryptor_.CreateSession( - session_id, std::string(type, type_size), init_data, init_data_size); - // Save the latest session ID for heartbeat and file IO test messages. - last_session_id_ = session_id; + scoped_ptr<media::NewSessionCdmPromise> promise( + new media::NewSessionCdmPromise(base::Bind(&ClearKeyCdm::OnSessionCreated, + base::Unretained(this), + promise_id), + base::Bind(&ClearKeyCdm::OnPromiseFailed, + base::Unretained(this), + promise_id))); + decryptor_.CreateSession(std::string(init_data_type, init_data_type_size), + init_data, + init_data_size, + ConvertSessionType(session_type), + promise.Pass()); if (key_system_ == kExternalClearKeyFileIOTestKeySystem) StartFileIOTest(); @@ -228,28 +265,53 @@ void ClearKeyCdm::CreateSession(uint32 session_id, // Loads a emulated stored session. Currently only |kLoadableWebSessionId| // (containing a |kLoadableSessionKey| for |kLoadableSessionKeyId|) is // supported. -void ClearKeyCdm::LoadSession(uint32_t session_id, +void ClearKeyCdm::LoadSession(uint32 promise_id, const char* web_session_id, uint32_t web_session_id_length) { DVLOG(1) << __FUNCTION__; if (std::string(kLoadableWebSessionId) != std::string(web_session_id, web_session_id_length)) { - // TODO(xhwang): Report "NotFoundError" when we support DOMError style. - OnSessionError(session_id, MediaKeys::kUnknownError, 0); + std::string message("Incorrect session id specified for LoadSession()."); + host_->OnRejectPromise(promise_id, + cdm::kInvalidAccessError, + 0, + message.data(), + message.length()); return; } - session_id_for_emulated_loadsession_ = session_id; - - decryptor_.CreateSession(session_id, kLoadableSessionContentType, NULL, 0); -} - -void ClearKeyCdm::UpdateSession(uint32 session_id, + scoped_ptr<media::NewSessionCdmPromise> promise( + new media::NewSessionCdmPromise(base::Bind(&ClearKeyCdm::OnSessionLoaded, + base::Unretained(this), + promise_id), + base::Bind(&ClearKeyCdm::OnPromiseFailed, + base::Unretained(this), + promise_id))); + decryptor_.CreateSession(std::string(kLoadableSessionContentType), + NULL, + 0, + MediaKeys::TEMPORARY_SESSION, + promise.Pass()); +} + +void ClearKeyCdm::UpdateSession(uint32 promise_id, + const char* web_session_id, + uint32_t web_session_id_size, const uint8* response, uint32 response_size) { DVLOG(1) << __FUNCTION__; - decryptor_.UpdateSession(session_id, response, response_size); + std::string web_session_str(web_session_id, web_session_id_size); + + scoped_ptr<media::SimpleCdmPromise> promise(new media::SimpleCdmPromise( + base::Bind(&ClearKeyCdm::OnSessionUpdated, + base::Unretained(this), + promise_id, + web_session_str), + base::Bind( + &ClearKeyCdm::OnPromiseFailed, base::Unretained(this), promise_id))); + decryptor_.UpdateSession( + web_session_str, response, response_size, promise.Pass()); if (!heartbeat_timer_set_) { ScheduleNextHeartBeat(); @@ -257,9 +319,27 @@ void ClearKeyCdm::UpdateSession(uint32 session_id, } } -void ClearKeyCdm::ReleaseSession(uint32 session_id) { +void ClearKeyCdm::ReleaseSession(uint32 promise_id, + const char* web_session_id, + uint32_t web_session_id_size) { DVLOG(1) << __FUNCTION__; - decryptor_.ReleaseSession(session_id); + std::string web_session_str(web_session_id, web_session_id_size); + + scoped_ptr<media::SimpleCdmPromise> promise(new media::SimpleCdmPromise( + base::Bind(&ClearKeyCdm::OnSessionReleased, + base::Unretained(this), + promise_id, + web_session_str), + base::Bind( + &ClearKeyCdm::OnPromiseFailed, base::Unretained(this), promise_id))); + decryptor_.ReleaseSession(web_session_str, promise.Pass()); +} + +void ClearKeyCdm::SetServerCertificate(uint32 promise_id, + const uint8_t* server_certificate_data, + uint32_t server_certificate_data_size) { + // ClearKey doesn't use a server certificate. + host_->OnResolvePromise(promise_id); } void ClearKeyCdm::TimerExpired(void* context) { @@ -281,9 +361,10 @@ void ClearKeyCdm::TimerExpired(void* context) { // There is no service at this URL, so applications should ignore it. const char url[] = "http://test.externalclearkey.chromium.org"; - host_->OnSessionMessage(last_session_id_, + host_->OnSessionMessage(last_session_id_.data(), + last_session_id_.length(), heartbeat_message.data(), - heartbeat_message.size(), + heartbeat_message.length(), url, arraysize(url) - 1); @@ -476,7 +557,7 @@ void ClearKeyCdm::ScheduleNextHeartBeat() { // Prepare the next heartbeat message and set timer. std::ostringstream msg_stream; msg_stream << kHeartBeatHeader << " from ClearKey CDM set at time " - << host_->GetCurrentWallTimeInSeconds() << "."; + << host_->GetCurrentTime() << "."; next_heartbeat_message_ = msg_stream.str(); host_->SetTimer(timer_delay_ms_, &next_heartbeat_message_[0]); @@ -535,66 +616,102 @@ void ClearKeyCdm::LoadLoadableSession() { sizeof(kLoadableSessionKeyId) - 1); // TODO(xhwang): This triggers OnSessionUpdated(). For prefixed EME support, // this is okay. Check WD EME support. + scoped_ptr<media::SimpleCdmPromise> promise(new media::SimpleCdmPromise( + base::Bind(&ClearKeyCdm::OnSessionUpdated, + base::Unretained(this), + promise_id_for_emulated_loadsession_, + session_id_for_emulated_loadsession_), + base::Bind(&ClearKeyCdm::OnPromiseFailed, + base::Unretained(this), + promise_id_for_emulated_loadsession_))); decryptor_.UpdateSession(session_id_for_emulated_loadsession_, reinterpret_cast<const uint8*>(jwk_set.data()), - jwk_set.size()); + jwk_set.size(), + promise.Pass()); } -void ClearKeyCdm::OnSessionCreated(uint32 session_id, - const std::string& web_session_id) { - std::string new_web_session_id = web_session_id; - - if (session_id == session_id_for_emulated_loadsession_) { - // Delay LoadLoadableSession() to test the case where Decrypt*() calls are - // made before the session is fully loaded. - const int64 kDelayToLoadSessionMs = 500; - // Use the address of |session_id_for_emulated_loadsession_| as the timer - // context so that we can call LoadLoadableSession() when the timer expires. - host_->SetTimer(kDelayToLoadSessionMs, - &session_id_for_emulated_loadsession_); - // Defer OnSessionCreated() until the session is loaded. - return; - } - - host_->OnSessionCreated( - session_id, web_session_id.data(), web_session_id.size()); -} - -void ClearKeyCdm::OnSessionMessage(uint32 session_id, +void ClearKeyCdm::OnSessionMessage(const std::string& web_session_id, const std::vector<uint8>& message, const GURL& destination_url) { DVLOG(1) << "OnSessionMessage: " << message.size(); // Ignore the message when we are waiting to update the loadable session. - if (session_id == session_id_for_emulated_loadsession_) + if (web_session_id == session_id_for_emulated_loadsession_) return; - host_->OnSessionMessage(session_id, + // OnSessionMessage() only called during CreateSession(), so no promise + // involved (OnSessionCreated() called to resolve the CreateSession() + // promise). + host_->OnSessionMessage(web_session_id.data(), + web_session_id.length(), reinterpret_cast<const char*>(message.data()), message.size(), destination_url.spec().data(), destination_url.spec().size()); } -void ClearKeyCdm::OnSessionReady(uint32 session_id) { - if (session_id == session_id_for_emulated_loadsession_) { - session_id_for_emulated_loadsession_ = MediaKeys::kInvalidSessionId; - host_->OnSessionCreated( - session_id, kLoadableWebSessionId, strlen(kLoadableWebSessionId)); +void ClearKeyCdm::OnSessionCreated(uint32 promise_id, + const std::string& web_session_id) { + // Save the latest session ID for heartbeat and file IO test messages. + last_session_id_ = web_session_id; + + host_->OnResolveNewSessionPromise( + promise_id, web_session_id.data(), web_session_id.length()); +} + +void ClearKeyCdm::OnSessionLoaded(uint32 promise_id, + const std::string& web_session_id) { + // Save the latest session ID for heartbeat and file IO test messages. + last_session_id_ = web_session_id; + + // |decryptor_| created some session as |web_session_id|, but going forward + // we need to map that to |kLoadableWebSessionId|, as that is what callers + // expect. + session_id_for_emulated_loadsession_ = web_session_id; + + // Delay LoadLoadableSession() to test the case where Decrypt*() calls are + // made before the session is fully loaded. + const int64 kDelayToLoadSessionMs = 500; + + // Defer resolving the promise until the session is loaded. + promise_id_for_emulated_loadsession_ = promise_id; + + // Use the address of |session_id_for_emulated_loadsession_| as the timer + // context so that we can call LoadLoadableSession() when the timer expires. + host_->SetTimer(kDelayToLoadSessionMs, &session_id_for_emulated_loadsession_); +} + +void ClearKeyCdm::OnSessionUpdated(uint32 promise_id, + const std::string& web_session_id) { + // OnSessionReady() only called as success for UpdateSession(). However, + // UpdateSession() also called to finish loading sessions, so handle + // appropriately. + if (web_session_id == session_id_for_emulated_loadsession_) { + session_id_for_emulated_loadsession_ = std::string(); + // |promise_id| is the LoadSession() promise, so resolve appropriately. + host_->OnResolveNewSessionPromise( + promise_id, kLoadableWebSessionId, strlen(kLoadableWebSessionId)); + host_->OnSessionReady(kLoadableWebSessionId, strlen(kLoadableWebSessionId)); + return; } - host_->OnSessionReady(session_id); + host_->OnResolvePromise(promise_id); } -void ClearKeyCdm::OnSessionClosed(uint32 session_id) { - host_->OnSessionClosed(session_id); +void ClearKeyCdm::OnSessionReleased(uint32 promise_id, + const std::string& web_session_id) { + host_->OnResolvePromise(promise_id); } -void ClearKeyCdm::OnSessionError(uint32 session_id, - media::MediaKeys::KeyError error_code, - uint32 system_code) { - host_->OnSessionError( - session_id, static_cast<cdm::MediaKeyError>(error_code), system_code); +void ClearKeyCdm::OnPromiseFailed(uint32 promise_id, + MediaKeys::Exception exception_code, + uint32 system_code, + const std::string& error_message) { + host_->OnRejectPromise(promise_id, + ConvertException(exception_code), + system_code, + error_message.data(), + error_message.length()); } #if defined(CLEAR_KEY_CDM_USE_FAKE_AUDIO_DECODER) @@ -666,8 +783,12 @@ void ClearKeyCdm::StartFileIOTest() { void ClearKeyCdm::OnFileIOTestComplete(bool success) { DVLOG(1) << __FUNCTION__ << ": " << success; std::string message = GetFileIOTestResultMessage(success); - host_->OnSessionMessage( - last_session_id_, message.data(), message.size(), NULL, 0); + host_->OnSessionMessage(last_session_id_.data(), + last_session_id_.length(), + message.data(), + message.length(), + NULL, + 0); file_io_test_runner_.reset(); } diff --git a/media/cdm/ppapi/external_clear_key/clear_key_cdm.h b/media/cdm/ppapi/external_clear_key/clear_key_cdm.h index d0ef3c7..e489c1b 100644 --- a/media/cdm/ppapi/external_clear_key/clear_key_cdm.h +++ b/media/cdm/ppapi/external_clear_key/clear_key_cdm.h @@ -35,17 +35,27 @@ class ClearKeyCdm : public ClearKeyCdmInterface { virtual ~ClearKeyCdm(); // ContentDecryptionModule implementation. - virtual void CreateSession( - uint32 session_id, - const char* type, uint32 type_size, - const uint8* init_data, uint32 init_data_size) OVERRIDE; - virtual void LoadSession( - uint32_t session_id, - const char* web_session_id, uint32_t web_session_id_length) OVERRIDE; - virtual void UpdateSession( - uint32 session_id, - const uint8* response, uint32 response_size) OVERRIDE; - virtual void ReleaseSession(uint32 session_id) OVERRIDE; + virtual void CreateSession(uint32 promise_id, + const char* init_data_type, + uint32 init_data_type_size, + const uint8* init_data, + uint32 init_data_size, + cdm::SessionType session_type) OVERRIDE; + virtual void LoadSession(uint32 promise_id, + const char* web_session_id, + uint32_t web_session_id_length) OVERRIDE; + virtual void UpdateSession(uint32 promise_id, + const char* web_session_id, + uint32_t web_session_id_length, + const uint8* response, + uint32 response_size) OVERRIDE; + virtual void ReleaseSession(uint32 promise_id, + const char* web_session_id, + uint32_t web_session_id_length) OVERRIDE; + virtual void SetServerCertificate( + uint32 promise_id, + const uint8_t* server_certificate_data, + uint32_t server_certificate_data_size) OVERRIDE; virtual void TimerExpired(void* context) OVERRIDE; virtual cdm::Status Decrypt(const cdm::InputBuffer& encrypted_buffer, cdm::DecryptedBlock* decrypted_block) OVERRIDE; @@ -73,15 +83,20 @@ class ClearKeyCdm : public ClearKeyCdmInterface { void LoadLoadableSession(); // ContentDecryptionModule callbacks. - void OnSessionCreated(uint32 session_id, const std::string& web_session_id); - void OnSessionMessage(uint32 session_id, + void OnSessionMessage(const std::string& web_session_id, const std::vector<uint8>& message, const GURL& destination_url); - void OnSessionReady(uint32 session_id); - void OnSessionClosed(uint32 session_id); - void OnSessionError(uint32 session_id, - MediaKeys::KeyError error_code, - uint32 system_code); + + // Handle the success/failure of a promise. These methods are responsible for + // calling |host_| to resolve or reject the promise. + void OnSessionCreated(uint32 promise_id, const std::string& web_session_id); + void OnSessionLoaded(uint32 promise_id, const std::string& web_session_id); + void OnSessionUpdated(uint32 promise_id, const std::string& web_session_id); + void OnSessionReleased(uint32 promise_id, const std::string& web_session_id); + void OnPromiseFailed(uint32 promise_id, + MediaKeys::Exception exception_code, + uint32 system_code, + const std::string& error_message); // Prepares next heartbeat message and sets a timer for it. void ScheduleNextHeartBeat(); @@ -117,18 +132,22 @@ class ClearKeyCdm : public ClearKeyCdmInterface { // Callback for CDM File IO test. void OnFileIOTestComplete(bool success); + // Keep track of the last session created. + void SetSessionId(const std::string& web_session_id); + AesDecryptor decryptor_; ClearKeyCdmHost* host_; const std::string key_system_; - uint32 last_session_id_; + std::string last_session_id_; std::string next_heartbeat_message_; // TODO(xhwang): Extract testing code from main implementation. // See http://crbug.com/341751 - uint32 session_id_for_emulated_loadsession_; + std::string session_id_for_emulated_loadsession_; + uint32_t promise_id_for_emulated_loadsession_; // Timer delay in milliseconds for the next host_->SetTimer() call. int64 timer_delay_ms_; diff --git a/media/cdm/ppapi/external_clear_key/clear_key_cdm_common.h b/media/cdm/ppapi/external_clear_key/clear_key_cdm_common.h index 7c1a8c8..2bbc5b1 100644 --- a/media/cdm/ppapi/external_clear_key/clear_key_cdm_common.h +++ b/media/cdm/ppapi/external_clear_key/clear_key_cdm_common.h @@ -10,7 +10,7 @@ namespace media { // Aliases for the version of the interfaces that this CDM implements. -typedef cdm::ContentDecryptionModule_4 ClearKeyCdmInterface; +typedef cdm::ContentDecryptionModule_5 ClearKeyCdmInterface; typedef ClearKeyCdmInterface::Host ClearKeyCdmHost; } // namespace media diff --git a/media/cdm/ppapi/supported_cdm_versions.h b/media/cdm/ppapi/supported_cdm_versions.h index d2ae5b3..8de7a8c 100644 --- a/media/cdm/ppapi/supported_cdm_versions.h +++ b/media/cdm/ppapi/supported_cdm_versions.h @@ -21,10 +21,11 @@ bool IsSupportedCdmModuleVersion(int version) { bool IsSupportedCdmInterfaceVersion(int version) { COMPILE_ASSERT(cdm::ContentDecryptionModule::kVersion == - cdm::ContentDecryptionModule_4::kVersion, + cdm::ContentDecryptionModule_5::kVersion, update_code_below); switch(version) { // Supported versions in decreasing order. + case cdm::ContentDecryptionModule_5::kVersion: case cdm::ContentDecryptionModule_4::kVersion: return true; default: @@ -34,10 +35,11 @@ bool IsSupportedCdmInterfaceVersion(int version) { bool IsSupportedCdmHostVersion(int version) { COMPILE_ASSERT(cdm::ContentDecryptionModule::Host::kVersion == - cdm::ContentDecryptionModule_4::Host::kVersion, + cdm::ContentDecryptionModule_5::Host::kVersion, update_code_below); switch(version) { // Supported versions in decreasing order. + case cdm::Host_5::kVersion: case cdm::Host_4::kVersion: return true; default: |