Create unique cdm engines per WVDrmPlugin instance

This change creates a unique id in the cdm identifier in order to force
a one-to-one mapping between WVDrmPlugin instances and CDM Engines. This
change simplifies some assumptions. This includes ensuring that the
metrics for a given MediaDrm instance map to a given CdmEngine instance.

Bug: 73724453
Test: Updated unit tests. GTS test pass. Shaka Player, Netflix and Google Play test.
Change-Id: I7e041b6cdf3e272d067da49d25a297b4a4663f1f
This commit is contained in:
Adam Stone
2018-03-29 12:28:57 -07:00
parent 084c370db6
commit 58234a69f2
11 changed files with 339 additions and 199 deletions

View File

@@ -365,6 +365,10 @@ struct WVDrmPlugin : public IDrmPlugin, IDrmPluginListener,
const std::string& origin() const { return mCdmIdentifier.origin; }
bool set_origin(const std::string& id);
// Indicates whether the builder can still be modified. This returns false
// until a call to getCdmIdentifier.
bool is_sealed() { return mIsIdentifierSealed; }
private:
WVDRM_DISALLOW_COPY_AND_ASSIGN(CdmIdentifierBuilder);
@@ -382,6 +386,13 @@ struct WVDrmPlugin : public IDrmPlugin, IDrmPluginListener,
// outside this class should use getDeviceUniqueId() to get the
// application-safe device-unique ID.
Status getOemcryptoDeviceId(std::string* id);
// The unique identifier is meant to ensure that two clients with the
// same spoid, origin and app package name still get different cdm engine
// instances. This is a stepping stone to simplifying the implementation.
// Note that we do not have a lock or mutex around this object. We assume
// that locking is handled external to this object.
uint32_t getNextUniqueId();
} mCdmIdentifierBuilder;
sp<wvcdm::WvContentDecryptionModule> const mCDM;

View File

