summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--components/cdm/browser/DEPS2
-rw-r--r--components/cdm/browser/widevine_drm_delegate_android.cc140
-rw-r--r--media/cdm/cenc_utils.cc215
-rw-r--r--media/cdm/cenc_utils.h11
-rw-r--r--media/cdm/cenc_utils_unittest.cc215
-rw-r--r--media/formats/mp4/box_definitions.cc57
-rw-r--r--media/formats/mp4/box_definitions.h12
-rw-r--r--media/formats/mp4/box_reader.cc56
-rw-r--r--media/formats/mp4/box_reader.h19
9 files changed, 394 insertions, 333 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..2e7ead2 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 (uint 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 (uint i = 0; i < key1.size(); ++i)
box.push_back(key1[i]);
// Add key2.
- for (int i = 0; i < 16; ++i)
+ for (uint 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));
@@ -268,11 +272,12 @@ TEST_F(CencUtilsTest, InvalidPSSH) {
for (uint32 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));