1// Copyright (c) 2013 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 "media/base/android/media_drm_bridge.h"
6
7#include <algorithm>
8
9#include "base/android/build_info.h"
10#include "base/android/jni_array.h"
11#include "base/android/jni_string.h"
12#include "base/callback_helpers.h"
13#include "base/containers/hash_tables.h"
14#include "base/lazy_instance.h"
15#include "base/location.h"
16#include "base/logging.h"
17#include "base/message_loop/message_loop_proxy.h"
18#include "base/strings/string_util.h"
19#include "base/sys_byteorder.h"
20#include "jni/MediaDrmBridge_jni.h"
21
22#include "widevine_cdm_version.h"  // In SHARED_INTERMEDIATE_DIR.
23
24using base::android::AttachCurrentThread;
25using base::android::ConvertUTF8ToJavaString;
26using base::android::ConvertJavaStringToUTF8;
27using base::android::JavaByteArrayToByteVector;
28using base::android::ScopedJavaLocalRef;
29
30namespace media {
31
32static uint32 ReadUint32(const uint8_t* data) {
33  uint32 value = 0;
34  for (int i = 0; i < 4; ++i)
35    value = (value << 8) | data[i];
36  return value;
37}
38
39static uint64 ReadUint64(const uint8_t* data) {
40  uint64 value = 0;
41  for (int i = 0; i < 8; ++i)
42    value = (value << 8) | data[i];
43  return value;
44}
45
46// The structure of an ISO CENC Protection System Specific Header (PSSH) box is
47// as follows. (See ISO/IEC FDIS 23001-7:2011(E).)
48// Note: ISO boxes use big-endian values.
49//
50// PSSH {
51//   uint32 Size
52//   uint32 Type
53//   uint64 LargeSize  # Field is only present if value(Size) == 1.
54//   uint32 VersionAndFlags
55//   uint8[16] SystemId
56//   uint32 DataSize
57//   uint8[DataSize] Data
58// }
59const int kBoxHeaderSize = 8;  // Box's header contains Size and Type.
60const int kBoxLargeSizeSize = 8;
61const int kPsshVersionFlagSize = 4;
62const int kPsshSystemIdSize = 16;
63const int kPsshDataSizeSize = 4;
64const uint32 kTencType = 0x74656e63;
65const uint32 kPsshType = 0x70737368;
66const uint8 kWidevineUuid[16] = {
67    0xED, 0xEF, 0x8B, 0xA9, 0x79, 0xD6, 0x4A, 0xCE,
68    0xA3, 0xC8, 0x27, 0xDC, 0xD5, 0x1D, 0x21, 0xED };
69
70typedef std::vector<uint8> UUID;
71
72class KeySystemUuidManager {
73 public:
74  KeySystemUuidManager();
75  UUID GetUUID(const std::string& key_system);
76  void AddMapping(const std::string& key_system, const UUID& uuid);
77  std::vector<std::string> GetPlatformKeySystemNames();
78
79 private:
80  typedef base::hash_map<std::string, UUID> KeySystemUuidMap;
81
82  KeySystemUuidMap key_system_uuid_map_;
83
84  DISALLOW_COPY_AND_ASSIGN(KeySystemUuidManager);
85};
86
87KeySystemUuidManager::KeySystemUuidManager() {
88  // Widevine is always supported in Android.
89  key_system_uuid_map_[kWidevineKeySystem] =
90      UUID(kWidevineUuid, kWidevineUuid + arraysize(kWidevineUuid));
91}
92
93UUID KeySystemUuidManager::GetUUID(const std::string& key_system) {
94  KeySystemUuidMap::iterator it = key_system_uuid_map_.find(key_system);
95  if (it == key_system_uuid_map_.end())
96    return UUID();
97  return it->second;
98}
99
100void KeySystemUuidManager::AddMapping(const std::string& key_system,
101                                      const UUID& uuid) {
102  KeySystemUuidMap::iterator it = key_system_uuid_map_.find(key_system);
103  DCHECK(it == key_system_uuid_map_.end())
104      << "Shouldn't overwrite an existing key system.";
105  if (it != key_system_uuid_map_.end())
106    return;
107  key_system_uuid_map_[key_system] = uuid;
108}
109
110std::vector<std::string> KeySystemUuidManager::GetPlatformKeySystemNames() {
111  std::vector<std::string> key_systems;
112  for (KeySystemUuidMap::iterator it = key_system_uuid_map_.begin();
113       it != key_system_uuid_map_.end(); ++it) {
114    // Rule out the key system handled by Chrome explicitly.
115    if (it->first != kWidevineKeySystem)
116      key_systems.push_back(it->first);
117  }
118  return key_systems;
119}
120
121base::LazyInstance<KeySystemUuidManager>::Leaky g_key_system_uuid_manager =
122    LAZY_INSTANCE_INITIALIZER;
123
124// Tries to find a PSSH box whose "SystemId" is |uuid| in |data|, parses the
125// "Data" of the box and put it in |pssh_data|. Returns true if such a box is
126// found and successfully parsed. Returns false otherwise.
127// Notes:
128// 1, If multiple PSSH boxes are found,the "Data" of the first matching PSSH box
129// will be set in |pssh_data|.
130// 2, Only PSSH and TENC boxes are allowed in |data|. TENC boxes are skipped.
131static bool GetPsshData(const uint8* data, int data_size,
132                        const UUID& uuid,
133                        std::vector<uint8>* pssh_data) {
134  const uint8* cur = data;
135  const uint8* data_end = data + data_size;
136  int bytes_left = data_size;
137
138  while (bytes_left > 0) {
139    const uint8* box_head = cur;
140
141    if (bytes_left < kBoxHeaderSize)
142      return false;
143
144    uint64_t box_size = ReadUint32(cur);
145    uint32 type = ReadUint32(cur + 4);
146    cur += kBoxHeaderSize;
147    bytes_left -= kBoxHeaderSize;
148
149    if (box_size == 1) {  // LargeSize is present.
150      if (bytes_left < kBoxLargeSizeSize)
151        return false;
152
153      box_size = ReadUint64(cur);
154      cur += kBoxLargeSizeSize;
155      bytes_left -= kBoxLargeSizeSize;
156    } else if (box_size == 0) {
157      box_size = bytes_left + kBoxHeaderSize;
158    }
159
160    const uint8* box_end = box_head + box_size;
161    if (data_end < box_end)
162      return false;
163
164    if (type == kTencType) {
165      // Skip 'tenc' box.
166      cur = box_end;
167      bytes_left = data_end - cur;
168      continue;
169    } else if (type != kPsshType) {
170      return false;
171    }
172
173    const int kPsshBoxMinimumSize =
174        kPsshVersionFlagSize + kPsshSystemIdSize + kPsshDataSizeSize;
175    if (box_end < cur + kPsshBoxMinimumSize)
176      return false;
177
178    uint32 version_and_flags = ReadUint32(cur);
179    cur += kPsshVersionFlagSize;
180    bytes_left -= kPsshVersionFlagSize;
181    if (version_and_flags != 0)
182      return false;
183
184    DCHECK_GE(bytes_left, kPsshSystemIdSize);
185    if (!std::equal(uuid.begin(), uuid.end(), cur)) {
186      cur = box_end;
187      bytes_left = data_end - cur;
188      continue;
189    }
190
191    cur += kPsshSystemIdSize;
192    bytes_left -= kPsshSystemIdSize;
193
194    uint32 data_size = ReadUint32(cur);
195    cur += kPsshDataSizeSize;
196    bytes_left -= kPsshDataSizeSize;
197
198    if (box_end < cur + data_size)
199      return false;
200
201    pssh_data->assign(cur, cur + data_size);
202    return true;
203  }
204
205  return false;
206}
207
208static MediaDrmBridge::SecurityLevel GetSecurityLevelFromString(
209    const std::string& security_level_str) {
210  if (0 == security_level_str.compare("L1"))
211    return MediaDrmBridge::SECURITY_LEVEL_1;
212  if (0 == security_level_str.compare("L3"))
213    return MediaDrmBridge::SECURITY_LEVEL_3;
214  DCHECK(security_level_str.empty());
215  return MediaDrmBridge::SECURITY_LEVEL_NONE;
216}
217
218static std::string GetSecurityLevelString(
219    MediaDrmBridge::SecurityLevel security_level) {
220  switch (security_level) {
221    case MediaDrmBridge::SECURITY_LEVEL_NONE:
222      return "";
223    case MediaDrmBridge::SECURITY_LEVEL_1:
224      return "L1";
225    case MediaDrmBridge::SECURITY_LEVEL_3:
226      return "L3";
227  }
228  return "";
229}
230
231// Checks whether |key_system| is supported with |container_mime_type|. Only
232// checks |key_system| support if |container_mime_type| is empty.
233// TODO(xhwang): The |container_mime_type| is not the same as contentType in
234// the EME spec. Revisit this once the spec issue with initData type is
235// resolved.
236static bool IsKeySystemSupportedWithTypeImpl(
237    const std::string& key_system,
238    const std::string& container_mime_type) {
239  if (!MediaDrmBridge::IsAvailable())
240    return false;
241
242  UUID scheme_uuid = g_key_system_uuid_manager.Get().GetUUID(key_system);
243  if (scheme_uuid.empty())
244    return false;
245
246  JNIEnv* env = AttachCurrentThread();
247  ScopedJavaLocalRef<jbyteArray> j_scheme_uuid =
248      base::android::ToJavaByteArray(env, &scheme_uuid[0], scheme_uuid.size());
249  ScopedJavaLocalRef<jstring> j_container_mime_type =
250      ConvertUTF8ToJavaString(env, container_mime_type);
251  return Java_MediaDrmBridge_isCryptoSchemeSupported(
252      env, j_scheme_uuid.obj(), j_container_mime_type.obj());
253}
254
255// static
256bool MediaDrmBridge::IsAvailable() {
257  return base::android::BuildInfo::GetInstance()->sdk_int() >= 19;
258}
259
260// static
261bool MediaDrmBridge::IsSecureDecoderRequired(SecurityLevel security_level) {
262  DCHECK(IsAvailable());
263  return SECURITY_LEVEL_1 == security_level;
264}
265
266// static
267bool MediaDrmBridge::IsSecurityLevelSupported(const std::string& key_system,
268                                              SecurityLevel security_level) {
269  if (!IsAvailable())
270    return false;
271
272  scoped_ptr<MediaDrmBridge> media_drm_bridge =
273      MediaDrmBridge::CreateSessionless(key_system);
274  if (!media_drm_bridge)
275    return false;
276
277  return media_drm_bridge->SetSecurityLevel(security_level);
278}
279
280static void AddKeySystemUuidMapping(JNIEnv* env, jclass clazz,
281                                    jstring j_key_system,
282                                    jobject j_buffer) {
283  std::string key_system = ConvertJavaStringToUTF8(env, j_key_system);
284  uint8* buffer = static_cast<uint8*>(env->GetDirectBufferAddress(j_buffer));
285  UUID uuid(buffer, buffer + 16);
286  g_key_system_uuid_manager.Get().AddMapping(key_system, uuid);
287}
288
289// static
290std::vector<std::string> MediaDrmBridge::GetPlatformKeySystemNames() {
291  return g_key_system_uuid_manager.Get().GetPlatformKeySystemNames();
292}
293
294// static
295bool MediaDrmBridge::IsKeySystemSupported(const std::string& key_system) {
296  DCHECK(!key_system.empty());
297  return IsKeySystemSupportedWithTypeImpl(key_system, "");
298}
299
300// static
301bool MediaDrmBridge::IsKeySystemSupportedWithType(
302    const std::string& key_system,
303    const std::string& container_mime_type) {
304  DCHECK(!key_system.empty() && !container_mime_type.empty());
305  return IsKeySystemSupportedWithTypeImpl(key_system, container_mime_type);
306}
307
308bool MediaDrmBridge::RegisterMediaDrmBridge(JNIEnv* env) {
309  return RegisterNativesImpl(env);
310}
311
312MediaDrmBridge::MediaDrmBridge(const std::vector<uint8>& scheme_uuid,
313                               const SessionCreatedCB& session_created_cb,
314                               const SessionMessageCB& session_message_cb,
315                               const SessionReadyCB& session_ready_cb,
316                               const SessionClosedCB& session_closed_cb,
317                               const SessionErrorCB& session_error_cb)
318    : scheme_uuid_(scheme_uuid),
319      session_created_cb_(session_created_cb),
320      session_message_cb_(session_message_cb),
321      session_ready_cb_(session_ready_cb),
322      session_closed_cb_(session_closed_cb),
323      session_error_cb_(session_error_cb) {
324  JNIEnv* env = AttachCurrentThread();
325  CHECK(env);
326
327  ScopedJavaLocalRef<jbyteArray> j_scheme_uuid =
328      base::android::ToJavaByteArray(env, &scheme_uuid[0], scheme_uuid.size());
329  j_media_drm_.Reset(Java_MediaDrmBridge_create(
330      env, j_scheme_uuid.obj(), reinterpret_cast<intptr_t>(this)));
331}
332
333MediaDrmBridge::~MediaDrmBridge() {
334  JNIEnv* env = AttachCurrentThread();
335  player_tracker_.NotifyCdmUnset();
336  if (!j_media_drm_.is_null())
337    Java_MediaDrmBridge_release(env, j_media_drm_.obj());
338}
339
340// static
341scoped_ptr<MediaDrmBridge> MediaDrmBridge::Create(
342    const std::string& key_system,
343    const SessionCreatedCB& session_created_cb,
344    const SessionMessageCB& session_message_cb,
345    const SessionReadyCB& session_ready_cb,
346    const SessionClosedCB& session_closed_cb,
347    const SessionErrorCB& session_error_cb) {
348  scoped_ptr<MediaDrmBridge> media_drm_bridge;
349  if (!IsAvailable())
350    return media_drm_bridge.Pass();
351
352  UUID scheme_uuid = g_key_system_uuid_manager.Get().GetUUID(key_system);
353  if (scheme_uuid.empty())
354    return media_drm_bridge.Pass();
355
356  media_drm_bridge.reset(new MediaDrmBridge(scheme_uuid,
357                                            session_created_cb,
358                                            session_message_cb,
359                                            session_ready_cb,
360                                            session_closed_cb,
361                                            session_error_cb));
362
363  if (media_drm_bridge->j_media_drm_.is_null())
364    media_drm_bridge.reset();
365
366  return media_drm_bridge.Pass();
367}
368
369// static
370scoped_ptr<MediaDrmBridge> MediaDrmBridge::CreateSessionless(
371    const std::string& key_system) {
372  return MediaDrmBridge::Create(key_system,
373                                SessionCreatedCB(),
374                                SessionMessageCB(),
375                                SessionReadyCB(),
376                                SessionClosedCB(),
377                                SessionErrorCB());
378}
379
380bool MediaDrmBridge::SetSecurityLevel(SecurityLevel security_level) {
381  JNIEnv* env = AttachCurrentThread();
382
383  std::string security_level_str = GetSecurityLevelString(security_level);
384  if (security_level_str.empty())
385    return false;
386
387  ScopedJavaLocalRef<jstring> j_security_level =
388      ConvertUTF8ToJavaString(env, security_level_str);
389  return Java_MediaDrmBridge_setSecurityLevel(
390      env, j_media_drm_.obj(), j_security_level.obj());
391}
392
393bool MediaDrmBridge::CreateSession(uint32 session_id,
394                                   const std::string& content_type,
395                                   const uint8* init_data,
396                                   int init_data_length) {
397  DVLOG(1) << __FUNCTION__;
398
399  DCHECK(!session_created_cb_.is_null())
400      << "CreateSession called on a sessionless MediaDrmBridge object.";
401
402  JNIEnv* env = AttachCurrentThread();
403  ScopedJavaLocalRef<jbyteArray> j_init_data;
404  // Caller should always use "video/*" content types.
405  DCHECK_EQ(0u, content_type.find("video/"));
406
407  // Widevine MediaDrm plugin only accepts the "data" part of the PSSH box as
408  // the init data when using MP4 container.
409  if (std::equal(scheme_uuid_.begin(), scheme_uuid_.end(), kWidevineUuid) &&
410      content_type == "video/mp4") {
411    std::vector<uint8> pssh_data;
412    if (!GetPsshData(init_data, init_data_length, scheme_uuid_, &pssh_data))
413      return false;
414    j_init_data =
415        base::android::ToJavaByteArray(env, &pssh_data[0], pssh_data.size());
416  } else {
417    j_init_data =
418        base::android::ToJavaByteArray(env, init_data, init_data_length);
419  }
420
421  ScopedJavaLocalRef<jstring> j_mime =
422      ConvertUTF8ToJavaString(env, content_type);
423  Java_MediaDrmBridge_createSession(
424      env, j_media_drm_.obj(), session_id, j_init_data.obj(), j_mime.obj());
425  return true;
426}
427
428void MediaDrmBridge::LoadSession(uint32 session_id,
429                                 const std::string& web_session_id) {
430  // MediaDrmBridge doesn't support loading sessions.
431  NOTREACHED();
432}
433
434void MediaDrmBridge::UpdateSession(uint32 session_id,
435                                   const uint8* response,
436                                   int response_length) {
437  DVLOG(1) << __FUNCTION__;
438
439  DCHECK(!session_ready_cb_.is_null())
440      << __FUNCTION__ << " called on a sessionless MediaDrmBridge object.";
441
442  JNIEnv* env = AttachCurrentThread();
443  ScopedJavaLocalRef<jbyteArray> j_response =
444      base::android::ToJavaByteArray(env, response, response_length);
445  Java_MediaDrmBridge_updateSession(
446      env, j_media_drm_.obj(), session_id, j_response.obj());
447}
448
449void MediaDrmBridge::ReleaseSession(uint32 session_id) {
450  DVLOG(1) << __FUNCTION__;
451
452  DCHECK(!session_closed_cb_.is_null())
453      << __FUNCTION__ << " called on a sessionless MediaDrmBridge object.";
454
455  JNIEnv* env = AttachCurrentThread();
456  Java_MediaDrmBridge_releaseSession(env, j_media_drm_.obj(), session_id);
457}
458
459int MediaDrmBridge::RegisterPlayer(const base::Closure& new_key_cb,
460                                   const base::Closure& cdm_unset_cb) {
461  return player_tracker_.RegisterPlayer(new_key_cb, cdm_unset_cb);
462}
463
464void MediaDrmBridge::UnregisterPlayer(int registration_id) {
465  player_tracker_.UnregisterPlayer(registration_id);
466}
467
468void MediaDrmBridge::SetMediaCryptoReadyCB(const base::Closure& closure) {
469  if (closure.is_null()) {
470    media_crypto_ready_cb_.Reset();
471    return;
472  }
473
474  DCHECK(media_crypto_ready_cb_.is_null());
475
476  if (!GetMediaCrypto().is_null()) {
477    base::MessageLoopProxy::current()->PostTask(FROM_HERE, closure);
478    return;
479  }
480
481  media_crypto_ready_cb_ = closure;
482}
483
484void MediaDrmBridge::OnMediaCryptoReady(JNIEnv* env, jobject) {
485  DCHECK(!GetMediaCrypto().is_null());
486  if (!media_crypto_ready_cb_.is_null())
487    base::ResetAndReturn(&media_crypto_ready_cb_).Run();
488}
489
490void MediaDrmBridge::OnSessionCreated(JNIEnv* env,
491                                      jobject j_media_drm,
492                                      jint j_session_id,
493                                      jstring j_web_session_id) {
494  uint32 session_id = j_session_id;
495  std::string web_session_id = ConvertJavaStringToUTF8(env, j_web_session_id);
496  session_created_cb_.Run(session_id, web_session_id);
497}
498
499void MediaDrmBridge::OnSessionMessage(JNIEnv* env,
500                                      jobject j_media_drm,
501                                      jint j_session_id,
502                                      jbyteArray j_message,
503                                      jstring j_destination_url) {
504  uint32 session_id = j_session_id;
505  std::vector<uint8> message;
506  JavaByteArrayToByteVector(env, j_message, &message);
507  GURL destination_gurl = GURL(ConvertJavaStringToUTF8(env, j_destination_url));
508  if (!destination_gurl.is_valid() && !destination_gurl.is_empty()) {
509    DLOG(WARNING) << "SessionMessage destination_url is invalid : "
510                  << destination_gurl.possibly_invalid_spec();
511    destination_gurl = GURL::EmptyGURL();  // Replace invalid destination_url.
512  }
513  session_message_cb_.Run(session_id, message, destination_gurl);
514}
515
516void MediaDrmBridge::OnSessionReady(JNIEnv* env,
517                                    jobject j_media_drm,
518                                    jint j_session_id) {
519  uint32 session_id = j_session_id;
520  session_ready_cb_.Run(session_id);
521  // TODO(xhwang/jrummell): Move this when usableKeyIds/keyschange are
522  // implemented.
523  player_tracker_.NotifyNewKey();
524}
525
526void MediaDrmBridge::OnSessionClosed(JNIEnv* env,
527                                     jobject j_media_drm,
528                                     jint j_session_id) {
529  uint32 session_id = j_session_id;
530  session_closed_cb_.Run(session_id);
531}
532
533void MediaDrmBridge::OnSessionError(JNIEnv* env,
534                                    jobject j_media_drm,
535                                    jint j_session_id) {
536  uint32 session_id = j_session_id;
537  session_error_cb_.Run(session_id, MediaKeys::kUnknownError, 0);
538}
539
540ScopedJavaLocalRef<jobject> MediaDrmBridge::GetMediaCrypto() {
541  JNIEnv* env = AttachCurrentThread();
542  return Java_MediaDrmBridge_getMediaCrypto(env, j_media_drm_.obj());
543}
544
545MediaDrmBridge::SecurityLevel MediaDrmBridge::GetSecurityLevel() {
546  JNIEnv* env = AttachCurrentThread();
547  ScopedJavaLocalRef<jstring> j_security_level =
548      Java_MediaDrmBridge_getSecurityLevel(env, j_media_drm_.obj());
549  std::string security_level_str =
550      ConvertJavaStringToUTF8(env, j_security_level.obj());
551  return GetSecurityLevelFromString(security_level_str);
552}
553
554bool MediaDrmBridge::IsProtectedSurfaceRequired() {
555  return IsSecureDecoderRequired(GetSecurityLevel());
556}
557
558void MediaDrmBridge::ResetDeviceCredentials(
559    const ResetCredentialsCB& callback) {
560  DCHECK(reset_credentials_cb_.is_null());
561  reset_credentials_cb_ = callback;
562  JNIEnv* env = AttachCurrentThread();
563  Java_MediaDrmBridge_resetDeviceCredentials(env, j_media_drm_.obj());
564}
565
566void MediaDrmBridge::OnResetDeviceCredentialsCompleted(
567    JNIEnv* env, jobject, bool success) {
568  base::ResetAndReturn(&reset_credentials_cb_).Run(success);
569}
570
571}  // namespace media
572