Offline playback for fastball
[ Merge of http://go/wvgerrit/18560 ] This adds support for offline playback. If the content contains mutiple playlists which contain differing EXT-X-KEY attribute lists, each of those keys will need to be saved and restored into separate sessions. b/30041089 Test: Added unit tests to cover new functionality. Some oem_crypto, request_license_test failures but the same as without this CL. Change-Id: Ia1b877e12a67e8a720d29897ac7e2da236090123
This commit is contained in:
@@ -98,6 +98,13 @@ class DeviceFiles {
|
|||||||
CdmKeyMessage* license_request,
|
CdmKeyMessage* license_request,
|
||||||
CdmKeyResponse* license_response);
|
CdmKeyResponse* license_response);
|
||||||
|
|
||||||
|
virtual bool StoreHlsAttributes(const std::string& key_set_id,
|
||||||
|
const CdmHlsMethod method,
|
||||||
|
const std::vector<uint8_t>& media_segment_iv);
|
||||||
|
virtual bool RetrieveHlsAttributes(const std::string& key_set_id,
|
||||||
|
CdmHlsMethod* method,
|
||||||
|
std::vector<uint8_t>* media_segment_iv);
|
||||||
|
virtual bool DeleteHlsAttributes(const std::string& key_set_id);
|
||||||
private:
|
private:
|
||||||
// Helpers that wrap the File interface and automatically handle hashing, as
|
// Helpers that wrap the File interface and automatically handle hashing, as
|
||||||
// well as adding the device files base path to to the file name.
|
// well as adding the device files base path to to the file name.
|
||||||
@@ -112,6 +119,7 @@ class DeviceFiles {
|
|||||||
ssize_t GetFileSize(const std::string& name);
|
ssize_t GetFileSize(const std::string& name);
|
||||||
|
|
||||||
static std::string GetCertificateFileName();
|
static std::string GetCertificateFileName();
|
||||||
|
static std::string GetHlsAttributesFileNameExtension();
|
||||||
static std::string GetLicenseFileNameExtension();
|
static std::string GetLicenseFileNameExtension();
|
||||||
static std::string GetUsageInfoFileName(const std::string& app_id);
|
static std::string GetUsageInfoFileName(const std::string& app_id);
|
||||||
static std::string GetFileNameSafeHash(const std::string& input);
|
static std::string GetFileNameSafeHash(const std::string& input);
|
||||||
@@ -122,6 +130,9 @@ class DeviceFiles {
|
|||||||
FRIEND_TEST(DeviceCertificateTest, ReadCertificate);
|
FRIEND_TEST(DeviceCertificateTest, ReadCertificate);
|
||||||
FRIEND_TEST(DeviceCertificateTest, HasCertificate);
|
FRIEND_TEST(DeviceCertificateTest, HasCertificate);
|
||||||
FRIEND_TEST(DeviceFilesStoreTest, StoreLicense);
|
FRIEND_TEST(DeviceFilesStoreTest, StoreLicense);
|
||||||
|
FRIEND_TEST(DeviceFilesHlsAttributesTest, Delete);
|
||||||
|
FRIEND_TEST(DeviceFilesHlsAttributesTest, Read);
|
||||||
|
FRIEND_TEST(DeviceFilesHlsAttributesTest, Store);
|
||||||
FRIEND_TEST(DeviceFilesTest, DeleteLicense);
|
FRIEND_TEST(DeviceFilesTest, DeleteLicense);
|
||||||
FRIEND_TEST(DeviceFilesTest, ReserveLicenseIdsDoesNotUseFileSystem);
|
FRIEND_TEST(DeviceFilesTest, ReserveLicenseIdsDoesNotUseFileSystem);
|
||||||
FRIEND_TEST(DeviceFilesTest, RetrieveLicenses);
|
FRIEND_TEST(DeviceFilesTest, RetrieveLicenses);
|
||||||
|
|||||||
@@ -18,13 +18,16 @@
|
|||||||
#define MD5 CC_MD5
|
#define MD5 CC_MD5
|
||||||
#define MD5_DIGEST_LENGTH CC_MD5_DIGEST_LENGTH
|
#define MD5_DIGEST_LENGTH CC_MD5_DIGEST_LENGTH
|
||||||
#else
|
#else
|
||||||
#include <openssl/sha.h>
|
|
||||||
#include <openssl/md5.h>
|
#include <openssl/md5.h>
|
||||||
|
#include <openssl/sha.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Protobuf generated classes.
|
// Protobuf generated classes.
|
||||||
using video_widevine_client::sdk::DeviceCertificate;
|
using video_widevine_client::sdk::DeviceCertificate;
|
||||||
using video_widevine_client::sdk::HashedFile;
|
using video_widevine_client::sdk::HashedFile;
|
||||||
|
using video_widevine_client::sdk::HlsAttributes;
|
||||||
|
using video_widevine_client::sdk::HlsAttributes_Method_AES_128;
|
||||||
|
using video_widevine_client::sdk::HlsAttributes_Method_SAMPLE_AES;
|
||||||
using video_widevine_client::sdk::License;
|
using video_widevine_client::sdk::License;
|
||||||
using video_widevine_client::sdk::License_LicenseState_ACTIVE;
|
using video_widevine_client::sdk::License_LicenseState_ACTIVE;
|
||||||
using video_widevine_client::sdk::License_LicenseState_RELEASING;
|
using video_widevine_client::sdk::License_LicenseState_RELEASING;
|
||||||
@@ -298,6 +301,7 @@ bool DeviceFiles::DeleteAllFiles() {
|
|||||||
|
|
||||||
// We pass an empty string to RemoveFile to delete the device files base
|
// We pass an empty string to RemoveFile to delete the device files base
|
||||||
// directory itself.
|
// directory itself.
|
||||||
|
// TODO[gmorgan]: verify RemoveFile("") should remove all files.
|
||||||
return RemoveFile(kEmptyFileName);
|
return RemoveFile(kEmptyFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -523,6 +527,103 @@ bool DeviceFiles::RetrieveUsageInfoByKeySetId(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DeviceFiles::StoreHlsAttributes(
|
||||||
|
const std::string& key_set_id, const CdmHlsMethod method,
|
||||||
|
const std::vector<uint8_t>& media_segment_iv) {
|
||||||
|
if (!initialized_) {
|
||||||
|
LOGW("DeviceFiles::StoreHlsAttributes: not initialized");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill in file information
|
||||||
|
video_widevine_client::sdk::File file;
|
||||||
|
|
||||||
|
file.set_type(video_widevine_client::sdk::File::HLS_ATTRIBUTES);
|
||||||
|
file.set_version(video_widevine_client::sdk::File::VERSION_1);
|
||||||
|
|
||||||
|
HlsAttributes* hls_attributes = file.mutable_hls_attributes();
|
||||||
|
switch (method) {
|
||||||
|
case kHlsMethodAes128:
|
||||||
|
hls_attributes->set_method(HlsAttributes_Method_AES_128);
|
||||||
|
break;
|
||||||
|
case kHlsMethodSampleAes:
|
||||||
|
hls_attributes->set_method(HlsAttributes_Method_SAMPLE_AES);
|
||||||
|
break;
|
||||||
|
case kHlsMethodNone:
|
||||||
|
default:
|
||||||
|
LOGW("DeviceFiles::StoreHlsAttributeInfo: Unknown HLS method: %u",
|
||||||
|
method);
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
hls_attributes->set_media_segment_iv(&media_segment_iv[0],
|
||||||
|
media_segment_iv.size());
|
||||||
|
|
||||||
|
std::string serialized_file;
|
||||||
|
file.SerializeToString(&serialized_file);
|
||||||
|
|
||||||
|
return StoreFileWithHash(key_set_id + kHlsAttributesFileNameExt,
|
||||||
|
serialized_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeviceFiles::RetrieveHlsAttributes(
|
||||||
|
const std::string& key_set_id, CdmHlsMethod* method,
|
||||||
|
std::vector<uint8_t>* media_segment_iv) {
|
||||||
|
if (!initialized_) {
|
||||||
|
LOGW("DeviceFiles::RetrieveHlsAttributes: not initialized");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
video_widevine_client::sdk::File file;
|
||||||
|
if (!RetrieveHashedFile(key_set_id + kHlsAttributesFileNameExt, &file)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.type() != video_widevine_client::sdk::File::HLS_ATTRIBUTES) {
|
||||||
|
LOGW("DeviceFiles::RetrieveHlsAttributes: Incorrect file type: %u",
|
||||||
|
file.type());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.version() != video_widevine_client::sdk::File::VERSION_1) {
|
||||||
|
LOGW("DeviceFiles::RetrieveHlsAttributes: Incorrect file version: %u",
|
||||||
|
file.version());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file.has_hls_attributes()) {
|
||||||
|
LOGW("DeviceFiles::RetrieveHlsAttributes: HLS attributes not present");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
HlsAttributes attributes = file.hls_attributes();
|
||||||
|
|
||||||
|
switch (attributes.method()) {
|
||||||
|
case HlsAttributes_Method_AES_128:
|
||||||
|
*method = kHlsMethodAes128;
|
||||||
|
break;
|
||||||
|
case HlsAttributes_Method_SAMPLE_AES:
|
||||||
|
*method = kHlsMethodSampleAes;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOGW("DeviceFiles::RetrieveHlsAttributes: Unrecognized HLS method: %u",
|
||||||
|
attributes.method());
|
||||||
|
*method = kHlsMethodNone;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
media_segment_iv->assign(attributes.media_segment_iv().begin(),
|
||||||
|
attributes.media_segment_iv().end());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DeviceFiles::DeleteHlsAttributes(const std::string& key_set_id) {
|
||||||
|
if (!initialized_) {
|
||||||
|
LOGW("DeviceFiles::DeleteHlsAttributes: not initialized");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return RemoveFile(key_set_id + kHlsAttributesFileNameExt);
|
||||||
|
}
|
||||||
|
|
||||||
bool DeviceFiles::StoreFileWithHash(const std::string& name,
|
bool DeviceFiles::StoreFileWithHash(const std::string& name,
|
||||||
const std::string& serialized_file) {
|
const std::string& serialized_file) {
|
||||||
// calculate SHA hash
|
// calculate SHA hash
|
||||||
@@ -691,6 +792,10 @@ std::string DeviceFiles::GetCertificateFileName() {
|
|||||||
return kCertificateFileName;
|
return kCertificateFileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string DeviceFiles::GetHlsAttributesFileNameExtension() {
|
||||||
|
return kHlsAttributesFileNameExt;
|
||||||
|
}
|
||||||
|
|
||||||
std::string DeviceFiles::GetLicenseFileNameExtension() {
|
std::string DeviceFiles::GetLicenseFileNameExtension() {
|
||||||
return kLicenseFileNameExt;
|
return kLicenseFileNameExt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,11 +52,21 @@ message UsageInfo {
|
|||||||
repeated ProviderSession sessions = 1;
|
repeated ProviderSession sessions = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message HlsAttributes {
|
||||||
|
enum Method {
|
||||||
|
AES_128 = 1;
|
||||||
|
SAMPLE_AES = 2;
|
||||||
|
}
|
||||||
|
optional Method method = 1;
|
||||||
|
optional bytes media_segment_iv = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message File {
|
message File {
|
||||||
enum FileType {
|
enum FileType {
|
||||||
DEVICE_CERTIFICATE = 1;
|
DEVICE_CERTIFICATE = 1;
|
||||||
LICENSE = 2;
|
LICENSE = 2;
|
||||||
USAGE_INFO = 3;
|
USAGE_INFO = 3;
|
||||||
|
HLS_ATTRIBUTES = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum FileVersion {
|
enum FileVersion {
|
||||||
@@ -68,6 +78,7 @@ message File {
|
|||||||
optional DeviceCertificate device_certificate = 3;
|
optional DeviceCertificate device_certificate = 3;
|
||||||
optional License license = 4;
|
optional License license = 4;
|
||||||
optional UsageInfo usage_info = 5;
|
optional UsageInfo usage_info = 5;
|
||||||
|
optional HlsAttributes hls_attributes = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
message HashedFile {
|
message HashedFile {
|
||||||
|
|||||||
@@ -1408,7 +1408,28 @@ UsageInfo kUsageInfoTestData[] = {
|
|||||||
"0E12202FF1FBA9926A24A1F79970EC427DDF87B4421488F7952499BC33CEB282D9E48"
|
"0E12202FF1FBA9926A24A1F79970EC427DDF87B4421488F7952499BC33CEB282D9E48"
|
||||||
"A")}};
|
"A")}};
|
||||||
|
|
||||||
const std::string kTestOrigin = "com.google";
|
struct HlsAttributesInfo {
|
||||||
|
std::string key_set_id;
|
||||||
|
CdmHlsMethod method;
|
||||||
|
std::string media_segment_iv;
|
||||||
|
std::string file_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
HlsAttributesInfo kHlsAttributesTestData[] = {
|
||||||
|
{
|
||||||
|
"ksidC8EAA2579A282EB0", kHlsMethodAes128, // hls attributes 0
|
||||||
|
a2bs_hex("F7C4D15BD466BF285E241A4E58638543"),
|
||||||
|
a2bs_hex("0A1A08041001321408011210F7C4D15BD466BF285E241A4E5863854312201"
|
||||||
|
"39114B0372FF80FADF92614106E27BE8BD1588B4CAE6E1AEFB7F9C34EA52E"
|
||||||
|
"CC"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ksidE8C37662C88DC673", kHlsMethodSampleAes, // hls attributes 1
|
||||||
|
a2bs_hex("16413F038088438B5D4CD99F03EBB3D8"),
|
||||||
|
a2bs_hex("0A1A0804100132140802121016413F038088438B5D4CD99F03EBB3D812205"
|
||||||
|
"9EA13188B75C55D1EB78B3A65DB3EA3F43BD1B16642266D988E3543943C5F"
|
||||||
|
"41"),
|
||||||
|
}};
|
||||||
|
|
||||||
class MockFile : public File {
|
class MockFile : public File {
|
||||||
public:
|
public:
|
||||||
@@ -1506,12 +1527,22 @@ class DeviceFilesSecurityLevelTest
|
|||||||
class DeviceFilesUsageInfoTest : public DeviceFilesTest,
|
class DeviceFilesUsageInfoTest : public DeviceFilesTest,
|
||||||
public ::testing::WithParamInterface<int> {};
|
public ::testing::WithParamInterface<int> {};
|
||||||
|
|
||||||
|
class DeviceFilesHlsAttributesTest
|
||||||
|
: public DeviceFilesTest,
|
||||||
|
public ::testing::WithParamInterface<HlsAttributesInfo*> {};
|
||||||
|
|
||||||
MATCHER(IsCreateFileFlagSet, "") { return FileSystem::kCreate & arg; }
|
MATCHER(IsCreateFileFlagSet, "") { return FileSystem::kCreate & arg; }
|
||||||
MATCHER_P(IsStrEq, str, "") {
|
MATCHER_P(IsStrEq, str, "") {
|
||||||
// Estimating the length of data. We can have gmock provide length
|
// Estimating the length of data. We can have gmock provide length
|
||||||
// as well as pointer to data but that will introduce a dependency on tr1
|
// as well as pointer to data but that will introduce a dependency on tr1
|
||||||
return memcmp(arg, str.c_str(), str.size()) == 0;
|
return memcmp(arg, str.c_str(), str.size()) == 0;
|
||||||
}
|
}
|
||||||
|
MATCHER_P2(Contains, str1, size, "") {
|
||||||
|
// Estimating the length of data. We can have gmock provide length
|
||||||
|
// as well as pointer to data but that will introduce a dependency on tr1
|
||||||
|
std::string data(arg, size + str1.size() + kProtobufEstimatedOverhead);
|
||||||
|
return (data.find(str1) != std::string::npos);
|
||||||
|
}
|
||||||
MATCHER_P3(Contains, str1, str2, size, "") {
|
MATCHER_P3(Contains, str1, str2, size, "") {
|
||||||
// Estimating the length of data. We can have gmock provide length
|
// Estimating the length of data. We can have gmock provide length
|
||||||
// as well as pointer to data but that will introduce a dependency on tr1
|
// as well as pointer to data but that will introduce a dependency on tr1
|
||||||
@@ -2209,4 +2240,75 @@ TEST_P(DeviceFilesUsageInfoTest, DeleteAll) {
|
|||||||
INSTANTIATE_TEST_CASE_P(UsageInfo, DeviceFilesUsageInfoTest,
|
INSTANTIATE_TEST_CASE_P(UsageInfo, DeviceFilesUsageInfoTest,
|
||||||
::testing::Range(-1, 4));
|
::testing::Range(-1, 4));
|
||||||
|
|
||||||
|
TEST_P(DeviceFilesHlsAttributesTest, Read) {
|
||||||
|
MockFileSystem file_system;
|
||||||
|
MockFile file;
|
||||||
|
HlsAttributesInfo* param = GetParam();
|
||||||
|
std::string path = device_base_path_ + param->key_set_id +
|
||||||
|
DeviceFiles::GetHlsAttributesFileNameExtension();
|
||||||
|
|
||||||
|
EXPECT_CALL(file_system, Exists(StrEq(path))).WillRepeatedly(Return(true));
|
||||||
|
EXPECT_CALL(file_system, FileSize(StrEq(path)))
|
||||||
|
.WillRepeatedly(Return(param->file_data.size()));
|
||||||
|
EXPECT_CALL(file_system, Open(StrEq(path), _)).WillOnce(Return(&file));
|
||||||
|
EXPECT_CALL(file, Read(NotNull(), Eq(param->file_data.size())))
|
||||||
|
.WillOnce(DoAll(
|
||||||
|
SetArrayArgument<0>(param->file_data.begin(), param->file_data.end()),
|
||||||
|
Return(param->file_data.size())));
|
||||||
|
EXPECT_CALL(file, Close()).Times(1);
|
||||||
|
|
||||||
|
EXPECT_CALL(file, Write(_, _)).Times(0);
|
||||||
|
|
||||||
|
DeviceFiles device_files(&file_system);
|
||||||
|
EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
|
||||||
|
|
||||||
|
CdmHlsMethod method;
|
||||||
|
std::vector<uint8_t> media_segment_iv;
|
||||||
|
ASSERT_TRUE(device_files.RetrieveHlsAttributes(param->key_set_id, &method,
|
||||||
|
&media_segment_iv));
|
||||||
|
EXPECT_EQ(param->method, method);
|
||||||
|
EXPECT_EQ(b2a_hex(param->media_segment_iv), b2a_hex(media_segment_iv));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(DeviceFilesHlsAttributesTest, Store) {
|
||||||
|
MockFileSystem file_system;
|
||||||
|
MockFile file;
|
||||||
|
HlsAttributesInfo* param = GetParam();
|
||||||
|
std::string path = device_base_path_ + param->key_set_id +
|
||||||
|
DeviceFiles::GetHlsAttributesFileNameExtension();
|
||||||
|
|
||||||
|
EXPECT_CALL(file_system, Exists(StrEq(path))).WillRepeatedly(Return(true));
|
||||||
|
EXPECT_CALL(file_system, Open(StrEq(path), _)).WillOnce(Return(&file));
|
||||||
|
EXPECT_CALL(file, Write(Contains(param->media_segment_iv, 0),
|
||||||
|
Gt(param->media_segment_iv.size())))
|
||||||
|
.WillOnce(ReturnArg<1>());
|
||||||
|
EXPECT_CALL(file, Read(_, _)).Times(0);
|
||||||
|
EXPECT_CALL(file, Close()).Times(1);
|
||||||
|
|
||||||
|
DeviceFiles device_files(&file_system);
|
||||||
|
EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
|
||||||
|
std::vector<uint8_t> iv(param->media_segment_iv.begin(),
|
||||||
|
param->media_segment_iv.end());
|
||||||
|
ASSERT_TRUE(
|
||||||
|
device_files.StoreHlsAttributes(param->key_set_id, param->method, iv));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_P(DeviceFilesHlsAttributesTest, Delete) {
|
||||||
|
MockFileSystem file_system;
|
||||||
|
MockFile file;
|
||||||
|
HlsAttributesInfo* param = GetParam();
|
||||||
|
std::string path = device_base_path_ + param->key_set_id +
|
||||||
|
DeviceFiles::GetHlsAttributesFileNameExtension();
|
||||||
|
|
||||||
|
EXPECT_CALL(file_system, Remove(StrEq(path))).WillOnce(Return(true));
|
||||||
|
|
||||||
|
DeviceFiles device_files(&file_system);
|
||||||
|
EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
|
||||||
|
ASSERT_TRUE(device_files.DeleteHlsAttributes(param->key_set_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
INSTANTIATE_TEST_CASE_P(HlsAttributes, DeviceFilesHlsAttributesTest,
|
||||||
|
::testing::Range(&kHlsAttributesTestData[0],
|
||||||
|
&kHlsAttributesTestData[2]));
|
||||||
|
|
||||||
} // namespace wvcdm
|
} // namespace wvcdm
|
||||||
|
|||||||
Reference in New Issue
Block a user