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 "OpenCDMSessionPrivate.h"
21 : #include "RialtoGStreamerEMEProtectionMetadata.h"
22 : #include <gst/base/base.h>
23 : #include <gst/gst.h>
24 : #include <gst/gstprotection.h>
25 :
26 : namespace
27 : {
28 9 : const KeyStatus convertKeyStatus(const firebolt::rialto::KeyStatus &keyStatus)
29 : {
30 9 : switch (keyStatus)
31 : {
32 4 : case firebolt::rialto::KeyStatus::USABLE:
33 : {
34 4 : return Usable;
35 : }
36 1 : case firebolt::rialto::KeyStatus::EXPIRED:
37 : {
38 1 : return Expired;
39 : }
40 1 : case firebolt::rialto::KeyStatus::RELEASED:
41 : {
42 1 : return Released;
43 : }
44 1 : case firebolt::rialto::KeyStatus::OUTPUT_RESTRICTED:
45 : {
46 1 : return OutputRestricted;
47 : }
48 1 : case firebolt::rialto::KeyStatus::PENDING:
49 : {
50 1 : return StatusPending;
51 : }
52 1 : case firebolt::rialto::KeyStatus::INTERNAL_ERROR:
53 : default:
54 : {
55 1 : return InternalError;
56 : }
57 : }
58 : }
59 :
60 : const std::string kDefaultSessionId{"0"};
61 : } // namespace
62 :
63 65 : OpenCDMSessionPrivate::OpenCDMSessionPrivate(const std::shared_ptr<ICdmBackend> &cdm,
64 : const std::shared_ptr<IMessageDispatcher> &messageDispatcher,
65 : const LicenseType &sessionType, OpenCDMSessionCallbacks *callbacks,
66 : void *context, const std::string &initDataType,
67 65 : const std::vector<uint8_t> &initData)
68 130 : : m_log{"OpenCDMSessionPrivate"}, m_context(context), m_cdmBackend(cdm), m_messageDispatcher(messageDispatcher),
69 130 : m_rialtoSessionId(firebolt::rialto::kInvalidSessionId), m_callbacks(callbacks),
70 65 : m_sessionType(getRialtoSessionType(sessionType)), m_initDataType(getRialtoInitDataType(initDataType)),
71 260 : m_initData(initData), m_isInitialized{false}
72 : {
73 65 : m_log << debug << "constructed: " << static_cast<void *>(this);
74 : }
75 :
76 130 : OpenCDMSessionPrivate::~OpenCDMSessionPrivate()
77 : {
78 65 : m_log << debug << "destructed: " << static_cast<void *>(this);
79 65 : if (m_isInitialized)
80 : {
81 38 : releaseSession();
82 : }
83 130 : }
84 :
85 41 : bool OpenCDMSessionPrivate::initialize()
86 : {
87 41 : return initialize(false);
88 : }
89 :
90 41 : bool OpenCDMSessionPrivate::initialize(bool isLDL)
91 : {
92 41 : if (!m_cdmBackend || !m_messageDispatcher)
93 : {
94 1 : m_log << error << "Cdm/message dispatcher is NULL or not initialized";
95 1 : return false;
96 : }
97 40 : if (!m_isInitialized)
98 : {
99 39 : if (!m_cdmBackend->createKeySession(m_sessionType, isLDL, m_rialtoSessionId))
100 : {
101 1 : m_log << error << "Failed to create a session. Got drm error %u", getLastDrmError();
102 1 : return false;
103 : }
104 38 : m_messageDispatcherClient = m_messageDispatcher->createClient(this);
105 38 : m_isInitialized = true;
106 38 : m_log << info << "Successfully created a session";
107 :
108 38 : if (!m_queuedDrmHeader.empty())
109 : {
110 2 : m_log << info << "Setting queued DRM header.";
111 2 : if (!m_cdmBackend->setDrmHeader(m_rialtoSessionId, m_queuedDrmHeader))
112 : {
113 1 : m_log << error << "Failed to set queued DRM header.";
114 : }
115 2 : m_queuedDrmHeader.clear();
116 : }
117 : }
118 39 : return true;
119 : }
120 :
121 9 : bool OpenCDMSessionPrivate::generateRequest(const std::string &initDataType, const std::vector<uint8_t> &initData,
122 : const std::vector<uint8_t> &cdmData)
123 : {
124 9 : firebolt::rialto::InitDataType dataType = getRialtoInitDataType(initDataType);
125 9 : if (!m_cdmBackend)
126 : {
127 1 : m_log << error << "Cdm is NULL or not initialized";
128 1 : return false;
129 : }
130 :
131 8 : if ((dataType != firebolt::rialto::InitDataType::UNKNOWN) && (-1 != m_rialtoSessionId))
132 : {
133 6 : if (m_cdmBackend->generateRequest(m_rialtoSessionId, dataType, initData))
134 : {
135 5 : m_log << info << "Successfully generated the request for the session";
136 5 : initializeCdmKeySessionId();
137 5 : return true;
138 : }
139 : else
140 : {
141 1 : m_log << error << "Failed to request for the session. Got drm error " << getLastDrmError();
142 : }
143 : }
144 :
145 3 : return false;
146 : }
147 :
148 4 : bool OpenCDMSessionPrivate::loadSession()
149 : {
150 4 : if (!m_cdmBackend)
151 : {
152 1 : m_log << error << "Cdm is NULL or not initialized";
153 1 : return false;
154 : }
155 :
156 3 : if (-1 != m_rialtoSessionId)
157 : {
158 2 : if (m_cdmBackend->loadSession(m_rialtoSessionId))
159 : {
160 1 : m_log << info << "Successfully loaded the session";
161 1 : return true;
162 : }
163 : else
164 : {
165 1 : m_log << error << "Failed to load the session. Got drm error " << getLastDrmError();
166 : }
167 : }
168 2 : return false;
169 : }
170 :
171 4 : bool OpenCDMSessionPrivate::updateSession(const std::vector<uint8_t> &license)
172 : {
173 4 : if (!m_cdmBackend)
174 : {
175 1 : m_log << error << "Cdm is NULL or not initialized";
176 1 : return false;
177 : }
178 :
179 3 : if (-1 != m_rialtoSessionId)
180 : {
181 2 : if (m_cdmBackend->updateSession(m_rialtoSessionId, license))
182 : {
183 1 : m_log << info << "Successfully updated the session";
184 1 : return true;
185 : }
186 : else
187 : {
188 1 : m_log << error << "Failed to update the session. Got drm error " << getLastDrmError();
189 : }
190 : }
191 :
192 2 : return false;
193 : }
194 :
195 4 : bool OpenCDMSessionPrivate::getChallengeData(std::vector<uint8_t> &challengeData)
196 : {
197 4 : if (!m_cdmBackend)
198 : {
199 1 : m_log << error << "Cdm is NULL or not initialized";
200 1 : return false;
201 : }
202 3 : if ((m_initDataType != firebolt::rialto::InitDataType::UNKNOWN) && (-1 != m_rialtoSessionId))
203 : {
204 3 : if (m_cdmBackend->generateRequest(m_rialtoSessionId, m_initDataType, m_initData))
205 : {
206 1 : m_log << info << "Successfully generated the request for the session";
207 1 : initializeCdmKeySessionId();
208 : }
209 : else
210 : {
211 1 : m_log << error << "Failed to request for the session. Got drm error " << getLastDrmError();
212 1 : return false;
213 : }
214 : }
215 : else
216 : {
217 1 : return false;
218 : }
219 1 : std::unique_lock<std::mutex> lock{m_mutex};
220 2 : m_challengeCv.wait_for(lock, std::chrono::seconds{1}, [this]() { return !m_challengeData.empty(); });
221 1 : challengeData = m_challengeData;
222 1 : return !challengeData.empty();
223 : }
224 :
225 3 : void OpenCDMSessionPrivate::addProtectionMeta(GstBuffer *buffer, GstBuffer *subSample, const uint32_t subSampleCount,
226 : GstBuffer *IV, GstBuffer *keyID, uint32_t initWithLast15)
227 : {
228 : // Set key for Playready
229 3 : GstBuffer *keyToApply = keyID;
230 3 : bool shouldReleaseKey{false};
231 3 : if (keyID && 0 == gst_buffer_get_size(keyID) && !m_playreadyKeyId.empty())
232 : {
233 1 : keyToApply = gst_buffer_new_allocate(nullptr, m_playreadyKeyId.size(), nullptr);
234 1 : gst_buffer_fill(keyToApply, 0, m_playreadyKeyId.data(), m_playreadyKeyId.size());
235 1 : shouldReleaseKey = true;
236 : }
237 :
238 3 : GstStructure *info = gst_structure_new("application/x-cenc", "encrypted", G_TYPE_BOOLEAN, TRUE, "mks_id",
239 : G_TYPE_INT, m_rialtoSessionId, "kid", GST_TYPE_BUFFER, keyToApply, "iv_size",
240 : G_TYPE_UINT, gst_buffer_get_size(IV), "iv", GST_TYPE_BUFFER, IV,
241 : "subsample_count", G_TYPE_UINT, subSampleCount, "subsamples", GST_TYPE_BUFFER,
242 : subSample, "encryption_scheme", G_TYPE_UINT, 0, // AES Counter
243 : "init_with_last_15", G_TYPE_UINT, initWithLast15, NULL);
244 :
245 3 : GstProtectionMeta *protectionMeta = reinterpret_cast<GstProtectionMeta *>(gst_buffer_get_protection_meta(buffer));
246 3 : if (protectionMeta && protectionMeta->info)
247 : {
248 1 : const char *cipherModeBuf = gst_structure_get_string(protectionMeta->info, "cipher-mode");
249 1 : if (cipherModeBuf)
250 : {
251 1 : GST_INFO("Copy cipher mode [%s] and crypt/skipt byte blocks to protection metadata.", cipherModeBuf);
252 1 : gst_structure_set(info, "cipher-mode", G_TYPE_STRING, cipherModeBuf, NULL);
253 :
254 1 : uint32_t patternCryptoBlocks = 0;
255 1 : uint32_t patternClearBlocks = 0;
256 :
257 1 : if (gst_structure_get_uint(protectionMeta->info, "crypt_byte_block", &patternCryptoBlocks))
258 : {
259 1 : gst_structure_set(info, "crypt_byte_block", G_TYPE_UINT, patternCryptoBlocks, NULL);
260 : }
261 :
262 1 : if (gst_structure_get_uint(protectionMeta->info, "skip_byte_block", &patternClearBlocks))
263 : {
264 1 : gst_structure_set(info, "skip_byte_block", G_TYPE_UINT, patternClearBlocks, NULL);
265 : }
266 : }
267 : }
268 :
269 3 : rialto_mse_add_protection_metadata(buffer, info);
270 :
271 3 : if (shouldReleaseKey)
272 : {
273 1 : gst_buffer_unref(keyToApply);
274 : }
275 3 : }
276 :
277 3 : bool OpenCDMSessionPrivate::addProtectionMeta(GstBuffer *buffer)
278 : {
279 3 : GstProtectionMeta *protectionMeta = reinterpret_cast<GstProtectionMeta *>(gst_buffer_get_protection_meta(buffer));
280 3 : if (!protectionMeta)
281 : {
282 1 : m_log << debug << "No protection meta added to the buffer";
283 1 : return false;
284 : }
285 :
286 2 : GstStructure *info = gst_structure_copy(protectionMeta->info);
287 2 : gst_structure_set(info, "mks_id", G_TYPE_INT, m_rialtoSessionId, NULL);
288 :
289 2 : if (!gst_structure_has_field_typed(info, "encrypted", G_TYPE_BOOLEAN))
290 : {
291 : // Set encrypted
292 2 : gst_structure_set(info, "encrypted", G_TYPE_BOOLEAN, TRUE, NULL);
293 : }
294 :
295 4 : if (gst_structure_has_field_typed(info, "iv", GST_TYPE_BUFFER) &&
296 2 : !gst_structure_has_field_typed(info, "iv_size", G_TYPE_UINT))
297 : {
298 2 : const GValue *value = gst_structure_get_value(info, "iv");
299 2 : if (value)
300 : {
301 2 : GstBuffer *ivBuffer = gst_value_get_buffer(value);
302 : // Set iv size
303 2 : gst_structure_set(info, "iv_size", G_TYPE_UINT, gst_buffer_get_size(ivBuffer), NULL);
304 : }
305 : }
306 :
307 2 : if (!gst_structure_has_field_typed(info, "encryption_scheme", G_TYPE_UINT))
308 : {
309 : // Not used but required
310 2 : gst_structure_set(info, "encryption_scheme", G_TYPE_UINT, 0, NULL);
311 : }
312 :
313 : // Set key for Playready
314 2 : if (!m_playreadyKeyId.empty())
315 : {
316 1 : GstBuffer *keyID = gst_buffer_new_allocate(nullptr, m_playreadyKeyId.size(), nullptr);
317 1 : gst_buffer_fill(keyID, 0, m_playreadyKeyId.data(), m_playreadyKeyId.size());
318 1 : gst_structure_set(info, "kid", GST_TYPE_BUFFER, keyID, NULL);
319 1 : gst_buffer_unref(keyID);
320 : }
321 :
322 2 : rialto_mse_add_protection_metadata(buffer, info);
323 :
324 2 : return true;
325 : }
326 :
327 4 : bool OpenCDMSessionPrivate::closeSession()
328 : {
329 4 : if (!m_cdmBackend)
330 : {
331 1 : m_log << error << "Cdm is NULL or not initialized";
332 1 : return false;
333 : }
334 :
335 3 : if (-1 != m_rialtoSessionId)
336 : {
337 2 : if (m_cdmBackend->closeKeySession(m_rialtoSessionId))
338 : {
339 1 : m_log << info << "Successfully closed the session";
340 1 : m_messageDispatcherClient.reset();
341 1 : m_challengeData.clear();
342 1 : m_keyStatuses.clear();
343 1 : return true;
344 : }
345 : else
346 : {
347 1 : m_log << warn << "Failed to close the session.";
348 : }
349 : }
350 :
351 2 : return false;
352 : }
353 :
354 4 : bool OpenCDMSessionPrivate::removeSession()
355 : {
356 4 : if (!m_cdmBackend)
357 : {
358 1 : m_log << error << "Cdm is NULL or not initialized";
359 1 : return false;
360 : }
361 :
362 3 : if (-1 != m_rialtoSessionId)
363 : {
364 2 : if (m_cdmBackend->removeKeySession(m_rialtoSessionId))
365 : {
366 1 : m_log << info << "Successfully removed the session";
367 1 : return true;
368 : }
369 : else
370 : {
371 1 : m_log << warn << "Failed to remove the session.";
372 : }
373 : }
374 :
375 2 : return false;
376 : }
377 :
378 38 : void OpenCDMSessionPrivate::releaseSession()
379 : {
380 38 : if (-1 != m_rialtoSessionId)
381 : {
382 38 : if (m_cdmBackend->releaseKeySession(m_rialtoSessionId))
383 : {
384 37 : m_log << info << "Successfully released the session";
385 : }
386 : else
387 : {
388 1 : m_log << warn << "Failed to release the session.";
389 : }
390 : }
391 38 : }
392 :
393 4 : bool OpenCDMSessionPrivate::containsKey(const std::vector<uint8_t> &keyId)
394 : {
395 4 : if (!m_cdmBackend)
396 : {
397 1 : m_log << error << "Cdm is NULL or not initialized";
398 1 : return false;
399 : }
400 :
401 3 : if (-1 != m_rialtoSessionId)
402 : {
403 2 : return m_cdmBackend->containsKey(m_rialtoSessionId, keyId);
404 : }
405 1 : return false;
406 : }
407 :
408 5 : bool OpenCDMSessionPrivate::setDrmHeader(const std::vector<uint8_t> &drmHeader)
409 : {
410 5 : if (!m_cdmBackend)
411 : {
412 1 : m_log << error << "Cdm is NULL or not initialized";
413 1 : return false;
414 : }
415 :
416 4 : if (-1 != m_rialtoSessionId)
417 : {
418 2 : return m_cdmBackend->setDrmHeader(m_rialtoSessionId, drmHeader);
419 : }
420 : else
421 : {
422 2 : m_log << info << "Queueing DRM header until session is initialized.";
423 2 : m_queuedDrmHeader = drmHeader;
424 : }
425 2 : return true;
426 : }
427 :
428 2 : bool OpenCDMSessionPrivate::selectKeyId(const std::vector<uint8_t> &keyId)
429 : {
430 2 : m_log << debug << "Playready key selected.";
431 2 : m_playreadyKeyId = keyId;
432 2 : return true;
433 : }
434 :
435 3 : void OpenCDMSessionPrivate::onLicenseRequest(int32_t keySessionId,
436 : const std::vector<unsigned char> &licenseRequestMessage,
437 : const std::string &url)
438 : {
439 3 : if (keySessionId == m_rialtoSessionId)
440 : {
441 2 : updateChallenge(licenseRequestMessage);
442 :
443 2 : if ((m_callbacks) && (m_callbacks->process_challenge_callback))
444 : {
445 2 : m_callbacks->process_challenge_callback(this, m_context, url.c_str(), licenseRequestMessage.data(),
446 2 : licenseRequestMessage.size());
447 : }
448 : }
449 3 : }
450 :
451 2 : void OpenCDMSessionPrivate::onLicenseRenewal(int32_t keySessionId, const std::vector<unsigned char> &licenseRenewalMessage)
452 : {
453 2 : if (keySessionId == m_rialtoSessionId)
454 : {
455 1 : updateChallenge(licenseRenewalMessage);
456 :
457 1 : if ((m_callbacks) && (m_callbacks->process_challenge_callback))
458 : {
459 1 : m_callbacks->process_challenge_callback(this, m_context, "" /*URL*/, licenseRenewalMessage.data(),
460 1 : licenseRenewalMessage.size());
461 : }
462 : }
463 2 : }
464 :
465 3 : void OpenCDMSessionPrivate::updateChallenge(const std::vector<unsigned char> &challenge)
466 : {
467 3 : std::unique_lock<std::mutex> lock{m_mutex};
468 3 : m_challengeData = challenge;
469 3 : m_challengeCv.notify_one();
470 : }
471 :
472 9 : void OpenCDMSessionPrivate::onKeyStatusesChanged(int32_t keySessionId,
473 : const firebolt::rialto::KeyStatusVector &keyStatuses)
474 : {
475 9 : if ((keySessionId == m_rialtoSessionId) && (m_callbacks) && (m_callbacks->key_update_callback))
476 : {
477 16 : for (const std::pair<std::vector<uint8_t>, firebolt::rialto::KeyStatus> &keyStatus : keyStatuses)
478 : {
479 : // Update internal key statuses
480 8 : m_keyStatuses[keyStatus.first] = keyStatus.second;
481 :
482 8 : const std::vector<uint8_t> &key = keyStatus.first;
483 8 : m_callbacks->key_update_callback(this, m_context, key.data(), key.size());
484 : }
485 :
486 8 : if (m_callbacks->keys_updated_callback)
487 : {
488 8 : m_callbacks->keys_updated_callback(this, m_context);
489 : }
490 : }
491 9 : }
492 :
493 11 : KeyStatus OpenCDMSessionPrivate::status(const std::vector<uint8_t> &key) const
494 : {
495 11 : auto it = m_keyStatuses.find(key);
496 11 : if (it != m_keyStatuses.end())
497 : {
498 9 : return convertKeyStatus(it->second);
499 : }
500 2 : return KeyStatus::InternalError;
501 : }
502 :
503 3 : const std::string &OpenCDMSessionPrivate::getSessionId() const
504 : {
505 3 : return m_cdmKeySessionId;
506 : }
507 :
508 6 : void OpenCDMSessionPrivate::initializeCdmKeySessionId()
509 : {
510 6 : bool result{false};
511 :
512 6 : if (-1 != m_rialtoSessionId)
513 : {
514 6 : result = m_cdmBackend->getCdmKeySessionId(m_rialtoSessionId, m_cdmKeySessionId);
515 : }
516 6 : if (!result)
517 : {
518 4 : m_cdmKeySessionId = kDefaultSessionId;
519 : }
520 6 : }
521 :
522 7 : uint32_t OpenCDMSessionPrivate::getLastDrmError() const
523 : {
524 7 : uint32_t err = 0;
525 7 : if (!m_cdmBackend)
526 : {
527 1 : m_log << error << "Cdm is NULL or not initialized";
528 1 : return -1;
529 : }
530 :
531 6 : (void)m_cdmBackend->getLastDrmError(m_rialtoSessionId, err);
532 :
533 6 : return err;
534 : }
535 :
536 65 : firebolt::rialto::KeySessionType OpenCDMSessionPrivate::getRialtoSessionType(const LicenseType licenseType)
537 : {
538 65 : switch (licenseType)
539 : {
540 62 : case Temporary:
541 62 : return firebolt::rialto::KeySessionType::TEMPORARY;
542 1 : case PersistentUsageRecord: /// TODO: Rialto's equivalent??
543 1 : return firebolt::rialto::KeySessionType::UNKNOWN;
544 1 : case PersistentLicense:
545 1 : return firebolt::rialto::KeySessionType::PERSISTENT_LICENCE;
546 1 : default:
547 1 : return firebolt::rialto::KeySessionType::UNKNOWN;
548 : }
549 : }
550 :
551 74 : firebolt::rialto::InitDataType OpenCDMSessionPrivate::getRialtoInitDataType(const std::string &type)
552 : {
553 74 : firebolt::rialto::InitDataType initDataType = firebolt::rialto::InitDataType::UNKNOWN;
554 :
555 74 : if (type == "cenc")
556 : {
557 1 : initDataType = firebolt::rialto::InitDataType::CENC;
558 : }
559 73 : else if (type == "webm")
560 : {
561 1 : initDataType = firebolt::rialto::InitDataType::WEBM;
562 : }
563 72 : else if (type == "drmheader")
564 : {
565 71 : initDataType = firebolt::rialto::InitDataType::DRMHEADER;
566 : }
567 :
568 74 : return initDataType;
569 : }
|