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:
committed by
John W. Bruce
parent
a4506542df
commit
2dc53442e7
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user