From 806c6c169b7659808fb653e663f665caec7dc201 Mon Sep 17 00:00:00 2001 From: CastagnaIT Date: Sat, 10 Jul 2021 09:22:48 +0200 Subject: [PATCH] Add support to pre initialize the DRM Allow add-ons to make licensed manifest request via proxy callback --- inputstream.adaptive/addon.xml.in | 2 +- src/SSD_dll.h | 5 +- src/common/AdaptiveTree.h | 3 +- src/main.cpp | 137 +++++++++++++++++++++-- src/main.h | 5 +- src/parser/DASHTree.cpp | 8 +- src/parser/DASHTree.h | 1 + src/parser/HLSTree.cpp | 8 +- src/parser/HLSTree.h | 1 + src/parser/SmoothTree.cpp | 8 +- src/parser/SmoothTree.h | 1 + wvdecrypter/cdm/media/cdm/cdm_adapter.cc | 9 +- wvdecrypter/cdm/media/cdm/cdm_adapter.h | 4 +- wvdecrypter/wvdecrypter.cpp | 32 +++++- wvdecrypter/wvdecrypter_android_jni.cpp | 59 +++++++--- 15 files changed, 241 insertions(+), 42 deletions(-) diff --git a/inputstream.adaptive/addon.xml.in b/inputstream.adaptive/addon.xml.in index 3f1dad145..e0bdb8a8c 100644 --- a/inputstream.adaptive/addon.xml.in +++ b/inputstream.adaptive/addon.xml.in @@ -10,7 +10,7 @@ name="adaptive" extension="" tags="true" - listitemprops="license_type|license_key|license_data|license_flags|manifest_type|server_certificate|stream_headers|manifest_update_parameter|original_audio_language|max_bandwidth|play_timeshift_buffer" + listitemprops="license_type|license_key|license_data|license_flags|manifest_type|server_certificate|stream_headers|manifest_update_parameter|original_audio_language|max_bandwidth|play_timeshift_buffer|pre_init_data" library_@PLATFORM@="@LIBRARY_FILENAME@"/> InputStream client for adaptive streams diff --git a/src/SSD_dll.h b/src/SSD_dll.h index ec3fa066b..979ce8ede 100644 --- a/src/SSD_dll.h +++ b/src/SSD_dll.h @@ -21,7 +21,7 @@ namespace SSD { PROPERTY_HEADER }; - static const uint32_t version = 12; + static const uint32_t version = 13; #if defined(ANDROID) virtual void* GetJNIEnv() = 0; virtual int GetSDKVersion() = 0; @@ -182,12 +182,13 @@ namespace SSD // Return supported URN if type matches to capabilities, otherwise null virtual const char *SelectKeySytem(const char* keySystem) = 0; virtual bool OpenDRMSystem(const char *licenseURL, const AP4_DataBuffer &serverCertificate, const uint8_t config) = 0; - virtual AP4_CencSingleSampleDecrypter *CreateSingleSampleDecrypter(AP4_DataBuffer &pssh, const char *optionalKeyParameter, const uint8_t *defaultkeyid) = 0; + virtual AP4_CencSingleSampleDecrypter *CreateSingleSampleDecrypter(AP4_DataBuffer &pssh, const char *optionalKeyParameter, const uint8_t *defaultkeyid, bool skipSessionMessage) = 0; virtual void DestroySingleSampleDecrypter(AP4_CencSingleSampleDecrypter* decrypter) = 0; virtual void GetCapabilities(AP4_CencSingleSampleDecrypter* decrypter, const uint8_t *keyid, uint32_t media, SSD_DECRYPTER::SSD_CAPS &caps) = 0; virtual bool HasLicenseKey(AP4_CencSingleSampleDecrypter* decrypter, const uint8_t *keyid) = 0; virtual bool HasCdmSession() = 0; + virtual std::string GetChallengeB64Data(AP4_CencSingleSampleDecrypter* decrypter) = 0; virtual bool OpenVideoDecoder(AP4_CencSingleSampleDecrypter* decrypter, const SSD_VIDEOINITDATA *initData) = 0; virtual SSD_DECODE_RETVAL DecodeVideo(void* instance, SSD_SAMPLE *sample, SSD_PICTURE *picture) = 0; diff --git a/src/common/AdaptiveTree.h b/src/common/AdaptiveTree.h index 5e9cd14d7..49ec3dd61 100644 --- a/src/common/AdaptiveTree.h +++ b/src/common/AdaptiveTree.h @@ -454,7 +454,8 @@ class ATTRIBUTE_HIDDEN AdaptiveTree AdaptiveTree(); virtual ~AdaptiveTree(); - virtual bool open(const std::string &url, const std::string &manifestUpdateParam) = 0; + virtual bool open(const std::string& url, const std::string& manifestUpdateParam) = 0; + virtual bool open(const std::string& url, const std::string& manifestUpdateParam, std::map additionalHeaders) = 0; virtual PREPARE_RESULT prepareRepresentation(Period* period, AdaptationSet* adp, Representation* rep, diff --git a/src/main.cpp b/src/main.cpp index b77673e2d..b0d7e579d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1982,7 +1982,8 @@ Session::Session(MANIFEST_TYPE manifestType, uint16_t display_height, const std::string& ov_audio, bool play_timeshift_buffer, - bool force_secure_decoder) + bool force_secure_decoder, + const std::string& drmPreInitData) : manifest_type_(manifestType), manifestURL_(strURL), manifestUpdateParam_(strUpdateParam), @@ -2005,7 +2006,8 @@ Session::Session(MANIFEST_TYPE manifestType, chapter_start_time_(0), chapter_seek_time_(0.0), play_timeshift_buffer_(play_timeshift_buffer), - force_secure_decoder_(force_secure_decoder) + force_secure_decoder_(force_secure_decoder), + drmPreInitData_(drmPreInitData) { switch (manifest_type_) { @@ -2222,10 +2224,31 @@ bool Session::Initialize(const std::uint8_t config, uint32_t max_user_bandwidth) kodi::Log(ADDON_LOG_DEBUG, "Supported URN: %s", adaptiveTree_->supportedKeySystem_.c_str()); } + // Preinitialize the DRM, if pre-initialisation data are provided + std::map additionalHeaders = std::map(); + + if (!drmPreInitData_.empty()) + { + std::string challengeB64; + std::string sessionId; + // Pre-initialize the DRM allow to generate the challenge and session ID data + // used to make licensed manifest requests (via proxy callback) + if (PreInitializeDRM(challengeB64, sessionId)) + { + additionalHeaders["challengeB64"] = challengeB64; + additionalHeaders["sessionId"] = sessionId; + } + else + { + kodi::Log(ADDON_LOG_ERROR, "%s - DRM pre-initialization failed", __FUNCTION__); + return false; + } + } + // Open manifest file with location redirect support bool mpdSuccess; std::string manifestUrl = adaptiveTree_->location_.empty() ? manifestURL_.c_str() : adaptiveTree_->location_; - if (!adaptiveTree_->open(manifestUrl.c_str(), manifestUpdateParam_.c_str()) || adaptiveTree_->empty()) + if (!adaptiveTree_->open(manifestUrl.c_str(), manifestUpdateParam_.c_str(), additionalHeaders) || adaptiveTree_->empty()) { kodi::Log(ADDON_LOG_ERROR, "Could not open / parse manifest (%s)", manifestUrl.c_str()); return false; @@ -2242,6 +2265,91 @@ bool Session::Initialize(const std::uint8_t config, uint32_t max_user_bandwidth) return InitializePeriod(); } +bool Session::PreInitializeDRM(std::string& challengeB64, std::string& sessionId) +{ + std::string psshData; + std::string kidData; + // Parse the PSSH/KID data + std::string::size_type posSplitter(drmPreInitData_.find("|")); + if (posSplitter != std::string::npos) + { + psshData = drmPreInitData_.substr(0, posSplitter); + kidData = drmPreInitData_.substr(posSplitter + 1); + } + + if (psshData.empty() || kidData.empty()) + { + kodi::Log(ADDON_LOG_ERROR, "%s - Invalid DRM pre-init data, must be as: {PSSH as base64}|{KID as base64}", __FUNCTION__); + return false; + } + + cdm_sessions_.resize(2); + memset(&cdm_sessions_.front(), 0, sizeof(CDMSESSION)); + // Try to initialize an SingleSampleDecryptor + kodi::Log(ADDON_LOG_DEBUG, "%s - Entering encryption section", __FUNCTION__); + + if (license_key_.empty()) + { + kodi::Log(ADDON_LOG_ERROR, "%s - Invalid license_key", __FUNCTION__); + return false; + } + + if (!decrypter_) + { + kodi::Log(ADDON_LOG_ERROR, "%s - No decrypter found for encrypted stream", __FUNCTION__); + return false; + } + + if (!decrypter_->HasCdmSession()) + { + if (!decrypter_->OpenDRMSystem(license_key_.c_str(), server_certificate_, drmConfig_)) + { + kodi::Log(ADDON_LOG_ERROR, "%s - OpenDRMSystem failed", __FUNCTION__); + return false; + } + } + + AP4_DataBuffer init_data; + const char* optionalKeyParameter(nullptr); + + // Set the provided PSSH + init_data.SetBufferSize(1024); + unsigned int init_data_size(1024); + + b64_decode(psshData.c_str(), psshData.size(), init_data.UseData(), init_data_size); + init_data.SetDataSize(init_data_size); + + // Decode the provided KID + uint8_t buffer[32]; + unsigned int buffer_size(32); + b64_decode(kidData.c_str(), kidData.size(), buffer, buffer_size); + const char* decodedKid = reinterpret_cast(buffer); + + CDMSESSION& session(cdm_sessions_[1]); + + char hexkid[36]; + AP4_FormatHex(reinterpret_cast(decodedKid), 16, hexkid), hexkid[32] = 0; + kodi::Log(ADDON_LOG_DEBUG, "%s - Initializing session with KID: %s", __FUNCTION__, hexkid); + + if (decrypter_ && init_data.GetDataSize() >= 4 && + (session.single_sample_decryptor_ = decrypter_->CreateSingleSampleDecrypter( + init_data, optionalKeyParameter, (const uint8_t*)decodedKid, true)) != 0) + { + session.cdm_session_str_ = session.single_sample_decryptor_->GetSessionId(); + sessionId = session.cdm_session_str_; + challengeB64 = decrypter_->GetChallengeB64Data(session.single_sample_decryptor_); + } + else + { + kodi::Log(ADDON_LOG_ERROR, "%s - Initialize failed (SingleSampleDecrypter)", __FUNCTION__); + cdm_sessions_[1].single_sample_decryptor_ = nullptr; + return false; + } + + DisposeSampleDecrypter(); + return true; +} + bool Session::InitializeDRM() { cdm_sessions_.resize(adaptiveTree_->current_period_->psshSets_.size()); @@ -2459,7 +2567,7 @@ bool Session::InitializeDRM() if (decrypter_ && init_data.GetDataSize() >= 4 && (session.single_sample_decryptor_ || (session.single_sample_decryptor_ = decrypter_->CreateSingleSampleDecrypter( - init_data, optionalKeyParameter, (const uint8_t*)defkid)) != 0)) + init_data, optionalKeyParameter, (const uint8_t*)defkid, false)) != 0)) { decrypter_->GetCapabilities(session.single_sample_decryptor_, (const uint8_t*)defkid, @@ -3325,7 +3433,7 @@ bool CInputStreamAdaptive::Open(const kodi::addon::InputstreamProperty& props) { kodi::Log(ADDON_LOG_DEBUG, "Open()"); - std::string lt, lk, ld, lsc, mfup, ov_audio; + std::string lt, lk, ld, lsc, mfup, ov_audio, drmPreInitData; std::map manh, medh; std::string url = props.GetURL(); MANIFEST_TYPE manifest(MANIFEST_TYPE_UNKNOWN); @@ -3401,7 +3509,19 @@ bool CInputStreamAdaptive::Open(const kodi::addon::InputstreamProperty& props) max_user_bandwidth); } else if (prop.first == "inputstream.adaptive.play_timeshift_buffer") + { m_playTimeshiftBuffer = stricmp(prop.second.c_str(), "true") == 0; + } + else if (prop.first == "inputstream.adaptive.pre_init_data") + { + // This property allow to "pre-initialize" the DRM with a PSSH/KID, + // the property value must be as "{PSSH as base64}|{KID as base64}". + // The challenge/session ID data generated by the initialisation of the DRM + // will be attached to the manifest request callback + // as HTTP headers with the names of "challengeB64" and "sessionId". + kodi::Log(ADDON_LOG_DEBUG, "found inputstream.adaptive.pre_init_data: [not shown]"); + drmPreInitData = prop.second; + } } if (manifest == MANIFEST_TYPE_UNKNOWN) @@ -3423,10 +3543,9 @@ bool CInputStreamAdaptive::Open(const kodi::addon::InputstreamProperty& props) kodihost->SetProfilePath(props.GetProfileFolder()); - m_session = std::shared_ptr(new Session(manifest, url.c_str(), mfup, lt, lk, ld, lsc, - manh, medh, props.GetProfileFolder(), - m_width, m_height, ov_audio, - m_playTimeshiftBuffer, force_secure_decoder)); + m_session = std::shared_ptr(new Session( + manifest, url.c_str(), mfup, lt, lk, ld, lsc, manh, medh, props.GetProfileFolder(), m_width, + m_height, ov_audio, m_playTimeshiftBuffer, force_secure_decoder, drmPreInitData)); m_session->SetVideoResolution(m_width, m_height); if (!m_session->Initialize(config, max_user_bandwidth)) diff --git a/src/main.h b/src/main.h index 2141736de..bc964956e 100644 --- a/src/main.h +++ b/src/main.h @@ -94,9 +94,11 @@ class ATTRIBUTE_HIDDEN Session : public adaptive::AdaptiveStreamObserver uint16_t display_height, const std::string& ov_audio, bool play_timeshift_buffer, - bool force_secure_decoder); + bool force_secure_decoder, + const std::string& drm_preinit_data); virtual ~Session(); bool Initialize(const std::uint8_t config, uint32_t max_user_bandwidth); + bool PreInitializeDRM(std::string& challengeB64, std::string& sessionId); bool InitializeDRM(); bool InitializePeriod(); SampleReader *GetNextSample(); @@ -177,6 +179,7 @@ class ATTRIBUTE_HIDDEN Session : public adaptive::AdaptiveStreamObserver MANIFEST_TYPE manifest_type_; std::string manifestURL_, manifestUpdateParam_; std::string license_key_, license_type_, license_data_; + std::string drmPreInitData_; std::map media_headers_; AP4_DataBuffer server_certificate_; std::string profile_path_; diff --git a/src/parser/DASHTree.cpp b/src/parser/DASHTree.cpp index f2963c415..6a5970b8d 100644 --- a/src/parser/DASHTree.cpp +++ b/src/parser/DASHTree.cpp @@ -1557,6 +1557,11 @@ static void XMLCALL end(void* data, const char* el) | DASHTree +---------------------------------------------------------------------*/ bool DASHTree::open(const std::string& url, const std::string& manifestUpdateParam) +{ + return open(url, manifestUpdateParam, std::map()); +} + +bool DASHTree::open(const std::string& url, const std::string& manifestUpdateParam, std::map additionalHeaders) { parser_ = XML_ParserCreate(NULL); if (!parser_) @@ -1569,7 +1574,8 @@ bool DASHTree::open(const std::string& url, const std::string& manifestUpdatePar strXMLText_.clear(); PrepareManifestUrl(url, manifestUpdateParam); - bool ret = download(manifest_url_.c_str(), manifest_headers_) && !periods_.empty(); + additionalHeaders.insert(manifest_headers_.begin(), manifest_headers_.end()); + bool ret = download(manifest_url_.c_str(), additionalHeaders) && !periods_.empty(); XML_ParserFree(parser_); parser_ = 0; diff --git a/src/parser/DASHTree.h b/src/parser/DASHTree.h index 0d03e7f12..64f4827a0 100644 --- a/src/parser/DASHTree.h +++ b/src/parser/DASHTree.h @@ -30,6 +30,7 @@ class ATTRIBUTE_HIDDEN DASHTree : public AdaptiveTree public: DASHTree(); virtual bool open(const std::string& url, const std::string& manifestUpdateParam) override; + virtual bool open(const std::string& url, const std::string& manifestUpdateParam, std::map additionalHeaders) override; virtual bool write_data(void* buffer, size_t buffer_size, void* opaque) override; virtual void RefreshSegments(Period* period, AdaptationSet* adp, diff --git a/src/parser/HLSTree.cpp b/src/parser/HLSTree.cpp index 14419cb5b..20a2d0627 100644 --- a/src/parser/HLSTree.cpp +++ b/src/parser/HLSTree.cpp @@ -157,9 +157,15 @@ int HLSTree::processEncryption(std::string baseUrl, std::map()); +} + +bool HLSTree::open(const std::string& url, const std::string& manifestUpdateParam, std::map additionalHeaders) { PrepareManifestUrl(url, manifestUpdateParam); - if (download(manifest_url_.c_str(), manifest_headers_, &manifest_stream)) + additionalHeaders.insert(manifest_headers_.begin(), manifest_headers_.end()); + if (download(manifest_url_.c_str(), additionalHeaders, &manifest_stream)) return processManifest(manifest_stream); return false; } diff --git a/src/parser/HLSTree.h b/src/parser/HLSTree.h index e2f245d0c..1e5fe28a9 100644 --- a/src/parser/HLSTree.h +++ b/src/parser/HLSTree.h @@ -45,6 +45,7 @@ class ATTRIBUTE_HIDDEN HLSTree : public AdaptiveTree virtual ~HLSTree(); virtual bool open(const std::string& url, const std::string& manifestUpdateParam) override; + virtual bool open(const std::string& url, const std::string& manifestUpdateParam, std::map additionalHeaders) override; virtual PREPARE_RESULT prepareRepresentation(Period* period, AdaptationSet* adp, Representation* rep, diff --git a/src/parser/SmoothTree.cpp b/src/parser/SmoothTree.cpp index 374985967..3a9b45d2d 100644 --- a/src/parser/SmoothTree.cpp +++ b/src/parser/SmoothTree.cpp @@ -348,6 +348,11 @@ static void XMLCALL end(void* data, const char* el) +---------------------------------------------------------------------*/ bool SmoothTree::open(const std::string& url, const std::string& manifestUpdateParam) +{ + return open(url, manifestUpdateParam, std::map()); +} + +bool SmoothTree::open(const std::string& url, const std::string& manifestUpdateParam, std::map additionalHeaders) { parser_ = XML_ParserCreate(NULL); if (!parser_) @@ -359,7 +364,8 @@ bool SmoothTree::open(const std::string& url, const std::string& manifestUpdateP strXMLText_.clear(); PrepareManifestUrl(url, manifestUpdateParam); - bool ret = download(manifest_url_.c_str(), manifest_headers_); + additionalHeaders.insert(manifest_headers_.begin(), manifest_headers_.end()); + bool ret = download(manifest_url_.c_str(), additionalHeaders); XML_ParserFree(parser_); parser_ = 0; diff --git a/src/parser/SmoothTree.h b/src/parser/SmoothTree.h index 68ec34a28..bac0642d0 100644 --- a/src/parser/SmoothTree.h +++ b/src/parser/SmoothTree.h @@ -30,6 +30,7 @@ class ATTRIBUTE_HIDDEN SmoothTree : public AdaptiveTree public: SmoothTree(); virtual bool open(const std::string& url, const std::string& manifestUpdateParam) override; + virtual bool open(const std::string& url, const std::string& manifestUpdateParam, std::map additionalHeaders) override; virtual bool write_data(void* buffer, size_t buffer_size, void* opaque) override; enum diff --git a/wvdecrypter/cdm/media/cdm/cdm_adapter.cc b/wvdecrypter/cdm/media/cdm/cdm_adapter.cc index c48f2aade..796b890fd 100644 --- a/wvdecrypter/cdm/media/cdm/cdm_adapter.cc +++ b/wvdecrypter/cdm/media/cdm/cdm_adapter.cc @@ -328,9 +328,14 @@ void CdmAdapter::UpdateSession(uint32_t promise_id, response, response_size); } -void CdmAdapter::SetSessionActive() +void CdmAdapter::SetSessionActive(bool isActive) { - session_active_ = true; + session_active_ = isActive; +} + +bool CdmAdapter::IsSessionActive() +{ + return session_active_; } void CdmAdapter::CloseSession(uint32_t promise_id, diff --git a/wvdecrypter/cdm/media/cdm/cdm_adapter.h b/wvdecrypter/cdm/media/cdm/cdm_adapter.h index 89afef688..8c5793cf2 100644 --- a/wvdecrypter/cdm/media/cdm/cdm_adapter.h +++ b/wvdecrypter/cdm/media/cdm/cdm_adapter.h @@ -101,7 +101,9 @@ class CdmAdapter : public std::enable_shared_from_this const uint8_t* response, uint32_t response_size); - void SetSessionActive(); + void SetSessionActive(bool isActive); + + bool IsSessionActive(); void CloseSession(uint32_t promise_id, const char* session_id, diff --git a/wvdecrypter/wvdecrypter.cpp b/wvdecrypter/wvdecrypter.cpp index 08a3d30ad..c8445846d 100644 --- a/wvdecrypter/wvdecrypter.cpp +++ b/wvdecrypter/wvdecrypter.cpp @@ -169,13 +169,15 @@ class WV_CencSingleSampleDecrypter : public AP4_CencSingleSampleDecrypter { public: // methods - WV_CencSingleSampleDecrypter(WV_DRM &drm, AP4_DataBuffer &pssh, const uint8_t *defaultKeyId); + WV_CencSingleSampleDecrypter(WV_DRM &drm, AP4_DataBuffer &pssh, const uint8_t *defaultKeyId, bool skipSessionMessage); virtual ~WV_CencSingleSampleDecrypter(); void GetCapabilities(const uint8_t* key, uint32_t media, SSD_DECRYPTER::SSD_CAPS &caps); virtual const char *GetSessionId() override; void SetSessionActive(); void CloseSessionId(); + AP4_DataBuffer GetChallengeData(); + void SetSession(const char* session, uint32_t session_size, const uint8_t *data, size_t data_size) { std::lock_guard lock(renewal_lock_); @@ -403,7 +405,7 @@ void WV_DRM::OnCDMMessage(const char* session, uint32_t session_size, CDMADPMSG | WV_CencSingleSampleDecrypter::WV_CencSingleSampleDecrypter +---------------------------------------------------------------------*/ -WV_CencSingleSampleDecrypter::WV_CencSingleSampleDecrypter(WV_DRM &drm, AP4_DataBuffer &pssh, const uint8_t *defaultKeyId) +WV_CencSingleSampleDecrypter::WV_CencSingleSampleDecrypter(WV_DRM &drm, AP4_DataBuffer &pssh, const uint8_t *defaultKeyId, bool skipSessionMessage) : AP4_CencSingleSampleDecrypter(0) , drm_(drm) , pssh_(pssh) @@ -469,7 +471,7 @@ WV_CencSingleSampleDecrypter::WV_CencSingleSampleDecrypter(WV_DRM &drm, AP4_Data reinterpret_cast(pssh_.GetData()), pssh_.GetDataSize()); int retrycount=0; - while (session_.empty() && ++retrycount < 100) + while (!drm.GetCdmAdapter()->IsSessionActive() && ++retrycount < 100) std::this_thread::sleep_for(std::chrono::milliseconds(10)); if (session_.empty()) @@ -478,6 +480,9 @@ WV_CencSingleSampleDecrypter::WV_CencSingleSampleDecrypter(WV_DRM &drm, AP4_Data return; } + if (skipSessionMessage) + return; + while (challenge_.GetDataSize() > 0 && SendSessionMessage()); if (keys_.empty()) @@ -578,7 +583,7 @@ const char *WV_CencSingleSampleDecrypter::GetSessionId() void WV_CencSingleSampleDecrypter::SetSessionActive() { - drm_.GetCdmAdapter()->SetSessionActive(); + drm_.GetCdmAdapter()->SetSessionActive(true); } void WV_CencSingleSampleDecrypter::CloseSessionId() @@ -593,6 +598,11 @@ void WV_CencSingleSampleDecrypter::CloseSessionId() } } +AP4_DataBuffer WV_CencSingleSampleDecrypter::GetChallengeData() +{ + return challenge_; +} + void WV_CencSingleSampleDecrypter::CheckLicenseRenewal() { { @@ -1424,9 +1434,9 @@ class WVDecrypter : public SSD_DECRYPTER return cdmsession_->GetCdmAdapter() != nullptr; } - virtual AP4_CencSingleSampleDecrypter *CreateSingleSampleDecrypter(AP4_DataBuffer &pssh, const char *optionalKeyParameter, const uint8_t *defaultkeyid) override + virtual AP4_CencSingleSampleDecrypter *CreateSingleSampleDecrypter(AP4_DataBuffer &pssh, const char *optionalKeyParameter, const uint8_t *defaultkeyid, bool skipSessionMessage) override { - WV_CencSingleSampleDecrypter *decrypter = new WV_CencSingleSampleDecrypter(*cdmsession_, pssh, defaultkeyid); + WV_CencSingleSampleDecrypter *decrypter = new WV_CencSingleSampleDecrypter(*cdmsession_, pssh, defaultkeyid, skipSessionMessage); if (!decrypter->GetSessionId()) { delete decrypter; @@ -1468,6 +1478,16 @@ class WVDecrypter : public SSD_DECRYPTER return cdmsession_ != nullptr; } + virtual std::string GetChallengeB64Data(AP4_CencSingleSampleDecrypter* decrypter) override + { + if (!decrypter) + return nullptr; + + AP4_DataBuffer challengeData = static_cast(decrypter)->GetChallengeData(); + // Keep b64_encode urlEncode enabled otherwise the data will not be sent correctly in the HTTP header + return b64_encode(challengeData.GetData(), challengeData.GetDataSize(), true); + } + virtual bool OpenVideoDecoder(AP4_CencSingleSampleDecrypter* decrypter, const SSD_VIDEOINITDATA *initData) override { if (!decrypter || !initData) diff --git a/wvdecrypter/wvdecrypter_android_jni.cpp b/wvdecrypter/wvdecrypter_android_jni.cpp index 08d7e582e..53f70dc59 100644 --- a/wvdecrypter/wvdecrypter_android_jni.cpp +++ b/wvdecrypter/wvdecrypter_android_jni.cpp @@ -279,9 +279,10 @@ class WV_CencSingleSampleDecrypter : public AP4_CencSingleSampleDecrypter WV_CencSingleSampleDecrypter(WV_DRM &drm, AP4_DataBuffer &pssh, const char *optionalKeyParameter, const uint8_t* defaultKeyId); ~WV_CencSingleSampleDecrypter(); - bool StartSession() { return KeyUpdateRequest(true); }; + bool StartSession(bool skipSessionMessage) { return KeyUpdateRequest(true, skipSessionMessage); }; const std::vector &GetSessionIdRaw() { return session_id_; }; virtual const char *GetSessionId() override; + std::vector GetChallengeData(); virtual bool HasLicenseKey(const uint8_t *keyid); virtual AP4_Result SetFragmentInfo(AP4_UI32 pool_id, const AP4_UI08 *key, const AP4_UI08 nal_length_size, AP4_DataBuffer &annexb_sps_pps, AP4_UI32 flags)override; @@ -310,7 +311,8 @@ class WV_CencSingleSampleDecrypter : public AP4_CencSingleSampleDecrypter private: bool ProvisionRequest(); - bool KeyUpdateRequest(bool waitForKeys); + bool GetKeyRequest(std::vector& keyRequestData); + bool KeyUpdateRequest(bool waitForKeys, bool skipSessionMessage); bool SendSessionMessage(const std::vector &keyRequestData); WV_DRM &media_drm_; @@ -319,6 +321,7 @@ class WV_CencSingleSampleDecrypter : public AP4_CencSingleSampleDecrypter std::vector session_id_; std::vector keySetId_; + std::vector keyRequestData_; char session_id_char_[128]; bool provisionRequested, keyUpdateRequested; @@ -477,6 +480,11 @@ const char *WV_CencSingleSampleDecrypter::GetSessionId() return session_id_char_; } +std::vector WV_CencSingleSampleDecrypter::GetChallengeData() +{ + return keyRequestData_; +} + bool WV_CencSingleSampleDecrypter::HasLicenseKey(const uint8_t *keyid) { // We work with one session for all streams. @@ -550,12 +558,10 @@ bool WV_CencSingleSampleDecrypter::ProvisionRequest() return true; } -bool WV_CencSingleSampleDecrypter::KeyUpdateRequest(bool waitKeys) +bool WV_CencSingleSampleDecrypter::GetKeyRequest(std::vector& keyRequestData) { - keyUpdateRequested = false; - - jni::CJNIMediaDrmKeyRequest keyRequest = media_drm_.GetMediaDrm()->getKeyRequest(session_id_, pssh_, - "video/mp4", jni::CJNIMediaDrm::KEY_TYPE_STREAMING, optParams_); + jni::CJNIMediaDrmKeyRequest keyRequest = media_drm_.GetMediaDrm()->getKeyRequest( + session_id_, pssh_, "video/mp4", jni::CJNIMediaDrm::KEY_TYPE_STREAMING, optParams_); if (xbmc_jnienv()->ExceptionCheck()) { @@ -564,29 +570,40 @@ bool WV_CencSingleSampleDecrypter::KeyUpdateRequest(bool waitKeys) { Log(SSD_HOST::LL_INFO, "Key request not successful - trying provisioning"); provisionRequested = true; - return KeyUpdateRequest(waitKeys); + return GetKeyRequest(keyRequestData); } else Log(SSD_HOST::LL_ERROR, "Key request not successful"); return false; } + keyRequestData = keyRequest.getData(); + Log(SSD_HOST::LL_DEBUG, "Key request successful size: %lu", keyRequestData.size()); + return true; +} + +bool WV_CencSingleSampleDecrypter::KeyUpdateRequest(bool waitKeys, bool skipSessionMessage) +{ + if (!GetKeyRequest(keyRequestData_)) + return false; + pssh_.clear(); optParams_.clear(); - std::vector keyRequestData = keyRequest.getData(); - Log(SSD_HOST::LL_DEBUG, "Key request successful size: %lu", keyRequestData.size()); + if (skipSessionMessage) + return true; - if (!SendSessionMessage(keyRequestData)) + keyUpdateRequested = false; + if (!SendSessionMessage(keyRequestData_)) return false; - if (waitKeys && keyRequestData.size() == 2) // Service Certificate call + if (waitKeys && keyRequestData_.size() == 2) // Service Certificate call { for (unsigned int i(0); i < 100 && !keyUpdateRequested; ++i) std::this_thread::sleep_for(std::chrono::milliseconds(10)); if (keyUpdateRequested) - KeyUpdateRequest(false); + KeyUpdateRequest(false, false); else { Log(SSD_HOST::LL_ERROR, "Timeout waiting for EVENT_KEYS_REQUIRED!"); @@ -999,7 +1016,7 @@ AP4_Result WV_CencSingleSampleDecrypter::SetFragmentInfo(AP4_UI32 pool_id, const fragment_pool_[pool_id].decrypter_flags_ = flags; if (keyUpdateRequested) - KeyUpdateRequest(false); + KeyUpdateRequest(false, false); return AP4_SUCCESS; } @@ -1236,7 +1253,7 @@ class WVDecrypter : public SSD_DECRYPTER, public jni::CJNIMediaDrmOnEventListene return cdmsession_->GetMediaDrm(); } - virtual AP4_CencSingleSampleDecrypter *CreateSingleSampleDecrypter(AP4_DataBuffer &pssh, const char *optionalKeyParameter, const uint8_t *defaultkeyid) override + virtual AP4_CencSingleSampleDecrypter *CreateSingleSampleDecrypter(AP4_DataBuffer &pssh, const char *optionalKeyParameter, const uint8_t *defaultkeyid, bool skipSessionMessage) override { WV_CencSingleSampleDecrypter *decrypter = new WV_CencSingleSampleDecrypter(*cdmsession_, pssh, optionalKeyParameter, defaultkeyid); @@ -1245,7 +1262,7 @@ class WVDecrypter : public SSD_DECRYPTER, public jni::CJNIMediaDrmOnEventListene decrypterList.push_back(decrypter); } - if (!(*decrypter->GetSessionId() && decrypter->StartSession())) + if (!(*decrypter->GetSessionId() && decrypter->StartSession(skipSessionMessage))) { DestroySingleSampleDecrypter(decrypter); return nullptr; @@ -1282,6 +1299,16 @@ class WVDecrypter : public SSD_DECRYPTER, public jni::CJNIMediaDrmOnEventListene return false; } + virtual std::string GetChallengeB64Data(AP4_CencSingleSampleDecrypter* decrypter) override + { + if (!decrypter) + return nullptr; + + std::vector challengeData = static_cast(decrypter)->GetChallengeData(); + // Keep b64_encode urlEncode enabled otherwise the data will not be sent correctly in the HTTP header + return b64_encode(reinterpret_cast(challengeData.data()), challengeData.size(), true); + } + virtual bool HasCdmSession() { return cdmsession_ != nullptr;