Implement Widevine drm HIDL HAL service.

Modify Android mediadrm and mediacrypto glue layer to use
HIDL interface.

Test: Play Movies (streaming and offline playback)

Test: ANDROID_BUILD_TOP= ./android-gts/tools/gts-tradefed
run gts -m GtsMediaTestCases

Test:
adb shell /system/bin/libwvdrmengine_hidl_test

Test:
adb shell /system/bin/libwvdrmmediacrypto_hidl_test

Test:
adb shell /system/bin/libwvdrmdrmplugin_hidl_test

bug: 34628973
Change-Id: Icd5f2dd556acb9874697963b4d7d62cb7c943e74
This commit is contained in:
Edwin Wong
2017-01-23 15:48:45 -08:00
committed by John W. Bruce
parent a4506542df
commit 2dc53442e7
33 changed files with 6853 additions and 684 deletions

View File

@@ -1,4 +1,8 @@
LOCAL_PATH := $(call my-dir)
# -----------------------------------------------------------------------------
# Builds libwvdrmcryptoplugin
#
include $(CLEAR_VARS)
LOCAL_SRC_FILES := \
@@ -14,10 +18,46 @@ LOCAL_C_INCLUDES := \
vendor/widevine/libwvdrmengine/mediacrypto/include \
vendor/widevine/libwvdrmengine/oemcrypto/include \
LOCAL_STATIC_LIBRARIES := \
libcrypto_static \
LOCAL_MODULE := libwvdrmcryptoplugin
LOCAL_MODULE_TAGS := optional
LOCAL_STATIC_LIBRARIES := libcrypto_static
include $(BUILD_STATIC_LIBRARY)
# -----------------------------------------------------------------------------
# Builds libwvdrmcryptoplugin_hidl
#
include $(CLEAR_VARS)
LOCAL_SRC_FILES := \
src_hidl/WVCryptoPlugin.cpp \
LOCAL_C_INCLUDES := \
frameworks/av/include \
frameworks/native/include \
vendor/widevine/libwvdrmengine/cdm/core/include \
vendor/widevine/libwvdrmengine/cdm/include \
vendor/widevine/libwvdrmengine/cdm/metrics/include \
vendor/widevine/libwvdrmengine/include_hidl \
vendor/widevine/libwvdrmengine/include \
vendor/widevine/libwvdrmengine/mediacrypto/include_hidl \
vendor/widevine/libwvdrmengine/mediacrypto/include \
vendor/widevine/libwvdrmengine/oemcrypto/include \
LOCAL_STATIC_LIBRARIES := \
libcrypto_static \
libhidl_utils \
LOCAL_SHARED_LIBRARIES := \
android.hardware.drm@1.0 \
android.hidl.memory@1.0 \
libhidlmemory \
LOCAL_MODULE := libwvdrmcryptoplugin_hidl
LOCAL_MODULE_TAGS := optional
include $(BUILD_STATIC_LIBRARY)

View File

@@ -0,0 +1,86 @@
//
// Copyright 2017 Google Inc. All Rights Reserved.
//
#ifndef WV_CRYPTO_PLUGIN_H_
#define WV_CRYPTO_PLUGIN_H_
#include <android/hardware/drm/1.0/ICryptoPlugin.h>
#include <android/hidl/memory/1.0/IMemory.h>
#include <media/stagefright/foundation/AString.h>
#include "wv_content_decryption_module.h"
#include "WVTypes.h"
namespace wvdrm {
namespace hardware {
namespace drm {
namespace V1_0 {
namespace widevine {
using ::android::hardware::drm::V1_0::DestinationBuffer;
using ::android::hardware::drm::V1_0::ICryptoPlugin;
using ::android::hardware::drm::V1_0::Mode;
using ::android::hardware::drm::V1_0::Pattern;
using ::android::hardware::drm::V1_0::SharedBuffer;
using ::android::hardware::drm::V1_0::Status;
using ::android::hardware::drm::V1_0::SubSample;
using ::android::hardware::hidl_array;
using ::android::hardware::hidl_string;
using ::android::hardware::hidl_vec;
using ::android::hardware::Return;
using ::android::hardware::hidl_memory;
using ::android::hidl::memory::V1_0::IMemory;
using ::android::sp;
struct WVCryptoPlugin : public ICryptoPlugin {
WVCryptoPlugin(const void* data, size_t size,
const sp<wvcdm::WvContentDecryptionModule>& cdm);
virtual ~WVCryptoPlugin() {}
Return<bool> requiresSecureDecoderComponent(const hidl_string& mime)
override;
Return<void> notifyResolution(uint32_t width, uint32_t height) override;
Return<Status> setMediaDrmSession(const hidl_vec<uint8_t>& sessionId)
override;
Return<void> setSharedBufferBase(const hidl_memory& base,
uint32_t bufferId) override;
Return<void> decrypt(
bool secure,
const hidl_array<uint8_t, 16>& keyId,
const hidl_array<uint8_t, 16>& iv,
Mode mode,
const Pattern& pattern,
const hidl_vec<SubSample>& subSamples,
const SharedBuffer& source,
uint64_t offset,
const DestinationBuffer& destination,
decrypt_cb _hidl_cb) override;
private:
WVDRM_DISALLOW_COPY_AND_ASSIGN_AND_NEW(WVCryptoPlugin);
wvcdm::CdmSessionId mSessionId;
std::map<uint32_t, sp<IMemory> > mSharedBufferMap;
sp<wvcdm::WvContentDecryptionModule> const mCDM;
android::status_t attemptDecrypt(
const wvcdm::CdmDecryptionParameters& params,
bool haveEncryptedSubsamples, android::AString* errorDetailMsg);
static wvcdm::CdmResponseType countEncryptedBlocksInPatternedRange(
size_t range, const Pattern& pattern, uint64_t* result);
static void incrementIV(uint64_t increaseBy, std::vector<uint8_t>* ivPtr);
};
} // namespace widevine
} // namespace V1_0
} // namespace drm
} // namespace hardware
} // namespace wvdrm
#endif // WV_CRYPTO_PLUGIN_H_

View File

