From a1253050fec19194f5340463b202edf45438409c Mon Sep 17 00:00:00 2001 From: Nikolaj Schlej Date: Tue, 29 Dec 2015 23:39:43 +0100 Subject: [PATCH] UT NE A17 - nothing major, just reworks and preparations for the new rebuild code - added try / catch bad_alloc to prevent crashes during decompression of malformed Tiano/EFI11 compressed data --- UEFITool/uefitool.cpp | 2 +- common/Tiano/EfiTianoDecompress.h | 121 +++++++------- common/ffsbuilder.cpp | 267 +++++++++++++----------------- common/ffsbuilder.h | 5 +- common/ffsparser.cpp | 174 +++++++------------ common/fitparser.cpp | 14 +- common/parsingdata.h | 2 - common/treeitem.cpp | 152 +---------------- common/treeitem.h | 75 +++++---- common/treemodel.cpp | 79 ++++++++- common/treemodel.h | 10 +- common/types.cpp | 11 +- common/types.h | 9 +- common/utility.cpp | 21 ++- 14 files changed, 395 insertions(+), 547 deletions(-) diff --git a/UEFITool/uefitool.cpp b/UEFITool/uefitool.cpp index a08e175..7885403 100644 --- a/UEFITool/uefitool.cpp +++ b/UEFITool/uefitool.cpp @@ -17,7 +17,7 @@ UEFITool::UEFITool(QWidget *parent) : QMainWindow(parent), ui(new Ui::UEFITool), -version(tr("0.30.0_alpha16")) +version(tr("0.30.0_alpha17")) { clipboard = QApplication::clipboard(); diff --git a/common/Tiano/EfiTianoDecompress.h b/common/Tiano/EfiTianoDecompress.h index 8c5e2e6..7a8a0f1 100644 --- a/common/Tiano/EfiTianoDecompress.h +++ b/common/Tiano/EfiTianoDecompress.h @@ -32,108 +32,105 @@ Providing both EFI and Tiano decompress algorithms. extern "C" { #endif - typedef struct { - UINT32 CompSize; - UINT32 OrigSize; - } EFI_TIANO_HEADER; +typedef struct { + UINT32 CompSize; + UINT32 OrigSize; +} EFI_TIANO_HEADER; - EFI_STATUS - EFIAPI - EfiTianoGetInfo( +EFI_STATUS +EFIAPI +EfiTianoGetInfo( const VOID *Source, UINT32 SrcSize, UINT32 *DstSize, UINT32 *ScratchSize ) - /*++ +/*++ - Routine Description: +Routine Description: - The implementation is same as that of EFI_DECOMPRESS_PROTOCOL.GetInfo(). +The implementation is same as that of EFI_DECOMPRESS_PROTOCOL.GetInfo(). - Arguments: +Arguments: - This - The protocol instance pointer - Source - The source buffer containing the compressed data. - SrcSize - The size of source buffer - DstSize - The size of destination buffer. - ScratchSize - The size of scratch buffer. +Source - The source buffer containing the compressed data. +SrcSize - The size of source buffer +DstSize - The size of destination buffer. +ScratchSize - The size of scratch buffer. - Returns: +Returns: - EFI_SUCCESS - The size of destination buffer and the size of scratch buffer are successfully retrieved. - EFI_INVALID_PARAMETER - The source data is corrupted +EFI_SUCCESS - The size of destination buffer and the size of scratch buffer are successfully retrieved. +EFI_INVALID_PARAMETER - The source data is corrupted - --*/ - ; +--*/ +; - EFI_STATUS - EFIAPI - EfiDecompress( - const VOID *Source, +EFI_STATUS +EFIAPI +EfiDecompress( + const VOID *Source, UINT32 SrcSize, VOID *Destination, UINT32 DstSize, VOID *Scratch, UINT32 ScratchSize - ) - /*++ + ); +/*++ - Routine Description: +Routine Description: - The implementation is same as that of EFI_DECOMPRESS_PROTOCOL.Decompress(). +The implementation is same as that of EFI_DECOMPRESS_PROTOCOL.Decompress(). - Arguments: +Arguments: - This - The protocol instance pointer - Source - The source buffer containing the compressed data. - SrcSize - The size of source buffer - Destination - The destination buffer to store the decompressed data - DstSize - The size of destination buffer. - Scratch - The buffer used internally by the decompress routine. This buffer is needed to store intermediate data. - ScratchSize - The size of scratch buffer. +Source - The source buffer containing the compressed data. +SrcSize - The size of source buffer +Destination - The destination buffer to store the decompressed data +DstSize - The size of destination buffer. +Scratch - The buffer used internally by the decompress routine. This buffer is needed to store intermediate data. +ScratchSize - The size of scratch buffer. - Returns: +Returns: - EFI_SUCCESS - Decompression is successful - EFI_INVALID_PARAMETER - The source data is corrupted +EFI_SUCCESS - Decompression is successful +EFI_INVALID_PARAMETER - The source data is corrupted - --*/ - ; +--*/ +; - EFI_STATUS - EFIAPI - TianoDecompress( - const VOID *Source, +EFI_STATUS +EFIAPI +TianoDecompress( + const VOID *Source, UINT32 SrcSize, VOID *Destination, UINT32 DstSize, VOID *Scratch, UINT32 ScratchSize ) - /*++ +/*++ - Routine Description: +Routine Description: - The implementation is same as that of EFI_TIANO_DECOMPRESS_PROTOCOL.Decompress(). +The implementation is same as that of EFI_TIANO_DECOMPRESS_PROTOCOL.Decompress(). - Arguments: +Arguments: - This - The protocol instance pointer - Source - The source buffer containing the compressed data. - SrcSize - The size of source buffer - Destination - The destination buffer to store the decompressed data - DstSize - The size of destination buffer. - Scratch - The buffer used internally by the decompress routine. This buffer is needed to store intermediate data. - ScratchSize - The size of scratch buffer. +Source - The source buffer containing the compressed data. +SrcSize - The size of source buffer +Destination - The destination buffer to store the decompressed data +DstSize - The size of destination buffer. +Scratch - The buffer used internally by the decompress routine. This buffer is needed to store intermediate data. +ScratchSize - The size of scratch buffer. - Returns: +Returns: - EFI_SUCCESS - Decompression is successful - EFI_INVALID_PARAMETER - The source data is corrupted +EFI_SUCCESS - Decompression is successful +EFI_INVALID_PARAMETER - The source data is corrupted - --*/ - ; +--*/ +; #ifdef __cplusplus } diff --git a/common/ffsbuilder.cpp b/common/ffsbuilder.cpp index dc489f9..ef258ff 100644 --- a/common/ffsbuilder.cpp +++ b/common/ffsbuilder.cpp @@ -54,10 +54,9 @@ STATUS FfsBuilder::buildCapsule(const QModelIndex & index, QByteArray & capsule) if (!index.isValid()) return ERR_INVALID_PARAMETER; - STATUS result; - // No action required if (model->action(index) == Actions::NoAction) { + // Use original item data capsule = model->header(index).append(model->body(index)); return ERR_SUCCESS; } @@ -69,41 +68,49 @@ STATUS FfsBuilder::buildCapsule(const QModelIndex & index, QByteArray & capsule) // Clear the supplied QByteArray capsule.clear(); - // Build children - for (int i = 0; i < model->rowCount(index); i++) { - QModelIndex currentChild = index.child(i, 0); - QByteArray currentData; - // Check child type - if (model->type(currentChild) == Types::Image) { - if (model->subtype(currentChild) == Subtypes::IntelImage) - result = buildIntelImage(currentChild, currentData); - else - result = buildRawArea(currentChild, currentData); - - // Check build result - if (result) { - msg(tr("buildCapsule: building of \"%1\" failed with error \"%2\", original item data used").arg(model->name(currentChild)).arg(errorCodeToQString(result)), currentChild); - capsule.append(model->header(currentChild)).append(model->body(currentChild)); - } - else - capsule.append(currentData); - } - else { - msg(tr("buildCapsule: unexpected child item of type \"%1\" can't be processed, original item data used").arg(itemTypeToQString(model->type(currentChild))), currentChild); - capsule.append(model->header(currentChild)).append(model->body(currentChild)); - } + // Right now there is only one capsule image element supported + if (model->rowCount(index) != 1) { + msg(tr("buildCapsule: building of capsules with %1 elements are not supported, original item data used").arg(model->rowCount(index)), index); + // Use original item data + capsule = model->header(index).append(model->body(index)); + return ERR_SUCCESS; } + + // Build image + QModelIndex imageIndex = index.child(0, 0); + QByteArray imageData; + + // Check image type + if (model->type(imageIndex) == Types::Image) { + STATUS result; + if (model->subtype(imageIndex) == Subtypes::IntelImage) + result = buildIntelImage(imageIndex, imageData); + else + result = buildRawArea(imageIndex, imageData); + // Check build result + if (result) { + msg(tr("buildCapsule: building of \"%1\" failed with error \"%2\", original item data used").arg(model->name(imageIndex)).arg(errorCodeToQString(result)), imageIndex); + capsule.append(model->header(imageIndex)).append(model->body(imageIndex)); + } + else + capsule.append(imageData); + } + else { + msg(tr("buildCapsule: unexpected child item of type \"%1\" can't be processed, original item data used").arg(itemTypeToQString(model->type(imageIndex))), imageIndex); + capsule.append(model->header(imageIndex)).append(model->body(imageIndex)); + } + // Check size of reconstructed capsule, it must remain the same UINT32 newSize = capsule.size(); UINT32 oldSize = model->body(index).size(); if (newSize > oldSize) { - msg(tr("buildCapsule: new capsule size %1h (%2) is bigger than the original %3h (%4)") + msg(tr("buildCapsule: new capsule body size %1h (%2) is bigger than the original %3h (%4)") .hexarg(newSize).arg(newSize).hexarg(oldSize).arg(oldSize),index); return ERR_INVALID_PARAMETER; } else if (newSize < oldSize) { - msg(tr("buildCapsule: new capsule size %1h (%2) is smaller than the original %3h (%4)") + msg(tr("buildCapsule: new capsule body size %1h (%2) is smaller than the original %3h (%4)") .hexarg(newSize).arg(newSize).hexarg(oldSize).arg(oldSize), index); return ERR_INVALID_PARAMETER; } @@ -125,7 +132,7 @@ STATUS FfsBuilder::buildIntelImage(const QModelIndex & index, QByteArray & intel if (!index.isValid()) return ERR_SUCCESS; - UINT8 result; + // No action if (model->action(index) == Actions::NoAction) { @@ -136,75 +143,94 @@ STATUS FfsBuilder::buildIntelImage(const QModelIndex & index, QByteArray & intel // Other supported actions else if (model->action(index) == Actions::Rebuild) { intelImage.clear(); - // First child will always be descriptor for this type of image - QByteArray descriptor; - result = buildRegion(index.child(0, 0), descriptor); - if (result) - return result; - intelImage.append(descriptor); - - const FLASH_DESCRIPTOR_MAP* descriptorMap = (const FLASH_DESCRIPTOR_MAP*)(descriptor.constData() + sizeof(FLASH_DESCRIPTOR_HEADER)); - const FLASH_DESCRIPTOR_REGION_SECTION* regionSection = (const FLASH_DESCRIPTOR_REGION_SECTION*)calculateAddress8((const UINT8*)descriptor.constData(), descriptorMap->RegionBase); + // First child will always be descriptor for this type of image, and it's read only + QByteArray descriptor = model->header(index.child(0, 0)).append(model->body(index.child(0, 0))); + + // Other regions can be in different order, GbE, PDR and EC my be skipped QByteArray gbe; - UINT32 gbeBegin = calculateRegionOffset(regionSection->GbeBase); - UINT32 gbeEnd = gbeBegin + calculateRegionSize(regionSection->GbeBase, regionSection->GbeLimit); QByteArray me; - UINT32 meBegin = calculateRegionOffset(regionSection->MeBase); - UINT32 meEnd = meBegin + calculateRegionSize(regionSection->MeBase, regionSection->MeLimit); QByteArray bios; - UINT32 biosBegin = calculateRegionOffset(regionSection->BiosBase); - UINT32 biosEnd = biosBegin + calculateRegionSize(regionSection->BiosBase, regionSection->BiosLimit); QByteArray pdr; - UINT32 pdrBegin = calculateRegionOffset(regionSection->PdrBase); - UINT32 pdrEnd = pdrBegin + calculateRegionSize(regionSection->PdrBase, regionSection->PdrLimit); + QByteArray ec; + QByteArray padding; - UINT32 offset = descriptor.size(); - // Reconstruct other regions - char empty = '\xFF'; for (int i = 1; i < model->rowCount(index); i++) { - QByteArray region; - result = buildRegion(index.child(i, 0), region); - if (result) - return result; + QModelIndex currentRegion = index.child(i, 0); + // Skip regions with Remove action + if (model->action(currentRegion) == Actions::Remove) + continue; - UINT8 type = model->subtype(index.child(i, 0)); - switch (type) - { + // Check item type to be either region or padding + UINT8 type = model->type(currentRegion); + if (type == Types::Padding) { + if (!padding.isEmpty()) { + msg(tr("buildIntelImage: more than one padding found during image rebuild, the latest one is used"), index); + } + padding = model->header(currentRegion).append(model->body(currentRegion)); + continue; + } + + // Check region subtype + STATUS result; + UINT8 regionType = model->subtype(currentRegion); + switch (regionType) { case Subtypes::GbeRegion: - gbe = region; - if (gbeBegin > offset) - intelImage.append(QByteArray(gbeBegin - offset, empty)); - intelImage.append(gbe); - offset = gbeEnd; + if (!gbe.isEmpty()) { + msg(tr("buildIntelImage: more than one GbE region found during image rebuild, the latest one is used"), index); + } + result = buildGbeRegion(currentRegion, gbe); + if (result) { + msg(tr("buildIntelImage: building of GbE region failed with error \"%1\", original item data used").arg(errorCodeToQString(result)), currentRegion); + gbe = model->header(currentRegion).append(model->body(currentRegion)); + } break; case Subtypes::MeRegion: - me = region; - if (meBegin > offset) - intelImage.append(QByteArray(meBegin - offset, empty)); - intelImage.append(me); - offset = meEnd; + if (!me.isEmpty()) { + msg(tr("buildIntelImage: more than one ME region found during image rebuild, the latest one is used"), index); + } + result = buildMeRegion(currentRegion, me); + if (result) { + msg(tr("buildIntelImage: building of ME region failed with error \"%1\", original item data used").arg(errorCodeToQString(result)), currentRegion); + me = model->header(currentRegion).append(model->body(currentRegion)); + } break; case Subtypes::BiosRegion: - bios = region; - if (biosBegin > offset) - intelImage.append(QByteArray(biosBegin - offset, empty)); - intelImage.append(bios); - offset = biosEnd; + if (!bios.isEmpty()) { + msg(tr("buildIntelImage: more than one BIOS region found during image rebuild, the latest one is used"), index); + } + result = buildRawArea(currentRegion, bios); + if (result) { + msg(tr("buildIntelImage: building of BIOS region failed with error \"%1\", original item data used").arg(errorCodeToQString(result)), currentRegion); + bios = model->header(currentRegion).append(model->body(currentRegion)); + } break; case Subtypes::PdrRegion: - pdr = region; - if (pdrBegin > offset) - intelImage.append(QByteArray(pdrBegin - offset, empty)); - intelImage.append(pdr); - offset = pdrEnd; + if (!pdr.isEmpty()) { + msg(tr("buildIntelImage: more than one PDR region found during image rebuild, the latest one is used"), index); + } + result = buildPdrRegion(currentRegion, pdr); + if (result) { + msg(tr("buildIntelImage: building of PDR region failed with error \"%1\", original item data used").arg(errorCodeToQString(result)), currentRegion); + pdr = model->header(currentRegion).append(model->body(currentRegion)); + } + break; + case Subtypes::EcRegion: + if (!ec.isEmpty()) { + msg(tr("buildIntelImage: more than one EC region found during image rebuild, the latest one is used"), index); + } + result = buildEcRegion(currentRegion, ec); + if (result) { + msg(tr("buildIntelImage: building of EC region failed with error \"%1\", original item data used").arg(errorCodeToQString(result)), currentRegion); + ec = model->header(currentRegion).append(model->body(currentRegion)); + } break; default: - msg(tr("buildIntelImage: unknown region type found"), index); - return ERR_INVALID_REGION; + msg(tr("buildIntelImage: don't know how to build region of unknown type"), index); + return ERR_UNKNOWN_ITEM_TYPE; } + } - if ((UINT32)model->body(index).size() > offset) - intelImage.append(QByteArray((UINT32)model->body(index).size() - offset, empty)); + // Check size of new image, it must be same as old one UINT32 newSize = intelImage.size(); @@ -228,80 +254,23 @@ STATUS FfsBuilder::buildIntelImage(const QModelIndex & index, QByteArray & intel return ERR_NOT_IMPLEMENTED; } -STATUS FfsBuilder::buildRegion(const QModelIndex & index, QByteArray & region) +STATUS FfsBuilder::buildGbeRegion(const QModelIndex & index, QByteArray & region) { - if (!index.isValid()) - return ERR_SUCCESS; + return ERR_NOT_IMPLEMENTED; +} - UINT8 result; +STATUS FfsBuilder::buildMeRegion(const QModelIndex & index, QByteArray & region) +{ + return ERR_NOT_IMPLEMENTED; +} - // No action required - if (model->action(index) == Actions::NoAction) { - region = model->header(index).append(model->body(index)); - return ERR_SUCCESS; - } +STATUS FfsBuilder::buildPdrRegion(const QModelIndex & index, QByteArray & region) +{ + return ERR_NOT_IMPLEMENTED; +} - // Erase - else if (model->action(index) == Actions::Erase) { - region = model->header(index).append(model->body(index)); - if (erase(index, region)) - msg(tr("buildRegion: erase failed, original item data used"), index); - return ERR_SUCCESS; - } - - // Rebuild or replace - else if (model->action(index) == Actions::Rebuild || - model->action(index) == Actions::Replace) { - if (model->rowCount(index)) { - region.clear(); - // Build children - for (int i = 0; i < model->rowCount(index); i++) { - QModelIndex currentChild = index.child(i, 0); - QByteArray currentData; - // Check child type - if (model->type(currentChild) == Types::Volume) { - result = buildVolume(currentChild, currentData); - } - else if (model->type(currentChild) == Types::Padding) { - result = buildPadding(currentChild, currentData); - } - else { - msg(tr("buildRegion: unexpected child item of type \"%1\" can't be processed, original item data used").arg(itemTypeToQString(model->type(currentChild))), currentChild); - result = ERR_SUCCESS; - currentData = model->header(currentChild).append(model->body(currentChild)); - } - // Check build result - if (result) { - msg(tr("buildRegion: building of \"%1\" failed with error \"%2\", original item data used").arg(model->name(currentChild)).arg(errorCodeToQString(result)), currentChild); - currentData = model->header(currentChild).append(model->body(currentChild)); - } - // Append current data - region.append(currentData); - } - } - else - region = model->body(index); - - // Check size of new region, it must be same as original one - UINT32 newSize = region.size(); - UINT32 oldSize = model->body(index).size(); - if (newSize > oldSize) { - msg(tr("buildRegion: new region size %1h (%2) is bigger than the original %3h (%4)") - .hexarg(newSize).arg(newSize).hexarg(oldSize).arg(oldSize), index); - return ERR_INVALID_PARAMETER; - } - else if (newSize < oldSize) { - msg(tr("buildRegion: new region size %1h (%2) is smaller than the original %3h (%4)") - .hexarg(newSize).arg(newSize).hexarg(oldSize).arg(oldSize), index); - return ERR_INVALID_PARAMETER; - } - - // Build successful - region = model->header(index).append(region); - return ERR_SUCCESS; - } - - msg(tr("buildRegion: unexpected action \"%1\"").arg(actionTypeToQString(model->action(index))), index); +STATUS FfsBuilder::buildEcRegion(const QModelIndex & index, QByteArray & region) +{ return ERR_NOT_IMPLEMENTED; } diff --git a/common/ffsbuilder.h b/common/ffsbuilder.h index dbd2805..6f7a7cb 100644 --- a/common/ffsbuilder.h +++ b/common/ffsbuilder.h @@ -46,7 +46,10 @@ private: // UEFI standard structures STATUS buildCapsule(const QModelIndex & index, QByteArray & capsule); STATUS buildIntelImage(const QModelIndex & index, QByteArray & intelImage); - STATUS buildRegion(const QModelIndex & index, QByteArray & region); + STATUS buildGbeRegion(const QModelIndex & index, QByteArray & region); + STATUS buildMeRegion(const QModelIndex & index, QByteArray & region); + STATUS buildPdrRegion(const QModelIndex & index, QByteArray & region); + STATUS buildEcRegion(const QModelIndex & index, QByteArray & region); STATUS buildRawArea(const QModelIndex & index, QByteArray & rawArea, bool addHeader = true); STATUS buildPadding(const QModelIndex & index, QByteArray & padding); STATUS buildVolume(const QModelIndex & index, QByteArray & volume); diff --git a/common/ffsparser.cpp b/common/ffsparser.cpp index cf9f9bf..1fd034a 100644 --- a/common/ffsparser.cpp +++ b/common/ffsparser.cpp @@ -94,22 +94,18 @@ STATUS FfsParser::parseImageFile(const QByteArray & buffer, const QModelIndex & QByteArray header = buffer.left(capsuleHeaderSize); QByteArray body = buffer.mid(capsuleHeaderSize); QString name = tr("UEFI capsule"); - QString info = tr("Offset: 0h\nCapsule GUID: %1\nFull size: %2h (%3)\nHeader size: %4h (%5)\nImage size: %6h (%7)\nFlags: %8h") + QString info = tr("Capsule GUID: %1\nFull size: %2h (%3)\nHeader size: %4h (%5)\nImage size: %6h (%7)\nFlags: %8h") .arg(guidToQString(capsuleHeader->CapsuleGuid)) .hexarg(buffer.size()).arg(buffer.size()) .hexarg(capsuleHeaderSize).arg(capsuleHeaderSize) .hexarg(capsuleHeader->CapsuleImageSize - capsuleHeaderSize).arg(capsuleHeader->CapsuleImageSize - capsuleHeaderSize) .hexarg2(capsuleHeader->Flags, 8); - // Construct parsing data - PARSING_DATA pdata = parsingDataFromQModelIndex(QModelIndex()); - pdata.fixed = TRUE; - // Set capsule offset fixup for correct volume allignment warnings capsuleOffsetFixup = capsuleHeaderSize; // Add tree item - index = model->addItem(Types::Capsule, Subtypes::UefiCapsule, name, QString(), info, header, body, parsingDataToQByteArray(pdata), root); + index = model->addItem(Types::Capsule, Subtypes::UefiCapsule, name, QString(), info, header, body, TRUE, QByteArray(), root); } // Check buffer for being Toshiba capsule header else if (buffer.startsWith(TOSHIBA_CAPSULE_GUID)) { @@ -132,22 +128,18 @@ STATUS FfsParser::parseImageFile(const QByteArray & buffer, const QModelIndex & QByteArray header = buffer.left(capsuleHeaderSize); QByteArray body = buffer.right(buffer.size() - capsuleHeaderSize); QString name = tr("Toshiba capsule"); - QString info = tr("Offset: 0h\nCapsule GUID: %1\nFull size: %2h (%3)\nHeader size: %4h (%5)\nImage size: %6h (%7)\nFlags: %8h") + QString info = tr("Capsule GUID: %1\nFull size: %2h (%3)\nHeader size: %4h (%5)\nImage size: %6h (%7)\nFlags: %8h") .arg(guidToQString(capsuleHeader->CapsuleGuid)) .hexarg(buffer.size()).arg(buffer.size()) .hexarg(capsuleHeaderSize).arg(capsuleHeaderSize) .hexarg(capsuleHeader->FullSize - capsuleHeaderSize).arg(capsuleHeader->FullSize - capsuleHeaderSize) .hexarg2(capsuleHeader->Flags, 8); - // Construct parsing data - PARSING_DATA pdata = parsingDataFromQModelIndex(QModelIndex()); - pdata.fixed = TRUE; - // Set capsule offset fixup for correct volume allignment warnings capsuleOffsetFixup = capsuleHeaderSize; // Add tree item - index = model->addItem(Types::Capsule, Subtypes::ToshibaCapsule, name, QString(), info, header, body, parsingDataToQByteArray(pdata), root); + index = model->addItem(Types::Capsule, Subtypes::ToshibaCapsule, name, QString(), info, header, body, TRUE, QByteArray(), root); } // Check buffer for being extended Aptio signed capsule header else if (buffer.startsWith(APTIO_SIGNED_CAPSULE_GUID) || buffer.startsWith(APTIO_UNSIGNED_CAPSULE_GUID)) { @@ -175,22 +167,18 @@ STATUS FfsParser::parseImageFile(const QByteArray & buffer, const QModelIndex & QByteArray header = buffer.left(capsuleHeaderSize); QByteArray body = buffer.mid(capsuleHeaderSize); QString name = tr("AMI Aptio capsule"); - QString info = tr("Offset: 0h\nCapsule GUID: %1\nFull size: %2h (%3)\nHeader size: %4h (%5)\nImage size: %6h (%7)\nFlags: %8h") + QString info = tr("Capsule GUID: %1\nFull size: %2h (%3)\nHeader size: %4h (%5)\nImage size: %6h (%7)\nFlags: %8h") .arg(guidToQString(capsuleHeader->CapsuleHeader.CapsuleGuid)) .hexarg(buffer.size()).arg(buffer.size()) .hexarg(capsuleHeaderSize).arg(capsuleHeaderSize) .hexarg(capsuleHeader->CapsuleHeader.CapsuleImageSize - capsuleHeaderSize).arg(capsuleHeader->CapsuleHeader.CapsuleImageSize - capsuleHeaderSize) .hexarg2(capsuleHeader->CapsuleHeader.Flags, 8); - // Construct parsing data - PARSING_DATA pdata = parsingDataFromQModelIndex(QModelIndex()); - pdata.fixed = TRUE; - // Set capsule offset fixup for correct volume allignment warnings capsuleOffsetFixup = capsuleHeaderSize; // Add tree item - index = model->addItem(Types::Capsule, signedCapsule ? Subtypes::AptioSignedCapsule : Subtypes::AptioUnsignedCapsule, name, QString(), info, header, body, parsingDataToQByteArray(pdata), root); + index = model->addItem(Types::Capsule, signedCapsule ? Subtypes::AptioSignedCapsule : Subtypes::AptioUnsignedCapsule, name, QString(), info, header, body, TRUE, QByteArray(), root); // Show message about possible Aptio signature break if (signedCapsule) { @@ -220,16 +208,14 @@ STATUS FfsParser::parseImageFile(const QByteArray & buffer, const QModelIndex & // Get info QString name = tr("UEFI image"); - QString info = tr("Offset: %1h\nFull size: %2h (%3)") - .hexarg(capsuleHeaderSize).hexarg(flashImage.size()).arg(flashImage.size()); + QString info = tr("Full size: %2h (%3)").hexarg(flashImage.size()).arg(flashImage.size()); // Construct parsing data PARSING_DATA pdata = parsingDataFromQModelIndex(index); - pdata.fixed = TRUE; pdata.offset = capsuleHeaderSize; // Add tree item - QModelIndex biosIndex = model->addItem(Types::Image, Subtypes::UefiImage, name, QString(), info, QByteArray(), flashImage, parsingDataToQByteArray(pdata), index); + QModelIndex biosIndex = model->addItem(Types::Image, Subtypes::UefiImage, name, QString(), info, QByteArray(), flashImage, TRUE, parsingDataToQByteArray(pdata), index); // Parse the image result = parseRawArea(flashImage, biosIndex); @@ -460,19 +446,16 @@ STATUS FfsParser::parseIntelImage(const QByteArray & intelImage, const UINT32 pa .arg(descriptorMap->NumberOfProcStraps); // Construct parsing data - pdata.fixed = TRUE; pdata.offset = parentOffset; - if (pdata.isOnFlash) info.prepend(tr("Offset: %1h\n").hexarg(pdata.offset)); // Add Intel image tree item - index = model->addItem(Types::Image, Subtypes::IntelImage, name, QString(), info, QByteArray(), intelImage, parsingDataToQByteArray(pdata), parent); + index = model->addItem(Types::Image, Subtypes::IntelImage, name, QString(), info, QByteArray(), intelImage, TRUE, parsingDataToQByteArray(pdata), parent); // Descriptor // Get descriptor info QByteArray body = intelImage.left(FLASH_DESCRIPTOR_SIZE); name = tr("Descriptor region"); info = tr("Full size: %1h (%2)").hexarg(FLASH_DESCRIPTOR_SIZE).arg(FLASH_DESCRIPTOR_SIZE); - if (pdata.isOnFlash) info.prepend(tr("Offset: %1h\n").hexarg(pdata.offset)); // Check regions presence once again QVector offsets; @@ -569,7 +552,7 @@ STATUS FfsParser::parseIntelImage(const QByteArray & intelImage, const UINT32 pa } // Add descriptor tree item - model->addItem(Types::Region, Subtypes::DescriptorRegion, name, QString(), info, QByteArray(), body, parsingDataToQByteArray(pdata), index); + model->addItem(Types::Region, Subtypes::DescriptorRegion, name, QString(), info, QByteArray(), body, TRUE, parsingDataToQByteArray(pdata), index); // Sort regions in ascending order qSort(offsets); @@ -638,12 +621,10 @@ STATUS FfsParser::parseIntelImage(const QByteArray & intelImage, const UINT32 pa .hexarg(padding.size()).arg(padding.size()); // Construct parsing data - pdata.fixed = TRUE; pdata.offset = IntelDataEnd; - if (pdata.isOnFlash) info.prepend(tr("Offset: %1h\n").hexarg(pdata.offset)); // Add tree item - model->addItem(Types::Padding, getPaddingType(padding), name, QString(), info, QByteArray(), padding, parsingDataToQByteArray(pdata), index); + model->addItem(Types::Padding, getPaddingType(padding), name, QString(), info, QByteArray(), padding, TRUE, parsingDataToQByteArray(pdata), index); } // Check if the last VTF is found @@ -684,12 +665,10 @@ STATUS FfsParser::parseGbeRegion(const QByteArray & gbe, const UINT32 parentOffs .arg(version->minor); // Construct parsing data - pdata.fixed = TRUE; pdata.offset += parentOffset; - if (pdata.isOnFlash) info.prepend(tr("Offset: %1h\n").hexarg(pdata.offset)); // Add tree item - index = model->addItem(Types::Region, Subtypes::GbeRegion, name, QString(), info, QByteArray(), gbe, parsingDataToQByteArray(pdata), parent); + index = model->addItem(Types::Region, Subtypes::GbeRegion, name, QString(), info, QByteArray(), gbe, TRUE, parsingDataToQByteArray(pdata), parent); return ERR_SUCCESS; } @@ -745,12 +724,10 @@ STATUS FfsParser::parseMeRegion(const QByteArray & me, const UINT32 parentOffset } // Construct parsing data - pdata.fixed = TRUE; pdata.offset += parentOffset; - if (pdata.isOnFlash) info.prepend(tr("Offset: %1h\n").hexarg(pdata.offset)); // Add tree item - index = model->addItem(Types::Region, Subtypes::MeRegion, name, QString(), info, QByteArray(), me, parsingDataToQByteArray(pdata), parent); + index = model->addItem(Types::Region, Subtypes::MeRegion, name, QString(), info, QByteArray(), me, TRUE, parsingDataToQByteArray(pdata), parent); // Show messages if (emptyRegion) { @@ -778,12 +755,10 @@ STATUS FfsParser::parsePdrRegion(const QByteArray & pdr, const UINT32 parentOffs hexarg(pdr.size()).arg(pdr.size()); // Construct parsing data - pdata.fixed = TRUE; pdata.offset += parentOffset; - if (pdata.isOnFlash) info.prepend(tr("Offset: %1h\n").hexarg(pdata.offset)); // Add tree item - index = model->addItem(Types::Region, Subtypes::PdrRegion, name, QString(), info, QByteArray(), pdr, parsingDataToQByteArray(pdata), parent); + index = model->addItem(Types::Region, Subtypes::PdrRegion, name, QString(), info, QByteArray(), pdr, TRUE, parsingDataToQByteArray(pdata), parent); // Parse PDR region as BIOS space UINT8 result = parseRawArea(pdr, index); @@ -808,12 +783,10 @@ STATUS FfsParser::parseEcRegion(const QByteArray & ec, const UINT32 parentOffset hexarg(ec.size()).arg(ec.size()); // Construct parsing data - pdata.fixed = TRUE; pdata.offset += parentOffset; - if (pdata.isOnFlash) info.prepend(tr("Offset: %1h\n").hexarg(pdata.offset)); // Add tree item - index = model->addItem(Types::Region, Subtypes::PdrRegion, name, QString(), info, QByteArray(), ec, parsingDataToQByteArray(pdata), parent); + index = model->addItem(Types::Region, Subtypes::EcRegion, name, QString(), info, QByteArray(), ec, TRUE, parsingDataToQByteArray(pdata), parent); return ERR_SUCCESS; } @@ -833,12 +806,10 @@ STATUS FfsParser::parseBiosRegion(const QByteArray & bios, const UINT32 parentOf hexarg(bios.size()).arg(bios.size()); // Construct parsing data - pdata.fixed = TRUE; pdata.offset += parentOffset; - if (pdata.isOnFlash) info.prepend(tr("Offset: %1h\n").hexarg(pdata.offset)); // Add tree item - index = model->addItem(Types::Region, Subtypes::BiosRegion, name, QString(), info, QByteArray(), bios, parsingDataToQByteArray(pdata), parent); + index = model->addItem(Types::Region, Subtypes::BiosRegion, name, QString(), info, QByteArray(), bios, TRUE, parsingDataToQByteArray(pdata), parent); return parseRawArea(bios, index); } @@ -882,12 +853,10 @@ STATUS FfsParser::parseRawArea(const QByteArray & data, const QModelIndex & inde .hexarg(padding.size()).arg(padding.size()); // Construct parsing data - pdata.fixed = TRUE; pdata.offset = offset + headerSize; - if (pdata.isOnFlash) info.prepend(tr("Offset: %1h\n").hexarg(pdata.offset)); // Add tree item - model->addItem(Types::Padding, getPaddingType(padding), name, QString(), info, QByteArray(), padding, parsingDataToQByteArray(pdata), index); + model->addItem(Types::Padding, getPaddingType(padding), name, QString(), info, QByteArray(), padding, TRUE, parsingDataToQByteArray(pdata), index); } // Search for and parse all volumes @@ -908,12 +877,10 @@ STATUS FfsParser::parseRawArea(const QByteArray & data, const QModelIndex & inde .hexarg(padding.size()).arg(padding.size()); // Construct parsing data - pdata.fixed = TRUE; pdata.offset = offset + headerSize + paddingOffset; - if (pdata.isOnFlash) info.prepend(tr("Offset: %1h\n").hexarg(pdata.offset)); // Add tree item - model->addItem(Types::Padding, getPaddingType(padding), name, QString(), info, QByteArray(), padding, parsingDataToQByteArray(pdata), index); + model->addItem(Types::Padding, getPaddingType(padding), name, QString(), info, QByteArray(), padding, TRUE, parsingDataToQByteArray(pdata), index); } // Get volume size @@ -942,12 +909,10 @@ STATUS FfsParser::parseRawArea(const QByteArray & data, const QModelIndex & inde .hexarg(padding.size()).arg(padding.size()); // Construct parsing data - pdata.fixed = TRUE; pdata.offset = offset + headerSize + volumeOffset; - if (pdata.isOnFlash) info.prepend(tr("Offset: %1h\n").hexarg(pdata.offset)); // Add tree item - QModelIndex paddingIndex = model->addItem(Types::Padding, getPaddingType(padding), name, QString(), info, QByteArray(), padding, parsingDataToQByteArray(pdata), index); + QModelIndex paddingIndex = model->addItem(Types::Padding, getPaddingType(padding), name, QString(), info, QByteArray(), padding, TRUE, parsingDataToQByteArray(pdata), index); msg(tr("parseRawArea: one of volumes inside overlaps the end of data"), paddingIndex); // Update variables @@ -987,12 +952,10 @@ STATUS FfsParser::parseRawArea(const QByteArray & data, const QModelIndex & inde .hexarg(padding.size()).arg(padding.size()); // Construct parsing data - pdata.fixed = TRUE; pdata.offset = offset + headerSize + volumeOffset; - if (pdata.isOnFlash) info.prepend(tr("Offset: %1h\n").hexarg(pdata.offset)); // Add tree item - model->addItem(Types::Padding, getPaddingType(padding), name, QString(), info, QByteArray(), padding, parsingDataToQByteArray(pdata), index); + model->addItem(Types::Padding, getPaddingType(padding), name, QString(), info, QByteArray(), padding, TRUE, parsingDataToQByteArray(pdata), index); } // Parse bodies @@ -1112,7 +1075,7 @@ STATUS FfsParser::parseVolumeHeader(const QByteArray & volume, const UINT32 pare // Acquire alignment alignment = (UINT32)pow(2.0, (int)(volumeHeader->Attributes & EFI_FVB2_ALIGNMENT) >> 16); // Check alignment - if (!isUnknown && pdata.isOnFlash && ((pdata.offset + parentOffset - capsuleOffsetFixup) % alignment)) + if (!isUnknown && !model->compressed(parent) && ((pdata.offset + parentOffset - capsuleOffsetFixup) % alignment)) msgUnaligned = true; } else @@ -1178,7 +1141,6 @@ STATUS FfsParser::parseVolumeHeader(const QByteArray & volume, const UINT32 pare } // Construct parsing data - pdata.fixed = TRUE; pdata.offset += parentOffset; pdata.emptyByte = emptyByte; pdata.ffsVersion = ffsVersion; @@ -1189,7 +1151,6 @@ STATUS FfsParser::parseVolumeHeader(const QByteArray & volume, const UINT32 pare pdata.volume.hasAppleCrc32 = hasAppleCrc32; pdata.volume.hasAppleFSO = hasAppleFSO; pdata.volume.isWeakAligned = (volumeHeader->Revision > 1 && (volumeHeader->Attributes & EFI_FVB2_WEAK_ALIGNMENT)); - if (pdata.isOnFlash) info.prepend(tr("Offset: %1h\n").hexarg(pdata.offset)); // Add text QString text; @@ -1206,7 +1167,7 @@ STATUS FfsParser::parseVolumeHeader(const QByteArray & volume, const UINT32 pare else if (ffsVersion == 3) subtype = Subtypes::Ffs3Volume; } - index = model->addItem(Types::Volume, subtype, name, text, info, header, body, parsingDataToQByteArray(pdata), parent); + index = model->addItem(Types::Volume, subtype, name, text, info, header, body, TRUE, parsingDataToQByteArray(pdata), parent); // Show messages if (isUnknown) @@ -1298,7 +1259,6 @@ STATUS FfsParser::parseVolumeNonUefiData(const QByteArray & data, const UINT32 p PARSING_DATA pdata = parsingDataFromQModelIndex(index); // Modify it - pdata.fixed = TRUE; // Non-UEFI data is fixed pdata.offset += parentOffset; // Search for VTF GUID backwards in received data @@ -1322,10 +1282,9 @@ STATUS FfsParser::parseVolumeNonUefiData(const QByteArray & data, const UINT32 p // Add non-UEFI data first // Get info QString info = tr("Full size: %1h (%2)").hexarg(padding.size()).arg(padding.size()); - if (pdata.isOnFlash) info.prepend(tr("Offset: %1h\n").hexarg(pdata.offset)); // Add padding tree item - QModelIndex paddingIndex = model->addItem(Types::Padding, Subtypes::DataPadding, tr("Non-UEFI data"), "", info, QByteArray(), padding, parsingDataToQByteArray(pdata), index); + QModelIndex paddingIndex = model->addItem(Types::Padding, Subtypes::DataPadding, tr("Non-UEFI data"), "", info, QByteArray(), padding, TRUE, parsingDataToQByteArray(pdata), index); msg(tr("parseVolumeNonUefiData: non-UEFI data found in volume's free space"), paddingIndex); if (vtfIndex >= 0) { @@ -1346,10 +1305,9 @@ STATUS FfsParser::parseVolumeNonUefiData(const QByteArray & data, const UINT32 p pdata.offset += vtfIndex; // Get info QString info = tr("Full size: %1h (%2)").hexarg(vtf.size()).arg(vtf.size()); - if (pdata.isOnFlash) info.prepend(tr("Offset: %1h\n").hexarg(pdata.offset)); // Add padding tree item - QModelIndex paddingIndex = model->addItem(Types::Padding, Subtypes::DataPadding, tr("Non-UEFI data"), "", info, QByteArray(), vtf, parsingDataToQByteArray(pdata), index); + QModelIndex paddingIndex = model->addItem(Types::Padding, Subtypes::DataPadding, tr("Non-UEFI data"), "", info, QByteArray(), vtf, TRUE, parsingDataToQByteArray(pdata), index); msg(tr("parseVolumeNonUefiData: non-UEFI data found in volume's free space"), paddingIndex); } } @@ -1403,7 +1361,6 @@ STATUS FfsParser::parseVolumeBody(const QModelIndex & index) i = ALIGN8(i) - 8; // Construct parsing data - pdata.fixed = FALSE; // Free space is not fixed pdata.offset = offset + volumeHeaderSize + fileOffset; // Add all bytes before as free space @@ -1412,10 +1369,9 @@ STATUS FfsParser::parseVolumeBody(const QModelIndex & index) // Get info QString info = tr("Full size: %1h (%2)").hexarg(free.size()).arg(free.size()); - if (pdata.isOnFlash) info.prepend(tr("Offset: %1h\n").hexarg(pdata.offset)); // Add free space item - model->addItem(Types::FreeSpace, 0, tr("Volume free space"), "", info, QByteArray(), free, parsingDataToQByteArray(pdata), index); + model->addItem(Types::FreeSpace, 0, tr("Volume free space"), "", info, QByteArray(), free, FALSE, parsingDataToQByteArray(pdata), index); } // Parse non-UEFI data @@ -1423,15 +1379,13 @@ STATUS FfsParser::parseVolumeBody(const QModelIndex & index) } else { // Construct parsing data - pdata.fixed = FALSE; // Free space is not fixed pdata.offset = offset + volumeHeaderSize + fileOffset; // Get info QString info = tr("Full size: %1h (%2)").hexarg(freeSpace.size()).arg(freeSpace.size()); - if (pdata.isOnFlash) info.prepend(tr("Offset: %1h\n").hexarg(pdata.offset)); // Add free space item - model->addItem(Types::FreeSpace, 0, tr("Volume free space"), "", info, QByteArray(), freeSpace, parsingDataToQByteArray(pdata), index); + model->addItem(Types::FreeSpace, 0, tr("Volume free space"), "", info, QByteArray(), freeSpace, FALSE, parsingDataToQByteArray(pdata), index); } break; // Exit from parsing loop } @@ -1647,14 +1601,13 @@ STATUS FfsParser::parseFileHeader(const QByteArray & file, const UINT32 parentOf } // Construct parsing data - pdata.fixed = fileHeader->Attributes & FFS_ATTRIB_FIXED; + bool fixed = fileHeader->Attributes & FFS_ATTRIB_FIXED; pdata.offset += parentOffset; pdata.file.hasTail = hasTail ? TRUE : FALSE; pdata.file.tail = tail; - if (pdata.isOnFlash) info.prepend(tr("Offset: %1h\n").hexarg(pdata.offset)); // Add tree item - index = model->addItem(Types::File, fileHeader->Type, name, text, info, header, body, parsingDataToQByteArray(pdata), parent); + index = model->addItem(Types::File, fileHeader->Type, name, text, info, header, body, fixed, parsingDataToQByteArray(pdata), parent); // Overwrite lastVtf, if needed if (isVtf) { @@ -1758,10 +1711,9 @@ STATUS FfsParser::parsePadFileBody(const QModelIndex & index) // Constuct parsing data pdata.offset += model->header(index).size(); - if (pdata.isOnFlash) info.prepend(tr("Offset: %1h\n").hexarg(pdata.offset)); // Add tree item - model->addItem(Types::FreeSpace, 0, tr("Free space"), QString(), info, QByteArray(), free, parsingDataToQByteArray(pdata), index); + model->addItem(Types::FreeSpace, 0, tr("Free space"), QString(), info, QByteArray(), free, FALSE, parsingDataToQByteArray(pdata), index); } else i = 0; @@ -1774,11 +1726,9 @@ STATUS FfsParser::parsePadFileBody(const QModelIndex & index) // Constuct parsing data pdata.offset += i; - pdata.fixed = TRUE; // This data is fixed - if (pdata.isOnFlash) info.prepend(tr("Offset: %1h\n").hexarg(pdata.offset)); // Add tree item - QModelIndex dataIndex = model->addItem(Types::Padding, Subtypes::DataPadding, tr("Non-UEFI data"), "", info, QByteArray(), padding, parsingDataToQByteArray(pdata), index); + QModelIndex dataIndex = model->addItem(Types::Padding, Subtypes::DataPadding, tr("Non-UEFI data"), "", info, QByteArray(), padding, TRUE, parsingDataToQByteArray(pdata), index); // Show message msg(tr("parsePadFileBody: non-UEFI data found in pad-file"), dataIndex); @@ -1815,12 +1765,10 @@ STATUS FfsParser::parseSections(const QByteArray & sections, const QModelIndex & QString info = tr("Full size: %1h (%2)").hexarg(padding.size()).arg(padding.size()); // Constuct parsing data - pdata.fixed = TRUE; // Non-UEFI data is fixed pdata.offset += headerSize + sectionOffset; - if (pdata.isOnFlash) info.prepend(tr("Offset: %1h\n").hexarg(pdata.offset)); // Add tree item - QModelIndex dataIndex = model->addItem(Types::Padding, Subtypes::DataPadding, tr("Non-UEFI data"), "", info, QByteArray(), padding, parsingDataToQByteArray(pdata), index); + QModelIndex dataIndex = model->addItem(Types::Padding, Subtypes::DataPadding, tr("Non-UEFI data"), "", info, QByteArray(), padding, TRUE, parsingDataToQByteArray(pdata), index); // Show message msg(tr("parseSections: non-UEFI data found in sections area"), dataIndex); @@ -1849,9 +1797,6 @@ STATUS FfsParser::parseSections(const QByteArray & sections, const QModelIndex & case Types::Padding: // No parsing required break; - case Types::Signature: - // No parsing required - break; default: return ERR_UNKNOWN_ITEM_TYPE; } @@ -1923,10 +1868,9 @@ STATUS FfsParser::parseCommonSectionHeader(const QByteArray & section, const UIN // Construct parsing data pdata.offset += parentOffset; - if (pdata.isOnFlash) info.prepend(tr("Offset: %1h\n").hexarg(pdata.offset)); // Add tree item - index = model->addItem(Types::Section, sectionHeader->Type, name, QString(), info, header, body, parsingDataToQByteArray(pdata), parent); + index = model->addItem(Types::Section, sectionHeader->Type, name, QString(), info, header, body, FALSE, parsingDataToQByteArray(pdata), parent); return ERR_SUCCESS; } @@ -1972,10 +1916,9 @@ STATUS FfsParser::parseCompressedSectionHeader(const QByteArray & section, const pdata.offset += parentOffset; pdata.section.compressed.compressionType = compressionType; pdata.section.compressed.uncompressedSize = uncompressedLength; - if (pdata.isOnFlash) info.prepend(tr("Offset: %1h\n").hexarg(pdata.offset)); // Add tree item - index = model->addItem(Types::Section, sectionHeader->Type, name, QString(), info, header, body, parsingDataToQByteArray(pdata), parent); + index = model->addItem(Types::Section, sectionHeader->Type, name, QString(), info, header, body, FALSE, parsingDataToQByteArray(pdata), parent); return ERR_SUCCESS; } @@ -2104,10 +2047,9 @@ STATUS FfsParser::parseGuidedSectionHeader(const QByteArray & section, const UIN // Construct parsing data pdata.offset += parentOffset; pdata.section.guidDefined.guid = guid; - if (pdata.isOnFlash) info.prepend(tr("Offset: %1h\n").hexarg(pdata.offset)); // Add tree item - index = model->addItem(Types::Section, sectionHeader->Type, name, QString(), info, header, body, parsingDataToQByteArray(pdata), parent); + index = model->addItem(Types::Section, sectionHeader->Type, name, QString(), info, header, body, FALSE, parsingDataToQByteArray(pdata), parent); // Show messages if (msgNoAuthStatusAttribute) @@ -2163,10 +2105,9 @@ STATUS FfsParser::parseFreeformGuidedSectionHeader(const QByteArray & section, c // Construct parsing data pdata.offset += parentOffset; pdata.section.freeformSubtypeGuid.guid = guid; - if (pdata.isOnFlash) info.prepend(tr("Offset: %1h\n").hexarg(pdata.offset)); // Add tree item - index = model->addItem(Types::Section, sectionHeader->Type, name, QString(), info, header, body, parsingDataToQByteArray(pdata), parent); + index = model->addItem(Types::Section, sectionHeader->Type, name, QString(), info, header, body, FALSE, parsingDataToQByteArray(pdata), parent); // Rename section model->setName(index, guidToQString(guid)); @@ -2210,10 +2151,9 @@ STATUS FfsParser::parseVersionSectionHeader(const QByteArray & section, const UI // Construct parsing data pdata.offset += parentOffset; - if (pdata.isOnFlash) info.prepend(tr("Offset: %1h\n").hexarg(pdata.offset)); // Add tree item - index = model->addItem(Types::Section, sectionHeader->Type, name, QString(), info, header, body, parsingDataToQByteArray(pdata), parent); + index = model->addItem(Types::Section, sectionHeader->Type, name, QString(), info, header, body, FALSE, parsingDataToQByteArray(pdata), parent); return ERR_SUCCESS; } @@ -2254,10 +2194,9 @@ STATUS FfsParser::parsePostcodeSectionHeader(const QByteArray & section, const U // Construct parsing data pdata.offset += parentOffset; - if (pdata.isOnFlash) info.prepend(tr("Offset: %1h\n").hexarg(pdata.offset)); // Add tree item - index = model->addItem(Types::Section, sectionHeader->Type, name, QString(), info, header, body, parsingDataToQByteArray(pdata), parent); + index = model->addItem(Types::Section, sectionHeader->Type, name, QString(), info, header, body, FALSE, parsingDataToQByteArray(pdata), parent); return ERR_SUCCESS; } @@ -2330,11 +2269,12 @@ STATUS FfsParser::parseCompressedSectionBody(const QModelIndex & index) // Add info model->addInfo(index, tr("\nCompression algorithm: %1").arg(compressionTypeToQString(algorithm))); - // Update parsing data - pdata.isOnFlash = (algorithm == COMPRESSION_ALGORITHM_NONE); // Data is not on flash unless not compressed + // Update data pdata.section.compressed.algorithm = algorithm; + if (algorithm != COMPRESSION_ALGORITHM_NONE) + model->setCompressed(index, true); model->setParsingData(index, parsingDataToQByteArray(pdata)); - + // Parse decompressed data return parseSections(decompressed, index); } @@ -2396,8 +2336,9 @@ STATUS FfsParser::parseGuidedSectionBody(const QModelIndex & index) // Add info model->addInfo(index, info); - // Update parsing data - pdata.isOnFlash = (algorithm == COMPRESSION_ALGORITHM_NONE); // Data is not on flash unless not compressed + // Update data + if (algorithm != COMPRESSION_ALGORITHM_NONE) + model->setCompressed(index, true); model->setParsingData(index, parsingDataToQByteArray(pdata)); if (!parseCurrentSection) { @@ -2758,13 +2699,15 @@ STATUS FfsParser::performSecondPass(const QModelIndex & index) if (!index.isValid() || !lastVtf.isValid()) return ERR_INVALID_PARAMETER; - // Get parsing data for the last VTF - PARSING_DATA pdata = parsingDataFromQModelIndex(lastVtf); - if (!pdata.isOnFlash) { + // Check for compressed lastVtf + if (model->compressed(lastVtf)) { msg(tr("performSecondPass: the last VTF appears inside compressed item, the image may be damaged"), lastVtf); return ERR_SUCCESS; } + // Get parsing data for the last VTF + PARSING_DATA pdata = parsingDataFromQModelIndex(lastVtf); + // Calculate address difference const UINT32 vtfSize = model->header(lastVtf).size() + model->body(lastVtf).size() + (pdata.file.hasTail ? sizeof(UINT16) : 0); const UINT32 diff = 0xFFFFFFFFUL - pdata.offset - vtfSize + 1; @@ -2780,12 +2723,15 @@ STATUS FfsParser::addMemoryAddressesRecursive(const QModelIndex & index, const U // Sanity check if (!index.isValid()) return ERR_SUCCESS; - - // Get parsing data for the current item - PARSING_DATA pdata = parsingDataFromQModelIndex(index); - + // Set address value for non-compressed data - if (pdata.isOnFlash) { + if (!model->compressed(index)) { + // Get parsing data for the current item + PARSING_DATA pdata = parsingDataFromQModelIndex(index); + + // Show offset + model->addInfo(index, tr("Offset: %1h\n").hexarg(pdata.offset), false); + // Check address sanity if ((const UINT64)diff + pdata.offset <= 0xFFFFFFFFUL) { // Update info @@ -2800,7 +2746,7 @@ STATUS FfsParser::addMemoryAddressesRecursive(const QModelIndex & index, const U } // Special case of uncompressed TE image sections - if (model->type(index) == Types::Section && model->subtype(index) == EFI_SECTION_TE && pdata.isOnFlash) { + if (model->type(index) == Types::Section && model->subtype(index) == EFI_SECTION_TE) { // Check data memory address to be equal to either ImageBase or AdjustedImageBase if (pdata.section.teImage.imageBase == pdata.address + headerSize) { pdata.section.teImage.revision = 1; @@ -2819,6 +2765,10 @@ STATUS FfsParser::addMemoryAddressesRecursive(const QModelIndex & index, const U } } + //TODO: debugging, don't shows FIT file fixed attribute correctly + model->addInfo(index, tr("\nCompressed: %1").arg(model->compressed(index) ? tr("Yes") : tr("No"))); + model->addInfo(index, tr("\nFixed: %1").arg(model->fixed(index) ? tr("Yes") : tr("No"))); + // Process child items for (int i = 0; i < model->rowCount(index); i++) { addMemoryAddressesRecursive(index.child(i, 0), diff); diff --git a/common/fitparser.cpp b/common/fitparser.cpp index 1723821..35fc4af 100644 --- a/common/fitparser.cpp +++ b/common/fitparser.cpp @@ -58,14 +58,8 @@ STATUS FitParser::parse(const QModelIndex & index, const QModelIndex & lastVtfIn if (!fitIndex.isValid()) return ERR_SUCCESS; - // Get parsing data for the current item - PARSING_DATA pdata = parsingDataFromQModelIndex(fitIndex); - // Explicitly set the item as fixed - pdata.fixed = TRUE; - - // Set modified parsing data - model->setParsingData(fitIndex, parsingDataToQByteArray(pdata)); + model->setFixed(index, true); // Special case of FIT header const FIT_ENTRY* fitHeader = (const FIT_ENTRY*)(model->body(fitIndex).constData() + fitOffset); @@ -98,7 +92,7 @@ STATUS FitParser::parse(const QModelIndex & index, const QModelIndex & lastVtfIn fitTable.append(currentStrings); // Process all other entries - bool modifiedImageMayNotWork = false; + bool msgModifiedImageMayNotWork = false; for (UINT32 i = 1; i < fitHeader->Size; i++) { currentStrings.clear(); const FIT_ENTRY* currentEntry = fitHeader + i; @@ -121,7 +115,7 @@ STATUS FitParser::parse(const QModelIndex & index, const QModelIndex & lastVtfIn case FIT_TYPE_AC_KEY_MANIFEST: case FIT_TYPE_AC_BOOT_POLICY: default: - modifiedImageMayNotWork = true; + msgModifiedImageMayNotWork = true; break; } @@ -134,7 +128,7 @@ STATUS FitParser::parse(const QModelIndex & index, const QModelIndex & lastVtfIn fitTable.append(currentStrings); } - if (modifiedImageMayNotWork) + if (msgModifiedImageMayNotWork) msg(tr("Opened image may not work after any modification")); return ERR_SUCCESS; diff --git a/common/parsingdata.h b/common/parsingdata.h index 696de49..53f3921 100644 --- a/common/parsingdata.h +++ b/common/parsingdata.h @@ -79,8 +79,6 @@ typedef struct _SECTION_PARSING_DATA { } SECTION_PARSING_DATA; typedef struct _PARSING_DATA { - BOOLEAN fixed; - BOOLEAN isOnFlash; UINT8 emptyByte; UINT8 ffsVersion; UINT32 offset; diff --git a/common/treeitem.cpp b/common/treeitem.cpp index e7b513c..1fa98cd 100644 --- a/common/treeitem.cpp +++ b/common/treeitem.cpp @@ -17,7 +17,8 @@ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. TreeItem::TreeItem(const UINT8 type, const UINT8 subtype, const QString & name, const QString & text, const QString & info, - const QByteArray & header, const QByteArray & body, const QByteArray & parsingData, + const QByteArray & header, const QByteArray & body, + const BOOLEAN fixed, const BOOLEAN compressed, const QByteArray & parsingData, TreeItem *parent) : itemAction(Actions::NoAction), itemType(type), @@ -28,23 +29,11 @@ TreeItem::TreeItem(const UINT8 type, const UINT8 subtype, itemHeader(header), itemBody(body), itemParsingData(parsingData), + itemFixed(fixed), + itemCompressed(compressed), parentItem(parent) { -} - -TreeItem::~TreeItem() -{ - qDeleteAll(childItems); -} - -void TreeItem::appendChild(TreeItem *item) -{ - childItems.append(item); -} - -void TreeItem::prependChild(TreeItem *item) -{ - childItems.prepend(item); + setFixed(fixed); } UINT8 TreeItem::insertChildBefore(TreeItem *item, TreeItem *newItem) @@ -65,21 +54,6 @@ UINT8 TreeItem::insertChildAfter(TreeItem *item, TreeItem *newItem) return ERR_SUCCESS; } -TreeItem *TreeItem::child(int row) -{ - return childItems.value(row, NULL); -} - -int TreeItem::childCount() const -{ - return childItems.count(); -} - -int TreeItem::columnCount() const -{ - return 5; -} - QVariant TreeItem::data(int column) const { switch (column) @@ -99,46 +73,6 @@ QVariant TreeItem::data(int column) const } } -TreeItem *TreeItem::parent() -{ - return parentItem; -} - -QString TreeItem::name() const -{ - return itemName; -} - -void TreeItem::setName(const QString &name) -{ - itemName = name; -} - -QString TreeItem::text() const -{ - return itemText; -} - -void TreeItem::setText(const QString &text) -{ - itemText = text; -} - -QString TreeItem::info() const -{ - return itemInfo; -} - -void TreeItem::addInfo(const QString &info) -{ - itemInfo += info; -} - -void TreeItem::setInfo(const QString &info) -{ - itemInfo = info; -} - int TreeItem::row() const { if (parentItem) @@ -146,79 +80,3 @@ int TreeItem::row() const return 0; } - -UINT8 TreeItem::type() const -{ - return itemType; -} - -void TreeItem::setType(const UINT8 type) -{ - itemType = type; -} - -UINT8 TreeItem::subtype() const -{ - return itemSubtype; -} - -void TreeItem::setSubtype(const UINT8 subtype) -{ - itemSubtype = subtype; -} - -QByteArray TreeItem::header() const -{ - return itemHeader; -} - -QByteArray TreeItem::body() const -{ - return itemBody; -} - -QByteArray TreeItem::parsingData() const -{ - return itemParsingData; -} - -bool TreeItem::hasEmptyHeader() const -{ - return itemHeader.isEmpty(); -} - -bool TreeItem::hasEmptyBody() const -{ - return itemBody.isEmpty(); -} - -bool TreeItem::hasEmptyParsingData() const -{ - return itemParsingData.isEmpty(); -} - -void TreeItem::setParsingData(const QByteArray & data) -{ - itemParsingData = data; -} - -UINT8 TreeItem::action() const -{ - return itemAction; -} - -void TreeItem::setAction(const UINT8 action) -{ - itemAction = action; - - // On insert action, set insert action for children - if (action == Actions::Insert) - for (int i = 0; i < childCount(); i++) - child(i)->setAction(Actions::Insert); - - // Set rebuild action for parent, if it has no action now - if (parentItem && parentItem->type() != Types::Root - && parentItem->action() == Actions::NoAction) - parentItem->setAction(Actions::Rebuild); -} - diff --git a/common/treeitem.h b/common/treeitem.h index 8ea2629..7901348 100644 --- a/common/treeitem.h +++ b/common/treeitem.h @@ -25,53 +25,60 @@ class TreeItem { public: TreeItem(const UINT8 type, const UINT8 subtype = 0, const QString &name = QString(), const QString &text = QString(), const QString &info = QString(), - const QByteArray & header = QByteArray(), const QByteArray & body = QByteArray(), const QByteArray & parsingData = QByteArray(), + const QByteArray & header = QByteArray(), const QByteArray & body = QByteArray(), + const BOOLEAN fixed = FALSE, const BOOLEAN compressed = FALSE, const QByteArray & parsingData = QByteArray(), TreeItem *parent = 0); - ~TreeItem(); + ~TreeItem() { qDeleteAll(childItems); } // Operations with items - void appendChild(TreeItem *item); - void prependChild(TreeItem *item); - UINT8 insertChildBefore(TreeItem *item, TreeItem *newItem); - UINT8 insertChildAfter(TreeItem *item, TreeItem *newItem); + void appendChild(TreeItem *item) { childItems.append(item); } + void prependChild(TreeItem *item) { childItems.prepend(item); }; + UINT8 insertChildBefore(TreeItem *item, TreeItem *newItem); // Non-trivial implementation in CPP file + UINT8 insertChildAfter(TreeItem *item, TreeItem *newItem); // Non-trivial implementation in CPP file // Model support operations - TreeItem *child(int row); - int childCount() const; - int columnCount() const; - QVariant data(int column) const; - int row() const; - TreeItem *parent(); + TreeItem *child(int row) { return childItems.value(row, NULL); } + int childCount() const {return childItems.count(); } + int columnCount() const { return 5; } + QVariant data(int column) const; // Non-trivial implementation in CPP file + int row() const; // Non-trivial implementation in CPP file + TreeItem *parent() { return parentItem; } // Reading operations for item parameters - QString name() const; - void setName(const QString &text); + QString name() const { return itemName; } + void setName(const QString &text) { itemName = text; } - UINT8 type() const; - void setType(const UINT8 type); + UINT8 type() const { return itemType; } + void setType(const UINT8 type) { itemType = type; } - UINT8 subtype() const; - void setSubtype(const UINT8 subtype); + UINT8 subtype() const { return itemSubtype; } + void setSubtype(const UINT8 subtype) { itemSubtype = subtype; } - QString text() const; - void setText(const QString &text); + QString text() const { return itemText; } + void setText(const QString &text) { itemText = text; } - QByteArray header() const; - bool hasEmptyHeader() const; + QByteArray header() const { return itemHeader; } + bool hasEmptyHeader() const { return itemHeader.isEmpty(); } - QByteArray body() const; - bool hasEmptyBody() const; + QByteArray body() const { return itemBody; }; + bool hasEmptyBody() const { return itemBody.isEmpty(); } - QByteArray parsingData() const; - bool hasEmptyParsingData() const; - void setParsingData(const QByteArray & data); + QByteArray parsingData() const { return itemParsingData; } + bool hasEmptyParsingData() const { return itemParsingData.isEmpty(); } + void setParsingData(const QByteArray & data) { itemParsingData = data; } - QString info() const; - void addInfo(const QString &info); - void setInfo(const QString &info); + QString info() const { return itemInfo; } + void addInfo(const QString &info, const BOOLEAN append) { if (append) itemInfo.append(info); else itemInfo.prepend(info); } + void setInfo(const QString &info) { itemInfo = info; } - UINT8 action() const; - void setAction(const UINT8 action); + UINT8 action() const {return itemAction; } + void setAction(const UINT8 action) { itemAction = action; } + + BOOLEAN fixed() const { return itemFixed; } + void setFixed(const bool fixed) { itemFixed = fixed; } + + BOOLEAN compressed() const { return itemCompressed; } + void setCompressed(const bool compressed) { itemCompressed = compressed; } private: QList childItems; @@ -84,7 +91,9 @@ private: QByteArray itemHeader; QByteArray itemBody; QByteArray itemParsingData; - TreeItem *parentItem; + bool itemFixed; + bool itemCompressed; + TreeItem* parentItem; }; #endif diff --git a/common/treemodel.cpp b/common/treemodel.cpp index a414847..830977a 100644 --- a/common/treemodel.cpp +++ b/common/treemodel.cpp @@ -226,6 +226,58 @@ UINT8 TreeModel::action(const QModelIndex &index) const return item->action(); } +bool TreeModel::fixed(const QModelIndex &index) const +{ + if (!index.isValid()) + return false; + TreeItem *item = static_cast(index.internalPointer()); + return item->fixed(); +} + +bool TreeModel::compressed(const QModelIndex &index) const +{ + if (!index.isValid()) + return false; + TreeItem *item = static_cast(index.internalPointer()); + return item->compressed(); +} + +void TreeModel::setFixed(const QModelIndex &index, const bool fixed) +{ + if (!index.isValid()) + return; + + TreeItem *item = static_cast(index.internalPointer()); + item->setFixed(fixed); + + if (!item->parent()) + return; + + if (fixed) { + if (item->compressed() && item->parent()->compressed() == FALSE) { + item->setFixed(item->parent()->fixed()); + return; + } + + if (item->parent()->type() != Types::Root) + item->parent()->setFixed(fixed); + } + + emit dataChanged(index, index); +} + +void TreeModel::setCompressed(const QModelIndex &index, const bool compressed) +{ + if (!index.isValid()) + return; + + TreeItem *item = static_cast(index.internalPointer()); + item->setCompressed(compressed); + + emit dataChanged(index, index); +} + + void TreeModel::setSubtype(const QModelIndex & index, const UINT8 subtype) { if (!index.isValid()) @@ -276,13 +328,13 @@ void TreeModel::setInfo(const QModelIndex &index, const QString &data) emit dataChanged(index, index); } -void TreeModel::addInfo(const QModelIndex &index, const QString &data) +void TreeModel::addInfo(const QModelIndex &index, const QString &data, const bool append) { if (!index.isValid()) return; TreeItem *item = static_cast(index.internalPointer()); - item->addInfo(data); + item->addInfo(data, (BOOLEAN)append); emit dataChanged(index, index); } @@ -293,7 +345,18 @@ void TreeModel::setAction(const QModelIndex &index, const UINT8 action) TreeItem *item = static_cast(index.internalPointer()); item->setAction(action); - emit dataChanged(this->index(0, 0), index); + + // On insert action, set insert action for children + if (action == Actions::Insert) + for (int i = 0; i < item->childCount(); i++) + setAction(index.child(i, 0), Actions::Insert); + + // Set rebuild action for parent, if it has no action now + if (index.parent().isValid() && this->type(index.parent()) != Types::Root + && this->action(index.parent()) == Actions::NoAction) + setAction(index.parent(), Actions::Rebuild); + + emit dataChanged(index, index); } void TreeModel::setParsingData(const QModelIndex &index, const QByteArray &data) @@ -308,7 +371,8 @@ void TreeModel::setParsingData(const QModelIndex &index, const QByteArray &data) QModelIndex TreeModel::addItem(const UINT8 type, const UINT8 subtype, const QString & name, const QString & text, const QString & info, - const QByteArray & header, const QByteArray & body, const QByteArray & parsingData, + const QByteArray & header, const QByteArray & body, + const bool fixed, const QByteArray & parsingData, const QModelIndex & parent, const UINT8 mode) { TreeItem *item = 0; @@ -330,7 +394,8 @@ QModelIndex TreeModel::addItem(const UINT8 type, const UINT8 subtype, } } - TreeItem *newItem = new TreeItem(type, subtype, name, text, info, header, body, parsingData, parentItem); + TreeItem *newItem = new TreeItem(type, subtype, name, text, info, header, body, fixed, this->compressed(parent), parsingData, parentItem); + if (mode == CREATE_MODE_APPEND) { emit layoutAboutToBeChanged(); parentItem->appendChild(newItem); @@ -354,7 +419,9 @@ QModelIndex TreeModel::addItem(const UINT8 type, const UINT8 subtype, emit layoutChanged(); - return createIndex(newItem->row(), parentColumn, newItem); + QModelIndex created = createIndex(newItem->row(), parentColumn, newItem); + setFixed(created, fixed); // Non-trivial logic requires additional call + return created; } QModelIndex TreeModel::findParentOfType(const QModelIndex& index, UINT8 type) const diff --git a/common/treemodel.h b/common/treemodel.h index 3039a4e..0251504 100644 --- a/common/treemodel.h +++ b/common/treemodel.h @@ -48,8 +48,10 @@ public: void setName(const QModelIndex &index, const QString &name); void setText(const QModelIndex &index, const QString &text); void setInfo(const QModelIndex &index, const QString &info); - void addInfo(const QModelIndex &index, const QString &info); + void addInfo(const QModelIndex &index, const QString &info, const bool append = TRUE); void setParsingData(const QModelIndex &index, const QByteArray &data); + void setFixed(const QModelIndex &index, const bool fixed); + void setCompressed(const QModelIndex &index, const bool compressed); QString name(const QModelIndex &index) const; QString text(const QModelIndex &index) const; @@ -63,10 +65,14 @@ public: QByteArray parsingData(const QModelIndex &index) const; bool hasEmptyParsingData(const QModelIndex &index) const; UINT8 action(const QModelIndex &index) const; + bool fixed(const QModelIndex &index) const; + + bool compressed(const QModelIndex &index) const; QModelIndex addItem(const UINT8 type, const UINT8 subtype = 0, const QString & name = QString(), const QString & text = QString(), const QString & info = QString(), - const QByteArray & header = QByteArray(), const QByteArray & body = QByteArray(), const QByteArray & parsingData = QByteArray(), + const QByteArray & header = QByteArray(), const QByteArray & body = QByteArray(), + const bool fixed = false, const QByteArray & parsingData = QByteArray(), const QModelIndex & parent = QModelIndex(), const UINT8 mode = CREATE_MODE_APPEND); QModelIndex findParentOfType(const QModelIndex & index, UINT8 type) const; diff --git a/common/types.cpp b/common/types.cpp index d693b37..ca97a5a 100644 --- a/common/types.cpp +++ b/common/types.cpp @@ -29,6 +29,8 @@ QString regionTypeToQString(const UINT8 type) return QObject::tr("BIOS"); case Subtypes::PdrRegion: return QObject::tr("PDR"); + case Subtypes::EcRegion: + return QObject::tr("EC"); default: return QObject::tr("Unknown"); }; @@ -55,8 +57,6 @@ QString itemTypeToQString(const UINT8 type) return QObject::tr("Section"); case Types::FreeSpace: return QObject::tr("Free space"); - case Types::Signature: - return QObject::tr("Signature"); default: return QObject::tr("Unknown"); } @@ -110,13 +110,6 @@ QString itemSubtypeToQString(const UINT8 type, const UINT8 subtype) return sectionTypeToQString(subtype); case Types::FreeSpace: return QString(); - case Types::Signature: - if (subtype == Subtypes::UefiSignature) - return QObject::tr("UEFI"); - else if (subtype == Subtypes::Pkcs7Signature) - return QObject::tr("PKCS#7"); - else - return QObject::tr("Unknown subtype"); default: return QObject::tr("Unknown subtype"); } diff --git a/common/types.h b/common/types.h index 9f3529d..0451118 100644 --- a/common/types.h +++ b/common/types.h @@ -42,7 +42,6 @@ namespace Types { Volume, File, Section, - Signature, FreeSpace }; } @@ -71,7 +70,8 @@ namespace Subtypes { GbeRegion, MeRegion, BiosRegion, - PdrRegion + PdrRegion, + EcRegion }; enum PaddingSubtypes { @@ -79,11 +79,6 @@ namespace Subtypes { OnePadding, DataPadding }; - - enum SignatureSubtypes { - UefiSignature = 120, - Pkcs7Signature - }; }; // *ToQString conversion routines diff --git a/common/utility.cpp b/common/utility.cpp index c65f837..3589a1c 100644 --- a/common/utility.cpp +++ b/common/utility.cpp @@ -24,12 +24,11 @@ PARSING_DATA parsingDataFromQModelIndex(const QModelIndex & index) { if (index.isValid()) { TreeModel* model = (TreeModel*)index.model(); - return *(PARSING_DATA*)model->parsingData(index).data(); + if (!model->hasEmptyParsingData(index)) + return *(PARSING_DATA*)model->parsingData(index).data(); } PARSING_DATA data; - data.fixed = FALSE; // Item is not fixed by default - data.isOnFlash = TRUE; // Data is on flash by default data.offset = 0; data.address = 0; data.ffsVersion = 0; // Unknown by default @@ -186,8 +185,13 @@ STATUS decompress(const QByteArray & compressedData, UINT8 & algorithm, QByteArr return ERR_STANDARD_DECOMPRESSION_FAILED; // Allocate memory - decompressed = new UINT8[decompressedSize]; - scratch = new UINT8[scratchSize]; + try { + decompressed = new UINT8[decompressedSize]; + scratch = new UINT8[scratchSize]; + } + catch (std::bad_alloc) { + return ERR_STANDARD_DECOMPRESSION_FAILED; + } // Decompress section data @@ -222,7 +226,12 @@ STATUS decompress(const QByteArray & compressedData, UINT8 & algorithm, QByteArr return ERR_CUSTOMIZED_DECOMPRESSION_FAILED; // Allocate memory - decompressed = new UINT8[decompressedSize]; + try { + decompressed = new UINT8[decompressedSize]; + } + catch (std::bad_alloc) { + return ERR_STANDARD_DECOMPRESSION_FAILED; + } // Decompress section data if (ERR_SUCCESS != LzmaDecompress(data, dataSize, decompressed)) {