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