@@ -0,0 +1,407 @@
//
// Copyright 2017 Google Inc. All Rights Reserved.
//
//#define LOG_NDEBUG 0
#define LOG_TAG "WVCdm"
#include <utils/Log.h>
#include <utils/String8.h>
#include "WVCryptoPlugin.h"
#include "TypeConvert.h"
#include <hidlmemory/mapping.h>
#include "mapErrors-inl.h"
#include "OEMCryptoCENC.h"
#include "openssl/sha.h"
#include "wv_cdm_constants.h"
#include "WVErrors.h"
namespace {
static const size_t kAESBlockSize = 16;
} // namespace
namespace wvdrm {
namespace hardware {
namespace drm {
namespace V1_0 {
namespace widevine {
using ::android::hardware::drm::V1_0::BufferType;
using ::android::hardware::drm::V1_0::widevine::toStatus;
using ::android::hardware::drm::V1_0::widevine::toVector;
using ::android::hardware::Void;
using android::AString;
using android::status_t;
using wvcdm::CdmDecryptionParameters;
using wvcdm::CdmQueryMap;
using wvcdm::CdmResponseType;
using wvcdm::CdmSessionId;
using wvcdm::KeyId;
using wvcdm::WvContentDecryptionModule;
WVCryptoPlugin::WVCryptoPlugin(const void* data, size_t size,
const sp<WvContentDecryptionModule>& cdm)
: mCDM(cdm){
if (data != NULL) {
mSessionId.assign(static_cast<const char *>(data), size);
}
if (!mCDM->IsOpenSession(mSessionId)) {
mSessionId.clear();
}
}
Return<bool> WVCryptoPlugin::requiresSecureDecoderComponent(
const hidl_string& mime) {
if (!strncasecmp(mime, "video/", 6)) {
// Type is video, so query CDM to see if we require a secure decoder.
CdmQueryMap status;
CdmResponseType res = mCDM->QuerySessionStatus(mSessionId, &status);
if (!isCdmResponseTypeSuccess(res)) {
ALOGE("Error querying CDM status: %u", res);
return false;
}
return status[wvcdm::QUERY_KEY_SECURITY_LEVEL] ==
wvcdm::QUERY_VALUE_SECURITY_LEVEL_L1;
} else {
// Type is not video, so never require a secure decoder.
return false;
}
}
Return<void> WVCryptoPlugin::notifyResolution(
uint32_t width, uint32_t height) {
mCDM->NotifyResolution(mSessionId, width, height);
return Void();
}
Return<Status> WVCryptoPlugin::setMediaDrmSession(
const hidl_vec<uint8_t>& sessionId) {
const android::Vector<uint8_t> sId = toVector(sessionId);
CdmSessionId cdmSessionId(sId.begin(), sId.end());
if (!mCDM->IsOpenSession(cdmSessionId)) {
return Status::ERROR_DRM_SESSION_NOT_OPENED;
} else {
mSessionId = cdmSessionId;
return Status::OK;
}
}
Return<void> WVCryptoPlugin::setSharedBufferBase(
const hidl_memory& base, uint32_t bufferId) {
mSharedBufferMap[bufferId] = mapMemory(base);
return Void();
}
Return<void> WVCryptoPlugin::decrypt(
bool secure,
const hidl_array<uint8_t, 16>& keyId,
const hidl_array<uint8_t, 16>& iv,
Mode mode,
const Pattern& pattern,
const hidl_vec<SubSample>& subSamples,
const SharedBuffer& source,
uint64_t offset,
const DestinationBuffer& destination,
decrypt_cb _hidl_cb) {
if (mSharedBufferMap.find(source.bufferId) == mSharedBufferMap.end()) {
_hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0,
"source decrypt buffer base not set");
return Void();
}
if (destination.type == BufferType::SHARED_MEMORY) {
const SharedBuffer& dest = destination.nonsecureMemory;
if (mSharedBufferMap.find(dest.bufferId) == mSharedBufferMap.end()) {
_hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0,
"destination decrypt buffer base not set");
return Void();
}
}
if (mode != Mode::UNENCRYPTED &&
mode != Mode::AES_CTR &&
mode != Mode::AES_CBC) {
_hidl_cb(Status::BAD_VALUE,
0, "Encryption mode is not supported by Widevine CDM.");
return Void();
}
// Convert parameters to the form the CDM wishes to consume them in.
const KeyId cryptoKey(
reinterpret_cast<const char*>(keyId.data()), wvcdm::KEY_ID_SIZE);
std::vector<uint8_t> ivVector(iv.data(), iv.data() + wvcdm::KEY_IV_SIZE);
AString errorDetailMsg;
sp<IMemory> sourceBase = mSharedBufferMap[source.bufferId];
if (source.offset + offset + source.size > sourceBase->getSize()) {
_hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, "invalid buffer size");
return Void();
}
uint8_t *base = static_cast<uint8_t *>
(static_cast<void *>(sourceBase->getPointer()));
uint8_t* srcPtr = static_cast<uint8_t *>(base + source.offset + offset);
void* destPtr = NULL;
if (destination.type == BufferType::SHARED_MEMORY) {
const SharedBuffer& destBuffer = destination.nonsecureMemory;
sp<IMemory> destBase = mSharedBufferMap[destBuffer.bufferId];
if (destBuffer.offset + destBuffer.size > destBase->getSize()) {
_hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0, "invalid buffer size");
return Void();
}
destPtr = static_cast<void *>(base + destination.nonsecureMemory.offset);
} else if (destination.type == BufferType::NATIVE_HANDLE) {
native_handle_t *handle = const_cast<native_handle_t *>(
destination.secureMemory.getNativeHandle());
destPtr = static_cast<void *>(handle);
}
// Calculate the output buffer size and determine if any subsamples are
// encrypted.
size_t destSize = 0;
bool haveEncryptedSubsamples = false;
for (size_t i = 0; i < subSamples.size(); i++) {
const SubSample &subSample = subSamples[i];
destSize += subSample.numBytesOfClearData;
destSize += subSample.numBytesOfEncryptedData;
if (subSample.numBytesOfEncryptedData > 0) {
haveEncryptedSubsamples = true;
}
}
// Set up the decrypt params that do not vary.
CdmDecryptionParameters params = CdmDecryptionParameters();
params.is_secure = secure;
params.key_id = &cryptoKey;
params.iv = &ivVector;
params.decrypt_buffer = destPtr;
params.decrypt_buffer_length = destSize;
params.pattern_descriptor.encrypt_blocks = pattern.encryptBlocks;
params.pattern_descriptor.skip_blocks = pattern.skipBlocks;
if (mode == Mode::AES_CTR) {
params.cipher_mode = wvcdm::kCipherModeCtr;
} else if (mode == Mode::AES_CBC) {
params.cipher_mode = wvcdm::kCipherModeCbc;
}
// Iterate through subsamples, sending them to the CDM serially.
size_t bufferOffset = 0;
size_t blockOffset = 0;
const size_t patternLengthInBytes =
(pattern.encryptBlocks + pattern.skipBlocks) * kAESBlockSize;
for (size_t i = 0; i < subSamples.size(); ++i) {
const SubSample& subSample = subSamples[i];
if (mode == Mode::UNENCRYPTED && subSample.numBytesOfEncryptedData != 0) {
_hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0,
"Encrypted subsamples found in allegedly unencrypted data.");
return Void();
}
// Calculate any flags that apply to this subsample's parts.
uint8_t clearFlags = 0;
uint8_t encryptedFlags = 0;
// If this is the first subsample…
if (i == 0) {
// …add OEMCrypto_FirstSubsample to the first part that is present.
if (subSample.numBytesOfClearData != 0) {
clearFlags = clearFlags | OEMCrypto_FirstSubsample;
} else {
encryptedFlags = encryptedFlags | OEMCrypto_FirstSubsample;
}
}
// If this is the last subsample…
if (i == subSamples.size() - 1) {
// …add OEMCrypto_LastSubsample to the last part that is present
if (subSample.numBytesOfEncryptedData != 0) {
encryptedFlags = encryptedFlags | OEMCrypto_LastSubsample;
} else {
clearFlags = clearFlags | OEMCrypto_LastSubsample;
}
}
// "Decrypt" any unencrypted data. Per the ISO-CENC standard, clear data
// comes before encrypted data.
if (subSample.numBytesOfClearData != 0) {
params.is_encrypted = false;
params.encrypt_buffer = srcPtr + bufferOffset;
params.encrypt_length = subSample.numBytesOfClearData;
params.block_offset = 0;
params.decrypt_buffer_offset = bufferOffset;
params.subsample_flags = clearFlags;
status_t res = attemptDecrypt(params, haveEncryptedSubsamples,
&errorDetailMsg);
if (res != android::OK) {
_hidl_cb(toStatus(res), 0, errorDetailMsg.c_str());
return Void();
}
bufferOffset += subSample.numBytesOfClearData;
}
// Decrypt any encrypted data. Per the ISO-CENC standard, encrypted data
// comes after clear data.
if (subSample.numBytesOfEncryptedData != 0) {
params.is_encrypted = true;
params.encrypt_buffer = srcPtr + bufferOffset;
params.encrypt_length = subSample.numBytesOfEncryptedData;
params.block_offset = blockOffset;
params.decrypt_buffer_offset = bufferOffset;
params.subsample_flags = encryptedFlags;
status_t res = attemptDecrypt(params, haveEncryptedSubsamples,
&errorDetailMsg);
if (res != android::OK) {
_hidl_cb(toStatus(res), 0, errorDetailMsg.c_str());
return Void();
}
bufferOffset += subSample.numBytesOfEncryptedData;
// Update the block offset, pattern offset, and IV as needed by the
// various crypto modes. Possible combinations are cenc (AES-CTR), cens
// (AES-CTR w/ Patterns), cbc1 (AES-CBC), and cbcs (AES-CBC w/ Patterns).
if (mode == Mode::AES_CTR) {
// Update the IV depending on how many encrypted blocks we passed.
uint64_t increment = 0;
if (patternLengthInBytes == 0) {
// If there's no pattern, all the blocks are encrypted. We have to add
// in blockOffset to account for any incomplete crypto blocks from the
// preceding subsample.
increment = (blockOffset + subSample.numBytesOfEncryptedData) /
kAESBlockSize;
} else {
CdmResponseType countRes = countEncryptedBlocksInPatternedRange(
subSample.numBytesOfEncryptedData, pattern, &increment);
if (!isCdmResponseTypeSuccess(countRes)) {
// Swallow the specifics of the error to obscure decrypt internals.
_hidl_cb(Status::ERROR_DRM_CANNOT_HANDLE, 0,
"Error decrypting data: unknown error");
return Void();
}
}
incrementIV(increment, &ivVector);
// Update the block offset
blockOffset = (blockOffset + subSample.numBytesOfEncryptedData) %
kAESBlockSize;
} else if (mode == Mode::AES_CBC && patternLengthInBytes == 0) {
// If there is no pattern, assume cbc1 mode and update the IV.
// Stash the last crypto block in the IV.
const uint8_t* bufferEnd = srcPtr + bufferOffset +
subSample.numBytesOfEncryptedData;
ivVector.assign(bufferEnd - kAESBlockSize, bufferEnd);
}
// There is no branch for cbcs mode because the IV and pattern offest
// reset at the start of each subsample, so they do not need to be
// updated.
}
}
uint32_t status;
uint32_t bytesWritten;
if (bufferOffset >= 0) {
status = android::OK;
bytesWritten = bufferOffset;
} else {
status = bufferOffset;
bytesWritten = 0;
}
_hidl_cb(toStatus(status), bytesWritten, errorDetailMsg.c_str());
return Void();
}
status_t WVCryptoPlugin::attemptDecrypt(const CdmDecryptionParameters& params,
bool haveEncryptedSubsamples,
AString* errorDetailMsg) {
CdmResponseType res = mCDM->Decrypt(mSessionId, haveEncryptedSubsamples,
params);
if (isCdmResponseTypeSuccess(res)) {
return android::OK;
} else {
ALOGE("Decrypt error result in session %s during %s block: %d",
mSessionId.c_str(),
params.is_encrypted ? "encrypted" : "unencrypted",
res);
if (res == wvcdm::INSUFFICIENT_CRYPTO_RESOURCES) {
errorDetailMsg->setTo(
"Error decrypting data: insufficient crypto resources");
// This error is actionable by the app and should be passed up.
return mapCdmResponseType(res);
} else if (res == wvcdm::NEED_KEY) {
errorDetailMsg->setTo(
"Error decrypting data: requested key has not been loaded");
// This error is actionable by the app and should be passed up.
return mapCdmResponseType(res);
} else if (res == wvcdm::SESSION_NOT_FOUND_FOR_DECRYPT) {
errorDetailMsg->setTo(
"Error decrypting data: session not found, possibly reclaimed");
// This error is actionable by the app and should be passed up.
return mapCdmResponseType(res);
} else if (res == wvcdm::DECRYPT_ERROR) {
errorDetailMsg->setTo(
"Error decrypting data: unspecified error");
// This error is actionable by the app and should be passed up.
return mapCdmResponseType(res);
} else if (res == wvcdm::INSUFFICIENT_OUTPUT_PROTECTION) {
errorDetailMsg->setTo(
"Error decrypting data: insufficient output protection");
// This error is actionable by the app and should be passed up.
return mapCdmResponseType(res);
} else {
// Swallow the specifics of other errors to obscure decrypt internals.
return kErrorCDMGeneric;
}
}
}
CdmResponseType WVCryptoPlugin::countEncryptedBlocksInPatternedRange(
size_t range, const Pattern& pattern, uint64_t* result) {
if (result == NULL || range % kAESBlockSize != 0) {
return wvcdm::UNKNOWN_ERROR;
}
const size_t patternLength = pattern.encryptBlocks + pattern.skipBlocks;
uint64_t encryptedBlocksPassed = 0;
size_t patternPosition = 0;
for (size_t remaining = range / kAESBlockSize; remaining > 0; --remaining) {
if (patternPosition < pattern.encryptBlocks) {
++encryptedBlocksPassed;
}
patternPosition = (patternPosition + 1) % patternLength;
}
*result = encryptedBlocksPassed;
return wvcdm::NO_ERROR;
}
void WVCryptoPlugin::incrementIV(uint64_t increaseBy,
std::vector<uint8_t>* ivPtr) {
std::vector<uint8_t>& iv = *ivPtr;
uint64_t* counterBuffer = reinterpret_cast<uint64_t*>(&iv[8]);
(*counterBuffer) = htonq(ntohq(*counterBuffer) + increaseBy);
}
} // namespace widevine
} // namespace V1_0
} // namespace drm
} // namespace hardware
} // namespace wvdrm

