From 079ee03869693b80f517da0428bd186dea06876e Mon Sep 17 00:00:00 2001 From: Rahul Frias Date: Mon, 9 Jan 2017 17:54:06 -0800 Subject: [PATCH] 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 --- .../cdm/core/include/device_files.h | 11 ++ libwvdrmengine/cdm/core/src/device_files.cpp | 107 +++++++++++++++++- .../cdm/core/src/device_files.proto | 11 ++ .../cdm/core/test/device_files_unittest.cpp | 104 ++++++++++++++++- 4 files changed, 231 insertions(+), 2 deletions(-) diff --git a/libwvdrmengine/cdm/core/include/device_files.h b/libwvdrmengine/cdm/core/include/device_files.h index e60559e6..6eef4ee7 100644 --- a/libwvdrmengine/cdm/core/include/device_files.h +++ b/libwvdrmengine/cdm/core/include/device_files.h @@ -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& media_segment_iv); + virtual bool RetrieveHlsAttributes(const std::string& key_set_id, + CdmHlsMethod* method, + std::vector* 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); diff --git a/libwvdrmengine/cdm/core/src/device_files.cpp b/libwvdrmengine/cdm/core/src/device_files.cpp index e08d8666..76a64148 100644 --- a/libwvdrmengine/cdm/core/src/device_files.cpp +++ b/libwvdrmengine/cdm/core/src/device_files.cpp @@ -18,13 +18,16 @@ #define MD5 CC_MD5 #define MD5_DIGEST_LENGTH CC_MD5_DIGEST_LENGTH #else -#include #include +#include #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& 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* 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; } diff --git a/libwvdrmengine/cdm/core/src/device_files.proto b/libwvdrmengine/cdm/core/src/device_files.proto index e9b793fd..89bd0c5e 100644 --- a/libwvdrmengine/cdm/core/src/device_files.proto +++ b/libwvdrmengine/cdm/core/src/device_files.proto @@ -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 { diff --git a/libwvdrmengine/cdm/core/test/device_files_unittest.cpp b/libwvdrmengine/cdm/core/test/device_files_unittest.cpp index 21d7ee39..dacf0882 100644 --- a/libwvdrmengine/cdm/core/test/device_files_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/device_files_unittest.cpp @@ -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 {}; +class DeviceFilesHlsAttributesTest + : public DeviceFilesTest, + public ::testing::WithParamInterface {}; + 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 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 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