Merge branch 'master' into gre

This commit is contained in:
Srivats P 2022-05-21 12:16:14 +05:30
commit c8cc7a021f
103 changed files with 10599 additions and 1048 deletions

76
.github/CODE_OF_CONDUCT.md vendored Normal file
View File

@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at support@ostinato.org. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq

16
.github/CONTRIBUTING.md vendored Normal file
View File

@ -0,0 +1,16 @@
# Contributing Guidelines
Please use a Pull Request to contribute code. Very small fixes (< 10 lines) can provide the diff via an issue instead of a PR.
In either case, you agree to the below legal terms and you indicate your acceptance by explicitly adding a comment to the issue or PR stating -
```
I have read the contributing guidelines (CONTRIBUTING.md) and I hereby assign the copyrights of these
changes to [Srivats P](https://github.com/pstavirs).
```
## Legal
By submitting a Pull Request, you disavow any rights or claims to any changes submitted to the Ostinato project and assign the copyright of those changes to [Srivats P](https://github.com/pstavirs).
If you cannot or do not want to reassign those rights (your employment contract for your employer may not allow this), you should not submit a PR. Open an issue and someone else can do the work.
This is a legal way of saying "_If you submit a PR to us, that code becomes ours_". 99.9% of the time that's what you intend anyways; we hope it doesn't scare you away from contributing.

View File

@ -1,4 +1,5 @@
language: cpp language: cpp
osx_image: xcode11.3
os: os:
- linux - linux
@ -13,17 +14,14 @@ matrix:
- os: osx - os: osx
compiler: gcc compiler: gcc
before_install: before_script:
- "if [ $TRAVIS_OS_NAME = 'osx' ]; then \ - "if [ $TRAVIS_OS_NAME = 'osx' ]; then \
brew update && \
brew install qt5 && \
brew link qt5 --force && \
brew install protobuf && \
ls -lR /usr/local/include/google/protobuf; \ ls -lR /usr/local/include/google/protobuf; \
which clang++; \ which clang++; \
clang++ -E -x c++ - -v < /dev/null; \ clang++ -E -x c++ - -v < /dev/null; \
export CPLUS_INCLUDE_PATH=/usr/local/include; \ export CPLUS_INCLUDE_PATH=/usr/local/include; \
export LIBRARY_PATH=/usr/local/lib; \ export LIBRARY_PATH=/usr/local/lib; \
export PATH=/usr/local/opt/qt/bin:$PATH; \
fi" fi"
addons: addons:
@ -37,7 +35,10 @@ addons:
- protobuf-compiler - protobuf-compiler
- libnl-3-dev - libnl-3-dev
- libnl-route-3-dev - libnl-route-3-dev
homebrew:
packages:
- qt5
- protobuf
script: script:
- QT_SELECT=qt5 qmake -config debug - QT_SELECT=qt5 qmake -config debug
- make - make

View File

@ -1,6 +1,6 @@
# Ostinato # Ostinato
[![Build Status](https://travis-ci.org/pstavirs/ostinato.svg?branch=master)](https://travis-ci.org/pstavirs/ostinato) [![Build Status](https://app.travis-ci.com/pstavirs/ostinato.svg?branch=master)](https://app.travis-ci.com/pstavirs/ostinato)
This is the code repository for the Ostinato network packet crafter and traffic generator This is the code repository for the Ostinato network packet crafter and traffic generator

View File

@ -262,8 +262,10 @@ void DumpView::paintEvent(QPaintEvent* /*event*/)
// FIXME(LOW): unable to set the self widget's font in constructor // FIXME(LOW): unable to set the self widget's font in constructor
painter.setFont(QFont("Courier")); painter.setFont(QFont("Courier"));
// set a white background // Qt automatically clears the background before we are called
painter.fillRect(rect(), QBrush(QColor(Qt::white))); // QWidget::paintEvent doc:
// When the paint event occurs, the update region has normally
// been erased, so you are painting on the widget's background.
if (model()) if (model())
{ {

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

@ -182,6 +182,8 @@ void LogsWindow::alert(State state)
// start - center of main window // start - center of main window
QRect start; QRect start;
QWidget *view = mainWindow; QWidget *view = mainWindow;
if (!view)
return;
alert_->setParent(view); alert_->setParent(view);
alert_->raise(); alert_->raise();
start.setSize(QSize(256, 256).scaled(view->size()/2, Qt::KeepAspectRatio)); start.setSize(QSize(256, 256).scaled(view->size()/2, Qt::KeepAspectRatio));

View File

@ -24,6 +24,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "params.h" #include "params.h"
#include "preferences.h" #include "preferences.h"
#include "settings.h" #include "settings.h"
#include "thememanager.h"
#include <QApplication> #include <QApplication>
#include <QDateTime> #include <QDateTime>
@ -84,6 +85,8 @@ int main(int argc, char* argv[])
appSettings->value(kDiffPathKey, kDiffPathDefaultValue).toString(), appSettings->value(kDiffPathKey, kDiffPathDefaultValue).toString(),
appSettings->value(kAwkPathKey, kAwkPathDefaultValue).toString()); appSettings->value(kAwkPathKey, kAwkPathDefaultValue).toString());
ThemeManager::instance()->setTheme(appSettings->value(kThemeKey).toString());
qsrand(QDateTime::currentDateTime().toTime_t()); qsrand(QDateTime::currentDateTime().toTime_t());
mainWindow = new MainWindow; mainWindow = new MainWindow;

View File

@ -119,8 +119,11 @@ MainWindow::MainWindow(QWidget *parent)
setupUi(this); setupUi(this);
menuFile->insertActions(menuFile->actions().at(3), portsWindow->actions()); menuFile->insertActions(menuFile->actions().at(3),
portsWindow->portActions());
menuEdit->addActions(clipboardHelper->actions()); menuEdit->addActions(clipboardHelper->actions());
menuStreams->addActions(portsWindow->streamActions());
menuDevices->addActions(portsWindow->deviceActions());
statsDock->setWidget(statsWindow); statsDock->setWidget(statsWindow);
addDockWidget(Qt::BottomDockWidgetArea, statsDock); addDockWidget(Qt::BottomDockWidgetArea, statsDock);

View File

@ -25,21 +25,10 @@
<addaction name="actionOpenSession" /> <addaction name="actionOpenSession" />
<addaction name="actionSaveSession" /> <addaction name="actionSaveSession" />
<addaction name="separator" /> <addaction name="separator" />
<addaction name="separator" />
<addaction name="actionPreferences" /> <addaction name="actionPreferences" />
<addaction name="actionFileExit" /> <addaction name="actionFileExit" />
</widget> </widget>
<widget class="QMenu" name="menuHelp" >
<property name="title" >
<string>&amp;Help</string>
</property>
<addaction name="actionHelpOnline" />
<addaction name="separator" />
<addaction name="actionDonate" />
<addaction name="actionCheckForUpdates" />
<addaction name="separator" />
<addaction name="actionHelpAbout" />
<addaction name="actionAboutQt" />
</widget>
<widget class="QMenu" name="menuEdit" > <widget class="QMenu" name="menuEdit" >
<property name="title" > <property name="title" >
<string>&amp;Edit</string> <string>&amp;Edit</string>
@ -52,9 +41,34 @@
<addaction name="actionViewShowMyReservedPortsOnly" /> <addaction name="actionViewShowMyReservedPortsOnly" />
<addaction name="actionViewRestoreDefaults" /> <addaction name="actionViewRestoreDefaults" />
</widget> </widget>
<widget class="QMenu" name="menuStreams" >
<property name="title" >
<string>&amp;Streams</string>
</property>
<addaction name="actionTest" />
</widget>
<widget class="QMenu" name="menuDevices" >
<property name="title" >
<string>&amp;Devices</string>
</property>
</widget>
<widget class="QMenu" name="menuHelp" >
<property name="title" >
<string>&amp;Help</string>
</property>
<addaction name="actionHelpOnline" />
<addaction name="separator" />
<addaction name="actionDonate" />
<addaction name="actionCheckForUpdates" />
<addaction name="separator" />
<addaction name="actionHelpAbout" />
<addaction name="actionAboutQt" />
</widget>
<addaction name="menuFile" /> <addaction name="menuFile" />
<addaction name="menuEdit" /> <addaction name="menuEdit" />
<addaction name="menuView" /> <addaction name="menuView" />
<addaction name="menuStreams" />
<addaction name="menuDevices" />
<addaction name="menuHelp" /> <addaction name="menuHelp" />
</widget> </widget>
<widget class="QStatusBar" name="statusbar" /> <widget class="QStatusBar" name="statusbar" />

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 \ devicemodel.h \
deviceswidget.h \ deviceswidget.h \
dumpview.h \ dumpview.h \
fieldedit.h \
hexlineedit.h \ hexlineedit.h \
logsmodel.h \ logsmodel.h \
logswindow.h \ logswindow.h \
findreplace.h \
mainwindow.h \ mainwindow.h \
mandatoryfieldsgroup.h \
ndpstatusmodel.h \ ndpstatusmodel.h \
packetmodel.h \ packetmodel.h \
port.h \ port.h \
@ -57,6 +60,7 @@ HEADERS += \
portstatsproxymodel.h \ portstatsproxymodel.h \
portstatswindow.h \ portstatswindow.h \
portswindow.h \ portswindow.h \
portwidget.h \
preferences.h \ preferences.h \
settings.h \ settings.h \
streamconfigdialog.h \ streamconfigdialog.h \
@ -65,6 +69,7 @@ HEADERS += \
streamstatsfiltermodel.h \ streamstatsfiltermodel.h \
streamstatsmodel.h \ streamstatsmodel.h \
streamstatswindow.h \ streamstatswindow.h \
streamswidget.h \
variablefieldswidget.h \ variablefieldswidget.h \
xtableview.h xtableview.h
@ -72,15 +77,18 @@ FORMS += \
about.ui \ about.ui \
devicegroupdialog.ui \ devicegroupdialog.ui \
deviceswidget.ui \ deviceswidget.ui \
findreplace.ui \
logswindow.ui \ logswindow.ui \
mainwindow.ui \ mainwindow.ui \
portconfigdialog.ui \ portconfigdialog.ui \
portstatsfilter.ui \ portstatsfilter.ui \
portstatswindow.ui \ portstatswindow.ui \
portswindow.ui \ portswindow.ui \
portwidget.ui \
preferences.ui \ preferences.ui \
streamconfigdialog.ui \ streamconfigdialog.ui \
streamstatswindow.ui \ streamstatswindow.ui \
streamswidget.ui \
variablefieldswidget.ui variablefieldswidget.ui
SOURCES += \ SOURCES += \
@ -95,8 +103,11 @@ SOURCES += \
hexlineedit.cpp \ hexlineedit.cpp \
logsmodel.cpp \ logsmodel.cpp \
logswindow.cpp \ logswindow.cpp \
fieldedit.cpp \
findreplace.cpp \
main.cpp \ main.cpp \
mainwindow.cpp \ mainwindow.cpp \
mandatoryfieldsgroup.cpp \
ndpstatusmodel.cpp \ ndpstatusmodel.cpp \
packetmodel.cpp \ packetmodel.cpp \
params.cpp \ params.cpp \
@ -109,18 +120,31 @@ SOURCES += \
portstatsfilterdialog.cpp \ portstatsfilterdialog.cpp \
portstatswindow.cpp \ portstatswindow.cpp \
portswindow.cpp \ portswindow.cpp \
portwidget.cpp \
preferences.cpp \ preferences.cpp \
streamconfigdialog.cpp \ streamconfigdialog.cpp \
streamlistdelegate.cpp \ streamlistdelegate.cpp \
streammodel.cpp \ streammodel.cpp \
streamstatsmodel.cpp \ streamstatsmodel.cpp \
streamstatswindow.cpp \ streamstatswindow.cpp \
streamswidget.cpp \
thememanager.cpp \
variablefieldswidget.cpp variablefieldswidget.cpp
THEMES += \
themes/material-dark.qss \
themes/material-dark.rcc \
themes/material-light.qss \
themes/material-light.rcc \
themes/qds-dark.qss \
themes/qds-dark.rcc \
themes/qds-light.qss \
themes/qds-light.rcc \
QMAKE_DISTCLEAN += object_script.* QMAKE_DISTCLEAN += object_script.*
include(../install.pri) include(../install.pri)
include(../shared.pri)
include(../version.pri) include(../version.pri)
include(../options.pri) include(../options.pri)

View File

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

View File

@ -88,7 +88,10 @@ void Port::updatePortConfig(OstProto::Port *port)
setAlias(QString("if%1").arg(id())); setAlias(QString("if%1").arg(id()));
if (recalc) if (recalc)
{
recalculateAverageRates(); recalculateAverageRates();
emit streamListChanged(mPortGroupId, mPortId); // show/hide 'next' col
}
} }
void Port::updateStreamOrdinalsFromIndex() void Port::updateStreamOrdinalsFromIndex()
@ -314,6 +317,12 @@ void Port::setAverageBitRate(double bitsPerSec)
emit portRateChanged(mPortGroupId, mPortId); emit portRateChanged(mPortGroupId, mPortId);
} }
void Port::setAverageLoadRate(double load)
{
Q_ASSERT(d.speed() > 0);
setAverageBitRate(load*d.speed()*1e6);
}
bool Port::newStreamAt(int index, OstProto::Stream const *stream) bool Port::newStreamAt(int index, OstProto::Stream const *stream)
{ {
Stream *s = new Stream; Stream *s = new Stream;
@ -600,7 +609,6 @@ bool Port::openStreams(QString fileName, bool append, QString &error)
int ret; int ret;
optDialog->setParent(mainWindow, Qt::Dialog); optDialog->setParent(mainWindow, Qt::Dialog);
ret = optDialog->exec(); ret = optDialog->exec();
optDialog->setParent(0, Qt::Dialog);
if (ret == QDialog::Rejected) if (ret == QDialog::Rejected)
goto _user_opt_cancel; goto _user_opt_cancel;
} }

View File

@ -102,6 +102,10 @@ public:
{ return d.transmit_mode(); } { return d.transmit_mode(); }
bool trackStreamStats() const bool trackStreamStats() const
{ return d.is_tracking_stream_stats(); } { return d.is_tracking_stream_stats(); }
double speed() const
{ return d.speed(); }
double averageLoadRate() const
{ return d.speed() ? avgBitsPerSec_/(d.speed()*1e6) : 0; }
double averagePacketRate() const double averagePacketRate() const
{ return avgPacketsPerSec_; } { return avgPacketsPerSec_; }
double averageBitRate() const double averageBitRate() const
@ -183,6 +187,7 @@ public:
void setAveragePacketRate(double packetsPerSec); void setAveragePacketRate(double packetsPerSec);
void setAverageBitRate(double bitsPerSec); void setAverageBitRate(double bitsPerSec);
void setAverageLoadRate(double loadPercent);
// FIXME(MED): Bad Hack! port should not need an external trigger to // FIXME(MED): Bad Hack! port should not need an external trigger to
// recalculate - refactor client side domain objects and model objects // recalculate - refactor client side domain objects and model objects
void recalculateAverageRates(); void recalculateAverageRates();

View File

@ -206,6 +206,7 @@ void PortGroup::processVersionCompatibility(PbRpcController *controller)
logError(id(), QString("checkVersion failed: %1") logError(id(), QString("checkVersion failed: %1")
.arg(QString::fromStdString(verCompat->notes()))); .arg(QString::fromStdString(verCompat->notes())));
compat = kIncompatible; compat = kIncompatible;
reconnect = false;
emit portGroupDataChanged(mPortGroupId); emit portGroupDataChanged(mPortGroupId);
QMessageBox msgBox; QMessageBox msgBox;
@ -289,8 +290,6 @@ void PortGroup::on_rpcChannel_error(QAbstractSocket::SocketError socketError)
" - is it drone or some other process?") " - is it drone or some other process?")
.arg(rpcChannel->serverName()) .arg(rpcChannel->serverName())
.arg(rpcChannel->serverPort())); .arg(rpcChannel->serverPort()));
// fall-through
case QAbstractSocket::RemoteHostClosedError:
reconnect = false; reconnect = false;
break; break;
default: default:

View File

