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