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