View File

@@ -1,8 +1,12 @@
LOCAL_PATH := $(call my-dir)
# -----------------------------------------------------------------------------
# Builds libwvdrmmediacrypto_test
#
include $(CLEAR_VARS)
LOCAL_SRC_FILES := \
WVCryptoPlugin_test.cpp \
legacy_src/WVCryptoPlugin_test.cpp \
LOCAL_C_INCLUDES := \
frameworks/av/include \
@@ -37,10 +41,67 @@ LOCAL_SHARED_LIBRARIES := \
LOCAL_C_INCLUDES += \
external/protobuf/src \
# End protobuf section
LOCAL_MODULE := libwvdrmmediacrypto_test
LOCAL_MODULE_TAGS := tests
include $(BUILD_EXECUTABLE)
# -----------------------------------------------------------------------------
# Builds libwvdrmmediacrypto_hidl_test
#
include $(CLEAR_VARS)
LOCAL_SRC_FILES := \
WVCryptoPlugin_test.cpp \
LOCAL_C_INCLUDES := \
frameworks/av/include \
frameworks/native/include \
vendor/widevine/libwvdrmengine/cdm/core/include \
vendor/widevine/libwvdrmengine/cdm/include \
vendor/widevine/libwvdrmengine/cdm/metrics/include \
vendor/widevine/libwvdrmengine/include_hidl \
vendor/widevine/libwvdrmengine/mediacrypto/include_hidl \
vendor/widevine/libwvdrmengine/mediacrypto/include \
vendor/widevine/libwvdrmengine/oemcrypto/include \
LOCAL_STATIC_LIBRARIES := \
libcdm \
libcdm_protos \
libcdm_utils \
libcrypto_static \
libjsmn \
libgmock \
libgmock_main \
libgtest \
libwvlevel3 \
libwvdrmcryptoplugin_hidl \
# When the GNU linker sees a library, it discards all symbols that it doesn't
# need. libhidl_utils must come after libwvdrmcryptoplugin.
LOCAL_STATIC_LIBRARIES += libhidl_utils
LOCAL_SHARED_LIBRARIES := \
android.hardware.drm@1.0 \
android.hidl.base@1.0 \
android.hidl.memory@1.0 \
libbinder \
libcutils \
libdl \
libhidlbase \
libhidlmemory \
liblog \
libmedia \
libprotobuf-cpp-lite \
libstagefright_foundation \
libutils \
LOCAL_C_INCLUDES += \
external/protobuf/src \
LOCAL_MODULE := libwvdrmmediacrypto_hidl_test
LOCAL_MODULE_TAGS := tests
include $(BUILD_EXECUTABLE)

View File

