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,
|
||||
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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user