1 // Copyright 2017 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 package org.chromium.media; 6 7 import android.media.MediaDrm; 8 9 import org.chromium.base.ApiCompatibilityUtils; 10 import org.chromium.base.Callback; 11 import org.chromium.media.MediaDrmStorageBridge.PersistentInfo; 12 13 import java.nio.ByteBuffer; 14 import java.util.ArrayList; 15 import java.util.Arrays; 16 import java.util.HashMap; 17 import java.util.List; 18 import java.util.UUID; 19 20 /** 21 * The class manages relations among eme session ID, drm session ID and keyset 22 * ID. It also records the associated session information. 23 * 24 * For temporary session, it simply maintains the in memory map from session ID 25 * to related informations. When session is closed, the mapping is also removed. 26 * 27 * For persistent session, it also talks to persistent storage when loading 28 * information back to memory and updating changes to disk. 29 */ 30 class MediaDrmSessionManager { 31 /** 32 * The class groups drm session ID, eme session ID and key set ID. It hides 33 * the conversion among the three different IDs. 34 */ 35 static class SessionId { 36 private static final char[] HEX_CHAR_LOOKUP = "0123456789ABCDEF".toCharArray(); 37 38 // ID used by browser and javascript to identify the session. It's 39 // the unique ID in EME world and also used as ID for this class. 40 // For temporary session, eme ID should match drm ID. For persistent 41 // session, EME ID is a random generated string because the persistent 42 // ID (key set ID) is generated much later than eme ID. 43 private final byte[] mEmeId; 44 45 // Temporary ID used by MediaDrm session, returned by 46 // MediaDrm.openSession. 47 private byte[] mDrmId; 48 49 // Persistent ID used by MediaDrm to identify persistent licenses, 50 // returned by MediaDrm.provideKeyResponse. 51 private byte[] mKeySetId; 52 53 /** 54 * Convert byte array to hex string for logging. 55 * This is modified from BytesToHexString() in url/url_canon_unittest.cc. 56 */ toHexString(byte[] bytes)57 static String toHexString(byte[] bytes) { 58 StringBuilder hexString = new StringBuilder(); 59 for (int i = 0; i < bytes.length; ++i) { 60 hexString.append(HEX_CHAR_LOOKUP[bytes[i] >>> 4]); 61 hexString.append(HEX_CHAR_LOOKUP[bytes[i] & 0xf]); 62 } 63 return hexString.toString(); 64 } 65 66 /** 67 * Create session ID with random generated (UUID.randomUUID) EME session ID. 68 * The ID must be unique within the origin of this object's Document over time, 69 * including across Documents and browsing sessions. 70 * https://w3c.github.io/encrypted-media/#dom-mediakeysession-generaterequest 71 * 72 * @param drmId Raw DRM ID created by MediaDrm. 73 * @return Session ID with random generated EME session ID. 74 */ createPersistentSessionId(byte[] drmId)75 static SessionId createPersistentSessionId(byte[] drmId) { 76 byte[] emeId = ApiCompatibilityUtils.getBytesUtf8( 77 UUID.randomUUID().toString().replace('-', '0')); 78 return new SessionId(emeId, drmId, null /* keySetId */); 79 } 80 81 /** 82 * Create session ID for temporary license session. The DRM session ID is 83 * used as EME session ID. 84 * 85 * @param drmIdAsEmeId Raw DRM session ID created by MediaDrm. 86 * @return Session ID with DRM session ID as EME session ID. 87 */ createTemporarySessionId(byte[] drmId)88 static SessionId createTemporarySessionId(byte[] drmId) { 89 return new SessionId(drmId, drmId, null /* keySetId */); 90 } 91 92 /** 93 * Create session ID used to report session doesn't exist. 94 */ createNoExistSessionId()95 static SessionId createNoExistSessionId() { 96 return createTemporarySessionId(new byte[0]); 97 } 98 SessionId(byte[] emeId, byte[] drmId, byte[] keySetId)99 private SessionId(byte[] emeId, byte[] drmId, byte[] keySetId) { 100 assert emeId != null; 101 assert drmId != null || keySetId != null; 102 103 mEmeId = emeId; 104 mDrmId = drmId; 105 mKeySetId = keySetId; 106 } 107 drmId()108 byte[] drmId() { 109 return mDrmId; 110 } 111 emeId()112 byte[] emeId() { 113 return mEmeId; 114 } 115 keySetId()116 byte[] keySetId() { 117 return mKeySetId; 118 } 119 setKeySetId(byte[] keySetId)120 private void setKeySetId(byte[] keySetId) { 121 mKeySetId = keySetId; 122 } 123 setDrmId(byte[] drmId)124 private void setDrmId(byte[] drmId) { 125 mDrmId = drmId; 126 } 127 isEqual(SessionId that)128 boolean isEqual(SessionId that) { 129 return Arrays.equals(mEmeId, that.emeId()); 130 } 131 toHexString()132 String toHexString() { 133 return toHexString(mEmeId); 134 } 135 } 136 137 static class SessionInfo { 138 private final SessionId mSessionId; 139 private final String mMimeType; 140 141 // Key type of license in the session. It should be one of 142 // MediaDrm.KEY_TYPE_XXX. 143 private int mKeyType; 144 SessionInfo(SessionId sessionId, String mimeType, int keyType)145 private SessionInfo(SessionId sessionId, String mimeType, int keyType) { 146 assert sessionId != null; 147 assert mimeType != null && !mimeType.isEmpty(); 148 149 mSessionId = sessionId; 150 mMimeType = mimeType; 151 mKeyType = keyType; 152 } 153 mimeType()154 String mimeType() { 155 return mMimeType; 156 } 157 keyType()158 int keyType() { 159 return mKeyType; 160 } 161 162 // Private methods that are visible in this file only. 163 sessionId()164 private SessionId sessionId() { 165 return mSessionId; 166 } 167 setKeyType(int keyType)168 private void setKeyType(int keyType) { 169 mKeyType = keyType; 170 } 171 toPersistentInfo()172 private PersistentInfo toPersistentInfo() { 173 assert mSessionId.keySetId() != null; 174 175 return new PersistentInfo( 176 mSessionId.emeId(), mSessionId.keySetId(), mMimeType, mKeyType); 177 } 178 fromPersistentInfo(PersistentInfo persistentInfo)179 private static SessionInfo fromPersistentInfo(PersistentInfo persistentInfo) { 180 assert persistentInfo != null; 181 assert persistentInfo.emeId() != null; 182 assert persistentInfo.keySetId() != null; 183 184 SessionId sessionId = new SessionId( 185 persistentInfo.emeId(), null /* drmId */, persistentInfo.keySetId()); 186 return new SessionInfo(sessionId, persistentInfo.mimeType(), 187 getKeyTypeFromPersistentInfo(persistentInfo)); 188 } 189 getKeyTypeFromPersistentInfo(PersistentInfo persistentInfo)190 private static int getKeyTypeFromPersistentInfo(PersistentInfo persistentInfo) { 191 int keyType = persistentInfo.keyType(); 192 if (keyType == MediaDrm.KEY_TYPE_OFFLINE || keyType == MediaDrm.KEY_TYPE_RELEASE) { 193 return keyType; 194 } 195 196 // Key type is missing. Use OFFLINE by default. 197 return MediaDrm.KEY_TYPE_OFFLINE; 198 } 199 } 200 201 // Maps from DRM/EME session ID to SessionInfo. SessionInfo contains 202 // SessionId, so that we can: 203 // 1. Get SessionInfo with EME/DRM session ID. 204 // 2. Get SessionId from EME/DRM session ID. 205 // 3. Get EME/DRM session ID from DRM/EME session ID. 206 // SessionId always has a valid EME session ID, so all opened session should 207 // have an entry in mEmeSessionInfoMap. 208 private HashMap<ByteBuffer, SessionInfo> mEmeSessionInfoMap; 209 private HashMap<ByteBuffer, SessionInfo> mDrmSessionInfoMap; 210 211 // The persistent storage to record map from EME session ID to key set ID 212 // for persistent license. 213 private MediaDrmStorageBridge mStorage; 214 MediaDrmSessionManager(MediaDrmStorageBridge storage)215 public MediaDrmSessionManager(MediaDrmStorageBridge storage) { 216 mEmeSessionInfoMap = new HashMap<>(); 217 mDrmSessionInfoMap = new HashMap<>(); 218 219 mStorage = storage; 220 } 221 222 /** 223 * Set drm ID. It should only be called for persistent license session 224 * without an opened drm session. 225 */ setDrmId(SessionId sessionId, byte[] drmId)226 void setDrmId(SessionId sessionId, byte[] drmId) { 227 SessionInfo info = get(sessionId); 228 229 assert info != null; 230 assert info.sessionId().isEqual(sessionId); 231 232 sessionId.setDrmId(drmId); 233 mDrmSessionInfoMap.put(ByteBuffer.wrap(drmId), info); 234 } 235 236 /** 237 * Set key set ID. It should only be called for persistent license session. 238 */ setKeySetId(SessionId sessionId, byte[] keySetId, Callback<Boolean> callback)239 void setKeySetId(SessionId sessionId, byte[] keySetId, Callback<Boolean> callback) { 240 assert get(sessionId) != null; 241 assert get(sessionId).keyType() == MediaDrm.KEY_TYPE_OFFLINE; 242 assert sessionId.keySetId() == null; 243 244 sessionId.setKeySetId(keySetId); 245 246 mStorage.saveInfo(get(sessionId).toPersistentInfo(), callback); 247 } 248 249 /** 250 * Mark key as released. It should only be called for persistent license 251 * session. 252 */ setKeyType(SessionId sessionId, int keyType, Callback<Boolean> callback)253 void setKeyType(SessionId sessionId, int keyType, Callback<Boolean> callback) { 254 SessionInfo info = get(sessionId); 255 256 assert info != null; 257 258 info.setKeyType(keyType); 259 mStorage.saveInfo(info.toPersistentInfo(), callback); 260 } 261 262 /** 263 * Load |emeId|'s session data from persistent storage. 264 */ load(byte[] emeId, final Callback<SessionId> callback)265 void load(byte[] emeId, final Callback<SessionId> callback) { 266 mStorage.loadInfo(emeId, new Callback<PersistentInfo>() { 267 @Override 268 public void onResult(PersistentInfo persistentInfo) { 269 if (persistentInfo == null) { 270 callback.onResult(null); 271 return; 272 } 273 274 // Loading same persistent license into different sessions isn't 275 // supported. 276 assert getSessionIdByEmeId(persistentInfo.emeId()) == null; 277 278 SessionInfo info = SessionInfo.fromPersistentInfo(persistentInfo); 279 mEmeSessionInfoMap.put(ByteBuffer.wrap(persistentInfo.emeId()), info); 280 callback.onResult(info.sessionId()); 281 } 282 }); 283 } 284 285 /** 286 * Remove persistent license info from persistent storage. 287 */ clearPersistentSessionInfo(SessionId sessionId, Callback<Boolean> callback)288 void clearPersistentSessionInfo(SessionId sessionId, Callback<Boolean> callback) { 289 sessionId.setKeySetId(null); 290 mStorage.clearInfo(sessionId.emeId(), callback); 291 } 292 293 /** 294 * Remove session and related infomration from memory, but doesn't touch 295 * persistent storage. 296 */ remove(SessionId sessionId)297 void remove(SessionId sessionId) { 298 SessionInfo info = get(sessionId); 299 300 assert info != null; 301 assert sessionId.isEqual(info.sessionId()); 302 303 mEmeSessionInfoMap.remove(ByteBuffer.wrap(sessionId.emeId())); 304 if (sessionId.drmId() != null) { 305 mDrmSessionInfoMap.remove(ByteBuffer.wrap(sessionId.drmId())); 306 } 307 } 308 getAllSessionIds()309 List<SessionId> getAllSessionIds() { 310 ArrayList<SessionId> sessionIds = new ArrayList<>(); 311 for (SessionInfo info : mEmeSessionInfoMap.values()) { 312 sessionIds.add(info.sessionId()); 313 } 314 315 return sessionIds; 316 } 317 get(SessionId sessionId)318 SessionInfo get(SessionId sessionId) { 319 return mEmeSessionInfoMap.get(ByteBuffer.wrap(sessionId.emeId())); 320 } 321 put(SessionId id, String mimeType, int keyType)322 void put(SessionId id, String mimeType, int keyType) { 323 SessionInfo info = new SessionInfo(id, mimeType, keyType); 324 mEmeSessionInfoMap.put(ByteBuffer.wrap(id.emeId()), info); 325 326 if (id.drmId() != null) { 327 mDrmSessionInfoMap.put(ByteBuffer.wrap(id.drmId()), info); 328 } 329 } 330 getSessionIdByEmeId(byte[] emeId)331 SessionId getSessionIdByEmeId(byte[] emeId) { 332 return getSessionIdFromMap(mEmeSessionInfoMap, emeId); 333 } 334 getSessionIdByDrmId(byte[] drmId)335 SessionId getSessionIdByDrmId(byte[] drmId) { 336 return getSessionIdFromMap(mDrmSessionInfoMap, drmId); 337 } 338 339 // Private methods 340 getSessionIdFromMap(HashMap<ByteBuffer, SessionInfo> map, byte[] id)341 private SessionId getSessionIdFromMap(HashMap<ByteBuffer, SessionInfo> map, byte[] id) { 342 SessionInfo info = map.get(ByteBuffer.wrap(id)); 343 if (info == null) { 344 return null; 345 } 346 347 return info.sessionId(); 348 } 349 } 350