From 19407fdc10fd0b2fa1c375e007f62820866ebfd9 Mon Sep 17 00:00:00 2001 From: Edwin Wong Date: Tue, 3 May 2022 21:11:28 +0000 Subject: [PATCH] Add libwvdrmmediacrypto_hal_test for AIDL service. [ Merge from http://go/wvgerrit/151349 ] - move plugin and mCdm creation in SetUp() test fixture - replace StrictMock with NiceMock; otherwise, "uninteresting mock for isOpenSession" will return fail by default - replace .WillOnce() for isOpenSession() with .WillRepeatedly Test: m libwvdrmmediacrypto_hal_test -j128 Test: m libwvdrmmediacrypto_hal_test WV_UNITTESTS_BUILD_TARGET=hidl -j128 Test: adb push $(OUT)/data/nativetest/libwvdrmmediacrypto_hal_test /data/nativetest/. Test: adb shell LD_LIBRARY_PATH=/vendor/lib64 /data/nativetest/libwvdrmmediacrypto_hal_test Bug: 217247987 Change-Id: I8d7189473d52738645c73c6665f4f3f6a13042f0 --- libwvdrmengine/mediacrypto/test/Android.mk | 49 +- .../test/WVCryptoPlugin_hal_test.cpp | 661 ++++++++++++++++++ .../test/{ => hidl}/WVCryptoPlugin_test.cpp | 0 3 files changed, 709 insertions(+), 1 deletion(-) create mode 100644 libwvdrmengine/mediacrypto/test/WVCryptoPlugin_hal_test.cpp rename libwvdrmengine/mediacrypto/test/{ => hidl}/WVCryptoPlugin_test.cpp (100%) diff --git a/libwvdrmengine/mediacrypto/test/Android.mk b/libwvdrmengine/mediacrypto/test/Android.mk index 6b8e83d2..5baaf873 100644 --- a/libwvdrmengine/mediacrypto/test/Android.mk +++ b/libwvdrmengine/mediacrypto/test/Android.mk @@ -4,8 +4,11 @@ LOCAL_PATH := $(call my-dir) # include $(CLEAR_VARS) +WV_UNITTESTS_BUILD_TARGET?= +ifeq ($(WV_UNITTESTS_BUILD_TARGET), hidl) + LOCAL_SRC_FILES := \ - WVCryptoPlugin_test.cpp \ + hidl/WVCryptoPlugin_test.cpp \ LOCAL_C_INCLUDES := \ frameworks/av/include \ @@ -48,6 +51,50 @@ LOCAL_SHARED_LIBRARIES := \ libprotobuf-cpp-lite \ libutils \ +# build unit tests for Aidl +else + +LOCAL_SRC_FILES := \ + WVCryptoPlugin_hal_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/cdm/util/include \ + vendor/widevine/libwvdrmengine/aidl_include \ + vendor/widevine/libwvdrmengine/mediacrypto/aidl_include \ + vendor/widevine/libwvdrmengine/oemcrypto/include \ + +LOCAL_STATIC_LIBRARIES := \ + libaidlcommonsupport \ + libcdm \ + libcdm_protos \ + libcdm_utils \ + libjsmn \ + libgmock \ + libgmock_main \ + libgtest \ + libwvlevel3 \ + libwvdrmcryptoplugin_aidl \ + libwv_odk \ + +LOCAL_SHARED_LIBRARIES := \ + android.hardware.drm-V1-ndk \ + libbase \ + libbinder_ndk \ + libcrypto \ + libcutils \ + libdl \ + liblog \ + libprotobuf-cpp-lite \ + libutils \ + +# endif $(WV_UNITTESTS_BUILD_TARGET) +endif + LOCAL_HEADER_LIBRARIES := \ libstagefright_headers \ diff --git a/libwvdrmengine/mediacrypto/test/WVCryptoPlugin_hal_test.cpp b/libwvdrmengine/mediacrypto/test/WVCryptoPlugin_hal_test.cpp new file mode 100644 index 00000000..3861f77b --- /dev/null +++ b/libwvdrmengine/mediacrypto/test/WVCryptoPlugin_hal_test.cpp @@ -0,0 +1,661 @@ +// +// Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +// + +// #define LOG_NDEBUG 0 +#define LOG_TAG "WVCryptoPluginTest" +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "OEMCryptoCENC.h" +#include "WVCryptoPlugin.h" +#include "wv_cdm_constants.h" +#include "wv_cdm_types.h" +#include "wv_content_decryption_module.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace wvdrm { +namespace hardware { +namespace drm { +namespace widevine { + +using ::aidl::android::hardware::common::Ashmem; +using ::aidl::android::hardware::drm::DecryptArgs; +using ::aidl::android::hardware::drm::DestinationBuffer; +using ::aidl::android::hardware::drm::Mode; +using ::aidl::android::hardware::drm::Pattern; +using ::aidl::android::hardware::drm::SharedBuffer; +using ::aidl::android::hardware::drm::Status; +using ::aidl::android::hardware::drm::SubSample; + +using ::android::sp; + +using ::testing::_; +using ::testing::DefaultValue; +using ::testing::DoAll; +using ::testing::ElementsAreArray; +using ::testing::Field; +using ::testing::InSequence; +using ::testing::Matcher; +using ::testing::NiceMock; +using ::testing::SetArgPointee; +using ::testing::Test; +using ::testing::Truly; +using ::testing::Value; +using ::testing::internal::ElementsAreArrayMatcher; + +using wvcdm::CdmCipherMode; +using wvcdm::CdmDecryptionParametersV16; +using wvcdm::CdmDecryptionSample; +using wvcdm::CdmDecryptionSubsample; +using wvcdm::CdmQueryMap; +using wvcdm::CdmResponseType; +using wvcdm::CdmSessionId; +using wvcdm::kCipherModeCbc; +using wvcdm::kCipherModeCtr; +using wvcdm::KEY_ID_SIZE; +using wvcdm::KEY_IV_SIZE; +using wvcdm::KeyId; +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_METHOD(bool, IsOpenSession, (const CdmSessionId &), (override)); + + MOCK_METHOD(CdmResponseType, DecryptV16, + (const CdmSessionId &, bool, const CdmDecryptionParametersV16 &), + (override)); + + MOCK_METHOD(CdmResponseType, QuerySessionStatus, + (const CdmSessionId &, CdmQueryMap *), (override)); +}; + +class WVCryptoPluginTest : public Test { +protected: + static constexpr uint32_t kSessionIdSize = 16; + uint8_t *pDest = nullptr; + uint8_t *pSrc = nullptr; + uint8_t blank[1] = {}; // Some compilers will not accept 0. + uint8_t sessionId[kSessionIdSize]; + uint32_t nextBufferId = 0; + std::map heapBases; + android::sp> mCdm = nullptr; + std::shared_ptr mPlugin = nullptr; + std::shared_ptr mPluginNoSid = nullptr; + +public: + void SetUp() override { + FILE *fp = fopen("/dev/urandom", "r"); + ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; + fread(sessionId, sizeof(uint8_t), kSessionIdSize, fp); + fclose(fp); + + // Set default CdmResponseType value for gMock + DefaultValue::Set(wvcdm::NO_ERROR); + heapBases.clear(); + + mCdm = new NiceMock(); + ASSERT_TRUE(mCdm) << "Failed to create mocked CDM"; + mPlugin = ::ndk::SharedRefBase::make( + sessionId, kSessionIdSize, mCdm.get()); + ASSERT_TRUE(mPlugin) << "Failed to create crypto plugin"; + + mPluginNoSid = + ::ndk::SharedRefBase::make(blank, 0, mCdm.get()); + ASSERT_TRUE(mPluginNoSid) << "Failed to create crypto plugin w/o sid"; + } + + void TearDown() override { + if (mCdm) + mCdm.clear(); + if (mPlugin) + mPlugin.reset(); + if (mPluginNoSid) + mPluginNoSid.reset(); + } + + void getDecryptMemory(std::shared_ptr plugin, size_t size, + size_t index, SharedBuffer &out) { + out.bufferId = static_cast(index); + out.offset = 0; + out.size = static_cast(size); + + int fd = ashmem_create_region("mediacryptoTestSharedMemory", size); + EXPECT_GE(fd, 0); + EXPECT_EQ(size, ashmem_get_size_region(fd)); + auto handle = native_handle_create(1, 0); + handle->data[0] = fd; + out.handle = ::android::makeToAidl(handle); + + auto ret = plugin->setSharedBufferBase(out); + EXPECT_TRUE(ret.isOk()) << "WVCryptoPlugin failed to setSharedBufferBase"; + native_handle_delete(handle); + } + + uint8_t *fillRandom(const ::aidl::android::hardware::drm::SharedBuffer &buf) { + std::random_device rd; + std::mt19937 rand(rd()); + + auto fd = buf.handle.fds[0].get(); + size_t size = buf.size; + uint8_t *base = static_cast( + mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)); + EXPECT_NE(MAP_FAILED, base); + auto p = reinterpret_cast(base); + for (size_t i = 0; i < size / sizeof(uint32_t); i++) { + p[i] = rand(); + } + return base; + } +}; + +TEST_F(WVCryptoPluginTest, CorrectlyReportsSecureBuffers) { + 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(*mCdm, IsOpenSession(_)).WillRepeatedly(testing::Return(true)); + + // Specify the expected calls to QuerySessionStatus + EXPECT_CALL(*mCdm, QuerySessionStatus(_, _)) + .WillOnce( + DoAll(SetArgPointee<1>(l1Map), testing::Return(wvcdm::NO_ERROR))) + .WillOnce( + DoAll(SetArgPointee<1>(l3Map), testing::Return(wvcdm::NO_ERROR))); + + bool isSecure = false; + auto ret = mPlugin->requiresSecureDecoderComponent("video/mp4", &isSecure); + EXPECT_TRUE(isSecure) + << "WVCryptoPlugin incorrectly allows an insecure video decoder on L1"; + ret = mPlugin->requiresSecureDecoderComponent("video/mp4", &isSecure); + EXPECT_FALSE(isSecure) + << "WVCryptoPlugin incorrectly expects a secure video decoder on L3"; + ret = mPlugin->requiresSecureDecoderComponent("audio/aac", &isSecure); + EXPECT_FALSE(isSecure) + << "WVCryptoPlugin incorrectly expects a secure audio decoder"; +} + +// TODO(b/28295739): +// Add New MediaCrypto Unit Tests for CBC & Pattern Mode in cdmPatternDesc. + +// Predicate that validates that the fields of a passed-in +// CdmDecryptionParametersV16 match the values it was given at construction +// time. +// +// This could be done with a huge pile of gMock matchers, but it is ugly and +// unmaintainable, particularly once you get into validating the subsamples. The +// logic here is complex enough to warrant a custom matcher for this one test. +class CDPMatcher { +public: + // TODO(b/35259313): Uncomment the removed parameters once the matcher can + // convert them from AIDL accesses to physical addresses. + CDPMatcher(const uint8_t *keyId, bool isSecure, Mode cipherMode, + const Pattern &pattern, const uint8_t * /* input */, + size_t inputLength, const uint8_t * /* output */, + size_t outputLength, const uint8_t *iv, + const SubSample *subsamples, size_t subsamplesLength) + : mKeyId(keyId, keyId + KEY_ID_SIZE), mIsSecure(isSecure), + mCipherMode(cipherMode), mPattern(pattern), /* mInput(input), */ + mInputLength(inputLength), /* mOutput(output), */ + mOutputLength(outputLength), mIv(iv, iv + KEY_IV_SIZE), + mSubsamples(subsamples, subsamples + subsamplesLength) {} + + bool operator()(const CdmDecryptionParametersV16 ¶ms) const { + if (mCipherMode == Mode::AES_CTR && params.cipher_mode != kCipherModeCtr) { + return false; + } else if (mCipherMode == Mode::AES_CBC && + params.cipher_mode != kCipherModeCbc) { + return false; + } + + if (params.key_id != mKeyId || params.is_secure != mIsSecure || + params.pattern.encrypt_blocks != mPattern.encryptBlocks || + params.pattern.skip_blocks != mPattern.skipBlocks || + params.samples.size() != 1) { + return false; + } + + const CdmDecryptionSample &sample = params.samples[0]; + if (sample.encrypt_buffer_length != mInputLength || + sample.decrypt_buffer_size != mOutputLength || + sample.decrypt_buffer_offset != 0 || sample.iv != mIv || + sample.subsamples.size() != mSubsamples.size()) { + return false; + } + + for (size_t i = 0; i < mSubsamples.size(); ++i) { + const SubSample &androidSubsample = mSubsamples[i]; + const CdmDecryptionSubsample &cdmSubsample = sample.subsamples[i]; + + if (cdmSubsample.clear_bytes != androidSubsample.numBytesOfClearData || + cdmSubsample.protected_bytes != + androidSubsample.numBytesOfEncryptedData) { + return false; + } + } + + return true; + } + +private: + const KeyId mKeyId; + const bool mIsSecure; + const Mode mCipherMode; + const Pattern mPattern; + const size_t mInputLength; + const size_t mOutputLength; + const std::vector mIv; + const std::vector mSubsamples; +}; + +TEST_F(WVCryptoPluginTest, AttemptsToDecrypt) { + constexpr 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 subSamplesVec( + subSamples, subSamples + sizeof(subSamples) / sizeof(subSamples[0])); + + int64_t totalSize = 0; + for (size_t i = 0; i < subSamplesVec.size(); ++i) { + totalSize += subSamples[i].numBytesOfClearData; + totalSize += subSamples[i].numBytesOfEncryptedData; + } + + uint8_t keyId[KEY_ID_SIZE]; + uint8_t iv[KEY_IV_SIZE]; + uint8_t sessionId[kSessionIdSize]; + + static const size_t kDataSize = 185; + + FILE *fp = fopen("/dev/urandom", "r"); + ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; + fread(keyId, sizeof(uint8_t), KEY_ID_SIZE, fp); + fread(iv, sizeof(uint8_t), KEY_IV_SIZE, fp); + fclose(fp); + + SharedBuffer sourceBuffer; + getDecryptMemory(mPlugin, kDataSize, 0, sourceBuffer); + auto sourceBase = fillRandom(sourceBuffer); + + SharedBuffer destBuffer; + getDecryptMemory(mPlugin, kDataSize, 0, destBuffer); + auto destBase = fillRandom(destBuffer); + + SharedBuffer sourceRange; + sourceRange.bufferId = 0; + sourceRange.offset = 0; + sourceRange.size = totalSize; + + SharedBuffer destRange; + destRange.bufferId = 0; + destRange.offset = 0; + destRange.size = totalSize; + + Pattern noPattern = {0, 0}; + + // Provide the expected behavior for IsOpenSession + EXPECT_CALL(*mCdm, IsOpenSession(_)).WillRepeatedly(testing::Return(true)); + + // TODO, initialize pSrc and pDest + // Specify the expected calls to Decrypt + CDPMatcher paramsMatcher(keyId, false, Mode::AES_CTR, noPattern, pSrc, + kDataSize, pDest, kDataSize, iv, subSamples, + kSubSampleCount); + + EXPECT_CALL(*mCdm, DecryptV16(ElementsAreArray(sessionId, kSessionIdSize), + true, Truly(paramsMatcher))) + .Times(1); + + DecryptArgs args; + args.secure = false; + args.keyId = std::vector(keyId, keyId + sizeof(keyId) / sizeof(keyId[0])); + args.iv = std::vector(iv, iv + sizeof(iv) / sizeof(iv[0])); + args.mode = Mode::AES_CTR; + args.pattern = noPattern; + args.subSamples = subSamplesVec; + args.source = std::move(sourceRange); + args.offset = 0; + args.destination = std::move(destRange); + + std::vector sessionIdVector(sessionId, sessionId + kSessionIdSize); + auto ret = mPlugin->setMediaDrmSession(sessionIdVector); + EXPECT_TRUE(ret.isOk()); + + int32_t bytesWritten = 0; + ret = mPlugin->decrypt(args, &bytesWritten); + EXPECT_TRUE(ret.isOk()) << ret.getMessage(); + std::string errorMessage(ret.getMessage()); + EXPECT_EQ(kDataSize, bytesWritten) + << "WVCryptoPlugin decrypted the wrong number of bytes"; + EXPECT_EQ(0u, errorMessage.size()) + << "WVCryptoPlugin reported a detailed error message."; + + munmap(sourceBase, totalSize); + munmap(destBase, totalSize); +} + +TEST_F(WVCryptoPluginTest, RejectsCens) { + constexpr size_t kSubSampleCount = 2; + SubSample subSamples[kSubSampleCount]; + memset(subSamples, 0, sizeof(subSamples)); + subSamples[0].numBytesOfEncryptedData = 16; + subSamples[1].numBytesOfClearData = 16; + subSamples[1].numBytesOfEncryptedData = 16; + + std::vector subSamplesVec( + subSamples, subSamples + sizeof(subSamples) / sizeof(subSamples[0])); + + int64_t totalSize = 0; + for (size_t i = 0; i < subSamplesVec.size(); ++i) { + totalSize += subSamples[i].numBytesOfClearData; + totalSize += subSamples[i].numBytesOfEncryptedData; + } + + uint8_t keyId[KEY_ID_SIZE]; + uint8_t iv[KEY_IV_SIZE]; + + static const size_t kDataSize = 48; + + FILE *fp = fopen("/dev/urandom", "r"); + ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; + fread(keyId, sizeof(uint8_t), KEY_ID_SIZE, fp); + fread(iv, sizeof(uint8_t), KEY_IV_SIZE, fp); + fread(sessionId, sizeof(uint8_t), kSessionIdSize, fp); + fclose(fp); + + // Provide the expected behavior for IsOpenSession + EXPECT_CALL(*mCdm, IsOpenSession(_)).WillRepeatedly(testing::Return(true)); + + // Refuse calls to Decrypt + EXPECT_CALL(*mCdm, DecryptV16(_, _, _)).Times(0); + + SharedBuffer sourceBuffer; + getDecryptMemory(mPlugin, kDataSize, 0, sourceBuffer); + auto sourceBase = fillRandom(sourceBuffer); + + SharedBuffer destBuffer; + getDecryptMemory(mPlugin, kDataSize, 0, destBuffer); + auto destBase = fillRandom(destBuffer); + + SharedBuffer sourceRange; + sourceRange.bufferId = 0; + sourceRange.offset = 0; + sourceRange.size = totalSize; + + SharedBuffer destRange; + destRange.bufferId = 0; + destRange.offset = 0; + destRange.size = totalSize; + + Pattern recommendedPattern = {1, 9}; + std::vector keyIdVec(keyId, keyId + KEY_ID_SIZE); + std::vector ivVec(iv, iv + KEY_IV_SIZE); + + DecryptArgs args; + args.secure = false; + args.keyId = keyIdVec; + args.iv = ivVec; + args.mode = Mode::AES_CTR; + args.pattern = recommendedPattern; + args.subSamples = subSamplesVec; + args.source = std::move(sourceRange); + args.offset = 0; + args.destination = std::move(destRange); + + int32_t bytesWritten = -1; + auto ret = mPlugin->decrypt(args, &bytesWritten); + EXPECT_EQ(static_cast(Status::BAD_VALUE), ret.getServiceSpecificError()); + EXPECT_EQ(bytesWritten, 0); + + munmap(sourceBase, totalSize); + munmap(destBase, totalSize); +} + +TEST_F(WVCryptoPluginTest, CommunicatesSecureBufferRequest) { + uint8_t keyId[KEY_ID_SIZE]; + uint8_t iv[KEY_IV_SIZE]; + + static const size_t kDataSize = 32; + + FILE *fp = fopen("/dev/urandom", "r"); + ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; + fread(keyId, sizeof(uint8_t), KEY_ID_SIZE, fp); + fread(iv, sizeof(uint8_t), KEY_IV_SIZE, fp); + fclose(fp); + + SubSample subSample; + subSample.numBytesOfClearData = 16; + subSample.numBytesOfEncryptedData = 16; + std::vector subSamples; + subSamples.push_back(subSample); + + int64_t totalSize = 0; + for (size_t i = 0; i < subSamples.size(); ++i) { + totalSize += subSamples[i].numBytesOfClearData; + totalSize += subSamples[i].numBytesOfEncryptedData; + } + + // Provide the expected behavior for IsOpenSession + EXPECT_CALL(*mCdm, IsOpenSession(_)).WillRepeatedly(testing::Return(true)); + + // Specify the expected calls to Decrypt + { + InSequence calls; + + typedef CdmDecryptionParametersV16 CDP; + + EXPECT_CALL(*mCdm, DecryptV16(_, _, Field(&CDP::is_secure, false))) + .Times(1); + + EXPECT_CALL(*mCdm, DecryptV16(_, _, Field(&CDP::is_secure, true))).Times(1); + } + + SharedBuffer sourceBuffer; + getDecryptMemory(mPlugin, kDataSize, 0, sourceBuffer); + auto sourceBase = fillRandom(sourceBuffer); + + SharedBuffer destBuffer; + getDecryptMemory(mPlugin, kDataSize, 0, destBuffer); + auto destBase = fillRandom(destBuffer); + + SharedBuffer sourceRange; + sourceRange.bufferId = 0; + sourceRange.offset = 0; + sourceRange.size = totalSize; + + SharedBuffer destRange; + destRange.bufferId = 0; + destRange.offset = 0; + destRange.size = totalSize; + + Pattern noPattern = {0, 0}; + std::vector keyIdVec(keyId, keyId + KEY_ID_SIZE); + std::vector ivVec(iv, iv + KEY_IV_SIZE); + + DecryptArgs args; + args.secure = false; + args.keyId = keyIdVec; + args.iv = ivVec; + args.mode = Mode::AES_CTR; + args.pattern = noPattern; + args.subSamples = subSamples; + args.source = std::move(sourceRange); + args.offset = 0; + args.destination = std::move(destRange); + + int32_t bytesWritten = 0; + auto ret = mPlugin->decrypt(args, &bytesWritten); + EXPECT_TRUE(ret.isOk()); + std::string errorMessage(ret.getMessage()); + EXPECT_EQ(0u, errorMessage.size()) + << "WVCryptoPlugin reported a detailed error message."; + + args.secure = true; + bytesWritten = 0; + ret = mPlugin->decrypt(args, &bytesWritten); + EXPECT_TRUE(ret.isOk()); + std::string errorMessage2(ret.getMessage()); + EXPECT_EQ(0u, errorMessage2.size()) + << "WVCryptoPlugin reported a detailed error message."; + + munmap(sourceBase, totalSize); + munmap(destBase, totalSize); +} + +TEST_F(WVCryptoPluginTest, AllowsSessionIdChanges) { + uint8_t keyId[KEY_ID_SIZE]; + uint8_t iv[KEY_IV_SIZE]; + uint8_t sessionId2[kSessionIdSize]; + + static constexpr size_t kDataSize = 32; + + FILE *fp = fopen("/dev/urandom", "r"); + ASSERT_NE(fp, nullptr) << "Failed to open /dev/urandom"; + 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); + fclose(fp); + + SubSample subSample; + subSample.numBytesOfClearData = 16; + subSample.numBytesOfEncryptedData = 16; + std::vector subSamples; + subSamples.push_back(subSample); + + int64_t totalSize = 0; + for (size_t i = 0; i < subSamples.size(); ++i) { + totalSize += subSamples[i].numBytesOfClearData; + totalSize += subSamples[i].numBytesOfEncryptedData; + } + + std::vector sessionIdVector(sessionId, sessionId + kSessionIdSize); + std::vector sessionId2Vector(sessionId2, + sessionId2 + kSessionIdSize); + + // Provide the expected behavior for IsOpenSession + EXPECT_CALL(*mCdm, IsOpenSession(_)).WillRepeatedly(testing::Return(true)); + + // Specify the expected calls to Decrypt + { + InSequence calls; + + EXPECT_CALL(*mCdm, + DecryptV16(ElementsAreArray(sessionId, kSessionIdSize), _, _)) + .Times(1); + + EXPECT_CALL(*mCdm, + DecryptV16(ElementsAreArray(sessionId2, kSessionIdSize), _, _)) + .Times(1); + } + + SharedBuffer sourceBuffer; + getDecryptMemory(mPluginNoSid, kDataSize, 0, sourceBuffer); + auto sourceBase = fillRandom(sourceBuffer); + + SharedBuffer destBuffer; + getDecryptMemory(mPluginNoSid, kDataSize, 0, destBuffer); + auto destBase = fillRandom(destBuffer); + + SharedBuffer sourceRange; + sourceRange.bufferId = 0; + sourceRange.offset = 0; + sourceRange.size = totalSize; + + SharedBuffer destRange; + destRange.bufferId = 0; + destRange.offset = 0; + destRange.size = totalSize; + + Pattern noPattern = {0, 0}; + std::vector keyIdVec(keyId, keyId + KEY_ID_SIZE); + std::vector ivVec(iv, iv + KEY_IV_SIZE); + + DecryptArgs args; + args.secure = false; + args.keyId = keyIdVec; + args.iv = ivVec; + args.mode = Mode::AES_CTR; + args.pattern = noPattern; + args.subSamples = subSamples; + args.source = std::move(sourceRange); + args.offset = 0; + args.destination = std::move(destRange); + + auto ret = mPluginNoSid->setMediaDrmSession(sessionIdVector); + EXPECT_TRUE(ret.isOk()); + + int32_t bytesWritten = 0; + ret = mPluginNoSid->decrypt(args, &bytesWritten); + std::string errorMessage(ret.getMessage()); + EXPECT_TRUE(ret.isOk()); + EXPECT_EQ(0u, errorMessage.size()) + << "WVCryptoPlugin reported a detailed error message."; + + ret = mPluginNoSid->setMediaDrmSession(sessionId2Vector); + EXPECT_TRUE(ret.isOk()); + + bytesWritten = 0; + ret = mPluginNoSid->decrypt(args, &bytesWritten); + EXPECT_TRUE(ret.isOk()); + std::string errorMessage2(ret.getMessage()); + EXPECT_EQ(0u, errorMessage2.size()) + << "WVCryptoPlugin reported a detailed error message."; + + munmap(sourceBase, totalSize); + munmap(destBase, totalSize); +} + +TEST_F(WVCryptoPluginTest, DisallowsUnopenedSessionIdChanges) { + std::vector sessionIdVector(sessionId, sessionId + kSessionIdSize); + + // Specify the expected calls to IsOpenSession + { + InSequence calls; + + EXPECT_CALL(*mCdm, IsOpenSession(ElementsAreArray(blank, 0))) + .WillRepeatedly(testing::Return(false)); + + EXPECT_CALL(*mCdm, + IsOpenSession(ElementsAreArray(sessionId, kSessionIdSize))) + .WillOnce(testing::Return(false)) + .WillOnce(testing::Return(true)); + } + + auto ret = mPluginNoSid->setMediaDrmSession(sessionIdVector); + EXPECT_EQ(static_cast(Status::ERROR_DRM_SESSION_NOT_OPENED), + ret.getServiceSpecificError()); + + ret = mPluginNoSid->setMediaDrmSession(sessionIdVector); + EXPECT_TRUE(ret.isOk()); +} + +} // namespace widevine +} // namespace drm +} // namespace hardware +} // namespace wvdrm diff --git a/libwvdrmengine/mediacrypto/test/WVCryptoPlugin_test.cpp b/libwvdrmengine/mediacrypto/test/hidl/WVCryptoPlugin_test.cpp similarity index 100% rename from libwvdrmengine/mediacrypto/test/WVCryptoPlugin_test.cpp rename to libwvdrmengine/mediacrypto/test/hidl/WVCryptoPlugin_test.cpp