Source release 14.0.0
This commit is contained in:
@@ -4,13 +4,16 @@
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <string.h>
|
||||
#include <sstream>
|
||||
|
||||
#include "buffer_reader.h"
|
||||
#include "cdm_engine.h"
|
||||
#include "jsmn.h"
|
||||
#include "log.h"
|
||||
#include "properties.h"
|
||||
#include "string_conversions.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace {
|
||||
const char kKeyFormatVersionsSeparator = '/';
|
||||
@@ -25,6 +28,12 @@ const std::string kKeyIds = "key_ids";
|
||||
|
||||
// Being conservative, usually we expect 6 + number of Key Ids
|
||||
const int kDefaultNumJsonTokens = 128;
|
||||
|
||||
// Widevine's registered system ID.
|
||||
const uint8_t kWidevineSystemId[] = {
|
||||
0xED, 0xEF, 0x8B, 0xA9, 0x79, 0xD6, 0x4A, 0xCE,
|
||||
0xA3, 0xC8, 0x27, 0xDC, 0xD5, 0x1D, 0x21, 0xED,
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace wvcdm {
|
||||
@@ -33,9 +42,13 @@ namespace wvcdm {
|
||||
using video_widevine::WidevinePsshData;
|
||||
using video_widevine::WidevinePsshData_Algorithm;
|
||||
using video_widevine::WidevinePsshData_Algorithm_AESCTR;
|
||||
using video_widevine::WidevinePsshData_Type;
|
||||
using video_widevine::WidevinePsshData_Type_ENTITLED_KEY;
|
||||
using video_widevine::WidevinePsshData_Type_SINGLE;
|
||||
|
||||
InitializationData::InitializationData(const std::string& type,
|
||||
const CdmInitData& data)
|
||||
const CdmInitData& data,
|
||||
const std::string& oec_version)
|
||||
: type_(type),
|
||||
is_cenc_(false),
|
||||
is_hls_(false),
|
||||
@@ -53,7 +66,12 @@ InitializationData::InitializationData(const std::string& type,
|
||||
|
||||
if (is_supported()) {
|
||||
if (is_cenc()) {
|
||||
ExtractWidevinePssh(data, &data_);
|
||||
bool oec_prefers_entitlements = DetectEntitlementPreference(oec_version);
|
||||
if (!SelectWidevinePssh(data, oec_prefers_entitlements, &data_)) {
|
||||
LOGE(
|
||||
"InitializationData: Unable to select a supported Widevine PSSH "
|
||||
"from the init data.");
|
||||
}
|
||||
} else if (is_webm()) {
|
||||
data_ = data;
|
||||
} else if (is_hls()) {
|
||||
@@ -67,172 +85,284 @@ InitializationData::InitializationData(const std::string& type,
|
||||
|
||||
// Parse the pssh data and return the embedded key data if it exists.
|
||||
std::vector<video_widevine::SubLicense>
|
||||
InitializationData::ExtractEmbeddedKeys() const {
|
||||
InitializationData::ExtractSublicenseKeys() const {
|
||||
std::vector<video_widevine::SubLicense> keys;
|
||||
// TODO(jfore): The pssh has changed in ways that are not compatible with
|
||||
//sublicenses. Restructure or remove sublicense support including this method.
|
||||
return keys;
|
||||
}
|
||||
|
||||
std::vector<video_widevine::WidevinePsshData_EntitledKey>
|
||||
InitializationData::ExtractWrappedKeys() const {
|
||||
std::vector<video_widevine::WidevinePsshData_EntitledKey> keys;
|
||||
WidevinePsshData cenc_header;
|
||||
if (!is_cenc_ || !cenc_header.ParseFromString(data_) ||
|
||||
cenc_header.sub_licenses().size() == 0)
|
||||
cenc_header.entitled_keys().size() == 0)
|
||||
return keys;
|
||||
|
||||
keys.reserve(cenc_header.sub_licenses().size());
|
||||
for (int i = 0; i < cenc_header.sub_licenses().size(); ++i) {
|
||||
keys.push_back(cenc_header.sub_licenses(i));
|
||||
keys.reserve(cenc_header.entitled_keys().size());
|
||||
for (int i = 0; i < cenc_header.entitled_keys().size(); ++i) {
|
||||
keys.push_back(cenc_header.entitled_keys(i));
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
// Parse a blob of multiple concatenated PSSH atoms to extract the first
|
||||
// Widevine PSSH.
|
||||
bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data,
|
||||
CdmInitData* output) {
|
||||
BufferReader reader(reinterpret_cast<const uint8_t*>(init_data.data()),
|
||||
init_data.length());
|
||||
// Parse a blob of multiple concatenated PSSH atoms to extract the Widevine
|
||||
// PSSH. The blob may have multiple Widevine PSSHs. Devices that prefer
|
||||
// entitlements should choose the |ENTITLED_KEY| PSSH while other devices should
|
||||
// stick to the |SINGLE| PSSH.
|
||||
bool InitializationData::SelectWidevinePssh(const CdmInitData& init_data,
|
||||
bool prefer_entitlements,
|
||||
CdmInitData* output) {
|
||||
// Extract the data payloads from the Widevine PSSHs.
|
||||
std::vector<CdmInitData> pssh_payloads;
|
||||
if (!ExtractWidevinePsshs(init_data, &pssh_payloads)) {
|
||||
LOGE(
|
||||
"InitializationData::SelectWidevinePssh: Unable to parse concatenated "
|
||||
"PSSH boxes.");
|
||||
return false;
|
||||
}
|
||||
if (pssh_payloads.empty()) {
|
||||
LOGE(
|
||||
"InitializationData::SelectWidevinePssh: The concatenated PSSH boxes "
|
||||
"could be parsed, but no Widevine PSSH was found.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Widevine's registered system ID.
|
||||
static const uint8_t kWidevineSystemId[] = {
|
||||
0xED, 0xEF, 0x8B, 0xA9, 0x79, 0xD6, 0x4A, 0xCE,
|
||||
0xA3, 0xC8, 0x27, 0xDC, 0xD5, 0x1D, 0x21, 0xED,
|
||||
};
|
||||
// If there are multiple PSSHs to choose from and this device prefers
|
||||
// entitlements, find the first |ENTITLED_KEY| PSSH, if present, and
|
||||
// select it.
|
||||
if (prefer_entitlements && pssh_payloads.size() > 1) {
|
||||
for (size_t i = 0; i < pssh_payloads.size(); ++i) {
|
||||
WidevinePsshData pssh;
|
||||
if (!pssh.ParseFromString(pssh_payloads[i])) {
|
||||
LOGE(
|
||||
"InitializationData::SelectWidevinePssh: Unable to parse PSSH data "
|
||||
"%lu into a protobuf.", i);
|
||||
continue;
|
||||
}
|
||||
if (pssh.type() == WidevinePsshData_Type_ENTITLED_KEY) {
|
||||
*output = pssh_payloads[i];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Either there is only one PSSH, this device does not prefer entitlements,
|
||||
// or no entitlement PSSH was found. Regardless of how we got here, select the
|
||||
// first PSSH, which should be a |SINGLE| PSSH.
|
||||
*output = pssh_payloads[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
// one PSSH box consists of:
|
||||
// 4 byte size of the atom, inclusive. (0 means the rest of the buffer.)
|
||||
// 4 byte atom type, "pssh".
|
||||
// (optional, if size == 1) 8 byte size of the atom, inclusive.
|
||||
// 1 byte version, value 0 or 1. (skip if larger.)
|
||||
// 3 byte flags, value 0. (ignored.)
|
||||
// 16 byte system id.
|
||||
// (optional, if version == 1) 4 byte key ID count. (K)
|
||||
// (optional, if version == 1) K * 16 byte key ID.
|
||||
// 4 byte size of PSSH data, exclusive. (N)
|
||||
// N byte PSSH data.
|
||||
|
||||
bool InitializationData::ExtractWidevinePsshs(
|
||||
const CdmInitData& init_data, std::vector<CdmInitData>* psshs) {
|
||||
if (psshs == NULL) {
|
||||
LOGE("InitializationData::ExtractWidevinePsshs: NULL psshs parameter");
|
||||
return false;
|
||||
}
|
||||
psshs->clear();
|
||||
psshs->reserve(2); // We expect 1 or 2 Widevine PSSHs
|
||||
|
||||
const uint8_t* data_start = reinterpret_cast<const uint8_t*>(init_data.data());
|
||||
BufferReader reader(data_start, init_data.length());
|
||||
|
||||
// one PSSH box consists of:
|
||||
// 4 byte size of the atom, inclusive. (0 means the rest of the buffer.)
|
||||
// 4 byte atom type, "pssh".
|
||||
// (optional, if size == 1) 8 byte size of the atom, inclusive.
|
||||
// 1 byte version, value 0 or 1. (skip if larger.)
|
||||
// 3 byte flags, value 0. (ignored.)
|
||||
// 16 byte system id.
|
||||
// (optional, if version == 1) 4 byte key ID count. (K)
|
||||
// (optional, if version == 1) K * 16 byte key ID.
|
||||
// 4 byte size of PSSH data, exclusive. (N)
|
||||
// N byte PSSH data.
|
||||
while (!reader.IsEOF()) {
|
||||
size_t start_pos = reader.pos();
|
||||
const size_t start_pos = reader.pos();
|
||||
|
||||
// atom size, used for skipping.
|
||||
// Atom size. Used for bounding the inner reader and knowing how far to skip
|
||||
// forward after parsing this PSSH.
|
||||
uint64_t size;
|
||||
if (!reader.Read4Into8(&size)) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePssh: Unable to read atom size.");
|
||||
return false;
|
||||
"InitializationData::ExtractWidevinePsshs: Unable to read the "
|
||||
"32-bit atom size.");
|
||||
return false; // We cannot continue reading safely. Abort.
|
||||
}
|
||||
std::vector<uint8_t> atom_type;
|
||||
if (!reader.ReadVec(&atom_type, 4)) {
|
||||
|
||||
// Skip the atom type for now.
|
||||
if (!reader.SkipBytes(4)) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePssh: Unable to read atom type.");
|
||||
return false;
|
||||
"InitializationData::ExtractWidevinePsshs: Unable to skip the "
|
||||
"atom type.");
|
||||
return false; // We cannot continue reading safely. Abort.
|
||||
}
|
||||
|
||||
if (size == 1) {
|
||||
// An "atom size" of 1 means the real atom size is a 64-bit number stored
|
||||
// after the atom type.
|
||||
if (!reader.Read8(&size)) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePssh: Unable to read 64-bit "
|
||||
"atom size.");
|
||||
return false;
|
||||
"InitializationData::ExtractWidevinePsshs: Unable to read the "
|
||||
"64-bit atom size.");
|
||||
return false; // We cannot continue reading safely. Abort.
|
||||
}
|
||||
} else if (size == 0) {
|
||||
// An "atom size" of 0 means the atom takes up the rest of the buffer.
|
||||
size = reader.size() - start_pos;
|
||||
}
|
||||
|
||||
// "pssh"
|
||||
if (memcmp(&atom_type[0], "pssh", 4)) {
|
||||
const size_t bytes_read = reader.pos() - start_pos;
|
||||
const size_t bytes_remaining = size - bytes_read;
|
||||
|
||||
if (!reader.HasBytes(bytes_remaining)) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePssh: PSSH literal not present.");
|
||||
if (!reader.SkipBytes(size - (reader.pos() - start_pos))) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePssh: Unable to skip the rest "
|
||||
"of the atom.");
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
"InitializationData::ExtractWidevinePsshs: Invalid atom size. The "
|
||||
"atom claims to be larger than the remaining init data.");
|
||||
return false; // We cannot continue reading safely. Abort.
|
||||
}
|
||||
|
||||
// version
|
||||
uint8_t version;
|
||||
if (!reader.Read1(&version)) {
|
||||
// Use ExtractWidevinePsshData() to check if this atom is a Widevine PSSH
|
||||
// and, if so, add its data to the list of PSSHs.
|
||||
CdmInitData data;
|
||||
if (ExtractWidevinePsshData(data_start + start_pos, size, &data)) {
|
||||
psshs->push_back(data);
|
||||
}
|
||||
|
||||
// Skip past the rest of the atom.
|
||||
if (!reader.SkipBytes(bytes_remaining)) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePssh: Unable to read PSSH "
|
||||
"version.");
|
||||
return false;
|
||||
"InitializationData::LocateWidevinePsshOffsets: Unable to skip the "
|
||||
"rest of the atom.");
|
||||
return false; // We cannot continue reading safely. Abort.
|
||||
}
|
||||
|
||||
if (version > 1) {
|
||||
// unrecognized version - skip.
|
||||
if (!reader.SkipBytes(size - (reader.pos() - start_pos))) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePssh: Unable to skip the rest "
|
||||
"of the atom.");
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// flags
|
||||
if (!reader.SkipBytes(3)) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePssh: Unable to skip the PSSH "
|
||||
"flags.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// system id
|
||||
std::vector<uint8_t> system_id;
|
||||
if (!reader.ReadVec(&system_id, sizeof(kWidevineSystemId))) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePssh: Unable to read system ID.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (memcmp(&system_id[0], kWidevineSystemId, sizeof(kWidevineSystemId))) {
|
||||
// skip non-Widevine PSSH boxes.
|
||||
if (!reader.SkipBytes(size - (reader.pos() - start_pos))) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePssh: Unable to skip the rest "
|
||||
"of the atom.");
|
||||
return false;
|
||||
}
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePssh: Skipping non-Widevine "
|
||||
"PSSH.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (version == 1) {
|
||||
// v1 has additional fields for key IDs. We can skip them.
|
||||
uint32_t num_key_ids;
|
||||
if (!reader.Read4(&num_key_ids)) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePssh: Unable to read num key "
|
||||
"IDs.");
|
||||
return false;
|
||||
}
|
||||
if (!reader.SkipBytes(num_key_ids * 16)) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePssh: Unable to skip key IDs.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// size of PSSH data
|
||||
uint32_t data_length;
|
||||
if (!reader.Read4(&data_length)) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePssh: Unable to read PSSH data "
|
||||
"size.");
|
||||
return false;
|
||||
}
|
||||
|
||||
output->clear();
|
||||
if (!reader.ReadString(output, data_length)) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePssh: Unable to read PSSH data.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// we did not find a matching record
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Checks if the given reader contains a Widevine PSSH. If so, it extracts the
|
||||
// data from the box. Returns true if a PSSH was extracted, false if there was
|
||||
// an error or if the data is not a Widevine PSSH.
|
||||
bool InitializationData::ExtractWidevinePsshData(
|
||||
const uint8_t* data, size_t length, CdmInitData* output) {
|
||||
BufferReader reader(data, length);
|
||||
|
||||
// Read the 32-bit size only so we can check if we need to expect a 64-bit
|
||||
// size.
|
||||
uint64_t size_32;
|
||||
if (!reader.Read4Into8(&size_32)) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePsshData: Unable to read the "
|
||||
"32-bit atom size.");
|
||||
return false;
|
||||
}
|
||||
const bool has_size_64 = (size_32 == 1);
|
||||
|
||||
// Read the atom type and check that it is "pssh".
|
||||
std::vector<uint8_t> atom_type;
|
||||
if (!reader.ReadVec(&atom_type, 4)) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePsshData: Unable to read the atom "
|
||||
"type.");
|
||||
return false;
|
||||
}
|
||||
if (memcmp(&atom_type[0], "pssh", 4) != 0) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePsshData: Atom type is not PSSH.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// If there is a 64-bit size, skip it.
|
||||
if (has_size_64) {
|
||||
if (!reader.SkipBytes(8)) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePsshData: Unable to skip the "
|
||||
"64-bit atom size.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Read the version number and abort if it is not one we can handle.
|
||||
uint8_t version;
|
||||
if (!reader.Read1(&version)) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePsshData: Unable to read the PSSH "
|
||||
"version.");
|
||||
return false;
|
||||
}
|
||||
if (version > 1) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePsshData: Unrecognized PSSH "
|
||||
"version.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip the flags.
|
||||
if (!reader.SkipBytes(3)) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePsshData: Unable to skip the PSSH "
|
||||
"flags.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read the System ID and validate that it is the Widevine System ID.
|
||||
std::vector<uint8_t> system_id;
|
||||
if (!reader.ReadVec(&system_id, sizeof(kWidevineSystemId))) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePsshData: Unable to read the "
|
||||
"system ID.");
|
||||
return false;
|
||||
}
|
||||
if (memcmp(&system_id[0],
|
||||
kWidevineSystemId,
|
||||
sizeof(kWidevineSystemId)) != 0) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePsshData: Found a non-Widevine "
|
||||
"PSSH.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip the key ID fields, if present.
|
||||
if (version == 1) {
|
||||
// Read the number of key IDs so we know how far to skip ahead.
|
||||
uint32_t num_key_ids;
|
||||
if (!reader.Read4(&num_key_ids)) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePsshData: Unable to read the "
|
||||
"number of key IDs.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip the key IDs.
|
||||
if (!reader.SkipBytes(num_key_ids * 16)) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePsshData: Unable to skip the key "
|
||||
"IDs.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Read the size of the PSSH data.
|
||||
uint32_t data_length;
|
||||
if (!reader.Read4(&data_length)) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePsshData: Unable to read the PSSH "
|
||||
"data size.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read the PSSH data.
|
||||
output->clear();
|
||||
if (!reader.ReadString(output, data_length)) {
|
||||
LOGV(
|
||||
"InitializationData::ExtractWidevinePsshData: Unable to read the PSSH "
|
||||
"data.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parse an EXT-X-KEY tag attribute list. Verify that Widevine supports it
|
||||
@@ -483,7 +613,7 @@ bool InitializationData::ConstructWidevineInitData(
|
||||
// have not yet been pushed to production. Set until then.
|
||||
cenc_header.set_algorithm(WidevinePsshData_Algorithm_AESCTR);
|
||||
for (size_t i = 0; i < key_ids.size(); ++i) {
|
||||
cenc_header.add_key_id(key_ids[i]);
|
||||
cenc_header.add_key_ids(key_ids[i]);
|
||||
}
|
||||
cenc_header.set_provider(provider);
|
||||
cenc_header.set_content_id(content_id);
|
||||
@@ -589,19 +719,18 @@ std::vector<std::string> InitializationData::ExtractKeyFormatVersions(
|
||||
return versions;
|
||||
}
|
||||
|
||||
// Extract the key id of the group master key used to generate sublicense data.
|
||||
// Returns an empty string if not defined.
|
||||
const std::string InitializationData::ExtractGroupMasterKeyId() const {
|
||||
if (!is_cenc_) {
|
||||
return "";
|
||||
bool InitializationData::DetectEntitlementPreference(
|
||||
const std::string& oec_version_string) {
|
||||
if (oec_version_string.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
WidevinePsshData cenc_header;
|
||||
if (!cenc_header.ParseFromString(data_)) {
|
||||
return "";
|
||||
}
|
||||
uint32_t oec_version_int = 0;
|
||||
std::istringstream parse_int;
|
||||
parse_int.str(oec_version_string);
|
||||
parse_int >> oec_version_int;
|
||||
|
||||
return cenc_header.group_master_key_id();
|
||||
return oec_version_int >= 14;
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
Reference in New Issue
Block a user