@@ -2,29 +2,77 @@
// Copyright 2013 Google Inc. All Rights Reserved.
//
//#define LOG_NDEBUG 0
#define LOG_TAG "WVCryptoPluginTest"
#include <utils/Log.h>
#include <map>
#include <stdio.h>
#include <string>
#include <vector>
#include <binder/MemoryDealer.h>
#include <hidl/Status.h>
#include <hidlmemory/mapping.h>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "media/stagefright/foundation/ABase.h"
#include "media/stagefright/foundation/AString.h"
#include "media/stagefright/MediaErrors.h"
#include "OEMCryptoCENC.h"
#include "wv_cdm_constants.h"
#include "wv_cdm_types.h"
#include "wv_content_decryption_module.h"
#include "OEMCryptoCENC.h"
#include "TypeConvert.h"
#include "WVCryptoPlugin.h"
using namespace android;
using namespace std;
using namespace testing;
using namespace wvcdm;
using namespace wvdrm;
namespace wvdrm {
namespace hardware {
namespace drm {
namespace V1_0 {
namespace widevine {
class MockCDM : public WvContentDecryptionModule {
using ::android::hardware::drm::V1_0::BufferType;
using ::android::hardware::drm::V1_0::DestinationBuffer;
using ::android::hardware::drm::V1_0::Mode;
using ::android::hardware::drm::V1_0::Pattern;
using ::android::hardware::drm::V1_0::SharedBuffer;
using ::android::hardware::drm::V1_0::Status;
using ::android::hardware::drm::V1_0::SubSample;
using ::android::hardware::drm::V1_0::widevine::toHidlVec;
using ::android::hardware::hidl_array;
using ::android::hardware::hidl_handle;
using ::android::hardware::hidl_memory;
using ::android::hardware::hidl_string;
using ::android::hardware::hidl_vec;
using ::android::hardware::Void;
using ::android::MemoryDealer;
using ::android::sp;
using ::testing::_;
using ::testing::DefaultValue;
using ::testing::ElementsAreArray;
using ::testing::Field;
using ::testing::InSequence;
using ::testing::Matcher;
using ::testing::SetArgPointee;
using ::testing::StrictMock;
using ::testing::Test;
using ::testing::Value;
using ::testing::internal::ElementsAreArrayMatcher;
using wvcdm::kCipherModeCtr;
using wvcdm::CdmCencPatternEncryptionDescriptor;
using wvcdm::CdmCipherMode;
using wvcdm::CdmDecryptionParameters;
using wvcdm::CdmQueryMap;
using wvcdm::CdmResponseType;
using wvcdm::CdmSessionId;
using wvcdm::KEY_ID_SIZE;
using wvcdm::KEY_IV_SIZE;
using wvcdm::QUERY_KEY_SECURITY_LEVEL;
using wvcdm::QUERY_VALUE_SECURITY_LEVEL_L1;
using wvcdm::QUERY_VALUE_SECURITY_LEVEL_L3;
class MockCDM : public wvcdm::WvContentDecryptionModule {
public:
MOCK_METHOD1(IsOpenSession, bool(const CdmSessionId&));
@@ -38,7 +86,11 @@ class MockCDM : public WvContentDecryptionModule {
class WVCryptoPluginTest : public Test {
protected:
static const uint32_t kSessionIdSize = 16;
uint8_t* pDest = nullptr;
uint8_t* pSrc = nullptr;
uint8_t sessionId[kSessionIdSize];
uint32_t nextBufferId = 0;
std::map<void *, uint32_t> heapBases;
virtual void SetUp() {
FILE* fp = fopen("/dev/urandom", "r");
@@ -47,6 +99,47 @@ class WVCryptoPluginTest : public Test {
// Set default CdmResponseType value for gMock
DefaultValue<CdmResponseType>::Set(wvcdm::NO_ERROR);
heapBases.clear();
}
void setHeapBase(WVCryptoPlugin& plugin,
const sp<android::IMemoryHeap>& heap) {
ASSERT_NE(heap, nullptr);
void* heapBase = heap->getBase();
ASSERT_NE(heapBase, nullptr);
native_handle_t* nativeHandle = native_handle_create(1, 0);
ASSERT_NE(nativeHandle, nullptr);
nativeHandle->data[0] = heap->getHeapID();
auto hidlHandle = hidl_handle(nativeHandle);
auto hidlMemory = hidl_memory("ashmem", hidlHandle, heap->getSize());
heapBases.insert(
std::pair<void*, uint32_t>(heapBase, nextBufferId));
Return<void> hResult =
plugin.setSharedBufferBase(hidlMemory, nextBufferId++);
ALOGE_IF(!hResult.isOk(), "setHeapBase failed setSharedBufferBase");
}
void toSharedBuffer(WVCryptoPlugin& plugin,
const sp<android::IMemory>& memory,
SharedBuffer* buffer) {
ssize_t offset;
size_t size;
ASSERT_NE(memory, nullptr);
ASSERT_NE(buffer, nullptr);
sp<android::IMemoryHeap> heap = memory->getMemory(&offset, &size);
ASSERT_NE(heap, nullptr);
setHeapBase(plugin, heap);
buffer->bufferId = heapBases[heap->getBase()];
buffer->offset = offset >= 0 ? offset : 0;
buffer->size = size;
}
};
@@ -60,14 +153,14 @@ TEST_F(WVCryptoPluginTest, CorrectlyReportsSecureBuffers) {
// Provide the expected behavior for IsOpenSession
EXPECT_CALL(*cdm, IsOpenSession(_))
.WillRepeatedly(Return(true));
.WillRepeatedly(testing::Return(true));
// Specify the expected calls to QuerySessionStatus
EXPECT_CALL(*cdm, QuerySessionStatus(_, _))
.WillOnce(DoAll(SetArgPointee<1>(l1Map),
Return(wvcdm::NO_ERROR)))
testing::Return(wvcdm::NO_ERROR)))
.WillOnce(DoAll(SetArgPointee<1>(l3Map),
Return(wvcdm::NO_ERROR)));
testing::Return(wvcdm::NO_ERROR)));
WVCryptoPlugin plugin(sessionId, kSessionIdSize, cdm.get());
@@ -87,20 +180,26 @@ class CDPMatcherFactory {
// to re-specify them at every call site, we pass them into the factory
// constructor.
CDPMatcherFactory(bool isSecure, CdmCipherMode cipherMode, uint8_t* keyId,
void* out, size_t outLen)
void* out, size_t outLen, bool isVideo)
: mIsSecure(isSecure), mCipherMode(cipherMode), mKeyId(keyId),
mOut(out), mOutLen(outLen) {}
mOut(out), mOutLen(outLen), mIsVideo(isVideo) {}
Matcher<const CdmDecryptionParameters&> operator()(bool isEncrypted,
uint8_t* in,
size_t inLen,
uint8_t* iv,
size_t blockOffset,
size_t outOffset,
uint8_t flags) const {
return Truly(CDPMatcher(mIsSecure, mCipherMode, mKeyId, mOut, mOutLen,
isEncrypted, in, inLen, iv, blockOffset,
outOffset, flags));
testing::Matcher<const CdmDecryptionParameters&> operator()(
bool isEncrypted,
uint8_t* in,
size_t inLen,
uint8_t* iv,
size_t blockOffset,
size_t outOffset,
uint8_t flags,
CdmCencPatternEncryptionDescriptor& cdmPatternDesc) const {
// TODO b/28295739
// Add New MediaCrypto Unit Tests for CBC & Pattern Mode
// in cdmPatternDesc.
return testing::Truly(CDPMatcher(mIsSecure, mCipherMode, mKeyId, mOut,
mOutLen, isEncrypted, in, inLen, iv,
blockOffset, outOffset, flags, mIsVideo,
cdmPatternDesc));
}
private:
@@ -112,25 +211,32 @@ class CDPMatcherFactory {
CDPMatcher(bool isSecure, CdmCipherMode cipherMode, uint8_t* keyId,
void* out, size_t outLen, bool isEncrypted, uint8_t* in,
size_t inLen, uint8_t* iv, size_t blockOffset,
size_t outOffset, uint8_t flags)
size_t outOffset, uint8_t flags, bool isVideo,
CdmCencPatternEncryptionDescriptor& cdmPatternDesc)
: mIsSecure(isSecure), mCipherMode(cipherMode), mKeyId(keyId),
mOut(out), mOutLen(outLen), mIsEncrypted(isEncrypted), mIn(in),
mInLen(inLen), mIv(iv), mBlockOffset(blockOffset),
mOutOffset(outOffset), mFlags(flags) {}
mOutOffset(outOffset), mFlags(flags), mIsVideo(isVideo),
mCdmPatternDesc(cdmPatternDesc) {}
bool operator()(const CdmDecryptionParameters& params) const {
return params.is_secure == mIsSecure &&
params.cipher_mode == mCipherMode &&
Value(*params.key_id, ElementsAreArray(mKeyId, KEY_ID_SIZE)) &&
params.decrypt_buffer == mOut &&
// TODO b/35259313
// Converts mOut from hidl address to physical address
// params.decrypt_buffer == mOut &&
params.decrypt_buffer_length == mOutLen &&
params.is_encrypted == mIsEncrypted &&
params.encrypt_buffer == mIn &&
// TODO b/35259313
// Converts mIn from hidl address to physical address
// params.encrypt_buffer == mIn &&
params.encrypt_length == mInLen &&
Value(*params.iv, ElementsAreArray(mIv, KEY_IV_SIZE)) &&
params.block_offset == mBlockOffset &&
params.decrypt_buffer_offset == mOutOffset &&
params.subsample_flags == mFlags;
params.subsample_flags == mFlags &&
params.is_video == mIsVideo;
}
private:
@@ -146,6 +252,8 @@ class CDPMatcherFactory {
size_t mBlockOffset;
size_t mOutOffset;
uint8_t mFlags;
bool mIsVideo;
CdmCencPatternEncryptionDescriptor mCdmPatternDesc;
};
bool mIsSecure;
@@ -153,17 +261,34 @@ class CDPMatcherFactory {
uint8_t* mKeyId;
void* mOut;
size_t mOutLen;
bool mIsVideo;
};
TEST_F(WVCryptoPluginTest, AttemptsToDecrypt) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
uint8_t keyId[KEY_ID_SIZE];
static const size_t kSubSampleCount = 6;
SubSample subSamples[kSubSampleCount];
memset(subSamples, 0, sizeof(subSamples));
subSamples[0].numBytesOfEncryptedData = 16;
subSamples[1].numBytesOfClearData = 16;
subSamples[1].numBytesOfEncryptedData = 16;
subSamples[2].numBytesOfEncryptedData = 8;
subSamples[3].numBytesOfClearData = 29;
subSamples[3].numBytesOfEncryptedData = 24;
subSamples[4].numBytesOfEncryptedData = 60;
subSamples[5].numBytesOfEncryptedData = 16;
std::vector<SubSample> subSamplesVector(
subSamples, subSamples + sizeof(subSamples) / sizeof(subSamples[0]));
auto hSubSamples = hidl_vec<SubSample>(subSamplesVector);
uint8_t baseIv[KEY_IV_SIZE];
uint8_t keyId[KEY_ID_SIZE];
static const size_t kDataSize = 185;
uint8_t in[kDataSize];
uint8_t out[kDataSize];
FILE* fp = fopen("/dev/urandom", "r");
fread(keyId, sizeof(uint8_t), KEY_ID_SIZE, fp);
@@ -171,17 +296,20 @@ TEST_F(WVCryptoPluginTest, AttemptsToDecrypt) {
fread(in, sizeof(uint8_t), kDataSize, fp);
fclose(fp);
static const size_t kSubSampleCount = 6;
CryptoPlugin::SubSample subSamples[kSubSampleCount];
memset(subSamples, 0, sizeof(subSamples));
subSamples[0].mNumBytesOfEncryptedData = 16;
subSamples[1].mNumBytesOfClearData = 16;
subSamples[1].mNumBytesOfEncryptedData = 16;
subSamples[2].mNumBytesOfEncryptedData = 8;
subSamples[3].mNumBytesOfClearData = 29;
subSamples[3].mNumBytesOfEncryptedData = 24;
subSamples[4].mNumBytesOfEncryptedData = 60;
subSamples[5].mNumBytesOfEncryptedData = 16;
sp<MemoryDealer> memDealer = new MemoryDealer(
kDataSize * 2, "WVCryptoPlugin_test");
sp<android::IMemory> source = memDealer->allocate(kDataSize);
ASSERT_NE(source, nullptr);
pSrc = static_cast<uint8_t*>(
static_cast<void*>(source->pointer()));
ASSERT_NE(pSrc, nullptr);
memcpy(pSrc, in, source->size());
sp<android::IMemory> destination = memDealer->allocate(kDataSize);
ASSERT_NE(destination, nullptr);
pDest = static_cast<uint8_t*>(
static_cast<void*>(destination->pointer()));
ASSERT_NE(pDest, nullptr);
uint8_t iv[5][KEY_IV_SIZE];
memcpy(iv[0], baseIv, sizeof(baseIv));
@@ -195,12 +323,13 @@ TEST_F(WVCryptoPluginTest, AttemptsToDecrypt) {
memcpy(iv[4], baseIv, sizeof(baseIv));
iv[4][15] = 7;
CdmCencPatternEncryptionDescriptor cdmPattern;
CDPMatcherFactory ParamsAre =
CDPMatcherFactory(false, kCipherModeCtr, keyId, out, kDataSize);
CDPMatcherFactory(false, kCipherModeCtr, keyId, pDest, kDataSize, true);
// Provide the expected behavior for IsOpenSession
EXPECT_CALL(*cdm, IsOpenSession(_))
.WillRepeatedly(Return(true));
.WillRepeatedly(testing::Return(true));
// Specify the expected calls to Decrypt
{
@@ -209,67 +338,86 @@ TEST_F(WVCryptoPluginTest, AttemptsToDecrypt) {
// SubSample 0
EXPECT_CALL(*cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize),
true,
ParamsAre(true, in, 16, iv[0], 0, 0,
OEMCrypto_FirstSubsample)))
ParamsAre(true, pSrc, 16, iv[0], 0, 0,
OEMCrypto_FirstSubsample, cdmPattern)))
.Times(1);
// SubSample 1
EXPECT_CALL(*cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize),
true,
ParamsAre(false, in + 16, 16, iv[1], 0, 16, 0)))
ParamsAre(false, pSrc + 16, 16, iv[1], 0, 16, 0,
cdmPattern)))
.Times(1);
EXPECT_CALL(*cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize),
true,
ParamsAre(true, in + 32, 16, iv[1], 0, 32, 0)))
ParamsAre(true, pSrc + 32, 16, iv[1], 0, 32, 0,
cdmPattern)))
.Times(1);
// SubSample 2
EXPECT_CALL(*cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize),
true,
ParamsAre(true, in + 48, 8, iv[2], 0, 48, 0)))
ParamsAre(true, pSrc + 48, 8, iv[2], 0, 48, 0,
cdmPattern)))
.Times(1);
// SubSample 3
EXPECT_CALL(*cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize),
true,
ParamsAre(false, in + 56, 29, iv[2], 0, 56, 0)))
ParamsAre(false, pSrc + 56, 29, iv[2], 0, 56, 0,
cdmPattern)))
.Times(1);
EXPECT_CALL(*cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize),
true,
ParamsAre(true, in + 85, 24, iv[2], 8, 85, 0)))
ParamsAre(true, pSrc + 85, 24, iv[2], 8, 85, 0,
cdmPattern)))
.Times(1);
// SubSample 4
EXPECT_CALL(*cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize),
true,
ParamsAre(true, in + 109, 60, iv[3], 0, 109, 0)))
ParamsAre(true, pSrc + 109, 60, iv[3], 0, 109, 0,
cdmPattern)))
.Times(1);
// SubSample 5
EXPECT_CALL(*cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize),
true,
ParamsAre(true, in + 169, 16, iv[4], 12, 169,
OEMCrypto_LastSubsample)))
ParamsAre(true, pSrc + 169, 16, iv[4], 12, 169,
OEMCrypto_LastSubsample, cdmPattern)))
.Times(1);
}
WVCryptoPlugin plugin(sessionId, kSessionIdSize, cdm.get());
android::CryptoPlugin::Pattern noPattern = {0};
AString errorDetailMessage;
ssize_t res = plugin.decrypt(false, keyId, iv[0], CryptoPlugin::kMode_AES_CTR,
noPattern, in, subSamples, kSubSampleCount,
out, &errorDetailMessage);
uint32_t bytesWritten = 0;
std::string errorDetailMessage;
DestinationBuffer hDestination;
hDestination.type = BufferType::SHARED_MEMORY;
toSharedBuffer(plugin, destination, &hDestination.nonsecureMemory);
Pattern noPattern = { 0, 0 };
EXPECT_EQ(static_cast<ssize_t>(kDataSize), res) <<
"WVCryptoPlugin decrypted the wrong number of bytes";
SharedBuffer sourceBuffer;
toSharedBuffer(plugin, source, &sourceBuffer);
plugin.decrypt(
false, hidl_array<uint8_t, 16>(keyId), hidl_array<uint8_t, 16>(iv[0]),
Mode::AES_CTR, noPattern, hSubSamples, sourceBuffer, 0, hDestination,
[&](Status status, uint32_t hBytesWritten, hidl_string hDetailedError) {
EXPECT_EQ(status, Status::OK);
bytesWritten = hBytesWritten;
errorDetailMessage.assign(hDetailedError.c_str());
});
EXPECT_EQ(kDataSize, bytesWritten) <<
"WVCryptoPlugin decrypted the wrong number of bytes";
EXPECT_EQ(0u, errorDetailMessage.size()) <<
"WVCryptoPlugin reported a detailed error message.";
"WVCryptoPlugin reported a detailed error message.";
}
TEST_F(WVCryptoPluginTest, CommunicatesSecureBufferRequest) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
@@ -278,7 +426,6 @@ TEST_F(WVCryptoPluginTest, CommunicatesSecureBufferRequest) {
static const size_t kDataSize = 32;
uint8_t in[kDataSize];
uint8_t out[kDataSize];
FILE* fp = fopen("/dev/urandom", "r");
fread(keyId, sizeof(uint8_t), KEY_ID_SIZE, fp);
@@ -286,15 +433,16 @@ TEST_F(WVCryptoPluginTest, CommunicatesSecureBufferRequest) {
fread(in, sizeof(uint8_t), kDataSize, fp);
fclose(fp);
static const uint32_t kSubSampleCount = 1;
CryptoPlugin::SubSample subSamples[kSubSampleCount];
memset(subSamples, 0, sizeof(subSamples));
subSamples[0].mNumBytesOfClearData = 16;
subSamples[0].mNumBytesOfEncryptedData = 16;
SubSample subSample;
subSample.numBytesOfClearData = 16;
subSample.numBytesOfEncryptedData = 16;
std::vector<SubSample> subSampleVector;
subSampleVector.push_back(subSample);
auto hSubSamples = hidl_vec<SubSample>(subSampleVector);
// Provide the expected behavior for IsOpenSession
EXPECT_CALL(*cdm, IsOpenSession(_))
.WillRepeatedly(Return(true));
.WillRepeatedly(testing::Return(true));
// Specify the expected calls to Decrypt
{
@@ -309,25 +457,59 @@ TEST_F(WVCryptoPluginTest, CommunicatesSecureBufferRequest) {
.Times(2);
}
sp<MemoryDealer> memDealer = new MemoryDealer(
kDataSize * 2, "WVCryptoPlugin_test");
sp<android::IMemory> source = memDealer->allocate(kDataSize);
ASSERT_NE(source, nullptr);
pSrc = static_cast<uint8_t*>(
static_cast<void*>(source->pointer()));
ASSERT_NE(pSrc, nullptr);
memcpy(pSrc, in, source->size());
sp<android::IMemory> destination = memDealer->allocate(kDataSize);
ASSERT_NE(destination, nullptr);
pDest = static_cast<uint8_t*>(
static_cast<void*>(destination->pointer()));
ASSERT_NE(pDest, nullptr);
WVCryptoPlugin plugin(sessionId, kSessionIdSize, cdm.get());
android::CryptoPlugin::Pattern noPattern = {0};
AString errorDetailMessage;
ssize_t res = plugin.decrypt(false, keyId, iv, CryptoPlugin::kMode_AES_CTR,
noPattern, in, subSamples, kSubSampleCount,
out, &errorDetailMessage);
ASSERT_GE(res, 0) <<
"WVCryptoPlugin returned an error";
EXPECT_EQ(0u, errorDetailMessage.size()) <<
"WVCryptoPlugin reported a detailed error message.";
uint32_t bytesWritten = 0;
Status err = Status::OK;
std::string errorDetailMessage;
DestinationBuffer hDestination;
hDestination.type = BufferType::SHARED_MEMORY;
toSharedBuffer(plugin, destination, &hDestination.nonsecureMemory);
Pattern noPattern = { 0, 0 };
SharedBuffer sourceBuffer;
toSharedBuffer(plugin, source, &sourceBuffer);
plugin.decrypt(
false, hidl_array<uint8_t, 16>(keyId), hidl_array<uint8_t, 16>(iv),
Mode::AES_CTR, noPattern, hSubSamples, sourceBuffer, 0, hDestination,
[&](Status status, uint32_t hBytesWritten, hidl_string hDetailedError) {
EXPECT_EQ(status, Status::OK);
bytesWritten = hBytesWritten;
errorDetailMessage.assign(hDetailedError.c_str());
});
res = plugin.decrypt(true, keyId, iv, CryptoPlugin::kMode_AES_CTR,
noPattern, in, subSamples, kSubSampleCount, out,
&errorDetailMessage);
ASSERT_GE(res, 0) <<
"WVCryptoPlugin returned an error";
EXPECT_EQ(0u, errorDetailMessage.size()) <<
"WVCryptoPlugin reported a detailed error message.";
"WVCryptoPlugin reported a detailed error message.";
plugin.decrypt(
true, hidl_array<uint8_t, 16>(keyId), hidl_array<uint8_t, 16>(iv),
Mode::AES_CTR, noPattern, hSubSamples, sourceBuffer, 0, hDestination,
[&](Status status, uint32_t hBytesWritten, hidl_string hDetailedError) {
EXPECT_EQ(status, Status::OK);
bytesWritten = hBytesWritten;
errorDetailMessage.assign(hDetailedError.c_str());
});
EXPECT_EQ(0u, errorDetailMessage.size()) <<
"WVCryptoPlugin reported a detailed error message.";
}
TEST_F(WVCryptoPluginTest, SetsFlagsForMinimumSubsampleRuns) {
@@ -338,7 +520,6 @@ TEST_F(WVCryptoPluginTest, SetsFlagsForMinimumSubsampleRuns) {
static const size_t kDataSize = 16;
uint8_t in[kDataSize];
uint8_t out[kDataSize];
FILE* fp = fopen("/dev/urandom", "r");
fread(keyId, sizeof(uint8_t), KEY_ID_SIZE, fp);
@@ -346,23 +527,30 @@ TEST_F(WVCryptoPluginTest, SetsFlagsForMinimumSubsampleRuns) {
fread(in, sizeof(uint8_t), kDataSize, fp);
fclose(fp);
static const uint32_t kSubSampleCount = 1;
CryptoPlugin::SubSample clearSubSamples[kSubSampleCount];
memset(clearSubSamples, 0, sizeof(clearSubSamples));
clearSubSamples[0].mNumBytesOfClearData = 16;
SubSample clearSubSample;
clearSubSample.numBytesOfClearData = 16;
clearSubSample.numBytesOfEncryptedData = 0;
std::vector<SubSample> clearSubSampleVector;
clearSubSampleVector.push_back(clearSubSample);
auto hClearSubSamples = hidl_vec<SubSample>(clearSubSampleVector);
CryptoPlugin::SubSample encryptedSubSamples[kSubSampleCount];
memset(encryptedSubSamples, 0, sizeof(encryptedSubSamples));
encryptedSubSamples[0].mNumBytesOfEncryptedData = 16;
SubSample encryptedSubSample;
encryptedSubSample.numBytesOfClearData = 0;
encryptedSubSample.numBytesOfEncryptedData = 16;
std::vector<SubSample> encryptedSubSampleVector;
encryptedSubSampleVector.push_back(encryptedSubSample);
auto hEncryptedSubSamples = hidl_vec<SubSample>(encryptedSubSampleVector);
CryptoPlugin::SubSample mixedSubSamples[kSubSampleCount];
memset(mixedSubSamples, 0, sizeof(mixedSubSamples));
mixedSubSamples[0].mNumBytesOfClearData = 8;
mixedSubSamples[0].mNumBytesOfEncryptedData = 8;
SubSample mixedSubSample;
mixedSubSample.numBytesOfClearData = 8;
mixedSubSample.numBytesOfEncryptedData = 8;
std::vector<SubSample> mixedSubSampleVector;
mixedSubSampleVector.push_back(mixedSubSample);
auto hMixedSubSamples = hidl_vec<SubSample>(mixedSubSampleVector);
// Provide the expected behavior for IsOpenSession
EXPECT_CALL(*cdm, IsOpenSession(_))
.WillRepeatedly(Return(true));
.WillRepeatedly(testing::Return(true));
// Specify the expected calls to Decrypt
{
@@ -371,46 +559,86 @@ TEST_F(WVCryptoPluginTest, SetsFlagsForMinimumSubsampleRuns) {
typedef CdmDecryptionParameters CDP;
EXPECT_CALL(*cdm, Decrypt(_, _, Field(&CDP::subsample_flags,
OEMCrypto_FirstSubsample |
OEMCrypto_LastSubsample)))
OEMCrypto_FirstSubsample |
OEMCrypto_LastSubsample)))
.Times(2);
EXPECT_CALL(*cdm, Decrypt(_, _, Field(&CDP::subsample_flags,
OEMCrypto_FirstSubsample)))
OEMCrypto_FirstSubsample)))
.Times(1);
EXPECT_CALL(*cdm, Decrypt(_, _, Field(&CDP::subsample_flags,
OEMCrypto_LastSubsample)))
OEMCrypto_LastSubsample)))
.Times(1);
}
sp<MemoryDealer> memDealer = new MemoryDealer(
kDataSize * 2, "WVCryptoPlugin_test");
sp<android::IMemory> source = memDealer->allocate(kDataSize);
ASSERT_NE(source, nullptr);
pSrc = static_cast<uint8_t*>(
static_cast<void*>(source->pointer()));
ASSERT_NE(pSrc, nullptr);
memcpy(pSrc, in, source->size());
sp<android::IMemory> destination = memDealer->allocate(kDataSize);
ASSERT_NE(destination, nullptr);
pDest = static_cast<uint8_t*>(
static_cast<void*>(destination->pointer()));
ASSERT_NE(pDest, nullptr);
WVCryptoPlugin plugin(sessionId, kSessionIdSize, cdm.get());
android::CryptoPlugin::Pattern noPattern = {0};
AString errorDetailMessage;
ssize_t res = plugin.decrypt(false, keyId, iv, CryptoPlugin::kMode_AES_CTR,
noPattern, in, clearSubSamples,
kSubSampleCount, out, &errorDetailMessage);
ASSERT_GE(res, 0) <<
"WVCryptoPlugin returned an error";
EXPECT_EQ(0u, errorDetailMessage.size()) <<
"WVCryptoPlugin reported a detailed error message.";
uint32_t bytesWritten = 0;
Status err = Status::OK;
std::string errorDetailMessage;
DestinationBuffer hDestination;
hDestination.type = BufferType::SHARED_MEMORY;
toSharedBuffer(plugin, destination, &hDestination.nonsecureMemory);
Pattern noPattern = { 0, 0 };
res = plugin.decrypt(false, keyId, iv, CryptoPlugin::kMode_AES_CTR,
noPattern, in, encryptedSubSamples, kSubSampleCount,
out, &errorDetailMessage);
ASSERT_GE(res, 0) <<
"WVCryptoPlugin returned an error";
EXPECT_EQ(0u, errorDetailMessage.size()) <<
"WVCryptoPlugin reported a detailed error message.";
SharedBuffer sourceBuffer;
toSharedBuffer(plugin, source, &sourceBuffer);
plugin.decrypt(
false, hidl_array<uint8_t, 16>(keyId), hidl_array<uint8_t, 16>(iv),
Mode::AES_CTR, noPattern, hClearSubSamples, sourceBuffer, 0, hDestination,
[&](Status status, uint32_t hBytesWritten, hidl_string hDetailedError) {
EXPECT_EQ(status, Status::OK);
bytesWritten = hBytesWritten;
errorDetailMessage.assign(hDetailedError.c_str());
});
res = plugin.decrypt(false, keyId, iv, CryptoPlugin::kMode_AES_CTR,
noPattern, in, mixedSubSamples, kSubSampleCount, out,
&errorDetailMessage);
ASSERT_GE(res, 0) <<
"WVCryptoPlugin returned an error";
EXPECT_EQ(0u, errorDetailMessage.size()) <<
"WVCryptoPlugin reported a detailed error message.";
"WVCryptoPlugin reported a detailed error message.";
plugin.decrypt(
false, hidl_array<uint8_t, 16>(keyId), hidl_array<uint8_t, 16>(iv),
Mode::AES_CTR, noPattern, hEncryptedSubSamples, sourceBuffer, 0,
hDestination,
[&](Status status, uint32_t hBytesWritten, hidl_string hDetailedError) {
EXPECT_EQ(status, Status::OK);
bytesWritten = hBytesWritten;
errorDetailMessage.assign(hDetailedError.c_str());
});
EXPECT_EQ(0u, errorDetailMessage.size()) <<
"WVCryptoPlugin reported a detailed error message.";
plugin.decrypt(
false, hidl_array<uint8_t, 16>(keyId), hidl_array<uint8_t, 16>(iv),
Mode::AES_CTR, noPattern, hMixedSubSamples, sourceBuffer, 0, hDestination,
[&](Status status, uint32_t hBytesWritten, hidl_string hDetailedError) {
EXPECT_EQ(status, Status::OK);
bytesWritten = hBytesWritten;
errorDetailMessage.assign(hDetailedError.c_str());
});
EXPECT_EQ(0u, errorDetailMessage.size()) <<
"WVCryptoPlugin reported a detailed error message.";
}
TEST_F(WVCryptoPluginTest, AllowsSessionIdChanges) {
@@ -422,7 +650,6 @@ TEST_F(WVCryptoPluginTest, AllowsSessionIdChanges) {
static const size_t kDataSize = 32;
uint8_t in[kDataSize];
uint8_t out[kDataSize];
FILE* fp = fopen("/dev/urandom", "r");
fread(keyId, sizeof(uint8_t), KEY_ID_SIZE, fp);
@@ -431,20 +658,20 @@ TEST_F(WVCryptoPluginTest, AllowsSessionIdChanges) {
fread(in, sizeof(uint8_t), kDataSize, fp);
fclose(fp);
static const uint32_t kSubSampleCount = 1;
CryptoPlugin::SubSample subSamples[kSubSampleCount];
memset(subSamples, 0, sizeof(subSamples));
subSamples[0].mNumBytesOfClearData = 16;
subSamples[0].mNumBytesOfEncryptedData = 16;
SubSample subSample;
subSample.numBytesOfClearData = 16;
subSample.numBytesOfEncryptedData = 16;
std::vector<SubSample> subSampleVector;
subSampleVector.push_back(subSample);
auto hSubSamples = hidl_vec<SubSample>(subSampleVector);
Vector<uint8_t> sessionIdVector;
sessionIdVector.appendArray(sessionId, kSessionIdSize);
Vector<uint8_t> sessionId2Vector;
sessionId2Vector.appendArray(sessionId2, kSessionIdSize);
std::vector<uint8_t> sessionIdVector(sessionId, sessionId + kSessionIdSize);
std::vector<uint8_t> sessionId2Vector(sessionId2,
sessionId2 + kSessionIdSize);
// Provide the expected behavior for IsOpenSession
EXPECT_CALL(*cdm, IsOpenSession(_))
.WillRepeatedly(Return(true));
.WillRepeatedly(testing::Return(true));
// Specify the expected calls to Decrypt
{
@@ -459,57 +686,97 @@ TEST_F(WVCryptoPluginTest, AllowsSessionIdChanges) {
.Times(2);
}
sp<MemoryDealer> memDealer = new MemoryDealer(
kDataSize * 2, "WVCryptoPlugin_test");
sp<android::IMemory> source = memDealer->allocate(kDataSize);
ASSERT_NE(source, nullptr);
pSrc = static_cast<uint8_t*>(
static_cast<void*>(source->pointer()));
ASSERT_NE(pSrc, nullptr);
memcpy(pSrc, in, source->size());
sp<android::IMemory> destination = memDealer->allocate(kDataSize);
ASSERT_NE(destination, nullptr);
pDest = static_cast<uint8_t*>(
static_cast<void*>(destination->pointer()));
ASSERT_NE(pDest, nullptr);
uint8_t blank[1]; // Some compilers will not accept 0.
WVCryptoPlugin plugin(blank, 0, cdm.get());
android::CryptoPlugin::Pattern noPattern = {0};
AString errorDetailMessage;
ssize_t res;
res = plugin.setMediaDrmSession(sessionIdVector);
EXPECT_EQ(android::NO_ERROR, res);
res = plugin.decrypt(false, keyId, iv, CryptoPlugin::kMode_AES_CTR,
noPattern, in, subSamples, kSubSampleCount, out,
&errorDetailMessage);
EXPECT_GE(res, 0) <<
"WVCryptoPlugin returned an error";
EXPECT_EQ(0u, errorDetailMessage.size()) <<
"WVCryptoPlugin reported a detailed error message.";
uint32_t bytesWritten = 0;
Status err = Status::OK;
std::string errorDetailMessage;
DestinationBuffer hDestination;
hDestination.type = BufferType::SHARED_MEMORY;
toSharedBuffer(plugin, destination, &hDestination.nonsecureMemory);
Pattern noPattern = { 0, 0 };
SharedBuffer sourceBuffer;
toSharedBuffer(plugin, source, &sourceBuffer);
Status status = plugin.setMediaDrmSession(sessionIdVector);
EXPECT_EQ(status, Status::OK);
plugin.decrypt(
false, hidl_array<uint8_t, 16>(keyId), hidl_array<uint8_t, 16>(iv),
Mode::AES_CTR, noPattern, hSubSamples, sourceBuffer, 0, hDestination,
[&](Status status, uint32_t hBytesWritten, hidl_string hDetailedError) {
EXPECT_EQ(status, Status::OK);
bytesWritten = hBytesWritten;
errorDetailMessage.assign(hDetailedError.c_str());
});
res = plugin.setMediaDrmSession(sessionId2Vector);
EXPECT_EQ(android::NO_ERROR, res);
res = plugin.decrypt(false, keyId, iv, CryptoPlugin::kMode_AES_CTR,
noPattern, in, subSamples, kSubSampleCount, out,
&errorDetailMessage);
EXPECT_GE(res, 0) <<
"WVCryptoPlugin returned an error";
EXPECT_EQ(0u, errorDetailMessage.size()) <<
"WVCryptoPlugin reported a detailed error message.";
"WVCryptoPlugin reported a detailed error message.";
status = plugin.setMediaDrmSession(sessionId2Vector);
EXPECT_EQ(status, Status::OK);
plugin.decrypt(
false, hidl_array<uint8_t, 16>(keyId), hidl_array<uint8_t, 16>(iv),
Mode::AES_CTR, noPattern, hSubSamples, sourceBuffer, 0, hDestination,
[&](Status status, uint32_t hBytesWritten, hidl_string hDetailedError) {
EXPECT_EQ(status, Status::OK);
bytesWritten = hBytesWritten;
errorDetailMessage.assign(hDetailedError.c_str());
});
EXPECT_EQ(0u, errorDetailMessage.size()) <<
"WVCryptoPlugin reported a detailed error message.";
}
TEST_F(WVCryptoPluginTest, DisallowsUnopenedSessionIdChanges) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
uint8_t blank[1]; // Some compilers will not accept 0.
Vector<uint8_t> sessionIdVector;
sessionIdVector.appendArray(sessionId, kSessionIdSize);
std::vector<uint8_t> sessionIdVector(sessionId, sessionId + kSessionIdSize);
// Specify the expected calls to IsOpenSession
{
InSequence calls;
EXPECT_CALL(*cdm, IsOpenSession(ElementsAreArray(blank, 0)))
.WillOnce(Return(false));
.WillOnce(testing::Return(false));
EXPECT_CALL(*cdm, IsOpenSession(ElementsAreArray(sessionId, kSessionIdSize)))
.WillOnce(Return(false))
.WillOnce(Return(true));
.WillOnce(testing::Return(false))
.WillOnce(testing::Return(true));
}
WVCryptoPlugin plugin(blank, 0, cdm.get());
ssize_t res;
res = plugin.setMediaDrmSession(sessionIdVector);
EXPECT_EQ(android::ERROR_DRM_SESSION_NOT_OPENED, res);
res = plugin.setMediaDrmSession(sessionIdVector);
EXPECT_EQ(android::NO_ERROR, res);
Status status = plugin.setMediaDrmSession(sessionIdVector);
EXPECT_EQ(status, Status::ERROR_DRM_SESSION_NOT_OPENED);
status = plugin.setMediaDrmSession(sessionIdVector);
EXPECT_EQ(status, Status::OK);
}
} // namespace widevine
} // namespace V1_0
} // namespace drm
} // namespace hardware
} // namespace wvdrm

