LCOV - code coverage report
Current view: top level - source - OpenCDMSessionPrivate.cpp (source / functions) Coverage Total Hit
Test: coverage.info Lines: 100.0 % 249 249
Test Date: 2025-07-07 09:31:15 Functions: 100.0 % 28 28

            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              : }
        

Generated by: LCOV version 2.0-1