LCOV - code coverage report
Current view: top level - frmts/e57 - e57driver.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 354 384 92.2 %
Date: 2026-01-23 20:24:11 Functions: 25 26 96.2 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  Driver for ASTM E57 3D file format (image part)
       5             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "gdal_frmts.h"
      14             : #include "gdal_pam.h"
      15             : #include "gdal_proxy.h"
      16             : #include "cpl_minixml.h"
      17             : #include "cpl_string.h"
      18             : #include "cpl_vsi_virtual.h"
      19             : 
      20             : #include <algorithm>
      21             : #include <array>
      22             : #include <cinttypes>
      23             : #include <limits>
      24             : #include <set>
      25             : 
      26             : // Useful links:
      27             : // - https://paulbourke.net/dataformats/e57/
      28             : // - https://paulbourke.net/dataformats/e57/2011-huber-e57-v3.pdf
      29             : // - http://www.libe57.org/data.html
      30             : // - https://github.com/asmaloney/libE57Format
      31             : // - https://store.astm.org/e2807-11r19e01.html
      32             : 
      33             : namespace
      34             : {
      35             : 
      36             : constexpr const char *E57_PREFIX = "E57:";
      37             : 
      38             : /* EndOfPage size */
      39             : constexpr int E57_EOP_SIZE = 4;
      40             : 
      41             : /************************************************************************/
      42             : /*                             E57ImageDesc()                           */
      43             : /************************************************************************/
      44             : 
      45             : struct E57ImageDesc
      46             : {
      47             :     std::string osDriverName{};
      48             :     int nWidth = 0;
      49             :     int nHeight = 0;
      50             :     uint64_t nOffset = 0;
      51             :     uint64_t nLength = 0;
      52             :     uint64_t nMaskOffset = 0;
      53             :     uint64_t nMaskLength = 0;
      54             :     CPLStringList aosExtraMD{};
      55             : };
      56             : 
      57             : /************************************************************************/
      58             : /*           IsValidPhysicalOffsetForBeginningOfSection()               */
      59             : /************************************************************************/
      60             : 
      61        3016 : static bool IsValidPhysicalOffsetForBeginningOfSection(uint64_t nOffset,
      62             :                                                        uint64_t pageSize)
      63             : {
      64             :     // The start of a section cannot be one of the last 3 bytes of a page
      65        3016 :     return (nOffset % pageSize) < pageSize - (E57_EOP_SIZE - 1);
      66             : }
      67             : 
      68             : /************************************************************************/
      69             : /*                 ConvertE57LogicalOffsetToPhysical()                  */
      70             : /************************************************************************/
      71             : 
      72             : // Convert nLogicalOffset (measured from nBasePhysicalOffset) to physical offset
      73             : // E57 files are divided into physical pages. The last 4 bytes of every page
      74             : // are a CRC32 checksum. This function calculates the physical jump required
      75             : // to skip these checksums when moving through a logical stream of data.
      76        9035 : static uint64_t ConvertE57LogicalOffsetToPhysical(uint64_t nBasePhysicalOffset,
      77             :                                                   uint64_t nLogicalOffset,
      78             :                                                   uint64_t nPhysicalPageSize)
      79             : {
      80        9035 :     const auto nLogicalPageSize = nPhysicalPageSize - E57_EOP_SIZE;
      81        9035 :     const auto numPagesCrossed =
      82        9035 :         ((nBasePhysicalOffset % nPhysicalPageSize) + nLogicalOffset) /
      83             :         nLogicalPageSize;
      84        9035 :     return nBasePhysicalOffset + nLogicalOffset +
      85        9035 :            numPagesCrossed * E57_EOP_SIZE;
      86             : }
      87             : 
      88             : /************************************************************************/
      89             : /*                           GDAL_E57Dataset                            */
      90             : /************************************************************************/
      91             : 
      92             : class GDAL_E57RasterBand;
      93             : 
      94             : class GDAL_E57Dataset final : public GDALProxyDataset
      95             : {
      96             :   public:
      97             :     GDAL_E57Dataset(std::unique_ptr<GDALDataset> poUnderlyingDataset,
      98             :                     std::unique_ptr<GDALDataset> poMaskDS,
      99             :                     const E57ImageDesc &sE57ImageDesc, std::string osXML);
     100             : 
     101             :     GDALDriver *GetDriver() override;
     102             : 
     103           1 :     char **GetMetadataDomainList() override
     104             :     {
     105           1 :         char **papszList = GDALProxyDataset::GetMetadataDomainList();
     106           1 :         papszList = CSLAddString(papszList, "xml:E57");
     107           1 :         return papszList;
     108             :     }
     109             : 
     110           6 :     CSLConstList GetMetadata(const char *pszDomain) override
     111             :     {
     112           6 :         if (!pszDomain || pszDomain[0] == 0)
     113             :         {
     114           2 :             if (!m_bMDSet)
     115             :             {
     116           1 :                 m_bMDSet = true;
     117             :                 m_aosMD.Assign(
     118             :                     CSLDuplicate(GDALProxyDataset::GetMetadata(pszDomain)),
     119           1 :                     true);
     120          16 :                 for (const auto &[pszKey, pszValue] :
     121          17 :                      cpl::IterateNameValue(m_sE57ImageDesc.aosExtraMD))
     122           8 :                     m_aosMD.SetNameValue(pszKey, pszValue);
     123             :             }
     124           2 :             return m_aosMD.List();
     125             :         }
     126           4 :         else if (EQUAL(pszDomain, "xml:E57"))
     127             :         {
     128           3 :             return const_cast<char **>(m_apszXMLE57.data());
     129             :         }
     130             : 
     131           1 :         return GDALProxyDataset::GetMetadata(pszDomain);
     132             :     }
     133             : 
     134           1 :     const char *GetMetadataItem(const char *pszName,
     135             :                                 const char *pszDomain) override
     136             :     {
     137           1 :         return CSLFetchNameValue(GetMetadata(pszDomain), pszName);
     138             :     }
     139             : 
     140             :     static int Identify(GDALOpenInfo *poOpenInfo);
     141             :     static GDALDataset *Open(GDALOpenInfo *poOpenInfo);
     142             : 
     143             :   protected:
     144        1006 :     GDALDataset *RefUnderlyingDataset() const override
     145             :     {
     146        1006 :         return m_poUnderlyingDataset.get();
     147             :     }
     148             : 
     149             :   private:
     150             :     friend class GDAL_E57RasterBand;
     151             :     std::unique_ptr<GDALDataset> m_poUnderlyingDataset{};
     152             :     std::unique_ptr<GDALDataset> m_poMaskDS{};
     153             :     const E57ImageDesc m_sE57ImageDesc;
     154             :     CPLStringList m_aosMD{};
     155             :     const std::string m_osXML;
     156             :     std::array<const char *, 2> m_apszXMLE57{nullptr, nullptr};
     157             :     bool m_bMDSet = false;
     158             : };
     159             : 
     160           0 : GDALDriver *GDAL_E57Dataset::GetDriver()
     161             : {
     162             :     // Short-circuit proxying, so as not to get the PNG/JPEG driver
     163           0 :     return GDALDataset::GetDriver();
     164             : }
     165             : 
     166             : /************************************************************************/
     167             : /*                          GDAL_E57FileHandle                          */
     168             : /************************************************************************/
     169             : 
     170             : class GDAL_E57FileHandle final : public VSIVirtualHandle
     171             : {
     172             :   public:
     173        3002 :     GDAL_E57FileHandle(VSIVirtualHandleUniquePtr poRawFP,
     174             :                        uint64_t nBasePhysicalOffset, uint64_t nLength,
     175             :                        uint64_t nPageSize, int nSectionHeaderSize)
     176        6004 :         : m_poRawFP(std::move(poRawFP)),
     177             :           m_nBasePhysicalOffset(nBasePhysicalOffset), m_nLength(nLength),
     178        3002 :           m_nPageSize(nPageSize), m_nSectionHeaderSize(nSectionHeaderSize)
     179             :     {
     180        3002 :     }
     181             : 
     182        1002 :     VSIVirtualHandleUniquePtr ReacquireRawFP()
     183             :     {
     184        1002 :         VSIVirtualHandleUniquePtr ret;
     185        1002 :         std::swap(ret, m_poRawFP);
     186        1002 :         return ret;
     187             :     }
     188             : 
     189        4052 :     int Seek(vsi_l_offset nOffset, int nWhence) override
     190             :     {
     191        4052 :         m_bEOF = false;
     192        4052 :         if (nWhence == SEEK_SET)
     193        4041 :             m_nPos = nOffset;
     194          11 :         else if (nWhence == SEEK_CUR)
     195           9 :             m_nPos += nOffset;
     196             :         else
     197             :         {
     198           2 :             CPLAssert(nWhence == SEEK_END);
     199           2 :             CPLAssert(nOffset == 0);
     200           2 :             m_nPos = m_nLength;
     201             :         }
     202        4052 :         return 0;
     203             :     }
     204             : 
     205             :     vsi_l_offset Tell() override;
     206             : 
     207       14140 :     size_t Read(void *pBuffer, size_t nToReadTotal) override
     208             :     {
     209       10042 :         if (m_bEOF || m_nPos > m_nLength ||
     210       10042 :             m_nBasePhysicalOffset >
     211       34224 :                 std::numeric_limits<uint64_t>::max() - m_nPos ||
     212       10042 :             m_nPos >
     213       10042 :                 std::numeric_limits<uint64_t>::max() - m_nSectionHeaderSize)
     214             :         {
     215        4098 :             m_bEOF = true;
     216        4098 :             return 0;
     217             :         }
     218       10042 :         if (nToReadTotal == 0)
     219        1007 :             return 0;
     220             : 
     221             :         // Align our raw file pointer to the physical location of the current
     222             :         // logical position, taking the E57 page/CRC overhead into account.
     223        9035 :         const auto nPhysicalOffset = ConvertE57LogicalOffsetToPhysical(
     224        9035 :             m_nBasePhysicalOffset, m_nPos + m_nSectionHeaderSize, m_nPageSize);
     225             : 
     226        9035 :         if (m_poRawFP->Seek(nPhysicalOffset, SEEK_SET) != 0)
     227           0 :             return 0;
     228             : 
     229        9035 :         GByte *pabyBuffer = static_cast<GByte *>(pBuffer);
     230             :         // Ingest number of requested bytes, by skipping the last 4 bytes of
     231             :         // each physical page.
     232        9035 :         size_t nReadTotal = 0;
     233       17081 :         while (nReadTotal < nToReadTotal)
     234             :         {
     235       11040 :             const uint64_t nCurPos = m_poRawFP->Tell();
     236             :             const uint64_t nEndOfPagePhysicalOffset =
     237       11040 :                 cpl::div_round_up(nCurPos + 1, m_nPageSize) * m_nPageSize;
     238       11040 :             CPLAssert(nEndOfPagePhysicalOffset - nCurPos >= E57_EOP_SIZE);
     239             : 
     240             :             const size_t nToReadChunk = static_cast<size_t>(
     241       22080 :                 std::min(static_cast<uint64_t>(nToReadTotal - nReadTotal),
     242       11040 :                          nEndOfPagePhysicalOffset - nCurPos - E57_EOP_SIZE));
     243             :             const size_t nReadChunk =
     244       11040 :                 m_poRawFP->Read(pabyBuffer + nReadTotal, nToReadChunk);
     245       11040 :             m_nPos += nReadChunk;
     246       11040 :             nReadTotal += nReadChunk;
     247       11040 :             if (m_nPos > m_nLength)
     248             :             {
     249        1998 :                 nReadTotal -= static_cast<size_t>(m_nPos - m_nLength);
     250        1998 :                 m_nPos = m_nLength;
     251        1998 :                 m_bEOF = true;
     252        1998 :                 break;
     253             :             }
     254        9042 :             if (nReadChunk != nToReadChunk)
     255             :             {
     256         996 :                 m_bEOF = true;
     257         996 :                 break;
     258             :             }
     259        8046 :             if (nReadTotal < nToReadTotal)
     260             :             {
     261             :                 // Skip 4 bytes of CRC
     262             :                 GByte abyCRC32[E57_EOP_SIZE];
     263        2005 :                 if (m_poRawFP->Read(abyCRC32, sizeof(abyCRC32)) !=
     264             :                     sizeof(abyCRC32))
     265             :                 {
     266           0 :                     CPLDebug("E57", "Cannot read CRC");
     267           0 :                     break;
     268             :                 }
     269             :             }
     270             :         }
     271        9035 :         return nReadTotal;
     272             :     }
     273             : 
     274        6137 :     int Eof() override
     275             :     {
     276        6137 :         return m_bEOF;
     277             :     }
     278             : 
     279        6137 :     int Error() override
     280             :     {
     281        6137 :         return m_poRawFP->Error();
     282             :     }
     283             : 
     284        1995 :     int Close() override
     285             :     {
     286        1995 :         int nRet = 0;
     287        1995 :         if (m_poRawFP)
     288        1995 :             nRet = m_poRawFP->Close();
     289        1995 :         m_poRawFP.reset();
     290        1995 :         return nRet;
     291             :     }
     292             : 
     293        6137 :     void ClearErr() override
     294             :     {
     295        6137 :         m_poRawFP->ClearErr();
     296        6137 :     }
     297             : 
     298        1007 :     size_t Write(const void *, size_t) override
     299             :     {
     300        1007 :         return 0;
     301             :     }
     302             : 
     303             :   private:
     304             :     VSIVirtualHandleUniquePtr m_poRawFP{};
     305             :     // physical offset of the start of the subfile
     306             :     const uint64_t m_nBasePhysicalOffset;
     307             :     const uint64_t m_nLength;  // logical length of the subfile
     308             :     const uint64_t m_nPageSize;
     309             :     const int m_nSectionHeaderSize;
     310             :     uint64_t m_nPos = 0;  // logical offset within the subfile
     311             :     bool m_bEOF = false;
     312             : };
     313             : 
     314             : // Offline definition to please -Wweak-vtables
     315        6142 : vsi_l_offset GDAL_E57FileHandle::Tell()
     316             : {
     317        6142 :     return m_nPos;
     318             : }
     319             : 
     320             : /************************************************************************/
     321             : /*                           GDAL_E57RasterBand                         */
     322             : /************************************************************************/
     323             : 
     324             : class GDAL_E57RasterBand final : public GDALProxyRasterBand
     325             : {
     326             :   public:
     327        1001 :     explicit GDAL_E57RasterBand(GDALRasterBand *poUnderlyingBand)
     328        1001 :         : m_poUnderlyingBand(poUnderlyingBand)
     329             :     {
     330        1001 :         nRasterXSize = m_poUnderlyingBand->GetXSize();
     331        1001 :         nRasterYSize = m_poUnderlyingBand->GetYSize();
     332        1001 :         eDataType = m_poUnderlyingBand->GetRasterDataType();
     333        1001 :         m_poUnderlyingBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
     334        1001 :     }
     335             : 
     336           2 :     int GetMaskFlags() override
     337             :     {
     338           2 :         auto poGDS = cpl::down_cast<GDAL_E57Dataset *>(poDS);
     339           2 :         if (poGDS->m_poMaskDS)
     340           1 :             return GMF_PER_DATASET;
     341           1 :         return GDALProxyRasterBand::GetMaskFlags();
     342             :     }
     343             : 
     344           2 :     GDALRasterBand *GetMaskBand() override
     345             :     {
     346           2 :         auto poGDS = cpl::down_cast<GDAL_E57Dataset *>(poDS);
     347           2 :         if (poGDS->m_poMaskDS)
     348           1 :             return poGDS->m_poMaskDS->GetRasterBand(1);
     349           1 :         return GDALProxyRasterBand::GetMaskBand();
     350             :     }
     351             : 
     352             :   protected:
     353             :     GDALRasterBand *RefUnderlyingRasterBand(bool bForceOpen) const override;
     354             : 
     355             :   private:
     356             :     GDALRasterBand *const m_poUnderlyingBand;
     357             :     CPL_DISALLOW_COPY_ASSIGN(GDAL_E57RasterBand)
     358             : };
     359             : 
     360          47 : GDALRasterBand *GDAL_E57RasterBand::RefUnderlyingRasterBand(bool) const
     361             : {
     362          47 :     return m_poUnderlyingBand;
     363             : }
     364             : 
     365        1001 : GDAL_E57Dataset::GDAL_E57Dataset(
     366             :     std::unique_ptr<GDALDataset> poUnderlyingDataset,
     367             :     std::unique_ptr<GDALDataset> poMaskDS, const E57ImageDesc &sE57ImageDesc,
     368        1001 :     std::string osXML)
     369        1001 :     : m_poUnderlyingDataset(std::move(poUnderlyingDataset)),
     370        1001 :       m_poMaskDS(std::move(poMaskDS)), m_sE57ImageDesc(sE57ImageDesc),
     371        1001 :       m_osXML(std::move(osXML))
     372             : {
     373        1001 :     nRasterXSize = m_poUnderlyingDataset->GetRasterXSize();
     374        1001 :     nRasterYSize = m_poUnderlyingDataset->GetRasterYSize();
     375        2002 :     for (int i = 0; i < m_poUnderlyingDataset->GetRasterCount(); ++i)
     376             :     {
     377        1001 :         SetBand(i + 1, std::make_unique<GDAL_E57RasterBand>(
     378        2002 :                            m_poUnderlyingDataset->GetRasterBand(i + 1)));
     379             :     }
     380        1001 :     m_apszXMLE57[0] = m_osXML.c_str();
     381        1001 : }
     382             : 
     383             : /************************************************************************/
     384             : /*                               Identify()                             */
     385             : /************************************************************************/
     386             : 
     387             : /* static */
     388       61185 : int GDAL_E57Dataset::Identify(GDALOpenInfo *poOpenInfo)
     389             : {
     390       64950 :     return (poOpenInfo->nHeaderBytes >= 1024 &&
     391        3765 :             memcmp(poOpenInfo->pabyHeader, "ASTM-E57", 8) == 0 &&
     392      122370 :             poOpenInfo->fpL != nullptr) ||
     393      120321 :            STARTS_WITH_CI(poOpenInfo->pszFilename, E57_PREFIX);
     394             : }
     395             : 
     396             : /************************************************************************/
     397             : /*                         asMDDescriptors                              */
     398             : /************************************************************************/
     399             : 
     400             : static const struct
     401             : {
     402             :     const char *pszXMLPath;
     403             :     const char *pszMDItem;
     404             : } asMDDescriptors[] = {
     405             :     {"name", "NAME"},
     406             :     {"description", "DESCRIPTION"},
     407             :     {"sensorVendor", "SENSOR_VENDOR"},
     408             :     {"sensorModel", "SENSOR_MODEL"},
     409             :     {"sensorSerialNumber", "SENSOR_SERIAL_NUMBER"},
     410             :     {"associatedData3DGuid", "ASSOCIATED_DATA_3D_GUID"},
     411             :     {"acquisitionDateTime.dateTimeValue", "ACQUISITION_DATE_TIME"},
     412             :     {"pose.rotation.w", "POSE_ROTATION_W"},
     413             :     {"pose.rotation.x", "POSE_ROTATION_X"},
     414             :     {"pose.rotation.y", "POSE_ROTATION_Y"},
     415             :     {"pose.rotation.z", "POSE_ROTATION_Z"},
     416             :     {"pose.translation.x", "POSE_TRANSLATION_X"},
     417             :     {"pose.translation.y", "POSE_TRANSLATION_Y"},
     418             :     {"pose.translation.z", "POSE_TRANSLATION_Z"},
     419             :     {"{rep}.pixelWidth", "PIXEL_WIDTH"},
     420             :     {"{rep}.pixelHeight", "PIXEL_HEIGHT"},
     421             :     {"{rep}.focalLength", "FOCAL_LENGTH"},
     422             :     {"{rep}.principalPointX", "PRINCIPAL_POINT_X"},
     423             :     {"{rep}.principalPointY", "PRINCIPAL_POINT_Y"},
     424             :     {"{rep}.radius", "RADIUS"},
     425             : };
     426             : 
     427             : /************************************************************************/
     428             : /*                                 Open()                               */
     429             : /************************************************************************/
     430             : 
     431             : /* static */
     432        1032 : GDALDataset *GDAL_E57Dataset::Open(GDALOpenInfo *poOpenInfo)
     433             : {
     434        1032 :     if (!Identify(poOpenInfo))
     435           0 :         return nullptr;
     436        1032 :     if (poOpenInfo->eAccess == GA_Update)
     437             :     {
     438           1 :         CPLError(CE_Failure, CPLE_NotSupported,
     439             :                  "E57 driver does not support updates");
     440           1 :         return nullptr;
     441             :     }
     442             : 
     443        2062 :     std::string osSubDSName;
     444        1031 :     std::unique_ptr<GDALOpenInfo> poOpenInfoSubDS;  // keep in that scope
     445        2062 :     std::string osPhysicalFilename(poOpenInfo->pszFilename);
     446        1031 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, E57_PREFIX))
     447             :     {
     448             :         const CPLStringList aosTokens(
     449          12 :             CSLTokenizeString2(poOpenInfo->pszFilename + strlen(E57_PREFIX),
     450          12 :                                ":", CSLT_HONOURSTRINGS));
     451          12 :         if (aosTokens.size() != 2)
     452             :         {
     453           2 :             CPLError(CE_Failure, CPLE_AppDefined,
     454             :                      "Invalid E57 subdataset syntax");
     455           2 :             return nullptr;
     456             :         }
     457             : 
     458          10 :         osPhysicalFilename = aosTokens[0];
     459          10 :         poOpenInfoSubDS = std::make_unique<GDALOpenInfo>(
     460          10 :             osPhysicalFilename.c_str(), GA_ReadOnly);
     461          10 :         osSubDSName = aosTokens[1];
     462          10 :         poOpenInfo = poOpenInfoSubDS.get();
     463             :         // cppcheck-suppress knownConditionTrueFalse
     464          10 :         if (!Identify(poOpenInfo) || !poOpenInfo->fpL)
     465           1 :             return nullptr;
     466             :     }
     467             : 
     468        2056 :     VSIVirtualHandleUniquePtr fp(poOpenInfo->fpL);
     469        1028 :     poOpenInfo->fpL = nullptr;
     470             : 
     471             :     // Parse E57 header
     472        1028 :     const uint32_t nMajorVersion = CPL_LSBUINT32PTR(poOpenInfo->pabyHeader + 8);
     473        1028 :     const uint32_t nMinorVersion =
     474        1028 :         CPL_LSBUINT32PTR(poOpenInfo->pabyHeader + 12);
     475        1028 :     CPLDebug("E57", "E57 v%d.%d file", nMajorVersion, nMinorVersion);
     476             : 
     477             :     uint64_t filePhysicalLength;
     478        1028 :     memcpy(&filePhysicalLength, poOpenInfo->pabyHeader + 16,
     479             :            sizeof(filePhysicalLength));
     480        1028 :     CPL_LSBPTR64(&filePhysicalLength);
     481        1028 :     CPLDebugOnly("E57", "filePhysicalLength = %" PRIu64, filePhysicalLength);
     482             : 
     483             :     uint64_t xmlPhysicalOffset;
     484        1028 :     memcpy(&xmlPhysicalOffset, poOpenInfo->pabyHeader + 24,
     485             :            sizeof(xmlPhysicalOffset));
     486        1028 :     CPL_LSBPTR64(&xmlPhysicalOffset);
     487        1028 :     CPLDebugOnly("E57", "xmlPhysicalOffset = %" PRIu64, xmlPhysicalOffset);
     488             : 
     489             :     uint64_t xmlLogicalLength;
     490        1028 :     memcpy(&xmlLogicalLength, poOpenInfo->pabyHeader + 32,
     491             :            sizeof(xmlLogicalLength));
     492        1028 :     CPL_LSBPTR64(&xmlLogicalLength);
     493        1028 :     CPLDebugOnly("E57", "xmlLogicalLength = %" PRIu64, xmlLogicalLength);
     494             : 
     495             :     uint64_t pageSize;
     496        1028 :     memcpy(&pageSize, poOpenInfo->pabyHeader + 40, sizeof(pageSize));
     497        1028 :     CPL_LSBPTR64(&pageSize);
     498        1028 :     CPLDebugOnly("E57", "pageSize = %" PRIu64, pageSize);
     499             :     // The page size NEEDS to be strictly greater than E57_EOP_SIZE (4), and
     500             :     // the nominal page size is 1024 bytes
     501        1028 :     constexpr uint64_t NOMINAL_PAGE_SIZE = 1024;
     502        1028 :     constexpr uint64_t MAX_LARGE_PAGE_SIZE = 1024 * 1024;  // arbitrary
     503        1028 :     if (pageSize < NOMINAL_PAGE_SIZE || pageSize > MAX_LARGE_PAGE_SIZE ||
     504        1022 :         (pageSize % 4) != 0)
     505             :     {
     506           7 :         CPLError(CE_Failure, CPLE_NotSupported,
     507             :                  "E57: invalid page size: %" PRIu64, pageSize);
     508           7 :         return nullptr;
     509             :     }
     510             : 
     511        1021 :     if (!IsValidPhysicalOffsetForBeginningOfSection(xmlPhysicalOffset,
     512             :                                                     pageSize))
     513             :     {
     514           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     515             :                  "E57: invalid xmlPhysicalOffset: %" PRIu64, xmlPhysicalOffset);
     516           0 :         return nullptr;
     517             :     }
     518             : 
     519        1021 :     constexpr size_t SIZEOF_E57_FILE_HEADER = 48;
     520        3056 :     if (xmlLogicalLength > filePhysicalLength ||
     521        1014 :         xmlLogicalLength > std::numeric_limits<size_t>::max() - 1 ||
     522        3049 :         xmlPhysicalOffset < SIZEOF_E57_FILE_HEADER ||
     523        1014 :         xmlPhysicalOffset > filePhysicalLength - xmlLogicalLength)
     524             :     {
     525          14 :         CPLError(CE_Failure, CPLE_AppDefined,
     526             :                  "E57: invalid "
     527             :                  "filePhysicalLength/xmlPhysicalOffset/xmlLogicalLength");
     528          14 :         return nullptr;
     529             :     }
     530             : 
     531             :     // Arbitrary threshold above which we check the consistency of the declared
     532             :     // size of the XML section w.r.t. whole file size
     533        1007 :     constexpr size_t XML_THRESHOLD_SIZE = 100 * 1024 * 1024;
     534        1007 :     if (xmlLogicalLength > XML_THRESHOLD_SIZE &&
     535           0 :         !(fp->Seek(0, SEEK_END) == 0 && fp->Tell() == filePhysicalLength &&
     536        1007 :           fp->Seek(0, SEEK_SET) == 0))
     537             :     {
     538           0 :         CPLError(CE_Failure, CPLE_AppDefined, "E57: file too short");
     539           0 :         return nullptr;
     540             :     }
     541             : 
     542        2014 :     std::string osXML;
     543             :     try
     544             :     {
     545        1007 :         osXML.resize(static_cast<size_t>(xmlLogicalLength));
     546             :     }
     547           0 :     catch (const std::exception &)
     548             :     {
     549           0 :         CPLError(CE_Failure, CPLE_OutOfMemory, "E57: out of memory");
     550           0 :         return nullptr;
     551             :     }
     552             : 
     553             :     auto poE57XmlFile = std::make_unique<GDAL_E57FileHandle>(
     554        2014 :         std::move(fp), xmlPhysicalOffset, xmlLogicalLength, pageSize, 0);
     555             : 
     556             : #ifdef DEBUG
     557             :     {
     558             :         // Quick actions just to increase test coverage
     559        1007 :         CPLAssert(poE57XmlFile->Tell() == 0);
     560        1007 :         char chDummy = 0;
     561        1007 :         CPLAssert(poE57XmlFile->Read(&chDummy, 0) == 0);
     562        1007 :         CPL_IGNORE_RET_VAL(chDummy);
     563        1007 :         CPLAssert(!poE57XmlFile->Eof());
     564        1007 :         CPLAssert(!poE57XmlFile->Error());
     565        1007 :         poE57XmlFile->ClearErr();
     566        1007 :         CPLAssert(poE57XmlFile->Write("", 0) == 0);
     567             :     }
     568             : #endif
     569             : 
     570        1007 :     if (poE57XmlFile->Read(osXML.data(),
     571        1007 :                            static_cast<size_t>(xmlLogicalLength)) !=
     572             :         static_cast<size_t>(xmlLogicalLength))
     573             :     {
     574           0 :         CPLError(CE_Failure, CPLE_AppDefined, "E57: cannot read XML");
     575           0 :         return nullptr;
     576             :     }
     577             : 
     578             : #ifdef DEBUG
     579        1007 :     if (EQUAL(CPLGetConfigOption("CPL_DEBUG", ""), "E57"))
     580             :     {
     581           0 :         fprintf(stderr, "XML: %s\n", osXML.c_str()); /*ok*/
     582             :     }
     583             : #endif
     584             : 
     585        2014 :     CPLXMLTreeCloser poRoot(CPLParseXMLString(osXML.c_str()));
     586             :     const CPLXMLNode *psImages2D =
     587        1007 :         CPLGetXMLNode(poRoot.get(), "=e57Root.images2D");
     588        2014 :     std::vector<E57ImageDesc> asE57ImageDesc;
     589        2014 :     std::set<std::string> aoSetNames;
     590        1007 :     size_t iCounter = 0;
     591             :     // Iterate through images
     592        1007 :     const CPLXMLNode *psIter = psImages2D;
     593        1007 :     if (psIter)
     594        1004 :         psIter = psIter->psChild;
     595        4029 :     for (/* */; psIter; psIter = psIter->psNext)
     596             :     {
     597        3022 :         if (psIter->eType == CXT_Element &&
     598        1014 :             strcmp(psIter->pszValue, "vectorChild") == 0)
     599             :         {
     600        1014 :             const CPLXMLNode *psRep = nullptr;
     601          10 :             for (const char *pszNodeName :
     602             :                  {"sphericalRepresentation", "pinholeRepresentation",
     603        1024 :                   "cylindricalRepresentation", "visualReferenceRepresentation"})
     604             :             {
     605        1024 :                 psRep = CPLGetXMLNode(psIter, pszNodeName);
     606        1024 :                 if (psRep)
     607        1014 :                     break;
     608             :             }
     609        1014 :             if (psRep)
     610             :             {
     611        1014 :                 const CPLXMLNode *psImage = CPLGetXMLNode(psRep, "jpegImage");
     612        1014 :                 const bool bIsJPEG = psImage != nullptr;
     613        1014 :                 if (!psImage)
     614          10 :                     psImage = CPLGetXMLNode(psRep, "pngImage");
     615        1014 :                 if (psImage)
     616             :                 {
     617             :                     const char *pszFileOffset =
     618        1014 :                         CPLGetXMLValue(psImage, "fileOffset", nullptr);
     619             :                     const char *pszLength =
     620        1014 :                         CPLGetXMLValue(psImage, "length", nullptr);
     621        1014 :                     if (pszFileOffset && pszLength)
     622             :                     {
     623        1014 :                         E57ImageDesc desc;
     624        1014 :                         desc.osDriverName = bIsJPEG ? "JPEG" : "PNG";
     625             :                         desc.aosExtraMD.SetNameValue(
     626             :                             "REPRESENTATION_TYPE",
     627        1014 :                             CPLString(psRep->pszValue)
     628        2028 :                                 .replaceAll("Representation", "")
     629        1014 :                                 .c_str());
     630       21294 :                         for (const auto &sMDDescriptor : asMDDescriptors)
     631             :                         {
     632       20280 :                             if (const char *pszValue = CPLGetXMLValue(
     633             :                                     psIter,
     634       40560 :                                     CPLString(sMDDescriptor.pszXMLPath)
     635       40560 :                                         .replaceAll("{rep}", psRep->pszValue)
     636             :                                         .c_str(),
     637             :                                     nullptr))
     638             :                                 desc.aosExtraMD.SetNameValue(
     639        6988 :                                     sMDDescriptor.pszMDItem, pszValue);
     640             :                         }
     641             :                         const char *pszName =
     642        1014 :                             desc.aosExtraMD.FetchNameValue("NAME");
     643        1014 :                         if (pszName)
     644        1014 :                             aoSetNames.insert(pszName);
     645        1014 :                         desc.nOffset =
     646        1014 :                             std::strtoull(pszFileOffset, nullptr, 10);
     647        1014 :                         desc.nLength = std::strtoull(pszLength, nullptr, 10);
     648        1014 :                         desc.nWidth =
     649        1014 :                             atoi(CPLGetXMLValue(psRep, "imageWidth", ""));
     650        1014 :                         desc.nHeight =
     651        1014 :                             atoi(CPLGetXMLValue(psRep, "imageHeight", ""));
     652             : 
     653             :                         const CPLXMLNode *psMask =
     654        1014 :                             CPLGetXMLNode(psRep, "imageMask");
     655        1014 :                         if (psMask)
     656             :                         {
     657             :                             const char *pszMaskFileOffset =
     658         994 :                                 CPLGetXMLValue(psMask, "fileOffset", nullptr);
     659             :                             const char *pszMaskLength =
     660         994 :                                 CPLGetXMLValue(psMask, "length", nullptr);
     661         994 :                             if (pszMaskFileOffset && pszMaskLength)
     662             :                             {
     663         994 :                                 desc.nMaskOffset = std::strtoull(
     664             :                                     pszMaskFileOffset, nullptr, 10);
     665         994 :                                 desc.nMaskLength =
     666         994 :                                     std::strtoull(pszMaskLength, nullptr, 10);
     667             :                             }
     668             :                         }
     669             : 
     670        1014 :                         ++iCounter;
     671        1014 :                         constexpr size_t MAX_IMAGES = 10000;
     672        1014 :                         if (iCounter > MAX_IMAGES)
     673             :                         {
     674           0 :                             CPLError(CE_Failure, CPLE_NotSupported,
     675             :                                      "Too many images");
     676           0 :                             break;
     677             :                         }
     678        1032 :                         if (osSubDSName.empty() ||
     679          28 :                             ((pszName && osSubDSName == pszName) ||
     680        1024 :                              (osSubDSName == std::to_string(iCounter))))
     681             :                         {
     682        1004 :                             asE57ImageDesc.push_back(std::move(desc));
     683             :                         }
     684             :                     }
     685             :                 }
     686             :             }
     687             :         }
     688             :     }
     689             : 
     690        1007 :     if (asE57ImageDesc.empty())
     691             :     {
     692           4 :         if (osSubDSName.empty())
     693             :         {
     694           3 :             CPLDebug("E57", "No image found");
     695             :         }
     696             :         else
     697             :         {
     698           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Subdataset %s not found",
     699             :                      osSubDSName.c_str());
     700             :         }
     701           4 :         return nullptr;
     702             :     }
     703        1003 :     else if (asE57ImageDesc.size() == 1)
     704             :     {
     705        1002 :         if (!IsValidPhysicalOffsetForBeginningOfSection(
     706        1002 :                 asE57ImageDesc[0].nOffset, pageSize))
     707             :         {
     708           0 :             CPLError(CE_Failure, CPLE_NotSupported,
     709             :                      "E57: invalid image offset: %" PRIu64,
     710           0 :                      asE57ImageDesc[0].nOffset);
     711           0 :             return nullptr;
     712             :         }
     713             : 
     714        1002 :         CPLDebugOnly("E57", "Image physical offset: %" PRIu64,
     715             :                      asE57ImageDesc[0].nOffset);
     716        1002 :         CPLDebugOnly("E57", "Image logical length: %" PRIu64,
     717             :                      asE57ImageDesc[0].nLength);
     718        1002 :         constexpr int E57_SIZEOF_BINARY_SECTION_HEADER = 16;
     719             :         // Create a file handle that implements physical-to-logical translation
     720             :         // by skipping CRCs transparently.
     721             :         auto poE57File = std::make_unique<GDAL_E57FileHandle>(
     722        1002 :             poE57XmlFile->ReacquireRawFP(), asE57ImageDesc[0].nOffset,
     723        1002 :             asE57ImageDesc[0].nLength, pageSize,
     724        2004 :             E57_SIZEOF_BINARY_SECTION_HEADER);
     725             : 
     726             :         GDALOpenInfo oOpenInfo(osPhysicalFilename.c_str(),
     727             :                                GDAL_OF_RASTER | GDAL_OF_INTERNAL,
     728        2004 :                                std::move(poE57File));
     729        1002 :         const char *const apszAllowedDrivers[] = {
     730        1002 :             asE57ImageDesc[0].osDriverName.c_str(), nullptr};
     731        2004 :         CPLStringList aosOpenOptions;
     732        1002 :         if (!osSubDSName.empty())
     733             :         {
     734             :             aosOpenOptions.SetNameValue("@PHYSICAL_FILENAME",
     735           8 :                                         osPhysicalFilename.c_str());
     736             :             aosOpenOptions.SetNameValue("@SUBDATASET_NAME",
     737           8 :                                         osSubDSName.c_str());
     738             :         }
     739             :         auto poImageDS =
     740        2004 :             GDALDataset::Open(&oOpenInfo, apszAllowedDrivers, aosOpenOptions);
     741        1002 :         std::unique_ptr<GDALDataset> poRetDS;
     742        1002 :         if (poImageDS)
     743             :         {
     744             :             // Open the mask if present
     745           0 :             std::unique_ptr<GDALDataset> poMaskDS;
     746        1001 :             if (asE57ImageDesc[0].nMaskLength &&
     747        1994 :                 asE57ImageDesc[0].nMaskOffset &&
     748         993 :                 IsValidPhysicalOffsetForBeginningOfSection(
     749         993 :                     asE57ImageDesc[0].nMaskOffset, pageSize))
     750             :             {
     751             :                 VSIVirtualHandleUniquePtr poNewRawFP(
     752        1986 :                     VSIFOpenL(osPhysicalFilename.c_str(), "rb"));
     753         993 :                 if (poNewRawFP)
     754             :                 {
     755             :                     auto poE57MaskHandle = std::make_unique<GDAL_E57FileHandle>(
     756         993 :                         std::move(poNewRawFP), asE57ImageDesc[0].nMaskOffset,
     757         993 :                         asE57ImageDesc[0].nMaskLength, pageSize,
     758        1986 :                         E57_SIZEOF_BINARY_SECTION_HEADER);
     759             :                     GDALOpenInfo oMaskOpenInfo(osPhysicalFilename.c_str(),
     760             :                                                GDAL_OF_RASTER |
     761             :                                                    GDAL_OF_INTERNAL,
     762        1986 :                                                std::move(poE57MaskHandle));
     763         993 :                     const char *const apszAllowedDriversPNG[] = {"PNG",
     764             :                                                                  nullptr};
     765             :                     auto poMaskDSTmp = GDALDataset::Open(
     766        1986 :                         &oMaskOpenInfo, apszAllowedDriversPNG, nullptr);
     767        1986 :                     if (poMaskDSTmp &&
     768         993 :                         poMaskDSTmp->GetRasterXSize() ==
     769        1986 :                             poImageDS->GetRasterXSize() &&
     770         993 :                         poMaskDSTmp->GetRasterYSize() ==
     771        2979 :                             poImageDS->GetRasterYSize() &&
     772         993 :                         poMaskDSTmp->GetRasterCount() == 1)
     773             :                     {
     774         993 :                         poMaskDS = std::move(poMaskDSTmp);
     775             :                     }
     776             :                 }
     777             :             }
     778             : 
     779        2002 :             poRetDS = std::make_unique<GDAL_E57Dataset>(
     780        1001 :                 std::move(poImageDS), std::move(poMaskDS), asE57ImageDesc[0],
     781        2002 :                 std::move(osXML));
     782             :         }
     783        1002 :         return poRetDS.release();
     784             :     }
     785             :     else
     786             :     {
     787             :         class GDAL_E57DatasetMultipleSDS final : public GDALDataset
     788             :         {
     789             :           public:
     790           1 :             GDAL_E57DatasetMultipleSDS()
     791           1 :             {
     792           1 :                 nRasterXSize = 0;
     793           1 :                 nRasterYSize = 0;
     794           1 :             }
     795             :         };
     796             : 
     797           1 :         const bool bUniqueNames = aoSetNames.size() == asE57ImageDesc.size();
     798           2 :         auto poDS = std::make_unique<GDAL_E57DatasetMultipleSDS>();
     799           2 :         CPLStringList aosSubDS;
     800           3 :         for (unsigned i = 0; i < asE57ImageDesc.size(); ++i)
     801             :         {
     802             :             const char *pszName =
     803           2 :                 asE57ImageDesc[i].aosExtraMD.FetchNameValueDef("NAME", "");
     804           2 :             const CPLString osSubdatasetName = CPLString().Printf(
     805             :                 "%s\"%s\":%s", E57_PREFIX, poOpenInfo->pszFilename,
     806             :                 bUniqueNames ? pszName
     807           4 :                              : CPLString().Printf("%u", i + 1).c_str());
     808             :             aosSubDS.SetNameValue(
     809           4 :                 CPLString().Printf("SUBDATASET_%u_NAME", i + 1),
     810           4 :                 osSubdatasetName.c_str());
     811           2 :             CPLString osDesc;
     812           2 :             if (bUniqueNames)
     813             :             {
     814           4 :                 osDesc = CPLString().Printf("Image %s (%dx%d)", pszName,
     815           2 :                                             asE57ImageDesc[i].nWidth,
     816           2 :                                             asE57ImageDesc[i].nHeight);
     817             :             }
     818           0 :             else if (pszName[0])
     819             :             {
     820           0 :                 osDesc = CPLString().Printf("Image %u (%s) (%dx%d)", i + 1,
     821           0 :                                             pszName, asE57ImageDesc[i].nWidth,
     822           0 :                                             asE57ImageDesc[i].nHeight);
     823             :             }
     824             :             else
     825             :             {
     826           0 :                 osDesc = CPLString().Printf("Image %u (%dx%d)", i + 1,
     827           0 :                                             asE57ImageDesc[i].nWidth,
     828           0 :                                             asE57ImageDesc[i].nHeight);
     829             :             }
     830             :             aosSubDS.SetNameValue(
     831           4 :                 CPLString().Printf("SUBDATASET_%u_DESC", i + 1),
     832           4 :                 osDesc.c_str());
     833             :         }
     834           1 :         poDS->SetMetadata(aosSubDS.List(), "SUBDATASETS");
     835           2 :         CPLStringList aosXMLE57;
     836           1 :         aosXMLE57.AddString(osXML.c_str());
     837           1 :         poDS->SetMetadata(aosXMLE57.List(), "xml:E57");
     838           1 :         return poDS.release();
     839             :     }
     840             : }
     841             : 
     842             : }  // namespace
     843             : 
     844             : /************************************************************************/
     845             : /*                         GDALRegister_E57()                           */
     846             : /************************************************************************/
     847             : 
     848        2058 : void GDALRegister_E57()
     849             : {
     850        2058 :     auto poDM = GetGDALDriverManager();
     851        2058 :     if (poDM->GetDriverByName("E57") != nullptr)
     852         283 :         return;
     853             : 
     854        3550 :     auto poDriver = std::make_unique<GDALDriver>();
     855             : 
     856        1775 :     poDriver->SetDescription("E57");
     857        1775 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
     858        1775 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
     859        1775 :                               "ASTM E57 3D file format (image part)");
     860        1775 :     poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "e57");
     861        1775 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/e57.html");
     862        1775 :     poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
     863             : 
     864        1775 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
     865             : 
     866        1775 :     poDriver->pfnOpen = GDAL_E57Dataset::Open;
     867        1775 :     poDriver->pfnIdentify = GDAL_E57Dataset::Identify;
     868             : 
     869        1775 :     poDM->RegisterDriver(poDriver.release());
     870             : }

Generated by: LCOV version 1.14