/* * Copyright 2017, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //#define LOG_NDEBUG 0 #define LOG_TAG "MediaDescrambler-JNI" #include #include "android_media_MediaDescrambler.h" #include "android_runtime/AndroidRuntime.h" #include "android_os_HwRemoteBinder.h" #include #include #include #include #include #include #include #include #include #include namespace android { class IMemory; class MemoryDealer; namespace hardware { class HidlMemory; }; using hardware::fromHeap; using hardware::HidlMemory; using hardware::hidl_string; using hardware::hidl_vec; using namespace hardware::cas::V1_0; using namespace hardware::cas::native::V1_0; struct JDescrambler : public RefBase { JDescrambler(JNIEnv *env, jobject descramberBinderObj); status_t descramble( uint32_t key, ssize_t totalLength, const hidl_vec& subSamples, const void *srcPtr, jint srcOffset, void *dstPtr, jint dstOffset, Status *status, uint32_t *bytesWritten, hidl_string *detailedError); protected: virtual ~JDescrambler(); private: sp mDescrambler; sp mMem; sp mDealer; sp mHidlMemory; SharedBuffer mDescramblerSrcBuffer; Mutex mSharedMemLock; bool ensureBufferCapacity(size_t neededSize); DISALLOW_EVIL_CONSTRUCTORS(JDescrambler); }; struct fields_t { jfieldID context; jbyte flagPesHeader; }; static fields_t gFields; static sp getDescrambler(JNIEnv *env, jobject thiz) { return (JDescrambler *)env->GetLongField(thiz, gFields.context); } static void setDescrambler( JNIEnv *env, jobject thiz, const sp &descrambler) { sp old = (JDescrambler *)env->GetLongField(thiz, gFields.context); if (descrambler != NULL) { descrambler->incStrong(thiz); } if (old != NULL) { old->decStrong(thiz); } env->SetLongField(thiz, gFields.context, (jlong)descrambler.get()); } static status_t getBufferAndSize( JNIEnv *env, jobject byteBuf, jint offset, jint limit, size_t length, void **outPtr, jbyteArray *outByteArray) { void *ptr = env->GetDirectBufferAddress(byteBuf); jbyteArray byteArray = NULL; ScopedLocalRef byteBufClass(env, env->FindClass("java/nio/ByteBuffer")); CHECK(byteBufClass.get() != NULL); if (ptr == NULL) { jmethodID arrayID = env->GetMethodID(byteBufClass.get(), "array", "()[B"); CHECK(arrayID != NULL); byteArray = (jbyteArray)env->CallObjectMethod(byteBuf, arrayID); if (byteArray == NULL) { return INVALID_OPERATION; } jboolean isCopy; ptr = env->GetByteArrayElements(byteArray, &isCopy); } if ((jint)length + offset > limit) { if (byteArray != NULL) { env->ReleaseByteArrayElements(byteArray, (jbyte *)ptr, 0); } return -ERANGE; } *outPtr = ptr; *outByteArray = byteArray; return OK; } JDescrambler::JDescrambler(JNIEnv *env, jobject descramblerBinderObj) { mDescrambler = GetDescrambler(env, descramblerBinderObj); if (mDescrambler == NULL) { jniThrowException(env, "java/lang/NullPointerException", NULL); } } JDescrambler::~JDescrambler() { // Don't call release() here, it's called by Java class mDescrambler.clear(); mMem.clear(); mDealer.clear(); } sp GetDescrambler(JNIEnv *env, jobject obj) { if (obj != NULL) { sp hwBinder = JHwRemoteBinder::GetNativeContext(env, obj)->getBinder(); if (hwBinder != NULL) { return hardware::fromBinder< IDescrambler, BpHwDescrambler, BnHwDescrambler>(hwBinder); } } return NULL; } bool JDescrambler::ensureBufferCapacity(size_t neededSize) { if (mMem != NULL && mMem->size() >= neededSize) { return true; } ALOGV("ensureBufferCapacity: current size %zu, new size %zu", mMem == NULL ? 0 : mMem->size(), neededSize); size_t alignment = MemoryDealer::getAllocationAlignment(); neededSize = (neededSize + (alignment - 1)) & ~(alignment - 1); // Align to multiples of 64K. neededSize = (neededSize + 65535) & ~65535; mDealer = new MemoryDealer(neededSize, "JDescrambler"); mMem = mDealer->allocate(neededSize); ssize_t offset; size_t size; sp heap = mMem->getMemory(&offset, &size); if (heap == NULL) { return false; } mHidlMemory = fromHeap(heap); mDescramblerSrcBuffer.heapBase = *mHidlMemory; mDescramblerSrcBuffer.offset = (uint64_t) offset; mDescramblerSrcBuffer.size = (uint64_t) size; return true; } status_t JDescrambler::descramble( uint32_t key, ssize_t totalLength, const hidl_vec& subSamples, const void *srcPtr, jint srcOffset, void *dstPtr, jint dstOffset, Status *status, uint32_t *bytesWritten, hidl_string *detailedError) { // TODO: IDescrambler::descramble() is re-entrant, however because we // only have 1 shared mem buffer, we can only do 1 descramble at a time. // Concurrency might be improved by allowing on-demand allocation of up // to 2 shared mem buffers. Mutex::Autolock autolock(mSharedMemLock); if (!ensureBufferCapacity(totalLength)) { return NO_MEMORY; } memcpy(mMem->pointer(), (const void*)((const uint8_t*)srcPtr + srcOffset), totalLength); DestinationBuffer dstBuffer; dstBuffer.type = BufferType::SHARED_MEMORY; dstBuffer.nonsecureMemory = mDescramblerSrcBuffer; auto err = mDescrambler->descramble( (ScramblingControl) key, subSamples, mDescramblerSrcBuffer, 0, dstBuffer, 0, [&status, &bytesWritten, &detailedError] ( Status _status, uint32_t _bytesWritten, const hidl_string& _detailedError) { *status = _status; *bytesWritten = _bytesWritten; *detailedError = _detailedError; }); if (!err.isOk()) { return FAILED_TRANSACTION; } if (*status == Status::OK) { if (*bytesWritten > 0 && (ssize_t) *bytesWritten <= totalLength) { memcpy((void*)((uint8_t*)dstPtr + dstOffset), mMem->pointer(), *bytesWritten); } else { // status seems OK but bytesWritten is invalid, we really // have no idea what is wrong. *status = Status::ERROR_CAS_UNKNOWN; } } return OK; } } // namespace android using namespace android; static void android_media_MediaDescrambler_native_release(JNIEnv *env, jobject thiz) { setDescrambler(env, thiz, NULL); } static void android_media_MediaDescrambler_native_init(JNIEnv *env) { ScopedLocalRef clazz( env, env->FindClass("android/media/MediaDescrambler")); CHECK(clazz.get() != NULL); gFields.context = env->GetFieldID(clazz.get(), "mNativeContext", "J"); CHECK(gFields.context != NULL); jfieldID fieldPesHeader = env->GetStaticFieldID( clazz.get(), "SCRAMBLE_FLAG_PES_HEADER", "B"); CHECK(fieldPesHeader != NULL); gFields.flagPesHeader = env->GetStaticByteField(clazz.get(), fieldPesHeader); } static void android_media_MediaDescrambler_native_setup( JNIEnv *env, jobject thiz, jobject descramblerBinderObj) { setDescrambler(env, thiz, new JDescrambler(env, descramblerBinderObj)); } static ssize_t getSubSampleInfo(JNIEnv *env, jint numSubSamples, jintArray numBytesOfClearDataObj, jintArray numBytesOfEncryptedDataObj, hidl_vec *outSubSamples) { if (numSubSamples <= 0 || numSubSamples >= (signed)(INT32_MAX / sizeof(SubSample))) { // subSamples array may silently overflow if number of samples are // too large. Use INT32_MAX as maximum allocation size may be less // than SIZE_MAX on some platforms. ALOGE("numSubSamples is invalid!"); return -1; } jboolean isCopy; ssize_t totalSize = 0; jint *numBytesOfClearData = (numBytesOfClearDataObj == NULL) ? NULL : env->GetIntArrayElements(numBytesOfClearDataObj, &isCopy); jint *numBytesOfEncryptedData = (numBytesOfEncryptedDataObj == NULL) ? NULL : env->GetIntArrayElements(numBytesOfEncryptedDataObj, &isCopy); outSubSamples->resize(numSubSamples); SubSample *subSamples = outSubSamples->data(); if (subSamples == NULL) { ALOGE("Failed to allocate SubSample array!"); return -1; } for (jint i = 0; i < numSubSamples; ++i) { subSamples[i].numBytesOfClearData = (numBytesOfClearData == NULL) ? 0 : numBytesOfClearData[i]; subSamples[i].numBytesOfEncryptedData = (numBytesOfEncryptedData == NULL) ? 0 : numBytesOfEncryptedData[i]; totalSize += subSamples[i].numBytesOfClearData + subSamples[i].numBytesOfEncryptedData; } if (numBytesOfEncryptedData != NULL) { env->ReleaseIntArrayElements( numBytesOfEncryptedDataObj, numBytesOfEncryptedData, 0); numBytesOfEncryptedData = NULL; } if (numBytesOfClearData != NULL) { env->ReleaseIntArrayElements( numBytesOfClearDataObj, numBytesOfClearData, 0); numBytesOfClearData = NULL; } if (totalSize < 0) { return -1; } return totalSize; } static jthrowable createServiceSpecificException( JNIEnv *env, int serviceSpecificError, const char *msg) { if (env->ExceptionCheck()) { ALOGW("Discarding pending exception"); env->ExceptionDescribe(); env->ExceptionClear(); } ScopedLocalRef clazz( env, env->FindClass("android/os/ServiceSpecificException")); CHECK(clazz.get() != NULL); const jmethodID ctor = env->GetMethodID(clazz.get(), "", "(ILjava/lang/String;)V"); CHECK(ctor != NULL); ScopedLocalRef msgObj( env, env->NewStringUTF(msg != NULL ? msg : String8::format("Error %#x", serviceSpecificError))); return (jthrowable)env->NewObject( clazz.get(), ctor, serviceSpecificError, msgObj.get()); } static jint android_media_MediaDescrambler_native_descramble( JNIEnv *env, jobject thiz, jbyte key, jbyte flags, jint numSubSamples, jintArray numBytesOfClearDataObj, jintArray numBytesOfEncryptedDataObj, jobject srcBuf, jint srcOffset, jint srcLimit, jobject dstBuf, jint dstOffset, jint dstLimit) { sp descrambler = getDescrambler(env, thiz); if (descrambler == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "Invalid descrambler object!"); return -1; } hidl_vec subSamples; ssize_t totalLength = getSubSampleInfo( env, numSubSamples, numBytesOfClearDataObj, numBytesOfEncryptedDataObj, &subSamples); if (totalLength < 0) { jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid subsample info!"); return -1; } void *srcPtr = NULL, *dstPtr = NULL; jbyteArray srcArray = NULL, dstArray = NULL; status_t err = getBufferAndSize( env, srcBuf, srcOffset, srcLimit, totalLength, &srcPtr, &srcArray); if (err == OK) { if (dstBuf == NULL) { dstPtr = srcPtr; } else { err = getBufferAndSize( env, dstBuf, dstOffset, dstLimit, totalLength, &dstPtr, &dstArray); } } if (err != OK) { jniThrowException(env, "java/lang/IllegalArgumentException", "Invalid buffer offset and/or size for subsamples!"); return -1; } uint32_t scramblingControl = (uint32_t)key; if (flags & gFields.flagPesHeader) { scramblingControl |= DescramblerPlugin::kScrambling_Flag_PesHeader; } Status status; uint32_t bytesWritten; hidl_string detailedError; err = descrambler->descramble( scramblingControl, totalLength, subSamples, srcPtr, srcOffset, dstPtr, dstOffset, &status, &bytesWritten, &detailedError); // Release byte array before throwing if (srcArray != NULL) { env->ReleaseByteArrayElements(srcArray, (jbyte *)srcPtr, 0); } if (dstArray != NULL) { env->ReleaseByteArrayElements(dstArray, (jbyte *)dstPtr, 0); } if (err == NO_MEMORY) { jniThrowException(env, "java/lang/OutOfMemoryError", NULL); } else if (err == FAILED_TRANSACTION) { jniThrowException(env, "android/os/RemoteException", NULL); } else if (status != Status::OK) { // Throw ServiceSpecific with cas error code and detailed msg, // which will be re-thrown as MediaCasStateException. env->Throw(createServiceSpecificException( env, (int) status, detailedError.c_str())); } return bytesWritten; } static const JNINativeMethod gMethods[] = { { "native_release", "()V", (void *)android_media_MediaDescrambler_native_release }, { "native_init", "()V", (void *)android_media_MediaDescrambler_native_init }, { "native_setup", "(Landroid/os/IHwBinder;)V", (void *)android_media_MediaDescrambler_native_setup }, { "native_descramble", "(BBI[I[ILjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;II)I", (void *)android_media_MediaDescrambler_native_descramble }, }; int register_android_media_Descrambler(JNIEnv *env) { return AndroidRuntime::registerNativeMethods(env, "android/media/MediaDescrambler", gMethods, NELEM(gMethods)); }