Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion depends/android/libandroidjni/libandroidjni.sha256
Original file line number Diff line number Diff line change
@@ -1 +1 @@
44a65c4f936cfa6b4b0114eee7a809efeb26a3fa442f70e95c998183d252953c
d5f615e88af51b4aa8fd473aa0c33698656f4fc3732d6207e5251b48b0de46a0
2 changes: 1 addition & 1 deletion depends/android/libandroidjni/libandroidjni.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
libandroidjni https://github.com/xbmc/libandroidjni/archive/2f8346bbe794cc3bd41b4195a59f0470470aa52e.tar.gz
libandroidjni https://github.com/xbmc/libandroidjni/archive/f2e50b4bb1e529023b96ef1cf535e16ce5c4c082.tar.gz
2 changes: 0 additions & 2 deletions src/Session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,6 @@ SResult SESSION::CSession::Initialize(std::string manifestUrl)
auto& kodiProps = CSrvBroker::GetKodiProps();
std::map<std::string, std::string> manifestHeaders = kodiProps.GetManifestHeaders();

m_drmEngine.Initialize();

DRM::DRMSession session;
// Pre-initialize the DRM allow to generate the challenge and session ID data
// used to make licensed manifest requests
Expand Down
12 changes: 12 additions & 0 deletions src/common/AdaptiveDecrypter.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#include "decrypters/DrmEngineDefines.h"
#include "utils/CryptoUtils.h"
#include "utils/ResultType.h"

#include <cstdint>
#include <stdexcept>
Expand Down Expand Up @@ -59,6 +60,17 @@ class Adaptive_CencSingleSampleDecrypter : public AP4_CencSingleSampleDecrypter
virtual AP4_UI32 AddPool() { return 0; }
virtual void RemovePool(AP4_UI32 poolId) {}

/*
* \brief Create a DRM session.
* \param pssh[OPT] A PSSH to make the license request, its use depends on DRM.
* \param licenseUrl[OPT] A license URL or URI to make the license request, its use depends on DRM.
* \param skipSessionMessage[OPT] Skip the session message to avoid make a license request, by default should be set false.
* \return SResult::Ok on success, otherwise a SResult failed status.
*/
virtual SResult CreateSession(const std::vector<uint8_t>& pssh,
std::string_view licenseUrl,
bool skipSessionMessage) = 0;

/*!
* \brief The session ID, is mandatory to distinguish sessions.
*/
Expand Down
245 changes: 134 additions & 111 deletions src/decrypters/DrmEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,35 +46,6 @@ STREAM_CRYPTO_KEY_SYSTEM KSToCryptoKeySystem(std::string_view keySystem)
return STREAM_CRYPTO_KEY_SYSTEM_NONE;
}

SResult CreateDRM(std::string_view keySystem, std::shared_ptr<DRM::IDecrypter>& drm)
{
std::string decrypterPath = CSrvBroker::GetSettings().GetDecrypterPath();
if (decrypterPath.empty())
{
LOG::LogF(LOGERROR, "No decrypter path set in the add-on settings");
return SResult::Error(GUI::GetLocalizedString(30302));
}

drm = DRM::FACTORY::GetDecrypter(KSToCryptoKeySystem(keySystem));

if (!drm)
{
LOG::LogF(LOGERROR, "Unable to create the DRM decrypter");
return SResult::Error(GUI::GetLocalizedString(30303));
}

drm->SetLibraryPath(decrypterPath);

if (!drm->Initialize())
{
drm = nullptr;
LOG::LogF(LOGERROR, "Unable to initialize the DRM decrypter");
return SResult::Error(GUI::GetLocalizedString(30303));
}

return SResultCode::OK;
}

