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