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 51 : 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 51 : const std::shared_ptr<IMainThreadFactory> &mainThreadFactory)
68 51 : : m_kKeySystem(keySystem), m_kKeySessionId(keySessionId), m_kSessionType(sessionType), m_mediaKeysClient(client),
69 51 : m_isSessionConstructed(false), m_isSessionClosed(false), m_licenseRequested(false), m_ongoingOcdmOperation(false),
70 153 : m_ocdmError(false)
71 : {
72 51 : RIALTO_SERVER_LOG_DEBUG("entry:");
73 :
74 51 : m_mainThread = mainThreadFactory->getMainThread();
75 51 : if (!m_mainThread)
76 : {
77 1 : throw std::runtime_error("Failed to get the main thread");
78 : }
79 50 : m_mainThreadClientId = m_mainThread->registerClient();
80 :
81 50 : m_ocdmSession = ocdmSystem.createSession(this);
82 50 : if (!m_ocdmSession)
83 : {
84 1 : throw std::runtime_error("Ocdm session could not be created");
85 : }
86 49 : RIALTO_SERVER_LOG_MIL("New OCDM session created");
87 67 : }
88 :
89 147 : MediaKeySession::~MediaKeySession()
90 : {
91 49 : RIALTO_SERVER_LOG_DEBUG("entry:");
92 :
93 49 : 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 49 : m_mainThread->unregisterClient(m_mainThreadClientId);
109 98 : }
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 3 : MediaKeyErrorStatus MediaKeySession::decrypt(GstBuffer *encrypted, GstCaps *caps)
242 : {
243 3 : initOcdmErrorChecking();
244 :
245 3 : MediaKeyErrorStatus status = m_ocdmSession->decryptBuffer(encrypted, caps);
246 3 : if (MediaKeyErrorStatus::OK != status)
247 : {
248 1 : RIALTO_SERVER_LOG_ERROR("Failed to decrypt buffer");
249 : }
250 :
251 3 : if ((checkForOcdmErrors("decrypt")) && (MediaKeyErrorStatus::OK == status))
252 : {
253 1 : status = MediaKeyErrorStatus::FAIL;
254 : }
255 :
256 3 : return status;
257 : }
258 :
259 19 : MediaKeyErrorStatus MediaKeySession::closeKeySession()
260 : {
261 19 : initOcdmErrorChecking();
262 :
263 : MediaKeyErrorStatus status;
264 19 : if (m_extendedInterfaceInUse)
265 : {
266 12 : if (MediaKeyErrorStatus::OK != m_ocdmSession->cancelChallengeData())
267 : {
268 1 : RIALTO_SERVER_LOG_WARN("Failed to cancel the challenge data for the key session");
269 : }
270 :
271 12 : if (MediaKeyErrorStatus::OK != m_ocdmSession->cleanDecryptContext())
272 : {
273 1 : RIALTO_SERVER_LOG_WARN("Failed to clean the decrypt context for the key session");
274 : }
275 12 : status = MediaKeyErrorStatus::OK;
276 12 : RIALTO_SERVER_LOG_MIL("OCDM session closed");
277 : }
278 : else
279 : {
280 7 : status = m_ocdmSession->close();
281 7 : if (MediaKeyErrorStatus::OK != status)
282 : {
283 1 : RIALTO_SERVER_LOG_ERROR("Failed to Close the key session");
284 : }
285 : }
286 19 : m_isSessionClosed = (MediaKeyErrorStatus::OK == status);
287 :
288 19 : if ((checkForOcdmErrors("closeKeySession")) && (MediaKeyErrorStatus::OK == status))
289 : {
290 1 : status = MediaKeyErrorStatus::FAIL;
291 : }
292 :
293 19 : return status;
294 : }
295 :
296 3 : MediaKeyErrorStatus MediaKeySession::removeKeySession()
297 : {
298 3 : initOcdmErrorChecking();
299 :
300 3 : MediaKeyErrorStatus status = m_ocdmSession->remove();
301 3 : if (MediaKeyErrorStatus::OK != status)
302 : {
303 1 : RIALTO_SERVER_LOG_ERROR("Failed to remove the key session");
304 : }
305 :
306 3 : if ((checkForOcdmErrors("removeKeySession")) && (MediaKeyErrorStatus::OK == status))
307 : {
308 1 : status = MediaKeyErrorStatus::FAIL;
309 : }
310 :
311 3 : return status;
312 : }
313 :
314 3 : MediaKeyErrorStatus MediaKeySession::getCdmKeySessionId(std::string &cdmKeySessionId)
315 : {
316 3 : initOcdmErrorChecking();
317 :
318 3 : MediaKeyErrorStatus status = m_ocdmSession->getCdmKeySessionId(cdmKeySessionId);
319 3 : if (MediaKeyErrorStatus::OK != status)
320 : {
321 1 : RIALTO_SERVER_LOG_ERROR("Failed to get cdm key session id");
322 : }
323 :
324 3 : if ((checkForOcdmErrors("getCdmKeySessionId")) && (MediaKeyErrorStatus::OK == status))
325 : {
326 1 : status = MediaKeyErrorStatus::FAIL;
327 : }
328 :
329 3 : return status;
330 : }
331 :
332 2 : bool MediaKeySession::containsKey(const std::vector<uint8_t> &keyId)
333 : {
334 2 : uint32_t result = m_ocdmSession->hasKeyId(keyId.data(), keyId.size());
335 :
336 2 : return static_cast<bool>(result);
337 : }
338 :
339 3 : MediaKeyErrorStatus MediaKeySession::setDrmHeader(const std::vector<uint8_t> &requestData)
340 : {
341 3 : initOcdmErrorChecking();
342 :
343 3 : if (!m_isSessionConstructed)
344 : {
345 0 : RIALTO_SERVER_LOG_INFO("Session not yet constructed, queueing drm header to be set after construction");
346 0 : m_extendedInterfaceInUse = true;
347 0 : m_queuedDrmHeader = requestData;
348 0 : return MediaKeyErrorStatus::OK;
349 : }
350 :
351 3 : MediaKeyErrorStatus status = m_ocdmSession->setDrmHeader(requestData.data(), requestData.size());
352 3 : if (MediaKeyErrorStatus::OK != status)
353 : {
354 1 : RIALTO_SERVER_LOG_ERROR("Failed to set drm header");
355 : }
356 :
357 3 : if ((checkForOcdmErrors("setDrmHeader")) && (MediaKeyErrorStatus::OK == status))
358 : {
359 1 : status = MediaKeyErrorStatus::FAIL;
360 : }
361 :
362 3 : return status;
363 : }
364 :
365 3 : MediaKeyErrorStatus MediaKeySession::getLastDrmError(uint32_t &errorCode)
366 : {
367 3 : initOcdmErrorChecking();
368 :
369 3 : MediaKeyErrorStatus status = m_ocdmSession->getLastDrmError(errorCode);
370 3 : if (MediaKeyErrorStatus::OK != status)
371 : {
372 1 : RIALTO_SERVER_LOG_ERROR("Failed to get last drm error");
373 : }
374 :
375 3 : if ((checkForOcdmErrors("getLastDrmError")) && (MediaKeyErrorStatus::OK == status))
376 : {
377 1 : status = MediaKeyErrorStatus::FAIL;
378 : }
379 :
380 3 : return status;
381 : }
382 :
383 7 : MediaKeyErrorStatus MediaKeySession::selectKeyId(const std::vector<uint8_t> &keyId)
384 : {
385 7 : if (m_selectedKeyId == keyId)
386 : {
387 1 : return MediaKeyErrorStatus::OK;
388 : }
389 :
390 6 : initOcdmErrorChecking();
391 :
392 6 : m_extendedInterfaceInUse = true;
393 6 : MediaKeyErrorStatus status = m_ocdmSession->selectKeyId(keyId.size(), keyId.data());
394 6 : if (MediaKeyErrorStatus::OK == status)
395 : {
396 4 : RIALTO_SERVER_LOG_INFO("New keyId selected successfully");
397 4 : m_selectedKeyId = keyId;
398 : }
399 :
400 6 : if ((checkForOcdmErrors("selectKeyId")) && (MediaKeyErrorStatus::OK == status))
401 : {
402 1 : status = MediaKeyErrorStatus::FAIL;
403 : }
404 :
405 6 : return status;
406 : }
407 :
408 12 : void MediaKeySession::onProcessChallenge(const char url[], const uint8_t challenge[], const uint16_t challengeLength)
409 : {
410 12 : std::string urlStr = url;
411 24 : std::vector<unsigned char> challengeVec = std::vector<unsigned char>{challenge, challenge + challengeLength};
412 12 : auto task = [&, urlStr = std::move(urlStr), challengeVec = std::move(challengeVec)]()
413 : {
414 12 : std::shared_ptr<IMediaKeysClient> client = m_mediaKeysClient.lock();
415 12 : if (client)
416 : {
417 12 : if (m_licenseRequested)
418 : {
419 11 : client->onLicenseRequest(m_kKeySessionId, challengeVec, urlStr);
420 11 : m_licenseRequested = false;
421 : }
422 : else
423 : {
424 1 : client->onLicenseRenewal(m_kKeySessionId, challengeVec);
425 : }
426 : }
427 24 : };
428 12 : m_mainThread->enqueueTask(m_mainThreadClientId, task);
429 : }
430 :
431 3 : void MediaKeySession::onKeyUpdated(const uint8_t keyId[], const uint8_t keyIdLength)
432 : {
433 6 : std::vector<unsigned char> keyIdVec = std::vector<unsigned char>{keyId, keyId + keyIdLength};
434 3 : auto task = [&, keyIdVec = std::move(keyIdVec)]()
435 : {
436 3 : std::shared_ptr<IMediaKeysClient> client = m_mediaKeysClient.lock();
437 3 : if (client)
438 : {
439 3 : KeyStatus status = m_ocdmSession->getStatus(&keyIdVec[0], keyIdVec.size());
440 3 : m_updatedKeyStatuses.push_back(std::make_pair(keyIdVec, status));
441 : }
442 6 : };
443 3 : m_mainThread->enqueueTask(m_mainThreadClientId, task);
444 : }
445 :
446 1 : void MediaKeySession::onAllKeysUpdated()
447 : {
448 1 : auto task = [&]()
449 : {
450 1 : std::shared_ptr<IMediaKeysClient> client = m_mediaKeysClient.lock();
451 1 : if (client)
452 : {
453 1 : client->onKeyStatusesChanged(m_kKeySessionId, m_updatedKeyStatuses);
454 1 : m_updatedKeyStatuses.clear();
455 : }
456 1 : };
457 1 : m_mainThread->enqueueTask(m_mainThreadClientId, task);
458 : }
459 :
460 10 : void MediaKeySession::onError(const char message[])
461 : {
462 10 : RIALTO_SERVER_LOG_ERROR("Ocdm returned error: %s", message);
463 :
464 10 : std::lock_guard<std::mutex> lock(m_ocdmErrorMutex);
465 10 : if (!m_ongoingOcdmOperation)
466 : {
467 0 : RIALTO_SERVER_LOG_WARN("Received an asycronous OCDM error, ignoring");
468 : }
469 : else
470 : {
471 10 : m_ocdmError = true;
472 : }
473 : }
474 :
475 65 : void MediaKeySession::initOcdmErrorChecking()
476 : {
477 65 : std::lock_guard<std::mutex> lock(m_ocdmErrorMutex);
478 65 : m_ongoingOcdmOperation = true;
479 65 : m_ocdmError = false;
480 : }
481 :
482 65 : bool MediaKeySession::checkForOcdmErrors(const char *operationStr)
483 : {
484 65 : bool error = false;
485 :
486 65 : std::lock_guard<std::mutex> lock(m_ocdmErrorMutex);
487 65 : if (m_ocdmError)
488 : {
489 10 : RIALTO_SERVER_LOG_ERROR("MediaKeySession received an onError callback, operation '%s' failed", operationStr);
490 10 : error = true;
491 : }
492 65 : m_ongoingOcdmOperation = false;
493 :
494 65 : return error;
495 : }
496 :
497 : }; // namespace firebolt::rialto::server
|