/*!
* \brief Get a DRMInfo by Key System
* \param drmInfos The manifest DRM info
Expand Down Expand Up @@ -128,58 +99,106 @@ bool GetCapabilities(const std::optional<bool> isForceSecureDecoder,
}
} // unnamed namespace

void DRM::CDRMEngine::Initialize()
bool DRM::CDRMEngine::Initialize()
{
// Build the list of the supported DRM's by KeySystem,
// the list depends on the operative system used,
// the order of addition also defines the DRM priority (always prefer real DRM's over ClearKey)
// in future could be improved to take in account also the DRM capability on the target system

// Widevine currently always preferred as first because on android can reach 4k on L1 devices
m_supportedKs.emplace_back(KS_WIDEVINE);
#if ANDROID
m_supportedKs.emplace_back(KS_PLAYREADY);
m_supportedKs.emplace_back(KS_WISEPLAY);
#endif
m_supportedKs.emplace_back(KS_CLEARKEY);
if (!m_drms.empty())
Comment thread
CastagnaIT marked this conversation as resolved.
return true; // assume as already initialized

// Sort key systems based on priorities
const auto& kodiProps = CSrvBroker::GetKodiProps();
if (m_status == EngineStatus::DRM_ERROR)
return false; // something wrong with a previous initialization

//! @todo: to test a way to initialize DRM when manifest is downloaded/parsed
//! in the hoping to have a more smoother playback transition from unencrypted->to->encrypted periods

// This is the list of keysystems supported by at least one DRM
// are ordered by priority where the lower index has higher priority
// by default ClearKey has the lowest priority since real DRMs should be preferred
std::vector<std::string_view> keySystemsPrio = {KS_WIDEVINE, KS_PLAYREADY, KS_WISEPLAY, KS_CLEARKEY};

const auto& kodiProps = CSrvBroker::GetKodiProps();
// Reorder the keysystems list by using the custom DRM configuration, if any
for (auto& [ks, cfg] : kodiProps.GetDrmConfigs())
{
if (cfg.priority.has_value() && *cfg.priority != 0)
{
auto it = std::find(m_supportedKs.begin(), m_supportedKs.end(), ks);
if (it != m_supportedKs.end())
auto it = std::find(keySystemsPrio.begin(), keySystemsPrio.end(), ks);
if (it != keySystemsPrio.end())
{
m_supportedKs.erase(it);
keySystemsPrio.erase(it);

size_t index = *cfg.priority - 1;
if (index >= m_supportedKs.size())
index = m_supportedKs.size() - 1;
if (index >= keySystemsPrio.size())
index = keySystemsPrio.size() - 1;

m_supportedKs.insert(m_supportedKs.begin() + index, ks);
keySystemsPrio.insert(keySystemsPrio.begin() + index, ks);
}
}
}

// Get all DRM supported by the platform in use to determine which keysystems are supported
std::vector<std::shared_ptr<DRM::IDecrypter>> drms = FACTORY::GetDecrypters();

std::string decrypterPath = CSrvBroker::GetSettings().GetDecrypterPath();
if (decrypterPath.empty())
{
LOG::LogF(LOGERROR,
"Cannot initialize DrmEngine, no decrypter path set in the add-on settings");
m_status = EngineStatus::DRM_ERROR;
return false;
}

// Initialize DRMs
for (auto it = drms.begin(); it != drms.end();)
{
(*it)->SetLibraryPath(decrypterPath);

if (!(*it)->Initialize()) // Failed to initialize DRM, delete it and go on
{
LOG::LogF(LOGERROR, "Unable to initialize %s DRM", (*it)->GetName().c_str());
it = drms.erase(it);
}
else
++it;
}

// Check what keysystems are supported by DRMs by priority order
// and so add the supported one to the DRM list
for (std::string_view ks : keySystemsPrio)
{
for (auto& drm : drms)
{
if (drm->IsKeySystemSupported(ks))
m_drms.emplace_back(ks, drm);
}
}

if (m_drms.empty())
{
LOG::LogF(LOGWARNING, "No DRM available");
return false;
}

return true;
}

bool DRM::CDRMEngine::PreInitializeDRM(DRMSession& session)
{
auto& kodiProps = CSrvBroker::GetKodiProps();

// Pre-initialize the DRM is available for Widevine only.
// Since the manifest will be downloaded later its assumed that
// the manifest support the DRM and that the priority is set to 1.
if (std::find(m_supportedKs.cbegin(), m_supportedKs.cend(), KS_WIDEVINE) == m_supportedKs.cend())
return false;

const auto propDrmCfg = kodiProps.GetDrmConfig(KS_WIDEVINE);

if (!propDrmCfg.priority.has_value() || propDrmCfg.priority != 1 || propDrmCfg.preInitData.empty())
return false;

if (!Initialize())
return false;

// Pre-initialize the DRM is available for Widevine only.
// Since the manifest will be downloaded later its assumed that
// the manifest support the DRM and that the priority is set to 1.
if (!HasKeySystemSupport(KS_WIDEVINE))
return false;

LOG::Log(LOGDEBUG, "Pre-initialize crypto session");
std::vector<uint8_t> initData;
std::vector<uint8_t> kidData;
Expand All @@ -201,40 +220,38 @@ bool DRM::CDRMEngine::PreInitializeDRM(DRMSession& session)

m_keySystem = KS_WIDEVINE;

std::shared_ptr<DRM::IDecrypter> drm;
SResult ret = CreateDRM(m_keySystem, drm);
if (ret.IsFailed())
std::shared_ptr<DRM::IDecrypter> drm = GetDrmInstance(m_keySystem);
if (!drm)
{
m_status = EngineStatus::DRM_ERROR;
LOG::LogF(LOGERROR, "%s", ret.Message().c_str());
GUI::ErrorDialog(ret.Message());
LOG::LogF(LOGERROR, "Cannot get the DRM instance for keysystem %s", m_keySystem.c_str());
GUI::ErrorDialog(GUI::GetLocalizedString(30303));
return false;
}

LOG::LogF(LOGDEBUG, "Initializing session with KID: %s", STRING::ToHexadecimal(kidData).c_str());

DRM::Config drmCfg = CreateDRMConfig(m_keySystem, kodiProps.GetDrmConfig(m_keySystem));

ret = drm->OpenDRMSystem(drmCfg);
if (ret.IsFailed())
auto dec = drm->CreateSingleSampleDecrypter(drmCfg, kidData, CryptoMode::AES_CTR);

if (!dec)
{
LOG::LogF(LOGERROR, "Failed to open the DRM");
m_status = EngineStatus::DRM_ERROR;
GUI::ErrorDialog(ret.Message());
LOG::Log(LOGERROR, "Failed to initialize the %s DRM decrypter", drm->GetName().c_str());
m_status = EngineStatus::DECRYPTER_ERROR;
return false;
}

LOG::LogF(LOGDEBUG, "Initializing session with KID: %s", STRING::ToHexadecimal(kidData).c_str());

auto dec = drm->CreateSingleSampleDecrypter(initData, kidData, "", true, CryptoMode::AES_CTR);
const SResult ret = dec->CreateSession(initData, "", true);

if (!dec)
if (ret.IsFailed())
{
LOG::LogF(LOGERROR, "Failed to initialize the decrypter");
m_status = EngineStatus::DECRYPTER_ERROR;
LOG::LogF(LOGERROR, "Failed to create the DRM session");
m_status = EngineStatus::DRM_ERROR;
GUI::ErrorDialog(ret.Message());
return false;
}

m_drms.emplace(m_keySystem, drm);

session.id = dec->GetSessionId();
session.challenge = drm->GetChallengeB64Data(dec);
session.drm = drm;
Expand Down Expand Up @@ -262,6 +279,9 @@ bool DRM::CDRMEngine::InitializeSession(std::vector<DRM::DRMInfo> drmInfos,
if (drmInfos.empty())
return false;

if (!Initialize())
return false;

LOG::Log(LOGDEBUG, "Initialize crypto session");

ConfigureClearKey(drmInfos);
Expand Down Expand Up @@ -345,24 +365,6 @@ bool DRM::CDRMEngine::InitializeSession(std::vector<DRM::DRMInfo> drmInfos,
ExtractStreamProtectionData(repr, adp, drmInfo);
}

// Create the DRM decrypter
if (!STRING::KeyExists(m_drms, m_keySystem))
{
//! @todo: to test a way to preinitialize DRM when manifest is downloaded/parsed
//! in the hoping to have a more smoother playback transition
//! this can be tested with multiperiods video where first period is unencrypted and second one DRM crypted
std::shared_ptr<DRM::IDecrypter> drm;
SResult ret = CreateDRM(m_keySystem, drm);
if (ret.IsFailed())
{
m_status = EngineStatus::DRM_ERROR;
LOG::LogF(LOGERROR, "%s", ret.Message().c_str());
GUI::ErrorDialog(ret.Message());
return false;
}
m_drms.emplace(m_keySystem, drm);
}

const std::vector<uint8_t> drmInfoKidBytes = DRM::ConvertKidStrToBytes(drmInfo.defaultKid);

if (m_isPreinitialized && m_sessions.size() == 1)
Expand Down Expand Up @@ -411,31 +413,43 @@ bool DRM::CDRMEngine::InitializeSession(std::vector<DRM::DRMInfo> drmInfos,
if (canCleanupSessions)
DeleteSessionsByType(mediaType);

std::shared_ptr<DRM::IDecrypter> drm = GetDrmInstance(m_keySystem);
if (!drm)
{
m_status = EngineStatus::DRM_ERROR;
LOG::LogF(LOGERROR, "Cannot get the DRM instance for keysystem %s", m_keySystem.c_str());
GUI::ErrorDialog(GUI::GetLocalizedString(30303));
return false;
}

DRMSession newSes;
newSes.drm = m_drms[m_keySystem];
newSes.drm = drm;
newSes.mediaType = mediaType;
newSes.kid = drmInfo.defaultKid;

if (!newSes.drm->IsInitialised())
{
DRM::Config drmCfg = DRM::CreateDRMConfig(m_keySystem, drmPropCfg);
const SResult ret = newSes.drm->OpenDRMSystem(drmCfg);
if (ret.IsFailed())
{
LOG::LogF(LOGERROR, "Failed to open the DRM");
m_status = EngineStatus::DRM_ERROR;
GUI::ErrorDialog(ret.Message());
return false;
}
}
DRM::Config drmCfg = DRM::CreateDRMConfig(m_keySystem, drmPropCfg);

newSes.decrypter = newSes.drm->CreateSingleSampleDecrypter(
drmInfo.initData, drmInfoKidBytes, drmInfo.licenseServerUri, false,
drmCfg, drmInfoKidBytes,
drmInfo.cryptoMode == CryptoMode::NONE ? CryptoMode::AES_CTR : drmInfo.cryptoMode);

if (!newSes.decrypter)
{
LOG::Log(LOGERROR, "Failed to initialize the decrypter");
m_status = EngineStatus::DECRYPTER_ERROR;
LOG::Log(LOGERROR, "Failed to initialize the %s DRM decrypter",
newSes.drm->GetName().c_str());
GUI::ErrorDialog(GUI::GetLocalizedString(30303));
return false;
}

const SResult ret =
newSes.decrypter->CreateSession(drmInfo.initData, drmInfo.licenseServerUri, false);

if (ret.IsFailed())
{
LOG::LogF(LOGERROR, "Failed to create the DRM session");
m_status = EngineStatus::DRM_ERROR;
GUI::ErrorDialog(ret.Message());
return false;
}

Expand Down Expand Up @@ -631,13 +645,14 @@ bool DRM::CDRMEngine::SelectDRM(std::vector<DRM::DRMInfo>& drmInfos)
// Iterate supported DRM Key System's to find a match with the drmInfo's,
// the supported DRM's are ordered by priority
// the lower index have the higher priority
for (const std::string& ks : m_supportedKs)
for (auto& drm : m_drms)
{
const DRM::DRMInfo* drmInfo = GetDRMInfoByKS(drmInfos, ks);
const DRM::DRMInfo* drmInfo = GetDRMInfoByKS(drmInfos, drm.keySystem);

if (drmInfo)
{
m_keySystem = ks;
m_keySystem = drm.keySystem;
LOG::LogF(LOGDEBUG, "Selected DRM key system: %s", m_keySystem.c_str());
break;
}
}
Expand Down Expand Up @@ -755,7 +770,15 @@ void DRM::CDRMEngine::DeleteSessionsByType(const DRMMediaType mediaType)

bool DRM::CDRMEngine::HasKeySystemSupport(std::string_view keySystem) const
{
return std::find(m_supportedKs.cbegin(), m_supportedKs.cend(), keySystem) != m_supportedKs.cend();
return std::any_of(m_drms.cbegin(), m_drms.cend(),
[&keySystem](const DRMInstance& a) { return a.keySystem == keySystem; });
}

std::shared_ptr<DRM::IDecrypter> DRM::CDRMEngine::GetDrmInstance(std::string_view ks) const
{
auto it = std::find_if(m_drms.cbegin(), m_drms.cend(),
[&ks](const DRMInstance& d) { return d.keySystem == ks; });
return it != m_drms.cend() ? it->drm : nullptr;
}

void DRM::CDRMEngine::Dispose()
Expand Down
Loading
Loading