View File

@@ -0,0 +1,515 @@
//
// Copyright 2013 Google Inc. All Rights Reserved.
//
#include <stdio.h>
#include <string>
#include <vector>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "media/stagefright/foundation/ABase.h"
#include "media/stagefright/foundation/AString.h"
#include "media/stagefright/MediaErrors.h"
#include "OEMCryptoCENC.h"
#include "wv_cdm_constants.h"
#include "wv_cdm_types.h"
#include "wv_content_decryption_module.h"
#include "WVCryptoPlugin.h"
using namespace android;
using namespace std;
using namespace testing;
using namespace wvcdm;
using namespace wvdrm;
class MockCDM : public WvContentDecryptionModule {
public:
MOCK_METHOD1(IsOpenSession, bool(const CdmSessionId&));
MOCK_METHOD3(Decrypt, CdmResponseType(const CdmSessionId&, bool,
const CdmDecryptionParameters&));
MOCK_METHOD2(QuerySessionStatus, CdmResponseType(const CdmSessionId&,
CdmQueryMap*));
};
class WVCryptoPluginTest : public Test {
protected:
static const uint32_t kSessionIdSize = 16;
uint8_t sessionId[kSessionIdSize];
virtual void SetUp() {
FILE* fp = fopen("/dev/urandom", "r");
fread(sessionId, sizeof(uint8_t), kSessionIdSize, fp);
fclose(fp);
// Set default CdmResponseType value for gMock
DefaultValue<CdmResponseType>::Set(wvcdm::NO_ERROR);
}
};
TEST_F(WVCryptoPluginTest, CorrectlyReportsSecureBuffers) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
CdmQueryMap l1Map;
l1Map[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L1;
CdmQueryMap l3Map;
l3Map[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L3;
// Provide the expected behavior for IsOpenSession
EXPECT_CALL(*cdm, IsOpenSession(_))
.WillRepeatedly(Return(true));
// Specify the expected calls to QuerySessionStatus
EXPECT_CALL(*cdm, QuerySessionStatus(_, _))
.WillOnce(DoAll(SetArgPointee<1>(l1Map),
Return(wvcdm::NO_ERROR)))
.WillOnce(DoAll(SetArgPointee<1>(l3Map),
Return(wvcdm::NO_ERROR)));
WVCryptoPlugin plugin(sessionId, kSessionIdSize, cdm.get());
EXPECT_TRUE(plugin.requiresSecureDecoderComponent("video/mp4")) <<
"WVCryptoPlugin incorrectly allows an insecure video decoder on L1";
EXPECT_FALSE(plugin.requiresSecureDecoderComponent("video/mp4")) <<
"WVCryptoPlugin incorrectly expects a secure video decoder on L3";
EXPECT_FALSE(plugin.requiresSecureDecoderComponent("audio/aac")) <<
"WVCryptoPlugin incorrectly expects a secure audio decoder";
}
// Factory for matchers that perform deep matching of values against a
// CdmDecryptionParameters struct. For use in the test AttemptsToDecrypt.
class CDPMatcherFactory {
public:
// Some values do not change over the course of the test. To avoid having
// to re-specify them at every call site, we pass them into the factory
// constructor.
CDPMatcherFactory(bool isSecure, CdmCipherMode cipherMode, uint8_t* keyId,
void* out, size_t outLen)
: mIsSecure(isSecure), mCipherMode(cipherMode), mKeyId(keyId),
mOut(out), mOutLen(outLen) {}
Matcher<const CdmDecryptionParameters&> operator()(bool isEncrypted,
uint8_t* in,
size_t inLen,
uint8_t* iv,
size_t blockOffset,
size_t outOffset,
uint8_t flags) const {
return Truly(CDPMatcher(mIsSecure, mCipherMode, mKeyId, mOut, mOutLen,
isEncrypted, in, inLen, iv, blockOffset,
outOffset, flags));
}
private:
// Predicate that validates that the fields of a passed-in
// CdmDecryptionParameters match the values it was given at construction
// time.
class CDPMatcher {
public:
CDPMatcher(bool isSecure, CdmCipherMode cipherMode, uint8_t* keyId,
void* out, size_t outLen, bool isEncrypted, uint8_t* in,
size_t inLen, uint8_t* iv, size_t blockOffset,
size_t outOffset, uint8_t flags)
: mIsSecure(isSecure), mCipherMode(cipherMode), mKeyId(keyId),
mOut(out), mOutLen(outLen), mIsEncrypted(isEncrypted), mIn(in),
mInLen(inLen), mIv(iv), mBlockOffset(blockOffset),
mOutOffset(outOffset), mFlags(flags) {}
bool operator()(const CdmDecryptionParameters& params) const {
return params.is_secure == mIsSecure &&
params.cipher_mode == mCipherMode &&
Value(*params.key_id, ElementsAreArray(mKeyId, KEY_ID_SIZE)) &&
params.decrypt_buffer == mOut &&
params.decrypt_buffer_length == mOutLen &&
params.is_encrypted == mIsEncrypted &&
params.encrypt_buffer == mIn &&
params.encrypt_length == mInLen &&
Value(*params.iv, ElementsAreArray(mIv, KEY_IV_SIZE)) &&
params.block_offset == mBlockOffset &&
params.decrypt_buffer_offset == mOutOffset &&
params.subsample_flags == mFlags;
}
private:
bool mIsSecure;
CdmCipherMode mCipherMode;
uint8_t* mKeyId;
void* mOut;
size_t mOutLen;
bool mIsEncrypted;
uint8_t* mIn;
size_t mInLen;
uint8_t* mIv;
size_t mBlockOffset;
size_t mOutOffset;
uint8_t mFlags;
};
bool mIsSecure;
CdmCipherMode mCipherMode;
uint8_t* mKeyId;
void* mOut;
size_t mOutLen;
};
TEST_F(WVCryptoPluginTest, AttemptsToDecrypt) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
uint8_t keyId[KEY_ID_SIZE];
uint8_t baseIv[KEY_IV_SIZE];
static const size_t kDataSize = 185;
uint8_t in[kDataSize];
uint8_t out[kDataSize];
FILE* fp = fopen("/dev/urandom", "r");
fread(keyId, sizeof(uint8_t), KEY_ID_SIZE, fp);
fread(baseIv, sizeof(uint8_t), KEY_IV_SIZE, fp);
fread(in, sizeof(uint8_t), kDataSize, fp);
fclose(fp);
static const size_t kSubSampleCount = 6;
CryptoPlugin::SubSample subSamples[kSubSampleCount];
memset(subSamples, 0, sizeof(subSamples));
subSamples[0].mNumBytesOfEncryptedData = 16;
subSamples[1].mNumBytesOfClearData = 16;
subSamples[1].mNumBytesOfEncryptedData = 16;
subSamples[2].mNumBytesOfEncryptedData = 8;
subSamples[3].mNumBytesOfClearData = 29;
subSamples[3].mNumBytesOfEncryptedData = 24;
subSamples[4].mNumBytesOfEncryptedData = 60;
subSamples[5].mNumBytesOfEncryptedData = 16;
uint8_t iv[5][KEY_IV_SIZE];
memcpy(iv[0], baseIv, sizeof(baseIv));
iv[0][15] = 0;
memcpy(iv[1], baseIv, sizeof(baseIv));
iv[1][15] = 1;
memcpy(iv[2], baseIv, sizeof(baseIv));
iv[2][15] = 2;
memcpy(iv[3], baseIv, sizeof(baseIv));
iv[3][15] = 4;
memcpy(iv[4], baseIv, sizeof(baseIv));
iv[4][15] = 7;
CDPMatcherFactory ParamsAre =
CDPMatcherFactory(false, kCipherModeCtr, keyId, out, kDataSize);
// Provide the expected behavior for IsOpenSession
EXPECT_CALL(*cdm, IsOpenSession(_))
.WillRepeatedly(Return(true));
// Specify the expected calls to Decrypt
{
InSequence calls;
// SubSample 0
EXPECT_CALL(*cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize),
true,
ParamsAre(true, in, 16, iv[0], 0, 0,
OEMCrypto_FirstSubsample)))
.Times(1);
// SubSample 1
EXPECT_CALL(*cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize),
true,
ParamsAre(false, in + 16, 16, iv[1], 0, 16, 0)))
.Times(1);
EXPECT_CALL(*cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize),
true,
ParamsAre(true, in + 32, 16, iv[1], 0, 32, 0)))
.Times(1);
// SubSample 2
EXPECT_CALL(*cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize),
true,
ParamsAre(true, in + 48, 8, iv[2], 0, 48, 0)))
.Times(1);
// SubSample 3
EXPECT_CALL(*cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize),
true,
ParamsAre(false, in + 56, 29, iv[2], 0, 56, 0)))
.Times(1);
EXPECT_CALL(*cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize),
true,
ParamsAre(true, in + 85, 24, iv[2], 8, 85, 0)))
.Times(1);
// SubSample 4
EXPECT_CALL(*cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize),
true,
ParamsAre(true, in + 109, 60, iv[3], 0, 109, 0)))
.Times(1);
// SubSample 5
EXPECT_CALL(*cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize),
true,
ParamsAre(true, in + 169, 16, iv[4], 12, 169,
OEMCrypto_LastSubsample)))
.Times(1);
}
WVCryptoPlugin plugin(sessionId, kSessionIdSize, cdm.get());
android::CryptoPlugin::Pattern noPattern = {0};
AString errorDetailMessage;
ssize_t res = plugin.decrypt(false, keyId, iv[0], CryptoPlugin::kMode_AES_CTR,
noPattern, in, subSamples, kSubSampleCount,
out, &errorDetailMessage);
EXPECT_EQ(static_cast<ssize_t>(kDataSize), res) <<
"WVCryptoPlugin decrypted the wrong number of bytes";
EXPECT_EQ(0u, errorDetailMessage.size()) <<
"WVCryptoPlugin reported a detailed error message.";
}
TEST_F(WVCryptoPluginTest, CommunicatesSecureBufferRequest) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
uint8_t keyId[KEY_ID_SIZE];
uint8_t iv[KEY_IV_SIZE];
static const size_t kDataSize = 32;
uint8_t in[kDataSize];
uint8_t out[kDataSize];
FILE* fp = fopen("/dev/urandom", "r");
fread(keyId, sizeof(uint8_t), KEY_ID_SIZE, fp);
fread(iv, sizeof(uint8_t), KEY_IV_SIZE, fp);
fread(in, sizeof(uint8_t), kDataSize, fp);
fclose(fp);
static const uint32_t kSubSampleCount = 1;
CryptoPlugin::SubSample subSamples[kSubSampleCount];
memset(subSamples, 0, sizeof(subSamples));
subSamples[0].mNumBytesOfClearData = 16;
subSamples[0].mNumBytesOfEncryptedData = 16;
// Provide the expected behavior for IsOpenSession
EXPECT_CALL(*cdm, IsOpenSession(_))
.WillRepeatedly(Return(true));
// Specify the expected calls to Decrypt
{
InSequence calls;
typedef CdmDecryptionParameters CDP;
EXPECT_CALL(*cdm, Decrypt(_, _, Field(&CDP::is_secure, false)))
.Times(2);
EXPECT_CALL(*cdm, Decrypt(_, _, Field(&CDP::is_secure, true)))
.Times(2);
}
WVCryptoPlugin plugin(sessionId, kSessionIdSize, cdm.get());
android::CryptoPlugin::Pattern noPattern = {0};
AString errorDetailMessage;
ssize_t res = plugin.decrypt(false, keyId, iv, CryptoPlugin::kMode_AES_CTR,
noPattern, in, subSamples, kSubSampleCount,
out, &errorDetailMessage);
ASSERT_GE(res, 0) <<
"WVCryptoPlugin returned an error";
EXPECT_EQ(0u, errorDetailMessage.size()) <<
"WVCryptoPlugin reported a detailed error message.";
res = plugin.decrypt(true, keyId, iv, CryptoPlugin::kMode_AES_CTR,
noPattern, in, subSamples, kSubSampleCount, out,
&errorDetailMessage);
ASSERT_GE(res, 0) <<
"WVCryptoPlugin returned an error";
EXPECT_EQ(0u, errorDetailMessage.size()) <<
"WVCryptoPlugin reported a detailed error message.";
}
TEST_F(WVCryptoPluginTest, SetsFlagsForMinimumSubsampleRuns) {
android::sp<MockCDM> cdm = new MockCDM();
uint8_t keyId[KEY_ID_SIZE];
uint8_t iv[KEY_IV_SIZE];
static const size_t kDataSize = 16;
uint8_t in[kDataSize];
uint8_t out[kDataSize];
FILE* fp = fopen("/dev/urandom", "r");
fread(keyId, sizeof(uint8_t), KEY_ID_SIZE, fp);
fread(iv, sizeof(uint8_t), KEY_IV_SIZE, fp);
fread(in, sizeof(uint8_t), kDataSize, fp);
fclose(fp);
static const uint32_t kSubSampleCount = 1;
CryptoPlugin::SubSample clearSubSamples[kSubSampleCount];
memset(clearSubSamples, 0, sizeof(clearSubSamples));
clearSubSamples[0].mNumBytesOfClearData = 16;
CryptoPlugin::SubSample encryptedSubSamples[kSubSampleCount];
memset(encryptedSubSamples, 0, sizeof(encryptedSubSamples));
encryptedSubSamples[0].mNumBytesOfEncryptedData = 16;
CryptoPlugin::SubSample mixedSubSamples[kSubSampleCount];
memset(mixedSubSamples, 0, sizeof(mixedSubSamples));
mixedSubSamples[0].mNumBytesOfClearData = 8;
mixedSubSamples[0].mNumBytesOfEncryptedData = 8;
// Provide the expected behavior for IsOpenSession
EXPECT_CALL(*cdm, IsOpenSession(_))
.WillRepeatedly(Return(true));
// Specify the expected calls to Decrypt
{
InSequence calls;
typedef CdmDecryptionParameters CDP;
EXPECT_CALL(*cdm, Decrypt(_, _, Field(&CDP::subsample_flags,
OEMCrypto_FirstSubsample |
OEMCrypto_LastSubsample)))
.Times(2);
EXPECT_CALL(*cdm, Decrypt(_, _, Field(&CDP::subsample_flags,
OEMCrypto_FirstSubsample)))
.Times(1);
EXPECT_CALL(*cdm, Decrypt(_, _, Field(&CDP::subsample_flags,
OEMCrypto_LastSubsample)))
.Times(1);
}
WVCryptoPlugin plugin(sessionId, kSessionIdSize, cdm.get());
android::CryptoPlugin::Pattern noPattern = {0};
AString errorDetailMessage;
ssize_t res = plugin.decrypt(false, keyId, iv, CryptoPlugin::kMode_AES_CTR,
noPattern, in, clearSubSamples,
kSubSampleCount, out, &errorDetailMessage);
ASSERT_GE(res, 0) <<
"WVCryptoPlugin returned an error";
EXPECT_EQ(0u, errorDetailMessage.size()) <<
"WVCryptoPlugin reported a detailed error message.";
res = plugin.decrypt(false, keyId, iv, CryptoPlugin::kMode_AES_CTR,
noPattern, in, encryptedSubSamples, kSubSampleCount,
out, &errorDetailMessage);
ASSERT_GE(res, 0) <<
"WVCryptoPlugin returned an error";
EXPECT_EQ(0u, errorDetailMessage.size()) <<
"WVCryptoPlugin reported a detailed error message.";
res = plugin.decrypt(false, keyId, iv, CryptoPlugin::kMode_AES_CTR,
noPattern, in, mixedSubSamples, kSubSampleCount, out,
&errorDetailMessage);
ASSERT_GE(res, 0) <<
"WVCryptoPlugin returned an error";
EXPECT_EQ(0u, errorDetailMessage.size()) <<
"WVCryptoPlugin reported a detailed error message.";
}
TEST_F(WVCryptoPluginTest, AllowsSessionIdChanges) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
uint8_t keyId[KEY_ID_SIZE];
uint8_t iv[KEY_IV_SIZE];
uint8_t sessionId2[kSessionIdSize];
static const size_t kDataSize = 32;
uint8_t in[kDataSize];
uint8_t out[kDataSize];
FILE* fp = fopen("/dev/urandom", "r");
fread(keyId, sizeof(uint8_t), KEY_ID_SIZE, fp);
fread(iv, sizeof(uint8_t), KEY_IV_SIZE, fp);
fread(sessionId2, sizeof(uint8_t), kSessionIdSize, fp);
fread(in, sizeof(uint8_t), kDataSize, fp);
fclose(fp);
static const uint32_t kSubSampleCount = 1;
CryptoPlugin::SubSample subSamples[kSubSampleCount];
memset(subSamples, 0, sizeof(subSamples));
subSamples[0].mNumBytesOfClearData = 16;
subSamples[0].mNumBytesOfEncryptedData = 16;
Vector<uint8_t> sessionIdVector;
sessionIdVector.appendArray(sessionId, kSessionIdSize);
Vector<uint8_t> sessionId2Vector;
sessionId2Vector.appendArray(sessionId2, kSessionIdSize);
// Provide the expected behavior for IsOpenSession
EXPECT_CALL(*cdm, IsOpenSession(_))
.WillRepeatedly(Return(true));
// Specify the expected calls to Decrypt
{
InSequence calls;
EXPECT_CALL(*cdm,
Decrypt(ElementsAreArray(sessionId, kSessionIdSize), _, _))
.Times(2);
EXPECT_CALL(*cdm,
Decrypt(ElementsAreArray(sessionId2, kSessionIdSize), _, _))
.Times(2);
}
uint8_t blank[1]; // Some compilers will not accept 0.
WVCryptoPlugin plugin(blank, 0, cdm.get());
android::CryptoPlugin::Pattern noPattern = {0};
AString errorDetailMessage;
ssize_t res;
res = plugin.setMediaDrmSession(sessionIdVector);
EXPECT_EQ(android::NO_ERROR, res);
res = plugin.decrypt(false, keyId, iv, CryptoPlugin::kMode_AES_CTR,
noPattern, in, subSamples, kSubSampleCount, out,
&errorDetailMessage);
EXPECT_GE(res, 0) <<
"WVCryptoPlugin returned an error";
EXPECT_EQ(0u, errorDetailMessage.size()) <<
"WVCryptoPlugin reported a detailed error message.";
res = plugin.setMediaDrmSession(sessionId2Vector);
EXPECT_EQ(android::NO_ERROR, res);
res = plugin.decrypt(false, keyId, iv, CryptoPlugin::kMode_AES_CTR,
noPattern, in, subSamples, kSubSampleCount, out,
&errorDetailMessage);
EXPECT_GE(res, 0) <<
"WVCryptoPlugin returned an error";
EXPECT_EQ(0u, errorDetailMessage.size()) <<
"WVCryptoPlugin reported a detailed error message.";
}
TEST_F(WVCryptoPluginTest, DisallowsUnopenedSessionIdChanges) {
android::sp<StrictMock<MockCDM>> cdm = new StrictMock<MockCDM>();
uint8_t blank[1]; // Some compilers will not accept 0.
Vector<uint8_t> sessionIdVector;
sessionIdVector.appendArray(sessionId, kSessionIdSize);
// Specify the expected calls to IsOpenSession
{
InSequence calls;
EXPECT_CALL(*cdm, IsOpenSession(ElementsAreArray(blank, 0)))
.WillOnce(Return(false));
EXPECT_CALL(*cdm, IsOpenSession(ElementsAreArray(sessionId, kSessionIdSize)))
.WillOnce(Return(false))
.WillOnce(Return(true));
}
WVCryptoPlugin plugin(blank, 0, cdm.get());
ssize_t res;
res = plugin.setMediaDrmSession(sessionIdVector);
EXPECT_EQ(android::ERROR_DRM_SESSION_NOT_OPENED, res);
res = plugin.setMediaDrmSession(sessionIdVector);
EXPECT_EQ(android::NO_ERROR, res);
}