Merge pull request #346 from pstavirs/find-replace

Find replace
This commit is contained in:
Srivats P 2021-12-18 12:03:02 +05:30 committed by GitHub
commit a89400a4e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 1521 additions and 84 deletions

98
client/fieldedit.cpp Normal file
View File

@ -0,0 +1,98 @@
/*
Copyright (C) 2021 Srivats P.
This file is part of "Ostinato"
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#include "fieldedit.h"
FieldEdit::FieldEdit(QWidget *parent)
: QLineEdit(parent)
{
setType(kUInt64);
}
void FieldEdit::setType(FieldType type)
{
// clear existing contents before changing the validator
clear();
setPlaceholderText("");
type_ = type;
switch (type_) {
case kUInt64:
setValidator(&uint64Validator_);
if (isMaskMode_)
setText("0xFFFFFFFFFFFFFFFF");
break;
case kMacAddress:
setValidator(&macValidator_);
setPlaceholderText("00:00:00:00:00:00");
if (isMaskMode_)
setText("FF:FF:FF:FF:FF:FF");
break;
case kIp4Address:
setValidator(&ip4Validator_);
setPlaceholderText("0.0.0.0");
if (isMaskMode_)
setText("255.255.255.255");
break;
case kIp6Address:
setValidator(&ip6Validator_);
setPlaceholderText("::");
if (isMaskMode_)
setText("FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF");
break;
default:
setValidator(nullptr);
break;
}
}
// Applicable only if type is kUInt64
void FieldEdit::setRange(quint64 min, quint64 max)
{
uint64Validator_.setRange(min, max);
if (type_ == kUInt64) {
setPlaceholderText(QString("%1 - %2").arg(min).arg(max));
if (isMaskMode_)
setText(QString::number(max, 16).toUpper().prepend("0x"));
}
}
void FieldEdit::setMaskMode(bool maskMode)
{
isMaskMode_ = maskMode;
}
QString FieldEdit::text() const
{
QString str = QLineEdit::text();
switch (type_) {
case kMacAddress:
str.remove(QRegularExpression("[:-]"));
str.prepend("0x");
break;
case kIp4Address:
str = QString::number(QHostAddress(str).toIPv4Address());
break;
default:
break;
}
return str;
}

60
client/fieldedit.h Normal file
View File

@ -0,0 +1,60 @@
/*
Copyright (C) 2021 Srivats P.
This file is part of "Ostinato"
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#ifndef _FIELD_EDIT_H
#define _FIELD_EDIT_H
#include "ipv4addressvalidator.h"
#include "ipv6addressvalidator.h"
#include "macaddressvalidator.h"
#include "ulonglongvalidator.h"
#include <QLineEdit>
class FieldEdit: public QLineEdit
{
Q_OBJECT
public:
enum FieldType {
kUInt64,
kMacAddress,
kIp4Address,
kIp6Address
};
FieldEdit(QWidget *parent = 0);
void setType(FieldType type);
void setRange(quint64 min, quint64 max);
void setMaskMode(bool maskMode);
QString text() const;
private:
FieldType type_{kUInt64};
bool isMaskMode_{false};
IPv4AddressValidator ip4Validator_;
IPv6AddressValidator ip6Validator_;
MacAddressValidator macValidator_;
ULongLongValidator uint64Validator_;
};
#endif

230
client/findreplace.cpp Normal file
View File

@ -0,0 +1,230 @@
/*
Copyright (C) 2021 Srivats P.
This file is part of "Ostinato"
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#include "findreplace.h"
#include "abstractprotocol.h"
#include "iputils.h"
#include "mandatoryfieldsgroup.h"
#include "protocolmanager.h"
#include "stream.h"
#include "uint128.h"
#include <QPushButton>
extern ProtocolManager *OstProtocolManager;
// TODO: It might be useful for this dialog to support a "preview"
// of the replacements
FindReplaceDialog::FindReplaceDialog(Action *action, QWidget *parent)
: QDialog(parent), action_(action)
{
setupUi(this);
findMask->setMaskMode(true);
replaceMask->setMaskMode(true);
// Keep things simple and don't use mask(s) (default)
useFindMask->setChecked(false);
useReplaceMask->setChecked(false);
// TODO: remove combo protocols - see note in StreamBase::findReplace
QStringList protocolList = OstProtocolManager->protocolDatabase();
protocolList.sort();
protocol->addItems(protocolList);
// Enable this setting if we have streams selected on input
selectedStreamsOnly->setEnabled(action->selectedStreamsOnly);
// Reset for user input
action->selectedStreamsOnly = false;
QPushButton *ok = buttonBox->button(QDialogButtonBox::Ok);
ok->setText(tr("Replace All"));
ok->setDisabled(true);
mandatoryFields_ = new MandatoryFieldsGroup(this);
mandatoryFields_->add(protocol);
mandatoryFields_->add(field);
mandatoryFields_->add(findValue);
mandatoryFields_->add(findMask);
mandatoryFields_->add(replaceValue);
mandatoryFields_->add(replaceMask);
mandatoryFields_->setSubmitButton(ok);
}
FindReplaceDialog::~FindReplaceDialog()
{
delete mandatoryFields_;
}
void FindReplaceDialog::on_protocol_currentIndexChanged(const QString &name)
{
field->clear();
fieldAttrib_.clear();
Stream stream;
AbstractProtocol *protocol = OstProtocolManager->createProtocol(
name, &stream);
int count = protocol->fieldCount();
for (int i = 0; i < count; i++) {
// XXX: It might be useful to support meta fields too, later!
if (!protocol->fieldFlags(i).testFlag(AbstractProtocol::FrameField))
continue;
int bitSize = protocol->fieldData(i, AbstractProtocol::FieldBitSize)
.toInt();
if (bitSize <= 0) // skip optional fields
continue;
FieldAttrib fieldAttrib;
fieldAttrib.index = i; // fieldIndex
fieldAttrib.bitSize = bitSize;
// field and fieldAttrib_ have same count and order of fields
fieldAttrib_.append(fieldAttrib);
field->addItem(protocol->fieldData(i, AbstractProtocol::FieldName)
.toString());
}
protocolId_ = protocol->protocolNumber();
delete protocol;
}
void FindReplaceDialog::on_field_currentIndexChanged(int index)
{
if (index < 0)
return;
QString fieldName = field->currentText();
FieldAttrib fieldAttrib = fieldAttrib_.at(index);
// Use heuristics to determine field type
if (fieldAttrib.bitSize == 48) {
findMask->setType(FieldEdit::kMacAddress);
findValue->setType(FieldEdit::kMacAddress);
replaceMask->setType(FieldEdit::kMacAddress);
replaceValue->setType(FieldEdit::kMacAddress);
} else if ((fieldAttrib.bitSize == 32)
&& (fieldName.contains(QRegularExpression(
"address|source|destination",
QRegularExpression::CaseInsensitiveOption)))) {
findMask->setType(FieldEdit::kIp4Address);
findValue->setType(FieldEdit::kIp4Address);
replaceMask->setType(FieldEdit::kIp4Address);
replaceValue->setType(FieldEdit::kIp4Address);
} else if ((fieldAttrib.bitSize == 128)
&& (fieldName.contains(QRegularExpression(
"address|source|destination",
QRegularExpression::CaseInsensitiveOption)))) {
findMask->setType(FieldEdit::kIp6Address);
findValue->setType(FieldEdit::kIp6Address);
replaceMask->setType(FieldEdit::kIp6Address);
replaceValue->setType(FieldEdit::kIp6Address);
} else {
quint64 max = quint64(~0) >> (64-fieldAttrib.bitSize);
qDebug("XXXXXX %s bitSize %d max %llx",
qPrintable(field->currentText()),
fieldAttrib.bitSize, max);
findMask->setType(FieldEdit::kUInt64);
findMask->setRange(0, max);
findValue->setType(FieldEdit::kUInt64);
findValue->setRange(0, max);
replaceMask->setType(FieldEdit::kUInt64);
replaceMask->setRange(0, max);
replaceValue->setType(FieldEdit::kUInt64);
replaceValue->setRange(0, max);
}
}
void FindReplaceDialog::on_matchAny_toggled(bool checked)
{
if (checked) {
findValueLabel->setHidden(true);
findValue->setHidden(true);
useFindMask->setHidden(true);
findMask->setHidden(true);
findMaskHint->setHidden(true);
} else {
findValueLabel->setVisible(true);
findValue->setVisible(true);
useFindMask->setVisible(true);
if (useFindMask->isChecked()) {
findMask->setVisible(true);
findMaskHint->setVisible(true);
}
}
}
void FindReplaceDialog::on_buttonBox_accepted()
{
FieldAttrib fieldAttrib = fieldAttrib_.at(field->currentIndex());
action_->protocolField = QString("%1 %2")
.arg(protocol->currentText())
.arg(field->currentText());
action_->protocolNumber = protocolId_;
action_->fieldIndex = fieldAttrib.index;
action_->fieldBitSize = fieldAttrib.bitSize;
if (fieldAttrib.bitSize == 128) { // IPv6 address
if (matchAny->isChecked()) {
action_->findMask.setValue(UInt128(0));
action_->findValue.setValue(UInt128(0));
} else {
action_->findMask.setValue(
useFindMask->isChecked() ?
ipUtils::ip6StringToUInt128(findMask->text()) :
~UInt128(0));
action_->findValue.setValue(
ipUtils::ip6StringToUInt128(findValue->text()));
}
action_->replaceMask.setValue(
useReplaceMask->isChecked() ?
ipUtils::ip6StringToUInt128(replaceMask->text()) :
~UInt128(0));
action_->replaceValue.setValue(
ipUtils::ip6StringToUInt128(replaceValue->text()));
} else { // everything else
if (matchAny->isChecked()) {
action_->findMask.setValue(0);
action_->findValue.setValue(0);
} else {
action_->findMask.setValue(
useFindMask->isChecked() ?
findMask->text().toULongLong(nullptr, 0) :
~quint64(0));
action_->findValue.setValue(
findValue->text().toULongLong(nullptr, 0));
}
action_->replaceMask.setValue(
useReplaceMask->isChecked() ?
replaceMask->text().toULongLong(nullptr, 0) :
~quint64(0));
action_->replaceValue.setValue(QString::number(
replaceValue->text().toULongLong(nullptr, 0)));
}
action_->selectedStreamsOnly = selectedStreamsOnly->isChecked();
}

71
client/findreplace.h Normal file
View File

@ -0,0 +1,71 @@
/*
Copyright (C) 2021 Srivats P.
This file is part of "Ostinato"
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#ifndef _FIND_REPLACE_H
#define _FIND_REPLACE_H
#include "ui_findreplace.h"
class MandatoryFieldsGroup;
class FindReplaceDialog: public QDialog, public Ui::FindReplace
{
Q_OBJECT
public:
struct Action;
FindReplaceDialog(Action *action, QWidget *parent = 0);
~FindReplaceDialog();
private slots:
void on_protocol_currentIndexChanged(const QString &name);
void on_field_currentIndexChanged(int index);
void on_matchAny_toggled(bool checked);
void on_buttonBox_accepted();
private:
struct FieldAttrib;
quint32 protocolId_{0};
Action *action_{nullptr};
QList<FieldAttrib> fieldAttrib_;
MandatoryFieldsGroup *mandatoryFields_{nullptr};
};
struct FindReplaceDialog::Action
{
QString protocolField;
quint32 protocolNumber;
quint32 fieldIndex;
int fieldBitSize;
QVariant findValue;
QVariant findMask;
QVariant replaceValue;
QVariant replaceMask;
bool selectedStreamsOnly; // in-out param
};
struct FindReplaceDialog::FieldAttrib
{
quint32 index;
int bitSize;
};
#endif

265
client/findreplace.ui Normal file
View File

@ -0,0 +1,265 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FindReplace</class>
<widget class="QDialog" name="FindReplace">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>361</width>
<height>309</height>
</rect>
</property>
<property name="windowTitle">
<string>Find &amp; Replace</string>
</property>
<property name="windowIcon">
<iconset resource="ostinato.qrc">
<normaloff>:/icons/find.png</normaloff>:/icons/find.png</iconset>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="find">
<property name="title">
<string>Find</string>
</property>
<layout class="QGridLayout" name="gridLayout" columnstretch="0,1,0">
<item row="0" column="0">
<widget class="QLabel" name="protocolLabel">
<property name="text">
<string>Protocol</string>
</property>
<property name="buddy">
<cstring>protocol</cstring>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="QComboBox" name="protocol"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="fieldLabel">
<property name="text">
<string>Field</string>
</property>
<property name="buddy">
<cstring>field</cstring>
</property>
</widget>
</item>
<item row="1" column="1" colspan="2">
<widget class="QComboBox" name="field"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="findValueLabel">
<property name="text">
<string>Value</string>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="FieldEdit" name="findValue"/>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="useFindMask">
<property name="text">
<string>Mask</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="FieldEdit" name="findMask"/>
</item>
<item row="3" column="2">
<widget class="QLabel" name="findMaskHint">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;center&quot;&gt;Matches a field only if &lt;span style=&quot;white-space:nowrap&quot;&gt;(FieldValue &amp;amp; FindMask) = FindValue&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="pixmap">
<pixmap resource="ostinato.qrc">:/icons/info.png</pixmap>
</property>
</widget>
</item>
<item row="4" column="0" colspan="3">
<widget class="QCheckBox" name="matchAny">
<property name="text">
<string>Match any value</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="replace">
<property name="title">
<string>Replace with</string>
</property>
<layout class="QGridLayout" name="gridLayout_2" columnstretch="0,1,0">
<item row="0" column="0">
<widget class="QLabel" name="replaceValueLabel">
<property name="text">
<string>Value</string>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="FieldEdit" name="replaceValue"/>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="useReplaceMask">
<property name="text">
<string>Mask</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="FieldEdit" name="replaceMask"/>
</item>
<item row="1" column="2">
<widget class="QLabel" name="replaceMaskHint">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;center&quot;&gt;New field value = &lt;span style=&quot;white-space:nowrap&quot;&gt;(OldFieldValue &amp;amp; ~ReplaceMask) | (ReplaceValue &amp;amp; ReplaceMask)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="pixmap">
<pixmap resource="ostinato.qrc">:/icons/info.png</pixmap>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QCheckBox" name="selectedStreamsOnly">
<property name="text">
<string>Selected Streams Only</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>FieldEdit</class>
<extends>QLineEdit</extends>
<header>fieldedit.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="ostinato.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>FindReplace</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>277</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>FindReplace</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>283</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>useFindMask</sender>
<signal>toggled(bool)</signal>
<receiver>findMask</receiver>
<slot>setVisible(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>60</x>
<y>115</y>
</hint>
<hint type="destinationlabel">
<x>76</x>
<y>119</y>
</hint>
</hints>
</connection>
<connection>
<sender>useReplaceMask</sender>
<signal>toggled(bool)</signal>
<receiver>replaceMask</receiver>
<slot>setVisible(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>56</x>
<y>228</y>
</hint>
<hint type="destinationlabel">
<x>73</x>
<y>227</y>
</hint>
</hints>
</connection>
<connection>
<sender>useReplaceMask</sender>
<signal>toggled(bool)</signal>
<receiver>replaceMaskHint</receiver>
<slot>setVisible(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>50</x>
<y>230</y>
</hint>
<hint type="destinationlabel">
<x>333</x>
<y>233</y>
</hint>
</hints>
</connection>
<connection>
<sender>useFindMask</sender>
<signal>toggled(bool)</signal>
<receiver>findMaskHint</receiver>
<slot>setVisible(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>46</x>
<y>116</y>
</hint>
<hint type="destinationlabel">
<x>335</x>
<y>122</y>
</hint>
</hints>
</connection>
</connections>
</ui>

BIN
client/icons/find.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 B

BIN
client/icons/info.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 B

View File

@ -0,0 +1,126 @@
/*
Copyright (C) 2021 Srivats P.
This file is part of "Ostinato"
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#include "mandatoryfieldsgroup.h"
// No need for QDateEdit, QSpinBox, etc., since these always return values
#include <QCheckBox>
#include <QComboBox>
#include <QLineEdit>
#include <QPushButton>
#include <QWidget>
void MandatoryFieldsGroup::add(QWidget *widget)
{
if (!widgets_.contains(widget)) {
if (widget->inherits("QCheckBox"))
connect(qobject_cast<QCheckBox*>(widget),
SIGNAL(clicked()),
this, SLOT(changed()));
else if (widget->inherits("QComboBox"))
connect(qobject_cast<QComboBox*>(widget),
SIGNAL(highlighted(int)),
this, SLOT(changed()));
else if (widget->inherits("QLineEdit"))
connect(qobject_cast<QLineEdit*>(widget),
SIGNAL(textChanged(const QString&)),
this, SLOT(changed()));
else {
qWarning("MandatoryFieldsGroup: unsupported class %s",
widget->metaObject()->className());
return;
}
widgets_.append(widget);
changed();
}
}
void MandatoryFieldsGroup::remove(QWidget *widget)
{
widgets_.removeAll(widget);
changed();
}
void MandatoryFieldsGroup::setSubmitButton(QPushButton *button)
{
if (submitButton_ && submitButton_ != button)
submitButton_->setEnabled(true);
submitButton_ = button;
changed();
}
void MandatoryFieldsGroup::changed()
{
if (!submitButton_)
return;
bool enable = true;
for (auto widget : widgets_) {
// Invisible mandatory widgets are treated as non-mandatory
if (!widget->isVisible())
continue;
if (widget->inherits("QCheckBox")) {
// Makes sense only for tristate checkbox
auto checkBox = qobject_cast<const QCheckBox*>(widget);
if (checkBox->checkState() == Qt::PartiallyChecked) {
enable = false;
break;
} else
continue;
}
if (widget->inherits("QComboBox")) {
auto comboBox = qobject_cast<const QComboBox*>(widget);
if (comboBox->currentText().isEmpty()) {
enable = false;
break;
} else
continue;
}
if (widget->inherits("QLineEdit")) {
auto lineEdit = qobject_cast<const QLineEdit*>(widget);
if (lineEdit->text().isEmpty()
|| !lineEdit->hasAcceptableInput()) {
enable = false;
break;
} else
continue;
}
}
submitButton_->setEnabled(enable);
}
void MandatoryFieldsGroup::clear()
{
widgets_.clear();
if (submitButton_) {
submitButton_->setEnabled(true);
submitButton_ = nullptr;
}
}

View File

@ -0,0 +1,59 @@
/*
Copyright (C) 2021 Srivats P.
This file is part of "Ostinato"
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#ifndef _MANDATORY_FIELDS_GROUP_H
#define _MANDATORY_FIELDS_GROUP_H
#include <QObject>
#include <QList>
class QPushButton;
class QWidget;
// Adapted from https://doc.qt.io/archives/qq/qq11-mandatoryfields.html
// and improved
class MandatoryFieldsGroup : public QObject
{
Q_OBJECT
public:
MandatoryFieldsGroup(QObject *parent)
: QObject(parent)
{
}
void add(QWidget *widget);
void remove(QWidget *widget);
void setSubmitButton(QPushButton *button);
public slots:
void clear();
private slots:
void changed();
private:
QList<const QWidget*> widgets_;
QPushButton *submitButton_{nullptr};
};
#endif

View File

@ -41,10 +41,13 @@ HEADERS += \
devicemodel.h \
deviceswidget.h \
dumpview.h \
fieldedit.h \
hexlineedit.h \
logsmodel.h \
logswindow.h \
findreplace.h \
mainwindow.h \
mandatoryfieldsgroup.h \
ndpstatusmodel.h \
packetmodel.h \
port.h \
@ -74,6 +77,7 @@ FORMS += \
about.ui \
devicegroupdialog.ui \
deviceswidget.ui \
findreplace.ui \
logswindow.ui \
mainwindow.ui \
portconfigdialog.ui \
@ -99,8 +103,11 @@ SOURCES += \
hexlineedit.cpp \
logsmodel.cpp \
logswindow.cpp \
fieldedit.cpp \
findreplace.cpp \
main.cpp \
mainwindow.cpp \
mandatoryfieldsgroup.cpp \
ndpstatusmodel.cpp \
packetmodel.cpp \
params.cpp \

View File

@ -1,5 +1,7 @@
<RCC>
<qresource prefix="/">
<file>icons/find.png</file>
<file>icons/info.png</file>
<file>icons/about.png</file>
<file>icons/add.png</file>
<file>icons/anime_error.gif</file>

View File

@ -609,7 +609,6 @@ bool Port::openStreams(QString fileName, bool append, QString &error)
int ret;
optDialog->setParent(mainWindow, Qt::Dialog);
ret = optDialog->exec();
optDialog->setParent(0, Qt::Dialog);
if (ret == QDialog::Rejected)
goto _user_opt_cancel;
}

View File

@ -20,6 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "streamswidget.h"
#include "clipboardhelper.h"
#include "findreplace.h"
#include "portgrouplist.h"
#include "streamconfigdialog.h"
#include "streamfileformat.h"
@ -52,6 +53,12 @@ StreamsWidget::StreamsWidget(QWidget *parent)
sep2->setSeparator(true);
tvStreamList->addAction(sep2);
tvStreamList->addAction(actionFind_Replace);
QAction *sep3 = new QAction(this);
sep3->setSeparator(true);
tvStreamList->addAction(sep3);
tvStreamList->addAction(actionOpen_Streams);
tvStreamList->addAction(actionSave_Streams);
@ -61,9 +68,9 @@ StreamsWidget::StreamsWidget(QWidget *parent)
// Add the clipboard actions to the context menu of streamList
// but not to StreamsWidget's actions since they are already available
// in the global Edit Menu
QAction *sep3 = new QAction("Clipboard", this);
sep3->setSeparator(true);
tvStreamList->insertAction(sep2, sep3);
QAction *sep4 = new QAction("Clipboard", this);
sep4->setSeparator(true);
tvStreamList->insertAction(sep2, sep4);
tvStreamList->insertActions(sep2, clipboardHelper->actions());
}
@ -186,6 +193,9 @@ void StreamsWidget::updateStreamViewActions()
actionDuplicate_Stream->setDisabled(true);
actionDelete_Stream->setDisabled(true);
}
actionFind_Replace->setEnabled(tvStreamList->model()->rowCount() > 0);
actionOpen_Streams->setEnabled(plm->isPort(currentPortIndex_));
actionSave_Streams->setEnabled(tvStreamList->model()->rowCount() > 0);
}
@ -290,6 +300,76 @@ void StreamsWidget::on_actionDelete_Stream_triggered()
qDebug("No selection");
}
void StreamsWidget::on_actionFind_Replace_triggered()
{
qDebug("Find & Replace Action");
QItemSelectionModel* selectionModel = tvStreamList->selectionModel();
FindReplaceDialog::Action action;
action.selectedStreamsOnly = selectionModel->selection().size() > 0 ?
true : false;
FindReplaceDialog findReplace(&action, this);
if (findReplace.exec() == QDialog::Accepted) {
QProgressDialog progress(this);
progress.setLabelText(tr("Replacing %1 ...").arg(action.protocolField));
progress.setWindowModality(Qt::WindowModal);
int c, fc = 0, sc = 0; // replace counts
Port &port = plm->port(currentPortIndex_);
if (action.selectedStreamsOnly) {
QModelIndexList selected = selectionModel->selectedRows();
int count = selected.size();
progress.setMaximum(count);
for (int i = 0; i < count; i++) {
QModelIndex index = selected.at(i);
Stream *stream = port.mutableStreamByIndex(index.row(), false);
c = stream->protocolFieldReplace(action.protocolNumber,
action.fieldIndex,
action.fieldBitSize,
action.findValue,
action.findMask,
action.replaceValue,
action.replaceMask);
if (c) {
fc += c;
sc++;
}
progress.setValue(i+1);
if (progress.wasCanceled())
break;
}
} else {
int count = tvStreamList->model()->rowCount();
progress.setMaximum(count);
for (int i = 0; i < count; i++) {
Stream *stream = port.mutableStreamByIndex(i, false);
c = stream->protocolFieldReplace(action.protocolNumber,
action.fieldIndex,
action.fieldBitSize,
action.findValue,
action.findMask,
action.replaceValue,
action.replaceMask);
if (c) {
fc += c;
sc++;
}
progress.setValue(i+1);
if (progress.wasCanceled())
break;
}
}
if (fc)
port.setLocalConfigChanged(true);
QMessageBox::information(this, tr("Find & Replace"),
tr("%1 fields replaced in %2 streams").arg(fc).arg(sc));
}
}
void StreamsWidget::on_actionOpen_Streams_triggered()
{
qDebug("Open Streams Action");

View File

@ -49,6 +49,8 @@ private slots:
void on_actionDuplicate_Stream_triggered();
void on_actionDelete_Stream_triggered();
void on_actionFind_Replace_triggered();
void on_actionOpen_Streams_triggered();
void on_actionSave_Streams_triggered();

View File

@ -105,6 +105,18 @@ Right-click to create a stream</string>
<string>Duplicate Stream</string>
</property>
</action>
<action name="actionFind_Replace">
<property name="icon">
<iconset resource="ostinato.qrc">
<normaloff>:/icons/find.png</normaloff>:/icons/find.png</iconset>
</property>
<property name="text">
<string>Find &amp;&amp; Replace</string>
</property>
<property name="toolTip">
<string>Find &amp; Replace protocol field values</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View File

@ -671,6 +671,7 @@ bool GmpProtocol::setFieldData(int index, const QVariant &value,
ba.append(QByteArray(4 - (ba.size() % 4), char(0)));
rec->set_aux_data(std::string(ba.constData(), ba.size()));
}
isOk = true;
break;
}

View File

@ -57,7 +57,7 @@ void GmpConfigForm::loadWidget(AbstractProtocol *proto)
groupAddress->setText(
proto->fieldData(
GmpProtocol::kGroupAddress,
AbstractProtocol::FieldValue
AbstractProtocol::FieldTextValue
).toString());
groupMode->setCurrentIndex(
proto->fieldData(

View File

@ -56,7 +56,7 @@ void PdmlIcmpProtocol::preProtocolHandler(QString name,
else if (name == "icmpv6")
icmp->set_icmp_version(OstProto::Icmp::kIcmp6);
icmp->set_is_override_checksum(true);
icmp->set_is_override_checksum(overrideCksum_);
icmp->set_type(kIcmpInvalidType);
}

View File

@ -134,6 +134,7 @@ QVariant IgmpProtocol::fieldData(int index, FieldAttrib attrib,
case FieldName:
return QString("Group Address");
case FieldValue:
return grpIp;
case FieldTextValue:
return QHostAddress(grpIp).toString();
case FieldFrameValue:
@ -289,8 +290,14 @@ bool IgmpProtocol::setFieldData(int index, const QVariant &value,
}
case kGroupAddress:
{
quint32 ip = value.toUInt(&isOk);
if (isOk) {
data.mutable_group_address()->set_v4(ip);
break;
}
QHostAddress addr(value.toString());
quint32 ip = addr.toIPv4Address();
ip = addr.toIPv4Address();
isOk = (addr.protocol() == QAbstractSocket::IPv4Protocol);
if (isOk)
data.mutable_group_address()->set_v4(ip);
@ -306,6 +313,7 @@ bool IgmpProtocol::setFieldData(int index, const QVariant &value,
quint32 ip = QHostAddress(str).toIPv4Address();
data.add_sources()->set_v4(ip);
}
isOk = true;
break;
}
@ -332,6 +340,7 @@ bool IgmpProtocol::setFieldData(int index, const QVariant &value,
QHostAddress(src).toIPv4Address());
}
}
isOk = true;
break;
}

View File

@ -49,7 +49,7 @@ void PdmlIgmpProtocol::preProtocolHandler(QString /*name*/,
OstProto::Gmp *igmp = pbProto->MutableExtension(OstProto::igmp);
igmp->set_is_override_rsvd_code(true);
igmp->set_is_override_checksum(true);
igmp->set_is_override_checksum(overrideCksum_);
igmp->set_is_override_source_count(true);
igmp->set_is_override_group_record_count(true);