@ -110,8 +110,10 @@ void PortGroupList::addPortGroup(PortGroup &portGroup)
void PortGroupList::removePortGroup(PortGroup &portGroup) void PortGroupList::removePortGroup(PortGroup &portGroup)
{ {
mPortGroupListModel.portGroupAboutToBeRemoved(&portGroup); // Disconnect before removing from list
portGroup.disconnectFromHost();
mPortGroupListModel.portGroupAboutToBeRemoved(&portGroup);
PortGroup* pg = mPortGroups.takeAt(mPortGroups.indexOf(&portGroup)); PortGroup* pg = mPortGroups.takeAt(mPortGroups.indexOf(&portGroup));
qDebug("after takeAt()"); qDebug("after takeAt()");
mPortGroupListModel.portGroupRemoved(); mPortGroupListModel.portGroupRemoved();
@ -128,11 +130,12 @@ void PortGroupList::removeAllPortGroups()
do { do {
PortGroup *pg = mPortGroups.at(0); PortGroup *pg = mPortGroups.at(0);
pg->disconnectFromHost();
mPortGroupListModel.portGroupAboutToBeRemoved(pg); mPortGroupListModel.portGroupAboutToBeRemoved(pg);
mPortGroups.removeFirst(); mPortGroups.removeFirst();
delete pg; delete pg;
mPortGroupListModel.portGroupRemoved();
} while (!mPortGroups.isEmpty()); } while (!mPortGroups.isEmpty());
mPortGroupListModel.portGroupRemoved();
mPortGroupListModel.when_portListChanged(); mPortGroupListModel.when_portListChanged();
mPortStatsModel.when_portListChanged(); mPortStatsModel.when_portListChanged();

View File

@ -165,7 +165,7 @@
<string>Stop Capture</string> <string>Stop Capture</string>
</property> </property>
<property name="statusTip"> <property name="statusTip">
<string>End capture on selecteed port(s)</string> <string>End capture on selected port(s)</string>
</property> </property>
<property name="text"> <property name="text">
<string>Stop</string> <string>Stop</string>

View File

@ -20,51 +20,37 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "portswindow.h" #include "portswindow.h"
#include "applymsg.h" #include "applymsg.h"
#include "clipboardhelper.h"
#include "deviceswidget.h" #include "deviceswidget.h"
#include "portconfigdialog.h"
#include "settings.h"
#include "streamconfigdialog.h"
#include "streamfileformat.h"
#include "streamlistdelegate.h"
#include "fileformat.pb.h" #include "fileformat.pb.h"
#include "portconfigdialog.h"
#include "portgrouplist.h"
#include "portwidget.h"
#include "settings.h"
#include "streamswidget.h"
#include "xqlocale.h"
#include <QFileInfo>
#include <QInputDialog> #include <QInputDialog>
#include <QItemSelectionModel>
#include <QMainWindow> #include <QMainWindow>
#include <QMessageBox> #include <QMessageBox>
#include <QProgressDialog>
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
extern ClipboardHelper *clipboardHelper;
extern QMainWindow *mainWindow; extern QMainWindow *mainWindow;
PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent) PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent)
: QWidget(parent), proxyPortModel(NULL) : QWidget(parent), proxyPortModel(NULL)
{ {
QAction *sep;
delegate = new StreamListDelegate;
proxyPortModel = new QSortFilterProxyModel(this); proxyPortModel = new QSortFilterProxyModel(this);
//slm = new StreamListModel();
//plm = new PortGroupList();
plm = pgl; plm = pgl;
setupUi(this); setupUi(this);
applyMsg_ = new ApplyMessage(); applyMsg_ = new ApplyMessage();
portWidget->setPortGroupList(plm);
streamsWidget->setPortGroupList(plm);
devicesWidget->setPortGroupList(plm); devicesWidget->setPortGroupList(plm);
tvPortList->header()->hide(); tvPortList->header()->hide();
tvStreamList->setItemDelegate(delegate);
tvStreamList->verticalHeader()->setDefaultSectionSize(
tvStreamList->verticalHeader()->minimumSectionSize());
// Populate PortList Context Menu Actions // Populate PortList Context Menu Actions
tvPortList->addAction(actionNew_Port_Group); tvPortList->addAction(actionNew_Port_Group);
tvPortList->addAction(actionDelete_Port_Group); tvPortList->addAction(actionDelete_Port_Group);
@ -74,41 +60,18 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent)
tvPortList->addAction(actionExclusive_Control); tvPortList->addAction(actionExclusive_Control);
tvPortList->addAction(actionPort_Configuration); tvPortList->addAction(actionPort_Configuration);
// Populate StreamList Context Menu Actions
tvStreamList->addAction(actionNew_Stream);
tvStreamList->addAction(actionEdit_Stream);
tvStreamList->addAction(actionDuplicate_Stream);
tvStreamList->addAction(actionDelete_Stream);
QAction *sep2 = new QAction(this);
sep2->setSeparator(true);
tvStreamList->addAction(sep2);
tvStreamList->addAction(actionOpen_Streams);
tvStreamList->addAction(actionSave_Streams);
// PortList, StreamList, DeviceWidget actions combined // PortList, StreamList, DeviceWidget actions combined
// make this window's actions // make this window's actions
addActions(tvPortList->actions()); addActions(tvPortList->actions());
sep = new QAction(this); QAction *sep = new QAction(this);
sep->setSeparator(true); sep->setSeparator(true);
addAction(sep); addAction(sep);
addActions(tvStreamList->actions()); addActions(streamsWidget->actions());
sep = new QAction(this); sep = new QAction(this);
sep->setSeparator(true); sep->setSeparator(true);
addAction(sep); addAction(sep);
addActions(devicesWidget->actions()); addActions(devicesWidget->actions());
// Add the clipboard actions to the context menu of streamList
// but not to PortsWindow's actions since they are already available
// in the global Edit Menu
sep = new QAction("Clipboard", this);
sep->setSeparator(true);
tvStreamList->insertAction(sep2, sep);
tvStreamList->insertActions(sep2, clipboardHelper->actions());
tvStreamList->setModel(plm->getStreamModel());
// XXX: It would be ideal if we only needed to do the below to // XXX: It would be ideal if we only needed to do the below to
// get the proxy model to do its magic. However, the QModelIndex // get the proxy model to do its magic. However, the QModelIndex
// used by the source model and the proxy model are different // used by the source model and the proxy model are different
@ -138,52 +101,24 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent)
SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)),
this, SLOT(when_portView_currentChanged(const QModelIndex&, this, SLOT(when_portView_currentChanged(const QModelIndex&,
const QModelIndex&))); const QModelIndex&)));
connect(this,
SIGNAL(currentPortChanged(const QModelIndex&, const QModelIndex&)),
portWidget, SLOT(setCurrentPortIndex(const QModelIndex&)));
connect(this,
SIGNAL(currentPortChanged(const QModelIndex&, const QModelIndex&)),
streamsWidget, SLOT(setCurrentPortIndex(const QModelIndex&)));
connect(this, connect(this,
SIGNAL(currentPortChanged(const QModelIndex&, const QModelIndex&)), SIGNAL(currentPortChanged(const QModelIndex&, const QModelIndex&)),
devicesWidget, SLOT(setCurrentPortIndex(const QModelIndex&))); devicesWidget, SLOT(setCurrentPortIndex(const QModelIndex&)));
connect(plm->getStreamModel(), SIGNAL(rowsInserted(QModelIndex, int, int)),
SLOT(updateStreamViewActions()));
connect(plm->getStreamModel(), SIGNAL(rowsRemoved(QModelIndex, int, int)),
SLOT(updateStreamViewActions()));
connect(tvStreamList->selectionModel(),
SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)),
SLOT(updateStreamViewActions()));
connect(tvStreamList->selectionModel(),
SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
SLOT(updateStreamViewActions()));
tvStreamList->resizeColumnToContents(StreamModel::StreamIcon);
tvStreamList->resizeColumnToContents(StreamModel::StreamStatus);
// Initially we don't have any ports/streams/devices // Initially we don't have any ports/streams/devices
// - so send signal triggers // - so send signal triggers
when_portView_currentChanged(QModelIndex(), QModelIndex()); when_portView_currentChanged(QModelIndex(), QModelIndex());
updateStreamViewActions();
connect(plm->getStreamModel(),
SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
this, SLOT(streamModelDataChanged()));
connect(plm->getStreamModel(),
SIGNAL(modelReset()),
this, SLOT(streamModelDataChanged()));
}
void PortsWindow::streamModelDataChanged()
{
QModelIndex current = tvPortList->currentIndex();
if (proxyPortModel)
current = proxyPortModel->mapToSource(current);
if (plm->isPort(current))
plm->port(current).recalculateAverageRates();
} }
PortsWindow::~PortsWindow() PortsWindow::~PortsWindow()
{ {
delete delegate;
delete proxyPortModel; delete proxyPortModel;
delete applyMsg_; delete applyMsg_;
} }
@ -298,6 +233,21 @@ bool PortsWindow::saveSession(
return true; return true;
} }
QList<QAction*> PortsWindow::portActions()
{
return tvPortList->actions();
}
QList<QAction*> PortsWindow::streamActions()
{
return streamsWidget->actions();
}
QList<QAction*> PortsWindow::deviceActions()
{
return devicesWidget->actions();
}
void PortsWindow::clearCurrentSelection() void PortsWindow::clearCurrentSelection()
{ {
tvPortList->selectionModel()->clearCurrentIndex(); tvPortList->selectionModel()->clearCurrentIndex();
@ -321,30 +271,6 @@ void PortsWindow::showMyReservedPortsOnly(bool enabled)
proxyPortModel->setFilterRegExp(QRegExp("")); proxyPortModel->setFilterRegExp(QRegExp(""));
} }
void PortsWindow::on_tvStreamList_activated(const QModelIndex & index)
{
if (!index.isValid())
{
qDebug("%s: invalid index", __FUNCTION__);
return;
}
qDebug("stream list activated\n");
Port &curPort = plm->port(proxyPortModel ?
proxyPortModel->mapToSource(tvPortList->currentIndex()) :
tvPortList->currentIndex());
QList<Stream*> streams;
streams.append(curPort.mutableStreamByIndex(index.row(), false));
StreamConfigDialog scd(streams, curPort, this);
if (scd.exec() == QDialog::Accepted) {
curPort.recalculateAverageRates();
curPort.setLocalConfigChanged(true);
}
}
void PortsWindow::when_portView_currentChanged(const QModelIndex& currentIndex, void PortsWindow::when_portView_currentChanged(const QModelIndex& currentIndex,
const QModelIndex& previousIndex) const QModelIndex& previousIndex)
{ {
@ -356,16 +282,12 @@ void PortsWindow::when_portView_currentChanged(const QModelIndex& currentIndex,
previous = proxyPortModel->mapToSource(previous); previous = proxyPortModel->mapToSource(previous);
} }
plm->getStreamModel()->setCurrentPortIndex(current);
updatePortViewActions(currentIndex); updatePortViewActions(currentIndex);
updateStreamViewActions();
qDebug("In %s", __FUNCTION__); qDebug("In %s", __FUNCTION__);
if (previous.isValid() && plm->isPort(previous)) if (previous.isValid() && plm->isPort(previous))
{ {
disconnect(&(plm->port(previous)), SIGNAL(portRateChanged(int, int)),
this, SLOT(updatePortRates()));
disconnect(&(plm->port(previous)), disconnect(&(plm->port(previous)),
SIGNAL(localConfigChanged(int, int, bool)), SIGNAL(localConfigChanged(int, int, bool)),
this, this,
@ -386,9 +308,6 @@ void PortsWindow::when_portView_currentChanged(const QModelIndex& currentIndex,
else if (plm->isPort(current)) else if (plm->isPort(current))
{ {
swDetail->setCurrentIndex(2); // port detail page swDetail->setCurrentIndex(2); // port detail page
updatePortRates();
connect(&(plm->port(current)), SIGNAL(portRateChanged(int, int)),
SLOT(updatePortRates()));
connect(&(plm->port(current)), connect(&(plm->port(current)),
SIGNAL(localConfigChanged(int, int, bool)), SIGNAL(localConfigChanged(int, int, bool)),
SLOT(updateApplyHint(int, int, bool))); SLOT(updateApplyHint(int, int, bool)));
@ -446,139 +365,6 @@ void PortsWindow::when_portModel_reset()
when_portView_currentChanged(QModelIndex(), tvPortList->currentIndex()); when_portView_currentChanged(QModelIndex(), tvPortList->currentIndex());
} }
void PortsWindow::on_startTx_clicked()
{
QModelIndex current = tvPortList->currentIndex();
if (proxyPortModel)
current = proxyPortModel->mapToSource(current);
Q_ASSERT(plm->isPort(current));
QModelIndex curPortGroup = plm->getPortModel()->parent(current);
Q_ASSERT(curPortGroup.isValid());
Q_ASSERT(plm->isPortGroup(curPortGroup));
QList<uint> portList({plm->port(current).id()});
plm->portGroup(curPortGroup).startTx(&portList);
}
void PortsWindow::on_stopTx_clicked()
{
QModelIndex current = tvPortList->currentIndex();
if (proxyPortModel)
current = proxyPortModel->mapToSource(current);
Q_ASSERT(plm->isPort(current));
QModelIndex curPortGroup = plm->getPortModel()->parent(current);
Q_ASSERT(curPortGroup.isValid());
Q_ASSERT(plm->isPortGroup(curPortGroup));
QList<uint> portList({plm->port(current).id()});
plm->portGroup(curPortGroup).stopTx(&portList);
}
void PortsWindow::on_averagePacketsPerSec_editingFinished()
{
QModelIndex current = tvPortList->currentIndex();
if (proxyPortModel)
current = proxyPortModel->mapToSource(current);
Q_ASSERT(plm->isPort(current));
bool isOk;
double pps = XLocale().toDouble(averagePacketsPerSec->text(), &isOk);
plm->port(current).setAveragePacketRate(pps);
}
void PortsWindow::on_averageBitsPerSec_editingFinished()
{
QModelIndex current = tvPortList->currentIndex();
if (proxyPortModel)
current = proxyPortModel->mapToSource(current);
Q_ASSERT(plm->isPort(current));
bool isOk;
double bps = XLocale().toDouble(averageBitsPerSec->text(), &isOk);
plm->port(current).setAverageBitRate(bps);
}
void PortsWindow::updatePortRates()
{
QModelIndex current = tvPortList->currentIndex();
if (proxyPortModel)
current = proxyPortModel->mapToSource(current);
if (!current.isValid())
return;
if (!plm->isPort(current))
return;
averagePacketsPerSec->setText(QString("%L1")
.arg(plm->port(current).averagePacketRate(), 0, 'f', 4));
averageBitsPerSec->setText(QString("%L1")
.arg(plm->port(current).averageBitRate(), 0, 'f', 0));
}
void PortsWindow::updateStreamViewActions()
{
QModelIndex current = tvPortList->currentIndex();
if (proxyPortModel)
current = proxyPortModel->mapToSource(current);
// For some reason hasSelection() returns true even if selection size is 0
// so additional check for size introduced
if (tvStreamList->selectionModel()->hasSelection() &&
(tvStreamList->selectionModel()->selection().size() > 0))
{
qDebug("Has selection %d",
tvStreamList->selectionModel()->selection().size());
// If more than one non-contiguous ranges selected,
// disable "New" and "Edit"
if (tvStreamList->selectionModel()->selection().size() > 1)
{
actionNew_Stream->setDisabled(true);
actionEdit_Stream->setDisabled(true);
}
else
{
actionNew_Stream->setEnabled(true);
actionEdit_Stream->setEnabled(true);
}
// Duplicate/Delete are always enabled as long as we have a selection
actionDuplicate_Stream->setEnabled(true);
actionDelete_Stream->setEnabled(true);
}
else
{
qDebug("No selection");
if (plm->isPort(current))
actionNew_Stream->setEnabled(true);
else
actionNew_Stream->setDisabled(true);
actionEdit_Stream->setDisabled(true);
actionDuplicate_Stream->setDisabled(true);
actionDelete_Stream->setDisabled(true);
}
actionOpen_Streams->setEnabled(plm->isPort(current));
actionSave_Streams->setEnabled(tvStreamList->model()->rowCount() > 0);
startTx->setEnabled(tvStreamList->model()->rowCount() > 0);
stopTx->setEnabled(tvStreamList->model()->rowCount() > 0);
}
void PortsWindow::updateApplyHint(int /*portGroupId*/, int /*portId*/, void PortsWindow::updateApplyHint(int /*portGroupId*/, int /*portId*/,
bool configChanged) bool configChanged)
{ {
@ -586,7 +372,7 @@ void PortsWindow::updateApplyHint(int /*portGroupId*/, int /*portId*/,
applyHint->setText("Configuration has changed - " applyHint->setText("Configuration has changed - "
"<font color='red'><b>click Apply</b></font> " "<font color='red'><b>click Apply</b></font> "
"to activate the changes"); "to activate the changes");
else if (tvStreamList->model()->rowCount() > 0) else if (plm->getStreamModel()->rowCount() > 0)
applyHint->setText("Configuration activated - " applyHint->setText("Configuration activated - "
"click <img src=':/icons/control_play'/> " "click <img src=':/icons/control_play'/> "
"to transmit packets"); "to transmit packets");
@ -838,258 +624,3 @@ void PortsWindow::on_actionPort_Configuration_triggered()
if (dialog.exec() == QDialog::Accepted) if (dialog.exec() == QDialog::Accepted)
plm->portGroup(current.parent()).modifyPort(current.row(), config); plm->portGroup(current.parent()).modifyPort(current.row(), config);
} }
void PortsWindow::on_actionNew_Stream_triggered()
{
qDebug("New Stream Action");
QItemSelectionModel* selectionModel = tvStreamList->selectionModel();
if (selectionModel->selection().size() > 1) {
qDebug("%s: Unexpected selection size %d, can't add", __FUNCTION__,
selectionModel->selection().size());
return;
}
// In case nothing is selected, insert 1 row at the end
StreamModel *streamModel = plm->getStreamModel();
int row = streamModel->rowCount(), count = 1;
// In case we have a single range selected; insert as many rows as
// in the singe selected range before the top of the selected range
if (selectionModel->selection().size() == 1)
{
row = selectionModel->selection().at(0).top();
count = selectionModel->selection().at(0).height();
}
Port &curPort = plm->port(proxyPortModel ?
proxyPortModel->mapToSource(tvPortList->currentIndex()) :
tvPortList->currentIndex());
QList<Stream*> streams;
for (int i = 0; i < count; i++)
streams.append(new Stream);
StreamConfigDialog scd(streams, curPort, this);
scd.setWindowTitle(tr("Add Stream"));
if (scd.exec() == QDialog::Accepted)
streamModel->insert(row, streams);
}
void PortsWindow::on_actionEdit_Stream_triggered()
{
qDebug("Edit Stream Action");
QItemSelectionModel* streamModel = tvStreamList->selectionModel();
if (!streamModel->hasSelection())
return;
Port &curPort = plm->port(proxyPortModel ?
proxyPortModel->mapToSource(tvPortList->currentIndex()) :
tvPortList->currentIndex());
QList<Stream*> streams;
foreach(QModelIndex index, streamModel->selectedRows())
streams.append(curPort.mutableStreamByIndex(index.row(), false));
StreamConfigDialog scd(streams, curPort, this);
if (scd.exec() == QDialog::Accepted) {
curPort.recalculateAverageRates();
curPort.setLocalConfigChanged(true);
}
}
void PortsWindow::on_actionDuplicate_Stream_triggered()
{
QItemSelectionModel* model = tvStreamList->selectionModel();
QModelIndex current = tvPortList->selectionModel()->currentIndex();
qDebug("Duplicate Stream Action");
if (proxyPortModel)
current = proxyPortModel->mapToSource(current);
if (model->hasSelection())
{
bool isOk;
int count = QInputDialog::getInt(this, "Duplicate Streams",
"Count", 1, 1, 9999, 1, &isOk);
if (!isOk)
return;
QList<int> list;
foreach(QModelIndex index, model->selectedRows())
list.append(index.row());
plm->port(current).duplicateStreams(list, count);
}
else
qDebug("No selection");
}
void PortsWindow::on_actionDelete_Stream_triggered()
{
qDebug("Delete Stream Action");
QModelIndex index;
if (tvStreamList->selectionModel()->hasSelection())
{
qDebug("SelectedIndexes %d",
tvStreamList->selectionModel()->selectedRows().size());
while(tvStreamList->selectionModel()->selectedRows().size())
{
index = tvStreamList->selectionModel()->selectedRows().at(0);
plm->getStreamModel()->removeRows(index.row(), 1);
}
}
else
qDebug("No selection");
}
void PortsWindow::on_actionOpen_Streams_triggered()
{
qDebug("Open Streams Action");
QStringList fileTypes = StreamFileFormat::supportedFileTypes(
StreamFileFormat::kOpenFile);
QString fileType;
QModelIndex current = tvPortList->selectionModel()->currentIndex();
static QString dirName;
QString fileName;
QString errorStr;
bool append = true;
bool ret;
if (proxyPortModel)
current = proxyPortModel->mapToSource(current);
Q_ASSERT(plm->isPort(current));
if (fileTypes.size())
fileType = fileTypes.at(0);
fileName = QFileDialog::getOpenFileName(this, tr("Open Streams"),
dirName, fileTypes.join(";;"), &fileType);
if (fileName.isEmpty())
goto _exit;
if (tvStreamList->model()->rowCount())
{
QMessageBox msgBox(QMessageBox::Question, qApp->applicationName(),
tr("Append to existing streams? Or overwrite?"),
QMessageBox::NoButton, this);
QPushButton *appendBtn = msgBox.addButton(tr("Append"),
QMessageBox::ActionRole);
QPushButton *overwriteBtn = msgBox.addButton(tr("Overwrite"),
QMessageBox::ActionRole);
QPushButton *cancelBtn = msgBox.addButton(QMessageBox::Cancel);
msgBox.exec();
if (msgBox.clickedButton() == cancelBtn)
goto _exit;
else if (msgBox.clickedButton() == appendBtn)
append = true;
else if (msgBox.clickedButton() == overwriteBtn)
append = false;
else
Q_ASSERT(false);
}
ret = plm->port(current).openStreams(fileName, append, errorStr);
if (!ret || !errorStr.isEmpty())
{
QMessageBox msgBox(this);
QStringList str = errorStr.split("\n\n\n\n");
msgBox.setIcon(ret ? QMessageBox::Warning : QMessageBox::Critical);
msgBox.setWindowTitle(qApp->applicationName());
msgBox.setText(str.at(0));
if (str.size() > 1)
msgBox.setDetailedText(str.at(1));
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.exec();
}
dirName = QFileInfo(fileName).absolutePath();
_exit:
return;
}
void PortsWindow::on_actionSave_Streams_triggered()
{
qDebug("Save Streams Action");
QModelIndex current = tvPortList->selectionModel()->currentIndex();
static QString fileName;
QStringList fileTypes = StreamFileFormat::supportedFileTypes(
StreamFileFormat::kSaveFile);
QString fileType;
QString errorStr;
QFileDialog::Options options;
if (proxyPortModel)
current = proxyPortModel->mapToSource(current);
// On Mac OS with Native Dialog, getSaveFileName() ignores fileType
// which we need
#if defined(Q_OS_MAC)
options |= QFileDialog::DontUseNativeDialog;
#endif
if (fileTypes.size())
fileType = fileTypes.at(0);
Q_ASSERT(plm->isPort(current));
_retry:
fileName = QFileDialog::getSaveFileName(this, tr("Save Streams"),
fileName, fileTypes.join(";;"), &fileType, options);
if (fileName.isEmpty())
goto _exit;
if (QFileInfo(fileName).suffix().isEmpty()) {
QString fileExt = fileType.section(QRegExp("[\\*\\)]"), 1, 1);
qDebug("Adding extension '%s' to '%s'",
qPrintable(fileExt), qPrintable(fileName));
fileName.append(fileExt);
if (QFileInfo(fileName).exists()) {
if (QMessageBox::warning(this, tr("Overwrite File?"),
QString("The file \"%1\" already exists.\n\n"
"Do you wish to overwrite it?")
.arg(QFileInfo(fileName).fileName()),
QMessageBox::Yes|QMessageBox::No,
QMessageBox::No) != QMessageBox::Yes)
goto _retry;
}
}
fileType = fileType.remove(QRegExp("\\(.*\\)")).trimmed();
if (!fileType.startsWith("Ostinato")
&& !fileType.startsWith("Python"))
{
if (QMessageBox::warning(this, tr("Ostinato"),
QString("You have chosen to save in %1 format. All stream "
"attributes may not be saved in this format.\n\n"
"It is recommended to save in native Ostinato format.\n\n"
"Continue to save in %2 format?").arg(fileType).arg(fileType),
QMessageBox::Yes|QMessageBox::No,
QMessageBox::No) != QMessageBox::Yes)
goto _retry;
}
// TODO: all or selected?
if (!plm->port(current).saveStreams(fileName, fileType, errorStr))
QMessageBox::critical(this, qApp->applicationName(), errorStr);
else if (!errorStr.isEmpty())
QMessageBox::warning(this, qApp->applicationName(), errorStr);
fileName = QFileInfo(fileName).absolutePath();
_exit:
return;
}

View File

@ -20,13 +20,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#ifndef _PORTS_WINDOW_H #ifndef _PORTS_WINDOW_H
#define _PORTS_WINDOW_H #define _PORTS_WINDOW_H
#include <QWidget>
#include <QAbstractItemModel>
#include "ui_portswindow.h" #include "ui_portswindow.h"
#include "portgrouplist.h" #include <QWidget>
class ApplyMessage; class ApplyMessage;
class QAbstractItemDelegate; class PortGroupList;
class QProgressDialog; class QProgressDialog;
class QSortFilterProxyModel; class QSortFilterProxyModel;
@ -38,9 +37,6 @@ class PortsWindow : public QWidget, private Ui::PortsWindow
{ {
Q_OBJECT Q_OBJECT
//QAbstractItemModel *slm; // stream list model
PortGroupList *plm;
public: public:
PortsWindow(PortGroupList *pgl, QWidget *parent = 0); PortsWindow(PortGroupList *pgl, QWidget *parent = 0);
~PortsWindow(); ~PortsWindow();
@ -54,16 +50,14 @@ public:
QString &error, QString &error,
QProgressDialog *progress = NULL); QProgressDialog *progress = NULL);
QList<QAction*> portActions();
QList<QAction*> streamActions();
QList<QAction*> deviceActions();
signals: signals:
void currentPortChanged(const QModelIndex &current, void currentPortChanged(const QModelIndex &current,
const QModelIndex &previous); const QModelIndex &previous);
private:
QString lastNewPortGroup;
QAbstractItemDelegate *delegate;
QSortFilterProxyModel *proxyPortModel;
ApplyMessage *applyMsg_;
public slots: public slots:
void clearCurrentSelection(); void clearCurrentSelection();
void showMyReservedPortsOnly(bool enabled); void showMyReservedPortsOnly(bool enabled);
@ -71,14 +65,7 @@ public slots:
private slots: private slots:
void updateApplyHint(int portGroupId, int portId, bool configChanged); void updateApplyHint(int portGroupId, int portId, bool configChanged);
void updatePortViewActions(const QModelIndex& currentIndex); void updatePortViewActions(const QModelIndex& currentIndex);
void updateStreamViewActions();
void on_startTx_clicked();
void on_stopTx_clicked();
void on_averagePacketsPerSec_editingFinished();
void on_averageBitsPerSec_editingFinished();
void updatePortRates();
void on_tvStreamList_activated(const QModelIndex & index);
void when_portView_currentChanged(const QModelIndex& currentIndex, void when_portView_currentChanged(const QModelIndex& currentIndex,
const QModelIndex& previousIndex); const QModelIndex& previousIndex);
void when_portModel_dataChanged(const QModelIndex& topLeft, void when_portModel_dataChanged(const QModelIndex& topLeft,
@ -95,15 +82,11 @@ private slots:
void on_actionExclusive_Control_triggered(bool checked); void on_actionExclusive_Control_triggered(bool checked);
void on_actionPort_Configuration_triggered(); void on_actionPort_Configuration_triggered();
void on_actionNew_Stream_triggered(); private:
void on_actionEdit_Stream_triggered(); PortGroupList *plm;
void on_actionDuplicate_Stream_triggered(); QString lastNewPortGroup;
void on_actionDelete_Stream_triggered(); QSortFilterProxyModel *proxyPortModel;
ApplyMessage *applyMsg_;
void on_actionOpen_Streams_triggered();
void on_actionSave_Streams_triggered();
void streamModelDataChanged();
}; };
#endif #endif

View File

@ -39,7 +39,7 @@
<widget class="QStackedWidget" name="swDetail"> <widget class="QStackedWidget" name="swDetail">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred"> <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>2</horstretch> <horstretch>1</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
@ -126,7 +126,16 @@
</widget> </widget>
<widget class="QWidget" name="portDetail"> <widget class="QWidget" name="portDetail">
<layout class="QVBoxLayout"> <layout class="QVBoxLayout">
<property name="margin"> <property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number> <number>0</number>
</property> </property>
<item> <item>
@ -138,7 +147,16 @@
<enum>QFrame::Raised</enum> <enum>QFrame::Raised</enum>
</property> </property>
<layout class="QHBoxLayout"> <layout class="QHBoxLayout">
<property name="margin"> <property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number> <number>3</number>
</property> </property>
<item> <item>
@ -195,114 +213,10 @@
</attribute> </attribute>
<layout class="QVBoxLayout"> <layout class="QVBoxLayout">
<item> <item>
<layout class="QHBoxLayout"> <widget class="PortWidget" name="portWidget" native="true"/>
<item>
<widget class="QToolButton" name="startTx">
<property name="toolTip">
<string>Start Transmit</string>
</property>
<property name="statusTip">
<string>Start transmit on selected port</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="ostinato.qrc">
<normaloff>:/icons/control_play.png</normaloff>:/icons/control_play.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="stopTx">
<property name="toolTip">
<string>Stop Transmit</string>
</property>
<property name="statusTip">
<string>Stop transmit on selected port</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="ostinato.qrc">
<normaloff>:/icons/control_stop.png</normaloff>:/icons/control_stop.png</iconset>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QRadioButton" name="radioButton">
<property name="text">
<string>Avg pps</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="averagePacketsPerSec"/>
</item>
<item>
<widget class="QRadioButton" name="radioButton_2">
<property name="text">
<string>Avg bps</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="averageBitsPerSec">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item> </item>
<item> <item>
<widget class="XTableView" name="tvStreamList"> <widget class="StreamsWidget" name="streamsWidget" native="true"/>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="contextMenuPolicy">
<enum>Qt::ActionsContextMenu</enum>
</property>
<property name="whatsThis">
<string>This is the stream list for the selected port
A stream is a sequence of one or more packets
Right-click to create a stream</string>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="lineWidth">
<number>1</number>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
</widget>
</item> </item>
</layout> </layout>
</widget> </widget>
@ -360,33 +274,6 @@ Right-click to create a stream</string>
<string>Disconnect Port Group</string> <string>Disconnect Port Group</string>
</property> </property>
</action> </action>
<action name="actionNew_Stream">
<property name="icon">
<iconset resource="ostinato.qrc">
<normaloff>:/icons/stream_add.png</normaloff>:/icons/stream_add.png</iconset>
</property>
<property name="text">
<string>New Stream</string>
</property>
</action>
<action name="actionDelete_Stream">
<property name="icon">
<iconset resource="ostinato.qrc">
<normaloff>:/icons/stream_delete.png</normaloff>:/icons/stream_delete.png</iconset>
</property>
<property name="text">
<string>Delete Stream</string>
</property>
</action>
<action name="actionEdit_Stream">
<property name="icon">
<iconset resource="ostinato.qrc">
<normaloff>:/icons/stream_edit.png</normaloff>:/icons/stream_edit.png</iconset>
</property>
<property name="text">
<string>Edit Stream</string>
</property>
</action>
<action name="actionExclusive_Control"> <action name="actionExclusive_Control">
<property name="checkable"> <property name="checkable">
<bool>true</bool> <bool>true</bool>
@ -395,30 +282,11 @@ Right-click to create a stream</string>
<string>Exclusive Port Control (EXPERIMENTAL)</string> <string>Exclusive Port Control (EXPERIMENTAL)</string>
</property> </property>
</action> </action>
<action name="actionOpen_Streams">
<property name="text">
<string>Open Streams ...</string>
</property>
</action>
<action name="actionSave_Streams">
<property name="text">
<string>Save Streams ...</string>
</property>
</action>
<action name="actionPort_Configuration"> <action name="actionPort_Configuration">
<property name="text"> <property name="text">
<string>Port Configuration ...</string> <string>Port Configuration ...</string>
</property> </property>
</action> </action>
<action name="actionDuplicate_Stream">
<property name="icon">
<iconset resource="ostinato.qrc">
<normaloff>:/icons/stream_duplicate.png</normaloff>:/icons/stream_duplicate.png</iconset>
</property>
<property name="text">
<string>Duplicate Stream</string>
</property>
</action>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>
@ -433,46 +301,20 @@ Right-click to create a stream</string>
<header>xtreeview.h</header> <header>xtreeview.h</header>
</customwidget> </customwidget>
<customwidget> <customwidget>
<class>XTableView</class> <class>StreamsWidget</class>
<extends>QTableView</extends> <extends>QWidget</extends>
<header>xtableview.h</header> <header>streamswidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>PortWidget</class>
<extends>QWidget</extends>
<header>portwidget.h</header>
<container>1</container>
</customwidget> </customwidget>
</customwidgets> </customwidgets>
<resources> <resources>
<include location="ostinato.qrc"/> <include location="ostinato.qrc"/>
</resources> </resources>
<connections> <connections/>
<connection>
<sender>radioButton</sender>
<signal>toggled(bool)</signal>
<receiver>averagePacketsPerSec</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>326</x>
<y>80</y>
</hint>
<hint type="destinationlabel">
<x>454</x>
<y>79</y>
</hint>
</hints>
</connection>
<connection>
<sender>radioButton_2</sender>
<signal>toggled(bool)</signal>
<receiver>averageBitsPerSec</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>523</x>
<y>80</y>
</hint>
<hint type="destinationlabel">
<x>651</x>
<y>88</y>
</hint>
</hints>
</connection>
</connections>
</ui> </ui>

182
client/portwidget.cpp Normal file
View File

@ -0,0 +1,182 @@
/*
Copyright (C) 2010 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 "portwidget.h"
#include "portgrouplist.h"
#include "xqlocale.h"
#include <cfloat>
PortWidget::PortWidget(QWidget *parent)
: QWidget(parent)
{
setupUi(this);
}
void PortWidget::setPortGroupList(PortGroupList *portGroups)
{
plm = portGroups;
connect(plm->getStreamModel(), SIGNAL(rowsInserted(QModelIndex, int, int)),
SLOT(updatePortActions()));
connect(plm->getStreamModel(), SIGNAL(rowsRemoved(QModelIndex, int, int)),
SLOT(updatePortActions()));
connect(plm->getStreamModel(), SIGNAL(modelReset()),
SLOT(updatePortActions()));
updatePortActions();
}
PortWidget::~PortWidget()
{
}
void PortWidget::setCurrentPortIndex(const QModelIndex &portIndex)
{
if (!plm)
return;
// XXX: We assume portIndex corresponds to sourceModel, not proxyModel
if (!plm->isPort(portIndex))
return;
qDebug("In %s", __FUNCTION__);
// Disconnect previous port
if (plm->isPort(currentPortIndex_))
disconnect(&(plm->port(currentPortIndex_)),
SIGNAL(portRateChanged(int, int)),
this, SLOT(updatePortRates()));
currentPortIndex_ = portIndex;
// Connect current port
if (plm->isPort(currentPortIndex_))
connect(&(plm->port(currentPortIndex_)),
SIGNAL(portRateChanged(int, int)),
this, SLOT(updatePortRates()));
double speed = plm->port(currentPortIndex_).speed();
portSpeed->setText(QString("Max %L1 Mbps").arg(speed));
rbLoad->setVisible(speed > 0);
averageLoadPercent->setVisible(speed > 0);
speedSep->setVisible(speed > 0);
portSpeed->setVisible(speed > 0);
updatePortRates();
updatePortActions();
}
void PortWidget::on_startTx_clicked()
{
Q_ASSERT(plm->isPort(currentPortIndex_));
QModelIndex curPortGroup = plm->getPortModel()->parent(currentPortIndex_);
Q_ASSERT(curPortGroup.isValid());
Q_ASSERT(plm->isPortGroup(curPortGroup));
QList<uint> portList({plm->port(currentPortIndex_).id()});
plm->portGroup(curPortGroup).startTx(&portList);
}
void PortWidget::on_stopTx_clicked()
{
Q_ASSERT(plm->isPort(currentPortIndex_));
QModelIndex curPortGroup = plm->getPortModel()->parent(currentPortIndex_);
Q_ASSERT(curPortGroup.isValid());
Q_ASSERT(plm->isPortGroup(curPortGroup));
QList<uint> portList({plm->port(currentPortIndex_).id()});
plm->portGroup(curPortGroup).stopTx(&portList);
}
void PortWidget::on_averageLoadPercent_editingFinished()
{
Q_ASSERT(plm->isPort(currentPortIndex_));
plm->port(currentPortIndex_).setAverageLoadRate(
averageLoadPercent->value()/100);
}
void PortWidget::on_averagePacketsPerSec_editingFinished()
{
Q_ASSERT(plm->isPort(currentPortIndex_));
bool isOk;
double pps = XLocale().toPacketsPerSecond(averagePacketsPerSec->text(),
&isOk);
if (isOk)
plm->port(currentPortIndex_).setAveragePacketRate(pps);
else
updatePortRates();
}
void PortWidget::on_averageBitsPerSec_editingFinished()
{
Q_ASSERT(plm->isPort(currentPortIndex_));
bool isOk;
double bps = XLocale().toBitsPerSecond(averageBitsPerSec->text(), &isOk);
if (isOk)
plm->port(currentPortIndex_).setAverageBitRate(bps);
else
updatePortRates();
}
void PortWidget::updatePortRates()
{
if (!currentPortIndex_.isValid())
return;
if (!plm->isPort(currentPortIndex_))
return;
// XXX: pps/bps input widget is a LineEdit and not a SpinBox
// because we want users to be able to enter values in various
// units e.g. "1.5 Mbps", "1000K", "50" (bps) etc.
// XXX: It's a considered decision NOT to show frame rate in
// higher units of Kpps and Mpps as most users may not be
// familiar with those and also we want frame rate to have a
// high resolution for input e.g. if user enters 1,488,095.2381
// it should NOT be shown as 1.4881 Mpps
averagePacketsPerSec->setText(QString("%L1 pps")
.arg(plm->port(currentPortIndex_).averagePacketRate(), 0, 'f', 4));
averageBitsPerSec->setText(XLocale().toBitRateString(
plm->port(currentPortIndex_).averageBitRate()));
averageLoadPercent->setValue(
plm->port(currentPortIndex_).averageLoadRate()*100);
}
void PortWidget::updatePortActions()
{
if (!plm->isPort(currentPortIndex_))
return;
startTx->setEnabled(plm->port(currentPortIndex_).numStreams() > 0);
stopTx->setEnabled(plm->port(currentPortIndex_).numStreams() > 0);
}

60
client/portwidget.h Normal file
View File

@ -0,0 +1,60 @@
/*
Copyright (C) 2010 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 _PORT_WIDGET_H
#define _PORT_WIDGET_H
#include "ui_portwidget.h"
#include <QModelIndex>
#include <QWidget>
class PortGroupList;
class PortWidget : public QWidget, private Ui::PortWidget
{
Q_OBJECT
public:
PortWidget(QWidget *parent = 0);
~PortWidget();
void setPortGroupList(PortGroupList *portGroups);
public slots:
void setCurrentPortIndex(const QModelIndex &portIndex);
private slots:
void on_startTx_clicked();
void on_stopTx_clicked();
void on_averageLoadPercent_editingFinished();
void on_averagePacketsPerSec_editingFinished();
void on_averageBitsPerSec_editingFinished();
void updatePortActions();
void updatePortRates();
private:
PortGroupList *plm{nullptr}; // FIXME: rename to portGroups_?
QModelIndex currentPortIndex_;
};
#endif

218
client/portwidget.ui Normal file
View File

@ -0,0 +1,218 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PortWidget</class>
<widget class="QWidget" name="PortWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>806</width>
<height>73</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QToolButton" name="startTx">
<property name="toolTip">
<string>Start Transmit</string>
</property>
<property name="statusTip">
<string>Start transmit on selected port</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="ostinato.qrc">
<normaloff>:/icons/control_play.png</normaloff>:/icons/control_play.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="stopTx">
<property name="toolTip">
<string>Stop Transmit</string>
</property>
<property name="statusTip">
<string>Stop transmit on selected port</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="ostinato.qrc">
<normaloff>:/icons/control_stop.png</normaloff>:/icons/control_stop.png</iconset>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="Line" name="rateSep">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioButton">
<property name="text">
<string>Frame Rate</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="averagePacketsPerSec"/>
</item>
<item>
<widget class="QRadioButton" name="radioButton_2">
<property name="text">
<string>Bit Rate</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="averageBitsPerSec">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Bit rate on the line including overhead such as Preamble, IPG, FCS etc.</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="rbLoad">
<property name="text">
<string>Load</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="averageLoadPercent">
<property name="enabled">
<bool>false</bool>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="suffix">
<string>%</string>
</property>
<property name="decimals">
<number>4</number>
</property>
<property name="maximum">
<double>999.999900000000025</double>
</property>
</widget>
</item>
<item>
<widget class="Line" name="speedSep">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="portSpeed">
<property name="toolTip">
<string>Port Speed</string>
</property>
<property name="statusTip">
<string/>
</property>
<property name="text">
<string>Max speed</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources>
<include location="ostinato.qrc"/>
</resources>
<connections>
<connection>
<sender>radioButton</sender>
<signal>toggled(bool)</signal>
<receiver>averagePacketsPerSec</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>450</x>
<y>44</y>
</hint>
<hint type="destinationlabel">
<x>593</x>
<y>45</y>
</hint>
</hints>
</connection>
<connection>
<sender>radioButton_2</sender>
<signal>toggled(bool)</signal>
<receiver>averageBitsPerSec</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>661</x>
<y>44</y>
</hint>
<hint type="destinationlabel">
<x>804</x>
<y>45</y>
</hint>
</hints>
</connection>
<connection>
<sender>rbLoad</sender>
<signal>toggled(bool)</signal>
<receiver>averageLoadPercent</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>281</x>
<y>43</y>
</hint>
<hint type="destinationlabel">
<x>308</x>
<y>45</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -21,6 +21,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "../common/ostprotolib.h" #include "../common/ostprotolib.h"
#include "settings.h" #include "settings.h"
#include "thememanager.h"
#include <QFileDialog> #include <QFileDialog>
#include <QtGlobal> #include <QtGlobal>
@ -40,6 +41,7 @@ Preferences::Preferences()
setupUi(this); setupUi(this);
// Program paths
wiresharkPathEdit->setText(appSettings->value(kWiresharkPathKey, wiresharkPathEdit->setText(appSettings->value(kWiresharkPathKey,
kWiresharkPathDefaultValue).toString()); kWiresharkPathDefaultValue).toString());
tsharkPathEdit->setText(appSettings->value(kTsharkPathKey, tsharkPathEdit->setText(appSettings->value(kTsharkPathKey,
@ -51,6 +53,10 @@ Preferences::Preferences()
awkPathEdit->setText(appSettings->value(kAwkPathKey, awkPathEdit->setText(appSettings->value(kAwkPathKey,
kAwkPathDefaultValue).toString()); kAwkPathDefaultValue).toString());
// Theme
theme->addItems(ThemeManager::instance()->themeList());
theme->setCurrentText(appSettings->value(kThemeKey).toString());
// TODO(only if required): kUserKey // TODO(only if required): kUserKey
} }
@ -78,6 +84,7 @@ void Preferences::initDefaults()
void Preferences::accept() void Preferences::accept()
{ {
// Program paths
appSettings->setValue(kWiresharkPathKey, wiresharkPathEdit->text()); appSettings->setValue(kWiresharkPathKey, wiresharkPathEdit->text());
appSettings->setValue(kTsharkPathKey, tsharkPathEdit->text()); appSettings->setValue(kTsharkPathKey, tsharkPathEdit->text());
appSettings->setValue(kGzipPathKey, gzipPathEdit->text()); appSettings->setValue(kGzipPathKey, gzipPathEdit->text());
@ -90,6 +97,9 @@ void Preferences::accept()
appSettings->value(kDiffPathKey, kDiffPathDefaultValue).toString(), appSettings->value(kDiffPathKey, kDiffPathDefaultValue).toString(),
appSettings->value(kAwkPathKey, kAwkPathDefaultValue).toString()); appSettings->value(kAwkPathKey, kAwkPathDefaultValue).toString());
// Theme
ThemeManager::instance()->setTheme(theme->currentText());
QDialog::accept(); QDialog::accept();
} }

View File

@ -1,7 +1,8 @@
<ui version="4.0" > <?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Preferences</class> <class>Preferences</class>
<widget class="QDialog" name="Preferences" > <widget class="QDialog" name="Preferences">
<property name="geometry" > <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
@ -9,148 +10,149 @@
<height>220</height> <height>220</height>
</rect> </rect>
</property> </property>
<property name="windowTitle" > <property name="windowTitle">
<string>Preferences</string> <string>Preferences</string>
</property> </property>
<property name="windowIcon" > <property name="windowIcon">
<iconset resource="ostinato.qrc" >:/icons/preferences.png</iconset> <iconset resource="ostinato.qrc">
<normaloff>:/icons/preferences.png</normaloff>:/icons/preferences.png</iconset>
</property> </property>
<layout class="QVBoxLayout" > <layout class="QVBoxLayout">
<item> <item>
<widget class="QFrame" name="frame" > <widget class="QFrame" name="frame">
<property name="frameShape" > <property name="frameShape">
<enum>QFrame::Box</enum> <enum>QFrame::Box</enum>
</property> </property>
<property name="frameShadow" > <property name="frameShadow">
<enum>QFrame::Sunken</enum> <enum>QFrame::Sunken</enum>
</property> </property>
<layout class="QGridLayout" > <layout class="QGridLayout">
<item row="0" column="0" > <item row="0" column="0">
<widget class="QLabel" name="label" > <widget class="QLabel" name="label">
<property name="text" > <property name="text">
<string>'wireshark' Path</string> <string>'wireshark' Path</string>
</property> </property>
<property name="buddy" > <property name="buddy">
<cstring>wiresharkPathEdit</cstring> <cstring>wiresharkPathEdit</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1" > <item row="0" column="1">
<widget class="QLineEdit" name="wiresharkPathEdit" > <widget class="QLineEdit" name="wiresharkPathEdit">
<property name="enabled" > <property name="enabled">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="2" > <item row="0" column="2">
<widget class="QToolButton" name="wiresharkPathButton" > <widget class="QToolButton" name="wiresharkPathButton">
<property name="text" > <property name="text">
<string>...</string> <string>...</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0" > <item row="1" column="0">
<widget class="QLabel" name="label_2" > <widget class="QLabel" name="label_2">
<property name="text" > <property name="text">
<string>'tshark' Path</string> <string>'tshark' Path</string>
</property> </property>
<property name="buddy" > <property name="buddy">
<cstring>tsharkPathEdit</cstring> <cstring>tsharkPathEdit</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1" > <item row="1" column="1">
<widget class="QLineEdit" name="tsharkPathEdit" > <widget class="QLineEdit" name="tsharkPathEdit">
<property name="enabled" > <property name="enabled">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="2" > <item row="1" column="2">
<widget class="QToolButton" name="tsharkPathButton" > <widget class="QToolButton" name="tsharkPathButton">
<property name="text" > <property name="text">
<string>...</string> <string>...</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0" > <item row="2" column="0">
<widget class="QLabel" name="label_5" > <widget class="QLabel" name="label_5">
<property name="text" > <property name="text">
<string>'gzip' Path</string> <string>'gzip' Path</string>
</property> </property>
<property name="buddy" > <property name="buddy">
<cstring>diffPathEdit</cstring> <cstring>diffPathEdit</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1" > <item row="2" column="1">
<widget class="QLineEdit" name="gzipPathEdit" > <widget class="QLineEdit" name="gzipPathEdit">
<property name="enabled" > <property name="enabled">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="2" > <item row="2" column="2">
<widget class="QToolButton" name="gzipPathButton" > <widget class="QToolButton" name="gzipPathButton">
<property name="text" > <property name="text">
<string>...</string> <string>...</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0" > <item row="3" column="0">
<widget class="QLabel" name="label_3" > <widget class="QLabel" name="label_3">
<property name="text" > <property name="text">
<string>'diff' Path</string> <string>'diff' Path</string>
</property> </property>
<property name="buddy" > <property name="buddy">
<cstring>diffPathEdit</cstring> <cstring>diffPathEdit</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1" > <item row="3" column="1">
<widget class="QLineEdit" name="diffPathEdit" > <widget class="QLineEdit" name="diffPathEdit">
<property name="enabled" > <property name="enabled">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="2" > <item row="3" column="2">
<widget class="QToolButton" name="diffPathButton" > <widget class="QToolButton" name="diffPathButton">
<property name="text" > <property name="text">
<string>...</string> <string>...</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="0" > <item row="4" column="0">
<widget class="QLabel" name="label_4" > <widget class="QLabel" name="label_4">
<property name="text" > <property name="text">
<string>'awk' Path</string> <string>'awk' Path</string>
</property> </property>
<property name="buddy" > <property name="buddy">
<cstring>awkPathEdit</cstring> <cstring>awkPathEdit</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="1" > <item row="4" column="1">
<widget class="QLineEdit" name="awkPathEdit" > <widget class="QLineEdit" name="awkPathEdit">
<property name="enabled" > <property name="enabled">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="2" > <item row="4" column="2">
<widget class="QToolButton" name="awkPathButton" > <widget class="QToolButton" name="awkPathButton">
<property name="text" > <property name="text">
<string>...</string> <string>...</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="1" > <item row="5" column="1">
<spacer> <spacer>
<property name="orientation" > <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Vertical</enum>
</property> </property>
<property name="sizeHint" > <property name="sizeHint" stdset="0">
<size> <size>
<width>21</width> <width>21</width>
<height>61</height> <height>61</height>
@ -162,12 +164,26 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QDialogButtonBox" name="buttonBox" > <layout class="QHBoxLayout" name="horizontalLayout">
<property name="orientation" > <item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>Theme (Experimental)</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="theme"/>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
<property name="standardButtons" > <property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok</set> <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property> </property>
</widget> </widget>
</item> </item>
@ -187,7 +203,7 @@
<tabstop>buttonBox</tabstop> <tabstop>buttonBox</tabstop>
</tabstops> </tabstops>
<resources> <resources>
<include location="ostinato.qrc" /> <include location="ostinato.qrc"/>
</resources> </resources>
<connections> <connections>
<connection> <connection>
@ -196,11 +212,11 @@
<receiver>Preferences</receiver> <receiver>Preferences</receiver>
<slot>accept()</slot> <slot>accept()</slot>
<hints> <hints>
<hint type="sourcelabel" > <hint type="sourcelabel">
<x>248</x> <x>248</x>
<y>254</y> <y>254</y>
</hint> </hint>
<hint type="destinationlabel" > <hint type="destinationlabel">
<x>157</x> <x>157</x>
<y>274</y> <y>274</y>
</hint> </hint>
@ -212,11 +228,11 @@
<receiver>Preferences</receiver> <receiver>Preferences</receiver>
<slot>reject()</slot> <slot>reject()</slot>
<hints> <hints>
<hint type="sourcelabel" > <hint type="sourcelabel">
<x>316</x> <x>316</x>
<y>260</y> <y>260</y>
</hint> </hint>
<hint type="destinationlabel" > <hint type="destinationlabel">
<x>286</x> <x>286</x>
<y>274</y> <y>274</y>
</hint> </hint>

View File

@ -74,6 +74,8 @@ const QString kAwkPathDefaultValue("/usr/bin/awk");
const QString kAwkPathDefaultValue("/usr/bin/awk"); const QString kAwkPathDefaultValue("/usr/bin/awk");
#endif #endif
const QString kThemeKey("Theme");
const QString kUserKey("User"); const QString kUserKey("User");
extern QString kUserDefaultValue; extern QString kUserDefaultValue;

View File

@ -432,6 +432,12 @@ void StreamConfigDialog::on_cmbPktLenMode_currentIndexChanged(QString mode)
lePktLenMin->setEnabled(true); lePktLenMin->setEnabled(true);
lePktLenMax->setEnabled(true); lePktLenMax->setEnabled(true);
} }
else if (mode == "IMIX")
{
lePktLen->setDisabled(true);
lePktLenMin->setDisabled(true);
lePktLenMax->setDisabled(true);
}
else else
{ {
qWarning("Unhandled/Unknown PktLenMode = %s", qPrintable(mode)); qWarning("Unhandled/Unknown PktLenMode = %s", qPrintable(mode));
@ -1165,12 +1171,7 @@ void StreamConfigDialog::on_lePacketsPerSec_textChanged(const QString &text)
{ {
bool isOk; bool isOk;
Stream *pStream = mpStream; Stream *pStream = mpStream;
uint frameLen; uint frameLen = pStream->frameLenAvg();
if (pStream->lenMode() == Stream::e_fl_fixed)
frameLen = pStream->frameLen();
else
frameLen = (pStream->frameLenMin() + pStream->frameLenMax())/2;
if (rbSendPackets->isChecked()) if (rbSendPackets->isChecked())
{ {
@ -1189,13 +1190,9 @@ void StreamConfigDialog::on_leBurstsPerSec_textChanged(const QString &text)
bool isOk; bool isOk;
Stream *pStream = mpStream; Stream *pStream = mpStream;
uint burstSize = lePacketsPerBurst->text().toULong(&isOk); uint burstSize = lePacketsPerBurst->text().toULong(&isOk);
uint frameLen; uint frameLen = pStream->frameLenAvg();
qDebug("start of %s(%s)", __FUNCTION__, qPrintable(text)); qDebug("start of %s(%s)", __FUNCTION__, qPrintable(text));
if (pStream->lenMode() == Stream::e_fl_fixed)
frameLen = pStream->frameLen();
else
frameLen = (pStream->frameLenMin() + pStream->frameLenMax())/2;
if (rbSendBursts->isChecked()) if (rbSendBursts->isChecked())
{ {
@ -1215,12 +1212,7 @@ void StreamConfigDialog::on_leBitsPerSec_textEdited(const QString &text)
bool isOk; bool isOk;
Stream *pStream = mpStream; Stream *pStream = mpStream;
uint burstSize = lePacketsPerBurst->text().toULong(&isOk); uint burstSize = lePacketsPerBurst->text().toULong(&isOk);
uint frameLen; uint frameLen = pStream->frameLenAvg();
if (pStream->lenMode() == Stream::e_fl_fixed)
frameLen = pStream->frameLen();
else
frameLen = (pStream->frameLenMin() + pStream->frameLenMax())/2;
if (rbSendPackets->isChecked()) if (rbSendPackets->isChecked())
{ {

View File

@ -108,6 +108,11 @@ QLineEdit:enabled[inputMask = &quot;HH HH HH HH HH HH; &quot;] { background-colo
<string>Random</string> <string>Random</string>
</property> </property>
</item> </item>
<item>
<property name="text">
<string>IMIX</string>
</property>
</item>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="0" column="1">
@ -670,6 +675,19 @@ QLineEdit:enabled[inputMask = &quot;HH HH HH HH HH HH; &quot;] { background-colo
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="3" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="page_2"> <widget class="QWidget" name="page_2">

View File

@ -21,7 +21,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "protocol.pb.h" #include "protocol.pb.h"
#include <QApplication>
#include <QBrush> #include <QBrush>
#include <QFont>
#include <QPalette>
// XXX: Keep the enum in sync with it's string // XXX: Keep the enum in sync with it's string
enum { enum {
@ -103,20 +106,35 @@ QVariant StreamStatsModel::data(const QModelIndex &index, int role) const
return Qt::AlignRight; return Qt::AlignRight;
int portColumn = index.column() - kMaxAggrStreamStats; int portColumn = index.column() - kMaxAggrStreamStats;
if (role == Qt::BackgroundRole) {
if (portColumn < 0) // Stylesheets typically don't use or set palette colors, so if
return QBrush(QColor("lavender")); // Aggregate Column // using one, don't use palette colors
if (index.row() == (guidList_.size() - 1)) if ((role == Qt::BackgroundRole) && qApp->styleSheet().isEmpty()) {
return QBrush(QColor("burlywood")); // Aggregate Row QPalette palette = QApplication::palette();
else if ((portColumn/kMaxStreamStats) & 1) if (index.row() == (guidList_.size() - 1)) // Aggregate Row
return QBrush(QColor("beige")); // Color alternate Ports return palette.dark();
if (portColumn < 0) // Aggregate Column
return palette.alternateBase();
if ((portColumn/kMaxStreamStats) & 1) // Color alternate Ports
return palette.alternateBase();
} }
Guid guid = guidList_.at(index.row()); Guid guid = guidList_.at(index.row());
if (role == Qt::ForegroundRole) { if ((role == Qt::ForegroundRole && qApp->styleSheet().isEmpty())) {
QPalette palette = QApplication::palette();
if ((index.column() == kAggrPktLoss) if ((index.column() == kAggrPktLoss)
&& aggrGuidStats_.value(guid).pktLoss) && aggrGuidStats_.value(guid).pktLoss)
return QBrush(QColor("firebrick")); return palette.link();
if (index.row() == (guidList_.size() - 1)) // Aggregate Row
return palette.brightText();
}
if (role == Qt::FontRole ) {
if (index.row() == (guidList_.size() - 1)) { // Aggregate Row
QFont font;
font.setBold(true);
return font;
}
} }
if (role != Qt::DisplayRole) if (role != Qt::DisplayRole)

510
client/streamswidget.cpp Normal file
View File

@ -0,0 +1,510 @@
/*
Copyright (C) 2010 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 "streamswidget.h"
#include "clipboardhelper.h"
#include "findreplace.h"
#include "portgrouplist.h"
#include "streamconfigdialog.h"
#include "streamfileformat.h"
#include "streamlistdelegate.h"
#include <QInputDialog>
#include <QItemSelectionModel>
#include <QMessageBox>
extern ClipboardHelper *clipboardHelper;
StreamsWidget::StreamsWidget(QWidget *parent)
: QWidget(parent)
{
setupUi(this);
delegate = new StreamListDelegate;
tvStreamList->setItemDelegate(delegate);
tvStreamList->verticalHeader()->setDefaultSectionSize(
tvStreamList->verticalHeader()->minimumSectionSize());
// Populate StreamList Context Menu Actions
tvStreamList->addAction(actionNew_Stream);
tvStreamList->addAction(actionEdit_Stream);
tvStreamList->addAction(actionDuplicate_Stream);
tvStreamList->addAction(actionDelete_Stream);
QAction *sep2 = new QAction(this);
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);
// StreamWidget's actions is an aggegate of all sub-widget's actions
addActions(tvStreamList->actions());
// 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 *sep4 = new QAction("Clipboard", this);
sep4->setSeparator(true);
tvStreamList->insertAction(sep2, sep4);
tvStreamList->insertActions(sep2, clipboardHelper->actions());
}
void StreamsWidget::setPortGroupList(PortGroupList *portGroups)
{
plm = portGroups;
tvStreamList->setModel(plm->getStreamModel());
connect(plm->getStreamModel(), SIGNAL(rowsInserted(QModelIndex, int, int)),
SLOT(updateStreamViewActions()));
connect(plm->getStreamModel(), SIGNAL(rowsRemoved(QModelIndex, int, int)),
SLOT(updateStreamViewActions()));
connect(tvStreamList->selectionModel(),
SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)),
SLOT(updateStreamViewActions()));
connect(tvStreamList->selectionModel(),
SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
SLOT(updateStreamViewActions()));
tvStreamList->resizeColumnToContents(StreamModel::StreamIcon);
tvStreamList->resizeColumnToContents(StreamModel::StreamStatus);
updateStreamViewActions();
connect(plm->getStreamModel(),
SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
this, SLOT(streamModelDataChanged()));
connect(plm->getStreamModel(),
SIGNAL(modelReset()),
this, SLOT(streamModelDataChanged()));
}
void StreamsWidget::streamModelDataChanged()
{
if (plm->isPort(currentPortIndex_))
plm->port(currentPortIndex_).recalculateAverageRates();
}
StreamsWidget::~StreamsWidget()
{
delete delegate;
}
void StreamsWidget::on_tvStreamList_activated(const QModelIndex & index)
{
if (!index.isValid())
{
qDebug("%s: invalid index", __FUNCTION__);
return;
}
qDebug("stream list activated\n");
Port &curPort = plm->port(currentPortIndex_);
QList<Stream*> streams;
streams.append(curPort.mutableStreamByIndex(index.row(), false));
StreamConfigDialog scd(streams, curPort, this);
if (scd.exec() == QDialog::Accepted) {
curPort.recalculateAverageRates();
curPort.setLocalConfigChanged(true);
}
}
void StreamsWidget::setCurrentPortIndex(const QModelIndex &portIndex)
{
if (!plm)
return;
// XXX: We assume portIndex corresponds to sourceModel, not proxyModel
if (!plm->isPort(portIndex))
return;
qDebug("In %s", __FUNCTION__);
currentPortIndex_ = portIndex;
plm->getStreamModel()->setCurrentPortIndex(portIndex);
updateStreamViewActions();
}
void StreamsWidget::updateStreamViewActions()
{
// For some reason hasSelection() returns true even if selection size is 0
// so additional check for size introduced
if (tvStreamList->selectionModel()->hasSelection() &&
(tvStreamList->selectionModel()->selection().size() > 0))
{
qDebug("Has selection %d",
tvStreamList->selectionModel()->selection().size());
// If more than one non-contiguous ranges selected,
// disable "New" and "Edit"
if (tvStreamList->selectionModel()->selection().size() > 1)
{
actionNew_Stream->setDisabled(true);
actionEdit_Stream->setDisabled(true);
}
else
{
actionNew_Stream->setEnabled(true);
actionEdit_Stream->setEnabled(true);
}
// Duplicate/Delete are always enabled as long as we have a selection
actionDuplicate_Stream->setEnabled(true);
actionDelete_Stream->setEnabled(true);
}
else
{
qDebug("No selection");
if (plm->isPort(currentPortIndex_))
actionNew_Stream->setEnabled(true);
else
actionNew_Stream->setDisabled(true);
actionEdit_Stream->setDisabled(true);
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);
}
void StreamsWidget::on_actionNew_Stream_triggered()
{
qDebug("New Stream Action");
QItemSelectionModel* selectionModel = tvStreamList->selectionModel();
if (selectionModel->selection().size() > 1) {
qDebug("%s: Unexpected selection size %d, can't add", __FUNCTION__,
selectionModel->selection().size());
return;
}
// In case nothing is selected, insert 1 row at the end
StreamModel *streamModel = plm->getStreamModel();
int row = streamModel->rowCount(), count = 1;
// In case we have a single range selected; insert as many rows as
// in the singe selected range before the top of the selected range
if (selectionModel->selection().size() == 1)
{
row = selectionModel->selection().at(0).top();
count = selectionModel->selection().at(0).height();
}
Port &curPort = plm->port(currentPortIndex_);
QList<Stream*> streams;
for (int i = 0; i < count; i++)
streams.append(new Stream);
StreamConfigDialog scd(streams, curPort, this);
scd.setWindowTitle(tr("Add Stream"));
if (scd.exec() == QDialog::Accepted)
streamModel->insert(row, streams);
}
void StreamsWidget::on_actionEdit_Stream_triggered()
{
qDebug("Edit Stream Action");
QItemSelectionModel* streamModel = tvStreamList->selectionModel();
if (!streamModel->hasSelection())
return;
Port &curPort = plm->port(currentPortIndex_);
QList<Stream*> streams;
foreach(QModelIndex index, streamModel->selectedRows())
streams.append(curPort.mutableStreamByIndex(index.row(), false));
StreamConfigDialog scd(streams, curPort, this);
if (scd.exec() == QDialog::Accepted) {
curPort.recalculateAverageRates();
curPort.setLocalConfigChanged(true);
}
}
void StreamsWidget::on_actionDuplicate_Stream_triggered()
{
QItemSelectionModel* model = tvStreamList->selectionModel();
qDebug("Duplicate Stream Action");
if (model->hasSelection())
{
bool isOk;
int count = QInputDialog::getInt(this, "Duplicate Streams",
"Count", 1, 1, 9999, 1, &isOk);
if (!isOk)
return;
QList<int> list;
foreach(QModelIndex index, model->selectedRows())
list.append(index.row());
plm->port(currentPortIndex_).duplicateStreams(list, count);
}
else
qDebug("No selection");
}
void StreamsWidget::on_actionDelete_Stream_triggered()
{
qDebug("Delete Stream Action");
QModelIndex index;
if (tvStreamList->selectionModel()->hasSelection())
{
qDebug("SelectedIndexes %d",
tvStreamList->selectionModel()->selectedRows().size());
while(tvStreamList->selectionModel()->selectedRows().size())
{
index = tvStreamList->selectionModel()->selectedRows().at(0);
plm->getStreamModel()->removeRows(index.row(), 1);
}
}
else
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");
QStringList fileTypes = StreamFileFormat::supportedFileTypes(
StreamFileFormat::kOpenFile);
QString fileType;
static QString dirName;
QString fileName;
QString errorStr;
bool append = true;
bool ret;
Q_ASSERT(plm->isPort(currentPortIndex_));
if (fileTypes.size())
fileType = fileTypes.at(0);
fileName = QFileDialog::getOpenFileName(this, tr("Open Streams"),
dirName, fileTypes.join(";;"), &fileType);
if (fileName.isEmpty())
goto _exit;
if (tvStreamList->model()->rowCount())
{
QMessageBox msgBox(QMessageBox::Question, qApp->applicationName(),
tr("Append to existing streams? Or overwrite?"),
QMessageBox::NoButton, this);
QPushButton *appendBtn = msgBox.addButton(tr("Append"),
QMessageBox::ActionRole);
QPushButton *overwriteBtn = msgBox.addButton(tr("Overwrite"),
QMessageBox::ActionRole);
QPushButton *cancelBtn = msgBox.addButton(QMessageBox::Cancel);
msgBox.exec();
if (msgBox.clickedButton() == cancelBtn)
goto _exit;
else if (msgBox.clickedButton() == appendBtn)
append = true;
else if (msgBox.clickedButton() == overwriteBtn)
append = false;
else
Q_ASSERT(false);
}
ret = plm->port(currentPortIndex_).openStreams(fileName, append, errorStr);
if (!ret || !errorStr.isEmpty())
{
QMessageBox msgBox(this);
QStringList str = errorStr.split("\n\n\n\n");
msgBox.setIcon(ret ? QMessageBox::Warning : QMessageBox::Critical);
msgBox.setWindowTitle(qApp->applicationName());
msgBox.setText(str.at(0));
if (str.size() > 1)
msgBox.setDetailedText(str.at(1));
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.exec();
}
dirName = QFileInfo(fileName).absolutePath();
updateStreamViewActions();
_exit:
return;
}
void StreamsWidget::on_actionSave_Streams_triggered()
{
qDebug("Save Streams Action");
static QString fileName;
QStringList fileTypes = StreamFileFormat::supportedFileTypes(
StreamFileFormat::kSaveFile);
QString fileType;
QString errorStr;
QFileDialog::Options options;
// On Mac OS with Native Dialog, getSaveFileName() ignores fileType
// which we need
#if defined(Q_OS_MAC)
options |= QFileDialog::DontUseNativeDialog;
#endif
if (fileTypes.size())
fileType = fileTypes.at(0);
Q_ASSERT(plm->isPort(currentPortIndex_));
_retry:
fileName = QFileDialog::getSaveFileName(this, tr("Save Streams"),
fileName, fileTypes.join(";;"), &fileType, options);
if (fileName.isEmpty())
goto _exit;
if (QFileInfo(fileName).suffix().isEmpty()) {
QString fileExt = fileType.section(QRegExp("[\\*\\)]"), 1, 1);
qDebug("Adding extension '%s' to '%s'",
qPrintable(fileExt), qPrintable(fileName));
fileName.append(fileExt);
if (QFileInfo(fileName).exists()) {
if (QMessageBox::warning(this, tr("Overwrite File?"),
QString("The file \"%1\" already exists.\n\n"
"Do you wish to overwrite it?")
.arg(QFileInfo(fileName).fileName()),
QMessageBox::Yes|QMessageBox::No,
QMessageBox::No) != QMessageBox::Yes)
goto _retry;
}
}
fileType = fileType.remove(QRegExp("\\(.*\\)")).trimmed();
if (!fileType.startsWith("Ostinato")
&& !fileType.startsWith("Python"))
{
if (QMessageBox::warning(this, tr("Ostinato"),
QString("You have chosen to save in %1 format. All stream "
"attributes may not be saved in this format.\n\n"
"It is recommended to save in native Ostinato format.\n\n"
"Continue to save in %2 format?").arg(fileType).arg(fileType),
QMessageBox::Yes|QMessageBox::No,
QMessageBox::No) != QMessageBox::Yes)
goto _retry;
}
// TODO: all or selected?
if (!plm->port(currentPortIndex_).saveStreams(fileName, fileType, errorStr))
QMessageBox::critical(this, qApp->applicationName(), errorStr);
else if (!errorStr.isEmpty())
QMessageBox::warning(this, qApp->applicationName(), errorStr);
fileName = QFileInfo(fileName).absolutePath();
_exit:
return;
}

67
client/streamswidget.h Normal file
View File

@ -0,0 +1,67 @@
/*
Copyright (C) 2010 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 _STREAMS_WIDGET_H
#define _STREAMS_WIDGET_H
#include "ui_streamswidget.h"
#include <QWidget>
class PortGroupList;
class QAbstractItemDelegate;
class StreamsWidget : public QWidget, private Ui::StreamsWidget
{
Q_OBJECT
public:
StreamsWidget(QWidget *parent = 0);
~StreamsWidget();
void setPortGroupList(PortGroupList *portGroups);
public slots:
void setCurrentPortIndex(const QModelIndex &portIndex);
private slots:
void updateStreamViewActions();
void on_tvStreamList_activated(const QModelIndex & index);
void on_actionNew_Stream_triggered();
void on_actionEdit_Stream_triggered();
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();
void streamModelDataChanged();
private:
PortGroupList *plm{nullptr}; // FIXME: rename to portGroups_?
QModelIndex currentPortIndex_;
QAbstractItemDelegate *delegate;
};
#endif

132
client/streamswidget.ui Normal file
View File

@ -0,0 +1,132 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>StreamsWidget</class>
<widget class="QWidget" name="StreamsWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>602</width>
<height>364</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>9</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="XTableView" name="tvStreamList">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="contextMenuPolicy">
<enum>Qt::ActionsContextMenu</enum>
</property>
<property name="whatsThis">
<string>This is the stream list for the selected port
A stream is a sequence of one or more packets
Right-click to create a stream</string>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="lineWidth">
<number>1</number>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
</widget>
</item>
</layout>
<action name="actionNew_Stream">
<property name="icon">
<iconset resource="ostinato.qrc">
<normaloff>:/icons/stream_add.png</normaloff>:/icons/stream_add.png</iconset>
</property>
<property name="text">
<string>New Stream</string>
</property>
</action>
<action name="actionDelete_Stream">
<property name="icon">
<iconset resource="ostinato.qrc">
<normaloff>:/icons/stream_delete.png</normaloff>:/icons/stream_delete.png</iconset>
</property>
<property name="text">
<string>Delete Stream</string>
</property>
</action>
<action name="actionEdit_Stream">
<property name="icon">
<iconset resource="ostinato.qrc">
<normaloff>:/icons/stream_edit.png</normaloff>:/icons/stream_edit.png</iconset>
</property>
<property name="text">
<string>Edit Stream</string>
</property>
</action>
<action name="actionOpen_Streams">
<property name="text">
<string>Open Streams ...</string>
</property>
</action>
<action name="actionSave_Streams">
<property name="text">
<string>Save Streams ...</string>
</property>
</action>
<action name="actionDuplicate_Stream">
<property name="icon">
<iconset resource="ostinato.qrc">
<normaloff>:/icons/stream_duplicate.png</normaloff>:/icons/stream_duplicate.png</iconset>
</property>
<property name="text">
<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>
<class>XTableView</class>
<extends>QTableView</extends>
<header>xtableview.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="ostinato.qrc"/>
</resources>
<connections/>
</ui>

130
client/thememanager.cpp Normal file
View File

@ -0,0 +1,130 @@
/*
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 "thememanager.h"
#include "settings.h"
#include <QApplication>
#include <QDebug>
#include <QDir>
#include <QDirIterator>
#include <QRegularExpression>
#include <QResource>
ThemeManager *ThemeManager::instance_{nullptr};
ThemeManager::ThemeManager()
{
themeDir_ = QCoreApplication::applicationDirPath() + "/themes/";
#if defined(Q_OS_MAC)
/*
* Executable and Theme directory location inside app bundle -
* Ostinato.app/Contents/MacOS/
* Ostinato.app/Contents/SharedSupport/themes/
*/
themeDir_.replace("/MacOS/", "/SharedSupport/");
#elif defined(Q_OS_UNIX)
/*
* Possible (but not comprehensive) locations for Ostinato executable
* and theme directory locations
*
* non-install-dir/
* non-install-dir/themes/
*
* /usr/[local]/bin/
* /usr/[local]/share/ostinato/themes/
*
* /opt/ostinato/bin/
* /opt/ostinato/share/themes/
*/
if (themeDir_.contains(QRegularExpression("^/usr/.*/bin/")))
themeDir_.replace("/bin/", "/share/ostinato/");
else if (themeDir_.contains(QRegularExpression("^/opt/.*/bin/")))
themeDir_.replace("/bin/", "/share/");
#endif
qDebug("Themes directory: %s", qPrintable(themeDir_));
}
QStringList ThemeManager::themeList()
{
QDir themeDir(themeDir_);
themeDir.setFilter(QDir::Files);
themeDir.setNameFilters(QStringList() << "*.qss");
themeDir.setSorting(QDir::Name);
QStringList themes = themeDir.entryList();
for (QString& theme : themes)
theme.remove(".qss");
themes.prepend("default");
return themes;
}
void ThemeManager::setTheme(QString theme)
{
// Remove current theme, if we have one
QString oldTheme = appSettings->value(kThemeKey).toString();
if (!oldTheme.isEmpty()) {
// Remove stylesheet first so that there are
// no references to resources when unregistering 'em
qApp->setStyleSheet("");
QString rccFile = themeDir_ + oldTheme + ".rcc";
if (QResource::unregisterResource(rccFile)) {
qDebug("Unable to unregister theme rccFile %s",
qPrintable(rccFile));
}
appSettings->setValue(kThemeKey, QVariant());
}
if (theme.isEmpty() || (theme == "default"))
return;
// Apply new theme
QFile qssFile(themeDir_ + theme + ".qss");
if (!qssFile.open(QFile::ReadOnly)) {
qDebug("Unable to open theme qssFile %s",
qPrintable(qssFile.fileName()));
return;
}
// Register theme resource before applying theme style sheet
QString rccFile = themeDir_ + theme + ".rcc";
if (!QResource::registerResource(rccFile))
qDebug("Unable to register theme rccFile %s", qPrintable(rccFile));
#if 0 // FIXME: debug only
QDirIterator it(":", QDirIterator::Subdirectories);
while (it.hasNext()) {
qDebug() << it.next();
}
#endif
QString styleSheet { qssFile.readAll() };
qApp->setStyleSheet(styleSheet);
appSettings->setValue(kThemeKey, theme);
}
ThemeManager* ThemeManager::instance()
{
if (!instance_)
instance_ = new ThemeManager();
return instance_;
}

42
client/thememanager.h Normal file
View File

@ -0,0 +1,42 @@
/*
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 _THEME_MANAGER_H
#define _THEME_MANAGER_H
#include <QStringList>
class ThemeManager
{
public:
ThemeManager();
QStringList themeList();
void setTheme(QString theme);
static ThemeManager* instance();
private:
QString themeDir_;
static ThemeManager *instance_;
};
#endif

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

2213
client/themes/qds-dark.qss Normal file

File diff suppressed because it is too large Load Diff

BIN
client/themes/qds-dark.rcc Normal file

Binary file not shown.

2213
client/themes/qds-light.qss Normal file

File diff suppressed because it is too large Load Diff

BIN
client/themes/qds-light.rcc Normal file

Binary file not shown.

View File

@ -21,14 +21,85 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#define _X_LOCALE_H #define _X_LOCALE_H
#include <QLocale> #include <QLocale>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
class XLocale: public QLocale class XLocale: public QLocale
{ {
public: public:
double toDouble(const QString &s, bool *ok = Q_NULLPTR) const { double toDouble(const QString &s, bool *ok = Q_NULLPTR) const
{
QString s2 = s; QString s2 = s;
return QLocale::toDouble(s2.remove(groupSeparator()), ok); return QLocale::toDouble(s2.remove(groupSeparator()), ok);
} }
double toPacketsPerSecond(const QString &s, bool *ok = Q_NULLPTR) const
{
QString text = s;
double multiplier = 0;
QRegularExpression regex("[a-zA-Z/]+$");
QRegularExpressionMatch match = regex.match(text);
if (match.hasMatch()) {
QString unit = match.captured(0).toCaseFolded();
if ((unit == "mpps") || (unit == "m"))
multiplier = 1e6;
else if ((unit == "kpps") || (unit == "k"))
multiplier = 1e3;
else if (unit == "pps")
multiplier = 1;
if (multiplier)
text.remove(regex);
}
if (multiplier == 0)
multiplier = 1;
return toDouble(text, ok) * multiplier;
}
double toBitsPerSecond(const QString &s, bool *ok = Q_NULLPTR) const
{
QString text = s;
double multiplier = 0;
QRegularExpression regex("[a-zA-Z/]+$");
QRegularExpressionMatch match = regex.match(text);
if (match.hasMatch()) {
QString unit = match.captured(0).toCaseFolded();
if ((unit == "gbps") || (unit == "gb/s") || (unit == "g"))
multiplier = 1e9;
else if ((unit == "mbps") || (unit == "mb/s") || (unit == "m"))
multiplier = 1e6;
else if ((unit == "kbps") || (unit == "kb/s") || (unit == "k"))
multiplier = 1e3;
else if ((unit == "bps") || (unit == "b/s"))
multiplier = 1;
if (multiplier)
text.remove(regex);
}
if (multiplier == 0)
multiplier = 1;
return toDouble(text, ok) * multiplier;
}
QString toBitRateString(double bps) const
{
QString text;
if (bps >= 1e9)
return QObject::tr("%L1 Gbps").arg(bps/1e9, 0, 'f', 4);
if (bps >= 1e6)
return QObject::tr("%L1 Mbps").arg(bps/1e6, 0, 'f', 4);
if (bps >= 1e3)
return QObject::tr("%L1 Kbps").arg(bps/1e3, 0, 'f', 4);
return QObject::tr("%L1 bps").arg(bps, 0, 'f', 4);
}
}; };
#endif #endif

View File

@ -26,6 +26,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include <qendian.h> #include <qendian.h>
#if 0
#ifdef qDebug
#undef qDebug
#define qDebug(...)
#endif
#endif
/*! /*!
\class AbstractProtocol \class AbstractProtocol
@ -850,7 +857,7 @@ bool AbstractProtocol::protocolHasPayload() const
to prevent infinite recursion to prevent infinite recursion
*/ */
quint32 AbstractProtocol::protocolFrameCksum(int streamIndex, quint32 AbstractProtocol::protocolFrameCksum(int streamIndex,
CksumType cksumType) const CksumType cksumType, CksumFlags cksumFlags) const
{ {
static int recursionCount = 0; static int recursionCount = 0;
quint32 cksum = 0xFFFFFFFF; quint32 cksum = 0xFFFFFFFF;
@ -865,8 +872,10 @@ quint32 AbstractProtocol::protocolFrameCksum(int streamIndex,
QByteArray fv; QByteArray fv;
quint16 *ip; quint16 *ip;
quint32 len, sum = 0; quint32 len, sum = 0;
bool forCksum = cksumFlags.testFlag(IncludeCksumField) ?
false : true;
fv = protocolFrameValue(streamIndex, true); fv = protocolFrameValue(streamIndex, forCksum);
ip = (quint16*) fv.constData(); ip = (quint16*) fv.constData();
len = fv.size(); len = fv.size();
@ -907,7 +916,26 @@ quint32 AbstractProtocol::protocolFrameCksum(int streamIndex,
cksum = (~sum) & 0xFFFF; cksum = (~sum) & 0xFFFF;
break; break;
} }
case CksumIcmpIgmp:
{
quint16 cks;
quint32 sum = 0;
cks = protocolFrameCksum(streamIndex, CksumIp);
sum += (quint16) ~cks;
cks = protocolFramePayloadCksum(streamIndex, CksumIp);
sum += (quint16) ~cks;
while(sum>>16)
sum = (sum & 0xFFFF) + (sum >> 16);
cksum = (~sum) & 0xFFFF;
break;
}
default: default:
qDebug("Unknown cksumType %d", cksumType);
break; break;
} }
@ -994,7 +1022,7 @@ quint32 AbstractProtocol::protocolFramePayloadCksum(int streamIndex,
if (!p) if (!p)
return 0xFFFF; return 0xFFFF;
cksum = p->protocolFrameCksum(streamIndex, cksumType); cksum = p->protocolFrameCksum(streamIndex, cksumType, IncludeCksumField);
sum = (quint16) ~cksum; sum = (quint16) ~cksum;
if (cksumScope == CksumScopeAdjacentProtocol) if (cksumScope == CksumScopeAdjacentProtocol)
goto out; goto out;
@ -1002,9 +1030,9 @@ quint32 AbstractProtocol::protocolFramePayloadCksum(int streamIndex,
p = p->next; p = p->next;
while (p) while (p)
{ {
cksum = p->protocolFrameCksum(streamIndex, cksumType, IncludeCksumField);
// when combining cksums, a non-first protocol starting at odd offset // when combining cksums, a non-first protocol starting at odd offset
// needs a byte swap (see RFC 1071 section(s) 2A, 2B) // needs a byte swap (see RFC 1071 section(s) 2A, 2B)
cksum = p->protocolFrameCksum(streamIndex, cksumType);
if (p->protocolFrameOffset(streamIndex) & 0x1) if (p->protocolFrameOffset(streamIndex) & 0x1)
cksum = swap16(cksum); cksum = swap16(cksum);
sum += (quint16) ~cksum; sum += (quint16) ~cksum;

View File

@ -102,10 +102,17 @@ public:
CksumIp, //!< Standard IP Checksum CksumIp, //!< Standard IP Checksum
CksumIpPseudo, //!< Standard checksum for Pseudo-IP header CksumIpPseudo, //!< Standard checksum for Pseudo-IP header
CksumTcpUdp, //!< Standard TCP/UDP checksum including pseudo-IP CksumTcpUdp, //!< Standard TCP/UDP checksum including pseudo-IP
CksumIcmpIgmp, //!< Standard ICMP/IGMP checksum
CksumMax //!< Marker for number of cksum types CksumMax //!< Marker for number of cksum types
}; };
//! Flags affecting cksum calculation, can be OR'd
enum CksumFlag {
IncludeCksumField = 0x1, //!< Default: exclude cksum field(s)
};
Q_DECLARE_FLAGS(CksumFlags, CksumFlag); //!< \private abcd
//! Supported checksum scopes //! Supported checksum scopes
enum CksumScope { enum CksumScope {
CksumScopeAdjacentProtocol, //!< Cksum only the adjacent protocol CksumScopeAdjacentProtocol, //!< Cksum only the adjacent protocol
@ -164,7 +171,7 @@ public:
bool protocolHasPayload() const; bool protocolHasPayload() const;
virtual quint32 protocolFrameCksum(int streamIndex = 0, virtual quint32 protocolFrameCksum(int streamIndex = 0,
CksumType cksumType = CksumIp) const; CksumType cksumType = CksumIp, CksumFlags cksumFlags = 0) const;
quint32 protocolFrameHeaderCksum(int streamIndex = 0, quint32 protocolFrameHeaderCksum(int streamIndex = 0,
CksumType cksumType = CksumIp, CksumType cksumType = CksumIp,
CksumScope cksumScope = CksumScopeAdjacentProtocol) const; CksumScope cksumScope = CksumScopeAdjacentProtocol) const;

View File

@ -430,7 +430,7 @@ QVariant ArpProtocol::fieldData(int index, FieldAttrib attrib,
switch(data.target_proto_addr_mode()) switch(data.target_proto_addr_mode())
{ {
case OstProto::Arp::kFixed: case OstProto::Arp::kFixedHost:
protoAddr = data.target_proto_addr(); protoAddr = data.target_proto_addr();
break; break;
case OstProto::Arp::kIncrementHost: case OstProto::Arp::kIncrementHost:
@ -786,7 +786,7 @@ int ArpProtocol::protocolFrameVariableCount() const
} }
if (fieldData(arp_senderProtoAddrMode, FieldValue).toUInt() if (fieldData(arp_senderProtoAddrMode, FieldValue).toUInt()
!= uint(OstProto::Arp::kFixed)) != uint(OstProto::Arp::kFixedHost))
{ {
count = AbstractProtocol::lcm(count, count = AbstractProtocol::lcm(count,
fieldData(arp_senderProtoAddrCount, FieldValue).toUInt()); fieldData(arp_senderProtoAddrCount, FieldValue).toUInt());
@ -800,7 +800,7 @@ int ArpProtocol::protocolFrameVariableCount() const
} }
if (fieldData(arp_targetProtoAddrMode, FieldValue).toUInt() if (fieldData(arp_targetProtoAddrMode, FieldValue).toUInt()
!= uint(OstProto::Arp::kFixed)) != uint(OstProto::Arp::kFixedHost))
{ {
count = AbstractProtocol::lcm(count, count = AbstractProtocol::lcm(count,
fieldData(arp_targetProtoAddrCount, FieldValue).toUInt()); fieldData(arp_targetProtoAddrCount, FieldValue).toUInt());

View File

@ -168,14 +168,16 @@ public:
} }
virtual quint32 protocolFrameCksum(int streamIndex = 0, virtual quint32 protocolFrameCksum(int streamIndex = 0,
CksumType cksumType = CksumIp) const CksumType cksumType = CksumIp, CksumFlags cksumFlags = 0) const
{ {
// For a Pseudo IP cksum, we assume it is the succeeding protocol // For a Pseudo IP cksum, we assume it is the succeeding protocol
// that is requesting it and hence return protoB's cksum; // that is requesting it and hence return protoB's cksum;
if (cksumType == CksumIpPseudo) if (cksumType == CksumIpPseudo)
return protoB->protocolFrameCksum(streamIndex, cksumType); return protoB->protocolFrameCksum(
streamIndex,cksumType, cksumFlags);
return AbstractProtocol::protocolFrameCksum(streamIndex, cksumType); return AbstractProtocol::protocolFrameCksum(
streamIndex, cksumType, cksumFlags);
} }
#if 0 #if 0
quint32 protocolFrameHeaderCksum(int streamIndex = 0, quint32 protocolFrameHeaderCksum(int streamIndex = 0,

View File

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

View File

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

View File

@ -197,24 +197,11 @@ QVariant IcmpProtocol::fieldData(int index, FieldAttrib attrib,
} }
else else
{ {
quint16 cks; cksum = (icmpVersion() == OstProto::Icmp::kIcmp4) ?
quint32 sum = 0; AbstractProtocol::protocolFrameCksum(
streamIndex, CksumIcmpIgmp) :
cks = protocolFrameCksum(streamIndex, CksumIp); AbstractProtocol::protocolFrameCksum(
sum += (quint16) ~cks; streamIndex, CksumTcpUdp);
cks = protocolFramePayloadCksum(streamIndex, CksumIp);
sum += (quint16) ~cks;
if (icmpVersion() == OstProto::Icmp::kIcmp6)
{
cks = protocolFrameHeaderCksum(streamIndex,
CksumIpPseudo);
sum += (quint16) ~cks;
}
while(sum>>16)
sum = (sum & 0xFFFF) + (sum >> 16);
cksum = (~sum) & 0xFFFF;
} }
break; break;
default: default:

View File

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

View File

@ -134,6 +134,7 @@ QVariant IgmpProtocol::fieldData(int index, FieldAttrib attrib,
case FieldName: case FieldName:
return QString("Group Address"); return QString("Group Address");
case FieldValue: case FieldValue:
return grpIp;
case FieldTextValue: case FieldTextValue:
return QHostAddress(grpIp).toString(); return QHostAddress(grpIp).toString();
case FieldFrameValue: case FieldFrameValue:
@ -289,8 +290,14 @@ bool IgmpProtocol::setFieldData(int index, const QVariant &value,
} }
case kGroupAddress: case kGroupAddress:
{ {
quint32 ip = value.toUInt(&isOk);
if (isOk) {
data.mutable_group_address()->set_v4(ip);
break;
}
QHostAddress addr(value.toString()); QHostAddress addr(value.toString());
quint32 ip = addr.toIPv4Address(); ip = addr.toIPv4Address();
isOk = (addr.protocol() == QAbstractSocket::IPv4Protocol); isOk = (addr.protocol() == QAbstractSocket::IPv4Protocol);
if (isOk) if (isOk)
data.mutable_group_address()->set_v4(ip); data.mutable_group_address()->set_v4(ip);
@ -306,6 +313,7 @@ bool IgmpProtocol::setFieldData(int index, const QVariant &value,
quint32 ip = QHostAddress(str).toIPv4Address(); quint32 ip = QHostAddress(str).toIPv4Address();
data.add_sources()->set_v4(ip); data.add_sources()->set_v4(ip);
} }
isOk = true;
break; break;
} }
@ -332,6 +340,7 @@ bool IgmpProtocol::setFieldData(int index, const QVariant &value,
QHostAddress(src).toIPv4Address()); QHostAddress(src).toIPv4Address());
} }
} }
isOk = true;
break; break;
} }
@ -347,18 +356,5 @@ _exit:
quint16 IgmpProtocol::checksum(int streamIndex) const quint16 IgmpProtocol::checksum(int streamIndex) const
{ {
quint16 cks; return AbstractProtocol::protocolFrameCksum(streamIndex, CksumIcmpIgmp);
quint32 sum = 0;
// TODO: add as a new CksumType (CksumIgmp?) and implement in AbsProto
cks = protocolFrameCksum(streamIndex, CksumIp);
sum += (quint16) ~cks;
cks = protocolFramePayloadCksum(streamIndex, CksumIp);
sum += (quint16) ~cks;
while (sum >> 16)
sum = (sum & 0xFFFF) + (sum >> 16);
cks = (~sum) & 0xFFFF;
return cks;
} }

View File

@ -49,7 +49,7 @@ void PdmlIgmpProtocol::preProtocolHandler(QString /*name*/,
OstProto::Gmp *igmp = pbProto->MutableExtension(OstProto::igmp); OstProto::Gmp *igmp = pbProto->MutableExtension(OstProto::igmp);
igmp->set_is_override_rsvd_code(true); 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_source_count(true);
igmp->set_is_override_group_record_count(true); igmp->set_is_override_group_record_count(true);

View File

@ -845,7 +845,7 @@ int Ip4Protocol::protocolFrameVariableCount() const
} }
quint32 Ip4Protocol::protocolFrameCksum(int streamIndex, quint32 Ip4Protocol::protocolFrameCksum(int streamIndex,
CksumType cksumType) const CksumType cksumType, CksumFlags cksumFlags) const
{ {
switch (cksumType) switch (cksumType)
{ {
@ -876,7 +876,8 @@ quint32 Ip4Protocol::protocolFrameCksum(int streamIndex,
break; break;
} }
return AbstractProtocol::protocolFrameCksum(streamIndex, cksumType); return AbstractProtocol::protocolFrameCksum(
streamIndex, cksumType, cksumFlags);
} }
bool Ip4Protocol::hasErrors(QStringList *errors) const bool Ip4Protocol::hasErrors(QStringList *errors) const

View File

@ -89,7 +89,7 @@ public:
virtual int protocolFrameVariableCount() const; virtual int protocolFrameVariableCount() const;
virtual quint32 protocolFrameCksum(int streamIndex = 0, virtual quint32 protocolFrameCksum(int streamIndex = 0,
CksumType cksumType = CksumIp) const; CksumType cksumType = CksumIp, CksumFlags cksumFlags = 0) const;
virtual bool hasErrors(QStringList *errors = nullptr) const; virtual bool hasErrors(QStringList *errors = nullptr) const;
private: private:

View File

@ -88,6 +88,6 @@ void PdmlIp4Protocol::postProtocolHandler(OstProto::Protocol *pbProto,
ip4->set_is_override_hdrlen(true); ip4->set_is_override_hdrlen(true);
ip4->set_is_override_totlen(true); ip4->set_is_override_totlen(true);
ip4->set_is_override_proto(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: case FieldName:
return QString("Source"); return QString("Source");
case FieldValue: case FieldValue:
{
QVariant v;
v.setValue(src);
return v;
}
case FieldFrameValue: case FieldFrameValue:
case FieldTextValue: case FieldTextValue:
{ {
@ -412,6 +417,11 @@ QVariant Ip6Protocol::fieldData(int index, FieldAttrib attrib,
case FieldName: case FieldName:
return QString("Destination"); return QString("Destination");
case FieldValue: case FieldValue:
{
QVariant v;
v.setValue(dst);
return v;
}
case FieldFrameValue: case FieldFrameValue:
case FieldTextValue: case FieldTextValue:
{ {
@ -594,6 +604,14 @@ bool Ip6Protocol::setFieldData(int index, const QVariant &value,
} }
case ip6_srcAddress: 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(); Q_IPV6ADDR addr = QHostAddress(value.toString()).toIPv6Address();
quint64 x; quint64 x;
@ -616,10 +634,19 @@ bool Ip6Protocol::setFieldData(int index, const QVariant &value,
| (quint64(addr[14]) << 8) | (quint64(addr[14]) << 8)
| (quint64(addr[15]) << 0); | (quint64(addr[15]) << 0);
data.set_src_addr_lo(x); data.set_src_addr_lo(x);
isOk = true;
break; break;
} }
case ip6_dstAddress: 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(); Q_IPV6ADDR addr = QHostAddress(value.toString()).toIPv6Address();
quint64 x; quint64 x;
@ -642,6 +669,7 @@ bool Ip6Protocol::setFieldData(int index, const QVariant &value,
| (quint64(addr[14]) << 8) | (quint64(addr[14]) << 8)
| (quint64(addr[15]) << 0); | (quint64(addr[15]) << 0);
data.set_dst_addr_lo(x); data.set_dst_addr_lo(x);
isOk = true;
break; break;
} }
@ -740,7 +768,7 @@ int Ip6Protocol::protocolFrameVariableCount() const
} }
quint32 Ip6Protocol::protocolFrameCksum(int streamIndex, quint32 Ip6Protocol::protocolFrameCksum(int streamIndex,
CksumType cksumType) const CksumType cksumType, CksumFlags cksumFlags) const
{ {
if (cksumType == CksumIpPseudo) if (cksumType == CksumIpPseudo)
{ {
@ -764,7 +792,8 @@ quint32 Ip6Protocol::protocolFrameCksum(int streamIndex,
return qFromBigEndian((quint16) ~sum); return qFromBigEndian((quint16) ~sum);
} }
return AbstractProtocol::protocolFrameCksum(streamIndex, cksumType); return AbstractProtocol::protocolFrameCksum(
streamIndex, cksumType, cksumFlags);
} }
bool Ip6Protocol::hasErrors(QStringList *errors) const bool Ip6Protocol::hasErrors(QStringList *errors) const

View File

@ -103,7 +103,7 @@ public:
virtual int protocolFrameVariableCount() const; virtual int protocolFrameVariableCount() const;
virtual quint32 protocolFrameCksum(int streamIndex = 0, virtual quint32 protocolFrameCksum(int streamIndex = 0,
CksumType cksumType = CksumIp) const; CksumType cksumType = CksumIp, CksumFlags cksumFlags = 0) const;
virtual bool hasErrors(QStringList *errors = nullptr) const; virtual bool hasErrors(QStringList *errors = nullptr) const;
private: private:

View File

@ -20,6 +20,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#ifndef _IP_UTILS_H #ifndef _IP_UTILS_H
#define _IP_UTILS_H #define _IP_UTILS_H
#include "uint128.h"
#include <QHostAddress>
namespace ipUtils { namespace ipUtils {
enum AddrMode { enum AddrMode {
kFixed = 0, 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 } // namespace ipUtils
#endif #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: case mac_dstAddr:
{ {
quint64 mac = value.toULongLong(); quint64 mac = value.toULongLong(&isOk);
data.set_dst_mac(mac); if (isOk)
data.set_dst_mac(mac);
break; break;
} }
case mac_srcAddr: case mac_srcAddr:
{ {
quint64 mac = value.toULongLong(); quint64 mac = value.toULongLong(&isOk);
data.set_src_mac(mac); if (isOk)
data.set_src_mac(mac);
break; 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

@ -21,6 +21,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#define _MAC_EDIT_H #define _MAC_EDIT_H
#include <QLineEdit> #include <QLineEdit>
#include <QRegExpValidator>
class MacEdit: public QLineEdit class MacEdit: public QLineEdit
{ {

View File

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

View File

@ -53,7 +53,7 @@ void PdmlMldProtocol::preProtocolHandler(QString /*name*/,
OstProto::Gmp *mld = pbProto->MutableExtension(OstProto::mld); OstProto::Gmp *mld = pbProto->MutableExtension(OstProto::mld);
mld->set_is_override_rsvd_code(true); 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_source_count(true);
mld->set_is_override_group_record_count(true); mld->set_is_override_group_record_count(true);

View File

@ -33,6 +33,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
const quint32 kPcapFileMagic = 0xa1b2c3d4; const quint32 kPcapFileMagic = 0xa1b2c3d4;
const quint32 kPcapFileMagicSwapped = 0xd4c3b2a1; const quint32 kPcapFileMagicSwapped = 0xd4c3b2a1;
const quint32 kNanoSecondPcapFileMagic = 0xa1b23c4d;
const quint32 kNanoSecondPcapFileMagicSwapped = 0x4d3cb2a1;
const quint16 kPcapFileVersionMajor = 2; const quint16 kPcapFileVersionMajor = 2;
const quint16 kPcapFileVersionMinor = 4; const quint16 kPcapFileVersionMinor = 4;
const quint32 kMaxSnapLen = 65535; const quint32 kMaxSnapLen = 65535;
@ -44,9 +46,16 @@ PcapImportOptionsDialog::PcapImportOptionsDialog(QVariantMap *options)
: QDialog(NULL) : QDialog(NULL)
{ {
setupUi(this); setupUi(this);
setAttribute(Qt::WA_DeleteOnClose);
options_ = options; options_ = options;
viaPdml->setChecked(options_->value("ViaPdml").toBool()); viaPdml->setChecked(options_->value("ViaPdml").toBool());
// XXX: By default this key is absent - so that pcap import tests
// evaluate to false and hence show minimal diffs.
// However, for the GUI user, this should be enabled by default.
recalculateCksums->setChecked(
options_->value("RecalculateCksums", QVariant(true))
.toBool());
doDiff->setChecked(options_->value("DoDiff").toBool()); doDiff->setChecked(options_->value("DoDiff").toBool());
connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
@ -59,6 +68,7 @@ PcapImportOptionsDialog::~PcapImportOptionsDialog()
void PcapImportOptionsDialog::accept() void PcapImportOptionsDialog::accept()
{ {
options_->insert("ViaPdml", viaPdml->isChecked()); options_->insert("ViaPdml", viaPdml->isChecked());
options_->insert("RecalculateCksums", recalculateCksums->isChecked());
options_->insert("DoDiff", doDiff->isChecked()); options_->insert("DoDiff", doDiff->isChecked());
QDialog::accept(); QDialog::accept();
@ -68,13 +78,10 @@ PcapFileFormat::PcapFileFormat()
{ {
importOptions_.insert("ViaPdml", true); importOptions_.insert("ViaPdml", true);
importOptions_.insert("DoDiff", true); importOptions_.insert("DoDiff", true);
importDialog_ = NULL;
} }
PcapFileFormat::~PcapFileFormat() PcapFileFormat::~PcapFileFormat()
{ {
delete importDialog_;
} }
bool PcapFileFormat::open(const QString fileName, bool PcapFileFormat::open(const QString fileName,
@ -85,11 +92,12 @@ bool PcapFileFormat::open(const QString fileName,
QTemporaryFile file2; QTemporaryFile file2;
quint32 magic; quint32 magic;
uchar gzipMagic[2]; uchar gzipMagic[2];
bool nsecResolution = false;
int len; int len;
PcapFileHeader fileHdr; PcapFileHeader fileHdr;
PcapPacketHeader pktHdr; PcapPacketHeader pktHdr;
OstProto::Stream *prevStream = NULL; OstProto::Stream *prevStream = NULL;
uint lastUsec = 0; quint64 lastXsec = 0;
int pktCount; int pktCount;
qint64 byteCount = 0; qint64 byteCount = 0;
qint64 byteTotal; qint64 byteTotal;
@ -159,15 +167,22 @@ _retry:
{ {
// Do nothing // Do nothing
} }
else if (magic == kPcapFileMagicSwapped) else if (magic == kNanoSecondPcapFileMagic)
{
nsecResolution = true;
}
else if ((magic == kPcapFileMagicSwapped)
|| (magic == kNanoSecondPcapFileMagicSwapped))
{ {
// Toggle Byte order // Toggle Byte order
if (fd_.byteOrder() == QDataStream::BigEndian) if (fd_.byteOrder() == QDataStream::BigEndian)
fd_.setByteOrder(QDataStream::LittleEndian); fd_.setByteOrder(QDataStream::LittleEndian);
else else
fd_.setByteOrder(QDataStream::BigEndian); fd_.setByteOrder(QDataStream::BigEndian);
nsecResolution = (magic == kNanoSecondPcapFileMagicSwapped);
} }
else // Not a pcap file else // Not a pcap file (could be pcapng or something else)
{ {
if (tryConvert) if (tryConvert)
{ {
@ -219,12 +234,15 @@ _retry:
pktBuf.resize(fileHdr.snapLen); pktBuf.resize(fileHdr.snapLen);
// XXX: PDML also needs the PCAP file to cross check packet bytes
// with the PDML data, so we can't do PDML conversion any earlier
// than this
qDebug("pdml check"); qDebug("pdml check");
if (importOptions_.value("ViaPdml").toBool()) if (importOptions_.value("ViaPdml").toBool())
{ {
QProcess tshark; QProcess tshark;
QTemporaryFile pdmlFile; QTemporaryFile pdmlFile;
PdmlReader reader(&streams); PdmlReader reader(&streams, importOptions_);
if (!pdmlFile.open()) if (!pdmlFile.open())
{ {
@ -258,6 +276,8 @@ _retry:
emit status("Reading PDML packets..."); emit status("Reading PDML packets...");
emit target(100); // in percentage emit target(100); // in percentage
// pdml reader needs pcap, so pass self
isOk = reader.read(&pdmlFile, this, &stop_); isOk = reader.read(&pdmlFile, this, &stop_);
if (stop_) if (stop_)
@ -453,6 +473,7 @@ _retry:
} }
_non_pdml: _non_pdml:
qDebug("pcap resolution: %s", nsecResolution ? "nsec" : "usec");
emit status("Reading Packets..."); emit status("Reading Packets...");
emit target(100); // in percentage emit target(100); // in percentage
pktCount = 1; pktCount = 1;
@ -480,20 +501,22 @@ _non_pdml:
stream->mutable_control()->set_num_packets(1); stream->mutable_control()->set_num_packets(1);
// setup packet rate to the timing in pcap (as close as possible) // setup packet rate to the timing in pcap (as close as possible)
const double kUsecsInSec = 1e6; // use quint64 rather than double to store micro/nano second as
uint usec = (pktHdr.tsSec*kUsecsInSec + pktHdr.tsUsec); // it has a larger range (~580 years) and therefore better accuracy
uint delta = usec - lastUsec; const quint64 kXsecsInSec = nsecResolution ? 1e9 : 1e6;
quint64 xsec = (pktHdr.tsSec*kXsecsInSec + pktHdr.tsUsec);
quint64 delta = xsec - lastXsec;
qDebug("pktCount = %d, delta = %llu", pktCount, delta);
if ((pktCount != 1) && delta) if ((pktCount != 1) && delta)
stream->mutable_control()->set_packets_per_sec(kUsecsInSec/delta); stream->mutable_control()->set_packets_per_sec(double(kXsecsInSec)/delta);
if (prevStream) if (prevStream)
prevStream->mutable_control()->CopyFrom(stream->control()); prevStream->mutable_control()->CopyFrom(stream->control());
lastUsec = usec; lastXsec = xsec;
prevStream = stream; prevStream = stream;
pktCount++; pktCount++;
qDebug("pktCount = %d", pktCount);
byteCount += pktHdr.inclLen + sizeof(pktHdr); byteCount += pktHdr.inclLen + sizeof(pktHdr);
emit progress(int(byteCount*100/byteTotal)); // in percentage emit progress(int(byteCount*100/byteTotal)); // in percentage
if (stop_) if (stop_)
@ -569,7 +592,7 @@ bool PcapFileFormat::convertToStandardPcap(
tshark.start(OstProtoLib::tsharkPath(), tshark.start(OstProtoLib::tsharkPath(),
QStringList() QStringList()
<< QString("-r%1").arg(fileName) << QString("-r%1").arg(fileName)
<< "-Fpcap" << "-Fnsecpcap"
<< QString("-w%1").arg(outputFileName)); << QString("-w%1").arg(outputFileName));
if (!tshark.waitForStarted(-1)) if (!tshark.waitForStarted(-1))
{ {
@ -632,7 +655,7 @@ bool PcapFileFormat::save(const OstProto::StreamConfigList streams,
fd_.setDevice(&file); fd_.setDevice(&file);
fileHdr.magicNumber = kPcapFileMagic; fileHdr.magicNumber = kNanoSecondPcapFileMagic;
fileHdr.versionMajor = kPcapFileVersionMajor; fileHdr.versionMajor = kPcapFileVersionMajor;
fileHdr.versionMinor = kPcapFileVersionMinor; fileHdr.versionMinor = kPcapFileVersionMinor;
fileHdr.thisZone = 0; fileHdr.thisZone = 0;
@ -680,11 +703,16 @@ bool PcapFileFormat::save(const OstProto::StreamConfigList streams,
fd_.writeRawData(pktBuf.data(), pktHdr.inclLen); fd_.writeRawData(pktBuf.data(), pktHdr.inclLen);
if (s.packetRate()) if (s.packetRate())
pktHdr.tsUsec += quint32(1e6/s.packetRate()); {
if (pktHdr.tsUsec >= 1000000) quint64 delta = quint64(1e9/s.packetRate());
pktHdr.tsSec += delta/quint32(1e9);
pktHdr.tsUsec += delta % quint32(1e9);
}
if (pktHdr.tsUsec >= quint32(1e9))
{ {
pktHdr.tsSec++; pktHdr.tsSec++;
pktHdr.tsUsec -= 1000000; pktHdr.tsUsec -= quint32(1e9);
} }
emit progress(i); emit progress(i);
@ -705,10 +733,7 @@ _exit:
QDialog* PcapFileFormat::openOptionsDialog() QDialog* PcapFileFormat::openOptionsDialog()
{ {
if (!importDialog_) return new PcapImportOptionsDialog(&importOptions_);
importDialog_ = new PcapImportOptionsDialog(&importOptions_);
return importDialog_;
} }
bool PcapFileFormat::isMyFileFormat(const QString /*fileName*/) bool PcapFileFormat::isMyFileFormat(const QString /*fileName*/)

View File

@ -81,7 +81,6 @@ private:
QDataStream fd_; QDataStream fd_;
QVariantMap importOptions_; QVariantMap importOptions_;
PcapImportOptionsDialog *importDialog_;
}; };
extern PcapFileFormat pcapFileFormat; 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> <class>PcapFileImport</class>
<widget class="QDialog" name="PcapFileImport" > <widget class="QDialog" name="PcapFileImport">
<property name="geometry" > <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>326</width> <width>326</width>
<height>93</height> <height>132</height>
</rect> </rect>
</property> </property>
<property name="windowTitle" > <property name="windowTitle">
<string>PCAP import options</string> <string>PCAP import options</string>
</property> </property>
<layout class="QVBoxLayout" > <layout class="QGridLayout" name="gridLayout">
<item> <item row="0" column="0" colspan="2">
<widget class="QCheckBox" name="viaPdml" > <widget class="QCheckBox" name="viaPdml">
<property name="text" > <property name="text">
<string>Intelligent Import (via PDML)</string> <string>Intelligent Import (via PDML)</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item row="1" column="0" rowspan="2">
<layout class="QHBoxLayout" > <widget class="QWidget" name="indentSpacing" native="true">
<item> <property name="sizePolicy">
<widget class="QWidget" native="1" name="widget" > <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<property name="sizePolicy" > <horstretch>0</horstretch>
<sizepolicy vsizetype="Fixed" hsizetype="Fixed" > <verstretch>0</verstretch>
<horstretch>0</horstretch> </sizepolicy>
<verstretch>0</verstretch> </property>
</sizepolicy> <property name="minimumSize">
</property> <size>
<property name="minimumSize" > <width>16</width>
<size> <height>16</height>
<width>16</width> </size>
<height>16</height> </property>
</size> </widget>
</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> </item>
<item> <item row="1" column="1">
<widget class="QDialogButtonBox" name="buttonBox" > <widget class="QCheckBox" name="recalculateCksums">
<property name="orientation" > <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> <enum>Qt::Horizontal</enum>
</property> </property>
<property name="standardButtons" > <property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok</set> <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property> </property>
</widget> </widget>
</item> </item>
@ -70,11 +77,11 @@
<receiver>PcapFileImport</receiver> <receiver>PcapFileImport</receiver>
<slot>accept()</slot> <slot>accept()</slot>
<hints> <hints>
<hint type="sourcelabel" > <hint type="sourcelabel">
<x>249</x> <x>249</x>
<y>81</y> <y>81</y>
</hint> </hint>
<hint type="destinationlabel" > <hint type="destinationlabel">
<x>157</x> <x>157</x>
<y>90</y> <y>90</y>
</hint> </hint>
@ -86,11 +93,11 @@
<receiver>PcapFileImport</receiver> <receiver>PcapFileImport</receiver>
<slot>reject()</slot> <slot>reject()</slot>
<hints> <hints>
<hint type="sourcelabel" > <hint type="sourcelabel">
<x>249</x> <x>249</x>
<y>81</y> <y>81</y>
</hint> </hint>
<hint type="destinationlabel" > <hint type="destinationlabel">
<x>258</x> <x>258</x>
<y>90</y> <y>90</y>
</hint> </hint>
@ -102,13 +109,13 @@
<receiver>doDiff</receiver> <receiver>doDiff</receiver>
<slot>setEnabled(bool)</slot> <slot>setEnabled(bool)</slot>
<hints> <hints>
<hint type="sourcelabel" > <hint type="sourcelabel">
<x>15</x> <x>15</x>
<y>16</y> <y>16</y>
</hint> </hint>
<hint type="destinationlabel" > <hint type="destinationlabel">
<x>37</x> <x>68</x>
<y>42</y> <y>71</y>
</hint> </hint>
</hints> </hints>
</connection> </connection>
@ -118,13 +125,45 @@
<receiver>doDiff</receiver> <receiver>doDiff</receiver>
<slot>setChecked(bool)</slot> <slot>setChecked(bool)</slot>
<hints> <hints>
<hint type="sourcelabel" > <hint type="sourcelabel">
<x>151</x> <x>151</x>
<y>14</y> <y>14</y>
</hint> </hint>
<hint type="destinationlabel" > <hint type="destinationlabel">
<x>150</x> <x>181</x>
<y>34</y> <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> </hint>
</hints> </hints>
</connection> </connection>

View File

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

View File

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

View File

@ -43,12 +43,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "udppdml.h" #include "udppdml.h"
#include "vlanpdml.h" #include "vlanpdml.h"
PdmlReader::PdmlReader(OstProto::StreamConfigList *streams) PdmlReader::PdmlReader(OstProto::StreamConfigList *streams,
const QVariantMap &options)
{ {
//gPdmlReader = this; //gPdmlReader = this;
pcap_ = NULL; pcap_ = NULL;
streams_ = streams; streams_ = streams;
recalculateCksums_ = options.value("RecalculateCksums").toBool();
currentStream_ = NULL; currentStream_ = NULL;
prevStream_ = NULL; prevStream_ = NULL;
@ -355,6 +358,8 @@ void PdmlReader::readProto()
pdmlProto = appendPdmlProto(protoName, &pbProto); pdmlProto = appendPdmlProto(protoName, &pbProto);
pdmlProto->setRecalculateCksum(recalculateCksums_);
qDebug("%s: preProtocolHandler(expPos = %d)", qDebug("%s: preProtocolHandler(expPos = %d)",
qPrintable(protoName), expPos_); qPrintable(protoName), expPos_);
pdmlProto->preProtocolHandler(protoName, attributes(), expPos_, pbProto, 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 <QByteArray>
#include <QXmlStreamReader> #include <QXmlStreamReader>
#include <QVariantMap>
class PcapFileFormat; class PcapFileFormat;
class PdmlReader : public QObject, public QXmlStreamReader class PdmlReader : public QObject, public QXmlStreamReader
{ {
Q_OBJECT Q_OBJECT
public: public:
PdmlReader(OstProto::StreamConfigList *streams); PdmlReader(OstProto::StreamConfigList *streams,
const QVariantMap &options = QVariantMap());
~PdmlReader(); ~PdmlReader();
bool read(QIODevice *device, PcapFileFormat *pcap = NULL, bool read(QIODevice *device, PcapFileFormat *pcap = NULL,
@ -64,6 +66,8 @@ private:
PcapFileFormat *pcap_; PcapFileFormat *pcap_;
QByteArray pktBuf_; QByteArray pktBuf_;
bool recalculateCksums_{false};
bool isMldSupport_; bool isMldSupport_;
int packetCount_; int packetCount_;
int expPos_; int expPos_;

View File

@ -45,6 +45,7 @@ message StreamCore {
e_fl_inc = 1; e_fl_inc = 1;
e_fl_dec = 2; e_fl_dec = 2;
e_fl_random = 3; e_fl_random = 3;
e_fl_imix = 4;
} }
// Basics // Basics
@ -212,6 +213,9 @@ message Port {
optional TransmitMode transmit_mode = 7 [default = kSequentialTransmit]; optional TransmitMode transmit_mode = 7 [default = kSequentialTransmit];
optional string user_name = 8; optional string user_name = 8;
optional bool is_tracking_stream_stats = 9; optional bool is_tracking_stream_stats = 9;
optional double speed = 10; // in Mbps
optional uint32 mtu = 11;
} }
message PortConfigList { message PortConfigList {

View File

@ -239,3 +239,33 @@ QStringList ProtocolManager::protocolDatabase()
{ {
return numberToNameMap.values(); 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 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 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, 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*/, void PdmlSampleProtocol::postProtocolHandler(OstProto::Protocol* /*pbProto*/,
OstProto::Stream* /*stream*/) OstProto::Stream* /*stream*/)

View File

@ -156,23 +156,7 @@
<item> <item>
<layout class="QGridLayout" name="gridLayout_3"> <layout class="QGridLayout" name="gridLayout_3">
<item row="1" column="1"> <item row="1" column="1">
<widget class="QLineEdit" name="ui_root_id"> <widget class="MacEdit" name="ui_root_id"/>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="inputMask">
<string>&gt;HH HH HH HH HH HH; </string>
</property>
<property name="text">
<string> </string>
</property>
<property name="cursorPosition">
<number>17</number>
</property>
</widget>
</item> </item>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="label_11"> <widget class="QLabel" name="label_11">
@ -254,11 +238,7 @@
<item> <item>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="1" column="1"> <item row="1" column="1">
<widget class="QLineEdit" name="ui_bridge_id"> <widget class="MacEdit" name="ui_bridge_id"/>
<property name="inputMask">
<string>&gt;HH HH HH HH HH HH; </string>
</property>
</widget>
</item> </item>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="label_15"> <widget class="QLabel" name="label_15">
@ -499,6 +479,13 @@
<tabstop>ui_hello_time</tabstop> <tabstop>ui_hello_time</tabstop>
<tabstop>ui_forward_delay</tabstop> <tabstop>ui_forward_delay</tabstop>
</tabstops> </tabstops>
<customwidgets>
<customwidget>
<class>MacEdit</class>
<extends>QLineEdit</extends>
<header>macedit.h</header>
</customwidget>
</customwidgets>
<resources/> <resources/>
<connections/> <connections/>
</ui> </ui>

View File

@ -136,8 +136,7 @@ void StpConfigForm::loadWidget(AbstractProtocol *proto)
AbstractProtocol::FieldValue AbstractProtocol::FieldValue
).toULongLong(&isOk); ).toULongLong(&isOk);
ui_root_id->setText( ui_root_id->setValue(rootId & 0x0000FFFFFFFFFFFFULL);
QString::number(rootId & 0x0000FFFFFFFFFFFFULL, BASE_HEX));
ui_root_id_priority->setText(QString::number(rootId >> 48)); ui_root_id_priority->setText(QString::number(rootId >> 48));
ui_root_path_cost->setText( ui_root_path_cost->setText(
@ -153,8 +152,7 @@ void StpConfigForm::loadWidget(AbstractProtocol *proto)
AbstractProtocol::FieldValue AbstractProtocol::FieldValue
).toULongLong(&isOk); ).toULongLong(&isOk);
ui_bridge_id->setText( ui_bridge_id->setValue(bridgeId & 0x0000FFFFFFFFFFFFULL);
QString::number(bridgeId & 0x0000FFFFFFFFFFFFULL, BASE_HEX));
ui_bridge_id_priority->setText(QString::number(bridgeId >> 48)); ui_bridge_id_priority->setText(QString::number(bridgeId >> 48));
// port priority is a first byte of stp_port_id field // port priority is a first byte of stp_port_id field
@ -214,8 +212,7 @@ void StpConfigForm::storeWidget(AbstractProtocol *proto)
// and the last 6 bytes are root MAC address (IEEE802.1D-2008) // and the last 6 bytes are root MAC address (IEEE802.1D-2008)
quint64 rootIdPrio = ui_root_id_priority->text() quint64 rootIdPrio = ui_root_id_priority->text()
.toULongLong(&isOk) & TWO_BYTE_MAX; .toULongLong(&isOk) & TWO_BYTE_MAX;
quint64 rootId = hexStrToUInt64( quint64 rootId = ui_root_id->value() | (rootIdPrio << 48);
ui_root_id->text()) | rootIdPrio << 48;
proto->setFieldData(StpProtocol::stp_root_id, rootId); proto->setFieldData(StpProtocol::stp_root_id, rootId);
proto->setFieldData( proto->setFieldData(
@ -226,8 +223,7 @@ void StpConfigForm::storeWidget(AbstractProtocol *proto)
// and the last 6 bytes are bridge MAC address (IEEE802.1D-2008) // and the last 6 bytes are bridge MAC address (IEEE802.1D-2008)
quint64 bridgeIdPrio = quint64 bridgeIdPrio =
ui_bridge_id_priority->text().toULongLong(&isOk) & TWO_BYTE_MAX; ui_bridge_id_priority->text().toULongLong(&isOk) & TWO_BYTE_MAX;
quint64 bridgeId = quint64 bridgeId = ui_bridge_id->value() | (bridgeIdPrio << 48);
hexStrToUInt64(ui_bridge_id->text()) | bridgeIdPrio << 48;
proto->setFieldData(StpProtocol::stp_bridge_id, bridgeId); proto->setFieldData(StpProtocol::stp_bridge_id, bridgeId);
// port priority is a first byte of stp_port_id field // port priority is a first byte of stp_port_id field

View File

@ -18,11 +18,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
*/ */
#include "streambase.h" #include "streambase.h"
#include "abstractprotocol.h" #include "abstractprotocol.h"
#include "framevalueattrib.h" #include "framevalueattrib.h"
#include "protocollist.h" #include "protocollist.h"
#include "protocollistiterator.h" #include "protocollistiterator.h"
#include "protocolmanager.h" #include "protocolmanager.h"
#include "uint128.h"
#include <QDebug>
extern ProtocolManager *OstProtocolManager; extern ProtocolManager *OstProtocolManager;
extern quint64 getDeviceMacAddress(int portId, int streamId, int frameIndex); extern quint64 getDeviceMacAddress(int portId, int streamId, int frameIndex);
@ -217,18 +221,18 @@ quint16 StreamBase::frameLen(int streamIndex) const
// Decide a frame length based on length mode // Decide a frame length based on length mode
switch(lenMode()) switch(lenMode())
{ {
case OstProto::StreamCore::e_fl_fixed: case e_fl_fixed:
pktLen = mCore->frame_len(); pktLen = mCore->frame_len();
break; break;
case OstProto::StreamCore::e_fl_inc: case e_fl_inc:
pktLen = frameLenMin() + (streamIndex % pktLen = frameLenMin() + (streamIndex %
(frameLenMax() - frameLenMin() + 1)); (frameLenMax() - frameLenMin() + 1));
break; break;
case OstProto::StreamCore::e_fl_dec: case e_fl_dec:
pktLen = frameLenMax() - (streamIndex % pktLen = frameLenMax() - (streamIndex %
(frameLenMax() - frameLenMin() + 1)); (frameLenMax() - frameLenMin() + 1));
break; break;
case OstProto::StreamCore::e_fl_random: case e_fl_random:
//! \todo (MED) This 'random' sequence is same across iterations //! \todo (MED) This 'random' sequence is same across iterations
pktLen = 64; // to avoid the 'maybe used uninitialized' warning pktLen = 64; // to avoid the 'maybe used uninitialized' warning
qsrand(reinterpret_cast<ulong>(this)); qsrand(reinterpret_cast<ulong>(this));
@ -237,6 +241,14 @@ quint16 StreamBase::frameLen(int streamIndex) const
pktLen = frameLenMin() + (pktLen % pktLen = frameLenMin() + (pktLen %
(frameLenMax() - frameLenMin() + 1)); (frameLenMax() - frameLenMin() + 1));
break; break;
case e_fl_imix: {
// 64, 594, 1518 in 7:4:1 ratio
// sizes mixed up intentionally below
static int imixPattern[12]
= {64, 594, 64, 594, 64, 1518, 64, 64, 594, 64, 594, 64};
pktLen = imixPattern[streamIndex % 12];
break;
}
default: default:
qWarning("Unhandled len mode %d. Using default 64", qWarning("Unhandled len mode %d. Using default 64",
lenMode()); lenMode());
@ -282,6 +294,8 @@ quint16 StreamBase::frameLenAvg() const
if (lenMode() == e_fl_fixed) if (lenMode() == e_fl_fixed)
avgFrameLen = frameLen(); avgFrameLen = frameLen();
else if (lenMode() == e_fl_imix)
avgFrameLen = (7*64 + 4*594 + 1*1518)/12; // 64,594,1518 in 7:4:1 ratio
else else
avgFrameLen = (frameLenMin() + frameLenMax())/2; avgFrameLen = (frameLenMin() + frameLenMax())/2;
@ -463,13 +477,16 @@ int StreamBase::frameSizeVariableCount() const
switch(lenMode()) switch(lenMode())
{ {
case OstProto::StreamCore::e_fl_fixed: case e_fl_fixed:
break; break;
case OstProto::StreamCore::e_fl_inc: case e_fl_inc:
case OstProto::StreamCore::e_fl_dec: case e_fl_dec:
case OstProto::StreamCore::e_fl_random: case e_fl_random:
count = qMin(frameLenMax() - frameLenMin() + 1, frameCount()); count = qMin(frameLenMax() - frameLenMin() + 1, frameCount());
break; break;
case e_fl_imix:
count = 12; // 7:4:1 ratio, so 7+4+1
break;
default: default:
qWarning("%s: Unhandled len mode %d", __FUNCTION__, lenMode()); qWarning("%s: Unhandled len mode %d", __FUNCTION__, lenMode());
break; break;
@ -585,6 +602,64 @@ int StreamBase::frameValue(uchar *buf, int bufMaxSize, int frameIndex,
return len; 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 quint64 StreamBase::deviceMacAddress(int frameIndex) const
{ {
return getDeviceMacAddress(portId_, int(mStreamId->id()), frameIndex); 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 <QString>
#include <QLinkedList> #include <QLinkedList>
#include <QVariant>
#include "protocol.pb.h" #include "protocol.pb.h"
@ -51,7 +52,8 @@ public:
e_fl_fixed, e_fl_fixed,
e_fl_inc, e_fl_inc,
e_fl_dec, e_fl_dec,
e_fl_random e_fl_random,
e_fl_imix
}; };
enum SendUnit { enum SendUnit {
@ -140,6 +142,11 @@ public:
int frameValue(uchar *buf, int bufMaxSize, int frameIndex, int frameValue(uchar *buf, int bufMaxSize, int frameIndex,
FrameValueAttrib *attrib = nullptr) const; 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 deviceMacAddress(int frameIndex) const;
quint64 neighborMacAddress(int frameIndex) const; quint64 neighborMacAddress(int frameIndex) const;
@ -148,6 +155,10 @@ public:
static bool StreamLessThan(StreamBase* stream1, StreamBase* stream2); static bool StreamLessThan(StreamBase* stream1, StreamBase* stream2);
private: private:
template <typename T>
int findReplace(quint32 protocolNumber, int fieldIndex,
QVariant findValue, QVariant findMask,
QVariant replaceValue, QVariant replaceMask);
int portId_; int portId_;
OstProto::StreamId *mStreamId; 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_src_port(true);
tcp->set_is_override_dst_port(true); tcp->set_is_override_dst_port(true);
tcp->set_is_override_hdrlen(true); tcp->set_is_override_hdrlen(true);
tcp->set_is_override_cksum(true); tcp->set_is_override_cksum(overrideCksum_);
if (options_.size()) 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_src_port(true);
udp->set_is_override_dst_port(true); udp->set_is_override_dst_port(true);
udp->set_is_override_totlen(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_; quint64 lo_;
quint8 array_[16]; quint8 array_[16];
}; };
Q_DECLARE_METATYPE(UInt128);
inline UInt128::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

View File

@ -225,7 +225,7 @@ int UserScriptProtocol::protocolFrameVariableCount() const
} }
quint32 UserScriptProtocol::protocolFrameCksum(int streamIndex, quint32 UserScriptProtocol::protocolFrameCksum(int streamIndex,
CksumType cksumType) const CksumType cksumType, CksumFlags cksumFlags) const
{ {
QScriptValue userFunction; QScriptValue userFunction;
QScriptValue userValue; QScriptValue userValue;
@ -243,9 +243,12 @@ quint32 UserScriptProtocol::protocolFrameCksum(int streamIndex,
Q_ASSERT(userFunction.isFunction()); Q_ASSERT(userFunction.isFunction());
userValue = userFunction.call(QScriptValue(), userValue = userFunction.call(
QScriptValueList() << QScriptValue(&engine_, streamIndex) QScriptValue(),
<< QScriptValue(&engine_, cksumType)); QScriptValueList()
<< QScriptValue(&engine_, streamIndex)
<< QScriptValue(&engine_, cksumType)
<< QScriptValue(&engine_, cksumFlags));
Q_ASSERT(userValue.isValid()); Q_ASSERT(userValue.isValid());
Q_ASSERT(userValue.isNumber()); Q_ASSERT(userValue.isNumber());
@ -253,7 +256,8 @@ quint32 UserScriptProtocol::protocolFrameCksum(int streamIndex,
return userValue.toUInt32(); return userValue.toUInt32();
_do_default: _do_default:
return AbstractProtocol::protocolFrameCksum(streamIndex, cksumType); return AbstractProtocol::protocolFrameCksum(
streamIndex, cksumType, cksumFlags);
} }
void UserScriptProtocol::evaluateUserScript() const void UserScriptProtocol::evaluateUserScript() const

View File

@ -58,6 +58,13 @@ public:
CksumTcpUdp = AbstractProtocol::CksumTcpUdp CksumTcpUdp = AbstractProtocol::CksumTcpUdp
}; };
enum CksumFlag
{
IncludeCksumField = AbstractProtocol::IncludeCksumField
};
Q_DECLARE_FLAGS(CksumFlags, CksumFlag);
Q_FLAG(CksumFlags);
UserProtocol(AbstractProtocol *parent); UserProtocol(AbstractProtocol *parent);
public slots: public slots:
@ -131,7 +138,7 @@ public:
virtual int protocolFrameVariableCount() const; virtual int protocolFrameVariableCount() const;
virtual quint32 protocolFrameCksum(int streamIndex = 0, virtual quint32 protocolFrameCksum(int streamIndex = 0,
CksumType cksumType = CksumIp) const; CksumType cksumType = CksumIp, CksumFlags cksumFlags = 0) const;
void evaluateUserScript() const; void evaluateUserScript() const;
bool isScriptValid() const; bool isScriptValid() const;

View File

@ -64,6 +64,11 @@ AbstractPort::~AbstractPort()
void AbstractPort::init() void AbstractPort::init()
{ {
if (interfaceInfo_) {
data_.set_speed(interfaceInfo_->speed);
data_.set_mtu(interfaceInfo_->mtu);
}
if (deviceManager_) if (deviceManager_)
deviceManager_->createHostDevices(); deviceManager_->createHostDevices();
} }
@ -256,7 +261,7 @@ int AbstractPort::updatePacketListSequential()
switch (streamList_[i]->sendUnit()) switch (streamList_[i]->sendUnit())
{ {
case OstProto::StreamControl::e_su_bursts: case StreamBase::e_su_bursts:
burstSize = streamList_[i]->burstSize(); burstSize = streamList_[i]->burstSize();
x = AbstractProtocol::lcm(frameVariableCount, burstSize); x = AbstractProtocol::lcm(frameVariableCount, burstSize);
n = ulong(burstSize * streamList_[i]->numBursts()) / x; n = ulong(burstSize * streamList_[i]->numBursts()) / x;
@ -271,7 +276,7 @@ int AbstractPort::updatePacketListSequential()
} }
loopDelay = ibg2; loopDelay = ibg2;
break; break;
case OstProto::StreamControl::e_su_packets: case StreamBase::e_su_packets:
x = frameVariableCount; x = frameVariableCount;
n = 2; n = 2;
while (x < minPacketSetSize_) while (x < minPacketSetSize_)
@ -364,10 +369,10 @@ int AbstractPort::updatePacketListSequential()
switch(streamList_[i]->nextWhat()) switch(streamList_[i]->nextWhat())
{ {
case ::OstProto::StreamControl::e_nw_stop: case StreamBase::e_nw_stop:
goto _stop_no_more_pkts; goto _stop_no_more_pkts;
case ::OstProto::StreamControl::e_nw_goto_id: case StreamBase::e_nw_goto_id:
/*! \todo (MED): define and use /*! \todo (MED): define and use
streamList_[i].d.control().goto_stream_id(); */ streamList_[i].d.control().goto_stream_id(); */
@ -385,7 +390,7 @@ int AbstractPort::updatePacketListSequential()
StreamBase::e_su_bursts ? ibg1 : ipg1); StreamBase::e_su_bursts ? ibg1 : ipg1);
goto _stop_no_more_pkts; goto _stop_no_more_pkts;
case ::OstProto::StreamControl::e_nw_goto_next: case StreamBase::e_nw_goto_next:
break; break;
default: default:
@ -464,7 +469,7 @@ int AbstractPort::updatePacketListInterleaved()
switch (streamList_[i]->sendUnit()) switch (streamList_[i]->sendUnit())
{ {
case OstProto::StreamControl::e_su_bursts: case StreamBase::e_su_bursts:
numBursts = streamList_[i]->burstRate(); numBursts = streamList_[i]->burstRate();
if (streamList_[i]->burstRate() > 0) if (streamList_[i]->burstRate() > 0)
{ {
@ -476,7 +481,7 @@ int AbstractPort::updatePacketListInterleaved()
_burstSize = streamList_[i]->burstSize(); _burstSize = streamList_[i]->burstSize();
} }
break; break;
case OstProto::StreamControl::e_su_packets: case StreamBase::e_su_packets:
numPackets = streamList_[i]->packetRate(); numPackets = streamList_[i]->packetRate();
if (streamList_[i]->packetRate() > 0) if (streamList_[i]->packetRate() > 0)
{ {

View File

@ -215,6 +215,10 @@ void BsdPort::populateInterfaceInfo()
interfaceInfo_ = new InterfaceInfo; interfaceInfo_ = new InterfaceInfo;
interfaceInfo_->mac = mac; interfaceInfo_->mac = mac;
if (mac) {
interfaceInfo_->speed = ((struct if_data*)addr->ifa_data)->ifi_baudrate/1e6;
interfaceInfo_->mtu = ((struct if_data*)addr->ifa_data)->ifi_mtu;
}
// //
// Find gateways // Find gateways

View File

@ -59,6 +59,7 @@ SOURCES += \
bsdport.cpp \ bsdport.cpp \
linuxhostdevice.cpp \ linuxhostdevice.cpp \
linuxport.cpp \ linuxport.cpp \
linuxutils.cpp \
params.cpp \ params.cpp \
winhostdevice.cpp \ winhostdevice.cpp \
winpcapport.cpp winpcapport.cpp

View File

@ -39,6 +39,9 @@ struct InterfaceInfo
quint64 mac; quint64 mac;
QList<Ip4Config> ip4; QList<Ip4Config> ip4;
QList<Ip6Config> ip6; QList<Ip6Config> ip6;
double speed{0}; // in Mbps
quint32 mtu{0};
}; };
#endif #endif

View File

@ -20,6 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "linuxport.h" #include "linuxport.h"
#include "interfaceinfo.h" #include "interfaceinfo.h"
#include "linuxutils.h"
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
@ -243,10 +244,13 @@ void LinuxPort::populateInterfaceInfo()
return; return;
} }
interfaceInfo_ = new InterfaceInfo;
interfaceInfo_->speed = sysfsAttrib(name(), "speed").toDouble();
interfaceInfo_->mtu = rtnl_link_get_mtu(link);
int ifIndex = rtnl_link_get_ifindex(link); int ifIndex = rtnl_link_get_ifindex(link);
rtnl_link_put(link); rtnl_link_put(link);
interfaceInfo_ = new InterfaceInfo;
interfaceInfo_->mac = mac; interfaceInfo_->mac = mac;
// //

65
server/linuxutils.cpp Normal file
View File

@ -0,0 +1,65 @@
/*
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 "linuxutils.h"
#include <QFile>
#include <QTextStream>
// Reads a text file (size < 4K) and returns content as a string
// A terminating \n will be removed
// There's no way to distinguish an empty file and error while reading
QString readTextFile(QString fileName)
{
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qWarning("Can't read %s", qUtf8Printable(fileName));
return QString();
}
if (file.size() > 4096) {
qWarning("Can't read %s - too large (%lld)",
qUtf8Printable(fileName), file.size());
return QString();
}
QTextStream in(&file);
QString text = in.readAll();
file.close();
if (text.endsWith('\n'))
text.chop(1);
return text;
}
// Reads value from /sys/class/net/<device>/<attrib-path>
// and returns as string
// XXX: reading from sysfs is discouraged
QString sysfsAttrib(const char *device, const char *attribPath)
{
return readTextFile(QString("/sys/class/net/%1/%2")
.arg(device).arg(attribPath));
}
// Convenience overload
QString sysfsAttrib(QString device, const char *attribPath)
{
return sysfsAttrib(qPrintable(device), attribPath);
}

29
server/linuxutils.h Normal file
View File

@ -0,0 +1,29 @@
/*
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 _LINUX_UTILS_H
#define _LINUX_UTILS_H
#include <QString>
QString readTextFile(QString fileName);
QString sysfsAttrib(const char *device, const char *attribPath);
QString sysfsAttrib(QString device, const char *attribPath);
#endif

Some files were not shown because too many files have changed in this diff Show More