Skip to content

Commit

Permalink
[DASHTree] Fixed license_data property use case
Browse files Browse the repository at this point in the history
  • Loading branch information
CastagnaIT committed Sep 6, 2023
1 parent f4e47c6 commit 9d62b82
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 120 deletions.
15 changes: 7 additions & 8 deletions src/Session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -480,10 +480,10 @@ bool CSession::InitializeDRM(bool addDefaultKID /* = false */)

if (sessionPsshset.pssh_ == PSSH_FROM_FILE)
{
LOG::Log(LOGDEBUG, "Searching PSSH data in FILE");

if (m_kodiProps.m_licenseData.empty())
{
LOG::Log(LOGDEBUG, "Searching for PSSH data in the stream file");

auto initialRepr{m_reprChooser->GetRepresentation(sessionPsshset.adaptation_set_)};

CStream stream{*m_adaptiveTree, sessionPsshset.adaptation_set_, initialRepr, m_kodiProps};
Expand Down Expand Up @@ -554,17 +554,16 @@ bool CSession::InitializeDRM(bool addDefaultKID /* = false */)
}
stream.Disable();
}
else if (!sessionPsshset.defaultKID_.empty())
else
{
std::string licenseData = BASE64::Decode(m_kodiProps.m_licenseData);
// Replace KID placeholder, if any
STRING::ReplaceFirst(licenseData, "{KID}", sessionPsshset.defaultKID_);
// This can allow to initialize a DRM that could be also not specified
// as supported in the manifest (e.g. on DASH ContentProtection tags)
LOG::Log(LOGDEBUG, "Set init PSSH data provided by the license data property");

std::string licenseData = BASE64::Decode(m_kodiProps.m_licenseData);
init_data.SetData(reinterpret_cast<const AP4_Byte*>(licenseData.c_str()),
static_cast<AP4_Size>(licenseData.size()));
}
else
return false;
}
else
{
Expand Down
2 changes: 1 addition & 1 deletion src/common/AdaptiveUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ namespace PLAYLIST
constexpr uint16_t PSSHSET_POS_DEFAULT = 0;
// Marker for not valid psshset position
constexpr uint16_t PSSHSET_POS_INVALID = std::numeric_limits<uint16_t>::max();
// Marker to extract the PSSH from the file
// Marker to try extract the PSSH from the file, or to try use a custom PSSH license data provided
constexpr std::string_view PSSH_FROM_FILE = "FILE";
// Marker for not set/not found segment position
constexpr size_t SEGMENT_NO_POS = std::numeric_limits<size_t>::max();
Expand Down
215 changes: 108 additions & 107 deletions src/parser/DASHTree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,16 @@ std::string DetectCodecFromMimeType(std::string_view mimeType)

adaptive::CDashTree::CDashTree(const CDashTree& left) : AdaptiveTree(left)
{
m_isCustomInitPssh = left.m_isCustomInitPssh;
}

void adaptive::CDashTree::Configure(const UTILS::PROPERTIES::KodiProperties& kodiProps,
CHOOSER::IRepresentationChooser* reprChooser,
std::string_view supportedKeySystem,
std::string_view manifestUpdParams)
{
AdaptiveTree::Configure(kodiProps, reprChooser, supportedKeySystem, manifestUpdParams);
m_isCustomInitPssh = !kodiProps.m_licenseData.empty();
}

bool adaptive::CDashTree::Open(std::string_view url,
Expand Down Expand Up @@ -576,10 +586,9 @@ void adaptive::CDashTree::ParseTagAdaptationSet(pugi::xml_node nodeAdp, PLAYLIST
}

// Parse <Representation> child tags
bool hasReprURN{false}; // True if representations has URNs
for (xml_node node : nodeAdp.children("Representation"))
{
ParseTagRepresentation(node, adpSet.get(), period, hasReprURN);
ParseTagRepresentation(node, adpSet.get(), period);
}

if (adpSet->GetRepresentations().empty())
Expand All @@ -593,37 +602,35 @@ void adaptive::CDashTree::ParseTagAdaptationSet(pugi::xml_node nodeAdp, PLAYLIST
if (nodeAdp.child("ContentProtection"))
{
period->SetEncryptionState(EncryptionState::ENCRYPTED);
uint16_t currentPsshSetPos{PSSHSET_POS_DEFAULT};
std::string currentPssh{PSSH_FROM_FILE};
std::string currentDefaultKID;
bool isSecureDecoderNeeded{false};

if (ParseTagContentProtection(nodeAdp, currentPssh, currentDefaultKID, isSecureDecoderNeeded))
// If a custom init PSSH is provided, should mean that a certain content protection tag
// is missing, in this case we ignore the content protection tags and we add a PSSH marked
// PSSH_FROM_FILE to allow initialization of DRM with the specified PSSH data provided
if (m_isCustomInitPssh || ParseTagContentProtection(nodeAdp, currentPssh, currentDefaultKID))
{
period->SetEncryptionState(EncryptionState::ENCRYPTED_SUPPORTED);
currentPsshSetPos = InsertPsshSet(adpSet->GetStreamType(), period, adpSet.get(), currentPssh,
currentDefaultKID);
}
if (currentPsshSetPos == PSSHSET_POS_INVALID)
{
LOG::LogF(LOGWARNING, "The PSSHSet in the adaptation set id \"%s\" is not valid.",
adpSet->GetId().data());
}
period->SetSecureDecodeNeeded(isSecureDecoderNeeded);
uint16_t currentPsshSetPos = InsertPsshSet(adpSet->GetStreamType(), period, adpSet.get(),
currentPssh, currentDefaultKID);

if (hasReprURN && currentPsshSetPos == PSSHSET_POS_INVALID)
{
LOG::LogF(LOGWARNING, "Skipped adaptation set with id: \"%s\", pssh not valid.",
adpSet->GetId().data());
return;
}
if (currentPsshSetPos == PSSHSET_POS_INVALID)
{
LOG::LogF(LOGWARNING, "Skipped adaptation set with id: \"%s\", due to not valid PSSH.",
adpSet->GetId().data());
return;
}

if (currentPsshSetPos != PSSHSET_POS_DEFAULT)
{
// Set PSSHSet of AdaptationSet to all representations
for (auto& rep : adpSet->GetRepresentations())
period->SetSecureDecodeNeeded(ParseTagContentProtectionSecDec(nodeAdp));

if (currentPsshSetPos != PSSHSET_POS_DEFAULT)
{
rep->m_psshSetPos = currentPsshSetPos;
// Set PSSHSet of AdaptationSet to representations when its not set
for (auto& rep : adpSet->GetRepresentations())
{
if (rep->m_psshSetPos == PSSHSET_POS_DEFAULT)
rep->m_psshSetPos = currentPsshSetPos;
}
}
}
}
Expand All @@ -639,8 +646,7 @@ void adaptive::CDashTree::ParseTagAdaptationSet(pugi::xml_node nodeAdp, PLAYLIST

void adaptive::CDashTree::ParseTagRepresentation(pugi::xml_node nodeRepr,
PLAYLIST::CAdaptationSet* adpSet,
PLAYLIST::CPeriod* period,
bool& hasReprURN)
PLAYLIST::CPeriod* period)
{
std::unique_ptr<CRepresentation> repr = CRepresentation::MakeUniquePtr(adpSet);

Expand Down Expand Up @@ -946,30 +952,32 @@ void adaptive::CDashTree::ParseTagRepresentation(pugi::xml_node nodeRepr,
if (nodeRepr.child("ContentProtection"))
{
period->SetEncryptionState(EncryptionState::ENCRYPTED);
uint16_t currentPsshSetPos{PSSHSET_POS_DEFAULT};
std::string currentPssh;
std::string currentDefaultKID;
std::string currentPssh{PSSH_FROM_FILE};
bool isSecureDecoderNeeded{false};

if (ParseTagContentProtection(nodeRepr, currentPssh, currentDefaultKID, isSecureDecoderNeeded))
// If a custom init PSSH is provided, should mean that a certain content protection tag
// is missing, in this case we ignore the content protection tags and we add a PSSH marked
// PSSH_FROM_FILE to allow initialization of DRM with the specified PSSH data provided
if (m_isCustomInitPssh || ParseTagContentProtection(nodeRepr, currentPssh, currentDefaultKID))
{
period->SetEncryptionState(EncryptionState::ENCRYPTED_SUPPORTED);
currentPsshSetPos =

uint16_t psshSetPos =
InsertPsshSet(adpSet->GetStreamType(), period, adpSet, currentPssh, currentDefaultKID);

repr->m_psshSetPos = currentPsshSetPos;
hasReprURN = true;
}
if (currentPsshSetPos == PSSHSET_POS_INVALID)
{
LOG::LogF(LOGWARNING, "Representation id \"%s\" skipped, invalid PSSHSet.",
repr->GetId().data());
return;
}
if (isSecureDecoderNeeded)
{
LOG::LogF(LOGERROR, "The <ContentProtection><widevine:license> tag must be child of "
"the <AdaptationSet> tag.");
if (psshSetPos == PSSHSET_POS_INVALID)
{
LOG::LogF(LOGWARNING, "Skipped representation with id: \"%s\", due to not valid PSSH",
repr->GetId().data());
return;
}
repr->m_psshSetPos = psshSetPos;

if (ParseTagContentProtectionSecDec(nodeRepr))
{
LOG::LogF(LOGERROR, "The <ContentProtection><widevine:license> tag must be child of "
"the <AdaptationSet> tag.");
}
}
}

Expand Down Expand Up @@ -1243,52 +1251,52 @@ void adaptive::CDashTree::ParseSegmentTemplate(pugi::xml_node node, CSegmentTemp

bool adaptive::CDashTree::ParseTagContentProtection(pugi::xml_node nodeParent,
std::string& currentPssh,
std::string& currentDefaultKID,
bool& isSecureDecoderNeeded)
std::string& currentDefaultKID)
{
bool isUrnProtectionFound{false};
bool isUrnSchemeFound{false};

// Parse <ContentProtection> tags to find "default_KID" attribute
// We try read "default_KID" attribute on every ContentProtection type
const char* defaultKID{nullptr};

for (xml_node nodeCP : nodeParent.children("ContentProtection"))
{
if (defaultKID)
break;

std::string_view schemeIdUri = XML::GetAttrib(nodeCP, "schemeIdUri");

if (schemeIdUri == "urn:mpeg:dash:mp4protection:2011" ||
STRING::CompareNoCase(schemeIdUri, m_supportedKeySystem))
if (schemeIdUri == "urn:mpeg:dash:mp4protection:2011")
{
// Parse first attribute that end with "... default_KID"
// e.g. cenc:default_KID="01004b6f-0835-b807-9098-c070dc30a6c7"
xml_attribute attrKID = XML::FirstAttributeNoPrefix(nodeCP, "default_KID");
if (attrKID)
defaultKID = attrKID.value();

// get crypto mode if available
// Get common crypto mode
std::string_view protectionValue = XML::GetAttrib(nodeCP, "value");
if (protectionValue == "cenc")
m_cryptoMode = CryptoMode::AES_CTR;
else if (protectionValue == "cbcs")
m_cryptoMode = CryptoMode::AES_CBC;
}
}

// Parse <ContentProtection> tags
bool isUrnProtectionFound{false};
bool isUrnSchemeFound{false};
std::string commonPssh;
std::string playReadyPro;

for (xml_node nodeCP : nodeParent.children("ContentProtection"))
{
std::string_view schemeIdUri = XML::GetAttrib(nodeCP, "schemeIdUri");
// Get optional default KID
// Parse first attribute that end with "... default_KID"
// e.g. cenc:default_KID="01004b6f-0835-b807-9098-c070dc30a6c7"
xml_attribute attrKID = XML::FirstAttributeNoPrefix(nodeCP, "default_KID");
if (attrKID)
{
const char* defaultKID = attrKID.value();

if (schemeIdUri == "urn:mpeg:dash:mp4protection:2011")
if (defaultKID && std::strlen(defaultKID) == 36)
{
currentDefaultKID.resize(16);
for (size_t i{0}; i < 16; i++)
{
if (i == 4 || i == 6 || i == 8 || i == 10)
defaultKID++;
currentDefaultKID[i] = STRING::ToHexNibble(*defaultKID) << 4;
defaultKID++;
currentDefaultKID[i] |= STRING::ToHexNibble(*defaultKID);
defaultKID++;
}
}
}
isUrnProtectionFound = true;
}

// Find Content protection compatible with current systemid
// Find a content protection compatible with current systemid
if (!STRING::CompareNoCase(schemeIdUri, m_supportedKeySystem))
continue;

Expand All @@ -1301,13 +1309,33 @@ bool adaptive::CDashTree::ParseTagContentProtection(pugi::xml_node nodeParent,

if (StringUtils::EndsWith(childName, "pssh")) // e.g. <cenc:pssh> or <pssh> ...
{
commonPssh = node.child_value();
std::string_view pssh = node.child_value();
if (!pssh.empty())
currentPssh = pssh;
}
else if (childName == "mspr:pro")
else if (childName == "mspr:pro" || childName == "pro")
{
playReadyPro = node.child_value();
PRProtectionParser parser;
if (parser.ParseHeader(node.child_value()))
currentDefaultKID = parser.GetKID();
}
else if (childName == "widevine:license")
}
}

return isUrnSchemeFound || isUrnProtectionFound;
}

bool adaptive::CDashTree::ParseTagContentProtectionSecDec(pugi::xml_node nodeParent)
{
// Try to find ISA custom tag/attrib:
// <ContentProtection><widevine:license robustness_level="HW_SECURE_CODECS_REQUIRED">
// to know if its needed to force the secure decoder
for (xml_node nodeCP : nodeParent.children("ContentProtection"))
{
// Parse child tags
for (xml_node node : nodeCP.children())
{
if (STRING::Compare(node.name(), "widevine:license"))
{
// <widevine:license robustness_level="HW_SECURE_CODECS_REQUIRED"> Custom ISA tag
// to force secure decoder, accepted in the <AdaptationSet> only
Expand All @@ -1322,38 +1350,11 @@ bool adaptive::CDashTree::ParseTagContentProtection(pugi::xml_node nodeParent,
"You must change it to \"HW_SECURE_CODECS_REQUIRED\".");
robustnessLevel = "HW_SECURE_CODECS_REQUIRED";
}
isSecureDecoderNeeded = robustnessLevel == "HW_SECURE_CODECS_REQUIRED";
return robustnessLevel == "HW_SECURE_CODECS_REQUIRED";
}
}
}

if (commonPssh.empty() && !playReadyPro.empty())
{
PRProtectionParser parser;
if (parser.ParseHeader(playReadyPro))
currentDefaultKID = parser.GetKID();
}
else
{
if (!commonPssh.empty())
currentPssh = commonPssh;

if ((isUrnSchemeFound || isUrnProtectionFound) && defaultKID && std::strlen(defaultKID) == 36)
{
currentDefaultKID.resize(16);
for (size_t i{0}; i < 16; i++)
{
if (i == 4 || i == 6 || i == 8 || i == 10)
defaultKID++;
currentDefaultKID[i] = STRING::ToHexNibble(*defaultKID) << 4;
defaultKID++;
currentDefaultKID[i] |= STRING::ToHexNibble(*defaultKID);
defaultKID++;
}
}
}

return isUrnSchemeFound || isUrnProtectionFound;
return false;
}

uint32_t adaptive::CDashTree::ParseAudioChannelConfig(pugi::xml_node node)
Expand Down
15 changes: 11 additions & 4 deletions src/parser/DASHTree.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ class ATTR_DLL_LOCAL CDashTree : public adaptive::AdaptiveTree
CDashTree() : AdaptiveTree() {}
CDashTree(const CDashTree& left);

void Configure(const UTILS::PROPERTIES::KodiProperties& kodiProps,
CHOOSER::IRepresentationChooser* reprChooser,
std::string_view supportedKeySystem,
std::string_view manifestUpdParams);

virtual TreeType GetTreeType() override { return TreeType::DASH; }

virtual bool Open(std::string_view url,
Expand All @@ -56,8 +61,7 @@ class ATTR_DLL_LOCAL CDashTree : public adaptive::AdaptiveTree
void ParseTagAdaptationSet(pugi::xml_node nodeAdp, PLAYLIST::CPeriod* period);
void ParseTagRepresentation(pugi::xml_node nodeRepr,
PLAYLIST::CAdaptationSet* adpSet,
PLAYLIST::CPeriod* period,
bool& hasReprURN);
PLAYLIST::CPeriod* period);

uint64_t ParseTagSegmentTimeline(pugi::xml_node parentNode,
PLAYLIST::CSpinCache<uint32_t>& SCTimeline,
Expand All @@ -72,8 +76,9 @@ class ATTR_DLL_LOCAL CDashTree : public adaptive::AdaptiveTree

bool ParseTagContentProtection(pugi::xml_node nodeCP,
std::string& currentPssh,
std::string& currentDefaultKID,
bool& isSecureDecoderNeeded);
std::string& currentDefaultKID);

bool ParseTagContentProtectionSecDec(pugi::xml_node nodeParent);

uint32_t ParseAudioChannelConfig(pugi::xml_node node);

Expand Down Expand Up @@ -116,5 +121,7 @@ class ATTR_DLL_LOCAL CDashTree : public adaptive::AdaptiveTree

uint64_t m_minimumUpdatePeriod{0}; // in seconds
bool m_allowInsertLiveSegments{false};
// Determines if a custom PSSH initialization license data is provided
bool m_isCustomInitPssh{false};
};
} // namespace adaptive
Loading

0 comments on commit 9d62b82

Please sign in to comment.