Support Latest Version of EME Spec Init Data Specification
(This is a merge of https://widevine-internal-review.googlesource.com/9711 from the Widevine CDM repo.) This change updates the CDM's handling of init data types, previously known as MIME types, to comply with the latest version of the EME spec. Following this change, in addition to accepting the deprecated MIME types "video/mp4", "audio/mp4", "video/webm", and "audio/webm", the CDM will accept the new standard: Init data types "cenc" and "webm". Furthermore, this removes the non-PSSH-parsing path from the CDM. All platforms have unified on the CDM being responsible for parsing the concatenated PSSH box list, as outlined in the latest EME spec. As Android has shipped code that expects pre-unwrapped PSSH boxes and must maintain backwards-compatibility, code has been inserted on that platform to detect pre-unwrapped data and re-wrap it with a PSSH header before sending it to the CDM. There are some small changes to unit tests because of this change: 1) The CDM Engine unit test now no longer needs to unwrap the PSSH on any platforms when testing ISO-BMFF. It now pre-caches the unwrapped key ID for use when testing WebM. 2) Several substantially-similar unit tests in the Android code have been rolled into one test. Bug: 13564917 Bug: 13570595 Bug: 9465346 Bug: 13570288 Change-Id: I7f27b16b8503f24a26746b5dce71fb61b6fd1bb2
This commit is contained in:
committed by
John Bruce
parent
de6f6f6324
commit
951f08c2da
@@ -236,6 +236,8 @@ class WVDrmPlugin : public android::DrmPlugin,
|
||||
OEMCryptoResult res);
|
||||
|
||||
status_t mapOEMCryptoResult(OEMCryptoResult res);
|
||||
|
||||
bool InitDataResemblesPSSH(const Vector<uint8_t>& initData);
|
||||
};
|
||||
|
||||
} // namespace wvdrm
|
||||
|
||||
@@ -19,16 +19,20 @@
|
||||
#include "utils/Errors.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
|
||||
namespace {
|
||||
static const char* const kResetSecurityLevel = "";
|
||||
static const char* const kEnable = "enable";
|
||||
static const char* const kDisable = "disable";
|
||||
|
||||
static const std::string kPsshTag = "pssh";
|
||||
}
|
||||
|
||||
namespace wvdrm {
|
||||
|
||||
using namespace android;
|
||||
using namespace std;
|
||||
using namespace wvcdm;
|
||||
|
||||
static const char* const kResetSecurityLevel = "";
|
||||
static const char* const kEnable = "enable";
|
||||
static const char* const kDisable = "disable";
|
||||
|
||||
WVDrmPlugin::WVDrmPlugin(WvContentDecryptionModule* cdm,
|
||||
WVGenericCryptoInterface* crypto)
|
||||
: mCDM(cdm), mCrypto(crypto) {}
|
||||
@@ -149,21 +153,18 @@ status_t WVDrmPlugin::getKeyRequest(
|
||||
return android::ERROR_DRM_CANNOT_HANDLE;
|
||||
}
|
||||
|
||||
string cdmMimeType = initDataType.string();
|
||||
|
||||
string cdmInitDataType = initDataType.string();
|
||||
// Provide backwards-compatibility for apps that pass non-EME-compatible MIME
|
||||
// types.
|
||||
if (cdmMimeType != wvcdm::ISO_BMFF_VIDEO_MIME_TYPE &&
|
||||
cdmMimeType != wvcdm::ISO_BMFF_AUDIO_MIME_TYPE &&
|
||||
cdmMimeType != wvcdm::WEBM_VIDEO_MIME_TYPE &&
|
||||
cdmMimeType != wvcdm::WEBM_AUDIO_MIME_TYPE) {
|
||||
cdmMimeType = wvcdm::ISO_BMFF_VIDEO_MIME_TYPE;
|
||||
if (!WvContentDecryptionModule::IsSupported(cdmInitDataType)) {
|
||||
cdmInitDataType = wvcdm::ISO_BMFF_VIDEO_MIME_TYPE;
|
||||
}
|
||||
|
||||
CdmInitData processedInitData;
|
||||
if (cdmMimeType == wvcdm::ISO_BMFF_VIDEO_MIME_TYPE ||
|
||||
cdmMimeType == wvcdm::ISO_BMFF_AUDIO_MIME_TYPE) {
|
||||
// For ISO-BMFF, we need to wrap the init data in a new PSSH header.
|
||||
if (WvContentDecryptionModule::IsCenc(cdmInitDataType) &&
|
||||
!InitDataResemblesPSSH(initData)) {
|
||||
// This data was passed in the old format, pre-unwrapped. We need to wrap
|
||||
// the init data in a new PSSH header.
|
||||
static const char psshPrefix[] = {
|
||||
0, 0, 0, 0, // Total size
|
||||
'p', 's', 's', 'h', // "PSSH"
|
||||
@@ -204,7 +205,8 @@ status_t WVDrmPlugin::getKeyRequest(
|
||||
CdmKeyMessage keyRequest;
|
||||
string cdmDefaultUrl;
|
||||
CdmResponseType res = mCDM->GenerateKeyRequest(cdmSessionId, cdmKeySetId,
|
||||
cdmMimeType, processedInitData,
|
||||
cdmInitDataType,
|
||||
processedInitData,
|
||||
cdmLicenseType, cdmParameters,
|
||||
&keyRequest, &cdmDefaultUrl);
|
||||
|
||||
@@ -929,4 +931,24 @@ status_t WVDrmPlugin::mapOEMCryptoResult(OEMCryptoResult res) {
|
||||
}
|
||||
}
|
||||
|
||||
bool WVDrmPlugin::InitDataResemblesPSSH(const Vector<uint8_t>& initData) {
|
||||
const uint8_t* const initDataArray = initData.array();
|
||||
|
||||
// Extract the size field
|
||||
const uint8_t* const sizeField = &initDataArray[0];
|
||||
uint32_t nboSize;
|
||||
memcpy(&nboSize, sizeField, sizeof(nboSize));
|
||||
uint32_t size = ntohl(nboSize);
|
||||
|
||||
if (size > initData.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract the ID field
|
||||
const char* const idField =
|
||||
reinterpret_cast<const char* const>(&initDataArray[sizeof(nboSize)]);
|
||||
string id(idField, kPsshTag.size());
|
||||
return id == kPsshTag;
|
||||
}
|
||||
|
||||
} // namespace wvdrm
|
||||
|
||||
@@ -189,7 +189,7 @@ TEST_F(WVDrmPluginTest, ClosesSessions) {
|
||||
ASSERT_EQ(OK, res);
|
||||
}
|
||||
|
||||
TEST_F(WVDrmPluginTest, GeneratesIsoBmffKeyRequests) {
|
||||
TEST_F(WVDrmPluginTest, GeneratesKeyRequests) {
|
||||
StrictMock<MockCDM> cdm;
|
||||
StrictMock<MockCrypto> crypto;
|
||||
WVDrmPlugin plugin(&cdm, &crypto);
|
||||
@@ -207,10 +207,11 @@ TEST_F(WVDrmPluginTest, GeneratesIsoBmffKeyRequests) {
|
||||
fclose(fp);
|
||||
|
||||
memcpy(keySetIdRaw, KEY_SET_ID_PREFIX, sizeof(KEY_SET_ID_PREFIX) - 1);
|
||||
CdmKeySetId cdmKeySetId(reinterpret_cast<char *>(keySetIdRaw), kKeySetIdSize);
|
||||
CdmKeySetId cdmKeySetId(reinterpret_cast<char*>(keySetIdRaw), kKeySetIdSize);
|
||||
Vector<uint8_t> keySetId;
|
||||
keySetId.appendArray(keySetIdRaw, kKeySetIdSize);
|
||||
|
||||
CdmInitData cdmInitData(reinterpret_cast<char*>(initDataRaw), kInitDataSize);
|
||||
Vector<uint8_t> initData;
|
||||
initData.appendArray(initDataRaw, kInitDataSize);
|
||||
|
||||
@@ -224,174 +225,106 @@ TEST_F(WVDrmPluginTest, GeneratesIsoBmffKeyRequests) {
|
||||
};
|
||||
static const size_t kPsshPrefixSize = sizeof(psshPrefix);
|
||||
static const size_t kPsshBoxSize = kPsshPrefixSize + kInitDataSize;
|
||||
uint8_t psshBox[kPsshBoxSize];
|
||||
memcpy(psshBox, psshPrefix, kPsshPrefixSize);
|
||||
memcpy(psshBox + kPsshPrefixSize, initDataRaw, kInitDataSize);
|
||||
uint8_t psshBoxRaw[kPsshBoxSize];
|
||||
memcpy(psshBoxRaw, psshPrefix, kPsshPrefixSize);
|
||||
memcpy(psshBoxRaw + kPsshPrefixSize, initDataRaw, kInitDataSize);
|
||||
CdmInitData cdmPsshBox(reinterpret_cast<char*>(psshBoxRaw), kPsshBoxSize);
|
||||
Vector<uint8_t> psshBox;
|
||||
psshBox.appendArray(psshBoxRaw, kPsshBoxSize);
|
||||
|
||||
CdmKeyMessage cdmRequest(requestRaw, requestRaw + kRequestSize);
|
||||
|
||||
KeyedVector<String8, String8> parameters;
|
||||
CdmAppParameterMap cdmParameters;
|
||||
|
||||
parameters.add(String8("paddingScheme"), String8("PKCS7"));
|
||||
cdmParameters["paddingScheme"] = "PKCS7";
|
||||
parameters.add(String8("favoriteParticle"), String8("tetraquark"));
|
||||
cdmParameters["favoriteParticle"] = "tetraquark";
|
||||
parameters.add(String8("answer"), String8("42"));
|
||||
cdmParameters["answer"] = "42";
|
||||
|
||||
static const char* kDefaultUrl = "http://google.com/";
|
||||
static const char* kMimeType = "video/mp4";
|
||||
|
||||
{
|
||||
InSequence calls;
|
||||
|
||||
EXPECT_CALL(cdm, GenerateKeyRequest(cdmSessionId, "", kMimeType,
|
||||
ElementsAreArray(psshBox, kPsshBoxSize),
|
||||
kLicenseTypeOffline, cdmParameters, _,
|
||||
_))
|
||||
.WillOnce(DoAll(SetArgPointee<6>(cdmRequest),
|
||||
SetArgPointee<7>(kDefaultUrl),
|
||||
Return(wvcdm::KEY_MESSAGE)));
|
||||
|
||||
EXPECT_CALL(cdm, GenerateKeyRequest(cdmSessionId, "", kMimeType,
|
||||
ElementsAreArray(psshBox, kPsshBoxSize),
|
||||
kLicenseTypeStreaming, cdmParameters, _,
|
||||
_))
|
||||
.WillOnce(DoAll(SetArgPointee<6>(cdmRequest),
|
||||
SetArgPointee<7>(kDefaultUrl),
|
||||
Return(wvcdm::KEY_MESSAGE)));
|
||||
|
||||
EXPECT_CALL(cdm, GenerateKeyRequest("", cdmKeySetId, kMimeType,
|
||||
ElementsAreArray(psshBox, kPsshBoxSize),
|
||||
kLicenseTypeRelease, cdmParameters, _,
|
||||
_))
|
||||
.WillOnce(DoAll(SetArgPointee<6>(cdmRequest),
|
||||
SetArgPointee<7>(kDefaultUrl),
|
||||
Return(wvcdm::KEY_MESSAGE)));
|
||||
}
|
||||
|
||||
Vector<uint8_t> request;
|
||||
String8 defaultUrl;
|
||||
|
||||
status_t res = plugin.getKeyRequest(sessionId, initData,
|
||||
String8(kMimeType),
|
||||
DrmPlugin::kKeyType_Offline,
|
||||
parameters, request, defaultUrl);
|
||||
ASSERT_EQ(OK, res);
|
||||
EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize));
|
||||
EXPECT_STREQ(kDefaultUrl, defaultUrl.string());
|
||||
|
||||
res = plugin.getKeyRequest(sessionId, initData, String8(kMimeType),
|
||||
DrmPlugin::kKeyType_Streaming, parameters,
|
||||
request, defaultUrl);
|
||||
ASSERT_EQ(OK, res);
|
||||
EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize));
|
||||
EXPECT_STREQ(kDefaultUrl, defaultUrl.string());
|
||||
|
||||
res = plugin.getKeyRequest(keySetId, initData, String8(kMimeType),
|
||||
DrmPlugin::kKeyType_Release, parameters,
|
||||
request, defaultUrl);
|
||||
ASSERT_EQ(OK, res);
|
||||
EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize));
|
||||
EXPECT_STREQ(kDefaultUrl, defaultUrl.string());
|
||||
}
|
||||
|
||||
TEST_F(WVDrmPluginTest, GeneratesWebmKeyRequests) {
|
||||
StrictMock<MockCDM> cdm;
|
||||
StrictMock<MockCrypto> crypto;
|
||||
WVDrmPlugin plugin(&cdm, &crypto);
|
||||
|
||||
static const size_t kInitDataSize = 128;
|
||||
uint8_t initDataRaw[kInitDataSize];
|
||||
static const size_t kRequestSize = 256;
|
||||
uint8_t requestRaw[kRequestSize];
|
||||
static const uint32_t kKeySetIdSize = 32;
|
||||
uint8_t keySetIdRaw[kKeySetIdSize];
|
||||
FILE* fp = fopen("/dev/urandom", "r");
|
||||
fread(initDataRaw, sizeof(uint8_t), kInitDataSize, fp);
|
||||
fread(requestRaw, sizeof(uint8_t), kRequestSize, fp);
|
||||
fread(keySetIdRaw, sizeof(uint8_t), kKeySetIdSize, fp);
|
||||
fclose(fp);
|
||||
|
||||
memcpy(keySetIdRaw, KEY_SET_ID_PREFIX, sizeof(KEY_SET_ID_PREFIX) - 1);
|
||||
CdmKeySetId cdmKeySetId(reinterpret_cast<char *>(keySetIdRaw), kKeySetIdSize);
|
||||
Vector<uint8_t> keySetId;
|
||||
keySetId.appendArray(keySetIdRaw, kKeySetIdSize);
|
||||
|
||||
Vector<uint8_t> initData;
|
||||
initData.appendArray(initDataRaw, kInitDataSize);
|
||||
|
||||
CdmKeyMessage cdmRequest(requestRaw, requestRaw + kRequestSize);
|
||||
|
||||
KeyedVector<String8, String8> parameters;
|
||||
CdmAppParameterMap cdmParameters;
|
||||
|
||||
parameters.add(String8("paddingScheme"), String8("BUBBLE WRAP"));
|
||||
cdmParameters["paddingScheme"] = "BUBBLE WRAP";
|
||||
parameters.add(String8("favoriteParticle"), String8("higgs boson"));
|
||||
cdmParameters["favoriteParticle"] = "higgs boson";
|
||||
parameters.add(String8("favorite-particle"), String8("tetraquark"));
|
||||
cdmParameters["favorite-particle"] = "tetraquark";
|
||||
parameters.add(String8("answer"), String8("6 * 9"));
|
||||
cdmParameters["answer"] = "6 * 9";
|
||||
|
||||
static const char* kDefaultUrl = "http://google.com/";
|
||||
static const char* kMimeType = "video/webm";
|
||||
static const char* kIsoBmffMimeType = "cenc";
|
||||
static const char* kWebmMimeType = "webm";
|
||||
|
||||
struct TestSet {
|
||||
const char* mimeType;
|
||||
const Vector<uint8_t>& initDataIn;
|
||||
const CdmInitData& initDataOut;
|
||||
};
|
||||
|
||||
// We run the same set of operations on several sets of data, simulating
|
||||
// different valid calling patterns.
|
||||
TestSet testSets[] = {
|
||||
{kIsoBmffMimeType, psshBox, cdmPsshBox}, // ISO-BMFF, EME passing style
|
||||
{kIsoBmffMimeType, initData, cdmPsshBox}, // ISO-BMFF, old passing style
|
||||
{kWebmMimeType, initData, cdmInitData} // WebM
|
||||
};
|
||||
size_t testSetCount = sizeof(testSets) / sizeof(TestSet);
|
||||
|
||||
// Set up the expected calls. Per gMock rules, this must be done for all test
|
||||
// sets prior to testing any of them.
|
||||
{
|
||||
InSequence calls;
|
||||
|
||||
EXPECT_CALL(cdm, GenerateKeyRequest(cdmSessionId, "", kMimeType,
|
||||
ElementsAreArray(initDataRaw,
|
||||
kInitDataSize),
|
||||
kLicenseTypeOffline, cdmParameters, _,
|
||||
_))
|
||||
.WillOnce(DoAll(SetArgPointee<6>(cdmRequest),
|
||||
SetArgPointee<7>(kDefaultUrl),
|
||||
Return(wvcdm::KEY_MESSAGE)));
|
||||
for (size_t i = 0; i < testSetCount; ++i)
|
||||
{
|
||||
const char* mimeType = testSets[i].mimeType;
|
||||
const CdmInitData& initData = testSets[i].initDataOut;
|
||||
|
||||
EXPECT_CALL(cdm, GenerateKeyRequest(cdmSessionId, "", kMimeType,
|
||||
ElementsAreArray(initDataRaw,
|
||||
kInitDataSize),
|
||||
kLicenseTypeStreaming, cdmParameters, _,
|
||||
_))
|
||||
.WillOnce(DoAll(SetArgPointee<6>(cdmRequest),
|
||||
SetArgPointee<7>(kDefaultUrl),
|
||||
Return(wvcdm::KEY_MESSAGE)));
|
||||
EXPECT_CALL(cdm, GenerateKeyRequest(cdmSessionId, "", mimeType, initData,
|
||||
kLicenseTypeOffline, cdmParameters, _,
|
||||
_))
|
||||
.WillOnce(DoAll(SetArgPointee<6>(cdmRequest),
|
||||
SetArgPointee<7>(kDefaultUrl),
|
||||
Return(wvcdm::KEY_MESSAGE)));
|
||||
|
||||
EXPECT_CALL(cdm, GenerateKeyRequest("", cdmKeySetId, kMimeType,
|
||||
ElementsAreArray(initDataRaw,
|
||||
kInitDataSize),
|
||||
kLicenseTypeRelease, cdmParameters, _,
|
||||
_))
|
||||
.WillOnce(DoAll(SetArgPointee<6>(cdmRequest),
|
||||
SetArgPointee<7>(kDefaultUrl),
|
||||
Return(wvcdm::KEY_MESSAGE)));
|
||||
EXPECT_CALL(cdm, GenerateKeyRequest(cdmSessionId, "", mimeType, initData,
|
||||
kLicenseTypeStreaming, cdmParameters, _,
|
||||
_))
|
||||
.WillOnce(DoAll(SetArgPointee<6>(cdmRequest),
|
||||
SetArgPointee<7>(kDefaultUrl),
|
||||
Return(wvcdm::KEY_MESSAGE)));
|
||||
|
||||
EXPECT_CALL(cdm, GenerateKeyRequest("", cdmKeySetId, mimeType, initData,
|
||||
kLicenseTypeRelease, cdmParameters, _,
|
||||
_))
|
||||
.WillOnce(DoAll(SetArgPointee<6>(cdmRequest),
|
||||
SetArgPointee<7>(kDefaultUrl),
|
||||
Return(wvcdm::KEY_MESSAGE)));
|
||||
}
|
||||
}
|
||||
|
||||
Vector<uint8_t> request;
|
||||
String8 defaultUrl;
|
||||
// Performs the actual tests
|
||||
for (size_t i = 0; i < testSetCount; ++i)
|
||||
{
|
||||
const String8 mimeType(testSets[i].mimeType);
|
||||
const Vector<uint8_t>& initData = testSets[i].initDataIn;
|
||||
|
||||
status_t res = plugin.getKeyRequest(sessionId, initData,
|
||||
String8(kMimeType),
|
||||
DrmPlugin::kKeyType_Offline,
|
||||
parameters, request, defaultUrl);
|
||||
ASSERT_EQ(OK, res);
|
||||
EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize));
|
||||
EXPECT_STREQ(kDefaultUrl, defaultUrl.string());
|
||||
Vector<uint8_t> request;
|
||||
String8 defaultUrl;
|
||||
|
||||
res = plugin.getKeyRequest(sessionId, initData, String8(kMimeType),
|
||||
DrmPlugin::kKeyType_Streaming, parameters,
|
||||
request, defaultUrl);
|
||||
ASSERT_EQ(OK, res);
|
||||
EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize));
|
||||
EXPECT_STREQ(kDefaultUrl, defaultUrl.string());
|
||||
status_t res = plugin.getKeyRequest(sessionId, initData, mimeType,
|
||||
DrmPlugin::kKeyType_Offline,
|
||||
parameters, request, defaultUrl);
|
||||
ASSERT_EQ(OK, res);
|
||||
EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize));
|
||||
EXPECT_STREQ(kDefaultUrl, defaultUrl.string());
|
||||
|
||||
res = plugin.getKeyRequest(keySetId, initData, String8(kMimeType),
|
||||
DrmPlugin::kKeyType_Release, parameters,
|
||||
request, defaultUrl);
|
||||
ASSERT_EQ(OK, res);
|
||||
EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize));
|
||||
EXPECT_STREQ(kDefaultUrl, defaultUrl.string());
|
||||
res = plugin.getKeyRequest(sessionId, initData, mimeType,
|
||||
DrmPlugin::kKeyType_Streaming, parameters,
|
||||
request, defaultUrl);
|
||||
ASSERT_EQ(OK, res);
|
||||
EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize));
|
||||
EXPECT_STREQ(kDefaultUrl, defaultUrl.string());
|
||||
|
||||
res = plugin.getKeyRequest(keySetId, initData, mimeType,
|
||||
DrmPlugin::kKeyType_Release, parameters,
|
||||
request, defaultUrl);
|
||||
ASSERT_EQ(OK, res);
|
||||
EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize));
|
||||
EXPECT_STREQ(kDefaultUrl, defaultUrl.string());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(WVDrmPluginTest, AddsKeys) {
|
||||
|
||||
Reference in New Issue
Block a user