diff --git a/kasten/controllers/test/customtostringtest.cpp b/kasten/controllers/test/customtostringtest.cpp index 96a2d73e..ad124637 100644 --- a/kasten/controllers/test/customtostringtest.cpp +++ b/kasten/controllers/test/customtostringtest.cpp @@ -1,132 +1,141 @@ /* * This file is part of the Okteta Kasten module, made within the KDE community. * * Copyright 2013 Alex Richardson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "view/structures/script/scriptengineinitializer.hpp" #include "view/structures/parsers/scriptvalueconverter.hpp" #include "view/structures/datatypes/topleveldatainformation.hpp" #include "testutils.hpp" #include +namespace Okteta { +using TextStreamFunction = QTextStream& (*)(QTextStream&); +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) +constexpr TextStreamFunction hex = Qt::hex; +#else +constexpr TextStreamFunction hex = ::hex; +#endif +} + class CustomToStringTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testUuid_data(); void testUuid(); }; static uchar uuid1[16] = { 0x55, 0x0e, 0x84, 0x00, 0xe2, 0x9b, 0x41, 0xd4, 0xa7, 0x16, 0x44, 0x66, 0x55, 0x44, 0x00, 0x00 }; static uchar uuid2[16] = { 0x3f, 0x25, 0x04, 0xe0, 0x4f, 0x89, 0x11, 0xd3, 0x9a, 0x0c, 0x03, 0x05, 0xe8, 0x2c, 0x33, 0x01 }; static uchar nullUuid[16] = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }; void CustomToStringTest::initTestCase() { // needed so that imports can be resolved QString examples = QFINDTESTDATA("../view/structures/examples"); QVERIFY2(!examples.isEmpty(), "Test data must exist!"); qputenv("XDG_DATA_DIRS", QFile::encodeName(QFileInfo(examples).absoluteFilePath())); } void CustomToStringTest::testUuid_data() { QTest::addColumn("isGUID"); QTest::addColumn("uuidString"); QTest::addColumn("data"); QTest::newRow("uuid1") << false << "{550e8400-e29b-41d4-a716-446655440000}" << QByteArray::fromRawData(reinterpret_cast(uuid1), sizeof(uuid1)); QTest::newRow("uuid2") << false << "{3f2504e0-4f89-11d3-9a0c-0305e82c3301}" << QByteArray::fromRawData(reinterpret_cast(uuid2), sizeof(uuid2)); QTest::newRow("null uuid") << false << "{00000000-0000-0000-0000-000000000000}" << QByteArray::fromRawData(reinterpret_cast(nullUuid), sizeof(nullUuid)); // now the same just as a Microsoft GUID QTest::newRow("guid1") << true << "{00840e55-9be2-d441-a716-446655440000}" << QByteArray::fromRawData(reinterpret_cast(uuid1), sizeof(uuid1)); QTest::newRow("guid2") << true << "{e004253f-894f-d311-9a0c-0305e82c3301}" << QByteArray::fromRawData(reinterpret_cast(uuid2), sizeof(uuid2)); QTest::newRow("null guid") << true << "{00000000-0000-0000-0000-000000000000}" << QByteArray::fromRawData(reinterpret_cast(nullUuid), sizeof(nullUuid)); } void CustomToStringTest::testUuid() { QFETCH(QByteArray, data); QFETCH(QString, uuidString); QFETCH(bool, isGUID); QScriptEngine* eng = ScriptEngineInitializer::newEngine(); auto* logger = new ScriptLogger(); logger->setLogToStdOut(true); DataInformation* structure = nullptr; if (isGUID) { structure = Utils::evalAndParse(eng, "var u = importScript('uuid.js'); u.GUID();", logger); } else { structure = Utils::evalAndParse(eng, "var u = importScript('uuid.js'); u.UUID();", logger); } QVERIFY(structure); TopLevelDataInformation top(structure, logger, eng); QCOMPARE(structure->childCount(), 4u); QVERIFY(structure->toStringFunction().isFunction()); Okteta::ByteArrayModel model(reinterpret_cast(data.constData()), data.size()); model.setAutoDelete(false); top.read(&model, 0, Okteta::ArrayChangeMetricsList(), true); QCOMPARE(structure->childAt(0)->effectiveByteOrder(), isGUID ? QSysInfo::LittleEndian : QSysInfo::BigEndian); QCOMPARE(structure->childAt(1)->effectiveByteOrder(), isGUID ? QSysInfo::LittleEndian : QSysInfo::BigEndian); QCOMPARE(structure->childAt(2)->effectiveByteOrder(), isGUID ? QSysInfo::LittleEndian : QSysInfo::BigEndian); bool ok; quint32 val1 = uuidString.midRef(1, 8).toUInt(&ok, 16); QVERIFY(ok); quint16 val2 = uuidString.midRef(10, 4).toUShort(&ok, 16); QVERIFY(ok); quint16 val3 = uuidString.midRef(15, 4).toUShort(&ok, 16); QVERIFY(ok); - qDebug() << hex << val1 << val2 << val3; + qDebug() << Okteta::hex << val1 << val2 << val3; QCOMPARE(structure->childAt(0)->asPrimitive()->value().value(), val1); QCOMPARE(structure->childAt(1)->asPrimitive()->value().value(), val2); QCOMPARE(structure->childAt(2)->asPrimitive()->value().value(), val3); QString typeStr = isGUID ? QStringLiteral("GUID") : QStringLiteral("UUID"); QCOMPARE(structure->typeName(), typeStr); QCOMPARE(structure->valueString(), uuidString); } QTEST_GUILESS_MAIN(CustomToStringTest) #include "customtostringtest.moc" diff --git a/kasten/controllers/view/structures/datatypes/topleveldatainformation.cpp b/kasten/controllers/view/structures/datatypes/topleveldatainformation.cpp index a680abe0..44897a63 100644 --- a/kasten/controllers/view/structures/datatypes/topleveldatainformation.cpp +++ b/kasten/controllers/view/structures/datatypes/topleveldatainformation.cpp @@ -1,270 +1,279 @@ /* * This file is part of the Okteta Kasten Framework, made within the KDE community. * * Copyright 2010, 2011 Alex Richardson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "topleveldatainformation.hpp" #include "datainformation.hpp" #include "primitive/pointerdatainformation.hpp" #include "datainformationwithchildren.hpp" #include "../script/scripthandler.hpp" #include "../script/scriptlogger.hpp" #include "../script/scriptengineinitializer.hpp" #include "../structlogging.hpp" #include "primitivefactory.hpp" #include #include #include +namespace Okteta { +using TextStreamFunction = QTextStream& (*)(QTextStream&); +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) +constexpr TextStreamFunction hex = Qt::hex; +#else +constexpr TextStreamFunction hex = ::hex; +#endif +} + const quint64 TopLevelDataInformation::INVALID_OFFSET = std::numeric_limits::max(); TopLevelDataInformation::TopLevelDataInformation(DataInformation* data, ScriptLogger* logger, QScriptEngine* engine, const QFileInfo& structureFile) : QObject(nullptr) , mData(data) , mLogger(logger) , mStructureFile(structureFile) , mValid(!data->isDummy()) , mChildDataChanged(false) { Q_CHECK_PTR(mData); mData->setParent(this); setObjectName(mData->name()); if (!mLogger) { mLogger.reset(new ScriptLogger()); } if (!engine) { engine = ScriptEngineInitializer::newEngine(); } mScriptHandler.reset(new ScriptHandler(engine, this)); } TopLevelDataInformation::~TopLevelDataInformation() = default; void TopLevelDataInformation::validate() { logger()->info(mData.data()) << "Validation requested."; mScriptHandler->validateData(mData.data()); } QScriptEngine* TopLevelDataInformation::scriptEngine() const { return mScriptHandler->engine(); } void TopLevelDataInformation::resetValidationState() { mData->resetValidationState(); } void TopLevelDataInformation::read(Okteta::AbstractByteArrayModel* input, Okteta::Address address, const Okteta::ArrayChangeMetricsList& changesList, bool forceRead) { mChildDataChanged = false; const bool updateNeccessary = forceRead || isReadingNecessary(input, address, changesList); if (!updateNeccessary) { return; } quint64 remainingBits = BitCount64(input->size() - address) * 8; quint8 bitOffset = 0; mData->beginRead(); // before reading set wasAbleToRead to false mData->resetValidationState(); // reading new data -> validation state is old const DataInformation* oldData = mData.data(); mScriptHandler->updateDataInformation(mData.data()); // unlikely that this is useful, but maybe someone needs it if (mData.data() != oldData) { mLogger->info() << "Main element was replaced!"; } mData->readData(input, address, remainingBits, &bitOffset); // Read all the delayed PointerDataInformation // We do this because the pointed data is independent from the // structure containing the pointer and so we can use fields // which come after the pointer itself in the structure while (!mDelayedRead.isEmpty()) { mDelayedRead.dequeue()->delayedReadData(input, address); } if (mChildDataChanged) { emit dataChanged(); mChildDataChanged = false; } mLastModel = input; mLastReadOffset = address; } void TopLevelDataInformation::enqueueReadData(PointerDataInformation* toRead) { mDelayedRead.append(toRead); } bool TopLevelDataInformation::isReadingNecessary(Okteta::AbstractByteArrayModel* model, Okteta::Address address, const Okteta::ArrayChangeMetricsList& changesList) { if (model != mLastModel) { return true; // whenever we have a new model we have to read } if (isLockedFor(model)) { address = lockPositionFor(model); } if (quint64(address) != mLastReadOffset) { return true; // address as changed, we have to read again } // address has not changed, check whether the changes affect us // TODO always return true if structure contains pointers if (changesList.isEmpty()) { return false; // no changes } const Okteta::Address structureSizeInBytes = (mData->size() / 8) + (mData->size() % 8 == 0 ? 0 : 1); const Okteta::Address end = address + structureSizeInBytes; for (int i = 0; i < changesList.length(); ++i) { const Okteta::ArrayChangeMetrics& change = changesList.at(i); // this is valid for all types if (change.offset() >= end) { // insertion/deletion/swapping is after end of structure, it doesn't interest us continue; } // now handle special cases if (change.type() == Okteta::ArrayChangeMetrics::Replacement) { // handle it for replacements if (change.lengthChange() == 0 && change.offset() + change.removeLength() <= address) { // no length change and it doesn't affect structure since it is before // could use insertLength() instead of removeLength() since they are the same continue; } // something was removed/inserted before start of structure // which means all bytes have moved forwards/backwards: reread all return true; } if (change.type() == Okteta::ArrayChangeMetrics::Swapping) { if (change.secondEnd() < address) { // swapped ranges end before start, i.e. the range of interest does not change continue; } // Not sure what other possibilities there are, but rather waste CPU or I/O rereading // than showing outdated values return true; } qCDebug(LOG_KASTEN_OKTETA_CONTROLLERS_STRUCTURES) << "Invalid change"; continue; } return false; // nothing affected us } void TopLevelDataInformation::lockPositionToOffset(Okteta::Address offset, const Okteta::AbstractByteArrayModel* model) { if (quint64(offset) == INVALID_OFFSET) { // we use quint64 max to indicate not locked -> error out mLogger->error() << "Attempting to lock at uint64_max, this is forbidden."; return; } mLockedPositions.insert(model, quint64(offset)); qCDebug(LOG_KASTEN_OKTETA_CONTROLLERS_STRUCTURES) - << mData->name() << ": Locking start offset in model" << model << "to position" << hex << offset; + << mData->name() << ": Locking start offset in model" << model << "to position" << Okteta::hex << offset; // remove when deleted connect(model, &Okteta::AbstractByteArrayModel::destroyed, this, &TopLevelDataInformation::removeByteArrayModelFromList); } void TopLevelDataInformation::unlockPosition(const Okteta::AbstractByteArrayModel* model) { Q_ASSERT(mLockedPositions.contains(model) && mLockedPositions.value(model) != INVALID_OFFSET); qCDebug(LOG_KASTEN_OKTETA_CONTROLLERS_STRUCTURES) << "removing lock at position" << mLockedPositions.value(model) << ", model=" << model; mLockedPositions.insert(model, INVALID_OFFSET); } void TopLevelDataInformation::removeByteArrayModelFromList(QObject* obj) { const Okteta::AbstractByteArrayModel* model = static_cast(obj); mLockedPositions.remove(model); } bool TopLevelDataInformation::isLockedByDefault() const { return mDefaultLockOffset != INVALID_OFFSET; } void TopLevelDataInformation::setDefaultLockOffset(Okteta::Address offset) { if (quint64(offset) == INVALID_OFFSET) { // we use quint64 max to indicate not locked -> error out mLogger->error() << "Attempting to lock by default at uint64_max, this is forbidden."; return; } mDefaultLockOffset = offset; } void TopLevelDataInformation::newModelActivated(Okteta::AbstractByteArrayModel* model) { // don't add null pointers to map if (model && !mLockedPositions.contains(model)) { // if this structure has no default lock offset, mDefaultLockOfsset will contain NOT_LOCKED mLockedPositions.insert(model, mDefaultLockOffset); if (mDefaultLockOffset == INVALID_OFFSET) { qCDebug(LOG_KASTEN_OKTETA_CONTROLLERS_STRUCTURES) << "new model activated:" << model << ", not locked."; } else { qCDebug(LOG_KASTEN_OKTETA_CONTROLLERS_STRUCTURES) << "new model activated:" << model << ", locked at 0x" << QString::number(mDefaultLockOffset, 16); } } } bool TopLevelDataInformation::isLockedFor(const Okteta::AbstractByteArrayModel* model) const { Q_ASSERT(mLockedPositions.contains(model)); return mLockedPositions.value(model, 0) != INVALID_OFFSET; } quint64 TopLevelDataInformation::lockPositionFor(const Okteta::AbstractByteArrayModel* model) const { Q_ASSERT(mLockedPositions.contains(model) && mLockedPositions.value(model) != INVALID_OFFSET); return mLockedPositions.value(model); } int TopLevelDataInformation::indexOf(const DataInformation* const data) const { Q_ASSERT(data == mData.data()); Q_UNUSED(data) return mIndex; } void TopLevelDataInformation::setActualDataInformation(DataInformation* newData) { Q_ASSERT(newData); mData.reset(newData); } bool TopLevelDataInformation::isTopLevel() const { return true; }