Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Session] Rework of ExtractStreamProtectionData #1659

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 94 additions & 62 deletions src/Session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "common/Chooser.h"
#include "decrypters/DrmFactory.h"
#include "decrypters/Helpers.h"
#include "parser/PRProtectionParser.h"
#include "utils/Base64Utils.h"
#include "utils/CurlUtils.h"
#include "utils/StringUtils.h"
Expand Down Expand Up @@ -381,6 +382,11 @@ bool CSession::InitializeDRM(bool addDefaultKID /* = false */)
// cdmSession 0 is reserved for unencrypted streams
for (size_t ses{1}; ses < m_cdmSessions.size(); ++ses)
{
const CPeriod::PSSHSet& sessionPsshset = m_adaptiveTree->m_currentPeriod->GetPSSHSets()[ses];

if (sessionPsshset.adaptation_set_->GetStreamType() == StreamType::NOTYPE)
continue;

CCdmSession& session{m_cdmSessions[ses]};

// Check if the decrypter has been previously initialized, if so skip it,
Expand All @@ -392,15 +398,10 @@ bool CSession::InitializeDRM(bool addDefaultKID /* = false */)
if (session.m_cencSingleSampleDecrypter)
continue;

std::vector<uint8_t> initData;
std::vector<uint8_t> initData = sessionPsshset.pssh_;
std::string defaultKidStr = sessionPsshset.defaultKID_;
std::string drmOptionalKeyParam;

CPeriod::PSSHSet& sessionPsshset = m_adaptiveTree->m_currentPeriod->GetPSSHSets()[ses];

if (sessionPsshset.adaptation_set_->GetStreamType() == StreamType::NOTYPE)
continue;

const std::vector<uint8_t> defaultKid = DRM::ConvertKidStrToBytes(sessionPsshset.defaultKID_);
std::string_view licenseDataStr = CSrvBroker::GetKodiProps().GetLicenseData();

if (m_adaptiveTree->GetTreeType() == adaptive::TreeType::SMOOTH_STREAMING)
Expand All @@ -423,7 +424,7 @@ bool CSession::InitializeDRM(bool addDefaultKID /* = false */)
licenseData.empty() ? "" : "(with custom data)");

std::vector<uint8_t> wvPsshData;
if (DRM::MakeWidevinePsshData(defaultKid, licenseData, wvPsshData))
if (DRM::MakeWidevinePsshData(DRM::ConvertKidStrToBytes(defaultKidStr), licenseData, wvPsshData))
DRM::MakePssh(DRM::ID_WIDEVINE, wvPsshData, initData);
}
}
Expand Down Expand Up @@ -451,22 +452,34 @@ bool CSession::InitializeDRM(bool addDefaultKID /* = false */)
initData = BASE64::Decode(licenseDataStr);
}

if (initData.empty() && sessionPsshset.m_licenseUrl.empty())
if (!initData.empty() && defaultKidStr.empty())
{
if (!sessionPsshset.pssh_.empty())
CPsshParser parser;
if (parser.Parse(initData))
{
// Use the init data provided by manifest (e.g. PSSH)
initData = sessionPsshset.pssh_;
}
else if (licenseType != DRM::KS_CLEARKEY)
{
// Try extract the PSSH/KID from the stream
// only if clearkeys are not used (use case e.g. Widevine manifest tested with ClearKey DRM)
if (!ExtractStreamProtectionData(sessionPsshset, initData, m_adaptiveTree->m_supportedKeySystems))
LOG::Log(LOGERROR, "License data: Cannot extract PSSH/KID data from the stream");
if (!parser.GetData().empty())
{
auto data = DRM::GetKIDWidevinePsshData(parser.GetData());
if (!data.empty())
{
LOG::LogF(LOGDEBUG, "KID PARSED FROM PSSH");
defaultKidStr = STRING::ToHexadecimal(data);
}
}
}
}

