Skip to content

Commit

Permalink
Update padding logic for EmvTreeView to match emv-decode
Browse files Browse the repository at this point in the history
Identification of possible padding will now apply to nested constructed
fields as well, and the resulting EmvTreeItem objects will be clickable.
  • Loading branch information
leonlynch committed Nov 23, 2024
1 parent 13e202b commit 1c243db
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 51 deletions.
48 changes: 12 additions & 36 deletions viewer/emv-viewer-mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -173,7 +174,6 @@ void EmvViewerMainWindow::parseData()
QString str;
int validLen;
QByteArray data;
bool paddingIsPossible = false;
unsigned int validBytes;

str = dataEdit->toPlainText();
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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)
Expand Down
47 changes: 47 additions & 0 deletions viewer/emvtreeitem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<const uint8_t*>(value));

// Render the widget according to the current state
render(false);
}

void EmvTreeItem::deleteChildren()
{
QList<QTreeWidgetItem*> list;
Expand Down Expand Up @@ -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<std::size_t>(length)) +
// Create an uppercase hex string, with spaces, from the
// field's value bytes
QByteArray::fromRawData(
reinterpret_cast<const char*>(value),
length
).toHex(' ').toUpper().constData();
} else {
return
str +
QString::asprintf(" : [%zu]", static_cast<std::size_t>(length));
}
}

static QString buildSimpleFieldString(
unsigned int tag,
qsizetype length,
Expand Down
8 changes: 8 additions & 0 deletions viewer/emvtreeitem.h
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
78 changes: 63 additions & 15 deletions viewer/emvtreeview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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
);
Expand All @@ -62,32 +65,72 @@ 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<const char*>(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;
}

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.
Expand All @@ -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);
Expand All @@ -117,6 +167,4 @@ void EmvTreeView::setDecodeFields(bool enabled)
}
++itr;
}

m_decodeFields = enabled;
}
4 changes: 4 additions & 0 deletions viewer/emvtreeview.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,23 @@
class EmvTreeView : public QTreeWidget
{
Q_OBJECT
Q_PROPERTY(bool ignorePadding READ ignorePadding WRITE setIgnorePadding)
Q_PROPERTY(bool decodeFields READ decodeFields WRITE setDecodeFields)

public:
EmvTreeView(QWidget* parent);

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;
};

Expand Down

0 comments on commit 1c243db

Please sign in to comment.