736 lines
23 KiB
C++
736 lines
23 KiB
C++
// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
|
|
// source code may only be used and distributed under the Widevine Master
|
|
// License Agreement.
|
|
|
|
#include "initialization_data.h"
|
|
|
|
#include <string.h>
|
|
#include <string>
|
|
|
|
#include "buffer_reader.h"
|
|
#include "cdm_engine.h"
|
|
#include "jsmn.h"
|
|
#include "log.h"
|
|
#include "platform.h"
|
|
#include "properties.h"
|
|
#include "string_conversions.h"
|
|
#include "wv_cdm_constants.h"
|
|
#include "wv_cdm_types.h"
|
|
|
|
namespace {
|
|
const char kKeyFormatVersionsSeparator = '/';
|
|
const std::string kBase64String = "base64,";
|
|
const uint32_t kFourCcCbc1 = 0x63626331;
|
|
const uint32_t kFourCcCbcs = 0x63626373;
|
|
|
|
// json init data key values
|
|
const std::string kProvider = "provider";
|
|
const std::string kContentId = "content_id";
|
|
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 {
|
|
|
|
// Protobuf generated classes.
|
|
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 std::string& oec_version)
|
|
: type_(type),
|
|
is_cenc_(false),
|
|
is_hls_(false),
|
|
is_webm_(false),
|
|
is_audio_(false),
|
|
contains_entitled_keys_(false),
|
|
hls_method_(kHlsMethodNone) {
|
|
if (type == ISO_BMFF_VIDEO_MIME_TYPE || type == ISO_BMFF_AUDIO_MIME_TYPE ||
|
|
type == CENC_INIT_DATA_FORMAT) {
|
|
is_cenc_ = true;
|
|
} else if (type == WEBM_VIDEO_MIME_TYPE || type == WEBM_AUDIO_MIME_TYPE ||
|
|
type == WEBM_INIT_DATA_FORMAT) {
|
|
is_webm_ = true;
|
|
} else if (type == HLS_INIT_DATA_FORMAT) {
|
|
is_hls_ = true;
|
|
}
|
|
if (type == ISO_BMFF_AUDIO_MIME_TYPE || type == WEBM_AUDIO_MIME_TYPE) {
|
|
is_audio_ = true;
|
|
}
|
|
|
|
if (data.size() && is_supported()) {
|
|
if (is_cenc()) {
|
|
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()) {
|
|
std::string uri;
|
|
if (ExtractHlsAttributes(data, &hls_method_, &hls_iv_, &uri)) {
|
|
ConstructWidevineInitData(hls_method_, uri, &data_);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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.entitled_keys().size() == 0)
|
|
return keys;
|
|
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 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;
|
|
}
|
|
|
|
// 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) {
|
|
contains_entitled_keys_ = true;
|
|
*output = pssh_payloads[i];
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
WidevinePsshData pssh;
|
|
if (prefer_entitlements && pssh.ParseFromString(pssh_payloads[0])) {
|
|
if (pssh.type() == WidevinePsshData_Type_ENTITLED_KEY)
|
|
contains_entitled_keys_ = 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());
|
|
|
|
while (!reader.IsEOF()) {
|
|
const size_t start_pos = reader.pos();
|
|
|
|
// 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::ExtractWidevinePsshs: Unable to read the "
|
|
"32-bit atom size.");
|
|
return false; // We cannot continue reading safely. Abort.
|
|
}
|
|
|
|
// Skip the atom type for now.
|
|
if (!reader.SkipBytes(4)) {
|
|
LOGV(
|
|
"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::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;
|
|
}
|
|
|
|
const size_t bytes_read = reader.pos() - start_pos;
|
|
const size_t bytes_remaining = size - bytes_read;
|
|
|
|
if (!reader.HasBytes(bytes_remaining)) {
|
|
LOGV(
|
|
"InitializationData::ExtractWidevinePsshs: Invalid atom size. The "
|
|
"atom claims to be larger than the remaining init data.");
|
|
return false; // We cannot continue reading safely. Abort.
|
|
}
|
|
|
|
// 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::LocateWidevinePsshOffsets: Unable to skip the "
|
|
"rest of the atom.");
|
|
return false; // We cannot continue reading safely. Abort.
|
|
}
|
|
}
|
|
|
|
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
|
|
// by validating KEYFORMAT and KEYFORMATVERSION attributes. Extract out
|
|
// method, IV, URI and WV init data.
|
|
//
|
|
// An example of a widevine supported attribute list from an HLS media playlist
|
|
// is,
|
|
// "EXT-X-KEY: METHOD=SAMPLE-AES, \"
|
|
// "URI=”data:text/plain;base64,eyANCiAgICJwcm92aWRlciI6Im1sYmFtaGJvIiwNCiAg"
|
|
// "ICJjb250ZW50X2lkIjoiMjAxNV9UZWFycyIsDQogICAia2V5X2lkcyI6DQogICBbDQo"
|
|
// "gICAgICAiMzcxZTEzNWUxYTk4NWQ3NWQxOThhN2Y0MTAyMGRjMjMiDQogICBdDQp9DQ"
|
|
// "o=, \"
|
|
// "IV=0x6df49213a781e338628d0e9c812d328e, \"
|
|
// "KEYFORMAT=”com.widevine”, \"
|
|
// "KEYFORMATVERSIONS=”1”"
|
|
bool InitializationData::ExtractHlsAttributes(const std::string& attribute_list,
|
|
CdmHlsMethod* method,
|
|
std::vector<uint8_t>* iv,
|
|
std::string* uri) {
|
|
std::string value;
|
|
if (!ExtractQuotedAttribute(attribute_list, HLS_KEYFORMAT_ATTRIBUTE,
|
|
&value)) {
|
|
LOGV(
|
|
"InitializationData::ExtractHlsInitDataAtttribute: Unable to read HLS "
|
|
"keyformat value");
|
|
return false;
|
|
}
|
|
|
|
if (value.compare(0, sizeof(KEY_SYSTEM) - 1, KEY_SYSTEM) != 0) {
|
|
LOGV(
|
|
"InitializationData::ExtractHlsInitDataAtttribute: unrecognized HLS "
|
|
"keyformat value: %s",
|
|
value.c_str());
|
|
return false;
|
|
}
|
|
|
|
// KEYFORMATVERSIONS is an optional parameter. If absent its
|
|
// value defaults to "1"
|
|
if (ExtractQuotedAttribute(attribute_list, HLS_KEYFORMAT_VERSIONS_ATTRIBUTE,
|
|
&value)) {
|
|
std::vector<std::string> versions = ExtractKeyFormatVersions(value);
|
|
bool supported = false;
|
|
for (size_t i = 0; i < versions.size(); ++i) {
|
|
if (versions[i].compare(HLS_KEYFORMAT_VERSION_VALUE_1) == 0) {
|
|
supported = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!supported) {
|
|
LOGV(
|
|
"InitializationData::ExtractHlsInitDataAtttribute: HLS keyformat "
|
|
"version is not supported: %s",
|
|
value.c_str());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!ExtractAttribute(attribute_list, HLS_METHOD_ATTRIBUTE, &value)) {
|
|
LOGV(
|
|
"InitializationData::ExtractHlsInitDataAtttribute: Unable to read HLS "
|
|
"method");
|
|
return false;
|
|
}
|
|
|
|
if (value.compare(HLS_METHOD_AES_128) == 0) {
|
|
*method = kHlsMethodAes128;
|
|
} else if (value.compare(HLS_METHOD_SAMPLE_AES) == 0) {
|
|
*method = kHlsMethodSampleAes;
|
|
} else if (value.compare(HLS_METHOD_NONE) == 0) {
|
|
*method = kHlsMethodNone;
|
|
} else {
|
|
LOGV(
|
|
"InitializationData::ExtractHlsInitDataAtttribute: HLS method "
|
|
"unrecognized: %s",
|
|
value.c_str());
|
|
return false;
|
|
}
|
|
|
|
if (!ExtractHexAttribute(attribute_list, HLS_IV_ATTRIBUTE, iv)) {
|
|
LOGV(
|
|
"InitializationData::ExtractHlsInitDataAtttribute: HLS IV attribute "
|
|
"not present");
|
|
return false;
|
|
}
|
|
|
|
if (!ExtractQuotedAttribute(attribute_list, HLS_URI_ATTRIBUTE, uri)) {
|
|
LOGV(
|
|
"InitializationData::ExtractHlsInitDataAtttribute: HLS URI attribute "
|
|
"not present");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Extracts a base64 encoded string from URI data. This is then base64 decoded
|
|
// and the Json formatted init data is then parsed. The information is used
|
|
// to generate a Widevine init data protobuf (WidevineCencHeader).
|
|
//
|
|
// An example of a widevine supported json formatted init data string is,
|
|
//
|
|
// {
|
|
// "provider":"mlbamhbo",
|
|
// "content_id":"MjAxNV9UZWFycw==",
|
|
// "key_ids":
|
|
// [
|
|
// "371e135e1a985d75d198a7f41020dc23"
|
|
// ]
|
|
// }
|
|
bool InitializationData::ConstructWidevineInitData(
|
|
CdmHlsMethod method, const std::string& uri, CdmInitData* init_data_proto) {
|
|
if (!init_data_proto) {
|
|
LOGV("InitializationData::ConstructWidevineInitData: Invalid parameter");
|
|
return false;
|
|
}
|
|
if (method != kHlsMethodAes128 && method != kHlsMethodSampleAes) {
|
|
LOGV(
|
|
"InitializationData::ConstructWidevineInitData: Invalid method"
|
|
" parameter");
|
|
return false;
|
|
}
|
|
|
|
size_t pos = uri.find(kBase64String);
|
|
if (pos == std::string::npos) {
|
|
LOGV(
|
|
"InitializationData::ConstructWidevineInitData: URI attribute "
|
|
"unexpected format: %s",
|
|
uri.c_str());
|
|
return false;
|
|
}
|
|
|
|
std::vector<uint8_t> json_init_data =
|
|
Base64Decode(uri.substr(pos + kBase64String.size()));
|
|
if (json_init_data.size() == 0) {
|
|
LOGV(
|
|
"InitializationData::ConstructWidevineInitData: Base64 decode of json "
|
|
"data failed");
|
|
return false;
|
|
}
|
|
std::string json_string((const char*)(&json_init_data[0]),
|
|
json_init_data.size());
|
|
|
|
// Parse the Json string using jsmn
|
|
jsmn_parser parser;
|
|
jsmntok_t tokens[kDefaultNumJsonTokens];
|
|
jsmn_init(&parser);
|
|
int num_of_tokens =
|
|
jsmn_parse(&parser, json_string.c_str(), json_string.size(), tokens,
|
|
kDefaultNumJsonTokens);
|
|
|
|
if (num_of_tokens <= 0) {
|
|
LOGV(
|
|
"InitializationData::ConstructWidevineInitData: Json parsing failed: "
|
|
"%d",
|
|
num_of_tokens);
|
|
return false;
|
|
}
|
|
|
|
std::string provider;
|
|
std::string content_id;
|
|
std::vector<std::string> key_ids;
|
|
|
|
enum JsmnParserState {
|
|
kParseState,
|
|
kProviderState,
|
|
kContentIdState,
|
|
kKeyIdsState,
|
|
} state = kParseState;
|
|
|
|
int number_of_key_ids = 0;
|
|
|
|
// Extract the provider, content_id and key_ids
|
|
for (int i = 0; i < num_of_tokens; ++i) {
|
|
if (tokens[i].start < 0 || tokens[i].end < 0) {
|
|
LOGV(
|
|
"InitializationData::ConstructWidevineInitData: Invalid start or end "
|
|
"of token");
|
|
return false;
|
|
}
|
|
|
|
switch (state) {
|
|
case kParseState:
|
|
if (tokens[i].type == JSMN_STRING) {
|
|
std::string token(json_string, tokens[i].start,
|
|
tokens[i].end - tokens[i].start);
|
|
if (token == kProvider) {
|
|
state = kProviderState;
|
|
} else if (token == kContentId) {
|
|
state = kContentIdState;
|
|
} else if (token == kKeyIds) {
|
|
state = kKeyIdsState;
|
|
}
|
|
}
|
|
break;
|
|
case kProviderState:
|
|
if (tokens[i].type == JSMN_STRING) {
|
|
provider.assign(json_string, tokens[i].start,
|
|
tokens[i].end - tokens[i].start);
|
|
}
|
|
state = kParseState;
|
|
break;
|
|
case kContentIdState:
|
|
if (tokens[i].type == JSMN_STRING) {
|
|
std::string base64_content_id(json_string, tokens[i].start,
|
|
tokens[i].end - tokens[i].start);
|
|
std::vector<uint8_t> content_id_data =
|
|
Base64Decode(base64_content_id);
|
|
content_id.assign(reinterpret_cast<const char*>(&content_id_data[0]),
|
|
content_id_data.size());
|
|
}
|
|
state = kParseState;
|
|
break;
|
|
case kKeyIdsState:
|
|
if (tokens[i].type == JSMN_ARRAY) {
|
|
number_of_key_ids = tokens[i].size;
|
|
} else if (tokens[i].type == JSMN_STRING) {
|
|
std::string key_id(a2bs_hex(json_string.substr(
|
|
tokens[i].start, tokens[i].end - tokens[i].start)));
|
|
if (key_id.size() == 16) key_ids.push_back(key_id);
|
|
--number_of_key_ids;
|
|
} else {
|
|
state = kParseState;
|
|
}
|
|
if (number_of_key_ids <= 0) state = kParseState;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (provider.size() == 0) {
|
|
LOGV("InitializationData::ConstructWidevineInitData: Invalid provider");
|
|
return false;
|
|
}
|
|
|
|
if (content_id.size() == 0) {
|
|
LOGV("InitializationData::ConstructWidevineInitData: Invalid content_id");
|
|
return false;
|
|
}
|
|
|
|
if (key_ids.size() == 0) {
|
|
LOGV("InitializationData::ConstructWidevineInitData: No key_ids present");
|
|
return false;
|
|
}
|
|
|
|
// Now format as Widevine init data protobuf
|
|
WidevinePsshData cenc_header;
|
|
// TODO(rfrias): The algorithm is a deprecated field, but proto changes
|
|
// 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_ids(key_ids[i]);
|
|
}
|
|
cenc_header.set_provider(provider);
|
|
cenc_header.set_content_id(content_id);
|
|
if (method == kHlsMethodAes128)
|
|
cenc_header.set_protection_scheme(kFourCcCbc1);
|
|
else
|
|
cenc_header.set_protection_scheme(kFourCcCbcs);
|
|
cenc_header.SerializeToString(init_data_proto);
|
|
return true;
|
|
}
|
|
|
|
bool InitializationData::ExtractQuotedAttribute(
|
|
const std::string& attribute_list, const std::string& key,
|
|
std::string* value) {
|
|
bool result = ExtractAttribute(attribute_list, key, value);
|
|
if (value->size() < 2 || value->at(0) != '\"' ||
|
|
value->at(value->size() - 1) != '\"')
|
|
return false;
|
|
*value = value->substr(1, value->size() - 2);
|
|
if (value->find('\"') != std::string::npos) return false;
|
|
return result;
|
|
}
|
|
|
|
bool InitializationData::ExtractHexAttribute(const std::string& attribute_list,
|
|
const std::string& key,
|
|
std::vector<uint8_t>* value) {
|
|
std::string val;
|
|
bool result = ExtractAttribute(attribute_list, key, &val);
|
|
if (!result || val.size() <= 2 || val.size() % 2 != 0 || val[0] != '0' ||
|
|
((val[1] != 'x') && (val[1] != 'X')))
|
|
return false;
|
|
for (size_t i = 2; i < val.size(); ++i) {
|
|
if (!isxdigit(val[i])) return false;
|
|
}
|
|
*value = a2b_hex(val.substr(2, val.size() - 2));
|
|
return result;
|
|
}
|
|
|
|
bool InitializationData::ExtractAttribute(const std::string& attribute_list,
|
|
const std::string& key,
|
|
std::string* value) {
|
|
// validate the key
|
|
for (size_t i = 0; i < key.size(); ++i)
|
|
if (!isupper(key[i]) && !isdigit(key[i]) && key[i] != '-') return false;
|
|
|
|
bool found = false;
|
|
size_t pos = 0;
|
|
// Find the key followed by '='
|
|
while (!found) {
|
|
pos = attribute_list.find(key, pos);
|
|
if (pos == std::string::npos) return false;
|
|
pos += key.size();
|
|
if (attribute_list[pos] != '=') continue;
|
|
found = true;
|
|
}
|
|
|
|
if (attribute_list.size() <= ++pos) return false;
|
|
|
|
size_t end_pos = pos;
|
|
found = false;
|
|
// Find the next comma outside the quote or end of string
|
|
while (!found) {
|
|
end_pos = attribute_list.find(',', end_pos);
|
|
if (end_pos != std::string::npos && attribute_list[pos] == '\"' &&
|
|
attribute_list[end_pos - 1] != '\"') {
|
|
++end_pos;
|
|
continue;
|
|
}
|
|
|
|
if (end_pos == std::string::npos)
|
|
end_pos = attribute_list.size() - 1;
|
|
else
|
|
--end_pos;
|
|
found = true;
|
|
}
|
|
|
|
*value = attribute_list.substr(pos, end_pos - pos + 1);
|
|
|
|
// validate the value
|
|
for (size_t i = 0; i < value->size(); ++i)
|
|
if (!isgraph(value->at(i))) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
// Key format versions are individual values or multiple versions
|
|
// separated by '/'. "1" or "1/2/5"
|
|
std::vector<std::string> InitializationData::ExtractKeyFormatVersions(
|
|
const std::string& key_format_versions) {
|
|
std::vector<std::string> versions;
|
|
size_t pos = 0;
|
|
while (pos < key_format_versions.size()) {
|
|
size_t next_pos =
|
|
key_format_versions.find(kKeyFormatVersionsSeparator, pos);
|
|
if (next_pos == std::string::npos) {
|
|
versions.push_back(key_format_versions.substr(pos));
|
|
break;
|
|
} else {
|
|
versions.push_back(key_format_versions.substr(pos, next_pos - pos));
|
|
pos = next_pos + 1;
|
|
}
|
|
}
|
|
return versions;
|
|
}
|
|
|
|
bool InitializationData::DetectEntitlementPreference(
|
|
const std::string& oec_version_string) {
|
|
if (oec_version_string.empty()) {
|
|
return false;
|
|
}
|
|
return std::stoul(oec_version_string) >= 14;
|
|
}
|
|
|
|
} // namespace wvcdm
|