@@ -392,7 +392,7 @@ status_t WVDrmPlugin::provideProvisionResponse(
}
CdmProvisioningResponse cdmResponse(response.begin(), response.end());
if (cdmResponse == kSpecialUnprovisionResponse) {
if (mCdmIdentifier == kDefaultCdmIdentifier) {
if (mCdmIdentifier.IsEquivalentToDefault()) {
return kErrorNoOriginSpecified;
}
return unprovision(mCdmIdentifier);
@@ -540,9 +540,14 @@ status_t WVDrmPlugin::getPropertyByteArray(const String8& name,
} else if (name == "serviceCertificate") {
value = ToVector(mPropertySet.service_certificate());
} else if (name == "metrics") {
std::string metrics_value;
mCDM->GetSerializedMetrics(&metrics_value);
value = ToVector(metrics_value);
std::string serialized_metrics;
drm_metrics::WvCdmMetrics metrics;
mCDM->GetMetrics(mCdmIdentifier, &metrics);
if (!metrics.SerializeToString(&serialized_metrics)) {
return android::ERROR_DRM_UNKNOWN;
} else {
value = ToVector(serialized_metrics);
}
} else {
ALOGE("App requested unknown byte array property %s", name.string());
return android::ERROR_DRM_CANNOT_HANDLE;

View File

@@ -14,6 +14,7 @@
#include "mapErrors-inl.h"
#include "media/stagefright/MediaErrors.h"
#include "metrics.pb.h"
#include "openssl/sha.h"
#include "wv_cdm_constants.h"
@@ -139,6 +140,16 @@ WVDrmPlugin::~WVDrmPlugin() {
}
}
mCryptoSessions.clear();
CdmIdentifier identifier;
Status status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier);
if (status != Status::OK) {
ALOGE("Failed to get cdm identifier %d", status);
} else {
status = mapCdmResponseType(mCDM->CloseCdm(identifier));
if (status != Status::OK) {
ALOGE("Failed to get close cdm %d", status);
}
}
}
Status WVDrmPlugin::openSessionCommon(std::vector<uint8_t>& sessionId) {
@@ -616,7 +627,7 @@ Return<void> WVDrmPlugin::provideProvisionResponse(
CdmProvisioningResponse cdmResponse(resp.begin(), resp.end());
if (cdmResponse == kSpecialUnprovisionResponse) {
if (identifier == kDefaultCdmIdentifier) {
if (identifier.IsEquivalentToDefault()) {
ALOGW("Returns UNKNOWN error for legacy status kErrorNoOriginSpecified");
_hidl_cb(Status::ERROR_DRM_UNKNOWN, toHidlVec(certificate),
toHidlVec(wrappedKey));
@@ -984,9 +995,27 @@ Return<void> WVDrmPlugin::getPropertyByteArray(
} else if (name == "serviceCertificate") {
value = StrToVector(mPropertySet.service_certificate());
} else if (name == "metrics") {
std::string metrics_value;
mCDM->GetSerializedMetrics(&metrics_value);
value = StrToVector(metrics_value);
drm_metrics::WvCdmMetrics metrics;
// If the cdm identifier is not yet sealed, then there are no metrics
// for that cdm engine. Avoid calling getCdmIdentifier and sealing
// the identifier builder.
if (mCdmIdentifierBuilder.is_sealed()) {
CdmIdentifier identifier;
status = mCdmIdentifierBuilder.getCdmIdentifier(&identifier);
if (status != Status::OK) {
ALOGE("Unexpected error retrieving cdm identifier: %d", status);
} else {
status = mapCdmResponseType(mCDM->GetMetrics(identifier, &metrics));
}
}
if (status == Status::OK) {
std::string serialized_metrics;
if (!metrics.SerializeToString(&serialized_metrics)) {
status = Status::ERROR_DRM_UNKNOWN;
} else {
value = StrToVector(serialized_metrics);
}
}
} else {
ALOGE("App requested unknown byte array property %s", name.c_str());
status = Status::ERROR_DRM_CANNOT_HANDLE;
@@ -1641,6 +1670,7 @@ WVDrmPlugin::CdmIdentifierBuilder::CdmIdentifierBuilder(
mAppPackageName(appPackageName),
mParent(parent) {
mCdmIdentifier.app_package_name = mAppPackageName;
mCdmIdentifier.unique_id = getNextUniqueId();
}
Status WVDrmPlugin::CdmIdentifierBuilder::getCdmIdentifier(
@@ -1648,7 +1678,6 @@ Status WVDrmPlugin::CdmIdentifierBuilder::getCdmIdentifier(
if (!mIsIdentifierSealed) {
Status res = calculateSpoid();
if (res != Status::OK) return res;
mIsIdentifierSealed = true;
}
*identifier = mCdmIdentifier;
@@ -1717,6 +1746,12 @@ Status WVDrmPlugin::CdmIdentifierBuilder::getOemcryptoDeviceId(
return mParent.queryProperty(wvcdm::QUERY_KEY_DEVICE_ID, *id);
}
uint32_t WVDrmPlugin::CdmIdentifierBuilder::getNextUniqueId() {
// Start with 1. 0 is reserved for the default cdm identifier.
static uint32_t unique_id = 1;
return ++unique_id;
}
} // namespace widevine
} // namespace V1_1
} // namespace drm

View File

@@ -20,6 +20,7 @@
#include "gtest/gtest.h"
#include "media/stagefright/foundation/ABase.h"
#include "media/stagefright/MediaErrors.h"
#include "string_conversions.h"
#include "wv_cdm_constants.h"
#include "wv_cdm_types.h"
#include "wv_content_decryption_module.h"
@@ -120,15 +121,17 @@ const uint8_t* const kUnprovisionResponse =
const size_t kUnprovisionResponseSize = 11;
const std::string kDeviceId("0123456789\0ABCDEF", 17);
// This is a serialized MetricsGroup message containing a small amount of
// This is a serialized WvCdmMetrics message containing a small amount of
// sample data. This ensures we're able to extract it via a property.
const char kSerializedMetrics[] = {
0x0a, 0x0a, 0x0a, 0x04, 0x74, 0x65, 0x73, 0x74, 0x12, 0x02, 0x08, 0x00,
0x0a, 0x12, 0x0a, 0x05, 0x74, 0x65, 0x73, 0x74, 0x32, 0x12, 0x09, 0x11,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x15, 0x0a, 0x05,
0x74, 0x65, 0x73, 0x74, 0x33, 0x12, 0x0c, 0x1a, 0x0a, 0x74, 0x65, 0x73,
0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65
};
const char kSerializedMetricsHex[] =
"0a580a001a0210072202100d2a02100832182216636f6d2e676f6f676c652e616e64726f69"
"642e676d734208220631342e302e304a06080112020800520610d5f3fad5056a0b1d00fd4c"
"47280132020804a2010608011202080012cb010a0622047369643412b5010a021001520919"
"1d5a643bdfff50405a0b1d00d01945280132021001620d1d00f8e84528013204080020006a"
"0310b739820102100d8a01060801120248009a010310ff01da0106080112024800e2010e1d"
"005243472801320528800248009a020d1d00b016452801320428404800a202060801120248"
"19aa0206080212024800b2020b1d8098f047280132024800ba02021001ca020b1d00101945"
"280132024800e202021004fa02021002a203021000b2030210021a09196891ed7c3f355040";
#define N_ELEM(a) (sizeof(a)/sizeof(a[0]))
} // anonymous namespace
@@ -198,7 +201,8 @@ class MockCDM : public WvContentDecryptionModule {
MOCK_METHOD1(IsValidServiceCertificate, bool(const std::string&));
MOCK_METHOD1(GetSerializedMetrics, void(std::string*));
MOCK_METHOD2(GetMetrics, CdmResponseType(const CdmIdentifier&,
drm_metrics::WvCdmMetrics*));
};
class MockCrypto : public WVGenericCryptoInterface {
@@ -1126,8 +1130,9 @@ TEST_F(WVDrmPluginTest, ReturnsExpectedPropertyValues) {
static const std::string oemCryptoApiVersion = "13";
static const std::string currentSRMVersion = "1";
static const std::string cdmVersion = "Infinity Minus 1";
std::string serializedMetrics(
kSerializedMetrics, kSerializedMetrics + sizeof(kSerializedMetrics));
drm_metrics::WvCdmMetrics expected_metrics;
std::string serialized_metrics = wvcdm::a2bs_hex(kSerializedMetricsHex);
ASSERT_TRUE(expected_metrics.ParseFromString(serialized_metrics));
EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_SECURITY_LEVEL, _))
.WillOnce(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L1),
@@ -1171,8 +1176,9 @@ TEST_F(WVDrmPluginTest, ReturnsExpectedPropertyValues) {
.WillOnce(DoAll(SetArgPointee<2>(cdmVersion),
testing::Return(wvcdm::NO_ERROR)));
EXPECT_CALL(*cdm, GetSerializedMetrics(_))
.WillOnce(SetArgPointee<0>(serializedMetrics));
EXPECT_CALL(*cdm, GetMetrics(_, _))
.WillOnce(DoAll(SetArgPointee<1>(expected_metrics),
testing::Return(wvcdm::NO_ERROR)));
WVDrmPlugin plugin(cdm.get(), appPackageName, &crypto, false);
std::string stringResult;
@@ -1275,14 +1281,51 @@ TEST_F(WVDrmPluginTest, ReturnsExpectedPropertyValues) {
EXPECT_STREQ(currentSRMVersion.c_str(), stringResult.c_str());
});
// This call occurs before any open session or other call. This means
// that the cdm identifer is not yet sealed, and metrics return empty
// metrics data.
plugin.getPropertyByteArray(
hidl_string("metrics"),
[&](Status status, hidl_vec<uint8_t> vectorResult) {
ASSERT_EQ(Status::OK, status);
std::vector<uint8_t> id(vectorResult);
EXPECT_THAT(id, ElementsAreArray(serializedMetrics.data(),
serializedMetrics.size()));
const char empty[] = {};
EXPECT_THAT(id, ElementsAreArray(empty, sizeof(empty)));
});
// Set expectations for the OpenSession call and a CloseSession call.
EXPECT_CALL(*cdm,
OpenSession(StrEq("com.widevine"), _, HasOrigin(EMPTY_ORIGIN), _, _))
.WillOnce(DoAll(SetArgPointee<4>(cdmSessionId),
testing::Return(wvcdm::NO_ERROR)));
// Provide expected behavior when plugin requests session control info
EXPECT_CALL(*cdm, QueryOemCryptoSessionId(cdmSessionId, _))
.Times(AtLeast(1))
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
EXPECT_CALL(*cdm, CloseSession(_))
.Times(AtLeast(0));
// This call causes the cdm identifier to become sealed.
std::vector<uint8_t> sessionId;
plugin.openSession([&](Status status, hidl_vec<uint8_t> hSessionId) {
ASSERT_EQ(Status::OK, status);
sessionId.clear();
sessionId.assign(hSessionId.data(),
hSessionId.data() + hSessionId.size());
});
// This call occurs after open session. The CDM identifer should be sealed.
// And the call should populate the mock metrics data.
plugin.getPropertyByteArray(
hidl_string("metrics"),
[&](Status status, hidl_vec<uint8_t> vectorResult) {
ASSERT_EQ(Status::OK, status);
std::vector<uint8_t> id(vectorResult);
EXPECT_THAT(id, ElementsAreArray(serialized_metrics.data(),
serialized_metrics.size()));
});
ASSERT_EQ(Status::OK, plugin.closeSession(toHidlVec(sessionId)));
}
TEST_F(WVDrmPluginTest, DoesNotGetUnknownProperties) {

View File

@@ -13,6 +13,7 @@
#include "media/stagefright/foundation/ABase.h"
#include "media/stagefright/foundation/AString.h"
#include "media/stagefright/MediaErrors.h"
#include "string_conversions.h"
#include "wv_cdm_constants.h"
#include "wv_cdm_types.h"
#include "wv_content_decryption_module.h"
@@ -33,14 +34,18 @@ const uint8_t* const kUnprovisionResponse =
reinterpret_cast<const uint8_t*>("unprovision");
const size_t kUnprovisionResponseSize = 11;
// This is a serialized MetricsGroup message containing a small amount of
// This is a serialized WvCdmMetrics message containing a small amount of
// sample data. This ensures we're able to extract it via a property.
const char kSerializedMetrics[] = {
0x0a, 0x0a, 0x0a, 0x04, 0x74, 0x65, 0x73, 0x74, 0x12, 0x02, 0x08, 0x00,
0x0a, 0x12, 0x0a, 0x05, 0x74, 0x65, 0x73, 0x74, 0x32, 0x12, 0x09, 0x11,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x15, 0x0a, 0x05,
0x74, 0x65, 0x73, 0x74, 0x33, 0x12, 0x0c, 0x1a, 0x0a, 0x74, 0x65, 0x73,
0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65};
const char kSerializedMetricsHex[] =
"0a580a001a0210072202100d2a02100832182216636f6d2e676f6f676c652e616e64726f69"
"642e676d734208220631342e302e304a06080112020800520610d5f3fad5056a0b1d00fd4c"
"47280132020804a2010608011202080012cb010a0622047369643412b5010a021001520919"
"1d5a643bdfff50405a0b1d00d01945280132021001620d1d00f8e84528013204080020006a"
"0310b739820102100d8a01060801120248009a010310ff01da0106080112024800e2010e1d"
"005243472801320528800248009a020d1d00b016452801320428404800a202060801120248"
"19aa0206080212024800b2020b1d8098f047280132024800ba02021001ca020b1d00101945"
"280132024800e202021004fa02021002a203021000b2030210021a09196891ed7c3f355040";
} // anonymous namespace
class MockCDM : public WvContentDecryptionModule {
@@ -108,7 +113,8 @@ class MockCDM : public WvContentDecryptionModule {
MOCK_METHOD1(IsValidServiceCertificate, bool(const std::string&));
MOCK_METHOD1(GetSerializedMetrics, void(std::string*));
MOCK_METHOD2(GetMetrics, CdmResponseType(const CdmIdentifier&,
drm_metrics::WvCdmMetrics*));
};
class MockCrypto : public WVGenericCryptoInterface {
@@ -850,8 +856,10 @@ TEST_F(WVDrmPluginTest, ReturnsExpectedPropertyValues) {
static const string oemCryptoApiVersion = "10";
static const string currentSRMVersion = "1";
static const string cdmVersion = "Infinity Minus 1";
string serializedMetrics(kSerializedMetrics,
kSerializedMetrics + sizeof(kSerializedMetrics));
drm_metrics::WvCdmMetrics expected_metrics;
std::string serialized_metrics = wvcdm::a2bs_hex(kSerializedMetricsHex);
ASSERT_TRUE(expected_metrics.ParseFromString(serialized_metrics));
EXPECT_CALL(*cdm, QueryStatus(_, QUERY_KEY_SECURITY_LEVEL, _))
.WillOnce(DoAll(SetArgPointee<2>(QUERY_VALUE_SECURITY_LEVEL_L1),
@@ -895,8 +903,9 @@ TEST_F(WVDrmPluginTest, ReturnsExpectedPropertyValues) {
.WillOnce(DoAll(SetArgPointee<2>(cdmVersion),
Return(wvcdm::NO_ERROR)));
EXPECT_CALL(*cdm, GetSerializedMetrics(_))
.WillOnce(SetArgPointee<0>(serializedMetrics));
EXPECT_CALL(*cdm, GetMetrics(_, _))
.WillOnce(DoAll(SetArgPointee<1>(expected_metrics),
testing::Return(wvcdm::NO_ERROR)));
String8 stringResult;
Vector<uint8_t> vectorResult;
@@ -962,8 +971,8 @@ TEST_F(WVDrmPluginTest, ReturnsExpectedPropertyValues) {
vectorResult.clear();
res = plugin.getPropertyByteArray(String8("metrics"), vectorResult);
ASSERT_EQ(OK, res);
EXPECT_THAT(vectorResult, ElementsAreArray(serializedMetrics.data(),
serializedMetrics.size()));
EXPECT_THAT(vectorResult, ElementsAreArray(serialized_metrics.data(),
serialized_metrics.size()));
}
TEST_F(WVDrmPluginTest, DoesNotGetUnknownProperties) {