1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "content/renderer/media/crypto/proxy_media_keys.h"
6
7#include <vector>
8
9#include "base/basictypes.h"
10#include "base/logging.h"
11#include "base/stl_util.h"
12#include "content/renderer/media/crypto/key_systems.h"
13#include "content/renderer/media/crypto/renderer_cdm_manager.h"
14#include "media/base/cdm_promise.h"
15
16namespace content {
17
18scoped_ptr<ProxyMediaKeys> ProxyMediaKeys::Create(
19    const std::string& key_system,
20    const GURL& security_origin,
21    RendererCdmManager* manager,
22    const media::SessionMessageCB& session_message_cb,
23    const media::SessionReadyCB& session_ready_cb,
24    const media::SessionClosedCB& session_closed_cb,
25    const media::SessionErrorCB& session_error_cb,
26    const media::SessionKeysChangeCB& session_keys_change_cb,
27    const media::SessionExpirationUpdateCB& session_expiration_update_cb) {
28  DCHECK(manager);
29
30  // TODO(jrummell): Add support for SessionKeysChangeCB and
31  // SessionExpirationUpdateCB.
32  scoped_ptr<ProxyMediaKeys> proxy_media_keys(
33      new ProxyMediaKeys(manager,
34                         session_message_cb,
35                         session_ready_cb,
36                         session_closed_cb,
37                         session_error_cb));
38  proxy_media_keys->InitializeCdm(key_system, security_origin);
39  return proxy_media_keys.Pass();
40}
41
42ProxyMediaKeys::~ProxyMediaKeys() {
43  manager_->DestroyCdm(cdm_id_);
44  manager_->UnregisterMediaKeys(cdm_id_);
45
46  // Reject any outstanding promises.
47  for (PromiseMap::iterator it = session_id_to_promise_map_.begin();
48       it != session_id_to_promise_map_.end();
49       ++it) {
50    it->second->reject(
51        media::MediaKeys::NOT_SUPPORTED_ERROR, 0, "The operation was aborted.");
52  }
53  session_id_to_promise_map_.clear();
54}
55
56void ProxyMediaKeys::SetServerCertificate(
57    const uint8* certificate_data,
58    int certificate_data_length,
59    scoped_ptr<media::SimpleCdmPromise> promise) {
60  promise->reject(NOT_SUPPORTED_ERROR, 0, "Not yet implemented.");
61}
62
63void ProxyMediaKeys::CreateSession(
64    const std::string& init_data_type,
65    const uint8* init_data,
66    int init_data_length,
67    SessionType session_type,
68    scoped_ptr<media::NewSessionCdmPromise> promise) {
69  // TODO(xhwang): Move these checks up to blink and DCHECK here.
70  // See http://crbug.com/342510
71  CdmHostMsg_CreateSession_ContentType create_session_content_type;
72  if (init_data_type == "cenc") {
73    create_session_content_type = CREATE_SESSION_TYPE_MP4;
74  } else if (init_data_type == "webm") {
75    create_session_content_type = CREATE_SESSION_TYPE_WEBM;
76  } else {
77    DLOG(ERROR) << "Unsupported EME CreateSession content type of "
78                << init_data_type;
79    promise->reject(
80        NOT_SUPPORTED_ERROR,
81        0,
82        "Unsupported EME CreateSession init data type of " + init_data_type);
83    return;
84  }
85
86  uint32 session_id = CreateSessionId();
87  SavePromise(session_id, promise.PassAs<media::CdmPromise>());
88  manager_->CreateSession(
89      cdm_id_,
90      session_id,
91      create_session_content_type,
92      std::vector<uint8>(init_data, init_data + init_data_length));
93}
94
95void ProxyMediaKeys::LoadSession(
96    const std::string& web_session_id,
97    scoped_ptr<media::NewSessionCdmPromise> promise) {
98  // TODO(xhwang): Check key system and platform support for LoadSession in
99  // blink and add NOTREACHED() here.
100  DLOG(ERROR) << "ProxyMediaKeys doesn't support session loading.";
101  promise->reject(NOT_SUPPORTED_ERROR, 0, "LoadSession() is not supported.");
102}
103
104void ProxyMediaKeys::UpdateSession(
105    const std::string& web_session_id,
106    const uint8* response,
107    int response_length,
108    scoped_ptr<media::SimpleCdmPromise> promise) {
109  uint32 session_id = LookupSessionId(web_session_id);
110  if (!session_id) {
111    promise->reject(INVALID_ACCESS_ERROR, 0, "Session does not exist.");
112    return;
113  }
114
115  SavePromise(session_id, promise.PassAs<media::CdmPromise>());
116  manager_->UpdateSession(
117      cdm_id_,
118      session_id,
119      std::vector<uint8>(response, response + response_length));
120}
121
122void ProxyMediaKeys::CloseSession(const std::string& web_session_id,
123                                  scoped_ptr<media::SimpleCdmPromise> promise) {
124  uint32 session_id = LookupSessionId(web_session_id);
125  if (!session_id) {
126    promise->reject(INVALID_ACCESS_ERROR, 0, "Session does not exist.");
127    return;
128  }
129
130  SavePromise(session_id, promise.PassAs<media::CdmPromise>());
131  manager_->ReleaseSession(cdm_id_, session_id);
132}
133
134void ProxyMediaKeys::RemoveSession(
135    const std::string& web_session_id,
136    scoped_ptr<media::SimpleCdmPromise> promise) {
137  promise->reject(NOT_SUPPORTED_ERROR, 0, "Not yet implemented.");
138}
139
140void ProxyMediaKeys::GetUsableKeyIds(const std::string& web_session_id,
141                                     scoped_ptr<media::KeyIdsPromise> promise) {
142  promise->reject(NOT_SUPPORTED_ERROR, 0, "Not yet implemented.");
143}
144
145void ProxyMediaKeys::OnSessionCreated(uint32 session_id,
146                                      const std::string& web_session_id) {
147  AssignWebSessionId(session_id, web_session_id);
148  scoped_ptr<media::CdmPromise> promise = TakePromise(session_id);
149  if (promise) {
150    media::NewSessionCdmPromise* session_promise(
151        static_cast<media::NewSessionCdmPromise*>(promise.get()));
152    session_promise->resolve(web_session_id);
153  }
154}
155
156void ProxyMediaKeys::OnSessionMessage(uint32 session_id,
157                                      const std::vector<uint8>& message,
158                                      const GURL& destination_url) {
159  session_message_cb_.Run(
160      LookupWebSessionId(session_id), message, destination_url);
161}
162
163void ProxyMediaKeys::OnSessionReady(uint32 session_id) {
164  scoped_ptr<media::CdmPromise> promise = TakePromise(session_id);
165  if (promise) {
166    media::SimpleCdmPromise* simple_promise(
167        static_cast<media::SimpleCdmPromise*>(promise.get()));
168    simple_promise->resolve();
169  } else {
170    // Still needed for keyadded.
171    const std::string web_session_id = LookupWebSessionId(session_id);
172    session_ready_cb_.Run(web_session_id);
173  }
174}
175
176void ProxyMediaKeys::OnSessionClosed(uint32 session_id) {
177  const std::string web_session_id = LookupWebSessionId(session_id);
178  DropWebSessionId(web_session_id);
179  scoped_ptr<media::CdmPromise> promise = TakePromise(session_id);
180  if (promise) {
181    media::SimpleCdmPromise* simple_promise(
182        static_cast<media::SimpleCdmPromise*>(promise.get()));
183    simple_promise->resolve();
184  } else {
185    // It is possible for the CDM to close a session independent of a
186    // Release() request.
187    session_closed_cb_.Run(web_session_id);
188  }
189}
190
191void ProxyMediaKeys::OnSessionError(uint32 session_id,
192                                    media::MediaKeys::KeyError error_code,
193                                    uint32 system_code) {
194  const std::string web_session_id = LookupWebSessionId(session_id);
195  media::MediaKeys::Exception exception_code;
196  switch (error_code) {
197    case media::MediaKeys::kClientError:
198      exception_code = media::MediaKeys::CLIENT_ERROR;
199      break;
200    case media::MediaKeys::kOutputError:
201      exception_code = media::MediaKeys::OUTPUT_ERROR;
202      break;
203    case media::MediaKeys::kUnknownError:
204    default:
205      exception_code = media::MediaKeys::UNKNOWN_ERROR;
206      break;
207  }
208
209  scoped_ptr<media::CdmPromise> promise = TakePromise(session_id);
210  if (promise) {
211    promise->reject(exception_code, system_code, std::string());
212    return;
213  }
214
215  // Errors generally happen in response to a request, but it is possible
216  // for something bad to happen in the CDM and it needs to tell the client.
217  session_error_cb_.Run(
218      web_session_id, exception_code, system_code, std::string());
219}
220
221int ProxyMediaKeys::GetCdmId() const {
222  return cdm_id_;
223}
224
225ProxyMediaKeys::ProxyMediaKeys(
226    RendererCdmManager* manager,
227    const media::SessionMessageCB& session_message_cb,
228    const media::SessionReadyCB& session_ready_cb,
229    const media::SessionClosedCB& session_closed_cb,
230    const media::SessionErrorCB& session_error_cb)
231    : manager_(manager),
232      session_message_cb_(session_message_cb),
233      session_ready_cb_(session_ready_cb),
234      session_closed_cb_(session_closed_cb),
235      session_error_cb_(session_error_cb),
236      next_session_id_(1) {
237  cdm_id_ = manager->RegisterMediaKeys(this);
238}
239
240void ProxyMediaKeys::InitializeCdm(const std::string& key_system,
241                                   const GURL& security_origin) {
242  manager_->InitializeCdm(cdm_id_, this, key_system, security_origin);
243}
244
245uint32_t ProxyMediaKeys::CreateSessionId() {
246  return next_session_id_++;
247}
248
249void ProxyMediaKeys::AssignWebSessionId(uint32_t session_id,
250                                        const std::string& web_session_id) {
251  DCHECK(!ContainsKey(web_session_to_session_id_map_, web_session_id));
252  DCHECK(session_id);
253  web_session_to_session_id_map_.insert(
254      std::make_pair(web_session_id, session_id));
255}
256
257uint32_t ProxyMediaKeys::LookupSessionId(
258    const std::string& web_session_id) const {
259  SessionIdMap::const_iterator it =
260      web_session_to_session_id_map_.find(web_session_id);
261  return (it != web_session_to_session_id_map_.end()) ? it->second : 0;
262}
263
264std::string ProxyMediaKeys::LookupWebSessionId(uint32_t session_id) const {
265  for (SessionIdMap::const_iterator it = web_session_to_session_id_map_.begin();
266       it != web_session_to_session_id_map_.end();
267       ++it) {
268    if (it->second == session_id)
269      return it->first;
270  }
271  // Possible to get an error creating a session, so no |web_session_id|
272  // available.
273  return std::string();
274}
275
276void ProxyMediaKeys::DropWebSessionId(const std::string& web_session_id) {
277  web_session_to_session_id_map_.erase(web_session_id);
278}
279
280void ProxyMediaKeys::SavePromise(uint32_t session_id,
281                                 scoped_ptr<media::CdmPromise> promise) {
282  // Should only be one promise outstanding for any |session_id|.
283  DCHECK(!ContainsKey(session_id_to_promise_map_, session_id));
284  session_id_to_promise_map_.add(session_id, promise.Pass());
285}
286
287scoped_ptr<media::CdmPromise> ProxyMediaKeys::TakePromise(uint32_t session_id) {
288  PromiseMap::iterator it = session_id_to_promise_map_.find(session_id);
289  // May not be a promise associated with this session for asynchronous events.
290  if (it == session_id_to_promise_map_.end())
291    return scoped_ptr<media::CdmPromise>();
292  return session_id_to_promise_map_.take_and_erase(it);
293}
294
295}  // namespace content
296