View File

@ -88,6 +88,6 @@ void PdmlIp4Protocol::postProtocolHandler(OstProto::Protocol *pbProto,
ip4->set_is_override_hdrlen(true);
ip4->set_is_override_totlen(true);
ip4->set_is_override_proto(true);
ip4->set_is_override_cksum(true);
ip4->set_is_override_cksum(overrideCksum_);
}

View File

@ -354,6 +354,11 @@ QVariant Ip6Protocol::fieldData(int index, FieldAttrib attrib,
case FieldName:
return QString("Source");
case FieldValue:
{
QVariant v;
v.setValue(src);
return v;
}
case FieldFrameValue:
case FieldTextValue:
{
@ -412,6 +417,11 @@ QVariant Ip6Protocol::fieldData(int index, FieldAttrib attrib,
case FieldName:
return QString("Destination");
case FieldValue:
{
QVariant v;
v.setValue(dst);
return v;
}
case FieldFrameValue:
case FieldTextValue:
{
@ -594,6 +604,14 @@ bool Ip6Protocol::setFieldData(int index, const QVariant &value,
}
case ip6_srcAddress:
{
if (value.typeName() == QString("UInt128")) {
UInt128 addr = value.value<UInt128>();
data.set_src_addr_hi(addr.hi64());
data.set_src_addr_lo(addr.lo64());
isOk = true;
break;
}
Q_IPV6ADDR addr = QHostAddress(value.toString()).toIPv6Address();
quint64 x;
@ -616,10 +634,19 @@ bool Ip6Protocol::setFieldData(int index, const QVariant &value,
| (quint64(addr[14]) << 8)
| (quint64(addr[15]) << 0);
data.set_src_addr_lo(x);
isOk = true;
break;
}
case ip6_dstAddress:
{
if (value.typeName() == QString("UInt128")) {
UInt128 addr = value.value<UInt128>();
data.set_dst_addr_hi(addr.hi64());
data.set_dst_addr_lo(addr.lo64());
isOk = true;
break;
}
Q_IPV6ADDR addr = QHostAddress(value.toString()).toIPv6Address();
quint64 x;
@ -642,6 +669,7 @@ bool Ip6Protocol::setFieldData(int index, const QVariant &value,
| (quint64(addr[14]) << 8)
| (quint64(addr[15]) << 0);
data.set_dst_addr_lo(x);
isOk = true;
break;
}

View File

@ -20,6 +20,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#ifndef _IP_UTILS_H
#define _IP_UTILS_H
#include "uint128.h"
#include <QHostAddress>
namespace ipUtils {
enum AddrMode {
kFixed = 0,
@ -118,5 +122,31 @@ void inline ipAddress(quint64 baseIpHi, quint64 baseIpLo, int prefix,
}
}
UInt128 inline ip6StringToUInt128(QString ip6Str)
{
Q_IPV6ADDR addr = QHostAddress(ip6Str).toIPv6Address();
quint64 hi, lo;
hi = (quint64(addr[0]) << 56)
| (quint64(addr[1]) << 48)
| (quint64(addr[2]) << 40)
| (quint64(addr[3]) << 32)
| (quint64(addr[4]) << 24)
| (quint64(addr[5]) << 16)
| (quint64(addr[6]) << 8)
| (quint64(addr[7]) << 0);
lo = (quint64(addr[ 8]) << 56)
| (quint64(addr[ 9]) << 48)
| (quint64(addr[10]) << 40)
| (quint64(addr[11]) << 32)
| (quint64(addr[12]) << 24)
| (quint64(addr[13]) << 16)
| (quint64(addr[14]) << 8)
| (quint64(addr[15]) << 0);
return UInt128(hi, lo);
}
} // namespace ipUtils
#endif

View File

@ -0,0 +1,48 @@
/*
Copyright (C) 2021 Srivats P.
This file is part of "Ostinato"
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#ifndef _IPV4_ADDRESS_VALIDATOR_H
#define _IPV4_ADDRESS_VALIDATOR_H
#include <QHostAddress>
#include <QRegularExpressionValidator>
class IPv4AddressValidator : public QRegularExpressionValidator
{
public:
IPv4AddressValidator(QObject *parent = 0)
: QRegularExpressionValidator(
QRegularExpression(
"((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)(\\.(?!$)|$)){4}"),
parent)
{
}
virtual void fixup(QString &input) const
{
QStringList bytes = input.split('.', QString::SkipEmptyParts);
while (bytes.count() < 4)
bytes.append("0");
input = bytes.join('.');
}
};
#endif

View File

@ -279,14 +279,16 @@ bool MacProtocol::setFieldData(int index, const QVariant &value,
{
case mac_dstAddr:
{
quint64 mac = value.toULongLong();
data.set_dst_mac(mac);
quint64 mac = value.toULongLong(&isOk);
if (isOk)
data.set_dst_mac(mac);
break;
}
case mac_srcAddr:
{
quint64 mac = value.toULongLong();
data.set_src_mac(mac);
quint64 mac = value.toULongLong(&isOk);
if (isOk)
data.set_src_mac(mac);
break;
}

View File

@ -0,0 +1,52 @@
/*
Copyright (C) 2021 Srivats P.
This file is part of "Ostinato"
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#ifndef _MAC_ADDRESS_VALIDATOR_H
#define _MAC_ADDRESS_VALIDATOR_H
#include <QRegularExpressionValidator>
// Allow : or - as separator
class MacAddressValidator : public QRegularExpressionValidator
{
public:
MacAddressValidator(QObject *parent = 0)
: QRegularExpressionValidator(
QRegularExpression(
"([0-9,a-f,A-F]{2,2}[:-]){5,5}[0-9,a-f,A-F]{2,2}"),
parent)
{
}
virtual void fixup(QString &input) const
{
QStringList bytes = input.split(QRegularExpression("[:-]"),
QString::SkipEmptyParts);
if (!bytes.isEmpty() && bytes.last().size() == 1)
bytes.last().prepend("0");
while (bytes.count() < 6)
bytes.append("00");
input = bytes.join(":");
}
};
#endif

View File

@ -195,6 +195,7 @@ QVariant MldProtocol::fieldData(int index, FieldAttrib attrib,
case FieldName:
return QString("Group Address");
case FieldValue:
return QVariant::fromValue(UInt128(grpHi, grpLo));
case FieldTextValue:
case FieldFrameValue:
{
@ -401,6 +402,14 @@ bool MldProtocol::setFieldData(int index, const QVariant &value,
{
case kGroupAddress:
{
if (value.typeName() == QString("UInt128")) {
UInt128 addr = value.value<UInt128>();
data.mutable_group_address()->set_v6_hi(addr.hi64());
data.mutable_group_address()->set_v6_lo(addr.lo64());
isOk = true;
break;
}
Q_IPV6ADDR addr = QHostAddress(value.toString()).toIPv6Address();
quint64 x;
@ -423,6 +432,7 @@ bool MldProtocol::setFieldData(int index, const QVariant &value,
| (quint64(addr[14]) << 8)
| (quint64(addr[15]) << 0);
data.mutable_group_address()->set_v6_lo(x);
isOk = true;
break;
}
@ -457,6 +467,7 @@ bool MldProtocol::setFieldData(int index, const QVariant &value,
| (quint64(addr[15]) << 0);
src->set_v6_lo(x);
}
isOk = true;
break;
}
@ -524,7 +535,7 @@ bool MldProtocol::setFieldData(int index, const QVariant &value,
src->set_v6_lo(x);
}
}
isOk = true;
break;
}

View File

@ -53,7 +53,7 @@ void PdmlMldProtocol::preProtocolHandler(QString /*name*/,
OstProto::Gmp *mld = pbProto->MutableExtension(OstProto::mld);
mld->set_is_override_rsvd_code(true);
mld->set_is_override_checksum(true);
mld->set_is_override_checksum(overrideCksum_);
mld->set_is_override_source_count(true);
mld->set_is_override_group_record_count(true);

View File

@ -44,11 +44,19 @@ PcapImportOptionsDialog::PcapImportOptionsDialog(QVariantMap *options)
: QDialog(NULL)
{
setupUi(this);
setAttribute(Qt::WA_DeleteOnClose);
options_ = options;
viaPdml->setChecked(options_->value("ViaPdml").toBool());
recalculateCksums->setChecked(
options_->value("RecalculateCksums").toBool());
doDiff->setChecked(options_->value("DoDiff").toBool());
// XXX: By default this is false - for pcap import tests to show
// minimal diffs. However, for the user, this should be enabled
// by default
recalculateCksums->setChecked(true);
connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
}
@ -59,6 +67,7 @@ PcapImportOptionsDialog::~PcapImportOptionsDialog()
void PcapImportOptionsDialog::accept()
{
options_->insert("ViaPdml", viaPdml->isChecked());
options_->insert("RecalculateCksums", recalculateCksums->isChecked());
options_->insert("DoDiff", doDiff->isChecked());
QDialog::accept();
@ -67,14 +76,12 @@ void PcapImportOptionsDialog::accept()
PcapFileFormat::PcapFileFormat()
{
importOptions_.insert("ViaPdml", true);
importOptions_.insert("RecalculateCksums", false);
importOptions_.insert("DoDiff", true);
importDialog_ = NULL;
}
PcapFileFormat::~PcapFileFormat()
{
delete importDialog_;
}
bool PcapFileFormat::open(const QString fileName,
@ -224,7 +231,7 @@ _retry:
{
QProcess tshark;
QTemporaryFile pdmlFile;
PdmlReader reader(&streams);
PdmlReader reader(&streams, importOptions_);
if (!pdmlFile.open())
{
@ -705,10 +712,7 @@ _exit:
QDialog* PcapFileFormat::openOptionsDialog()
{
if (!importDialog_)
importDialog_ = new PcapImportOptionsDialog(&importOptions_);
return importDialog_;
return new PcapImportOptionsDialog(&importOptions_);
}
bool PcapFileFormat::isMyFileFormat(const QString /*fileName*/)

View File

@ -81,7 +81,6 @@ private:
QDataStream fd_;
QVariantMap importOptions_;
PcapImportOptionsDialog *importDialog_;
};
extern PcapFileFormat pcapFileFormat;

View File

@ -1,62 +1,69 @@
<ui version="4.0" >
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PcapFileImport</class>
<widget class="QDialog" name="PcapFileImport" >
<property name="geometry" >
<widget class="QDialog" name="PcapFileImport">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>326</width>
<height>93</height>
<height>132</height>
</rect>
</property>
<property name="windowTitle" >
<property name="windowTitle">
<string>PCAP import options</string>
</property>
<layout class="QVBoxLayout" >
<item>
<widget class="QCheckBox" name="viaPdml" >
<property name="text" >
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="2">
<widget class="QCheckBox" name="viaPdml">
<property name="text">
<string>Intelligent Import (via PDML)</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" >
<item>
<widget class="QWidget" native="1" name="widget" >
<property name="sizePolicy" >
<sizepolicy vsizetype="Fixed" hsizetype="Fixed" >
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize" >
<size>
<width>16</width>
<height>16</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="doDiff" >
<property name="enabled" >
<bool>false</bool>
</property>
<property name="text" >
<string>Do a diff after import</string>
</property>
</widget>
</item>
</layout>
<item row="1" column="0" rowspan="2">
<widget class="QWidget" name="indentSpacing" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox" >
<property name="orientation" >
<item row="1" column="1">
<widget class="QCheckBox" name="recalculateCksums">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Recalculate Checksums</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="doDiff">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Do a diff after import</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons" >
<set>QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok</set>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
@ -70,11 +77,11 @@
<receiver>PcapFileImport</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel" >
<hint type="sourcelabel">
<x>249</x>
<y>81</y>
</hint>
<hint type="destinationlabel" >
<hint type="destinationlabel">
<x>157</x>
<y>90</y>
</hint>
@ -86,11 +93,11 @@
<receiver>PcapFileImport</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel" >
<hint type="sourcelabel">
<x>249</x>
<y>81</y>
</hint>
<hint type="destinationlabel" >
<hint type="destinationlabel">
<x>258</x>
<y>90</y>
</hint>
@ -102,13 +109,13 @@
<receiver>doDiff</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel" >
<hint type="sourcelabel">
<x>15</x>
<y>16</y>
</hint>
<hint type="destinationlabel" >
<x>37</x>
<y>42</y>
<hint type="destinationlabel">
<x>68</x>
<y>71</y>
</hint>
</hints>
</connection>
@ -118,13 +125,45 @@
<receiver>doDiff</receiver>
<slot>setChecked(bool)</slot>
<hints>
<hint type="sourcelabel" >
<hint type="sourcelabel">
<x>151</x>
<y>14</y>
</hint>
<hint type="destinationlabel" >
<x>150</x>
<y>34</y>
<hint type="destinationlabel">
<x>181</x>
<y>71</y>
</hint>
</hints>
</connection>
<connection>
<sender>viaPdml</sender>
<signal>toggled(bool)</signal>
<receiver>recalculateCksums</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>29</x>
<y>17</y>
</hint>
<hint type="destinationlabel">
<x>38</x>
<y>39</y>
</hint>
</hints>
</connection>
<connection>
<sender>viaPdml</sender>
<signal>toggled(bool)</signal>
<receiver>recalculateCksums</receiver>
<slot>setChecked(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>67</x>
<y>18</y>
</hint>
<hint type="destinationlabel">
<x>66</x>
<y>33</y>
</hint>
</hints>
</connection>

View File

@ -102,6 +102,11 @@ int PdmlProtocol::fieldId(QString name) const
return fieldMap_.value(name);
}
void PdmlProtocol::setRecalculateCksum(bool recalculate)
{
overrideCksum_ = !recalculate;
}
/*!
This method is called by PdmlReader before any fields within the protocol
are processed. All attributes associated with the 'proto' tag in the PDML

View File

@ -40,6 +40,8 @@ public:
bool hasField(QString name) const;
int fieldId(QString name) const;
void setRecalculateCksum(bool recalculate);
virtual void preProtocolHandler(QString name,
const QXmlStreamAttributes &attributes, int expectedPos,
OstProto::Protocol *pbProto, OstProto::Stream *stream);
@ -63,6 +65,8 @@ protected:
int ostProtoId_;
//!< Map of PDML field names to protobuf field numbers for 'known' fields
QMap<QString, int> fieldMap_;
bool overrideCksum_{true};
};
#endif

View File

@ -42,12 +42,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "udppdml.h"
#include "vlanpdml.h"
PdmlReader::PdmlReader(OstProto::StreamConfigList *streams)
PdmlReader::PdmlReader(OstProto::StreamConfigList *streams,
const QVariantMap &options)
{
//gPdmlReader = this;
pcap_ = NULL;
streams_ = streams;
recalculateCksums_ = options.value("RecalculateCksums").toBool();
currentStream_ = NULL;
prevStream_ = NULL;
@ -353,6 +356,8 @@ void PdmlReader::readProto()
pdmlProto = appendPdmlProto(protoName, &pbProto);
pdmlProto->setRecalculateCksum(recalculateCksums_);
qDebug("%s: preProtocolHandler(expPos = %d)",
qPrintable(protoName), expPos_);
pdmlProto->preProtocolHandler(protoName, attributes(), expPos_, pbProto,

View File

@ -24,13 +24,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include <QByteArray>
#include <QXmlStreamReader>
#include <QVariantMap>
class PcapFileFormat;
class PdmlReader : public QObject, public QXmlStreamReader
{
Q_OBJECT
public:
PdmlReader(OstProto::StreamConfigList *streams);
PdmlReader(OstProto::StreamConfigList *streams,
const QVariantMap &options = QVariantMap());
~PdmlReader();
bool read(QIODevice *device, PcapFileFormat *pcap = NULL,
@ -64,6 +66,8 @@ private:
PcapFileFormat *pcap_;
QByteArray pktBuf_;
bool recalculateCksums_{false};
bool isMldSupport_;
int packetCount_;
int expPos_;

View File

@ -236,3 +236,33 @@ QStringList ProtocolManager::protocolDatabase()
{
return numberToNameMap.values();
}
#if 0
void ProtocolManager::showFieldAttribs()
{
QStringList protocolList = protocolDatabase();
Stream stream;
foreach(QString name, protocolList) {
if (name.contains("/")) // assume combo
continue;
AbstractProtocol *protocol = OstProtocolManager->createProtocol(name, &stream);
int count = protocol->fieldCount();
for (int i = 0; i < count; i++) {
if (!protocol->fieldFlags(i).testFlag(AbstractProtocol::FrameField))
continue;
uint bitSize = protocol->fieldData(i, AbstractProtocol::FieldBitSize)
.toInt();
qDebug("$$$$, %s, %d, %s, %u, %x, %llu",
qPrintable(name),
i,
qPrintable(protocol->fieldData(i, AbstractProtocol::FieldName).toString()),
bitSize,
0, // min
(1 << bitSize) - 1);
}
delete protocol;
}
}
#endif

View File

@ -87,7 +87,9 @@ void PdmlSampleProtocol::prematureEndHandler(int /*pos*/,
fields such as length, checksum etc. may be correct or incorrect in the
PCAP/PDML - to retain the same value as in the PCAP/PDML and not let
Ostinato recalculate these, you can set the is_override_length,
is_override_cksum meta-fields to true here
is_override_cksum meta-fields to true here; for cksum, use the base
class attribute overrideCksum_ to decide - this is set based on user
input
*/
void PdmlSampleProtocol::postProtocolHandler(OstProto::Protocol* /*pbProto*/,
OstProto::Stream* /*stream*/)

View File

@ -18,11 +18,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#include "streambase.h"
#include "abstractprotocol.h"
#include "framevalueattrib.h"
#include "protocollist.h"
#include "protocollistiterator.h"
#include "protocolmanager.h"
#include "uint128.h"
#include <QDebug>
extern ProtocolManager *OstProtocolManager;
extern quint64 getDeviceMacAddress(int portId, int streamId, int frameIndex);
@ -598,6 +602,64 @@ int StreamBase::frameValue(uchar *buf, int bufMaxSize, int frameIndex,
return len;
}
template <typename T>
int StreamBase::findReplace(quint32 protocolNumber, int fieldIndex,
QVariant findValue, QVariant findMask,
QVariant replaceValue, QVariant replaceMask)
{
int replaceCount = 0;
ProtocolListIterator *iter = createProtocolListIterator();
// FIXME: Because protocol list iterator is unaware of combo protocols
// search for ip4.src will NOT succeed in a combo protocol containing ip4
while (iter->hasNext()) {
AbstractProtocol *proto = iter->next();
if (proto->protocolNumber() != protocolNumber)
continue;
T fieldValue = proto->fieldData(fieldIndex,
AbstractProtocol::FieldValue).value<T>();
qDebug() << "findReplace:"
<< "stream" << mStreamId->id()
<< "field" << fieldValue
<< "findMask" << hex << findMask.value<T>() << dec
<< "findValue" << findValue.value<T>();
if ((fieldValue & findMask.value<T>()) == findValue.value<T>()) {
T newValue = (fieldValue & ~replaceMask.value<T>())
| (replaceValue.value<T>() & replaceMask.value<T>());
qDebug() << "findReplace:"
<< "replaceMask" << hex << replaceMask.value<T>() << dec
<< "replaceValue" << replaceValue.value<T>()
<< "newValue" << newValue;
QVariant nv;
nv.setValue(newValue);
if (proto->setFieldData(fieldIndex, nv))
replaceCount++;
}
}
delete iter;
return replaceCount;
}
int StreamBase::protocolFieldReplace(quint32 protocolNumber,
int fieldIndex, int fieldBitSize,
QVariant findValue, QVariant findMask,
QVariant replaceValue, QVariant replaceMask)
{
if (fieldBitSize <= 64)
return findReplace<qulonglong>(protocolNumber, fieldIndex,
findValue, findMask, replaceValue, replaceMask);
if (fieldBitSize == 128)
return findReplace<UInt128>(protocolNumber, fieldIndex,
findValue, findMask, replaceValue, replaceMask);
qWarning("Unknown find/replace type %d", findValue.type());
return 0;
}
quint64 StreamBase::deviceMacAddress(int frameIndex) const
{
return getDeviceMacAddress(portId_, int(mStreamId->id()), frameIndex);

View File

@ -22,6 +22,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include <QString>
#include <QLinkedList>
#include <QVariant>
#include "protocol.pb.h"
@ -141,6 +142,11 @@ public:
int frameValue(uchar *buf, int bufMaxSize, int frameIndex,
FrameValueAttrib *attrib = nullptr) const;
int protocolFieldReplace(quint32 protocolNumber,
int fieldIndex, int fieldBitSize,
QVariant findValue, QVariant findMask,
QVariant replaceValue, QVariant replaceMask);
quint64 deviceMacAddress(int frameIndex) const;
quint64 neighborMacAddress(int frameIndex) const;
@ -149,6 +155,10 @@ public:
static bool StreamLessThan(StreamBase* stream1, StreamBase* stream2);
private:
template <typename T>
int findReplace(quint32 protocolNumber, int fieldIndex,
QVariant findValue, QVariant findMask,
QVariant replaceValue, QVariant replaceMask);
int portId_;
OstProto::StreamId *mStreamId;

View File

@ -78,7 +78,7 @@ void PdmlTcpProtocol::postProtocolHandler(OstProto::Protocol *pbProto,
tcp->set_is_override_src_port(true);
tcp->set_is_override_dst_port(true);
tcp->set_is_override_hdrlen(true);
tcp->set_is_override_cksum(true);
tcp->set_is_override_cksum(overrideCksum_);
if (options_.size())
{

View File

@ -48,6 +48,6 @@ void PdmlUdpProtocol::postProtocolHandler(OstProto::Protocol *pbProto,
udp->set_is_override_src_port(true);
udp->set_is_override_dst_port(true);
udp->set_is_override_totlen(true);
udp->set_is_override_cksum(true);
udp->set_is_override_cksum(overrideCksum_);
}

View File

@ -54,6 +54,7 @@ private:
quint64 lo_;
quint8 array_[16];
};
Q_DECLARE_METATYPE(UInt128);
inline UInt128::UInt128()
{

View File

@ -0,0 +1,80 @@
/*
Copyright (C) 2021 Srivats P.
This file is part of "Ostinato"
This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#ifndef _ULONGLONG_VALIDATOR_H
#define _ULONGLONG_VALIDATOR_H
#include <QValidator>
class ULongLongValidator : public QValidator
{
public:
ULongLongValidator(QObject *parent = 0)
: QValidator(parent)
{
}
~ULongLongValidator() {}
void setRange(qulonglong min, qulonglong max)
{
min_ = min;
max_ = max;
}
virtual QValidator::State validate(QString &input, int& /*pos*/) const
{
if (input.isEmpty())
return Intermediate;
if (input.compare("0x", Qt::CaseInsensitive) == 0)
return Intermediate;
bool isOk;
qulonglong v = input.toULongLong(&isOk, 0);
//qDebug("input: %s, ok: %d, %llu", qPrintable(input), isOk, v);
if (!isOk)
return Invalid;
if (v > max_)
return Invalid;
if (v < min_)
return Intermediate;
return Acceptable;
}
virtual void fixup(QString &input) const
{
int dummyPos = 0;
State state = validate(input, dummyPos);
if (state == Acceptable)
return;
input.setNum(min_);
}
private:
qulonglong min_{0};
qulonglong max_{~0ULL};
};
#endif