7#include <packager/media/base/cpix_parser.h>
10#include <initializer_list>
16#include <absl/log/check.h>
17#include <absl/strings/escaping.h>
18#include <absl/strings/numbers.h>
19#include <absl/strings/str_replace.h>
20#include <libxml/parser.h>
21#include <libxml/tree.h>
23#include <packager/macros/status.h>
24#include <packager/mpd/base/xml/scoped_xml_ptr.h>
25#include <packager/utils/hex_parser.h>
33bool IsElement(xmlNodePtr node,
const char* name) {
34 return node && node->type == XML_ELEMENT_NODE &&
35 xmlStrcmp(node->name, BAD_CAST name) == 0;
38xmlNodePtr FindChildElement(xmlNodePtr node,
const char* name) {
39 for (xmlNodePtr child = node->children; child; child = child->next) {
40 if (IsElement(child, name))
46std::optional<std::string> GetAttribute(xmlNodePtr node,
const char* name) {
47 xml::scoped_xml_ptr<xmlChar> value(xmlGetProp(node, BAD_CAST name));
50 return std::string(
reinterpret_cast<const char*
>(value.get()));
53std::string GetContent(xmlNodePtr node) {
54 xml::scoped_xml_ptr<xmlChar> content(xmlNodeGetContent(node));
57 return std::string(
reinterpret_cast<const char*
>(content.get()));
60Status GetRequiredAttribute(xmlNodePtr node,
62 const std::string& element_desc,
64 std::optional<std::string> attribute = GetAttribute(node, name);
66 return Status(error::INVALID_ARGUMENT,
67 element_desc +
" is missing the '" + name +
"' attribute.");
69 *value = std::move(*attribute);
73Status ParseUuid(
const std::string& uuid,
74 const std::string& error_context,
75 std::vector<uint8_t>* bytes) {
76 const std::string hex = absl::StrReplaceAll(uuid, {{
"-",
""}});
77 if (!ValidHexStringToBytes(hex, bytes) || bytes->size() != 16) {
78 return Status(error::INVALID_ARGUMENT,
79 error_context +
" is not a valid UUID: " + uuid);
84Status ParseBase64(
const std::string& base64,
85 const std::string& error_context,
86 std::vector<uint8_t>* bytes) {
90 if (!absl::Base64Unescape(base64, &decoded)) {
91 return Status(error::INVALID_ARGUMENT,
92 error_context +
" is not valid base64: " + base64);
94 bytes->assign(decoded.begin(), decoded.end());
98Status ParseEncryptedValue(xmlNodePtr node,
99 const std::string& error_context,
100 CpixEncryptedValue* encrypted_value) {
101 xmlNodePtr method = FindChildElement(node,
"EncryptionMethod");
103 encrypted_value->algorithm = GetAttribute(method,
"Algorithm").value_or(
"");
105 xmlNodePtr cipher_data = FindChildElement(node,
"CipherData");
106 xmlNodePtr cipher_value =
107 cipher_data ? FindChildElement(cipher_data,
"CipherValue") : nullptr;
109 return Status(error::INVALID_ARGUMENT,
110 "EncryptedValue of " + error_context +
111 " has no CipherData/CipherValue.");
113 return ParseBase64(GetContent(cipher_value),
114 "CipherValue of " + error_context,
115 &encrypted_value->cipher_value);
118Status ParseContentKey(xmlNodePtr node, CpixContentKey* content_key) {
121 GetRequiredAttribute(node,
"kid",
"ContentKey element", &kid));
122 RETURN_IF_ERROR(ParseUuid(kid,
"ContentKey@kid", &content_key->key_id));
124 std::optional<std::string> explicit_iv = GetAttribute(node,
"explicitIV");
126 RETURN_IF_ERROR(ParseBase64(*explicit_iv,
"explicitIV of ContentKey " + kid,
130 content_key->common_encryption_scheme =
131 GetAttribute(node,
"commonEncryptionScheme").value_or(
"");
133 xmlNodePtr data = FindChildElement(node,
"Data");
134 xmlNodePtr secret = data ? FindChildElement(data,
"Secret") : nullptr;
136 return Status(error::INVALID_ARGUMENT,
137 "ContentKey " + kid +
" has no Data/Secret element.");
139 xmlNodePtr plain_value = FindChildElement(secret,
"PlainValue");
140 xmlNodePtr encrypted_value = FindChildElement(secret,
"EncryptedValue");
141 if (plain_value && encrypted_value) {
143 error::INVALID_ARGUMENT,
144 "ContentKey " + kid +
" has both a PlainValue and an EncryptedValue.");
146 if (encrypted_value) {
147 CpixEncryptedValue value;
149 ParseEncryptedValue(encrypted_value,
"ContentKey " + kid, &value));
150 xmlNodePtr value_mac = FindChildElement(secret,
"ValueMAC");
152 RETURN_IF_ERROR(ParseBase64(GetContent(value_mac),
153 "ValueMAC of ContentKey " + kid,
156 content_key->encrypted_key = std::move(value);
160 return Status(error::INVALID_ARGUMENT,
161 "ContentKey " + kid +
162 " has no Data/Secret/PlainValue or EncryptedValue.");
164 RETURN_IF_ERROR(ParseBase64(GetContent(plain_value),
165 "PlainValue of ContentKey " + kid,
170Status ParseDeliveryData(xmlNodePtr node, CpixDeliveryData* delivery_data) {
171 xmlNodePtr document_key = FindChildElement(node,
"DocumentKey");
173 document_key ? FindChildElement(document_key,
"Data") : nullptr;
174 xmlNodePtr secret = data ? FindChildElement(data,
"Secret") : nullptr;
175 xmlNodePtr encrypted_value =
176 secret ? FindChildElement(secret,
"EncryptedValue") : nullptr;
177 if (!encrypted_value) {
178 return Status(error::INVALID_ARGUMENT,
179 "DeliveryData has no "
180 "DocumentKey/Data/Secret/EncryptedValue.");
182 RETURN_IF_ERROR(ParseEncryptedValue(encrypted_value,
"DocumentKey",
183 &delivery_data->document_key));
185 xmlNodePtr mac_method = FindChildElement(node,
"MACMethod");
187 delivery_data->mac_algorithm =
188 GetAttribute(mac_method,
"Algorithm").value_or(
"");
189 if (delivery_data->mac_algorithm.empty()) {
190 return Status(error::INVALID_ARGUMENT,
191 "MACMethod is missing the 'Algorithm' attribute.");
193 xmlNodePtr key = FindChildElement(mac_method,
"Key");
194 xmlNodePtr key_encrypted_value =
195 key ? FindChildElement(key,
"EncryptedValue") : nullptr;
196 if (!key_encrypted_value) {
197 return Status(error::INVALID_ARGUMENT,
198 "MACMethod has no Key/EncryptedValue.");
200 RETURN_IF_ERROR(ParseEncryptedValue(key_encrypted_value,
"MACMethod key",
201 &delivery_data->mac_key));
206Status ParseDrmSystem(xmlNodePtr node, CpixDrmSystem* drm_system) {
208 RETURN_IF_ERROR(GetRequiredAttribute(node,
"kid",
"DRMSystem element", &kid));
209 RETURN_IF_ERROR(ParseUuid(kid,
"DRMSystem@kid", &drm_system->key_id));
211 std::string system_id;
212 RETURN_IF_ERROR(GetRequiredAttribute(
213 node,
"systemId",
"DRMSystem element for key " + kid, &system_id));
215 ParseUuid(system_id,
"DRMSystem@systemId", &drm_system->system_id));
217 xmlNodePtr pssh = FindChildElement(node,
"PSSH");
219 RETURN_IF_ERROR(ParseBase64(
220 GetContent(pssh),
"PSSH of DRMSystem " + system_id +
" for key " + kid,
226Status ParseUsageRule(xmlNodePtr node, CpixUsageRule* usage_rule) {
229 GetRequiredAttribute(node,
"kid",
"ContentKeyUsageRule element", &kid));
231 ParseUuid(kid,
"ContentKeyUsageRule@kid", &usage_rule->key_id));
233 usage_rule->intended_track_type =
234 GetAttribute(node,
"intendedTrackType").value_or(
"");
240 auto reject_unsupported_attributes =
241 [&kid](xmlNodePtr filter,
const char* filter_desc,
242 std::initializer_list<const char*> attributes) -> Status {
243 for (
const char* attribute : attributes) {
244 if (GetAttribute(filter, attribute)) {
245 return Status(error::UNIMPLEMENTED,
246 "ContentKeyUsageRule for key " + kid +
" contains " +
247 filter_desc +
" with the '" + attribute +
248 "' attribute, which is not supported yet.");
253 for (xmlNodePtr child = node->children; child; child = child->next) {
254 if (child->type != XML_ELEMENT_NODE)
256 if (IsElement(child,
"VideoFilter")) {
257 RETURN_IF_ERROR(reject_unsupported_attributes(
258 child,
"a VideoFilter", {
"hdr",
"wcg",
"minFps",
"maxFps"}));
259 CpixVideoFilter video_filter;
260 std::optional<std::string> min_pixels = GetAttribute(child,
"minPixels");
262 (!absl::SimpleAtoi(*min_pixels, &video_filter.min_pixels) ||
263 video_filter.min_pixels < 0)) {
264 return Status(error::INVALID_ARGUMENT,
265 "Invalid VideoFilter@minPixels for key " + kid +
": " +
268 std::optional<std::string> max_pixels = GetAttribute(child,
"maxPixels");
270 (!absl::SimpleAtoi(*max_pixels, &video_filter.max_pixels) ||
271 video_filter.max_pixels < 0)) {
272 return Status(error::INVALID_ARGUMENT,
273 "Invalid VideoFilter@maxPixels for key " + kid +
": " +
276 usage_rule->video_filters.push_back(video_filter);
277 }
else if (IsElement(child,
"AudioFilter")) {
278 RETURN_IF_ERROR(reject_unsupported_attributes(
279 child,
"an AudioFilter", {
"minChannels",
"maxChannels"}));
280 usage_rule->has_audio_filter =
true;
283 error::UNIMPLEMENTED,
284 "ContentKeyUsageRule for key " + kid +
" contains a " +
285 reinterpret_cast<const char*
>(child->name) +
286 " element, which is not supported yet. Only VideoFilter, "
287 "AudioFilter and the 'intendedTrackType' attribute are "
292 if (usage_rule->has_audio_filter && !usage_rule->video_filters.empty()) {
296 return Status(error::INVALID_ARGUMENT,
297 "ContentKeyUsageRule for key " + kid +
298 " contains both an AudioFilter and a VideoFilter, so "
299 "it cannot match any stream.");
306Status ParseList(xmlNodePtr list,
307 const char* element_name,
308 Status (*parse)(xmlNodePtr, T*),
309 std::vector<T>* out) {
310 for (xmlNodePtr node = list->children; node; node = node->next) {
311 if (!IsElement(node, element_name))
314 RETURN_IF_ERROR(parse(node, &item));
315 out->push_back(std::move(item));
322Status ParseCpixDocument(
const std::string& xml, CpixDocument* document) {
325 xml::scoped_xml_ptr<xmlDoc> doc(xmlReadMemory(
326 xml.data(),
static_cast<int>(xml.size()),
nullptr,
327 nullptr, XML_PARSE_NONET));
329 return Status(error::INVALID_ARGUMENT,
330 "Failed to parse the CPIX document as XML.");
333 xmlNodePtr root = xmlDocGetRootElement(doc.get());
334 if (!IsElement(root,
"CPIX")) {
335 return Status(error::INVALID_ARGUMENT,
336 "The root element of a CPIX document must be CPIX.");
339 for (xmlNodePtr list = root->children; list; list = list->next) {
340 if (IsElement(list,
"ContentKeyList")) {
341 RETURN_IF_ERROR(ParseList(list,
"ContentKey", &ParseContentKey,
342 &document->content_keys));
343 }
else if (IsElement(list,
"DRMSystemList")) {
344 RETURN_IF_ERROR(ParseList(list,
"DRMSystem", &ParseDrmSystem,
345 &document->drm_systems));
346 }
else if (IsElement(list,
"ContentKeyUsageRuleList")) {
347 RETURN_IF_ERROR(ParseList(list,
"ContentKeyUsageRule", &ParseUsageRule,
348 &document->usage_rules));
349 }
else if (IsElement(list,
"DeliveryDataList")) {
350 RETURN_IF_ERROR(ParseList(list,
"DeliveryData", &ParseDeliveryData,
351 &document->delivery_data));
357 if (document->content_keys.empty()) {
358 return Status(error::INVALID_ARGUMENT,
359 "The CPIX document contains no content keys.");
361 for (
size_t i = 0; i < document->content_keys.size(); ++i) {
362 for (
size_t j = i + 1; j < document->content_keys.size(); ++j) {
363 if (document->content_keys[i].key_id ==
364 document->content_keys[j].key_id) {
365 const std::vector<uint8_t>& key_id = document->content_keys[i].key_id;
367 error::INVALID_ARGUMENT,
368 "Duplicate ContentKey kid " + absl::BytesToHexString(std::string(
369 key_id.begin(), key_id.end())));
All the methods that are virtual are virtual for mocking.