/* * Copyright (C) 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 "NuPlayerDrm" #include "NuPlayerDrm.h" #include <binder/IServiceManager.h> #include <media/IMediaDrmService.h> #include <utils/Log.h> namespace android { // static helpers - internal sp<IDrm> NuPlayerDrm::CreateDrm(status_t *pstatus) { status_t &status = *pstatus; sp<IServiceManager> sm = defaultServiceManager(); sp<IBinder> binder = sm->getService(String16("media.drm")); ALOGV("CreateDrm binder %p", (binder != NULL ? binder.get() : 0)); sp<IMediaDrmService> service = interface_cast<IMediaDrmService>(binder); if (service == NULL) { ALOGE("CreateDrm failed at IMediaDrmService"); return NULL; } sp<IDrm> drm = service->makeDrm(); if (drm == NULL) { ALOGE("CreateDrm failed at makeDrm"); return NULL; } // this is before plugin creation so NO_INIT is fine status = drm->initCheck(); if (status != OK && status != NO_INIT) { ALOGE("CreateDrm failed drm->initCheck(): %d", status); return NULL; } return drm; } sp<ICrypto> NuPlayerDrm::createCrypto(status_t *pstatus) { status_t &status = *pstatus; sp<IServiceManager> sm = defaultServiceManager(); sp<IBinder> binder = sm->getService(String16("media.drm")); sp<IMediaDrmService> service = interface_cast<IMediaDrmService>(binder); if (service == NULL) { status = UNKNOWN_ERROR; ALOGE("CreateCrypto failed at IMediaDrmService"); return NULL; } sp<ICrypto> crypto = service->makeCrypto(); if (crypto == NULL) { status = UNKNOWN_ERROR; ALOGE("createCrypto failed"); return NULL; } // this is before plugin creation so NO_INIT is fine status = crypto->initCheck(); if (status != OK && status != NO_INIT) { ALOGE("createCrypto failed crypto->initCheck(): %d", status); return NULL; } return crypto; } Vector<DrmUUID> NuPlayerDrm::parsePSSH(const void *pssh, size_t psshsize) { Vector<DrmUUID> drmSchemes, empty; const int DATALEN_SIZE = 4; // the format of the buffer is 1 or more of: // { // 16 byte uuid // 4 byte data length N // N bytes of data // } // Determine the number of entries in the source data. // Since we got the data from stagefright, we trust it is valid and properly formatted. const uint8_t *data = (const uint8_t*)pssh; size_t len = psshsize; size_t numentries = 0; while (len > 0) { if (len < DrmUUID::UUID_SIZE) { ALOGE("ParsePSSH: invalid PSSH data"); return empty; } const uint8_t *uuidPtr = data; // skip uuid data += DrmUUID::UUID_SIZE; len -= DrmUUID::UUID_SIZE; // get data length if (len < DATALEN_SIZE) { ALOGE("ParsePSSH: invalid PSSH data"); return empty; } uint32_t datalen = *((uint32_t*)data); data += DATALEN_SIZE; len -= DATALEN_SIZE; if (len < datalen) { ALOGE("ParsePSSH: invalid PSSH data"); return empty; } // skip the data data += datalen; len -= datalen; DrmUUID _uuid(uuidPtr); drmSchemes.add(_uuid); ALOGV("ParsePSSH[%zu]: %s: %s", numentries, _uuid.toHexString().string(), DrmUUID::arrayToHex(data, datalen).string() ); numentries++; } return drmSchemes; } Vector<DrmUUID> NuPlayerDrm::getSupportedDrmSchemes(const void *pssh, size_t psshsize) { Vector<DrmUUID> psshDRMs = parsePSSH(pssh, psshsize); Vector<DrmUUID> supportedDRMs; // temporary DRM object for crypto Scheme enquiry (without creating a plugin) status_t status = OK; sp<IDrm> drm = CreateDrm(&status); if (drm != NULL) { for (size_t i = 0; i < psshDRMs.size(); i++) { DrmUUID uuid = psshDRMs[i]; if (drm->isCryptoSchemeSupported(uuid.ptr(), String8())) supportedDRMs.add(uuid); } drm.clear(); } else { ALOGE("getSupportedDrmSchemes: Can't create Drm obj: %d", status); } ALOGV("getSupportedDrmSchemes: psshDRMs: %zu supportedDRMs: %zu", psshDRMs.size(), supportedDRMs.size()); return supportedDRMs; } // static helpers - public sp<ICrypto> NuPlayerDrm::createCryptoAndPlugin(const uint8_t uuid[16], const Vector<uint8_t> &drmSessionId, status_t &status) { // Extra check if (drmSessionId.isEmpty()) { status = INVALID_OPERATION; ALOGE("createCryptoAndPlugin: Failed. Empty drmSessionId. status: %d", status); return NULL; } status = OK; sp<ICrypto> crypto = createCrypto(&status); if (crypto == NULL) { ALOGE("createCryptoAndPlugin: createCrypto failed. status: %d", status); return NULL; } ALOGV("createCryptoAndPlugin: createCrypto succeeded"); status = crypto->createPlugin(uuid, drmSessionId.array(), drmSessionId.size()); if (status != OK) { ALOGE("createCryptoAndPlugin: createCryptoPlugin failed. status: %d", status); // crypto will clean itself when leaving the current scope return NULL; } return crypto; } // Parcel has only private copy constructor so passing it in rather than returning void NuPlayerDrm::retrieveDrmInfo(const void *pssh, size_t psshsize, Parcel *parcel) { // 1) PSSH bytes parcel->writeUint32(psshsize); parcel->writeByteArray(psshsize, (const uint8_t*)pssh); ALOGV("retrieveDrmInfo: MEDIA_DRM_INFO PSSH: size: %zu %s", psshsize, DrmUUID::arrayToHex((uint8_t*)pssh, psshsize).string()); // 2) supportedDRMs Vector<DrmUUID> supportedDRMs = getSupportedDrmSchemes(pssh, psshsize); parcel->writeUint32(supportedDRMs.size()); for (size_t i = 0; i < supportedDRMs.size(); i++) { DrmUUID uuid = supportedDRMs[i]; parcel->writeByteArray(DrmUUID::UUID_SIZE, uuid.ptr()); ALOGV("retrieveDrmInfo: MEDIA_DRM_INFO supportedScheme[%zu] %s", i, uuid.toHexString().string()); } } //////////////////////////////////////////////////////////////////////////////////////////// /// Helpers for NuPlayerDecoder //////////////////////////////////////////////////////////////////////////////////////////// NuPlayerDrm::CryptoInfo *NuPlayerDrm::makeCryptoInfo( int numSubSamples, uint8_t key[kBlockSize], uint8_t iv[kBlockSize], CryptoPlugin::Mode mode, size_t *clearbytes, size_t *encryptedbytes) { // size needed to store all the crypto data size_t cryptosize = sizeof(CryptoInfo) + sizeof(CryptoPlugin::SubSample) * numSubSamples; CryptoInfo *ret = (CryptoInfo*) malloc(cryptosize); if (ret == NULL) { ALOGE("couldn't allocate %zu bytes", cryptosize); return NULL; } ret->numSubSamples = numSubSamples; memcpy(ret->key, key, kBlockSize); memcpy(ret->iv, iv, kBlockSize); ret->mode = mode; ret->pattern.mEncryptBlocks = 0; ret->pattern.mSkipBlocks = 0; ret->subSamples = (CryptoPlugin::SubSample*)(ret + 1); CryptoPlugin::SubSample *subSamples = ret->subSamples; for (int i = 0; i < numSubSamples; i++) { subSamples[i].mNumBytesOfClearData = (clearbytes == NULL) ? 0 : clearbytes[i]; subSamples[i].mNumBytesOfEncryptedData = (encryptedbytes == NULL) ? 0 : encryptedbytes[i]; } return ret; } NuPlayerDrm::CryptoInfo *NuPlayerDrm::getSampleCryptoInfo(sp<MetaData> meta) { uint32_t type; const void *crypteddata; size_t cryptedsize; if (meta == NULL) { ALOGE("getSampleCryptoInfo: Unexpected. No meta data for sample."); return NULL; } if (!meta->findData(kKeyEncryptedSizes, &type, &crypteddata, &cryptedsize)) { return NULL; } size_t numSubSamples = cryptedsize / sizeof(size_t); if (numSubSamples <= 0) { ALOGE("getSampleCryptoInfo INVALID numSubSamples: %zu", numSubSamples); return NULL; } const void *cleardata; size_t clearsize; if (meta->findData(kKeyPlainSizes, &type, &cleardata, &clearsize)) { if (clearsize != cryptedsize) { // The two must be of the same length. ALOGE("getSampleCryptoInfo mismatch cryptedsize: %zu != clearsize: %zu", cryptedsize, clearsize); return NULL; } } const void *key; size_t keysize; if (meta->findData(kKeyCryptoKey, &type, &key, &keysize)) { if (keysize != kBlockSize) { ALOGE("getSampleCryptoInfo Keys must be %d bytes in length: %zu", kBlockSize, keysize); // Keys must be 16 bytes in length. return NULL; } } const void *iv; size_t ivsize; if (meta->findData(kKeyCryptoIV, &type, &iv, &ivsize)) { if (ivsize != kBlockSize) { ALOGE("getSampleCryptoInfo IV must be %d bytes in length: %zu", kBlockSize, ivsize); // IVs must be 16 bytes in length. return NULL; } } int32_t mode; if (!meta->findInt32(kKeyCryptoMode, &mode)) { mode = CryptoPlugin::kMode_AES_CTR; } return makeCryptoInfo(numSubSamples, (uint8_t*) key, (uint8_t*) iv, (CryptoPlugin::Mode)mode, (size_t*) cleardata, (size_t*) crypteddata); } } // namespace android