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