1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.media;
18 
19 import android.annotation.NonNull;
20 import android.annotation.TestApi;
21 import android.hardware.cas.IDescrambler;
22 import android.hardware.cas.ScramblingControl;
23 import android.hardware.cas.V1_0.IDescramblerBase;
24 import android.media.MediaCasException.UnsupportedCasException;
25 import android.os.IHwBinder;
26 import android.os.RemoteException;
27 import android.os.ServiceSpecificException;
28 import android.util.Log;
29 
30 import java.nio.ByteBuffer;
31 import java.util.ArrayList;
32 
33 /**
34  * MediaDescrambler class can be used in conjunction with {@link android.media.MediaCodec}
35  * and {@link android.media.MediaExtractor} to decode media data scrambled by conditional
36  * access (CA) systems such as those in the ISO/IEC13818-1.
37  *
38  * A MediaDescrambler object is initialized from a session opened by a MediaCas object,
39  * and can be used to descramble media streams scrambled with that session's keys.
40  *
41  * Scrambling schemes are identified by 16-bit unsigned integer as in CA_system_id.
42  *
43  */
44 public final class MediaDescrambler implements AutoCloseable {
45     private static final String TAG = "MediaDescrambler";
46     private DescramblerWrapper mIDescrambler;
47     private boolean mIsAidlHal;
48 
49     private interface DescramblerWrapper {
50 
asBinder()51         IHwBinder asBinder();
52 
descramble( @onNull ByteBuffer srcBuf, @NonNull ByteBuffer dstBuf, @NonNull MediaCodec.CryptoInfo cryptoInfo)53         int descramble(
54                 @NonNull ByteBuffer srcBuf,
55                 @NonNull ByteBuffer dstBuf,
56                 @NonNull MediaCodec.CryptoInfo cryptoInfo)
57                 throws RemoteException;
58 
requiresSecureDecoderComponent(@onNull String mime)59         boolean requiresSecureDecoderComponent(@NonNull String mime) throws RemoteException;
60 
setMediaCasSession(byte[] sessionId)61         void setMediaCasSession(byte[] sessionId) throws RemoteException;
62 
release()63         void release() throws RemoteException;
64     };
65 
66     private class AidlDescrambler implements DescramblerWrapper {
67 
68         IDescrambler mAidlDescrambler;
69 
AidlDescrambler(IDescrambler aidlDescrambler)70         AidlDescrambler(IDescrambler aidlDescrambler) throws Exception {
71             if (aidlDescrambler != null) {
72                 mAidlDescrambler = aidlDescrambler;
73             } else {
74                 throw new Exception("Descrambler could not be created");
75             }
76         }
77 
78         @Override
asBinder()79         public IHwBinder asBinder() {
80             return null;
81         }
82 
83         @Override
descramble( @onNull ByteBuffer src, @NonNull ByteBuffer dst, @NonNull MediaCodec.CryptoInfo cryptoInfo)84         public int descramble(
85                 @NonNull ByteBuffer src,
86                 @NonNull ByteBuffer dst,
87                 @NonNull MediaCodec.CryptoInfo cryptoInfo)
88                 throws RemoteException {
89             throw new RemoteException("Not supported");
90         }
91 
92         @Override
requiresSecureDecoderComponent(@onNull String mime)93         public boolean requiresSecureDecoderComponent(@NonNull String mime) throws RemoteException {
94             throw new RemoteException("Not supported");
95         }
96 
97         @Override
setMediaCasSession(byte[] sessionId)98         public void setMediaCasSession(byte[] sessionId) throws RemoteException {
99             throw new RemoteException("Not supported");
100         }
101 
102         @Override
release()103         public void release() throws RemoteException {
104             mAidlDescrambler.release();
105         }
106     }
107 
108     private class HidlDescrambler implements DescramblerWrapper {
109 
110         IDescramblerBase mHidlDescrambler;
111 
HidlDescrambler(IDescramblerBase hidlDescrambler)112         HidlDescrambler(IDescramblerBase hidlDescrambler) throws Exception {
113             if (hidlDescrambler != null) {
114                 mHidlDescrambler = hidlDescrambler;
115                 native_setup(hidlDescrambler.asBinder());
116             } else {
117                 throw new Exception("Descrambler could not be created");
118             }
119         }
120 
121         @Override
asBinder()122         public IHwBinder asBinder() {
123             return mHidlDescrambler.asBinder();
124         }
125 
126         @Override
descramble( @onNull ByteBuffer srcBuf, @NonNull ByteBuffer dstBuf, @NonNull MediaCodec.CryptoInfo cryptoInfo)127         public int descramble(
128                 @NonNull ByteBuffer srcBuf,
129                 @NonNull ByteBuffer dstBuf,
130                 @NonNull MediaCodec.CryptoInfo cryptoInfo)
131                 throws RemoteException {
132 
133             try {
134                 return native_descramble(
135                         cryptoInfo.key[0],
136                         cryptoInfo.key[1],
137                         cryptoInfo.numSubSamples,
138                         cryptoInfo.numBytesOfClearData,
139                         cryptoInfo.numBytesOfEncryptedData,
140                         srcBuf,
141                         srcBuf.position(),
142                         srcBuf.limit(),
143                         dstBuf,
144                         dstBuf.position(),
145                         dstBuf.limit());
146             } catch (ServiceSpecificException e) {
147                 MediaCasStateException.throwExceptionIfNeeded(e.errorCode, e.getMessage());
148             } catch (RemoteException e) {
149                 cleanupAndRethrowIllegalState();
150             }
151             return -1;
152         }
153 
154         @Override
requiresSecureDecoderComponent(@onNull String mime)155         public boolean requiresSecureDecoderComponent(@NonNull String mime) throws RemoteException {
156             return mHidlDescrambler.requiresSecureDecoderComponent(mime);
157         }
158 
159         @Override
setMediaCasSession(byte[] sessionId)160         public void setMediaCasSession(byte[] sessionId) throws RemoteException {
161             ArrayList<Byte> byteArray = new ArrayList<>();
162 
163             if (sessionId != null) {
164                 int length = sessionId.length;
165                 byteArray = new ArrayList<Byte>(length);
166                 for (int i = 0; i < length; i++) {
167                     byteArray.add(Byte.valueOf(sessionId[i]));
168                 }
169             }
170 
171             MediaCasStateException.throwExceptionIfNeeded(
172                     mHidlDescrambler.setMediaCasSession(byteArray));
173         }
174 
175         @Override
release()176         public void release() throws RemoteException {
177             mHidlDescrambler.release();
178             native_release();
179         }
180     }
181 
validateInternalStates()182     private final void validateInternalStates() {
183         if (mIDescrambler == null) {
184             throw new IllegalStateException();
185         }
186     }
187 
cleanupAndRethrowIllegalState()188     private final void cleanupAndRethrowIllegalState() {
189         mIDescrambler = null;
190         throw new IllegalStateException();
191     }
192 
193     /**
194      * Instantiate a MediaDescrambler.
195      *
196      * @param CA_system_id The system id of the scrambling scheme.
197      *
198      * @throws UnsupportedCasException if the scrambling scheme is not supported.
199      */
MediaDescrambler(int CA_system_id)200     public MediaDescrambler(int CA_system_id) throws UnsupportedCasException {
201         try {
202             if (MediaCas.getService() != null) {
203                 mIDescrambler =
204                         new AidlDescrambler(MediaCas.getService().createDescrambler(CA_system_id));
205                 mIsAidlHal = true;
206             } else if (MediaCas.getServiceHidl() != null) {
207                 mIDescrambler =
208                         new HidlDescrambler(
209                                 MediaCas.getServiceHidl().createDescrambler(CA_system_id));
210                 mIsAidlHal = false;
211             } else {
212                 throw new Exception("No CAS service found!");
213             }
214         } catch(Exception e) {
215             Log.e(TAG, "Failed to create descrambler: " + e);
216             mIDescrambler = null;
217         } finally {
218             if (mIDescrambler == null) {
219                 throw new UnsupportedCasException("Unsupported CA_system_id " + CA_system_id);
220             }
221         }
222     }
223 
224     /**
225      * Check if the underlying HAL is AIDL. For CTS testing purpose.
226      *
227      * @hide
228      */
229     @TestApi
isAidlHal()230     public boolean isAidlHal() {
231         return mIsAidlHal;
232     }
233 
getBinder()234     IHwBinder getBinder() {
235         validateInternalStates();
236 
237         return mIDescrambler.asBinder();
238     }
239 
240     /**
241      * Query if the scrambling scheme requires the use of a secure decoder
242      * to decode data of the given mime type.
243      *
244      * @param mime The mime type of the media data
245      *
246      * @throws IllegalStateException if the descrambler instance is not valid.
247      */
requiresSecureDecoderComponent(@onNull String mime)248     public final boolean requiresSecureDecoderComponent(@NonNull String mime) {
249         validateInternalStates();
250 
251         try {
252             return mIDescrambler.requiresSecureDecoderComponent(mime);
253         } catch (RemoteException e) {
254             cleanupAndRethrowIllegalState();
255         }
256         return true;
257     }
258 
259     /**
260      * Associate a MediaCas session with this MediaDescrambler instance.
261      * The MediaCas session is used to securely load decryption keys for
262      * the descrambler. The crypto keys loaded through the MediaCas session
263      * may be selected for use during the descrambling operation performed
264      * by {@link android.media.MediaExtractor or @link
265      * android.media.MediaCodec#queueSecureInputBuffer} by specifying even
266      * or odd key in the {@link android.media.MediaCodec.CryptoInfo#key} field.
267      *
268      * @param session the MediaCas session to associate with this
269      * MediaDescrambler instance.
270      *
271      * @throws IllegalStateException if the descrambler instance is not valid.
272      * @throws MediaCasStateException for CAS-specific state exceptions.
273      */
setMediaCasSession(@onNull MediaCas.Session session)274     public final void setMediaCasSession(@NonNull MediaCas.Session session) {
275         validateInternalStates();
276 
277         try {
278             mIDescrambler.setMediaCasSession(session.mSessionId);
279         } catch (RemoteException e) {
280             cleanupAndRethrowIllegalState();
281         }
282     }
283 
284     /**
285      * Scramble control value indicating that the samples are not scrambled.
286      *
287      * @see #descramble(ByteBuffer, ByteBuffer, android.media.MediaCodec.CryptoInfo)
288      */
289     public static final byte SCRAMBLE_CONTROL_UNSCRAMBLED = (byte) ScramblingControl.UNSCRAMBLED;
290 
291     /**
292      * Scramble control value reserved and shouldn't be used currently.
293      *
294      * @see #descramble(ByteBuffer, ByteBuffer, android.media.MediaCodec.CryptoInfo)
295      */
296     public static final byte SCRAMBLE_CONTROL_RESERVED = (byte) ScramblingControl.RESERVED;
297 
298     /**
299      * Scramble control value indicating that the even key is used.
300      *
301      * @see #descramble(ByteBuffer, ByteBuffer, android.media.MediaCodec.CryptoInfo)
302      */
303     public static final byte SCRAMBLE_CONTROL_EVEN_KEY = (byte) ScramblingControl.EVENKEY;
304 
305     /**
306      * Scramble control value indicating that the odd key is used.
307      *
308      * @see #descramble(ByteBuffer, ByteBuffer, android.media.MediaCodec.CryptoInfo)
309      */
310     public static final byte SCRAMBLE_CONTROL_ODD_KEY = (byte) ScramblingControl.ODDKEY;
311 
312     /**
313      * Scramble flag for a hint indicating that the descrambling request is for
314      * retrieving the PES header info only.
315      *
316      * @see #descramble(ByteBuffer, ByteBuffer, android.media.MediaCodec.CryptoInfo)
317      */
318     public static final byte SCRAMBLE_FLAG_PES_HEADER = (1 << 0);
319 
320     /**
321      * Descramble a ByteBuffer of data described by a
322      * {@link android.media.MediaCodec.CryptoInfo} structure.
323      *
324      * @param srcBuf ByteBuffer containing the scrambled data, which starts at
325      * srcBuf.position().
326      * @param dstBuf ByteBuffer to hold the descrambled data, which starts at
327      * dstBuf.position().
328      * @param cryptoInfo a {@link android.media.MediaCodec.CryptoInfo} structure
329      * describing the subsamples contained in srcBuf. The iv and mode fields in
330      * CryptoInfo are not used. key[0] contains the MPEG2TS scrambling control bits
331      * (as defined in ETSI TS 100 289 (2011): "Digital Video Broadcasting (DVB);
332      * Support for use of the DVB Scrambling Algorithm version 3 within digital
333      * broadcasting systems"), and the value must be one of {@link #SCRAMBLE_CONTROL_UNSCRAMBLED},
334      * {@link #SCRAMBLE_CONTROL_RESERVED}, {@link #SCRAMBLE_CONTROL_EVEN_KEY} or
335      * {@link #SCRAMBLE_CONTROL_ODD_KEY}. key[1] is a set of bit flags, with the
336      * only possible bit being {@link #SCRAMBLE_FLAG_PES_HEADER} currently.
337      * key[2~15] are not used.
338      *
339      * @return number of bytes that have been successfully descrambled, with negative
340      * values indicating errors.
341      *
342      * @throws IllegalStateException if the descrambler instance is not valid.
343      * @throws MediaCasStateException for CAS-specific state exceptions.
344      */
descramble( @onNull ByteBuffer srcBuf, @NonNull ByteBuffer dstBuf, @NonNull MediaCodec.CryptoInfo cryptoInfo)345     public final int descramble(
346             @NonNull ByteBuffer srcBuf, @NonNull ByteBuffer dstBuf,
347             @NonNull MediaCodec.CryptoInfo cryptoInfo) {
348         validateInternalStates();
349 
350         if (cryptoInfo.numSubSamples <= 0) {
351             throw new IllegalArgumentException(
352                     "Invalid CryptoInfo: invalid numSubSamples=" + cryptoInfo.numSubSamples);
353         } else if (cryptoInfo.numBytesOfClearData == null
354                 && cryptoInfo.numBytesOfEncryptedData == null) {
355             throw new IllegalArgumentException(
356                     "Invalid CryptoInfo: clearData and encryptedData size arrays are both null!");
357         } else if (cryptoInfo.numBytesOfClearData != null
358                 && cryptoInfo.numBytesOfClearData.length < cryptoInfo.numSubSamples) {
359             throw new IllegalArgumentException(
360                     "Invalid CryptoInfo: numBytesOfClearData is too small!");
361         } else if (cryptoInfo.numBytesOfEncryptedData != null
362                 && cryptoInfo.numBytesOfEncryptedData.length < cryptoInfo.numSubSamples) {
363             throw new IllegalArgumentException(
364                     "Invalid CryptoInfo: numBytesOfEncryptedData is too small!");
365         } else if (cryptoInfo.key == null || cryptoInfo.key.length != 16) {
366             throw new IllegalArgumentException(
367                     "Invalid CryptoInfo: key array is invalid!");
368         }
369 
370         try {
371             return mIDescrambler.descramble(srcBuf, dstBuf, cryptoInfo);
372         } catch (ServiceSpecificException e) {
373             MediaCasStateException.throwExceptionIfNeeded(e.errorCode, e.getMessage());
374         } catch (RemoteException e) {
375             cleanupAndRethrowIllegalState();
376         }
377         return -1;
378     }
379 
380     @Override
close()381     public void close() {
382         if (mIDescrambler != null) {
383             try {
384                 mIDescrambler.release();
385             } catch (RemoteException e) {
386             } finally {
387                 mIDescrambler = null;
388             }
389         }
390     }
391 
392     @Override
finalize()393     protected void finalize() {
394         close();
395     }
396 
native_init()397     private static native final void native_init();
native_setup(@onNull IHwBinder decramblerBinder)398     private native final void native_setup(@NonNull IHwBinder decramblerBinder);
native_release()399     private native final void native_release();
native_descramble( byte key, byte flags, int numSubSamples, int[] numBytesOfClearData, int[] numBytesOfEncryptedData, @NonNull ByteBuffer srcBuf, int srcOffset, int srcLimit, ByteBuffer dstBuf, int dstOffset, int dstLimit)400     private native final int native_descramble(
401             byte key, byte flags, int numSubSamples,
402             int[] numBytesOfClearData, int[] numBytesOfEncryptedData,
403             @NonNull ByteBuffer srcBuf, int srcOffset, int srcLimit,
404             ByteBuffer dstBuf, int dstOffset, int dstLimit) throws RemoteException;
405 
406     static {
407         System.loadLibrary("media_jni");
native_init()408         native_init();
409     }
410 
411     private long mNativeContext;
412 }
413