Line data Source code
1 : /*
2 : * If not stated otherwise in this file or this component's LICENSE file the
3 : * following copyright and licenses apply:
4 : *
5 : * Copyright 2022 Sky UK
6 : *
7 : * Licensed under the Apache License, Version 2.0 (the "License");
8 : * you may not use this file except in compliance with the License.
9 : * You may obtain a copy of the License at
10 : *
11 : * http://www.apache.org/licenses/LICENSE-2.0
12 : *
13 : * Unless required by applicable law or agreed to in writing, software
14 : * distributed under the License is distributed on an "AS IS" BASIS,
15 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 : * See the License for the specific language governing permissions and
17 : * limitations under the License.
18 : */
19 :
20 : #include <stdexcept>
21 : #include <utility>
22 :
23 : #include "MediaKeySession.h"
24 : #include "MediaKeysCommon.h"
25 : #include "RialtoServerLogging.h"
26 :
27 : namespace firebolt::rialto::server
28 : {
29 2 : std::shared_ptr<IMediaKeySessionFactory> IMediaKeySessionFactory::createFactory()
30 : {
31 2 : std::shared_ptr<IMediaKeySessionFactory> factory;
32 :
33 : try
34 : {
35 2 : factory = std::make_shared<MediaKeySessionFactory>();
36 : }
37 0 : catch (const std::exception &e)
38 : {
39 0 : RIALTO_SERVER_LOG_ERROR("Failed to create the media key session factory, reason: %s", e.what());
40 : }
41 :
42 2 : return factory;
43 : }
44 :
45 : std::unique_ptr<IMediaKeySession>
46 1 : MediaKeySessionFactory::createMediaKeySession(const std::string &keySystem, int32_t keySessionId,
47 : const firebolt::rialto::wrappers::IOcdmSystem &ocdmSystem,
48 : KeySessionType sessionType, std::weak_ptr<IMediaKeysClient> client) const
49 : {
50 1 : std::unique_ptr<IMediaKeySession> mediaKeys;
51 : try
52 : {
53 2 : mediaKeys = std::make_unique<server::MediaKeySession>(keySystem, keySessionId, ocdmSystem, sessionType, client,
54 3 : server::IMainThreadFactory::createFactory());
55 : }
56 0 : catch (const std::exception &e)
57 : {
58 0 : RIALTO_SERVER_LOG_ERROR("Failed to create the media key session, reason: %s", e.what());
59 : }
60 :
61 1 : return mediaKeys;
62 : }
63 :
64 55 : MediaKeySession::MediaKeySession(const std::string &keySystem, int32_t keySessionId,
65 : const firebolt::rialto::wrappers::IOcdmSystem &ocdmSystem, KeySessionType sessionType,
66 : std::weak_ptr<IMediaKeysClient> client,
67 55 : const std::shared_ptr<IMainThreadFactory> &mainThreadFactory)
68 55 : : m_kKeySystem(keySystem), m_kKeySessionId(keySessionId), m_kSessionType(sessionType), m_mediaKeysClient(client),
69 55 : m_isSessionConstructed(false), m_isSessionClosed(false), m_licenseRequested(false), m_ongoingOcdmOperation(false),
70 165 : m_ocdmError(false), m_decryptErrorLogged(false)
71 : {
72 55 : RIALTO_SERVER_LOG_DEBUG("entry:");
73 :
74 55 : m_mainThread = mainThreadFactory->getMainThread();
75 55 : if (!m_mainThread)
76 : {
77 1 : throw std::runtime_error("Failed to get the main thread");
78 : }
79 54 : m_mainThreadClientId = m_mainThread->registerClient();
80 :
81 54 : m_ocdmSession = ocdmSystem.createSession(this);
82 54 : if (!m_ocdmSession)
83 : {
84 1 : throw std::runtime_error("Ocdm session could not be created");
85 : }
86 53 : RIALTO_SERVER_LOG_MIL("New OCDM session created");
87 71 : }
88 :
89 159 : MediaKeySession::~MediaKeySession()
90 : {
91 53 : RIALTO_SERVER_LOG_DEBUG("entry:");
92 :
93 53 : if (m_isSessionConstructed)
94 : {
95 16 : if (!m_isSessionClosed)
96 : {
97 13 : if (MediaKeyErrorStatus::OK != closeKeySession())
98 : {
99 0 : RIALTO_SERVER_LOG_ERROR("Failed to close the key session");
100 : }
101 : }
102 16 : if (MediaKeyErrorStatus::OK != m_ocdmSession->destructSession())
103 : {
104 0 : RIALTO_SERVER_LOG_ERROR("Failed to destruct the key session");
105 : }
106 : }
107 :
108 53 : m_mainThread->unregisterClient(m_mainThreadClientId);
109 106 : }
110 :
111 19 : MediaKeyErrorStatus MediaKeySession::generateRequest(InitDataType initDataType, const std::vector<uint8_t> &initData,
112 : const LimitedDurationLicense &ldlState)
113 : {
114 19 : RIALTO_SERVER_LOG_DEBUG("entry:");
115 19 : if (LimitedDurationLicense::NOT_SPECIFIED != ldlState)
116 : {
117 12 : m_extendedInterfaceInUse = true;
118 : }
119 : else
120 : {
121 : // Set the request flag for the onLicenseRequest callback
122 7 : m_licenseRequested = true;
123 : }
124 :
125 19 : auto status = MediaKeyErrorStatus::OK;
126 :
127 : // Only construct session if it hasnt previously been constructed
128 19 : if (!m_isSessionConstructed)
129 : {
130 17 : initOcdmErrorChecking();
131 :
132 17 : status = m_ocdmSession->constructSession(m_kSessionType, initDataType, &initData[0], initData.size());
133 17 : if (MediaKeyErrorStatus::OK != status)
134 : {
135 1 : RIALTO_SERVER_LOG_ERROR("Failed to construct the key session");
136 1 : m_licenseRequested = false;
137 : }
138 : else
139 : {
140 16 : m_isSessionConstructed = true;
141 16 : if (!m_queuedDrmHeader.empty())
142 : {
143 0 : RIALTO_SERVER_LOG_DEBUG("Setting queued drm header after session construction");
144 0 : setDrmHeader(m_queuedDrmHeader);
145 0 : m_queuedDrmHeader.clear();
146 : }
147 : }
148 :
149 17 : if ((checkForOcdmErrors("generateRequest")) && (MediaKeyErrorStatus::OK == status))
150 : {
151 1 : status = MediaKeyErrorStatus::FAIL;
152 : }
153 : }
154 :
155 19 : if (m_isSessionConstructed && m_extendedInterfaceInUse)
156 : {
157 : // Ocdm-playready does not notify onProcessChallenge when complete.
158 : // Fetch the challenge manually.
159 12 : getChallenge(ldlState);
160 : }
161 :
162 19 : return status;
163 : }
164 :
165 12 : void MediaKeySession::getChallenge(const LimitedDurationLicense &ldlState)
166 : {
167 12 : RIALTO_SERVER_LOG_DEBUG("entry:");
168 12 : auto task = [&]()
169 : {
170 12 : const bool kIsLdl{LimitedDurationLicense::ENABLED == ldlState};
171 12 : uint32_t challengeSize = 0;
172 12 : MediaKeyErrorStatus status = m_ocdmSession->getChallengeData(kIsLdl, nullptr, &challengeSize);
173 12 : if (challengeSize == 0)
174 : {
175 1 : RIALTO_SERVER_LOG_ERROR("Failed to get the challenge data size, no onLicenseRequest will be generated");
176 2 : return;
177 : }
178 11 : std::vector<uint8_t> challenge(challengeSize, 0x00);
179 11 : status = m_ocdmSession->getChallengeData(kIsLdl, &challenge[0], &challengeSize);
180 11 : if (MediaKeyErrorStatus::OK != status)
181 : {
182 1 : RIALTO_SERVER_LOG_ERROR("Failed to get the challenge data, no onLicenseRequest will be generated");
183 1 : return;
184 : }
185 :
186 10 : std::string url;
187 10 : m_licenseRequested = true;
188 10 : onProcessChallenge(url.c_str(), &challenge[0], challengeSize);
189 11 : };
190 12 : m_mainThread->enqueueTask(m_mainThreadClientId, task);
191 : }
192 :
193 3 : MediaKeyErrorStatus MediaKeySession::loadSession()
194 : {
195 3 : initOcdmErrorChecking();
196 :
197 3 : MediaKeyErrorStatus status = m_ocdmSession->load();
198 3 : if (MediaKeyErrorStatus::OK != status)
199 : {
200 1 : RIALTO_SERVER_LOG_ERROR("Failed to load the key session");
201 : }
202 :
203 3 : if ((checkForOcdmErrors("loadSession")) && (MediaKeyErrorStatus::OK == status))
204 : {
205 1 : status = MediaKeyErrorStatus::FAIL;
206 : }
207 :
208 3 : return status;
209 : }
210 :
211 5 : MediaKeyErrorStatus MediaKeySession::updateSession(const std::vector<uint8_t> &responseData)
212 : {
213 5 : initOcdmErrorChecking();
214 :
215 : MediaKeyErrorStatus status;
216 5 : if (m_extendedInterfaceInUse)
217 : {
218 2 : status = m_ocdmSession->storeLicenseData(&responseData[0], responseData.size());
219 2 : if (MediaKeyErrorStatus::OK != status)
220 : {
221 1 : RIALTO_SERVER_LOG_ERROR("Failed to store the license data for the key session");
222 : }
223 : }
224 : else
225 : {
226 3 : status = m_ocdmSession->update(&responseData[0], responseData.size());
227 3 : if (MediaKeyErrorStatus::OK != status)
228 : {
229 1 : RIALTO_SERVER_LOG_ERROR("Failed to update the key session");
230 : }
231 : }
232 :
233 5 : if ((checkForOcdmErrors("updateSession")) && (MediaKeyErrorStatus::OK == status))
234 : {
235 1 : status = MediaKeyErrorStatus::FAIL;
236 : }
237 :
238 5 : return status;
239 : }
240 :
241 10 : MediaKeyErrorStatus MediaKeySession::decrypt(GstBuffer *encrypted, GstCaps *caps)
242 : {
243 10 : constexpr uint32_t kHdcpOutputProtectionFailure{4427};
244 :
245 10 : initOcdmErrorChecking();
246 :
247 10 : MediaKeyErrorStatus status = m_ocdmSession->decryptBuffer(encrypted, caps);
248 10 : if (MediaKeyErrorStatus::OUTPUT_RESTRICTED == status)
249 : {
250 1 : RIALTO_SERVER_LOG_WARN("Decrypt failed due to HDCP output protection");
251 : }
252 9 : else if (MediaKeyErrorStatus::OK != status)
253 : {
254 6 : uint32_t lastDrmError{0};
255 6 : m_ocdmSession->getLastDrmError(lastDrmError);
256 6 : if (lastDrmError == kHdcpOutputProtectionFailure)
257 : {
258 1 : RIALTO_SERVER_LOG_WARN("Decrypt failed due to HDCP output protection (DRM error %u)", lastDrmError);
259 1 : status = MediaKeyErrorStatus::OUTPUT_RESTRICTED;
260 : }
261 :
262 : // Log decrypt failures once and re-enable logging only after a successful decrypt.
263 6 : if ((MediaKeyErrorStatus::OUTPUT_RESTRICTED != status) && !m_decryptErrorLogged)
264 : {
265 4 : RIALTO_SERVER_LOG_ERROR("Failed to decrypt buffer");
266 4 : m_decryptErrorLogged = true;
267 : }
268 : }
269 : else
270 : {
271 3 : m_decryptErrorLogged = false;
272 : }
273 :
274 10 : if ((checkForOcdmErrors("decrypt")) && (MediaKeyErrorStatus::OK == status))
275 : {
276 1 : status = MediaKeyErrorStatus::FAIL;
277 : }
278 :
279 10 : return status;
280 : }
281 :
282 19 : MediaKeyErrorStatus MediaKeySession::closeKeySession()
283 : {
284 19 : initOcdmErrorChecking();
285 :
286 : MediaKeyErrorStatus status;
287 19 : if (m_extendedInterfaceInUse)
288 : {
289 12 : if (MediaKeyErrorStatus::OK != m_ocdmSession->cancelChallengeData())
290 : {
291 1 : RIALTO_SERVER_LOG_WARN("Failed to cancel the challenge data for the key session");
292 : }
293 :
294 12 : if (MediaKeyErrorStatus::OK != m_ocdmSession->cleanDecryptContext())
295 : {
296 1 : RIALTO_SERVER_LOG_WARN("Failed to clean the decrypt context for the key session");
297 : }
298 12 : status = MediaKeyErrorStatus::OK;
299 12 : RIALTO_SERVER_LOG_MIL("OCDM session closed");
300 : }
301 : else
302 : {
303 7 : status = m_ocdmSession->close();
304 7 : if (MediaKeyErrorStatus::OK != status)
305 : {
306 1 : RIALTO_SERVER_LOG_ERROR("Failed to Close the key session");
307 : }
308 : }
309 19 : m_isSessionClosed = (MediaKeyErrorStatus::OK == status);
310 :
311 19 : if ((checkForOcdmErrors("closeKeySession")) && (MediaKeyErrorStatus::OK == status))
312 : {
313 1 : status = MediaKeyErrorStatus::FAIL;
314 : }
315 :
316 19 : return status;
317 : }
318 :
319 3 : MediaKeyErrorStatus MediaKeySession::removeKeySession()
320 : {
321 3 : initOcdmErrorChecking();
322 :
323 3 : MediaKeyErrorStatus status = m_ocdmSession->remove();
324 3 : if (MediaKeyErrorStatus::OK != status)
325 : {
326 1 : RIALTO_SERVER_LOG_ERROR("Failed to remove the key session");
327 : }
328 :
329 3 : if ((checkForOcdmErrors("removeKeySession")) && (MediaKeyErrorStatus::OK == status))
330 : {
331 1 : status = MediaKeyErrorStatus::FAIL;
332 : }
333 :
334 3 : return status;
335 : }
336 :
337 3 : MediaKeyErrorStatus MediaKeySession::getCdmKeySessionId(std::string &cdmKeySessionId)
338 : {
339 3 : initOcdmErrorChecking();
340 :
341 3 : MediaKeyErrorStatus status = m_ocdmSession->getCdmKeySessionId(cdmKeySessionId);
342 3 : if (MediaKeyErrorStatus::OK != status)
343 : {
344 1 : RIALTO_SERVER_LOG_ERROR("Failed to get cdm key session id");
345 : }
346 :
347 3 : if ((checkForOcdmErrors("getCdmKeySessionId")) && (MediaKeyErrorStatus::OK == status))
348 : {
349 1 : status = MediaKeyErrorStatus::FAIL;
350 : }
351 :
352 3 : return status;
353 : }
354 :
355 2 : bool MediaKeySession::containsKey(const std::vector<uint8_t> &keyId)
356 : {
357 2 : uint32_t result = m_ocdmSession->hasKeyId(keyId.data(), keyId.size());
358 :
359 2 : return static_cast<bool>(result);
360 : }
361 :
362 3 : MediaKeyErrorStatus MediaKeySession::setDrmHeader(const std::vector<uint8_t> &requestData)
363 : {
364 3 : initOcdmErrorChecking();
365 :
366 3 : if (!m_isSessionConstructed)
367 : {
368 0 : RIALTO_SERVER_LOG_INFO("Session not yet constructed, queueing drm header to be set after construction");
369 0 : m_extendedInterfaceInUse = true;
370 0 : m_queuedDrmHeader = requestData;
371 0 : return MediaKeyErrorStatus::OK;
372 : }
373 :
374 3 : MediaKeyErrorStatus status = m_ocdmSession->setDrmHeader(requestData.data(), requestData.size());
375 3 : if (MediaKeyErrorStatus::OK != status)
376 : {
377 1 : RIALTO_SERVER_LOG_ERROR("Failed to set drm header");
378 : }
379 :
380 3 : if ((checkForOcdmErrors("setDrmHeader")) && (MediaKeyErrorStatus::OK == status))
381 : {
382 1 : status = MediaKeyErrorStatus::FAIL;
383 : }
384 :
385 3 : return status;
386 : }
387 :
388 3 : MediaKeyErrorStatus MediaKeySession::getLastDrmError(uint32_t &errorCode)
389 : {
390 3 : initOcdmErrorChecking();
391 :
392 3 : MediaKeyErrorStatus status = m_ocdmSession->getLastDrmError(errorCode);
393 3 : if (MediaKeyErrorStatus::OK != status)
394 : {
395 1 : RIALTO_SERVER_LOG_ERROR("Failed to get last drm error");
396 : }
397 :
398 3 : if ((checkForOcdmErrors("getLastDrmError")) && (MediaKeyErrorStatus::OK == status))
399 : {
400 1 : status = MediaKeyErrorStatus::FAIL;
401 : }
402 :
403 3 : return status;
404 : }
405 :
406 7 : MediaKeyErrorStatus MediaKeySession::selectKeyId(const std::vector<uint8_t> &keyId)
407 : {
408 7 : if (m_selectedKeyId == keyId)
409 : {
410 1 : return MediaKeyErrorStatus::OK;
411 : }
412 :
413 6 : initOcdmErrorChecking();
414 :
415 6 : m_extendedInterfaceInUse = true;
416 6 : MediaKeyErrorStatus status = m_ocdmSession->selectKeyId(keyId.size(), keyId.data());
417 6 : if (MediaKeyErrorStatus::OK == status)
418 : {
419 4 : RIALTO_SERVER_LOG_INFO("New keyId selected successfully");
420 4 : m_selectedKeyId = keyId;
421 : }
422 :
423 6 : if ((checkForOcdmErrors("selectKeyId")) && (MediaKeyErrorStatus::OK == status))
424 : {
425 1 : status = MediaKeyErrorStatus::FAIL;
426 : }
427 :
428 6 : return status;
429 : }
430 :
431 12 : void MediaKeySession::onProcessChallenge(const char url[], const uint8_t challenge[], const uint16_t challengeLength)
432 : {
433 12 : std::string urlStr = url;
434 24 : std::vector<unsigned char> challengeVec = std::vector<unsigned char>{challenge, challenge + challengeLength};
435 12 : auto task = [&, urlStr = std::move(urlStr), challengeVec = std::move(challengeVec)]()
436 : {
437 12 : std::shared_ptr<IMediaKeysClient> client = m_mediaKeysClient.lock();
438 12 : if (client)
439 : {
440 12 : if (m_licenseRequested)
441 : {
442 11 : client->onLicenseRequest(m_kKeySessionId, challengeVec, urlStr);
443 11 : m_licenseRequested = false;
444 : }
445 : else
446 : {
447 1 : client->onLicenseRenewal(m_kKeySessionId, challengeVec);
448 : }
449 : }
450 24 : };
451 12 : m_mainThread->enqueueTask(m_mainThreadClientId, task);
452 : }
453 :
454 3 : void MediaKeySession::onKeyUpdated(const uint8_t keyId[], const uint8_t keyIdLength)
455 : {
456 6 : std::vector<unsigned char> keyIdVec = std::vector<unsigned char>{keyId, keyId + keyIdLength};
457 3 : auto task = [&, keyIdVec = std::move(keyIdVec)]()
458 : {
459 3 : std::shared_ptr<IMediaKeysClient> client = m_mediaKeysClient.lock();
460 3 : if (client)
461 : {
462 3 : KeyStatus status = m_ocdmSession->getStatus(&keyIdVec[0], keyIdVec.size());
463 3 : m_updatedKeyStatuses.push_back(std::make_pair(keyIdVec, status));
464 : }
465 6 : };
466 3 : m_mainThread->enqueueTask(m_mainThreadClientId, task);
467 : }
468 :
469 1 : void MediaKeySession::onAllKeysUpdated()
470 : {
471 1 : auto task = [&]()
472 : {
473 1 : std::shared_ptr<IMediaKeysClient> client = m_mediaKeysClient.lock();
474 1 : if (client)
475 : {
476 1 : client->onKeyStatusesChanged(m_kKeySessionId, m_updatedKeyStatuses);
477 1 : m_updatedKeyStatuses.clear();
478 : }
479 1 : };
480 1 : m_mainThread->enqueueTask(m_mainThreadClientId, task);
481 : }
482 :
483 10 : void MediaKeySession::onError(const char message[])
484 : {
485 10 : RIALTO_SERVER_LOG_ERROR("Ocdm returned error: %s", message);
486 :
487 10 : std::lock_guard<std::mutex> lock(m_ocdmErrorMutex);
488 10 : if (!m_ongoingOcdmOperation)
489 : {
490 0 : RIALTO_SERVER_LOG_WARN("Received an asycronous OCDM error, ignoring");
491 : }
492 : else
493 : {
494 10 : m_ocdmError = true;
495 : }
496 : }
497 :
498 72 : void MediaKeySession::initOcdmErrorChecking()
499 : {
500 72 : std::lock_guard<std::mutex> lock(m_ocdmErrorMutex);
501 72 : m_ongoingOcdmOperation = true;
502 72 : m_ocdmError = false;
503 : }
504 :
505 72 : bool MediaKeySession::checkForOcdmErrors(const char *operationStr)
506 : {
507 72 : bool error = false;
508 :
509 72 : std::lock_guard<std::mutex> lock(m_ocdmErrorMutex);
510 72 : if (m_ocdmError)
511 : {
512 10 : RIALTO_SERVER_LOG_ERROR("MediaKeySession received an onError callback, operation '%s' failed", operationStr);
513 10 : error = true;
514 : }
515 72 : m_ongoingOcdmOperation = false;
516 :
517 72 : return error;
518 : }
519 :
520 : }; // namespace firebolt::rialto::server
|