//! @todo: as is implemented InitializeDRM will initialize all PSSHSet's also when are not used,
//! therefore ExtractStreamProtectionData can perform many (not needed) downloads of mp4 init files
if ((initData.empty() && licenseType != DRM::KS_CLEARKEY) || defaultKidStr.empty())
{
// Try extract the PSSH/KID from the stream
ExtractStreamProtectionData(sessionPsshset, defaultKidStr, initData,
m_adaptiveTree->m_supportedKeySystems);
}

const std::vector<uint8_t> defaultKid = DRM::ConvertKidStrToBytes(defaultKidStr);

if (addDefaultKID && ses == 1 && session.m_cencSingleSampleDecrypter)
{
// If the CDM has been pre-initialized, on non-android systems
Expand All @@ -478,7 +491,7 @@ bool CSession::InitializeDRM(bool addDefaultKID /* = false */)

if (m_decrypter && !defaultKid.empty())
{
LOG::Log(LOGDEBUG, "Initializing stream with KID: %s", sessionPsshset.defaultKID_.c_str());
LOG::Log(LOGDEBUG, "Initializing stream with KID: %s", defaultKidStr.c_str());

for (size_t i{1}; i < ses; ++i)
{
Expand Down Expand Up @@ -1457,13 +1470,18 @@ bool CSession::SeekChapter(int ch)
return false;
}

bool CSession::ExtractStreamProtectionData(PLAYLIST::CPeriod::PSSHSet& sessionPsshset,
void CSession::ExtractStreamProtectionData(const PLAYLIST::CPeriod::PSSHSet& psshSet,
std::string& defaultKid,
std::vector<uint8_t>& initData,
std::vector<std::string_view> keySystems)
const std::vector<std::string_view>& keySystems)
{
auto initialRepr = m_reprChooser->GetRepresentation(sessionPsshset.adaptation_set_);
auto initialRepr = m_reprChooser->GetRepresentation(psshSet.adaptation_set_);

CStream stream{m_adaptiveTree, sessionPsshset.adaptation_set_, initialRepr};
if (initialRepr->GetContainerType() != ContainerType::MP4)
return;

LOG::LogF(LOGDEBUG, "Open stream to extract protection data...");
CStream stream{m_adaptiveTree, psshSet.adaptation_set_, initialRepr};

stream.m_isEnabled = true;
stream.m_adStream.start_stream();
Expand All @@ -1475,61 +1493,75 @@ bool CSession::ExtractStreamProtectionData(PLAYLIST::CPeriod::PSSHSet& sessionPs
{
LOG::LogF(LOGERROR, "No MOOV atom in stream");
stream.Disable();
return false;
return;
}
AP4_Array<AP4_PsshAtom>& pssh{movie->GetPsshAtoms()};

for (std::string_view keySystem : keySystems)
AP4_Track* track =
movie->GetTrack(static_cast<AP4_Track::Type>(stream.m_adStream.GetTrackType()));

if (track) // Try extract the default KID from tenc / piff mp4 box
{
std::vector<uint8_t> systemIdBytes;
STRING::ToHexBytes(DRM::UrnToSystemId(keySystem), systemIdBytes);
AP4_ProtectedSampleDescription* protSampleDesc =
static_cast<AP4_ProtectedSampleDescription*>(track->GetSampleDescription(0));

for (unsigned int i = 0; initData.size() == 0 && i < pssh.ItemCount(); i++)
if (protSampleDesc)
{
if (std::memcmp(pssh[i].GetSystemId(), systemIdBytes.data(), 16) == 0)
AP4_ProtectionSchemeInfo* psi = protSampleDesc->GetSchemeInfo();
if (psi)
{
const AP4_DataBuffer& dataBuf = pssh[i].GetData();

initData.insert(initData.end(), dataBuf.GetData(), dataBuf.GetData() + dataBuf.GetDataSize());

if (sessionPsshset.defaultKID_.empty())
AP4_ContainerAtom* schi = protSampleDesc->GetSchemeInfo()->GetSchiAtom();
if (schi)
{
if (pssh[i].GetKid(0))
AP4_TencAtom* tenc =
AP4_DYNAMIC_CAST(AP4_TencAtom, schi->GetChild(AP4_ATOM_TYPE_TENC, 0));
if (tenc)
{
sessionPsshset.defaultKID_ = std::string((const char*)pssh[i].GetKid(0), 16);
defaultKid = STRING::ToHexadecimal(tenc->GetDefaultKid(), 16);
}
else if (AP4_Track* track = movie->GetTrack(
static_cast<AP4_Track::Type>(stream.m_adStream.GetTrackType())))
else
{
AP4_ProtectedSampleDescription* m_protectedDesc =
static_cast<AP4_ProtectedSampleDescription*>(track->GetSampleDescription(0));
AP4_ContainerAtom* schi;
if (m_protectedDesc->GetSchemeInfo() &&
(schi = m_protectedDesc->GetSchemeInfo()->GetSchiAtom()))
AP4_PiffTrackEncryptionAtom* piff =
AP4_DYNAMIC_CAST(AP4_PiffTrackEncryptionAtom,
schi->GetChild(AP4_UUID_PIFF_TRACK_ENCRYPTION_ATOM, 0));
if (piff)
{
AP4_TencAtom* tenc{
AP4_DYNAMIC_CAST(AP4_TencAtom, schi->GetChild(AP4_ATOM_TYPE_TENC, 0)) };
if (tenc)
{
sessionPsshset.defaultKID_ = STRING::ToHexadecimal(tenc->GetDefaultKid(), 16);
}
else
{
AP4_PiffTrackEncryptionAtom* piff{
AP4_DYNAMIC_CAST(AP4_PiffTrackEncryptionAtom,
schi->GetChild(AP4_UUID_PIFF_TRACK_ENCRYPTION_ATOM, 0)) };
if (piff)
{
sessionPsshset.defaultKID_ = STRING::ToHexadecimal(piff->GetDefaultKid(), 16);
}
}
defaultKid = STRING::ToHexadecimal(piff->GetDefaultKid(), 16);
}
}
}
}
}
}

if (initData.empty() || defaultKid.empty())
{
const std::vector<std::string> systemIds = DRM::UrnsToSystemIds(keySystems);
AP4_Array<AP4_PsshAtom>& pssh{movie->GetPsshAtoms()};

for (unsigned int i = 0; i < pssh.ItemCount(); ++i)
{
AP4_PsshAtom& psshAtom = pssh[i];

std::string systemId = STRING::ToHexadecimal(psshAtom.GetSystemId(), 16);

// Find supported system id
if (std::find(systemIds.cbegin(), systemIds.cend(), systemId) != systemIds.cend())
{
const AP4_DataBuffer& dataBuf = psshAtom.GetData();
const std::vector<uint8_t> psshData{dataBuf.GetData(),
dataBuf.GetData() + dataBuf.GetDataSize()};

DRM::MakePssh(psshAtom.GetSystemId(), psshData, initData);

if (psshAtom.GetKid(0))
{
defaultKid = STRING::ToHexadecimal(pssh[i].GetKid(0), 16);
}

break;
}
}
}

stream.Disable();
return !initData.empty();
}
5 changes: 3 additions & 2 deletions src/Session.h
Original file line number Diff line number Diff line change
Expand Up @@ -334,9 +334,10 @@ class ATTR_DLL_LOCAL CSession : public adaptive::AdaptiveStreamObserver
*/
void DisposeDecrypter();

bool ExtractStreamProtectionData(PLAYLIST::CPeriod::PSSHSet& sessionPsshset,
void ExtractStreamProtectionData(const PLAYLIST::CPeriod::PSSHSet& psshSet,
std::string& defaultKid,
std::vector<uint8_t>& initData,
std::vector<std::string_view> keySystems);
const std::vector<std::string_view>& keySystems);

private:
std::string m_manifestUrl;
Expand Down
54 changes: 54 additions & 0 deletions src/decrypters/Helpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,21 @@ void WriteProtobufVarint(std::vector<uint8_t>& data, int size)
} while (size > 0);
}

int ReadProtobufVarint(const std::vector<uint8_t>& data, size_t& offset)
{
int value = 0;
int shift = 0;
while (true)
{
uint8_t byte = data[offset++];
value |= (byte & 0x7F) << shift;
if (!(byte & 0x80))
break;
shift += 7;
}
return value;
}

/*!
* \brief Replace in a vector, a sequence of vector data with another one.
* \param data The data to be modified
Expand Down Expand Up @@ -128,6 +143,20 @@ std::string DRM::UrnToSystemId(std::string_view urn)
return sysId;
}

std::vector<std::string> DRM::UrnsToSystemIds(const std::vector<std::string_view>& urns)
{
std::vector<std::string> sids;

for (std::string_view urn : urns)
{
std::string sid = DRM::UrnToSystemId(urn);
if (!sid.empty())
sids.emplace_back(DRM::UrnToSystemId(urn));
}

return sids;
}

bool DRM::IsKeySystemSupported(std::string_view keySystem)
{
return keySystem == DRM::KS_NONE || keySystem == DRM::KS_WIDEVINE ||
Expand Down Expand Up @@ -215,6 +244,31 @@ bool DRM::IsValidPsshHeader(const std::vector<uint8_t>& pssh)
return pssh.size() >= 8 && std::equal(pssh.begin() + 4, pssh.begin() + 8, PSSHBOX_HEADER_PSSH);
}

std::vector<uint8_t> DRM::GetKIDWidevinePsshData(const std::vector<uint8_t>& wvPsshData)
{
size_t offset = 0;
while (offset < wvPsshData.size())
{
uint8_t tag = wvPsshData[offset++];
int fieldNumber = tag >> 3;
int wireType = tag & 0x07;

if (fieldNumber == 2 && wireType == 2) // "key_id" field, id: 2
{
int length = ReadProtobufVarint(wvPsshData, offset);
std::vector<uint8_t> kid(wvPsshData.begin() + offset, wvPsshData.begin() + offset + length);
return kid;
}
else // Skip field
{
int length = ReadProtobufVarint(wvPsshData, offset);
if (wireType != 0)
offset += length;
}
}
return {}; // Not found
}

bool DRM::MakeWidevinePsshData(const std::vector<uint8_t>& kid,
std::vector<uint8_t> contentIdData,
std::vector<uint8_t>& wvPsshData)
Expand Down
8 changes: 8 additions & 0 deletions src/decrypters/Helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ std::string GenerateUrlDomainHash(std::string_view url);
*/
std::string UrnToSystemId(std::string_view urn);

/*!
* \brief Convert a list of DRM URN's to System ID's.
* \param urn The URN
* \return The System ID's, failed conversions are not included.
*/
std::vector<std::string> UrnsToSystemIds(const std::vector<std::string_view>& urns);

/*!
* \brief Convert a hexdecimal KeyId of 32 chars to 16 bytes.
* \param kidStr The hexdecimal KeyId
Expand All @@ -74,6 +81,7 @@ std::vector<uint8_t> ConvertKidToUUIDVec(const std::vector<uint8_t>& kid);
* \param kid The PlayReady KeyId
* \return The Widevine KeyId, otherwise empty if fails.
*/
std::vector<uint8_t> GetKIDWidevinePsshData(const std::vector<uint8_t>& wvPsshData);
std::vector<uint8_t> ConvertPrKidtoWvKid(std::vector<uint8_t> kid);

bool IsValidPsshHeader(const std::vector<uint8_t>& pssh);
Expand Down
Loading