#include "nmNumericalResultPersistence.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace { const char g_aWellHistoryMagic[8] = { 'N', 'M', 'W', 'H', 'I', 'S', '3', 0 }; const quint32 g_nWellHistoryFormatVersion = 1; const quint32 g_nEndianMarker = 0x01020304u; const quint32 g_nMaximumCurveSeries = 64u; const quint64 g_nMaximumCurvePoints = 50000000ull; const qint64 g_nStreamChunkBytes = 4 * 1024 * 1024; bool setError(QString* pError, const QString& sError) { if(pError != NULL) { *pError = sError; } return false; } bool writeAll(QFile& oFile, const char* pData, quint64 nBytes) { quint64 nWritten = 0; while(nWritten < nBytes) { const qint64 nChunk = static_cast(qMin( static_cast(g_nStreamChunkBytes), nBytes - nWritten)); const qint64 nResult = oFile.write(pData + nWritten, nChunk); if(nResult != nChunk) { return false; } nWritten += static_cast(nResult); } return true; } bool readAll(QFile& oFile, char* pData, quint64 nBytes) { quint64 nRead = 0; while(nRead < nBytes) { const qint64 nChunk = static_cast(qMin( static_cast(g_nStreamChunkBytes), nBytes - nRead)); const qint64 nResult = oFile.read(pData + nRead, nChunk); if(nResult != nChunk) { return false; } nRead += static_cast(nResult); } return true; } template bool writePod(QFile& oFile, const T& oValue) { return writeAll(oFile, reinterpret_cast(&oValue), static_cast(sizeof(T))); } template bool readPod(QFile& oFile, T& oValue) { return readAll(oFile, reinterpret_cast(&oValue), static_cast(sizeof(T))); } bool safeMultiply(quint64 nLeft, quint64 nRight, quint64& nProduct) { if(nLeft != 0 && nRight > (~static_cast(0)) / nLeft) { return false; } nProduct = nLeft * nRight; return true; } bool writeCurve(QFile& oFile, const QVector >& vecCurve) { const quint32 nSeriesCount = static_cast(vecCurve.size()); if(!writePod(oFile, nSeriesCount)) { return false; } for(int nSeries = 0; nSeries < vecCurve.size(); ++nSeries) { const QVector& vecValues = vecCurve[nSeries]; const quint64 nPointCount = static_cast(vecValues.size()); quint64 nBytes = 0; if(!safeMultiply(nPointCount, sizeof(double), nBytes) || !writePod(oFile, nPointCount) || (nBytes > 0 && !writeAll(oFile, reinterpret_cast(vecValues.constData()), nBytes))) { return false; } } return true; } bool readCurve(QFile& oFile, QVector >& vecCurve, quint64& nTotalPointCount) { quint32 nSeriesCount = 0; if(!readPod(oFile, nSeriesCount) || nSeriesCount > g_nMaximumCurveSeries) { return false; } QVector > vecLoaded; vecLoaded.resize(static_cast(nSeriesCount)); for(quint32 nSeries = 0; nSeries < nSeriesCount; ++nSeries) { quint64 nPointCount = 0; quint64 nBytes = 0; if(!readPod(oFile, nPointCount) || nPointCount > g_nMaximumCurvePoints || nTotalPointCount > g_nMaximumCurvePoints - nPointCount || !safeMultiply(nPointCount, sizeof(double), nBytes) || nBytes > static_cast(oFile.bytesAvailable()) || nPointCount > static_cast(INT_MAX)) { return false; } nTotalPointCount += nPointCount; vecLoaded[static_cast(nSeries)].resize( static_cast(nPointCount)); if(nBytes > 0 && !readAll(oFile, reinterpret_cast( vecLoaded[static_cast(nSeries)].data()), nBytes)) { return false; } } vecCurve.swap(vecLoaded); return true; } bool hasExactMagic(const char* pActual, const char* pExpected, int nSize) { for(int nIndex = 0; nIndex < nSize; ++nIndex) { if(pActual[nIndex] != pExpected[nIndex]) { return false; } } return true; } } nmNumericalFileReference::nmNumericalFileReference() : m_nLength(0) { } bool nmNumericalFileReference::isValid() const { QString sNormalizedPath; if(!nmNumericalResultPersistence::normalizeRelativePath( m_sRelativePath, sNormalizedPath) || sNormalizedPath != m_sRelativePath || m_sSha1.size() != 40) { return false; } for(int nIndex = 0; nIndex < m_sSha1.size(); ++nIndex) { const QChar oChar = m_sSha1[nIndex]; if(!((oChar >= '0' && oChar <= '9') || (oChar >= 'a' && oChar <= 'f'))) { return false; } } return true; } nmNumericalWindowPayloadReferences::nmNumericalWindowPayloadReferences() : m_bHasCurrentGrid(false), m_bHasSnapshot(false) { } int nmNumericalResultPersistence::projectVersion() { return 3; } quint64 nmNumericalResultPersistence::maximumBinaryPayloadBytes() { return 512ull * 1024ull * 1024ull; } bool nmNumericalResultPersistence::normalizeRelativePath( const QString& sPath, QString& sNormalizedPath) { sNormalizedPath.clear(); if(sPath.isEmpty() || QDir::isAbsolutePath(sPath) || sPath.contains(':')) { return false; } QString sForwardPath = sPath; sForwardPath.replace('\\', '/'); const QStringList listParts = sForwardPath.split( '/', QString::KeepEmptyParts); if(listParts.isEmpty()) { return false; } for(int nIndex = 0; nIndex < listParts.size(); ++nIndex) { if(listParts[nIndex].isEmpty() || listParts[nIndex] == "." || listParts[nIndex] == "..") { return false; } } sNormalizedPath = listParts.join("/"); return !sNormalizedPath.isEmpty(); } bool nmNumericalResultPersistence::resolveReferencedPath( const QString& sRootDirectory, const QString& sRelativePath, QString& sAbsolutePath) { QString sBaseCanonical; QString sFileCanonical; sAbsolutePath.clear(); QString sNormalizedPath; if(!normalizeRelativePath(sRelativePath, sNormalizedPath)) { return false; } QString sRoot = QDir::cleanPath(QFileInfo(sRootDirectory).absoluteFilePath()); QString sTarget = QDir::cleanPath( QDir(sRoot).absoluteFilePath(sNormalizedPath)); sRoot.replace('\\', '/'); sTarget.replace('\\', '/'); const QString sRootPrefix = sRoot.endsWith('/') ? sRoot : sRoot + "/"; if(!sTarget.startsWith(sRootPrefix, Qt::CaseInsensitive)) { return false; } // 已存在文件再核对规范路径,防止目录联接或符号链接逃逸成果根目录。 sBaseCanonical = QFileInfo(sRoot).canonicalFilePath(); sFileCanonical = QFileInfo(sTarget).canonicalFilePath(); if(!sBaseCanonical.isEmpty() && !sFileCanonical.isEmpty()) { QString sCanonicalPrefix = QDir::fromNativeSeparators(sBaseCanonical); if(!sCanonicalPrefix.endsWith('/')) { sCanonicalPrefix += "/"; } if(!QDir::fromNativeSeparators(sFileCanonical).startsWith( sCanonicalPrefix, Qt::CaseInsensitive)) { return false; } } sAbsolutePath = QDir::toNativeSeparators(sTarget); return true; } bool nmNumericalResultPersistence::buildFileReference( const QString& sAbsolutePath, const QString& sRelativePath, nmNumericalFileReference& oReference) { QString sNormalizedPath; if(!normalizeRelativePath(sRelativePath, sNormalizedPath)) { return false; } QFile oFile(sAbsolutePath); if(!oFile.open(QIODevice::ReadOnly)) { return false; } QCryptographicHash oHash(QCryptographicHash::Sha1); while(!oFile.atEnd()) { const QByteArray baChunk = oFile.read(g_nStreamChunkBytes); if(baChunk.isEmpty() && oFile.error() != QFile::NoError) { oFile.close(); return false; } oHash.addData(baChunk); } const qint64 nSize = oFile.size(); oFile.close(); if(nSize < 0) { return false; } oReference.m_sRelativePath = sNormalizedPath; oReference.m_nLength = static_cast(nSize); oReference.m_sSha1 = QString::fromLatin1(oHash.result().toHex()); return oReference.isValid(); } bool nmNumericalResultPersistence::validateFileReference( const QString& sRootDirectory, const nmNumericalFileReference& oReference, QString* pAbsolutePath, QString* pError) { if(pError != NULL) { pError->clear(); } if(!oReference.isValid()) { return setError(pError, "Numerical file reference is invalid."); } QString sFilePath; if(!resolveReferencedPath(sRootDirectory, oReference.m_sRelativePath, sFilePath)) { return setError(pError, "Numerical file reference leaves its root directory."); } const QFileInfo oInfo(sFilePath); if(!oInfo.exists() || !oInfo.isFile() || oInfo.size() < 0 || static_cast(oInfo.size()) != oReference.m_nLength) { return setError(pError, "Numerical file length does not match its manifest."); } nmNumericalFileReference oActual; if(!buildFileReference(sFilePath, oReference.m_sRelativePath, oActual) || oActual.m_nLength != oReference.m_nLength || oActual.m_sSha1 != oReference.m_sSha1) { return setError(pError, "Numerical file SHA-1 does not match its manifest."); } if(pAbsolutePath != NULL) { *pAbsolutePath = sFilePath; } return true; } bool nmNumericalResultPersistence::writeWellHistory( const QString& sFilePath, const nmNumericalWellHistoryData& oHistory, QString* pError) { if(pError != NULL) { pError->clear(); } QFile oFile(sFilePath); if(!oFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { return setError(pError, "Cannot create numerical well history file."); } const bool bWritten = writeAll(oFile, g_aWellHistoryMagic, 8) && writePod(oFile, g_nWellHistoryFormatVersion) && writePod(oFile, g_nEndianMarker) && writeCurve(oFile, oHistory.m_vecPressure) && writeCurve(oFile, oHistory.m_vecLogLog) && writeCurve(oFile, oHistory.m_vecSemiLog) && oFile.flush(); oFile.close(); if(!bWritten) { QFile::remove(sFilePath); return setError(pError, "Cannot write complete numerical well history file."); } return true; } bool nmNumericalResultPersistence::readWellHistory( const QString& sFilePath, nmNumericalWellHistoryData& oHistory, QString* pError) { if(pError != NULL) { pError->clear(); } const QFileInfo oInfo(sFilePath); if(!oInfo.exists() || oInfo.size() < 0 || static_cast(oInfo.size()) > maximumBinaryPayloadBytes()) { return setError(pError, "Numerical well history payload is missing or too large."); } try { QFile oFile(sFilePath); if(!oFile.open(QIODevice::ReadOnly)) { return setError(pError, "Cannot open numerical well history file."); } char aMagic[8] = { 0 }; quint32 nFormatVersion = 0; quint32 nEndianMarker = 0; quint64 nTotalPointCount = 0; nmNumericalWellHistoryData oLoaded; const bool bRead = readAll(oFile, aMagic, 8) && hasExactMagic(aMagic, g_aWellHistoryMagic, 8) && readPod(oFile, nFormatVersion) && nFormatVersion == g_nWellHistoryFormatVersion && readPod(oFile, nEndianMarker) && nEndianMarker == g_nEndianMarker && readCurve(oFile, oLoaded.m_vecPressure, nTotalPointCount) && readCurve(oFile, oLoaded.m_vecLogLog, nTotalPointCount) && readCurve(oFile, oLoaded.m_vecSemiLog, nTotalPointCount) && oFile.atEnd(); oFile.close(); if(!bRead) { return setError(pError, "Numerical well history file is damaged."); } oHistory = oLoaded; return true; } catch(const std::bad_alloc&) { return setError(pError, "Not enough memory to load numerical well history."); } } bool nmNumericalResultPersistence::writeGrid( vtkUnstructuredGrid* pGrid, const QString& sFilePath, QString* pError) { if(pError != NULL) { pError->clear(); } if(pGrid == NULL || pGrid->GetNumberOfPoints() <= 0 || pGrid->GetNumberOfCells() <= 0) { return setError(pError, "Numerical grid is empty."); } vtkNew pWriter; pWriter->SetInputData(pGrid); pWriter->SetFileName(sFilePath.toLocal8Bit().constData()); pWriter->SetDataModeToBinary(); if(pWriter->Write() == 0 || !QFileInfo(sFilePath).isFile() || QFileInfo(sFilePath).size() <= 0) { QFile::remove(sFilePath); return setError(pError, "Cannot write numerical VTU grid."); } return true; } bool nmNumericalResultPersistence::readGrid( const QString& sFilePath, vtkSmartPointer& pGrid, QString* pError) { if(pError != NULL) { pError->clear(); } pGrid = NULL; if(!QFileInfo(sFilePath).isFile()) { return setError(pError, "Numerical VTU grid is missing."); } try { vtkNew pReader; pReader->SetFileName(sFilePath.toLocal8Bit().constData()); pReader->Update(); vtkUnstructuredGrid* pOutput = pReader->GetOutput(); if(pOutput == NULL || pOutput->GetNumberOfPoints() <= 0 || pOutput->GetNumberOfCells() <= 0) { return setError(pError, "Numerical VTU grid is invalid."); } pGrid = pOutput; return pGrid != NULL; } catch(const std::bad_alloc&) { pGrid = NULL; return setError(pError, "Not enough memory to load numerical VTU grid."); } }