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:
Rahul Frias
2017-01-09 17:54:06 -08:00
parent 3bb90b9450
commit 079ee03869
4 changed files with 231 additions and 2 deletions

View File

@@ -98,6 +98,13 @@ class DeviceFiles {
CdmKeyMessage* license_request,
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:
// Helpers that wrap the File interface and automatically handle hashing, as
// 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);
static std::string GetCertificateFileName();
static std::string GetHlsAttributesFileNameExtension();
static std::string GetLicenseFileNameExtension();
static std::string GetUsageInfoFileName(const std::string& app_id);
static std::string GetFileNameSafeHash(const std::string& input);
@@ -122,6 +130,9 @@ class DeviceFiles {
FRIEND_TEST(DeviceCertificateTest, ReadCertificate);
FRIEND_TEST(DeviceCertificateTest, HasCertificate);
FRIEND_TEST(DeviceFilesStoreTest, StoreLicense);
FRIEND_TEST(DeviceFilesHlsAttributesTest, Delete);
FRIEND_TEST(DeviceFilesHlsAttributesTest, Read);
FRIEND_TEST(DeviceFilesHlsAttributesTest, Store);
FRIEND_TEST(DeviceFilesTest, DeleteLicense);
FRIEND_TEST(DeviceFilesTest, ReserveLicenseIdsDoesNotUseFileSystem);
FRIEND_TEST(DeviceFilesTest, RetrieveLicenses);

View File

@@ -18,13 +18,16 @@
#define MD5 CC_MD5
#define MD5_DIGEST_LENGTH CC_MD5_DIGEST_LENGTH
#else
#include <openssl/sha.h>
#include <openssl/md5.h>
#include <openssl/sha.h>
#endif
// Protobuf generated classes.
using video_widevine_client::sdk::DeviceCertificate;
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_LicenseState_ACTIVE;
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
// directory itself.
// TODO[gmorgan]: verify RemoveFile("") should remove all files.
return RemoveFile(kEmptyFileName);
}
@@ -523,6 +527,103 @@ bool DeviceFiles::RetrieveUsageInfoByKeySetId(
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,
const std::string& serialized_file) {
// calculate SHA hash
@@ -691,6 +792,10 @@ std::string DeviceFiles::GetCertificateFileName() {
return kCertificateFileName;
}
std::string DeviceFiles::GetHlsAttributesFileNameExtension() {
return kHlsAttributesFileNameExt;
}
std::string DeviceFiles::GetLicenseFileNameExtension() {
return kLicenseFileNameExt;
}

View File

@@ -52,11 +52,21 @@ message UsageInfo {
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 {
enum FileType {
DEVICE_CERTIFICATE = 1;
LICENSE = 2;
USAGE_INFO = 3;
HLS_ATTRIBUTES = 4;
}
enum FileVersion {
@@ -68,6 +78,7 @@ message File {
optional DeviceCertificate device_certificate = 3;
optional License license = 4;
optional UsageInfo usage_info = 5;
optional HlsAttributes hls_attributes = 6;
}
message HashedFile {

View File

@@ -1408,7 +1408,28 @@ UsageInfo kUsageInfoTestData[] = {
"0E12202FF1FBA9926A24A1F79970EC427DDF87B4421488F7952499BC33CEB282D9E48"
"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 {
public:
@@ -1506,12 +1527,22 @@ class DeviceFilesSecurityLevelTest
class DeviceFilesUsageInfoTest : public DeviceFilesTest,
public ::testing::WithParamInterface<int> {};
class DeviceFilesHlsAttributesTest
: public DeviceFilesTest,
public ::testing::WithParamInterface<HlsAttributesInfo*> {};
MATCHER(IsCreateFileFlagSet, "") { return FileSystem::kCreate & arg; }
MATCHER_P(IsStrEq, str, "") {
// 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
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, "") {
// 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
@@ -2209,4 +2240,75 @@ TEST_P(DeviceFilesUsageInfoTest, DeleteAll) {
INSTANTIATE_TEST_CASE_P(UsageInfo, DeviceFilesUsageInfoTest,
::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