diff options
-rw-r--r-- | components/cdm/browser/DEPS | 2 | ||||
-rw-r--r-- | components/cdm/browser/widevine_drm_delegate_android.cc | 140 | ||||
-rw-r--r-- | media/cdm/cenc_utils.cc | 215 | ||||
-rw-r--r-- | media/cdm/cenc_utils.h | 11 | ||||
-rw-r--r-- | media/cdm/cenc_utils_unittest.cc | 217 | ||||
-rw-r--r-- | media/formats/mp4/box_definitions.cc | 57 | ||||
-rw-r--r-- | media/formats/mp4/box_definitions.h | 12 | ||||
-rw-r--r-- | media/formats/mp4/box_reader.cc | 56 | ||||
-rw-r--r-- | media/formats/mp4/box_reader.h | 19 |
9 files changed, 395 insertions, 334 deletions
diff --git a/components/cdm/browser/DEPS b/components/cdm/browser/DEPS index d0fb59f..1891599 100644 --- a/components/cdm/browser/DEPS +++ b/components/cdm/browser/DEPS @@ -1,4 +1,4 @@ include_rules = [ "+content/public/browser", - "+media/base/android", + "+media/cdm", ] diff --git a/components/cdm/browser/widevine_drm_delegate_android.cc b/components/cdm/browser/widevine_drm_delegate_android.cc index 1c2b6e4..8acf800 100644 --- a/components/cdm/browser/widevine_drm_delegate_android.cc +++ b/components/cdm/browser/widevine_drm_delegate_android.cc @@ -4,151 +4,17 @@ #include "components/cdm/browser/widevine_drm_delegate_android.h" -#include "base/logging.h" -#include "base/numerics/safe_conversions.h" +#include "media/cdm/cenc_utils.h" namespace cdm { namespace { -uint32_t ReadUint32(const uint8_t* data) { - uint32_t value = 0; - for (int i = 0; i < 4; ++i) - value = (value << 8) | data[i]; - return value; -} - -uint64_t ReadUint64(const uint8_t* data) { - uint64_t value = 0; - for (int i = 0; i < 8; ++i) - value = (value << 8) | data[i]; - return value; -} - -// The structure of an ISO CENC Protection System Specific Header (PSSH) box is -// as follows. (See ISO/IEC FDIS 23001-7:2011(E).) -// Note: ISO boxes use big-endian values. -// -// PSSH { -// uint32 Size -// uint32 Type -// uint64 LargeSize # Field is only present if value(Size) == 1. -// uint8 Version -// uint24 Flags -// uint8[16] SystemId -// if (version > 0) { -// uint32 KID_count; -// uint8[16][KID_Count] KID; -// } -// uint32 DataSize -// uint8[DataSize] Data -// } -const int kBoxHeaderSize = 8; // Box's header contains Size and Type. -const int kBoxLargeSizeSize = 8; -const int kPsshVersionFlagSize = 4; -const uint32_t k24BitMask = 0x00ffffff; -const int kPsshSystemIdSize = 16; -const int kPsshKidCountSize = 4; -const int kPsshKidSize = 16; -const int kPsshDataSizeSize = 4; -const uint32_t kPsshType = 0x70737368; - const uint8_t kWidevineUuid[16] = { 0xED, 0xEF, 0x8B, 0xA9, 0x79, 0xD6, 0x4A, 0xCE, 0xA3, 0xC8, 0x27, 0xDC, 0xD5, 0x1D, 0x21, 0xED }; -// Tries to find a PSSH box with the Widevine UUID, parses the -// "Data" of the box and put it in |pssh_data|. Returns true if such a box is -// found and successfully parsed. Returns false otherwise. -// Notes: -// 1, If multiple PSSH boxes are found,the "Data" of the first matching PSSH box -// will be set in |pssh_data|. -// 2, Only PSSH boxes are allowed in |data|. -bool GetPsshData(const std::vector<uint8_t>& data, - std::vector<uint8_t>* pssh_data) { - int bytes_left = base::checked_cast<int>(data.size()); - const uint8_t* cur = &data[0]; - const uint8_t* data_end = cur + bytes_left; - - while (bytes_left > 0) { - const uint8_t* box_head = cur; - - if (bytes_left < kBoxHeaderSize) - return false; - - uint64_t box_size = ReadUint32(cur); - uint32_t type = ReadUint32(cur + 4); - cur += kBoxHeaderSize; - bytes_left -= kBoxHeaderSize; - - if (box_size == 1) { // LargeSize is present. - if (bytes_left < kBoxLargeSizeSize) - return false; - - box_size = ReadUint64(cur); - cur += kBoxLargeSizeSize; - bytes_left -= kBoxLargeSizeSize; - } else if (box_size == 0) { - box_size = bytes_left + kBoxHeaderSize; - } - - const uint8_t* box_end = box_head + box_size; - if (data_end < box_end) - return false; - - if (type != kPsshType) - return false; - - const int kPsshBoxMinimumSize = - kPsshVersionFlagSize + kPsshSystemIdSize + kPsshDataSizeSize; - if (box_end < cur + kPsshBoxMinimumSize) - return false; - - uint8_t version = cur[0]; - uint32_t flags = ReadUint32(cur) & k24BitMask; - cur += kPsshVersionFlagSize; - bytes_left -= kPsshVersionFlagSize; - if (flags != 0) - return false; - - DCHECK_GE(bytes_left, kPsshSystemIdSize); - if (!std::equal(kWidevineUuid, - kWidevineUuid + sizeof(kWidevineUuid), cur)) { - cur = box_end; - bytes_left = data_end - cur; - continue; - } - - cur += kPsshSystemIdSize; - bytes_left -= kPsshSystemIdSize; - - // If KeyIDs specified, skip them. - if (version > 0) { - DCHECK_GE(bytes_left, kPsshKidCountSize); - uint32_t kid_count = ReadUint32(cur); - cur += kPsshKidCountSize + kid_count * kPsshKidSize; - bytes_left -= kPsshKidCountSize + kid_count * kPsshKidSize; - // Must be bytes left in this box for data_size. - if (box_end < cur + kPsshDataSizeSize) - return false; - } - - DCHECK_GE(bytes_left, kPsshDataSizeSize); - uint32_t data_size = ReadUint32(cur); - cur += kPsshDataSizeSize; - bytes_left -= kPsshDataSizeSize; - - if (box_end < cur + data_size) - return false; - - pssh_data->assign(cur, cur + data_size); - return true; - } - - return false; -} - -} +} // namespace WidevineDrmDelegateAndroid::WidevineDrmDelegateAndroid() { } @@ -171,7 +37,7 @@ bool WidevineDrmDelegateAndroid::OnCreateSession( // Widevine MediaDrm plugin only accepts the "data" part of the PSSH box as // the init data when using MP4 container. - return GetPsshData(init_data, init_data_out); + return media::GetPsshData(init_data, GetUUID(), init_data_out); } } // namespace cdm diff --git a/media/cdm/cenc_utils.cc b/media/cdm/cenc_utils.cc index 86779b2..495a6f2 100644 --- a/media/cdm/cenc_utils.cc +++ b/media/cdm/cenc_utils.cc @@ -4,7 +4,9 @@ #include "media/cdm/cenc_utils.h" -#include "media/base/bit_reader.h" +#include "base/stl_util.h" +#include "media/formats/mp4/box_definitions.h" +#include "media/formats/mp4/box_reader.h" namespace media { @@ -12,31 +14,6 @@ namespace media { // Encryption ('cenc') protection scheme may contain one or more protection // system specific header ('pssh') boxes. // ref: https://w3c.github.io/encrypted-media/cenc-format.html -// -// The format of a 'pssh' box is as follows: -// unsigned int(32) size; -// unsigned int(32) type = "pssh"; -// if (size==1) { -// unsigned int(64) largesize; -// } else if (size==0) { -// -- box extends to end of file -// } -// unsigned int(8) version; -// bit(24) flags; -// unsigned int(8)[16] SystemID; -// if (version > 0) -// { -// unsigned int(32) KID_count; -// { -// unsigned int(8)[16] KID; -// } [KID_count] -// } -// unsigned int(32) DataSize; -// unsigned int(8)[DataSize] Data; - -// Minimum size of a 'pssh' box includes all the required fields (size, type, -// version, flags, SystemID, DataSize). -const int kMinimumBoxSizeInBytes = 32; // SystemID for the Common System. // https://w3c.github.io/encrypted-media/cenc-format.html#common-system @@ -45,136 +22,102 @@ const uint8_t kCommonSystemId[] = { 0x10, 0x77, 0xef, 0xec, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b }; -#define RCHECK(x) \ - do { \ - if (!(x)) \ - return false; \ - } while (0) - -// Helper function to read up to 32 bits from a bit stream. -static uint32_t ReadBits(BitReader* reader, int num_bits) { - DCHECK_GE(reader->bits_available(), num_bits); - DCHECK((num_bits > 0) && (num_bits <= 32)); - uint32_t value; - reader->ReadBits(num_bits, &value); - return value; -} - -// Checks whether the next 16 bytes matches the Common SystemID. -// Assumes |reader| has enough data. -static bool IsCommonSystemID(BitReader* reader) { - for (uint32_t i = 0; i < arraysize(kCommonSystemId); ++i) { - if (ReadBits(reader, 8) != kCommonSystemId[i]) - return false; - } - return true; -} - -// Checks that |reader| contains a valid 'ppsh' box header. |reader| is updated -// to point to the content immediately following the box header. Returns true -// if the header looks valid and |reader| contains enough data for the size of -// header. |size| is updated as the computed size of the box header. Otherwise -// false is returned. -static bool ValidBoxHeader(BitReader* reader, uint32* size) { - // Enough data for a miniumum size 'pssh' box? - uint32 available_bytes = reader->bits_available() / 8; - RCHECK(available_bytes >= kMinimumBoxSizeInBytes); - - *size = ReadBits(reader, 32); - - // Must be a 'pssh' box or else fail. - RCHECK(ReadBits(reader, 8) == 'p'); - RCHECK(ReadBits(reader, 8) == 's'); - RCHECK(ReadBits(reader, 8) == 's'); - RCHECK(ReadBits(reader, 8) == 'h'); - - if (*size == 1) { - // If largesize > 2**32 it is too big. - RCHECK(ReadBits(reader, 32) == 0); - *size = ReadBits(reader, 32); - } else if (*size == 0) { - *size = available_bytes; +static bool ReadAllPsshBoxes( + const std::vector<uint8_t>& input, + std::vector<mp4::FullProtectionSystemSpecificHeader>* pssh_boxes) { + DCHECK(!input.empty()); + + // Verify that |input| contains only 'pssh' boxes. ReadAllChildren() is + // templated, so it checks that each box in |input| matches the box type of + // the parameter (in this case mp4::ProtectionSystemSpecificHeader is a + // 'pssh' box). mp4::ProtectionSystemSpecificHeader doesn't validate the + // 'pssh' contents, so this simply verifies that |input| only contains + // 'pssh' boxes and nothing else. + scoped_ptr<mp4::BoxReader> input_reader( + mp4::BoxReader::ReadConcatentatedBoxes( + vector_as_array(&input), input.size())); + std::vector<mp4::ProtectionSystemSpecificHeader> raw_pssh_boxes; + if (!input_reader->ReadAllChildren(&raw_pssh_boxes)) + return false; + + // Now that we have |input| parsed into |raw_pssh_boxes|, reparse each one + // into a mp4::FullProtectionSystemSpecificHeader, which extracts all the + // relevant fields from the box. Since there may be unparseable 'pssh' boxes + // (due to unsupported version, for example), this is done one by one, + // ignoring any boxes that can't be parsed. + for (const auto& raw_pssh_box : raw_pssh_boxes) { + scoped_ptr<mp4::BoxReader> raw_pssh_reader( + mp4::BoxReader::ReadConcatentatedBoxes( + vector_as_array(&raw_pssh_box.raw_box), + raw_pssh_box.raw_box.size())); + // ReadAllChildren() appends any successfully parsed box onto it's + // parameter, so |pssh_boxes| will contain the collection of successfully + // parsed 'pssh' boxes. If an error occurs, try the next box. + if (!raw_pssh_reader->ReadAllChildren(pssh_boxes)) + continue; } - // Check that the buffer contains at least size bytes. - return available_bytes >= *size; + // Must have successfully parsed at least one 'pssh' box. + return pssh_boxes->size() > 0; } bool ValidatePsshInput(const std::vector<uint8_t>& input) { - size_t offset = 0; - while (offset < input.size()) { - // Create a BitReader over the remaining part of the buffer. - BitReader reader(&input[offset], input.size() - offset); - uint32 size; - RCHECK(ValidBoxHeader(&reader, &size)); - - // Update offset to point at the next 'pssh' box (may not be one). - offset += size; - } + // No 'pssh' boxes is considered valid. + if (input.empty()) + return true; - // Only valid if this contains 0 or more 'pssh' boxes. - return offset == input.size(); + std::vector<mp4::FullProtectionSystemSpecificHeader> children; + return ReadAllPsshBoxes(input, &children); } bool GetKeyIdsForCommonSystemId(const std::vector<uint8_t>& input, KeyIdList* key_ids) { - size_t offset = 0; KeyIdList result; + std::vector<uint8_t> common_system_id( + kCommonSystemId, kCommonSystemId + arraysize(kCommonSystemId)); - while (offset < input.size()) { - BitReader reader(&input[offset], input.size() - offset); - uint32 size; - RCHECK(ValidBoxHeader(&reader, &size)); - - // Update offset to point at the next 'pssh' box (may not be one). - offset += size; - - // Check the version, as KIDs only available if version > 0. - uint8_t version = ReadBits(&reader, 8); - if (version == 0) - continue; - - // flags must be 0. If not, assume incorrect 'pssh' box and move to the - // next one. - if (ReadBits(&reader, 24) != 0) - continue; - - // Validate SystemID - RCHECK(static_cast<uint32_t>(reader.bits_available()) >= - arraysize(kCommonSystemId) * 8); - if (!IsCommonSystemID(&reader)) - continue; // Not Common System, so try the next pssh box. - - // Since version > 0, next field is the KID_count. - RCHECK(static_cast<uint32_t>(reader.bits_available()) >= - sizeof(uint32_t) * 8); - uint32_t count = ReadBits(&reader, 32); - - if (count == 0) - continue; + if (!input.empty()) { + std::vector<mp4::FullProtectionSystemSpecificHeader> children; + if (!ReadAllPsshBoxes(input, &children)) + return false; - // Make sure there is enough data for all the KIDs specified, and then - // extract them. - RCHECK(static_cast<uint32_t>(reader.bits_available()) > count * 16 * 8); - while (count > 0) { - std::vector<uint8_t> key; - key.reserve(16); - for (int i = 0; i < 16; ++i) { - key.push_back(ReadBits(&reader, 8)); - } - result.push_back(key); - --count; + // Check all children for an appropriate 'pssh' box, concatenating any + // key IDs found. + for (const auto& child : children) { + if (child.system_id == common_system_id && child.key_ids.size() > 0) + result.insert(result.end(), child.key_ids.begin(), child.key_ids.end()); } - - // Don't bother checking DataSize and Data. } - key_ids->swap(result); - + // No matching 'pssh' box found. // TODO(jrummell): This should return true only if there was at least one // key ID present. However, numerous test files don't contain the 'pssh' box // for Common Format, so no keys are found. http://crbug.com/460308 + key_ids->swap(result); return true; } +bool GetPsshData(const std::vector<uint8_t>& input, + const std::vector<uint8_t>& system_id, + std::vector<uint8_t>* pssh_data) { + if (input.empty()) + return false; + + std::vector<mp4::FullProtectionSystemSpecificHeader> children; + if (!ReadAllPsshBoxes(input, &children)) + return false; + + // Check all children for an appropriate 'pssh' box, returning |data| from + // the first one found. + for (const auto& child : children) { + if (child.system_id == system_id) { + pssh_data->assign(child.data.begin(), child.data.end()); + return true; + } + } + + // No matching 'pssh' box found. + return false; +} + } // namespace media diff --git a/media/cdm/cenc_utils.h b/media/cdm/cenc_utils.h index ec85fb3..7dd82a1 100644 --- a/media/cdm/cenc_utils.h +++ b/media/cdm/cenc_utils.h @@ -28,6 +28,17 @@ MEDIA_EXPORT bool ValidatePsshInput(const std::vector<uint8_t>& input); MEDIA_EXPORT bool GetKeyIdsForCommonSystemId(const std::vector<uint8_t>& input, KeyIdList* key_ids); +// Gets the data field from the first 'pssh' box containing |system_id| UUID. +// Returns true if such a box is found and successfully parsed. Returns false +// otherwise. +// Notes: +// 1. If multiple PSSH boxes are found, the "Data" of the first matching 'pssh' +// box will be set in |pssh_data|. +// 2. Only PSSH boxes are allowed in |input|. +MEDIA_EXPORT bool GetPsshData(const std::vector<uint8_t>& input, + const std::vector<uint8_t>& system_id, + std::vector<uint8_t>* pssh_data); + } // namespace media #endif // MEDIA_CDM_CENC_UTILS_H_ diff --git a/media/cdm/cenc_utils_unittest.cc b/media/cdm/cenc_utils_unittest.cc index 9923619..3bc55cc 100644 --- a/media/cdm/cenc_utils_unittest.cc +++ b/media/cdm/cenc_utils_unittest.cc @@ -25,6 +25,10 @@ const uint8_t kKey4Data[] = { 0x7E, 0x57, 0x1D, 0x04, 0x7E, 0x57, 0x1D, 0x06, 0x7E, 0x57, 0x1D, 0x04, 0x7E, 0x57, 0x1D, 0x06, }; +const uint8_t kCommonSystemSystemId[] = { + 0x10, 0x77, 0xEF, 0xEC, 0xC0, 0xB2, 0x4D, 0x02, + 0xAC, 0xE3, 0x3C, 0x1E, 0x52, 0xE2, 0xFB, 0x4B +}; class CencUtilsTest : public testing::Test { public: @@ -32,7 +36,10 @@ class CencUtilsTest : public testing::Test { : key1_(kKey1Data, kKey1Data + arraysize(kKey1Data)), key2_(kKey2Data, kKey2Data + arraysize(kKey2Data)), key3_(kKey3Data, kKey3Data + arraysize(kKey3Data)), - key4_(kKey4Data, kKey4Data + arraysize(kKey4Data)) {} + key4_(kKey4Data, kKey4Data + arraysize(kKey4Data)), + common_system_system_id_( + kCommonSystemSystemId, + kCommonSystemSystemId + arraysize(kCommonSystemSystemId)) {} protected: // Initialize the start of the 'pssh' box (up to key_count) @@ -58,23 +65,9 @@ class CencUtilsTest : public testing::Test { box->push_back(0); box->push_back(0); box->push_back(0); - // Add Clear Key SystemID. - box->push_back(0x10); - box->push_back(0x77); - box->push_back(0xEF); - box->push_back(0xEC); - box->push_back(0xC0); - box->push_back(0xB2); - box->push_back(0x4D); - box->push_back(0x02); - box->push_back(0xAC); - box->push_back(0xE3); - box->push_back(0x3C); - box->push_back(0x1E); - box->push_back(0x52); - box->push_back(0xE2); - box->push_back(0xFB); - box->push_back(0x4B); + // Add Common Encryption SystemID. + box->insert(box->end(), common_system_system_id_.begin(), + common_system_system_id_.end()); } std::vector<uint8_t> MakePSSHBox(uint8_t version) { @@ -112,7 +105,7 @@ class CencUtilsTest : public testing::Test { box.push_back(1); // Add key1. - for (int i = 0; i < 16; ++i) + for (size_t i = 0; i < key1.size(); ++i) box.push_back(key1[i]); // Add data_size (= 0). @@ -141,11 +134,11 @@ class CencUtilsTest : public testing::Test { box.push_back(2); // Add key1. - for (int i = 0; i < 16; ++i) + for (size_t i = 0; i < key1.size(); ++i) box.push_back(key1[i]); // Add key2. - for (int i = 0; i < 16; ++i) + for (size_t i = 0; i < key2.size(); ++i) box.push_back(key2[i]); // Add data_size (= 0). @@ -156,16 +149,32 @@ class CencUtilsTest : public testing::Test { return box; } + void AppendData(std::vector<uint8_t>& pssh_box, + const std::vector<uint8_t>& data) { + // This assumes that |pssh_box| has been created using the routines above, + // and simply appends the data to the end of it. It updates the box size + // and sets the data size. + DCHECK(data.size() < 100); + pssh_box[3] += data.size(); + pssh_box.pop_back(); + pssh_box.push_back(data.size()); + pssh_box.insert(pssh_box.end(), data.begin(), data.end()); + } + const std::vector<uint8_t>& Key1() { return key1_; } const std::vector<uint8_t>& Key2() { return key2_; } const std::vector<uint8_t>& Key3() { return key3_; } const std::vector<uint8_t>& Key4() { return key4_; } + const std::vector<uint8_t>& CommonSystemSystemId() { + return common_system_system_id_; + } private: std::vector<uint8_t> key1_; std::vector<uint8_t> key2_; std::vector<uint8_t> key3_; std::vector<uint8_t> key4_; + std::vector<uint8_t> common_system_system_id_; }; TEST_F(CencUtilsTest, EmptyPSSH) { @@ -214,9 +223,8 @@ TEST_F(CencUtilsTest, PSSHVersion0Plus1) { std::vector<uint8_t> box0 = MakePSSHBox(0); std::vector<uint8_t> box1 = MakePSSHBox(1, Key1()); - // Concatentate box1 into box0. - for (const auto& value : box1) - box0.push_back(value); + // Concatentate box1 onto end of box0. + box0.insert(box0.end(), box1.begin(), box1.end()); KeyIdList key_ids; EXPECT_TRUE(ValidatePsshInput(box0)); @@ -229,9 +237,8 @@ TEST_F(CencUtilsTest, PSSHVersion1Plus0) { std::vector<uint8_t> box0 = MakePSSHBox(0); std::vector<uint8_t> box1 = MakePSSHBox(1, Key1()); - // Concatentate box0 into box1. - for (const auto& value : box0) - box1.push_back(value); + // Concatentate box0 onto end of box1. + box1.insert(box1.end(), box0.begin(), box0.end()); KeyIdList key_ids; EXPECT_TRUE(ValidatePsshInput(box1)); @@ -245,12 +252,9 @@ TEST_F(CencUtilsTest, MultiplePSSHVersion1) { std::vector<uint8_t> box1 = MakePSSHBox(1, Key3()); std::vector<uint8_t> box2 = MakePSSHBox(1, Key4()); - // Concatentate box1 into box. - for (const auto& value : box1) - box.push_back(value); - // Concatentate box2 into box. - for (const auto& value : box2) - box.push_back(value); + // Concatentate box1 and box2 onto end of box. + box.insert(box.end(), box1.begin(), box1.end()); + box.insert(box.end(), box2.begin(), box2.end()); KeyIdList key_ids; EXPECT_TRUE(ValidatePsshInput(box)); @@ -265,14 +269,15 @@ TEST_F(CencUtilsTest, MultiplePSSHVersion1) { TEST_F(CencUtilsTest, InvalidPSSH) { std::vector<uint8_t> box = MakePSSHBox(1, Key1(), Key2()); KeyIdList key_ids; - for (uint32 i = 1; i < box.size(); ++i) { + for (size_t i = 1; i < box.size(); ++i) { // Modify size of data passed to be less than real size. std::vector<uint8_t> truncated(&box[0], &box[0] + i); - EXPECT_FALSE(ValidatePsshInput(truncated)); + EXPECT_FALSE(ValidatePsshInput(truncated)) << "Failed for length " << i; EXPECT_FALSE(GetKeyIdsForCommonSystemId(truncated, &key_ids)); // Modify starting point. std::vector<uint8_t> changed_offset(&box[i], &box[i] + box.size() - i); - EXPECT_FALSE(ValidatePsshInput(changed_offset)); + EXPECT_FALSE(ValidatePsshInput(changed_offset)) << "Failed for offset " + << i; EXPECT_FALSE(GetKeyIdsForCommonSystemId(changed_offset, &key_ids)); } } @@ -295,9 +300,7 @@ TEST_F(CencUtilsTest, InvalidFlags) { box[10] = 3; KeyIdList key_ids; - // TODO(jrummell): This should fail as the 'pssh' box is skipped. - EXPECT_TRUE(GetKeyIdsForCommonSystemId(box, &key_ids)); - EXPECT_EQ(0u, key_ids.size()); + EXPECT_FALSE(GetKeyIdsForCommonSystemId(box, &key_ids)); } TEST_F(CencUtilsTest, LongSize) { @@ -373,4 +376,140 @@ TEST_F(CencUtilsTest, HugeSize) { std::vector<uint8_t>(data, data + arraysize(data)), &key_ids)); } +TEST_F(CencUtilsTest, GetPsshData_Version0) { + const uint8_t data_bytes[] = {0x01, 0x02, 0x03, 0x04}; + std::vector<uint8_t> pssh_data; + + std::vector<uint8_t> box = MakePSSHBox(0); + EXPECT_TRUE(GetPsshData(box, CommonSystemSystemId(), &pssh_data)); + EXPECT_EQ(0u, pssh_data.size()); + + std::vector<uint8_t> data(data_bytes, data_bytes + arraysize(data_bytes)); + AppendData(box, data); + EXPECT_TRUE(GetPsshData(box, CommonSystemSystemId(), &pssh_data)); + EXPECT_EQ(data, pssh_data); +} + +TEST_F(CencUtilsTest, GetPsshData_Version1NoKeys) { + const uint8_t data_bytes[] = {0x05, 0x06, 0x07, 0x08}; + std::vector<uint8_t> pssh_data; + + std::vector<uint8_t> box = MakePSSHBox(1); + EXPECT_TRUE(GetPsshData(box, CommonSystemSystemId(), &pssh_data)); + EXPECT_EQ(0u, pssh_data.size()); + + std::vector<uint8_t> data(data_bytes, data_bytes + arraysize(data_bytes)); + AppendData(box, data); + EXPECT_TRUE(GetPsshData(box, CommonSystemSystemId(), &pssh_data)); + EXPECT_EQ(data, pssh_data); +} + +TEST_F(CencUtilsTest, GetPsshData_Version1WithKeys) { + const uint8_t data_bytes[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; + std::vector<uint8_t> pssh_data; + + std::vector<uint8_t> box = MakePSSHBox(1, Key1()); + EXPECT_TRUE(GetPsshData(box, CommonSystemSystemId(), &pssh_data)); + EXPECT_EQ(0u, pssh_data.size()); + + std::vector<uint8_t> data(data_bytes, data_bytes + arraysize(data_bytes)); + AppendData(box, data); + EXPECT_TRUE(GetPsshData(box, CommonSystemSystemId(), &pssh_data)); + EXPECT_EQ(data, pssh_data); +} + +TEST_F(CencUtilsTest, GetPsshData_Version2) { + std::vector<uint8_t> pssh_data; + + std::vector<uint8_t> box = MakePSSHBox(1, Key1()); + EXPECT_TRUE(GetPsshData(box, CommonSystemSystemId(), &pssh_data)); + + // Change the version manually, since we don't know what v2 will contain. + box[8] = 2; + EXPECT_FALSE(GetPsshData(box, CommonSystemSystemId(), &pssh_data)); +} + +TEST_F(CencUtilsTest, GetPsshData_Version2ThenVersion1) { + std::vector<uint8_t> pssh_data; + + std::vector<uint8_t> box_v1 = MakePSSHBox(1, Key1()); + std::vector<uint8_t> box_v2 = MakePSSHBox(2, Key2(), Key3()); + + // Concatentate the boxes together (v2 first). + std::vector<uint8_t> boxes; + boxes.insert(boxes.end(), box_v2.begin(), box_v2.end()); + boxes.insert(boxes.end(), box_v1.begin(), box_v1.end()); + EXPECT_TRUE(GetPsshData(boxes, CommonSystemSystemId(), &pssh_data)); + + // GetKeyIdsForCommonSystemId() should return the single key from the v1 + // 'pssh' box. + KeyIdList key_ids; + EXPECT_TRUE(GetKeyIdsForCommonSystemId(boxes, &key_ids)); + EXPECT_EQ(1u, key_ids.size()); + EXPECT_EQ(key_ids[0], Key1()); +} + +TEST_F(CencUtilsTest, GetPsshData_Version1ThenVersion2) { + std::vector<uint8_t> pssh_data; + + std::vector<uint8_t> box_v1 = MakePSSHBox(1, Key3()); + std::vector<uint8_t> box_v2 = MakePSSHBox(2, Key4()); + + // Concatentate the boxes together (v1 first). + std::vector<uint8_t> boxes; + boxes.insert(boxes.end(), box_v1.begin(), box_v1.end()); + boxes.insert(boxes.end(), box_v2.begin(), box_v2.end()); + EXPECT_TRUE(GetPsshData(boxes, CommonSystemSystemId(), &pssh_data)); + + // GetKeyIdsForCommonSystemId() should return the single key from the v1 + // 'pssh' box. + KeyIdList key_ids; + EXPECT_TRUE(GetKeyIdsForCommonSystemId(boxes, &key_ids)); + EXPECT_EQ(1u, key_ids.size()); + EXPECT_EQ(key_ids[0], Key3()); +} + +TEST_F(CencUtilsTest, GetPsshData_DifferentSystemID) { + std::vector<uint8_t> unknown_system_id(kKey1Data, + kKey1Data + arraysize(kKey1Data)); + std::vector<uint8_t> pssh_data; + + std::vector<uint8_t> box = MakePSSHBox(1, Key1()); + EXPECT_TRUE(GetPsshData(box, CommonSystemSystemId(), &pssh_data)); + EXPECT_FALSE(GetPsshData(box, unknown_system_id, &pssh_data)); +} + +TEST_F(CencUtilsTest, GetPsshData_MissingData) { + const uint8_t data_bytes[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; + std::vector<uint8_t> pssh_data; + + std::vector<uint8_t> box = MakePSSHBox(1, Key1()); + std::vector<uint8_t> data(data_bytes, data_bytes + arraysize(data_bytes)); + AppendData(box, data); + EXPECT_TRUE(GetPsshData(box, CommonSystemSystemId(), &pssh_data)); + + // Remove some data from the end, so now the size is incorrect. + box.pop_back(); + box.pop_back(); + EXPECT_FALSE(GetPsshData(box, CommonSystemSystemId(), &pssh_data)); +} + +TEST_F(CencUtilsTest, GetPsshData_MultiplePssh) { + const uint8_t data1_bytes[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}; + const uint8_t data2_bytes[] = {0xa1, 0xa2, 0xa3, 0xa4}; + std::vector<uint8_t> pssh_data; + + std::vector<uint8_t> box1 = MakePSSHBox(1, Key1()); + std::vector<uint8_t> data1(data1_bytes, data1_bytes + arraysize(data1_bytes)); + AppendData(box1, data1); + + std::vector<uint8_t> box2 = MakePSSHBox(0); + std::vector<uint8_t> data2(data2_bytes, data2_bytes + arraysize(data2_bytes)); + AppendData(box2, data2); + + box1.insert(box1.end(), box2.begin(), box2.end()); + EXPECT_TRUE(GetPsshData(box1, CommonSystemSystemId(), &pssh_data)); + EXPECT_EQ(data1, pssh_data); + EXPECT_NE(data2, pssh_data); +} } // namespace media diff --git a/media/formats/mp4/box_definitions.cc b/media/formats/mp4/box_definitions.cc index 5a15240..72809cf 100644 --- a/media/formats/mp4/box_definitions.cc +++ b/media/formats/mp4/box_definitions.cc @@ -26,16 +26,65 @@ ProtectionSystemSpecificHeader::~ProtectionSystemSpecificHeader() {} FourCC ProtectionSystemSpecificHeader::BoxType() const { return FOURCC_PSSH; } bool ProtectionSystemSpecificHeader::Parse(BoxReader* reader) { - // Validate the box's contents and hang on to the system ID. - RCHECK(reader->ReadFullBoxHeader() && - reader->ReadVec(&system_id, 16)); - + // Don't bother validating the box's contents. // Copy the entire box, including the header, for passing to EME as initData. DCHECK(raw_box.empty()); raw_box.assign(reader->data(), reader->data() + reader->size()); return true; } +FullProtectionSystemSpecificHeader::FullProtectionSystemSpecificHeader() {} +FullProtectionSystemSpecificHeader::~FullProtectionSystemSpecificHeader() {} +FourCC FullProtectionSystemSpecificHeader::BoxType() const { + return FOURCC_PSSH; +} + +// The format of a 'pssh' box is as follows: +// unsigned int(32) size; +// unsigned int(32) type = "pssh"; +// if (size==1) { +// unsigned int(64) largesize; +// } else if (size==0) { +// -- box extends to end of file +// } +// unsigned int(8) version; +// bit(24) flags; +// unsigned int(8)[16] SystemID; +// if (version > 0) +// { +// unsigned int(32) KID_count; +// { +// unsigned int(8)[16] KID; +// } [KID_count] +// } +// unsigned int(32) DataSize; +// unsigned int(8)[DataSize] Data; + +bool FullProtectionSystemSpecificHeader::Parse(mp4::BoxReader* reader) { + RCHECK(reader->type() == BoxType() && reader->ReadFullBoxHeader()); + + // Only versions 0 and 1 of the 'pssh' boxes are supported. Any other + // versions are ignored. + RCHECK(reader->version() == 0 || reader->version() == 1); + RCHECK(reader->flags() == 0); + RCHECK(reader->ReadVec(&system_id, 16)); + + if (reader->version() > 0) { + uint32_t kid_count; + RCHECK(reader->Read4(&kid_count)); + for (uint32_t i = 0; i < kid_count; ++i) { + std::vector<uint8_t> kid; + RCHECK(reader->ReadVec(&kid, 16)); + key_ids.push_back(kid); + } + } + + uint32_t data_size; + RCHECK(reader->Read4(&data_size)); + RCHECK(reader->ReadVec(&data, data_size)); + return true; +} + SampleAuxiliaryInformationOffset::SampleAuxiliaryInformationOffset() {} SampleAuxiliaryInformationOffset::~SampleAuxiliaryInformationOffset() {} FourCC SampleAuxiliaryInformationOffset::BoxType() const { return FOURCC_SAIO; } diff --git a/media/formats/mp4/box_definitions.h b/media/formats/mp4/box_definitions.h index 026effe..2dfab63 100644 --- a/media/formats/mp4/box_definitions.h +++ b/media/formats/mp4/box_definitions.h @@ -44,13 +44,23 @@ struct MEDIA_EXPORT FileType : Box { uint32 minor_version; }; +// If only copying the 'pssh' boxes, use ProtectionSystemSpecificHeader. +// If access to the individual fields is needed, use +// FullProtectionSystemSpecificHeader. struct MEDIA_EXPORT ProtectionSystemSpecificHeader : Box { DECLARE_BOX_METHODS(ProtectionSystemSpecificHeader); - std::vector<uint8> system_id; std::vector<uint8> raw_box; }; +struct MEDIA_EXPORT FullProtectionSystemSpecificHeader : Box { + DECLARE_BOX_METHODS(FullProtectionSystemSpecificHeader); + + std::vector<uint8> system_id; + std::vector<std::vector<uint8>> key_ids; + std::vector<uint8> data; +}; + struct MEDIA_EXPORT SampleAuxiliaryInformationOffset : Box { DECLARE_BOX_METHODS(SampleAuxiliaryInformationOffset); diff --git a/media/formats/mp4/box_reader.cc b/media/formats/mp4/box_reader.cc index 5105726..4368d8d 100644 --- a/media/formats/mp4/box_reader.cc +++ b/media/formats/mp4/box_reader.cc @@ -75,15 +75,17 @@ bool BufferReader::Read4sInto8s(int64* v) { return true; } - -BoxReader::BoxReader(const uint8* buf, const int size, - const LogCB& log_cb) +BoxReader::BoxReader(const uint8* buf, + const int size, + const LogCB& log_cb, + bool is_EOS) : BufferReader(buf, size), log_cb_(log_cb), type_(FOURCC_NULL), version_(0), flags_(0), - scanned_(false) { + scanned_(false), + is_EOS_(is_EOS) { } BoxReader::~BoxReader() { @@ -100,7 +102,8 @@ BoxReader* BoxReader::ReadTopLevelBox(const uint8* buf, const int buf_size, const LogCB& log_cb, bool* err) { - scoped_ptr<BoxReader> reader(new BoxReader(buf, buf_size, log_cb)); + scoped_ptr<BoxReader> reader( + new BoxReader(buf, buf_size, log_cb, false)); if (!reader->ReadHeader(err)) return NULL; @@ -122,7 +125,7 @@ bool BoxReader::StartTopLevelBox(const uint8* buf, FourCC* type, int* box_size, bool* err) { - BoxReader reader(buf, buf_size, log_cb); + BoxReader reader(buf, buf_size, log_cb, false); if (!reader.ReadHeader(err)) return false; if (!IsValidTopLevelBox(reader.type(), log_cb)) { *err = true; @@ -134,6 +137,12 @@ bool BoxReader::StartTopLevelBox(const uint8* buf, } // static +BoxReader* BoxReader::ReadConcatentatedBoxes(const uint8* buf, + const int buf_size) { + return new BoxReader(buf, buf_size, LogCB(), true); +} + +// static bool BoxReader::IsValidTopLevelBox(const FourCC& type, const LogCB& log_cb) { switch (type) { @@ -169,7 +178,7 @@ bool BoxReader::ScanChildren() { bool err = false; while (pos() < size()) { - BoxReader child(&buf_[pos_], size_ - pos_, log_cb_); + BoxReader child(&buf_[pos_], size_ - pos_, log_cb_, is_EOS_); if (!child.ReadHeader(&err)) break; children_.insert(std::pair<FourCC, BoxReader>(child.type(), child)); @@ -215,16 +224,30 @@ bool BoxReader::ReadHeader(bool* err) { uint64 size = 0; *err = false; - if (!HasBytes(8)) return false; + if (!HasBytes(8)) { + // If EOS is known, then this is an error. If not, additional data may be + // appended later, so this is a soft error. + *err = is_EOS_; + return false; + } CHECK(Read4Into8(&size) && ReadFourCC(&type_)); if (size == 0) { - MEDIA_LOG(DEBUG, log_cb_) << "Media Source Extensions do not support ISO " - "BMFF boxes that run to EOS"; - *err = true; - return false; + if (is_EOS_) { + // All the data bytes are expected to be provided. + size = size_; + } else { + MEDIA_LOG(DEBUG, log_cb_) + << "ISO BMFF boxes that run to EOS are not supported"; + *err = true; + return false; + } } else if (size == 1) { - if (!HasBytes(8)) return false; + if (!HasBytes(8)) { + // If EOS is known, then this is an error. If not, it's a soft error. + *err = is_EOS_; + return false; + } CHECK(Read8(&size)); } @@ -236,6 +259,13 @@ bool BoxReader::ReadHeader(bool* err) { return false; } + // Make sure the buffer contains at least the expected number of bytes. + // Since the data may be appended in pieces, this can only be checked if EOS. + if (is_EOS_ && size > static_cast<uint64>(size_)) { + *err = true; + return false; + } + // Note that the pos_ head has advanced to the byte immediately after the // header, which is where we want it. size_ = size; diff --git a/media/formats/mp4/box_reader.h b/media/formats/mp4/box_reader.h index 6b59361..92e85fc 100644 --- a/media/formats/mp4/box_reader.h +++ b/media/formats/mp4/box_reader.h @@ -101,6 +101,14 @@ class MEDIA_EXPORT BoxReader : public BufferReader { int* box_size, bool* err) WARN_UNUSED_RESULT; + // Create a BoxReader from a buffer. |buf| must be the complete buffer, as + // errors are returned when sufficient data is not available. |buf| can start + // with any type of box -- it does not have to be IsValidTopLevelBox(). + // + // |buf| is retained but not owned, and must outlive the BoxReader instance. + static BoxReader* ReadConcatentatedBoxes(const uint8* buf, + const int buf_size); + // Returns true if |type| is recognized to be a top-level box, false // otherwise. This returns true for some boxes which we do not parse. // Helpful in debugging misaligned appends. @@ -148,7 +156,9 @@ class MEDIA_EXPORT BoxReader : public BufferReader { const LogCB& log_cb() const { return log_cb_; } private: - BoxReader(const uint8* buf, const int size, const LogCB& log_cb); + // Create a BoxReader from |buf|. |is_EOS| should be true if |buf| is + // complete stream (i.e. no additional data is expected to be appended). + BoxReader(const uint8* buf, const int size, const LogCB& log_cb, bool is_EOS); // Must be called immediately after init. If the return is false, this // indicates that the box header and its contents were not available in the @@ -170,6 +180,9 @@ class MEDIA_EXPORT BoxReader : public BufferReader { // valid if scanned_ is true. ChildMap children_; bool scanned_; + + // True if the buffer provided to the reader is the complete stream. + const bool is_EOS_; }; // Template definitions @@ -207,8 +220,8 @@ bool BoxReader::ReadAllChildren(std::vector<T>* children) { scanned_ = true; bool err = false; - while (pos() < size()) { - BoxReader child_reader(&buf_[pos_], size_ - pos_, log_cb_); + while (pos_ < size_) { + BoxReader child_reader(&buf_[pos_], size_ - pos_, log_cb_, is_EOS_); if (!child_reader.ReadHeader(&err)) break; T child; RCHECK(child.Parse(&child_reader)); |