From 1c243dbe886769a474a57f566dd79f8cd3c53f85 Mon Sep 17 00:00:00 2001 From: Leon Lynch Date: Sat, 23 Nov 2024 13:37:35 +0100 Subject: [PATCH] Update padding logic for EmvTreeView to match emv-decode Identification of possible padding will now apply to nested constructed fields as well, and the resulting EmvTreeItem objects will be clickable. --- viewer/emv-viewer-mainwindow.cpp | 48 +++++--------------- viewer/emvtreeitem.cpp | 47 +++++++++++++++++++ viewer/emvtreeitem.h | 8 ++++ viewer/emvtreeview.cpp | 78 ++++++++++++++++++++++++++------ viewer/emvtreeview.h | 4 ++ 5 files changed, 134 insertions(+), 51 deletions(-) diff --git a/viewer/emv-viewer-mainwindow.cpp b/viewer/emv-viewer-mainwindow.cpp index 8bde2a3..b0244e0 100644 --- a/viewer/emv-viewer-mainwindow.cpp +++ b/viewer/emv-viewer-mainwindow.cpp @@ -63,6 +63,7 @@ EmvViewerMainWindow::EmvViewerMainWindow( // changes the value to be different from the initial state. highlighter->setEmphasiseTags(tagsCheckBox->isChecked()); highlighter->setIgnorePadding(paddingCheckBox->isChecked()); + treeView->setIgnorePadding(paddingCheckBox->isChecked()); treeView->setDecodeFields(decodeCheckBox->isChecked()); // Load previous UI values @@ -173,7 +174,6 @@ void EmvViewerMainWindow::parseData() QString str; int validLen; QByteArray data; - bool paddingIsPossible = false; unsigned int validBytes; str = dataEdit->toPlainText(); @@ -202,49 +202,21 @@ void EmvViewerMainWindow::parseData() validLen -= 1; } - // Determine whether invalid data might be padding - if (paddingCheckBox->isChecked() && validLen == str.length()) { - // Input data is a valid hex string and therefore the possibility - // exists that if BER decoding fails, the remaining data might be - // cryptographic padding - paddingIsPossible = true; - } - data = QByteArray::fromHex(str.left(validLen).toUtf8()); validBytes = treeView->populateItems(data); validLen = validBytes * 2; if (validLen < str.length()) { - bool isPadding; - QString itemStr; - QColor itemColor; - - // Determine whether invalid data is padding and prepare item details - // accordingly - if (paddingIsPossible && - data.size() - validBytes > 0 && - ( - ((data.size() & 0x7) == 0 && data.size() - validBytes < 8) || - ((data.size() & 0xF) == 0 && data.size() - validBytes < 16) - ) - ) { - // Invalid data is likely to be padding - isPadding = true; - itemStr = QStringLiteral("Padding: "); - itemColor = Qt::darkGray; - } else { - // Invalid data is either absent or unlikely to be padding - isPadding = false; - itemStr = QStringLiteral("Remaining invalid data: "); - itemColor = Qt::red; - } - + // Remaining data is invalid and unlikely to be padding QTreeWidgetItem* item = new QTreeWidgetItem( treeView->invisibleRootItem(), - QStringList(itemStr + str.right(str.length() - validLen)) + QStringList( + QStringLiteral("Remaining invalid data: ") + + str.right(str.length() - validLen) + ) ); - item->setDisabled(isPadding); - item->setForeground(0, itemColor); + item->setDisabled(true); + item->setForeground(0, Qt::red); } } @@ -287,6 +259,10 @@ void EmvViewerMainWindow::on_paddingCheckBox_stateChanged(int state) // view item associated with invalid data or padding as well. highlighter->setIgnorePadding(state != Qt::Unchecked); highlighter->rehighlight(); + + // Note that tree view data must be reparsed when padding state changes + treeView->setIgnorePadding(state != Qt::Unchecked); + parseData(); } void EmvViewerMainWindow::on_decodeCheckBox_stateChanged(int state) diff --git a/viewer/emvtreeitem.cpp b/viewer/emvtreeitem.cpp index 11cbd38..479a039 100644 --- a/viewer/emvtreeitem.cpp +++ b/viewer/emvtreeitem.cpp @@ -36,6 +36,11 @@ // Helper functions static bool valueStrIsList(const QByteArray& str); +static QString buildSimpleFieldString( + QString str, + qsizetype length, + const std::uint8_t* value +); static QString buildSimpleFieldString( unsigned int tag, qsizetype length, @@ -91,6 +96,25 @@ EmvTreeItem::EmvTreeItem( setExpanded(autoExpand); } +EmvTreeItem::EmvTreeItem( + QTreeWidgetItem* parent, + unsigned int srcOffset, + unsigned int srcLength, + QString str, + const void* value +) +: QTreeWidgetItem(parent, EmvTreeItemType), + m_srcOffset(srcOffset), + m_srcLength(srcLength), + m_constructed(false) +{ + m_simpleFieldStr = m_decodedFieldStr = + buildSimpleFieldString(str, srcLength, static_cast(value)); + + // Render the widget according to the current state + render(false); +} + void EmvTreeItem::deleteChildren() { QList list; @@ -184,6 +208,29 @@ static bool valueStrIsList(const QByteArray& str) return str[qstrnlen(str.constData(), str.size()) - 1] == '\n'; } +static QString buildSimpleFieldString( + QString str, + qsizetype length, + const std::uint8_t* value +) +{ + if (value) { + return + str + + QString::asprintf(" : [%zu] ", static_cast(length)) + + // Create an uppercase hex string, with spaces, from the + // field's value bytes + QByteArray::fromRawData( + reinterpret_cast(value), + length + ).toHex(' ').toUpper().constData(); + } else { + return + str + + QString::asprintf(" : [%zu]", static_cast(length)); + } +} + static QString buildSimpleFieldString( unsigned int tag, qsizetype length, diff --git a/viewer/emvtreeitem.h b/viewer/emvtreeitem.h index 534f50a..a3f265e 100644 --- a/viewer/emvtreeitem.h +++ b/viewer/emvtreeitem.h @@ -41,6 +41,14 @@ class EmvTreeItem : public QTreeWidgetItem bool autoExpand = true ); + EmvTreeItem( + QTreeWidgetItem* parent, + unsigned int srcOffset, + unsigned int srcLength, + QString str, + const void* value + ); + unsigned int srcOffset() const { return m_srcOffset; } unsigned int srcLength() const { return m_srcLength; } QString tagName() const { return m_tagName; } diff --git a/viewer/emvtreeview.cpp b/viewer/emvtreeview.cpp index 6b63ea4..ee34dbd 100644 --- a/viewer/emvtreeview.cpp +++ b/viewer/emvtreeview.cpp @@ -34,14 +34,15 @@ static bool parseData( QTreeWidgetItem* parent, const void* ptr, unsigned int len, + bool ignorePadding, bool decode, - unsigned int* validBytes + unsigned int* totalValidBytes ) { int r; + unsigned int validBytes = 0; struct iso8825_ber_itr_t itr; struct iso8825_tlv_t tlv; - bool valid; r = iso8825_ber_itr_init(ptr, len, &itr); if (r) { @@ -50,10 +51,12 @@ static bool parseData( } while ((r = iso8825_ber_itr_next(&itr, &tlv)) > 0) { + unsigned int fieldLength = r; + EmvTreeItem* item = new EmvTreeItem( parent, - *validBytes, - r, + *totalValidBytes, + fieldLength, &tlv, decode ); @@ -62,24 +65,64 @@ static bool parseData( // If the field is constructed, only consider the tag and length // to be valid until the value has been parsed. The fields inside // the value will be added when they are parsed. - *validBytes += (r - tlv.length); + validBytes += (r - tlv.length); + *totalValidBytes += (r - tlv.length); // Recursively parse constructed fields - valid = parseData(item, tlv.value, tlv.length, decode, validBytes); + bool valid; + valid = parseData( + item, + tlv.value, + tlv.length, + ignorePadding, + decode, + totalValidBytes + ); if (!valid) { - qDebug("parseBerData() failed; validBytes=%u", *validBytes); + qDebug("parseData() failed; totalValidBytes=%u", *totalValidBytes); + + // Return here instead of breaking out to avoid repeated + // processing of the error by recursive callers return false; } + validBytes += tlv.length; } else { // If the field is not constructed, consider all of the bytes to // be valid BER encoded data - *validBytes += r; + validBytes += r; + *totalValidBytes += r; } } if (r < 0) { - qDebug("iso8825_ber_itr_next() failed; r=%d", r); - return false; + // Determine whether invalid data is padding and prepare item details + // accordingly + if (ignorePadding && + len - validBytes > 0 && + ( + ((len & 0x7) == 0 && len - validBytes < 8) || + ((len & 0xF) == 0 && len - validBytes < 16) + ) + ) { + // Invalid data is likely to be padding + EmvTreeItem* item = new EmvTreeItem( + parent, + *totalValidBytes, + len - validBytes, + "Padding", + reinterpret_cast(ptr) + validBytes + ); + item->setForeground(0, Qt::darkGray); + + // If the remaining bytes appear to be padding, consider these + // bytes to be valid + *totalValidBytes += len - validBytes; + validBytes = len; + + } else { + qDebug("iso8825_ber_itr_next() failed; r=%d", r); + return false; + } } return true; @@ -87,7 +130,7 @@ static bool parseData( unsigned int EmvTreeView::populateItems(const QByteArray& data) { - unsigned int validBytes = 0; + unsigned int totalValidBytes = 0; // For now, clear the widget before repopulating it. In future, the widget // should be updated incrementally instead. @@ -97,15 +140,22 @@ unsigned int EmvTreeView::populateItems(const QByteArray& data) invisibleRootItem(), data.constData(), data.size(), + m_ignorePadding, m_decodeFields, - &validBytes + &totalValidBytes ); - return validBytes; + return totalValidBytes; } void EmvTreeView::setDecodeFields(bool enabled) { + if (m_decodeFields == enabled) { + // No change + return; + } + m_decodeFields = enabled; + // Visit all EMV children recursively and re-render them according to the // current state QTreeWidgetItemIterator itr (this); @@ -117,6 +167,4 @@ void EmvTreeView::setDecodeFields(bool enabled) } ++itr; } - - m_decodeFields = enabled; } diff --git a/viewer/emvtreeview.h b/viewer/emvtreeview.h index a6caba2..32a3994 100644 --- a/viewer/emvtreeview.h +++ b/viewer/emvtreeview.h @@ -26,6 +26,7 @@ class EmvTreeView : public QTreeWidget { Q_OBJECT + Q_PROPERTY(bool ignorePadding READ ignorePadding WRITE setIgnorePadding) Q_PROPERTY(bool decodeFields READ decodeFields WRITE setDecodeFields) public: @@ -33,12 +34,15 @@ class EmvTreeView : public QTreeWidget public slots: unsigned int populateItems(const QByteArray& data); + void setIgnorePadding(bool enabled) { m_ignorePadding = enabled; } void setDecodeFields(bool enabled); public: + bool ignorePadding() const { return m_ignorePadding; } bool decodeFields() const { return m_decodeFields; } private: + bool m_ignorePadding = false; bool m_decodeFields = true; };