Merge branch 'master' into ci-dev

This commit is contained in:
Srivats P 2021-04-25 10:42:51 +05:30
commit df17547c1b
162 changed files with 6395 additions and 1428 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,5 +1,4 @@
language: cpp
#osx_image: xcode7.3
os:
- linux
@ -32,9 +31,12 @@ addons:
packages:
- qtbase5-dev
- qtscript5-dev
- libqt5svg5-dev
- libpcap-dev
- libprotobuf-dev
- protobuf-compiler
- libnl-3-dev
- libnl-route-3-dev
script:
- QT_SELECT=qt5 qmake -config debug

View File

@ -6,4 +6,18 @@ This is the code repository for the Ostinato network packet crafter and traffic
Visit https://ostinato.org for demo video and details
License: GPLv3+ (see [COPYING](https://raw.githubusercontent.com/pstavirs/ostinato/master/COPYING))
Source License: GPLv3+ (see [COPYING](https://raw.githubusercontent.com/pstavirs/ostinato/master/COPYING))
## Author's note
I have been developing and maintaining Ostinato [single-handedly](https://github.com/pstavirs/ostinato/graphs/contributors) for more than 12 years. And during this time I have grudgingly come around to the view that open source cannot survive and thrive without money. Mixing money with open-source is messy, but I don't see a way forward unless we as a community become open to the idea of talking about it and changing our culture so that money is no longer a bad word.
I sell binary licenses on [ostinato.org](https://ostinato.org/downloads) to try and cover the costs of development. Please consider buying those - they are priced low enough that you can afford it or you could just as easily expense them to your organisation.
If you build Ostinato from source and find it useful, please donate to keep the lights on and sustain the project.
Read the Ostinato [origin story](https://ostinato.org/about).
[![Donate](https://ostinato.org/images/donate.png)](https://gum.co/ostdonate)
Srivats P<br/>
Author, Ostinato

View File

@ -94,7 +94,7 @@
<item>
<widget class="QLabel" name="CopyrightLabel" >
<property name="text" >
<string>Copyright (c) 2007-2018 Srivats P.</string>
<string>Copyright (c) 2007-2020 Srivats P.</string>
</property>
<property name="alignment" >
<set>Qt::AlignCenter</set>
@ -104,7 +104,7 @@
<item>
<widget class="QLabel" name="LinksLabel" >
<property name="text" >
<string>&lt;a href="http://ostinato.org">http://ostinato.org&lt;/a>&lt;br>&lt;a href="http://twitter.com/ostinato">@ostinato&lt;/a></string>
<string>&lt;a href="https://ostinato.org">ostinato.org&lt;/a>&lt;br>&lt;a href="https://twitter.com/ostinato">@ostinato&lt;/a></string>
</property>
<property name="textFormat" >
<enum>Qt::RichText</enum>
@ -155,7 +155,7 @@ Icons (c): Mark James (http://www.famfamfam.com/lab/icons/silk/)</string>
<item>
<widget class="QLabel" name="label_2" >
<property name="text" >
<string>&lt;p>This program 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.&lt;/p>&lt;p>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.&lt;/p>&lt;p>You should have received a copy of the GNU General Public License along with this program. If not, see &lt;a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/&lt;/a>&lt;/p></string>
<string>&lt;p>This program 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.&lt;/p>&lt;p>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.&lt;/p>&lt;p>You should have received a copy of the GNU General Public License along with this program. If not, see &lt;a href="https://www.gnu.org/licenses/">https://www.gnu.org/licenses/&lt;/a>&lt;/p></string>
</property>
<property name="textFormat" >
<enum>Qt::RichText</enum>

86
client/applymsg.h Normal file
View File

@ -0,0 +1,86 @@
/*
Copyright (C) 2019 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 _APPLY_MESSAGE_H
#define _APPLY_MESSAGE_H
#include <QDialog>
#include <QMainWindow>
#include <QProgressBar>
#include <QTimer>
extern QMainWindow *mainWindow;
class ApplyMessage: public QDialog
{
public:
ApplyMessage(QWidget *parent = Q_NULLPTR);
public slots:
void show();
virtual void done(int r);
private:
QLabel *help_;
QTimer *helpTimer_;
};
ApplyMessage::ApplyMessage(QWidget *parent)
: QDialog(parent)
{
auto layout = new QVBoxLayout(this);
auto message = new QLabel(tr("Pushing configuration changes to agent "
"and re-building packets ..."), this);
auto progress = new QProgressBar(this);
progress->setRange(0, 0);
progress->setTextVisible(false);
help_ = new QLabel(tr("<b>This may take some time</b>"), this);
help_->setAlignment(Qt::AlignCenter);
layout->addWidget(message);
layout->addWidget(progress);
layout->addWidget(help_);
helpTimer_ = new QTimer(this);
helpTimer_->setSingleShot(true);
helpTimer_->setInterval(4000);
connect(helpTimer_, SIGNAL(timeout()), help_, SLOT(show()));
}
void ApplyMessage::show()
{
help_->hide(); // shown when helpTimer_ expires
QWidget *parent = parentWidget();
if (!parent)
parent = mainWindow;
move(parent->frameGeometry().center() - rect().center());
setModal(true);
QDialog::show();
helpTimer_->start();
}
void ApplyMessage::done(int r)
{
helpTimer_->stop();
QDialog::done(r);
}
#endif

View File

@ -136,6 +136,11 @@ QVariant ArpStatusModel::data(const QModelIndex &index, int role) const
return QVariant();
}
Qt::DropActions ArpStatusModel::supportedDropActions() const
{
return Qt::IgnoreAction; // read-only model, doesn't accept any data
}
void ArpStatusModel::setDeviceIndex(Port *port, int deviceIndex)
{
beginResetModel();

View File

@ -40,6 +40,8 @@ public:
int role = Qt::DisplayRole) const;
QVariant data(const QModelIndex &index, int role) const;
Qt::DropActions supportedDropActions() const;
void setDeviceIndex(Port *port, int deviceIndex);
public slots:

221
client/clipboardhelper.cpp Normal file
View File

@ -0,0 +1,221 @@
/*
Copyright (C) 2020 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 "clipboardhelper.h"
#include "xtableview.h"
#include <QAction>
#include <QApplication>
#include <QClipboard>
#include <QIcon>
#include <QItemSelection>
#if 0 // change 0 to 1 for debug
#define xDebug(...) qDebug(__VA_ARGS__)
#else
#define xDebug(...)
#endif
ClipboardHelper::ClipboardHelper(QObject *parent)
: QObject(parent)
{
actionCut_ = new QAction(tr("&Cut"), this);
actionCut_->setObjectName(QStringLiteral("actionCut"));
actionCut_->setIcon(QIcon(QString::fromUtf8(":/icons/cut.png")));
actionCopy_ = new QAction(tr("Cop&y"), this);
actionCopy_->setObjectName(QStringLiteral("actionCopy"));
actionCopy_->setIcon(QIcon(QString::fromUtf8(":/icons/copy.png")));
actionPaste_ = new QAction(tr("&Paste"), this);
actionPaste_->setObjectName(QStringLiteral("actionPaste"));
actionPaste_->setIcon(QIcon(QString::fromUtf8(":/icons/paste.png")));
connect(actionCut_, SIGNAL(triggered()), SLOT(actionTriggered()));
connect(actionCopy_, SIGNAL(triggered()), SLOT(actionTriggered()));
connect(actionPaste_, SIGNAL(triggered()), SLOT(actionTriggered()));
// XXX: failsafe in case updation of cut/copy/status causes issues
// Temporary for 1 release - will be removed after that
if (qEnvironmentVariableIsSet("X-OSTINATO-CCP-STATUS")) {
qWarning("FAILSAFE: Cut-Copy-Paste action status will not be updated");
return;
}
connect(qApp, SIGNAL(focusChanged(QWidget*, QWidget*)),
SLOT(updateCutCopyStatus(QWidget*, QWidget*)));
connect(qApp, SIGNAL(focusChanged(QWidget*, QWidget*)),
SLOT(updatePasteStatus()));
connect(QGuiApplication::clipboard(), SIGNAL(dataChanged()),
SLOT(updatePasteStatus()));
}
QList<QAction*> ClipboardHelper::actions()
{
QList<QAction*> actionList({actionCut_, actionCopy_, actionPaste_});
return actionList;
}
void ClipboardHelper::actionTriggered()
{
QWidget *focusWidget = qApp->focusWidget();
if (!focusWidget)
return;
// single slot to handle cut/copy/paste - find which action was triggered
QString action = sender()->objectName()
.remove("action").append("()").toLower();
if (focusWidget->metaObject()->indexOfSlot(qPrintable(action)) < 0) {
xDebug("%s slot not found for object %s:%s ",
qPrintable(action),
qPrintable(focusWidget->objectName()),
focusWidget->metaObject()->className());
return;
}
action.remove("()");
QMetaObject::invokeMethod(focusWidget, qPrintable(action),
Qt::DirectConnection);
}
void ClipboardHelper::updateCutCopyStatus(QWidget *old, QWidget *now)
{
xDebug("In %s", __FUNCTION__);
const XTableView *view = dynamic_cast<XTableView*>(old);
if (view) {
disconnect(view->selectionModel(),
SIGNAL(selectionChanged(const QItemSelection&,
const QItemSelection&)),
this,
SLOT(focusWidgetSelectionChanged(const QItemSelection&,
const QItemSelection&)));
disconnect(view->model(), SIGNAL(modelReset()),
this, SLOT(focusWidgetModelReset()));
}
if (!now) {
xDebug("No focus widget to copy from");
actionCut_->setEnabled(false);
actionCopy_->setEnabled(false);
return;
}
const QMetaObject *meta = now->metaObject();
if (meta->indexOfSlot("copy()") < 0) {
xDebug("Focus Widget (%s) doesn't have a copy slot",
qPrintable(now->objectName()));
actionCut_->setEnabled(false);
actionCopy_->setEnabled(false);
return;
}
view = dynamic_cast<XTableView*>(now);
if (view) {
connect(view->selectionModel(),
SIGNAL(selectionChanged(const QItemSelection&,
const QItemSelection&)),
SLOT(focusWidgetSelectionChanged(const QItemSelection&,
const QItemSelection&)));
connect(view->model(), SIGNAL(modelReset()),
this, SLOT(focusWidgetModelReset()));
if (!view->hasSelection()) {
xDebug("%s doesn't have anything selected to copy",
qPrintable(view->objectName()));
actionCut_->setEnabled(false);
actionCopy_->setEnabled(false);
return;
}
xDebug("%s model can cut: %d", qPrintable(view->objectName()),
view->canCut());
actionCut_->setEnabled(view->canCut());
}
xDebug("%s has a selection and copy slot: copy possible",
qPrintable(now->objectName()));
actionCopy_->setEnabled(true);
}
void ClipboardHelper::focusWidgetSelectionChanged(
const QItemSelection &selected, const QItemSelection &/*deselected*/)
{
xDebug("In %s", __FUNCTION__);
// Selection changed in the XTableView that has focus
const XTableView *view = dynamic_cast<XTableView*>(qApp->focusWidget());
xDebug("canCut:%d empty:%d", view->canCut(), selected.indexes().isEmpty());
actionCut_->setEnabled(!selected.indexes().isEmpty()
&& view && view->canCut());
actionCopy_->setEnabled(!selected.indexes().isEmpty());
}
void ClipboardHelper::updatePasteStatus()
{
xDebug("In %s", __FUNCTION__);
QWidget *focusWidget = qApp->focusWidget();
if (!focusWidget) {
xDebug("No focus widget to paste into");
actionPaste_->setEnabled(false);
return;
}
const QMimeData *item = QGuiApplication::clipboard()->mimeData();
if (!item || item->formats().isEmpty()) {
xDebug("Nothing on clipboard to paste");
actionPaste_->setEnabled(false);
return;
}
const QMetaObject *meta = focusWidget->metaObject();
if (meta->indexOfSlot("paste()") < 0) {
xDebug("Focus Widget (%s) doesn't have a paste slot",
qPrintable(focusWidget->objectName()));
actionPaste_->setEnabled(false);
return;
}
const XTableView *view = dynamic_cast<XTableView*>(focusWidget);
if (view && !view->canPaste(item)) {
xDebug("%s cannot accept this item (%s)",
qPrintable(view->objectName()),
qPrintable(item->formats().join("|")));
actionPaste_->setEnabled(false);
return;
}
xDebug("%s can accept this item (%s): paste possible",
qPrintable(focusWidget->objectName()),
qPrintable(item->formats().join("|")));
actionPaste_->setEnabled(true);
}
void ClipboardHelper::focusWidgetModelReset()
{
xDebug("In %s", __FUNCTION__);
QWidget *focusWidget = qApp->focusWidget();
updateCutCopyStatus(focusWidget, focusWidget); // re-eval cut/copy status
}
#undef xDebug

51
client/clipboardhelper.h Normal file
View File

@ -0,0 +1,51 @@
/*
Copyright (C) 2020 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 _CLIPBOARD_HELPER_H
#define _CLIPBOARD_HELPER_H
#include <QObject>
#include <QList>
class QAction;
class QItemSelection;
class ClipboardHelper : public QObject
{
Q_OBJECT
public:
ClipboardHelper(QObject *parent=nullptr);
QList<QAction*> actions();
private slots:
void actionTriggered();
void updateCutCopyStatus(QWidget *old, QWidget *now);
void focusWidgetSelectionChanged(const QItemSelection &selected,
const QItemSelection &deselected);
void focusWidgetModelReset();
void updatePasteStatus();
private:
QAction *actionCut_{nullptr};
QAction *actionCopy_{nullptr};
QAction *actionPaste_{nullptr};
};
#endif

View File

@ -25,6 +25,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "uint128.h"
#include <QHostAddress>
#include <QMimeData>
const QLatin1String kDeviceGroupsMimeType(
"application/vnd.ostinato.devicegroups");
enum {
kName,
@ -118,7 +122,7 @@ QVariant DeviceGroupModel::data(const QModelIndex &index, int role) const
return v;
return QString("None");
case Qt::TextAlignmentRole:
return Qt::AlignRight;
return static_cast<int>(Qt::AlignRight|Qt::AlignVCenter);
default:
break;
}
@ -129,7 +133,7 @@ QVariant DeviceGroupModel::data(const QModelIndex &index, int role) const
case Qt::DisplayRole:
return qMax(vlanCount(devGrp), 1)*devGrp->device_count();
case Qt::TextAlignmentRole:
return Qt::AlignRight;
return static_cast<int>(Qt::AlignRight|Qt::AlignVCenter);
default:
break;
}
@ -205,6 +209,85 @@ bool DeviceGroupModel::setData(
return false;
}
QStringList DeviceGroupModel::mimeTypes() const
{
return QStringList() << kDeviceGroupsMimeType;
}
QMimeData* DeviceGroupModel::mimeData(const QModelIndexList &indexes) const
{
using ::google::protobuf::uint8;
if (indexes.isEmpty())
return nullptr;
// indexes may include multiple columns for a row - but we are only
// interested in rows 'coz we have a single data for all columns
// XXX: use QMap instead of QSet to keep rows in sorted order
QMap<int, int> rows;
foreach(QModelIndex index, indexes)
rows.insert(index.row(), index.row());
OstProto::DeviceGroupConfigList dgList;
dgList.mutable_port_id()->set_id(port_->id());
foreach(int row, rows) {
OstProto::DeviceGroup *devGrp = dgList.add_device_group();
devGrp->CopyFrom(*port_->deviceGroupByIndex(row));
}
QByteArray data;
data.resize(dgList.ByteSize());
dgList.SerializeWithCachedSizesToArray((uint8*)data.data());
//qDebug("copy %s", dgList.DebugString().c_str());
//TODO: copy DebugString as text/plain?
QMimeData *mimeData = new QMimeData();
mimeData->setData(kDeviceGroupsMimeType, data);
return mimeData; // XXX: caller is expected to take ownership and free!
}
bool DeviceGroupModel::dropMimeData(const QMimeData *data,
Qt::DropAction action, int row, int /*column*/,
const QModelIndex &parent)
{
if (!data)
return false;
if (!data->hasFormat(kDeviceGroupsMimeType))
return false;
if (action != Qt::CopyAction)
return false;
OstProto::DeviceGroupConfigList dgList;
QByteArray ba(data->data(kDeviceGroupsMimeType));
dgList.ParseFromArray((void*)ba.constData(), ba.size());
//qDebug("paste %s", dgList.DebugString().c_str());
if ((row < 0) || (row > rowCount(parent)))
row = rowCount(parent);
// Delete rows that we are going to overwrite
int c = 0, count = dgList.device_group_size();
if (row < rowCount(parent))
removeRows(row, qMin(rowCount() - row, count));
beginInsertRows(parent, row, row+count-1);
for (int i = 0; i < count; i++) {
if (port_->newDeviceGroupAt(row+i, &dgList.device_group(i)))
c++;
}
endInsertRows();
if (c != count) {
qWarning("failed to copy rows in DeviceGroupModel at row %d; "
"requested = %d, actual = %d", row, count, c);
return false;
}
return true;
}
bool DeviceGroupModel::insertRows(
int row,
int count,

View File

@ -42,6 +42,12 @@ public:
QVariant data(const QModelIndex &index, int role) const;
bool setData(const QModelIndex &index, const QVariant &value,
int role = Qt::EditRole);
QStringList mimeTypes() const;
QMimeData* mimeData(const QModelIndexList &indexes) const;
bool dropMimeData(const QMimeData *data, Qt::DropAction action,
int row, int column, const QModelIndex &parent);
bool insertRows (int row, int count,
const QModelIndex &parent = QModelIndex());
bool removeRows (int row, int count,

View File

@ -172,9 +172,10 @@ QVariant DeviceModel::data(const QModelIndex &index, int role) const
case Qt::DisplayRole:
if (dev->has_ip6_prefix_length()) {
OstEmul::Ip6Address ip = dev->ip6();
return QHostAddress(
UInt128(ip.hi(), ip.lo()).toArray())
.toString();
return QString("%1/%2")
.arg(QHostAddress(UInt128(ip.hi(), ip.lo())
.toArray()).toString())
.arg(dev->ip6_prefix_length());
}
else
return QString("--");
@ -241,6 +242,11 @@ QVariant DeviceModel::data(const QModelIndex &index, int role) const
return QVariant();
}
Qt::DropActions DeviceModel::supportedDropActions() const
{
return Qt::IgnoreAction; // read-only model, doesn't accept any data
}
void DeviceModel::setPort(Port *port)
{
beginResetModel();

View File

@ -39,6 +39,8 @@ public:
int role = Qt::DisplayRole) const;
QVariant data(const QModelIndex &index, int role) const;
Qt::DropActions supportedDropActions() const;
void setPort(Port *port);
QAbstractItemModel* detailModel(const QModelIndex &index);

View File

@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "deviceswidget.h"
#include "clipboardhelper.h"
#include "devicegroupdialog.h"
#include "port.h"
#include "portgrouplist.h"
@ -26,13 +27,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include <QHeaderView>
#include <QKeyEvent>
extern ClipboardHelper *clipboardHelper;
DevicesWidget::DevicesWidget(QWidget *parent)
: QWidget(parent), portGroups_(NULL)
{
setupUi(this);
deviceGroupList->setVisible(deviceConfig->isChecked());
deviceList->setVisible(deviceInfo->isChecked());
refresh->setVisible(deviceInfo->isChecked());
setDeviceInfoButtonsVisible(deviceInfo->isChecked());
deviceDetail->hide();
deviceGroupList->verticalHeader()->setDefaultSectionSize(
@ -49,6 +52,14 @@ DevicesWidget::DevicesWidget(QWidget *parent)
// DevicesWidget's actions is an aggegate of all sub-widget's actions
addActions(deviceGroupList->actions());
// Add the clipboard actions to the context menu of deviceGroupList
// but not to DeviceWidget's actions since they are already available
// in the global Edit Menu
QAction *sep = new QAction("Clipboard", this);
sep->setSeparator(true);
deviceGroupList->addAction(sep);
deviceGroupList->addActions(clipboardHelper->actions());
}
void DevicesWidget::setPortGroupList(PortGroupList *portGroups)
@ -162,7 +173,7 @@ void DevicesWidget::updateDeviceViewActions()
void DevicesWidget::on_deviceInfo_toggled(bool checked)
{
refresh->setVisible(checked);
setDeviceInfoButtonsVisible(checked);
deviceGroupList->setHidden(checked);
deviceList->setVisible(checked);
deviceDetail->hide();
@ -236,6 +247,40 @@ void DevicesWidget::on_refresh_clicked()
.getDeviceInfo(portGroups_->port(currentPortIndex_).id());
}
void DevicesWidget::on_resolveNeighbors_clicked()
{
if (!portGroups_)
return;
Q_ASSERT(portGroups_->isPort(currentPortIndex_));
QModelIndex curPortGroup = portGroups_->getPortModel()
->parent(currentPortIndex_);
Q_ASSERT(curPortGroup.isValid());
Q_ASSERT(portGroups_->isPortGroup(curPortGroup));
deviceDetail->hide();
QList<uint> portList({portGroups_->port(currentPortIndex_).id()});
portGroups_->portGroup(curPortGroup).resolveDeviceNeighbors(&portList);
portGroups_->portGroup(curPortGroup).getDeviceInfo(portList.at(0));
}
void DevicesWidget::on_clearNeighbors_clicked()
{
if (!portGroups_)
return;
Q_ASSERT(portGroups_->isPort(currentPortIndex_));
QModelIndex curPortGroup = portGroups_->getPortModel()
->parent(currentPortIndex_);
Q_ASSERT(curPortGroup.isValid());
Q_ASSERT(portGroups_->isPortGroup(curPortGroup));
deviceDetail->hide();
QList<uint> portList({portGroups_->port(currentPortIndex_).id()});
portGroups_->portGroup(curPortGroup).clearDeviceNeighbors(&portList);
portGroups_->portGroup(curPortGroup).getDeviceInfo(portList.at(0));
}
void DevicesWidget::when_deviceList_currentChanged(const QModelIndex &index)
{
if (!index.isValid() || !portGroups_)
@ -247,3 +292,11 @@ void DevicesWidget::when_deviceList_currentChanged(const QModelIndex &index)
deviceDetail->setModel(detailModel);
deviceDetail->setVisible(detailModel != NULL);
}
void DevicesWidget::setDeviceInfoButtonsVisible(bool show)
{
refresh->setVisible(show);
arpNdpLabel->setVisible(show);
resolveNeighbors->setVisible(show);
clearNeighbors->setVisible(show);
}

View File

@ -49,10 +49,14 @@ private slots:
void on_deviceGroupList_activated(const QModelIndex &index);
void on_refresh_clicked();
void on_resolveNeighbors_clicked();
void on_clearNeighbors_clicked();
void when_deviceList_currentChanged(const QModelIndex &index);
private:
void setDeviceInfoButtonsVisible(bool show);
PortGroupList *portGroups_;
QModelIndex currentPortIndex_;
};

View File

@ -36,19 +36,6 @@
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>131</width>
<height>23</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="refresh">
<property name="toolTip">
@ -66,6 +53,60 @@
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>131</width>
<height>23</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="arpNdpLabel">
<property name="text">
<string>ARP/ND</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="resolveNeighbors">
<property name="toolTip">
<string>Resolve Neighbors</string>
</property>
<property name="statusTip">
<string>Resolve Device Neighbors on selected port(s)</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="ostinato.qrc">
<normaloff>:/icons/neighbor_resolve.png</normaloff>:/icons/neighbor_resolve.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="clearNeighbors">
<property name="toolTip">
<string>Clear Neighbors</string>
</property>
<property name="statusTip">
<string>Clear Device Neighbors on selected port(s)</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="ostinato.qrc">
<normaloff>:/icons/neighbor_clear.png</normaloff>:/icons/neighbor_clear.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
<item>
@ -117,8 +158,8 @@ To emulate a device, click on Configuration and create a device group</string>
<property name="whatsThis">
<string>IP neighbor cache is empty</string>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
</widget>
</item>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
client/icons/anime_warn.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
client/icons/copy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 663 B

BIN
client/icons/cut.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 B

BIN
client/icons/donate.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 732 B

23
client/icons/error.svg Normal file
View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="31.44mm" height="31.44mm" version="1.1" viewBox="0 0 31.44 31.44" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<metadata>
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:title/>
</cc:Work>
</rdf:RDF>
</metadata>
<g transform="translate(-37.525 -210.5)">
<circle cx="53.245" cy="226.22" r="15.72" fill="#e96c59" opacity=".8"/>
<path d="m53.419 212.28c-2.0736-0.0217-4.1616 0.41684-6.0884 1.3285-4.4585 2.1095-7.4154 6.2656-7.9501 11.175-0.50808 4.6643 1.5269 9.4967 5.2044 12.359 2.066 1.6079 4.6873 2.6928 7.1183 2.9462 2.643 0.2755 5.2282-0.17492 7.6011-1.3243 1.9456-0.94242 3.6302-2.2856 4.9044-3.9102 3.2672-4.1658 3.8994-9.8451 1.6275-14.62-1.0934-2.298-2.6916-4.1431-4.8639-5.6154-2.245-1.5216-4.8872-2.3101-7.5533-2.338zm-0.0276 2.2048c2.2445 0.0235 4.4688 0.68733 6.3588 1.9683 1.8288 1.2395 3.1743 2.7928 4.0948 4.7274 1.9126 4.0198 1.3803 8.8011-1.3702 12.308-1.0727 1.3677-2.4908 2.4985-4.1288 3.2919-1.9976 0.96764-4.1741 1.3468-6.3992 1.1148-2.0466-0.21332-4.2533-1.1267-5.9926-2.4803-3.096-2.4094-4.8092-6.4777-4.3814-10.404 0.45017-4.1327 2.9394-7.6316 6.6928-9.4075 1.6221-0.76747 3.38-1.1367 5.1257-1.1184z" fill="#f9bbab" opacity=".8"/>
<g fill="#fff">
<rect x="50.933" y="217.52" width="4.6236" height="12.862" opacity=".8"/>
<rect transform="scale(1,-1)" x="50.933" y="-234.92" width="4.6236" height="3.5749" opacity=".8"/>
<rect x="50.933" y="217.52" width="4.6236" height="12.862" opacity=".8"/>
<rect transform="scale(1,-1)" x="50.933" y="-234.92" width="4.6236" height="3.5749" opacity=".8"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 34 KiB

BIN
client/icons/paste.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 703 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

25
client/icons/warn.svg Normal file
View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="221.25mm" height="193.24mm" version="1.1" viewBox="0 0 221.25 193.24" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="linearGradient879" x1="193.48" x2="101.09" y1="198.47" y2="254.08" gradientTransform="matrix(1.7412 0 0 1.7412 -267.69 -212.02)" gradientUnits="userSpaceOnUse">
<stop stop-color="#c16602" stop-opacity=".99608" offset="0"/>
<stop stop-color="#e8b839" offset="1"/>
</linearGradient>
</defs>
<metadata>
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:title/>
</cc:Work>
</rdf:RDF>
</metadata>
<g transform="translate(97.776 -43.224)">
<path d="m12.851 49.318 104.53 181.06h-209.07z" fill="#fff" opacity=".8" stroke="url(#linearGradient879)" stroke-linecap="round" stroke-linejoin="round" stroke-opacity=".99216" stroke-width="12.188"/>
<path d="m12.851 101.1 62.098 107.56h-124.2z" fill="#f5db61" opacity=".8"/>
<rect x=".23388" y="113.27" width="25.235" height="54.405" fill="#c28832" opacity=".8"/>
<rect transform="scale(1,-1)" x=".23388" y="-196.48" width="25.235" height="14.793" fill="#c28832" opacity=".8"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -28,7 +28,7 @@ inline QString jumpUrl(
QString medium="hint",
QString name="help")
{
return QString("http://jump.ostinato.org/" + keyword + "?"
return QString("https://jump.ostinato.org/" + keyword + "?"
+ "utm_source=" + source + "&"
+ "utm_medium=" + medium + "&"
+ "utm_campaign=" + name);

View File

@ -108,6 +108,11 @@ QVariant LogsModel::data(const QModelIndex &index, int role) const
return QVariant();
}
Qt::DropActions LogsModel::supportedDropActions() const
{
return Qt::IgnoreAction; // read-only model, doesn't accept any data
}
// --------------------------------------------- //
// Slots
// --------------------------------------------- //
@ -140,6 +145,8 @@ void LogsModel::log(int logLevel,QString port, QString message)
logs_.last().timeStamp = QTime::currentTime();
logs_.last().logLevel = logLevel;
logs_.last().port = port;
logs_.last().message = message;
// XXX: QTableView does not honour newline unless we increase the
// row height, so we replace newlines with semicolon for now
logs_.last().message = message.trimmed().replace("\n", "; ");
endInsertRows();
}

View File

@ -44,6 +44,8 @@ public:
int role = Qt::DisplayRole) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
Qt::DropActions supportedDropActions() const;
public slots:
void clear();
void setLogLevel(int level);

View File

@ -24,6 +24,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include <QDockWidget>
#include <QHeaderView>
#include <QMainWindow>
#include <QMovie>
#include <QPropertyAnimation>
extern QMainWindow *mainWindow;
LogsWindow::LogsWindow(LogsModel *model, QWidget *parent)
: QWidget(parent)
@ -42,6 +47,17 @@ LogsWindow::LogsWindow(LogsModel *model, QWidget *parent)
parentDock_ = qobject_cast<QDockWidget*>(parent);
windowTitle_ = parentDock_->windowTitle();
warnAnime_ = new QMovie(":/icons/anime_warn.gif", QByteArray(), this);
errorAnime_ = new QMovie(":/icons/anime_error.gif", QByteArray(), this);
alert_ = new QLabel("ALERT!", this,
Qt::FramelessWindowHint|Qt::WindowStaysOnTopHint);
alert_->setScaledContents(true);
alert_->hide();
alertAnime_ = new QPropertyAnimation(alert_, "geometry", this);
alertAnime_->setDuration(2000);
alertAnime_->setEasingCurve(QEasingCurve::InOutExpo);
connect(level, SIGNAL(currentIndexChanged(int)),
model, SLOT(setLogLevel(int)));
connect(clear, SIGNAL(clicked()), model, SLOT(clear()));
@ -51,6 +67,9 @@ LogsWindow::LogsWindow(LogsModel *model, QWidget *parent)
connect(model, SIGNAL(rowsInserted(const QModelIndex&, int, int)),
SLOT(when_rowsInserted(const QModelIndex&, int, int)));
connect(logs->horizontalHeader(), SIGNAL(sectionResized(int, int, int)),
logs, SLOT(resizeRowsToContents()));
#if defined(QT_NO_DEBUG) || QT_VERSION < 0x050700
logsModelTest_ = nullptr;
#else
@ -60,14 +79,22 @@ LogsWindow::LogsWindow(LogsModel *model, QWidget *parent)
LogsWindow::~LogsWindow()
{
delete warnAnime_;
delete errorAnime_;
delete logsModelTest_;
}
void LogsWindow::clearCurrentSelection()
{
logs->selectionModel()->clearCurrentIndex();
logs->clearSelection();
}
void LogsWindow::when_visibilityChanged(bool visible)
{
if (visible) {
parentDock_->setWindowTitle(windowTitle_);
annotation_.clear();
logs->resizeRowsToContents();
setState(kInfo);
}
isVisible_ = visible;
@ -78,25 +105,25 @@ void LogsWindow::when_rowsInserted(const QModelIndex &parent,
int first, int last)
{
if (isVisible_)
return;
if (annotation_.contains("Error"))
return;
logs->resizeRowsToContents();
State incrementalState = kInfo;
for (int i = first; i <= last; i++) {
// FIXME: use a user-role instead, so we don't need to know column and
// have to compare strings?
QString level = logs->model()->data(logs->model()->index(i, 1, parent))
.toString();
if (level == "Error") {
annotation_ = QString(" - Error(s)");
incrementalState = kError;
break; // Highest level - no need to look further
}
else if (level == "Warning") {
annotation_ = QString(" - Warning(s)");
incrementalState = kWarning;
}
}
parentDock_->setWindowTitle(windowTitle_+annotation_);
alert(incrementalState);
if (incrementalState > state())
setState(incrementalState);
}
void LogsWindow::on_autoScroll_toggled(bool checked)
@ -112,3 +139,122 @@ void LogsWindow::on_autoScroll_toggled(bool checked)
logs, SLOT(scrollToBottom()));
}
}
LogsWindow::State LogsWindow::state()
{
return state_;
}
void LogsWindow::setState(State state)
{
if (isVisible_)
return;
state_ = state;
notify();
}
QLabel* LogsWindow::tabIcon()
{
QList<QTabBar*> tabBars = mainWindow->findChildren<QTabBar*>();
foreach(QTabBar* tabBar, tabBars) {
for (int i = 0; i < tabBar->count(); i++) {
if (tabBar->tabText(i).startsWith(windowTitle_)) {
QLabel *icon = qobject_cast<QLabel*>(
tabBar->tabButton(i, QTabBar::LeftSide));
if (!icon) { // Lazy create
icon = new QLabel();
tabBar->setTabButton(i, QTabBar::LeftSide, icon);
}
return icon;
}
}
}
return nullptr;
}
//! Popup and animate a big icon
void LogsWindow::alert(State state)
{
if (state == kInfo)
return;
// start - center of main window
QRect start;
QWidget *view = mainWindow;
alert_->setParent(view);
alert_->raise();
start.setSize(QSize(256, 256).scaled(view->size()/2, Qt::KeepAspectRatio));
start.moveCenter(QPoint(view->size().width()/2,
view->size().height()/2));
// end - center of logs window if visible, tab icon otherwise
QPoint c;
QLabel *icon = tabIcon();
view = isVisible_ ? dynamic_cast<QWidget*>(this) : mainWindow;
if (icon && !isVisible_) {
c = icon->geometry().center(); // in icon's parent (tabBar) coords
c = icon->mapFromParent(c); // in icon's own coords
c = icon->mapTo(view, c); // in mainWindow's coords
} else {
c = view->geometry().center();
c = view->mapTo(mainWindow, c); // in mainWindow's coords
}
QRect end;
end.moveCenter(c);
switch (state) {
case kError:
alert_->setPixmap(QPixmap(":/icons/error.svg"));
break;
case kWarning:
alert_->setPixmap(QPixmap(":/icons/warn.svg"));
break;
default:
Q_UNREACHABLE();
break;
}
alertAnime_->setStartValue(start);
alertAnime_->setEndValue(end);
alert_->show(); // ensure it's visible before starting animation
alertAnime_->start();
}
//! Show tab icon
void LogsWindow::notify()
{
QString annotation;
QMovie *anime = nullptr;
// Stop all animations before we start a new one
warnAnime_->stop();
errorAnime_->stop();
switch (state()) {
case kError:
anime = errorAnime_;
annotation = " - Error(s)";
break;
case kWarning:
anime = warnAnime_;
annotation = " - Warning(s)";
break;
case kInfo:
default:
break;
}
QLabel *icon = tabIcon(); // NOTE: we may not have a icon if not tabified
if (icon) {
if (anime) {
icon->setMovie(anime);
anime->start();
}
else
icon->clear();
icon->adjustSize();
}
parentDock_->setWindowTitle(windowTitle_ + annotation);
}

View File

@ -25,6 +25,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
class LogsModel;
class QDockWidget;
class QShowEvent;
class QMovie;
class QPropertyAnimation;
class LogsWindow: public QWidget, private Ui::LogsWindow
{
@ -33,17 +35,36 @@ public:
LogsWindow(LogsModel *model, QWidget *parent = 0);
~LogsWindow();
public slots:
void clearCurrentSelection();
private slots:
void when_visibilityChanged(bool visible);
void when_rowsInserted(const QModelIndex &parent, int first, int last);
void on_autoScroll_toggled(bool checked);
private:
enum State {kInfo, kWarning, kError};
QLabel* tabIcon();
State state();
void setState(State state);
void alert(State state);
void notify();
State state_{kInfo};
QDockWidget *parentDock_;
QMovie *warnAnime_{nullptr};
QMovie *errorAnime_{nullptr};
QLabel *alert_{nullptr};
QPropertyAnimation *alertAnime_{nullptr};
QString windowTitle_;
QString annotation_;
bool isVisible_{false};
bool isVisible_{false}; // see XXX below
QObject *logsModelTest_;
// XXX: We cannot use isVisible() instead of isVisible_ since
// LogsWindow::isVisible() returns true even when the parent LogsDock
// is tabified but not the selected tab
};
#endif

View File

@ -42,6 +42,9 @@ Params appParams;
QSettings *appSettings;
QMainWindow *mainWindow;
void NoMsgHandler(QtMsgType type, const QMessageLogContext &context,
const QString &msg);
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
@ -54,9 +57,16 @@ int main(int argc, char* argv[])
appParams.parseCommandLine(argc, argv);
#ifndef QT_DEBUG // Release mode
if (appParams.optLogsDisabled())
qInstallMessageHandler(NoMsgHandler);
#endif
OstProtocolManager = new ProtocolManager();
OstProtocolWidgetFactory = new ProtocolWidgetFactory();
Preferences::initDefaults();
/* (Portable Mode) If we have a .ini file in the same directory as the
executable, we use that instead of the platform specific location
and format for the settings */
@ -66,6 +76,7 @@ int main(int argc, char* argv[])
appSettings = new QSettings(portableIni, QSettings::IniFormat);
else
appSettings = new QSettings();
qDebug("Settings: %s", qPrintable(appSettings->fileName()));
OstProtoLib::setExternalApplicationPaths(
appSettings->value(kTsharkPathKey, kTsharkPathDefaultValue).toString(),
@ -73,7 +84,6 @@ int main(int argc, char* argv[])
appSettings->value(kDiffPathKey, kDiffPathDefaultValue).toString(),
appSettings->value(kAwkPathKey, kAwkPathDefaultValue).toString());
Preferences::initDefaults();
qsrand(QDateTime::currentDateTime().toTime_t());
mainWindow = new MainWindow;
@ -87,3 +97,14 @@ int main(int argc, char* argv[])
return exitCode;
}
void NoMsgHandler(QtMsgType type, const QMessageLogContext &/*context*/,
const QString &msg)
{
if (type == QtFatalMsg) {
fprintf(stderr, "%s\n", qPrintable(msg));
fflush(stderr);
abort();
}
}

View File

@ -23,6 +23,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "dbgthread.h"
#endif
#include "clipboardhelper.h"
#include "jumpurl.h"
#include "logsmodel.h"
#include "logswindow.h"
@ -38,6 +39,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "fileformat.pb.h"
#include <QDate>
#include <QDesktopServices>
#include <QDockWidget>
#include <QFileDialog>
@ -59,6 +61,7 @@ extern const char* revision;
PortGroupList *pgl;
LogsModel *appLogs;
ClipboardHelper *clipboardHelper;
MainWindow::MainWindow(QWidget *parent)
: QMainWindow (parent)
@ -93,6 +96,7 @@ MainWindow::MainWindow(QWidget *parent)
pgl = new PortGroupList;
appLogs = new LogsModel(this);
clipboardHelper = new ClipboardHelper(this);
portsWindow = new PortsWindow(pgl, this);
statsWindow = new PortStatsWindow(pgl, this);
@ -116,6 +120,7 @@ MainWindow::MainWindow(QWidget *parent)
setupUi(this);
menuFile->insertActions(menuFile->actions().at(3), portsWindow->actions());
menuEdit->addActions(clipboardHelper->actions());
statsDock->setWidget(statsWindow);
addDockWidget(Qt::BottomDockWidgetArea, statsDock);
@ -128,6 +133,13 @@ MainWindow::MainWindow(QWidget *parent)
portsDock->setWidget(portsWindow);
addDockWidget(Qt::TopDockWidgetArea, portsDock);
#if QT_VERSION >= 0x050600
// Set top and bottom docks to equal height
resizeDocks({portsDock, statsDock}, {height()/2, height()/2}, Qt::Vertical);
#endif
portsWindow->setFocus();
// Save the default window geometry and layout ...
defaultGeometry_ = geometry();
defaultLayout_ = saveState(0);
@ -153,10 +165,16 @@ MainWindow::MainWindow(QWidget *parent)
this, SLOT(onNewVersion(QString)));
updater->checkForNewVersion();
// Add the "Local" Port Group
if (appParams.optLocalDrone()) {
PortGroup *pg = new PortGroup;
pgl->addPortGroup(*pg);
}
if (appParams.argumentCount()) {
QString fileName = appParams.argument(0);
if (QFile::exists(fileName))
on_actionOpenSession_triggered(fileName);
openSession(fileName);
else
QMessageBox::information(NULL, qApp->applicationName(),
QString("File not found: " + fileName));
@ -201,7 +219,7 @@ MainWindow::~MainWindow()
}
}
void MainWindow::on_actionOpenSession_triggered(QString fileName)
void MainWindow::openSession(QString fileName)
{
qDebug("Open Session Action (%s)", qPrintable(fileName));
@ -253,6 +271,11 @@ _exit:
return;
}
void MainWindow::on_actionOpenSession_triggered()
{
openSession();
}
void MainWindow::on_actionSaveSession_triggered()
{
qDebug("Save Session Action");
@ -345,6 +368,10 @@ void MainWindow::on_actionViewRestoreDefaults_triggered()
statsDock->raise();
actionViewShowMyReservedPortsOnly->setChecked(false);
portsWindow->clearCurrentSelection();
statsWindow->clearCurrentSelection();
logsWindow_->clearCurrentSelection();
}
void MainWindow::on_actionHelpOnline_triggered()
@ -352,6 +379,19 @@ void MainWindow::on_actionHelpOnline_triggered()
QDesktopServices::openUrl(QUrl(jumpUrl("help", "app", "menu")));
}
void MainWindow::on_actionDonate_triggered()
{
QDesktopServices::openUrl(QUrl(jumpUrl("donate", "app", "menu")));
}
void MainWindow::on_actionCheckForUpdates_triggered()
{
Updater *updater = new Updater();
connect(updater, SIGNAL(latestVersion(QString)),
this, SLOT(onLatestVersion(QString)));
updater->checkForNewVersion();
}
void MainWindow::on_actionHelpAbout_triggered()
{
QDialog *aboutDialog = new QDialog;
@ -430,6 +470,21 @@ void MainWindow::reportLocalServerError()
void MainWindow::onNewVersion(QString newVersion)
{
QDate today = QDate::currentDate();
QDate lastChecked = QDate::fromString(
appSettings->value(kLastUpdateCheck).toString(),
Qt::ISODate);
if (lastChecked.daysTo(today) >= 5) {
QMessageBox::information(this, tr("Update check"),
tr("<p><b>Ostinato version %1 is now available</b> (you have %2). "
"See <a href='%3'>change log</a>.</p>"
"<p>Visit <a href='%4'>ostinato.org</a> to download.</p>")
.arg(newVersion)
.arg(version)
.arg(jumpUrl("changelog", "app", "status", "update"))
.arg(jumpUrl("download", "app", "status", "update")));
}
else {
QLabel *msg = new QLabel(tr("New Ostinato version %1 available. Visit "
"<a href='%2'>ostinato.org</a> to download")
.arg(newVersion)
@ -438,6 +493,31 @@ void MainWindow::onNewVersion(QString newVersion)
statusBar()->addPermanentWidget(msg);
}
appSettings->setValue(kLastUpdateCheck, today.toString(Qt::ISODate));
sender()->deleteLater();
}
void MainWindow::onLatestVersion(QString latestVersion)
{
if (version != latestVersion) {
QMessageBox::information(this, tr("Update check"),
tr("<p><b>Ostinato version %1 is now available</b> (you have %2). "
"See <a href='%3'>change log</a>.</p>"
"<p>Visit <a href='%4'>ostinato.org</a> to download.</p>")
.arg(latestVersion)
.arg(version)
.arg(jumpUrl("changelog", "app", "status", "update"))
.arg(jumpUrl("download", "app", "status", "update")));
}
else {
QMessageBox::information(this, tr("Update check"),
tr("You are already running the latest Ostinato version - %1")
.arg(version));
}
sender()->deleteLater();
}
//! Returns true on success (or user cancel) and false on failure
bool MainWindow::openSession(QString fileName, QString &error)
{

View File

@ -36,6 +36,7 @@ class MainWindow : public QMainWindow, private Ui::MainWindow
Q_OBJECT
private:
void openSession(QString fileName = QString());
bool openSession(QString fileName, QString &error);
bool saveSession(QString fileName, QString fileType, QString &error);
@ -55,11 +56,13 @@ public:
~MainWindow();
public slots:
void on_actionOpenSession_triggered(QString fileName = QString());
void on_actionOpenSession_triggered();
void on_actionSaveSession_triggered();
void on_actionPreferences_triggered();
void on_actionViewRestoreDefaults_triggered();
void on_actionHelpOnline_triggered();
void on_actionDonate_triggered();
void on_actionCheckForUpdates_triggered();
void on_actionHelpAbout_triggered();
private slots:
@ -68,6 +71,7 @@ private slots:
void onLocalServerError(QProcess::ProcessError error);
void reportLocalServerError();
void onNewVersion(QString version);
void onLatestVersion(QString version);
};
#endif

View File

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0" >
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow" >
@ -6,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>1024</width>
<height>600</height>
<height>700</height>
</rect>
</property>
<property name="windowTitle" >
@ -33,9 +34,17 @@
</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" >
<property name="title" >
<string>&amp;Edit</string>
</property>
</widget>
<widget class="QMenu" name="menuView" >
<property name="title" >
<string>&amp;View</string>
@ -44,6 +53,7 @@
<addaction name="actionViewRestoreDefaults" />
</widget>
<addaction name="menuFile" />
<addaction name="menuEdit" />
<addaction name="menuView" />
<addaction name="menuHelp" />
</widget>
@ -111,6 +121,19 @@
<string>Help (Online)</string>
</property>
</action>
<action name="actionDonate" >
<property name="icon" >
<iconset resource="ostinato.qrc" >:/icons/donate.png</iconset>
</property>
<property name="text" >
<string>Donate</string>
</property>
</action>
<action name="actionCheckForUpdates" >
<property name="text" >
<string>Check for Updates...</string>
</property>
</action>
</widget>
<resources>
<include location="ostinato.qrc" />

View File

@ -140,6 +140,11 @@ QVariant NdpStatusModel::data(const QModelIndex &index, int role) const
return QVariant();
}
Qt::DropActions NdpStatusModel::supportedDropActions() const
{
return Qt::IgnoreAction; // read-only model, doesn't accept any data
}
void NdpStatusModel::setDeviceIndex(Port *port, int deviceIndex)
{
beginResetModel();

View File

@ -40,6 +40,8 @@ public:
int role = Qt::DisplayRole) const;
QVariant data(const QModelIndex &index, int role) const;
Qt::DropActions supportedDropActions() const;
void setDeviceIndex(Port *port, int deviceIndex);
public slots:

View File

@ -3,7 +3,7 @@ CONFIG += qt ver_info
macx: TARGET = Ostinato
win32:RC_FILE = ostinato.rc
macx:ICON = icons/logo.icns
QT += widgets network script xml
QT += widgets network script xml svg
INCLUDEPATH += "../rpc/" "../common/"
win32 {
QMAKE_LFLAGS += -static
@ -35,6 +35,7 @@ LIBS += -L"../extra/qhexedit2/$(OBJECTS_DIR)/" -lqhexedit2
RESOURCES += ostinato.qrc
HEADERS += \
arpstatusmodel.h \
clipboardhelper.h \
devicegroupdialog.h \
devicegroupmodel.h \
devicemodel.h \
@ -64,7 +65,8 @@ HEADERS += \
streamstatsfiltermodel.h \
streamstatsmodel.h \
streamstatswindow.h \
variablefieldswidget.h
variablefieldswidget.h \
xtableview.h
FORMS += \
about.ui \
@ -83,6 +85,7 @@ FORMS += \
SOURCES += \
arpstatusmodel.cpp \
clipboardhelper.cpp \
devicegroupdialog.cpp \
devicegroupmodel.cpp \
devicemodel.cpp \

View File

@ -2,6 +2,8 @@
<qresource prefix="/">
<file>icons/about.png</file>
<file>icons/add.png</file>
<file>icons/anime_error.gif</file>
<file>icons/anime_warn.gif</file>
<file>icons/arrow_down.png</file>
<file>icons/arrow_left.png</file>
<file>icons/arrow_right.png</file>
@ -12,12 +14,16 @@
<file>icons/bullet_red.png</file>
<file>icons/bullet_white.png</file>
<file>icons/bullet_yellow.png</file>
<file>icons/copy.png</file>
<file>icons/control_play.png</file>
<file>icons/control_stop.png</file>
<file>icons/cut.png</file>
<file>icons/delete.png</file>
<file>icons/devicegroup_add.png</file>
<file>icons/devicegroup_delete.png</file>
<file>icons/devicegroup_edit.png</file>
<file>icons/donate.png</file>
<file>icons/error.svg</file>
<file>icons/exit.png</file>
<file>icons/frag_capture.png</file>
<file>icons/frag_exclusive.png</file>
@ -32,6 +38,7 @@
<file>icons/name.png</file>
<file>icons/neighbor_clear.png</file>
<file>icons/neighbor_resolve.png</file>
<file>icons/paste.png</file>
<file>icons/portgroup_add.png</file>
<file>icons/portgroup_connect.png</file>
<file>icons/portgroup_delete.png</file>
@ -50,5 +57,6 @@
<file>icons/stream_edit.png</file>
<file>icons/stream_stats.png</file>
<file>icons/transmit_on.png</file>
<file>icons/warn.svg</file>
</qresource>
</RCC>

View File

@ -243,3 +243,9 @@ QVariant PacketModel::data(const QModelIndex &index, int role) const
return QVariant();
}
Qt::DropActions PacketModel::supportedDropActions() const
{
return Qt::IgnoreAction; // read-only model, doesn't accept any data
}

View File

@ -42,6 +42,7 @@ public:
QModelIndex index (int row, int col, const QModelIndex & parent = QModelIndex() ) const;
QModelIndex parent(const QModelIndex &index) const;
Qt::DropActions supportedDropActions() const;
private:
typedef union _IndexId
{

View File

@ -21,9 +21,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include <unistd.h>
extern char *version;
extern char *revision;
Params::Params()
{
localDrone_ = true;
logsDisabled_ = true;
}
int Params::parseCommandLine(int argc, char* argv[])
@ -31,14 +35,22 @@ int Params::parseCommandLine(int argc, char* argv[])
int c, n = 0;
opterr = 0;
while ((c = getopt (argc, argv, "c")) != -1) {
while ((c = getopt (argc, argv, "cdhv")) != -1) {
switch (c)
{
case 'c':
localDrone_ = false;
break;
case 'd':
logsDisabled_ = false;
break;
case 'v':
qDebug("Ostinato %s rev %s\n", version, revision);
exit(0);
case 'h':
default:
qDebug("ignoring unrecognized option (%c)", c);
qDebug("usage: %s [-cdhv]\n", argv[0]);
exit(1);
}
n++;
}
@ -54,6 +66,11 @@ bool Params::optLocalDrone()
return localDrone_;
}
bool Params::optLogsDisabled()
{
return logsDisabled_;
}
int Params::argumentCount()
{
return args_.size();

View File

@ -28,12 +28,14 @@ public:
int parseCommandLine(int argc, char* argv[]);
bool optLocalDrone();
bool optLogsDisabled();
int argumentCount();
QString argument(int index);
private:
bool localDrone_;
bool logsDisabled_;
QStringList args_;
};

View File

@ -28,6 +28,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include <QVariant>
#include <google/protobuf/descriptor.h>
#include <vector>
#include <cmath>
extern QMainWindow *mainWindow;
@ -98,7 +99,7 @@ void Port::updateStreamOrdinalsFromIndex()
void Port::reorderStreamsByOrdinals()
{
qSort(mStreams.begin(), mStreams.end(), StreamBase::StreamLessThan);
std::sort(mStreams.begin(), mStreams.end(), StreamBase::StreamLessThan);
}
void Port::setDirty(bool dirty)
@ -187,6 +188,11 @@ void Port::setAveragePacketRate(double packetsPerSec)
Q_ASSERT(false); // Unreachable!!
}
// if old avgPps is 0, new rate will be calculated as nan (infinity)
// because of divide by 0 (old avgPps) above - fix that
if (std::isnan(rate))
rate = packetsPerSec;
qDebug("cur stream pps = %g", s->averagePacketRate());
s->setAveragePacketRate(rate);
@ -261,6 +267,11 @@ void Port::setAverageBitRate(double bitsPerSec)
Q_ASSERT(false); // Unreachable!!
}
// if old avgBps is 0, new rate will be calculated as nan (infinity)
// because of divide by 0 (old avgBps) above - fix that
if (std::isnan(rate))
rate = bitsPerSec/((s->frameLenAvg()+kEthOverhead)*8);
qDebug("cur stream pps = %g", s->averagePacketRate());
s->setAveragePacketRate(rate);
@ -495,6 +506,11 @@ bool Port::modifiablePortConfig(OstProto::Port &config) const
modCfg.set_user_name(config.user_name());
change = true;
}
if (config.is_tracking_stream_stats() != d.is_tracking_stream_stats()) {
modCfg.set_is_tracking_stream_stats(config.is_tracking_stream_stats());
change = true;
}
if (change) {
modCfg.mutable_port_id()->set_id(id());

View File

@ -87,6 +87,7 @@ void PortConfigDialog::accept()
else
Q_ASSERT(false); // Unreachable!!!
pc.set_user_name(portConfig_.user_name());
switch (reservedBy_) {
case kSelf:
if (!reserveButton->isChecked())

View File

@ -241,7 +241,8 @@ _error_exit:
void PortGroup::on_rpcChannel_disconnected()
{
qDebug("disconnected\n");
qDebug("disconnected %s:%u",
qPrintable(rpcChannel->serverName()), rpcChannel->serverPort());
logError(id(), "PortGroup disconnected");
emit portListAboutToBeChanged(mPortGroupId);
@ -252,6 +253,16 @@ void PortGroup::on_rpcChannel_disconnected()
emit portListChanged(mPortGroupId);
emit portGroupDataChanged(mPortGroupId);
// Disconnected during apply? Restore UI.
if (applyTimer_.isValid()) {
applyTimer_.invalidate();
emit applyFinished();
mainWindow->setEnabled(true);
QApplication::restoreOverrideCursor();
}
isGetStatsPending_ = false;
if (reconnect)
@ -265,11 +276,26 @@ void PortGroup::on_rpcChannel_disconnected()
void PortGroup::on_rpcChannel_error(QAbstractSocket::SocketError socketError)
{
qDebug("%s: error %d", __FUNCTION__, socketError);
qDebug("%s: error %d %s:%u", __FUNCTION__, socketError,
qPrintable(rpcChannel->serverName()), rpcChannel->serverPort());
emit portGroupDataChanged(mPortGroupId);
if (socketError == QAbstractSocket::RemoteHostClosedError)
switch(socketError)
{
case QAbstractSocket::SslInvalidUserDataError: // actually abort()
logWarn(id(), QString("Bad data received from portgroup, "
"aborting connection; "
"who is listening on %1:%2 "
" - is it drone or some other process?")
.arg(rpcChannel->serverName())
.arg(rpcChannel->serverPort()));
// fall-through
case QAbstractSocket::RemoteHostClosedError:
reconnect = false;
break;
default:
break;
}
qDebug("%s: state %d", __FUNCTION__, rpcChannel->state());
if ((rpcChannel->state() == QAbstractSocket::UnconnectedState) && reconnect)
@ -364,8 +390,7 @@ void PortGroup::processPortIdList(PbRpcController *controller)
{
qDebug("%s: rpc failed(%s)", __FUNCTION__,
qPrintable(controller->ErrorString()));
logError(id(), QString("getPortIdList RPC failed: %1")
.arg(controller->ErrorString()));
logError(id(), controller->ErrorString());
goto _error_exit;
}
@ -421,8 +446,7 @@ void PortGroup::processPortConfigList(PbRpcController *controller)
{
qDebug("%s: rpc failed(%s)", __FUNCTION__,
qPrintable(controller->ErrorString()));
logError(id(), QString("getPortConfig RPC failed: %1")
.arg(controller->ErrorString()));
logError(id(), controller->ErrorString());
goto _error_exit;
}
@ -504,6 +528,7 @@ void PortGroup::when_configApply(int portIndex)
{
OstProto::StreamIdList *streamIdList;
OstProto::StreamConfigList *streamConfigList;
OstProto::BuildConfig *buildConfig;
OstProto::Ack *ack;
PbRpcController *controller;
@ -515,14 +540,7 @@ void PortGroup::when_configApply(int portIndex)
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
mainWindow->setDisabled(true);
// FIXME: as currently written this code will make unnecessary RPCs
// even if the request contains no data; the fix will need to take
// care to identify when sync is complete
// NOTE: DeviceGroup RPCs are no longer called unnecessarily;
// Stream RPCs need to be fixed similarly
// Also, drone currently updates its packet list at the end of
// modifyStream() implicitly assuming that will be the last API
// called - this will also need to be fixed
applyTimer_.start();
//
// Update/Sync DeviceGroups
@ -532,12 +550,12 @@ void PortGroup::when_configApply(int portIndex)
bool refreshReqd = false;
qDebug("applying 'deleted deviceGroups' ...");
logInfo(id(), mPorts[portIndex]->id(),
QString("Deleting old DeviceGroups"));
deviceGroupIdList = new OstProto::DeviceGroupIdList;
deviceGroupIdList->mutable_port_id()->set_id(mPorts[portIndex]->id());
mPorts[portIndex]->getDeletedDeviceGroupsSinceLastSync(*deviceGroupIdList);
if (deviceGroupIdList->device_group_id_size()) {
logInfo(id(), mPorts[portIndex]->id(),
QString("Deleting old DeviceGroups"));
ack = new OstProto::Ack;
controller = new PbRpcController(deviceGroupIdList, ack);
serviceStub->deleteDeviceGroup(controller, deviceGroupIdList, ack,
@ -549,12 +567,12 @@ void PortGroup::when_configApply(int portIndex)
delete deviceGroupIdList;
qDebug("applying 'new deviceGroups' ...");
logInfo(id(), mPorts[portIndex]->id(),
QString("Creating new DeviceGroups"));
deviceGroupIdList = new OstProto::DeviceGroupIdList;
deviceGroupIdList->mutable_port_id()->set_id(mPorts[portIndex]->id());
mPorts[portIndex]->getNewDeviceGroupsSinceLastSync(*deviceGroupIdList);
if (deviceGroupIdList->device_group_id_size()) {
logInfo(id(), mPorts[portIndex]->id(),
QString("Creating new DeviceGroups"));
ack = new OstProto::Ack;
controller = new PbRpcController(deviceGroupIdList, ack);
serviceStub->addDeviceGroup(controller, deviceGroupIdList, ack,
@ -566,13 +584,13 @@ void PortGroup::when_configApply(int portIndex)
delete deviceGroupIdList;
qDebug("applying 'modified deviceGroups' ...");
logInfo(id(), mPorts[portIndex]->id(),
QString("Modifying changed DeviceGroups"));
deviceGroupConfigList = new OstProto::DeviceGroupConfigList;
deviceGroupConfigList->mutable_port_id()->set_id(mPorts[portIndex]->id());
mPorts[portIndex]->getModifiedDeviceGroupsSinceLastSync(
*deviceGroupConfigList);
if (deviceGroupConfigList->device_group_size()) {
logInfo(id(), mPorts[portIndex]->id(),
QString("Modifying changed DeviceGroups"));
ack = new OstProto::Ack;
controller = new PbRpcController(deviceGroupConfigList, ack);
serviceStub->modifyDeviceGroup(controller, deviceGroupConfigList, ack,
@ -590,54 +608,117 @@ void PortGroup::when_configApply(int portIndex)
// Update/Sync Streams
//
qDebug("applying 'deleted streams' ...");
logInfo(id(), mPorts[portIndex]->id(), QString("Deleting old Streams"));
streamIdList = new OstProto::StreamIdList;
ack = new OstProto::Ack;
controller = new PbRpcController(streamIdList, ack);
streamIdList->mutable_port_id()->set_id(mPorts[portIndex]->id());
mPorts[portIndex]->getDeletedStreamsSinceLastSync(*streamIdList);
serviceStub->deleteStream(controller, streamIdList, ack,
NewCallback(this, &PortGroup::processDeleteStreamAck, controller));
qDebug("applying 'new streams' ...");
logInfo(id(), mPorts[portIndex]->id(), QString("Creating new Streams"));
streamIdList = new OstProto::StreamIdList;
if (streamIdList->stream_id_size()) {
logInfo(id(), mPorts[portIndex]->id(), QString("Deleting old Streams"));
ack = new OstProto::Ack;
controller = new PbRpcController(streamIdList, ack);
serviceStub->deleteStream(controller, streamIdList, ack,
NewCallback(this, &PortGroup::processDeleteStreamAck, controller));
}
else
delete streamIdList;
qDebug("applying 'new streams' ...");
streamIdList = new OstProto::StreamIdList;
streamIdList->mutable_port_id()->set_id(mPorts[portIndex]->id());
mPorts[portIndex]->getNewStreamsSinceLastSync(*streamIdList);
if (streamIdList->stream_id_size()) {
logInfo(id(), mPorts[portIndex]->id(), QString("Creating new Streams"));
ack = new OstProto::Ack;
controller = new PbRpcController(streamIdList, ack);
serviceStub->addStream(controller, streamIdList, ack,
NewCallback(this, &PortGroup::processAddStreamAck, controller));
}
else
delete streamIdList;
qDebug("applying 'modified streams' ...");
logInfo(id(), mPorts[portIndex]->id(),
QString("Modifying changed Streams"));
streamConfigList = new OstProto::StreamConfigList;
ack = new OstProto::Ack;
controller = new PbRpcController(streamConfigList, ack);
streamConfigList->mutable_port_id()->set_id(mPorts[portIndex]->id());
mPorts[portIndex]->getModifiedStreamsSinceLastSync(*streamConfigList);
if (streamConfigList->stream_size()) {
logInfo(id(), mPorts[portIndex]->id(),
QString("Modifying changed Streams"));
ack = new OstProto::Ack;
controller = new PbRpcController(streamConfigList, ack);
serviceStub->modifyStream(controller, streamConfigList, ack,
NewCallback(this, &PortGroup::processModifyStreamAck,
portIndex, controller));
}
else
delete streamConfigList;
qDebug("resolve neighbors before building ...");
logInfo(id(), mPorts[portIndex]->id(),
QString("Resolving device neighbors"));
OstProto::PortIdList *portIdList = new OstProto::PortIdList;
OstProto::PortId *portId = portIdList->add_port_id();
portId->set_id(mPorts[portIndex]->id());
ack = new OstProto::Ack;
controller = new PbRpcController(portIdList, ack);
serviceStub->resolveDeviceNeighbors(controller, portIdList, ack,
NewCallback(this, &PortGroup::processResolveDeviceNeighborsAck,
controller));
qDebug("finish apply by building ...");
logInfo(id(), mPorts[portIndex]->id(),
QString("Re-building packets"));
buildConfig = new OstProto::BuildConfig;
ack = new OstProto::Ack;
controller = new PbRpcController(buildConfig, ack);
buildConfig->mutable_port_id()->set_id(mPorts[portIndex]->id());
serviceStub->build(controller, buildConfig, ack,
NewCallback(this, &PortGroup::processApplyBuildAck,
portIndex, controller));
}
void PortGroup::processAddDeviceGroupAck(PbRpcController *controller)
{
qDebug("In %s", __FUNCTION__);
OstProto::DeviceGroupIdList *dgidList
= static_cast<OstProto::DeviceGroupIdList*>(controller->request());
OstProto::Ack *ack = static_cast<OstProto::Ack*>(controller->response());
if (controller->Failed())
{
qDebug("%s: rpc failed(%s)", __FUNCTION__,
qPrintable(controller->ErrorString()));
logError(id(), dgidList->port_id().id(), controller->ErrorString());
goto _error_exit;
}
if (ack->status())
logError(id(), dgidList->port_id().id(),
QString::fromStdString(ack->notes()));
_error_exit:
delete controller;
}
void PortGroup::processDeleteDeviceGroupAck(PbRpcController *controller)
{
qDebug("In %s", __FUNCTION__);
OstProto::DeviceGroupIdList *dgidList
= static_cast<OstProto::DeviceGroupIdList*>(controller->request());
OstProto::Ack *ack = static_cast<OstProto::Ack*>(controller->response());
if (controller->Failed())
{
qDebug("%s: rpc failed(%s)", __FUNCTION__,
qPrintable(controller->ErrorString()));
logError(id(), dgidList->port_id().id(), controller->ErrorString());
goto _error_exit;
}
if (ack->status())
logError(id(), dgidList->port_id().id(),
QString::fromStdString(ack->notes()));
_error_exit:
delete controller;
}
@ -645,18 +726,71 @@ void PortGroup::processModifyDeviceGroupAck(int /*portIndex*/,
PbRpcController *controller)
{
qDebug("In %s", __FUNCTION__);
OstProto::DeviceGroupIdList *dgidList
= static_cast<OstProto::DeviceGroupIdList*>(controller->request());
OstProto::Ack *ack = static_cast<OstProto::Ack*>(controller->response());
if (controller->Failed())
{
qDebug("%s: rpc failed(%s)", __FUNCTION__,
qPrintable(controller->ErrorString()));
logError(id(), dgidList->port_id().id(), controller->ErrorString());
goto _error_exit;
}
if (ack->status())
logError(id(), dgidList->port_id().id(),
QString::fromStdString(ack->notes()));
_error_exit:
delete controller;
}
void PortGroup::processAddStreamAck(PbRpcController *controller)
{
qDebug("In %s", __FUNCTION__);
OstProto::StreamIdList *streamIdList = static_cast<OstProto::StreamIdList*>(
controller->request());
OstProto::Ack *ack = static_cast<OstProto::Ack*>(controller->response());
if (controller->Failed())
{
qDebug("%s: rpc failed(%s)", __FUNCTION__,
qPrintable(controller->ErrorString()));
logError(id(), streamIdList->port_id().id(), controller->ErrorString());
goto _error_exit;
}
if (ack->status())
logError(id(), streamIdList->port_id().id(),
QString::fromStdString(ack->notes()));
_error_exit:
delete controller;
}
void PortGroup::processDeleteStreamAck(PbRpcController *controller)
{
qDebug("In %s", __FUNCTION__);
OstProto::StreamIdList *streamIdList = static_cast<OstProto::StreamIdList*>(
controller->request());
OstProto::Ack *ack = static_cast<OstProto::Ack*>(controller->response());
if (controller->Failed())
{
qDebug("%s: rpc failed(%s)", __FUNCTION__,
qPrintable(controller->ErrorString()));
logError(id(), streamIdList->port_id().id(), controller->ErrorString());
goto _error_exit;
}
if (ack->status())
logError(id(), streamIdList->port_id().id(),
QString::fromStdString(ack->notes()));
_error_exit:
delete controller;
}
@ -665,9 +799,51 @@ void PortGroup::processModifyStreamAck(int portIndex,
{
qDebug("In %s", __FUNCTION__);
OstProto::Ack *ack = static_cast<OstProto::Ack*>(controller->response());
if (controller->Failed())
{
qDebug("%s: rpc failed(%s)", __FUNCTION__,
qPrintable(controller->ErrorString()));
logError(id(), mPorts[portIndex]->id(), controller->ErrorString());
goto _error_exit;
}
if (ack->status())
logError(id(), mPorts[portIndex]->id(),
QString::fromStdString(ack->notes()));
_error_exit:
delete controller;
}
void PortGroup::processApplyBuildAck(int portIndex, PbRpcController *controller)
{
qDebug("In %s", __FUNCTION__);
OstProto::Ack *ack = static_cast<OstProto::Ack*>(controller->response());
qDebug("apply completed");
logInfo(id(), mPorts[portIndex]->id(), QString("All port changes applied"));
logInfo(id(), mPorts[portIndex]->id(),
QString("All port changes applied - in %1s")
.arg(applyTimer_.elapsed()/1e3));
applyTimer_.invalidate();
if (controller->Failed())
{
qDebug("%s: rpc failed(%s)", __FUNCTION__,
qPrintable(controller->ErrorString()));
logError(id(), mPorts[portIndex]->id(), controller->ErrorString());
goto _error_exit;
}
if (ack->status())
logError(id(), mPorts[portIndex]->id(),
QString::fromStdString(ack->notes()));
_error_exit:
mPorts[portIndex]->when_syncComplete();
emit applyFinished();
mainWindow->setEnabled(true);
QApplication::restoreOverrideCursor();
@ -716,9 +892,7 @@ void PortGroup::processDeviceList(int portIndex, PbRpcController *controller)
{
qDebug("%s: rpc failed(%s)", __FUNCTION__,
qPrintable(controller->ErrorString()));
logError(id(), mPorts[portIndex]->id(),
QString("getDeviceList RPC failed: %1")
.arg(controller->ErrorString()));
logError(id(), mPorts[portIndex]->id(), controller->ErrorString());
goto _exit;
}
@ -753,9 +927,7 @@ void PortGroup::processDeviceNeighbors(
{
qDebug("%s: rpc failed(%s)", __FUNCTION__,
qPrintable(controller->ErrorString()));
logError(id(), mPorts[portIndex]->id(),
QString("getPortIdList RPC failed: %1")
.arg(controller->ErrorString()));
logError(id(), mPorts[portIndex]->id(),controller->ErrorString());
goto _exit;
}
@ -792,16 +964,28 @@ void PortGroup::modifyPort(int portIndex, OstProto::Port portConfig)
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
mainWindow->setDisabled(true);
logInfo(id(), mPorts[portIndex]->id(),
QString("Modifying port configuration"));
OstProto::Port *port = portConfigList->add_port();
port->CopyFrom(portConfig);
port->mutable_port_id()->set_id(mPorts[portIndex]->id());
PbRpcController *controller = new PbRpcController(portConfigList, ack);
serviceStub->modifyPort(controller, portConfigList, ack,
NewCallback(this, &PortGroup::processModifyPortAck, true, controller));
NewCallback(this, &PortGroup::processModifyPortAck, controller));
logInfo(id(), mPorts[portIndex]->id(),
QString("Re-building packets"));
OstProto::BuildConfig *buildConfig = new OstProto::BuildConfig;
ack = new OstProto::Ack;
controller = new PbRpcController(buildConfig, ack);
buildConfig->mutable_port_id()->set_id(mPorts[portIndex]->id());
serviceStub->build(controller, buildConfig, ack,
NewCallback(this, &PortGroup::processModifyPortBuildAck,
true, controller));
}
void PortGroup::processModifyPortAck(bool restoreUi,PbRpcController *controller)
void PortGroup::processModifyPortAck(PbRpcController *controller)
{
qDebug("In %s", __FUNCTION__);
@ -809,10 +993,31 @@ void PortGroup::processModifyPortAck(bool restoreUi,PbRpcController *controller)
{
qDebug("%s: rpc failed(%s)", __FUNCTION__,
qPrintable(controller->ErrorString()));
logError(id(), QString("modifyPort RPC failed: %1")
.arg(controller->ErrorString()));
logError(id(), controller->ErrorString());
}
OstProto::Ack *ack = static_cast<OstProto::Ack*>(controller->response());
if (ack->status())
logError(id(), QString::fromStdString(ack->notes()));
delete controller;
}
void PortGroup::processModifyPortBuildAck(bool restoreUi, PbRpcController *controller)
{
qDebug("In %s", __FUNCTION__);
if (controller->Failed())
{
qDebug("%s: rpc failed(%s)", __FUNCTION__,
qPrintable(controller->ErrorString()));
logError(id(), controller->ErrorString());
}
OstProto::Ack *ack = static_cast<OstProto::Ack*>(controller->response());
if (ack->status())
logError(id(), QString::fromStdString(ack->notes()));
if (restoreUi) {
mainWindow->setEnabled(true);
QApplication::restoreOverrideCursor();
@ -831,8 +1036,7 @@ void PortGroup::processUpdatedPortConfig(PbRpcController *controller)
{
qDebug("%s: rpc failed(%s)", __FUNCTION__,
qPrintable(controller->ErrorString()));
logError(id(), QString("getPortConfig RPC failed: %1")
.arg(controller->ErrorString()));
logError(id(), controller->ErrorString());
goto _exit;
}
@ -881,9 +1085,7 @@ void PortGroup::processStreamIdList(int portIndex, PbRpcController *controller)
{
qDebug("%s: rpc failed(%s)", __FUNCTION__,
qPrintable(controller->ErrorString()));
logError(id(), mPorts[portIndex]->id(),
QString("geStreamIdList RPC failed: %1")
.arg(controller->ErrorString()));
logError(id(), mPorts[portIndex]->id(), controller->ErrorString());
goto _exit;
}
@ -948,7 +1150,7 @@ void PortGroup::processStreamIdList(int portIndex, PbRpcController *controller)
serviceStub->modifyPort(controller, portConfigList, ack,
NewCallback(this, &PortGroup::processModifyPortAck,
false, controller));
controller));
}
// add/modify deviceGroups
@ -1015,6 +1217,15 @@ void PortGroup::processStreamIdList(int portIndex, PbRpcController *controller)
portIndex, controller));
}
// build packets using the new config
OstProto::BuildConfig *buildConfig = new OstProto::BuildConfig;
OstProto::Ack *ack = new OstProto::Ack;
controller = new PbRpcController(buildConfig, ack);
buildConfig->mutable_port_id()->set_id(mPorts[portIndex]->id());
serviceStub->build(controller, buildConfig, ack,
NewCallback(this, &PortGroup::processModifyPortBuildAck,
false, controller));
// delete newPortConfig
atConnectPortConfig_[portIndex] = NULL;
@ -1098,9 +1309,7 @@ void PortGroup::processStreamConfigList(int portIndex,
{
qDebug("%s: rpc failed(%s)", __FUNCTION__,
qPrintable(controller->ErrorString()));
logError(id(), mPorts[portIndex]->id(),
QString("getStreamConfigList RPC failed: %1")
.arg(controller->ErrorString()));
logError(id(), mPorts[portIndex]->id(), controller->ErrorString());
goto _exit;
}
@ -1171,9 +1380,7 @@ void PortGroup::processDeviceGroupIdList(
{
qDebug("%s: rpc failed(%s)", __FUNCTION__,
qPrintable(controller->ErrorString()));
logError(id(), mPorts[portIndex]->id(),
QString("getDeviceGroupIdList RPC failed: %1")
.arg(controller->ErrorString()));
logError(id(), mPorts[portIndex]->id(), controller->ErrorString());
goto _exit;
}
@ -1228,8 +1435,11 @@ void PortGroup::getDeviceGroupConfigList(int portIndex)
using OstProto::DeviceGroupIdList;
using OstProto::DeviceGroupConfigList;
if (mPorts[portIndex]->numDeviceGroups() == 0)
if (mPorts[portIndex]->numDeviceGroups() == 0) {
// No devGrps but we may still have devices (hostDev)
getDeviceInfo(portIndex);
return;
}
qDebug("requesting device group config list (port %d) ...", portIndex);
@ -1268,9 +1478,7 @@ void PortGroup::processDeviceGroupConfigList(int portIndex,
{
qDebug("%s: rpc failed(%s)", __FUNCTION__,
qPrintable(controller->ErrorString()));
logError(id(), mPorts[portIndex]->id(),
QString("getDeviceGroupConfigList RPC failed: %1")
.arg(controller->ErrorString()));
logError(id(), mPorts[portIndex]->id(), controller->ErrorString());
goto _exit;
}
@ -1291,7 +1499,6 @@ void PortGroup::processDeviceGroupConfigList(int portIndex,
devGrpCfgList->mutable_device_group(i));
}
if (devGrpCfgList->device_group_size())
getDeviceInfo(portIndex);
#if 0
@ -1339,6 +1546,19 @@ void PortGroup::processStartTxAck(PbRpcController *controller)
{
qDebug("In %s", __FUNCTION__);
OstProto::Ack *ack = static_cast<OstProto::Ack*>(controller->response());
if (controller->Failed())
{
qDebug("%s: rpc failed(%s)", __FUNCTION__,
qPrintable(controller->ErrorString()));
logError(id(), controller->ErrorString());
goto _exit;
}
if (ack->status())
logError(id(), QString::fromStdString(ack->notes()));
_exit:
delete controller;
}
@ -1374,6 +1594,19 @@ void PortGroup::processStopTxAck(PbRpcController *controller)
{
qDebug("In %s", __FUNCTION__);
OstProto::Ack *ack = static_cast<OstProto::Ack*>(controller->response());
if (controller->Failed())
{
qDebug("%s: rpc failed(%s)", __FUNCTION__,
qPrintable(controller->ErrorString()));
logError(id(), controller->ErrorString());
goto _exit;
}
if (ack->status())
logError(id(), QString::fromStdString(ack->notes()));
_exit:
delete controller;
}
@ -1409,6 +1642,17 @@ void PortGroup::processStartCaptureAck(PbRpcController *controller)
{
qDebug("In %s", __FUNCTION__);
OstProto::Ack *ack = static_cast<OstProto::Ack*>(controller->response());
if (controller->Failed())
{
qDebug("%s: rpc failed(%s)", __FUNCTION__,
qPrintable(controller->ErrorString()));
logError(id(), controller->ErrorString());
goto _exit;
}
if (ack->status())
logError(id(), QString::fromStdString(ack->notes()));
_exit:
delete controller;
}
@ -1444,6 +1688,17 @@ void PortGroup::processStopCaptureAck(PbRpcController *controller)
{
qDebug("In %s", __FUNCTION__);
OstProto::Ack *ack = static_cast<OstProto::Ack*>(controller->response());
if (controller->Failed())
{
qDebug("%s: rpc failed(%s)", __FUNCTION__,
qPrintable(controller->ErrorString()));
logError(id(), controller->ErrorString());
goto _exit;
}
if (ack->status())
logError(id(), QString::fromStdString(ack->notes()));
_exit:
delete controller;
}
@ -1480,6 +1735,8 @@ _exit:
void PortGroup::processViewCaptureAck(PbRpcController *controller)
{
OstProto::PortId *portId = static_cast<OstProto::PortId*>(
controller->request());
QFile *capFile = static_cast<QFile*>(controller->binaryBlob());
QString viewer = appSettings->value(kWiresharkPathKey,
@ -1487,6 +1744,14 @@ void PortGroup::processViewCaptureAck(PbRpcController *controller)
qDebug("In %s", __FUNCTION__);
if (controller->Failed())
{
qDebug("%s: rpc failed(%s)", __FUNCTION__,
qPrintable(controller->ErrorString()));
logError(id(), portId->id(), controller->ErrorString());
goto _exit;
}
capFile->flush();
capFile->close();
@ -1540,7 +1805,19 @@ _exit:
void PortGroup::processResolveDeviceNeighborsAck(PbRpcController *controller)
{
qDebug("In %s", __FUNCTION__);
OstProto::Ack *ack = static_cast<OstProto::Ack*>(controller->response());
if (controller->Failed())
{
qDebug("%s: rpc failed(%s)", __FUNCTION__,
qPrintable(controller->ErrorString()));
logError(id(), controller->ErrorString());
goto _exit;
}
if (ack->status())
logError(id(), QString::fromStdString(ack->notes()));
_exit:
delete controller;
}
@ -1576,7 +1853,19 @@ _exit:
void PortGroup::processClearDeviceNeighborsAck(PbRpcController *controller)
{
qDebug("In %s", __FUNCTION__);
OstProto::Ack *ack = static_cast<OstProto::Ack*>(controller->response());
if (controller->Failed())
{
qDebug("%s: rpc failed(%s)", __FUNCTION__,
qPrintable(controller->ErrorString()));
logError(id(), controller->ErrorString());
goto _exit;
}
if (ack->status())
logError(id(), QString::fromStdString(ack->notes()));
_exit:
delete controller;
}
@ -1612,8 +1901,7 @@ void PortGroup::processPortStatsList()
{
qDebug("%s: rpc failed(%s)", __FUNCTION__,
qPrintable(statsController->ErrorString()));
logError(id(), QString("getPortStatsList RPC failed: %1")
.arg(statsController->ErrorString()));
logError(id(), statsController->ErrorString());
goto _error_exit;
}
@ -1664,10 +1952,22 @@ _exit:
void PortGroup::processClearPortStatsAck(PbRpcController *controller)
{
qDebug("In %s", __FUNCTION__);
OstProto::Ack *ack = static_cast<OstProto::Ack*>(controller->response());
// Refresh stats immediately after a stats clear/reset
getPortStats();
if (controller->Failed())
{
qDebug("%s: rpc failed(%s)", __FUNCTION__,
qPrintable(controller->ErrorString()));
logError(id(), controller->ErrorString());
goto _exit;
}
if (ack->status())
logError(id(), QString::fromStdString(ack->notes()));
_exit:
delete controller;
}
@ -1700,6 +2000,17 @@ void PortGroup::processClearStreamStatsAck(PbRpcController *controller)
{
qDebug("In %s", __FUNCTION__);
OstProto::Ack *ack = static_cast<OstProto::Ack*>(controller->response());
if (controller->Failed())
{
qDebug("%s: rpc failed(%s)", __FUNCTION__,
qPrintable(controller->ErrorString()));
logError(id(), controller->ErrorString());
goto _exit;
}
if (ack->status())
logError(id(), QString::fromStdString(ack->notes()));
_exit:
delete controller;
}
@ -1717,9 +2028,12 @@ bool PortGroup::getStreamStats(QList<uint> *portList)
if (portList == NULL)
guidList->mutable_port_id_list()->CopyFrom(*portIdList_);
else
for (int i = 0; i < portList->size(); i++)
for (int i = 0; i < portList->size(); i++) {
guidList->mutable_port_id_list()->add_port_id()
->set_id(portList->at(i));
if (mPorts.at(i)->isTransmitting())
logWarn(id(), i, "Port is still transmitting - stream stats may be unavailable or incomplete");
}
serviceStub->getStreamStats(controller, guidList, statsList,
NewCallback(this, &PortGroup::processStreamStatsList, controller));

View File

@ -21,6 +21,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#define _PORT_GROUP_H
#include "port.h"
#include <QElapsedTimer>
#include <QHostAddress>
#include <QTcpSocket>
@ -62,6 +63,7 @@ private:
PbRpcChannel *rpcChannel;
PbRpcController *statsController;
bool isGetStatsPending_;
QElapsedTimer applyTimer_;
OstProto::OstService::Stub *serviceStub;
@ -118,6 +120,7 @@ public:
void processAddStreamAck(PbRpcController *controller);
void processDeleteStreamAck(PbRpcController *controller);
void processModifyStreamAck(int portIndex, PbRpcController *controller);
void processApplyBuildAck(int portIndex, PbRpcController *controller);
void processAddDeviceGroupAck(PbRpcController *controller);
void processDeleteDeviceGroupAck(PbRpcController *controller);
@ -127,7 +130,8 @@ public:
void processDeviceNeighbors(int portIndex, PbRpcController *controller);
void modifyPort(int portId, OstProto::Port portConfig);
void processModifyPortAck(bool restoreUi, PbRpcController *controller);
void processModifyPortAck(PbRpcController *controller);
void processModifyPortBuildAck(bool restoreUi, PbRpcController *controller);
void processUpdatedPortConfig(PbRpcController *controller);
void getStreamIdList();
@ -171,6 +175,7 @@ public:
void processStreamStatsList(PbRpcController *controller);
signals:
void applyFinished();
void portGroupDataChanged(int portGroupId, int portId = 0xFFFF);
void portListAboutToBeChanged(quint32 portGroupId);
void portListChanged(quint32 portGroupId);

View File

@ -43,12 +43,6 @@ PortGroupList::PortGroupList()
deviceGroupModelTester_ = new ModelTest(getDeviceGroupModel());
deviceModelTester_ = new ModelTest(getDeviceModel());
#endif
// Add the "Local" Port Group
if (appParams.optLocalDrone()) {
PortGroup *pg = new PortGroup;
addPortGroup(*pg);
}
}
PortGroupList::~PortGroupList()

View File

@ -200,6 +200,11 @@ QVariant PortModel::headerData(int /*section*/, Qt::Orientation orientation, int
return QString("Name");
}
Qt::DropActions PortModel::supportedDropActions() const
{
return Qt::IgnoreAction; // read-only model, doesn't accept any data
}
QModelIndex PortModel::index (int row, int col,
const QModelIndex & parent) const
{

View File

@ -45,6 +45,8 @@ public:
const QModelIndex &parent = QModelIndex()) const;
QModelIndex parent(const QModelIndex &index) const;
Qt::DropActions supportedDropActions() const;
bool isPortGroup(const QModelIndex& index);
bool isPort(const QModelIndex& index);
quint32 portGroupId(const QModelIndex& index);

View File

@ -87,7 +87,7 @@ void PortStatsFilterDialog::on_tbSelectIn_clicked()
foreach(QModelIndex idx, lvUnselected->selectionModel()->selectedIndexes())
rows.append(idx.row());
qSort(rows.begin(), rows.end(), qGreater<int>());
std::sort(rows.begin(), rows.end(), qGreater<int>());
QModelIndex idx = lvSelected->selectionModel()->currentIndex();
int insertAt = idx.isValid() ? idx.row() : mSelected.rowCount();
@ -105,7 +105,7 @@ void PortStatsFilterDialog::on_tbSelectOut_clicked()
foreach(QModelIndex idx, lvSelected->selectionModel()->selectedIndexes())
rows.append(idx.row());
qSort(rows.begin(), rows.end(), qGreater<int>());
std::sort(rows.begin(), rows.end(), qGreater<int>());
foreach(int row, rows)
{

View File

@ -297,6 +297,11 @@ QVariant PortStatsModel::headerData(int section, Qt::Orientation orientation, in
return PortStatName.at(section);
}
Qt::DropActions PortStatsModel::supportedDropActions() const
{
return Qt::IgnoreAction; // read-only model, doesn't accept any data
}
void PortStatsModel::portListFromIndex(QModelIndexList indices,
QList<PortGroupAndPortList> &portList)
{

View File

@ -123,6 +123,8 @@ class PortStatsModel : public QAbstractTableModel
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const;
Qt::DropActions supportedDropActions() const;
class PortGroupAndPortList {
public:
uint portGroupId;

View File

@ -29,6 +29,7 @@ public:
PortStatsProxyModel(QObject *parent = 0)
: QSortFilterProxyModel(parent)
{
setFilterRegExp(QRegExp(".*"));
}
protected:

View File

@ -89,6 +89,12 @@ PortStatsWindow::~PortStatsWindow()
/* ------------- SLOTS (public) -------------- */
void PortStatsWindow::clearCurrentSelection()
{
tvPortStats->selectionModel()->clearCurrentIndex();
tvPortStats->clearSelection();
}
void PortStatsWindow::showMyReservedPortsOnly(bool enabled)
{
if (!proxyStatsModel)
@ -225,6 +231,13 @@ void PortStatsWindow::on_tbResolveNeighbors_clicked()
{
pgl->portGroupByIndex(portList.at(i).portGroupId).
resolveDeviceNeighbors(&portList[i].portList);
// Update device info for the just processed portgroup
for (int j = 0; j < portList[i].portList.size(); j++)
{
pgl->portGroupByIndex(portList.at(i).portGroupId).
getDeviceInfo(portList[i].portList[j]);
}
}
}
@ -240,6 +253,13 @@ void PortStatsWindow::on_tbClearNeighbors_clicked()
{
pgl->portGroupByIndex(portList.at(i).portGroupId).
clearDeviceNeighbors(&portList[i].portList);
// Update device info for the just processed portgroup
for (int j = 0; j < portList[i].portList.size(); j++)
{
pgl->portGroupByIndex(portList.at(i).portGroupId).
getDeviceInfo(portList[i].portList[j]);
}
}
}
@ -275,6 +295,11 @@ void PortStatsWindow::on_tbClearAll_clicked()
}
}
if (proxyStatsModel) {
for(QModelIndex &index : shownColumns)
index = proxyStatsModel->mapToSource(index);
}
// Get ports corresponding to the shown columns
model->portListFromIndex(shownColumns, portList);
@ -306,9 +331,24 @@ void PortStatsWindow::on_tbGetStreamStats_clicked()
QDockWidget *statsDock = mainWindow->findChild<QDockWidget*>(
"statsDock");
mainWindow->addDockWidget(Qt::BottomDockWidgetArea, dock);
// Add stream stats tab to the immediate right of port-stats ...
mainWindow->tabifyDockWidget(statsDock, dock);
mainWindow->splitDockWidget(statsDock, dock, Qt::Horizontal);
// ... make it the currently visible tab ...
dock->show();
dock->raise();
// ... and set tab remove behaviour
// XXX: unfortunately, there's no direct way to get the TabBar
QList<QTabBar*> tabBars = mainWindow->findChildren<QTabBar*>();
foreach(QTabBar* tabBar, tabBars) {
if (tabBar->tabText(tabBar->currentIndex())
== dock->widget()->windowTitle())
tabBar->setSelectionBehaviorOnRemove(
QTabBar::SelectPreviousTab);
}
}
// Get stream stats for selected ports, portgroup by portgroup

View File

@ -38,6 +38,7 @@ public:
~PortStatsWindow();
public slots:
void clearCurrentSelection();
void showMyReservedPortsOnly(bool enabled);
private slots:

View File

@ -279,7 +279,7 @@
</widget>
</item>
<item>
<widget class="QTableView" name="tvPortStats">
<widget class="XTableView" name="tvPortStats">
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectColumns</enum>
</property>
@ -287,6 +287,14 @@
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>XTableView</class>
<extends>QTableView</extends>
<header>xtableview.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="ostinato.qrc"/>
</resources>

View File

@ -19,6 +19,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "portswindow.h"
#include "applymsg.h"
#include "clipboardhelper.h"
#include "deviceswidget.h"
#include "portconfigdialog.h"
#include "settings.h"
@ -37,6 +39,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include <QMessageBox>
#include <QSortFilterProxyModel>
extern ClipboardHelper *clipboardHelper;
extern QMainWindow *mainWindow;
PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent)
@ -52,6 +55,7 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent)
plm = pgl;
setupUi(this);
applyMsg_ = new ApplyMessage();
devicesWidget->setPortGroupList(plm);
tvPortList->header()->hide();
@ -76,9 +80,9 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent)
tvStreamList->addAction(actionDuplicate_Stream);
tvStreamList->addAction(actionDelete_Stream);
sep = new QAction(this);
sep->setSeparator(true);
tvStreamList->addAction(sep);
QAction *sep2 = new QAction(this);
sep2->setSeparator(true);
tvStreamList->addAction(sep2);
tvStreamList->addAction(actionOpen_Streams);
tvStreamList->addAction(actionSave_Streams);
@ -95,6 +99,14 @@ PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent)
addAction(sep);
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
@ -173,6 +185,7 @@ PortsWindow::~PortsWindow()
{
delete delegate;
delete proxyPortModel;
delete applyMsg_;
}
int PortsWindow::portGroupCount()
@ -285,6 +298,12 @@ bool PortsWindow::saveSession(
return true;
}
void PortsWindow::clearCurrentSelection()
{
tvPortList->selectionModel()->clearCurrentIndex();
tvPortList->clearSelection();
}
void PortsWindow::showMyReservedPortsOnly(bool enabled)
{
if (!proxyPortModel)
@ -377,8 +396,8 @@ void PortsWindow::when_portView_currentChanged(const QModelIndex& currentIndex,
updateApplyHint(plm->port(current).portGroupId(),
plm->port(current).id(), true);
else if (plm->port(current).numStreams())
applyHint->setText("Use the Statistics window to transmit "
"packets");
applyHint->setText("Click <img src=':/icons/control_play'/> "
"to transmit packets");
else
applyHint->setText("");
}
@ -427,6 +446,40 @@ void PortsWindow::when_portModel_reset()
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();
@ -521,6 +574,9 @@ void PortsWindow::updateStreamViewActions()
}
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*/,
@ -530,9 +586,12 @@ void PortsWindow::updateApplyHint(int /*portGroupId*/, int /*portId*/,
applyHint->setText("Configuration has changed - "
"<font color='red'><b>click Apply</b></font> "
"to activate the changes");
else if (tvStreamList->model()->rowCount() > 0)
applyHint->setText("Configuration activated - "
"click <img src=':/icons/control_play'/> "
"to transmit packets");
else
applyHint->setText("Configuration activated. Use the Statistics "
"window to transmit packets");
applyHint->setText("Configuration activated");
}
void PortsWindow::updatePortViewActions(const QModelIndex& currentIndex)
@ -647,6 +706,11 @@ void PortsWindow::on_pbApply_clicked()
goto _exit;
}
disconnect(applyMsg_);
connect(&(plm->portGroup(curPortGroup)), SIGNAL(applyFinished()),
applyMsg_, SLOT(hide()));
applyMsg_->show();
// FIXME(HI): shd this be a signal?
//portGroup.when_configApply(port);
// FIXME(MED): mixing port id and index!!!

View File

@ -25,6 +25,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "ui_portswindow.h"
#include "portgrouplist.h"
class ApplyMessage;
class QAbstractItemDelegate;
class QProgressDialog;
class QSortFilterProxyModel;
@ -61,8 +62,10 @@ private:
QString lastNewPortGroup;
QAbstractItemDelegate *delegate;
QSortFilterProxyModel *proxyPortModel;
ApplyMessage *applyMsg_;
public slots:
void clearCurrentSelection();
void showMyReservedPortsOnly(bool enabled);
private slots:
@ -70,6 +73,8 @@ private slots:
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();

View File

@ -51,13 +51,13 @@
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>&lt;p&gt;&lt;b&gt;Welcome to Ostinato&lt;/b&gt;&lt;/p&gt;
<string>&lt;p&gt;&lt;b&gt;Welcome to Ostinato!&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;The port list on the left contains all the ports on which you can transmit packets.&lt;/p&gt;
&lt;p&gt;Ports belong to a port group. Make sure the Port Group has a &lt;img src=&quot;:/icons/bullet_green.png&quot;/&gt; next to it, then double click the port group to show or hide the ports in the port group.&lt;/p&gt;
&lt;p&gt;To generate packets, you need to create and configure packet streams. A stream is a sequence of one or more packets.&lt;/p&gt;
&lt;p&gt;To create a stream, select the port on which you want to send packets.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;Don't see the port that you want (or any ports at all) inside the port group? &lt;a href=&quot;http://jump.ostinato.org/noports&quot;&gt;Get Help!&lt;/a&gt;&lt;/p&gt;</string>
&lt;p&gt;Don't see the port that you want (or any ports at all) inside the port group? &lt;a href=&quot;https://jump.ostinato.org/noports&quot;&gt;Get Help!&lt;/a&gt;&lt;/p&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
@ -96,7 +96,7 @@
&lt;p&gt;To generate packets, you need to create and configure packet streams. A stream is a sequence of one or more packets.&lt;/p&gt;
&lt;p&gt;To create a stream, select the port on which you want to send packets. &lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;Don't see the port that you want (or any ports at all) inside the port group? &lt;a href=&quot;http://jump.ostinato.org/noports&quot;&gt;Get Help!&lt;/a&gt;&lt;/p&gt;</string>
&lt;p&gt;Don't see the port that you want (or any ports at all) inside the port group? &lt;a href=&quot;https://jump.ostinato.org/noports&quot;&gt;Get Help!&lt;/a&gt;&lt;/p&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
@ -196,6 +196,53 @@
<layout class="QVBoxLayout">
<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="QRadioButton" name="radioButton">
<property name="text">
@ -223,19 +270,6 @@
</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>
</layout>
</item>
<item>

View File

@ -24,6 +24,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include <QFileDialog>
#include <QtGlobal>
#include <QXmlStreamReader>
#if defined(Q_OS_WIN32)
QString kGzipPathDefaultValue;
@ -98,6 +99,31 @@ void Preferences::on_wiresharkPathButton_clicked()
path = QFileDialog::getOpenFileName(0, "Locate Wireshark",
wiresharkPathEdit->text());
#ifdef Q_OS_MAC
// Find executable inside app bundle using Info.plist
if (!path.isEmpty() && path.endsWith(".app")) {
QFile plist(path+"/Contents/Info.plist");
plist.open(QIODevice::ReadOnly);
QXmlStreamReader xml(&plist);
while (!xml.atEnd()) {
xml.readNext();
if (xml.isStartElement()
&& (xml.name() == "key")
&& (xml.readElementText() == "CFBundleExecutable")) {
xml.readNext(); // </key>
xml.readNext(); // <string>
if (xml.isStartElement() && (xml.name() == "string"))
path = path+"/Contents/MacOs/"+xml.readElementText();
break;
}
if (xml.hasError())
qDebug("%lld:%lld Error reading Info.plist: %s",
xml.lineNumber(), xml.columnNumber(),
qPrintable(xml.errorString()));
}
}
#endif
if (!path.isEmpty())
wiresharkPathEdit->setText(path);

View File

@ -38,7 +38,7 @@
<item row="0" column="1" >
<widget class="QLineEdit" name="wiresharkPathEdit" >
<property name="enabled" >
<bool>false</bool>
<bool>true</bool>
</property>
</widget>
</item>
@ -62,7 +62,7 @@
<item row="1" column="1" >
<widget class="QLineEdit" name="tsharkPathEdit" >
<property name="enabled" >
<bool>false</bool>
<bool>true</bool>
</property>
</widget>
</item>
@ -86,7 +86,7 @@
<item row="2" column="1" >
<widget class="QLineEdit" name="gzipPathEdit" >
<property name="enabled" >
<bool>false</bool>
<bool>true</bool>
</property>
</widget>
</item>
@ -110,7 +110,7 @@
<item row="3" column="1" >
<widget class="QLineEdit" name="diffPathEdit" >
<property name="enabled" >
<bool>false</bool>
<bool>true</bool>
</property>
</widget>
</item>
@ -134,7 +134,7 @@
<item row="4" column="1" >
<widget class="QLineEdit" name="awkPathEdit" >
<property name="enabled" >
<bool>false</bool>
<bool>true</bool>
</property>
</widget>
</item>

View File

@ -31,7 +31,7 @@ const QString kWiresharkPathDefaultValue(
"C:/Program Files/Wireshark/wireshark.exe");
#elif defined(Q_OS_MAC)
const QString kWiresharkPathDefaultValue(
"/Applications/Wireshark.app/Contents/Resources/bin/wireshark");
"/Applications/Wireshark.app/Contents/MacOS/Wireshark");
#else
const QString kWiresharkPathDefaultValue("/usr/bin/wireshark");
#endif
@ -82,6 +82,7 @@ extern QString kUserDefaultValue;
//
const QString kApplicationWindowGeometryKey("LastUse/ApplicationWindowGeometry");
const QString kApplicationWindowLayout("LastUse/ApplicationWindowLayout");
const QString kLastUpdateCheck("LastUse/UpdateCheck");
#endif

View File

@ -22,6 +22,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "portgrouplist.h"
#include "qicon.h"
#include <QMimeData>
const QLatin1String kStreamsMimeType("application/vnd.ostinato.streams");
StreamModel::StreamModel(PortGroupList *p, QObject *parent)
: QAbstractTableModel(parent)
{
@ -223,6 +227,77 @@ QVariant StreamModel::headerData(int section, Qt::Orientation orientation, int r
return QVariant();
}
QStringList StreamModel::mimeTypes() const
{
return QStringList() << kStreamsMimeType;
}
QMimeData* StreamModel::mimeData(const QModelIndexList &indexes) const
{
using ::google::protobuf::uint8;
if (indexes.isEmpty())
return nullptr;
// indexes may include multiple columns for a row - but we are only
// interested in rows 'coz we have a single data for all columns
// XXX: use QMap instead of QSet to keep rows in sorted order
QMap<int, int> rows;
foreach(QModelIndex index, indexes)
rows.insert(index.row(), index.row());
OstProto::StreamConfigList streams;
streams.mutable_port_id()->set_id(mCurrentPort->id());
foreach(int row, rows) {
OstProto::Stream *stream = streams.add_stream();
mCurrentPort->streamByIndex(row)->protoDataCopyInto(*stream);
}
QByteArray data;
data.resize(streams.ByteSize());
streams.SerializeWithCachedSizesToArray((uint8*)data.data());
//qDebug("copy %s", streams.DebugString().c_str());
//TODO: copy DebugString as text/plain?
QMimeData *mimeData = new QMimeData();
mimeData->setData(kStreamsMimeType, data);
return mimeData; // XXX: caller is expected to take ownership and free!
}
bool StreamModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
int row, int /*column*/, const QModelIndex &parent)
{
if (!data)
return false;
if (!data->hasFormat(kStreamsMimeType))
return false;
if (action != Qt::CopyAction)
return false;
OstProto::StreamConfigList streamsData;
QByteArray ba(data->data(kStreamsMimeType));
streamsData.ParseFromArray((void*)ba.constData(), ba.size());
//qDebug("paste %s", streamsData.DebugString().c_str());
QList<Stream*> streams;
for (int i = 0; i < streamsData.stream_size(); i++) {
Stream *stream = new Stream;
stream->protoDataCopyFrom(streamsData.stream(i));
streams.append(stream);
}
if ((row < 0) || (row > rowCount(parent)))
row = rowCount(parent);
// Delete rows that we are going to overwrite
if (row < rowCount(parent))
removeRows(row, qMin(rowCount() - row, streams.size()));
return insert(row, streams); // callee will free streams after insert
}
/*!
* Inserts streams before the given row
*

View File

@ -39,12 +39,19 @@ class StreamModel : public QAbstractTableModel
int rowCount(const QModelIndex &parent = QModelIndex()) const;
int columnCount(const QModelIndex &parent = QModelIndex()) const;
Qt::ItemFlags flags(const QModelIndex &index) const;
QVariant data(const QModelIndex &index, int role) const;
bool setData(const QModelIndex &index, const QVariant &value,
int role = Qt::EditRole);
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const;
QStringList mimeTypes() const;
QMimeData* mimeData(const QModelIndexList &indexes) const;
bool dropMimeData(const QMimeData *data, Qt::DropAction action,
int row, int column, const QModelIndex &parent);
bool insert(int row, QList<Stream*> &streams);
bool insertRows (int row, int count,
const QModelIndex & parent = QModelIndex());

View File

@ -156,6 +156,11 @@ QVariant StreamStatsModel::data(const QModelIndex &index, int role) const
return QVariant();
}
Qt::DropActions StreamStatsModel::supportedDropActions() const
{
return Qt::IgnoreAction; // read-only model, doesn't accept any data
}
// --------------------------------------------- //
// Slots
// --------------------------------------------- //
@ -220,7 +225,7 @@ void StreamStatsModel::appendStreamStatsList(
guidList_.append(guid);
}
if (guidList_.size())
if (guidList_.size() && !guidList_.contains(kAggrGuid))
guidList_.append(kAggrGuid);
#if QT_VERSION >= 0x040600

View File

@ -43,6 +43,8 @@ public:
int role = Qt::DisplayRole) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
Qt::DropActions supportedDropActions() const;
public slots:
void clearStats();
void appendStreamStatsList(quint32 portGroupId,

View File

@ -297,7 +297,7 @@ void VariableFieldsWidget::on_type_currentIndexChanged(int index)
bitmask->setInputMask("HH");
bitmask->setText("FF");
valueRange_->setRange(0, 0xFF);
count->setRange(0, 0xFF);
count->setRange(1, 0x100);
step->setRange(0, 0xFF);
break;
case OstProto::VariableField::kCounter16:
@ -305,7 +305,7 @@ void VariableFieldsWidget::on_type_currentIndexChanged(int index)
bitmask->setInputMask("HHHH");
bitmask->setText("FFFF");
valueRange_->setRange(0, 0xFFFF);
count->setRange(0, 0xFFFF);
count->setRange(1, 0x10000);
step->setRange(0, 0xFFFF);
break;
case OstProto::VariableField::kCounter32:
@ -313,7 +313,7 @@ void VariableFieldsWidget::on_type_currentIndexChanged(int index)
bitmask->setInputMask("HHHHHHHH");
bitmask->setText("FFFFFFFF");
valueRange_->setRange(0, 0xFFFFFFFF);
count->setRange(0, 0x7FFFFFFF);
count->setRange(1, 0x7FFFFFFF); // XXX: QSpinBox max limited to int32
step->setRange(0, 0x7FFFFFFF);
break;
default:

View File

@ -22,16 +22,109 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include <QTableView>
#include <QApplication>
#include <QClipboard>
#include <QKeyEvent>
#include <QMimeData>
#include <QPainter>
class XTableView : public QTableView
{
Q_OBJECT
public:
XTableView(QWidget *parent) : QTableView(parent) {}
virtual ~XTableView() {}
void setModel(QAbstractItemModel *model)
{
// This is only a heuristic, but works for us
if (model && model->supportedDropActions() != Qt::IgnoreAction)
_modelAllowsRemove = true;
else
_modelAllowsRemove = false;
QTableView::setModel(model);
}
bool hasSelection() const
{
return !selectionModel()->selectedIndexes().isEmpty();
}
bool canCut() const
{
return _modelAllowsRemove;
}
bool canPaste(const QMimeData *data) const
{
return model()->canDropMimeData(data, Qt::CopyAction,
0, 0, QModelIndex());
}
public slots:
void cut()
{
copy();
foreach(QItemSelectionRange range, selectionModel()->selection())
model()->removeRows(range.top(), range.height());
}
void copy()
{
// Copy selection to clipboard (base class copies only current item)
// Selection, by default, is in the order in which items were selected
// - sort them before copying
QModelIndexList selected = selectionModel()->selectedIndexes();
if (selected.isEmpty())
return;
std::sort(selected.begin(), selected.end());
QMimeData *mimeData = model()->mimeData(selected);
copyPlainText(selected, mimeData);
qApp->clipboard()->setMimeData(mimeData);
qDebug("Copied data in %d format(s) to clipboard",
mimeData->formats().size());
for (int i = 0; i < mimeData->formats().size(); i++) {
qDebug(" %d: %s, %d bytes", i+1,
qPrintable(mimeData->formats().at(i)),
mimeData->data(mimeData->formats().at(i)).size());
}
}
void paste()
{
const QMimeData *mimeData = qApp->clipboard()->mimeData();
if (!mimeData || mimeData->formats().isEmpty())
return;
if (selectionModel()->hasSelection()
&& selectionModel()->selection().size() > 1) {
qWarning("Cannot paste into multiple(%d) selections",
selectionModel()->selection().size());
return;
}
// If no selection, insert at the end
int row, column;
if (selectionModel()->hasSelection()
&& selectionModel()->selection().size() == 1) {
row = selectionModel()->selection().first().top();
column = selectionModel()->selection().first().left();
} else {
row = model()->rowCount();
column = model()->columnCount();
}
if (model()->canDropMimeData(mimeData, Qt::CopyAction,
row, column, QModelIndex()))
model()->dropMimeData(mimeData, Qt::CopyAction,
row, column, QModelIndex());
}
protected:
virtual void paintEvent(QPaintEvent *event)
{
@ -47,28 +140,59 @@ protected:
virtual void keyPressEvent(QKeyEvent *event)
{
// Copy selection to clipboard (base class copies only current item)
if (event->matches(QKeySequence::Copy)
&& selectionBehavior() == SelectRows) {
if (event->matches(QKeySequence::Cut)) {
cut();
} else if (event->matches(QKeySequence::Copy)) {
copy();
} else if (event->matches(QKeySequence::Paste)) {
paste();
} else
QTableView::keyPressEvent(event);
}
private:
void copyPlainText(const QModelIndexList &indexes, QMimeData *mimeData)
{
if (mimeData->hasText())
return;
bool includeHeaders = (selectionBehavior()
!= QAbstractItemView::SelectItems);
QString text;
int lastRow = -1;
QModelIndexList selected = selectionModel()->selectedIndexes();
qSort(selected);
foreach(QModelIndex index, selected) {
if (index.row() != lastRow) {
if (!text.isEmpty())
if (includeHeaders) {
int start = 0, end = model()->columnCount(); // assume SelectRows
if (selectionBehavior() == QAbstractItemView::SelectColumns) {
start = indexes.first().column();
end = indexes.last().column()+1;
}
text.append("\t"); // column header for row number/title
for (int i = start; i < end; i++)
if (indexes.contains(model()->index(indexes.first().row(), i)))
text.append(model()->headerData(i, Qt::Horizontal)
.toString()+"\t");;
text.append("\n");
}
int lastRow = -1;
foreach(QModelIndex index, indexes) {
if (index.row() != lastRow) { // row changed
if (lastRow >= 0)
text.append("\n");
if (includeHeaders)
text.append(model()->headerData(index.row(), Qt::Vertical)
.toString()+"\t");
}
else
text.append("\t");
text.append(model()->data(index).toString());
lastRow = index.row();
}
qApp->clipboard()->setText(text);
}
else
QTableView::keyPressEvent(event);
text.append("\n");
mimeData->setText(text);
}
bool _modelAllowsRemove{false};
};
#endif

View File

@ -24,11 +24,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include <QMouseEvent>
#if 0 // FIXME: Test and remove
#if QT_VERSION >= 0x050000
#error "Do we even need this anymore?"
#endif
class XTreeView : public QTreeView
{
public:
@ -46,9 +41,6 @@ private:
QTreeView::mousePressEvent(event);
}
};
#else
typedef QTreeView XTreeView;
#endif
#endif

View File

@ -596,7 +596,8 @@ int AbstractProtocol::protocolFramePayloadSize(int streamIndex) const
the FrameValue of all the 'frame' fields of the protocol also taking care of
fields which are not an integral number of bytes\n
*/
QByteArray AbstractProtocol::protocolFrameValue(int streamIndex, bool forCksum) const
QByteArray AbstractProtocol::protocolFrameValue(int streamIndex, bool forCksum,
FrameValueAttrib */*attrib*/) const
{
QByteArray proto, field;
uint bits, lastbitpos = 0;
@ -1026,6 +1027,17 @@ out:
return cksum;
}
/*!
Returns true, if the protocol fields are incorrect or may cause
overall packet to be invalid
Error details are put in the optional INOUT param 'errors', if true.
*/
bool AbstractProtocol::hasErrors(QStringList* /*errors*/) const
{
return false;
}
// Stein's binary GCD algo - from wikipedia
quint64 AbstractProtocol::gcd(quint64 u, quint64 v)
{

View File

@ -36,6 +36,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#define BASE_DEC (10)
#define BASE_HEX (16)
struct FrameValueAttrib;
class StreamBase;
class ProtocolListIterator;
@ -147,8 +148,8 @@ public:
const OstProto::VariableField& variableField(int index) const;
OstProto::VariableField* mutableVariableField(int index);
QByteArray protocolFrameValue(int streamIndex = 0,
bool forCksum = false) const;
virtual QByteArray protocolFrameValue(int streamIndex = 0,
bool forCksum = false, FrameValueAttrib *attrib = nullptr) const;
virtual int protocolFrameSize(int streamIndex = 0) const;
int protocolFrameOffset(int streamIndex = 0) const;
int protocolFramePayloadSize(int streamIndex = 0) const;
@ -171,6 +172,8 @@ public:
CksumType cksumType = CksumIp,
CksumScope cksumScope = CksumScopeAllProtocols) const;
virtual bool hasErrors(QStringList *errors = nullptr) const;
static quint64 lcm(quint64 u, quint64 v);
static quint64 gcd(quint64 u, quint64 v);

View File

@ -122,3 +122,12 @@ void PdmlEthProtocol::unknownFieldHandler(QString name, int /*pos*/,
#endif
}
void PdmlEthProtocol::postProtocolHandler(OstProto::Protocol *pbProto,
OstProto::Stream* /*stream*/)
{
OstProto::Mac *mac = pbProto->MutableExtension(OstProto::mac);
mac->set_dst_mac_mode(OstProto::Mac::e_mm_fixed);
mac->set_src_mac_mode(OstProto::Mac::e_mm_fixed);
}

View File

@ -30,6 +30,8 @@ public:
virtual void unknownFieldHandler(QString name, int pos, int size,
const QXmlStreamAttributes &attributes,
OstProto::Protocol *pbProto, OstProto::Stream *stream);
virtual void postProtocolHandler(OstProto::Protocol *pbProto,
OstProto::Stream *stream);
protected:
PdmlEthProtocol();

49
common/framevalueattrib.h Normal file
View File

@ -0,0 +1,49 @@
/*
Copyright (C) 2019 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 _FRAME_VALUE_ATTRIB_H
#define _FRAME_VALUE_ATTRIB_H
#include <QFlags>
struct FrameValueAttrib
{
enum ErrorFlag {
UnresolvedSrcMacError = 0x1,
UnresolvedDstMacError = 0x2,
};
Q_DECLARE_FLAGS(ErrorFlags, ErrorFlag);
ErrorFlags errorFlags{0};
// TODO?: int len;
FrameValueAttrib& operator+=(const FrameValueAttrib& rhs) {
errorFlags |= rhs.errorFlags;
return *this;
}
FrameValueAttrib operator+(const FrameValueAttrib& rhs) {
FrameValueAttrib result = *this;
result += rhs;
return result;
}
};
Q_DECLARE_OPERATORS_FOR_FLAGS(FrameValueAttrib::ErrorFlags)
#endif

View File

@ -404,8 +404,8 @@ QVariant GmpProtocol::fieldData(int index, FieldAttrib attrib,
grpRec["overrideAuxDataLength"] =
rec.is_override_aux_data_length();
grpRec["auxDataLength"] = rec.aux_data_length();
grpRec["auxData"] = QByteArray().append(
QString::fromStdString(rec.aux_data()));
grpRec["auxData"] = QByteArray(rec.aux_data().data(),
rec.aux_data().size());
grpRecords.append(grpRec);
}
@ -434,7 +434,7 @@ QVariant GmpProtocol::fieldData(int index, FieldAttrib attrib,
// group_address => subclass responsibility
// source list => subclass responsibility
rv.append(QString().fromStdString(rec.aux_data()));
rv.append(QByteArray(rec.aux_data().data(), rv[1]*4));
fv.append(rv);
}
@ -467,9 +467,9 @@ QVariant GmpProtocol::fieldData(int index, FieldAttrib attrib,
default:
str.append("UNKNOWN"); break;
}
str.append(QString("; AuxLen: %1").arg(
rec.is_override_aux_data_length() ?
rec.aux_data_length() : rec.aux_data().size()/4));
int auxLen = rec.is_override_aux_data_length() ?
rec.aux_data_length() : rec.aux_data().size()/4;
str.append(QString("; AuxLen: %1").arg(auxLen));
str.append(QString("; Source Count: %1").arg(
rec.is_override_source_count() ?
rec.source_count(): rec.sources_size()));
@ -479,8 +479,8 @@ QVariant GmpProtocol::fieldData(int index, FieldAttrib attrib,
str.append(QString("; XXX"));
str.append(QString("; AuxData: ").append(
QByteArray().append(QString().fromStdString(
rec.aux_data())).toHex()));
QByteArray(rec.aux_data().data(), auxLen*4)
.toHex()));
list.append(str);
}

View File

@ -878,3 +878,27 @@ quint32 Ip4Protocol::protocolFrameCksum(int streamIndex,
return AbstractProtocol::protocolFrameCksum(streamIndex, cksumType);
}
bool Ip4Protocol::hasErrors(QStringList *errors) const
{
bool result = false;
if ((data.dst_ip() == 0)
&& (data.dst_ip_mode() == OstProto::Ip4::e_im_fixed)) {
if (errors)
*errors << QObject::tr("Frames with Destination IP 0.0.0.0 "
"are likely to be dropped");
result = true;
}
if ((data.src_ip() == 0)
&& (data.src_ip_mode() == OstProto::Ip4::e_im_fixed)) {
if (errors)
*errors << QObject::tr("Frames with Source IP 0.0.0.0 "
"may be dropped except for special cases "
"like BOOTP/DHCP");
result = true;
}
return result;
}

View File

@ -91,6 +91,7 @@ public:
virtual quint32 protocolFrameCksum(int streamIndex = 0,
CksumType cksumType = CksumIp) const;
virtual bool hasErrors(QStringList *errors = nullptr) const;
private:
OstProto::Ip4 data;
};

View File

@ -73,7 +73,9 @@ void PdmlIp4Protocol::unknownFieldHandler(QString name, int /*pos*/,
{
OstProto::Ip4 *ip4 = pbProto->MutableExtension(OstProto::ip4);
ip4->set_flags(attributes.value("value").toString().toUInt(&isOk, kBaseHex) >> 5);
int shift = attributes.value("size").toString().toUInt(&isOk) == 2 ?
13 : 5;
ip4->set_flags(attributes.value("value").toString().toUInt(&isOk, kBaseHex) >> shift);
}
}

View File

@ -18,6 +18,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#include "ip6.h"
#include "uint128.h"
#include <QHostAddress>
@ -313,49 +315,34 @@ QVariant Ip6Protocol::fieldData(int index, FieldAttrib attrib,
case ip6_srcAddress:
{
int u, p, q;
quint64 maskHi = 0, maskLo = 0;
quint64 prefixHi, prefixLo;
quint64 hostHi = 0, hostLo = 0;
quint64 srcHi = 0, srcLo = 0;
int u;
UInt128 mask = 0;
UInt128 prefix = 0;
UInt128 host = 0;
UInt128 src(data.src_addr_hi(), data.src_addr_lo());
switch(data.src_addr_mode())
{
case OstProto::Ip6::kFixed:
srcHi = data.src_addr_hi();
srcLo = data.src_addr_lo();
break;
case OstProto::Ip6::kIncHost:
case OstProto::Ip6::kDecHost:
case OstProto::Ip6::kRandomHost:
u = streamIndex % data.src_addr_count();
if (data.src_addr_prefix() > 64) {
p = 64;
q = data.src_addr_prefix() - 64;
} else {
p = data.src_addr_prefix();
q = 0;
}
if (p > 0)
maskHi = ~((quint64(1) << p) - 1);
if (q > 0)
maskLo = ~((quint64(1) << q) - 1);
prefixHi = data.src_addr_hi() & maskHi;
prefixLo = data.src_addr_lo() & maskLo;
mask = ~UInt128(0, 0) << (128 - data.src_addr_prefix());
prefix = src & mask;
if (data.src_addr_mode() == OstProto::Ip6::kIncHost) {
hostHi = ((data.src_addr_hi() & ~maskHi) + u) & ~maskHi;
hostLo = ((data.src_addr_lo() & ~maskLo) + u) & ~maskLo;
host = ((src & ~mask) + u) & ~mask;
}
else if (data.src_addr_mode() == OstProto::Ip6::kDecHost) {
hostHi = ((data.src_addr_hi() & ~maskHi) - u) & ~maskHi;
hostLo = ((data.src_addr_lo() & ~maskLo) - u) & ~maskLo;
host = ((src & ~mask) - u) & ~mask;
}
else if (data.src_addr_mode()==OstProto::Ip6::kRandomHost) {
hostHi = qrand() & ~maskHi;
hostLo = qrand() & ~maskLo;
// XXX: qrand is int (32bit) not 64bit, some stdlib
// implementations have RAND_MAX as low as 0x7FFF
host = UInt128(qrand(), qrand()) & ~mask;
}
srcHi = prefixHi | hostHi;
srcLo = prefixLo | hostLo;
src = prefix | host;
break;
default:
qWarning("Unhandled src_addr_mode = %d",
@ -372,10 +359,9 @@ QVariant Ip6Protocol::fieldData(int index, FieldAttrib attrib,
{
QByteArray fv;
fv.resize(16);
qToBigEndian(srcHi, (uchar*) fv.data());
qToBigEndian(srcLo, (uchar*) (fv.data() + 8));
qToBigEndian(src, (uchar*) fv.data());
if (attrib == FieldTextValue)
return QHostAddress((quint8*)fv.constData()).toString();
return QHostAddress(src.toArray()).toString();
else
return fv;
}
@ -387,49 +373,34 @@ QVariant Ip6Protocol::fieldData(int index, FieldAttrib attrib,
case ip6_dstAddress:
{
int u, p, q;
quint64 maskHi = 0, maskLo = 0;
quint64 prefixHi, prefixLo;
quint64 hostHi = 0, hostLo = 0;
quint64 dstHi = 0, dstLo = 0;
int u;
UInt128 mask = 0;
UInt128 prefix = 0;
UInt128 host = 0;
UInt128 dst(data.dst_addr_hi(), data.dst_addr_lo());
switch(data.dst_addr_mode())
{
case OstProto::Ip6::kFixed:
dstHi = data.dst_addr_hi();
dstLo = data.dst_addr_lo();
break;
case OstProto::Ip6::kIncHost:
case OstProto::Ip6::kDecHost:
case OstProto::Ip6::kRandomHost:
u = streamIndex % data.dst_addr_count();
if (data.dst_addr_prefix() > 64) {
p = 64;
q = data.dst_addr_prefix() - 64;
} else {
p = data.dst_addr_prefix();
q = 0;
}
if (p > 0)
maskHi = ~((quint64(1) << p) - 1);
if (q > 0)
maskLo = ~((quint64(1) << q) - 1);
prefixHi = data.dst_addr_hi() & maskHi;
prefixLo = data.dst_addr_lo() & maskLo;
mask = ~UInt128(0, 0) << (128 - data.dst_addr_prefix());
prefix = dst & mask;
if (data.dst_addr_mode() == OstProto::Ip6::kIncHost) {
hostHi = ((data.dst_addr_hi() & ~maskHi) + u) & ~maskHi;
hostLo = ((data.dst_addr_lo() & ~maskLo) + u) & ~maskLo;
host = ((dst & ~mask) + u) & ~mask;
}
else if (data.dst_addr_mode() == OstProto::Ip6::kDecHost) {
hostHi = ((data.dst_addr_hi() & ~maskHi) - u) & ~maskHi;
hostLo = ((data.dst_addr_lo() & ~maskLo) - u) & ~maskLo;
host = ((dst & ~mask) - u) & ~mask;
}
else if (data.dst_addr_mode()==OstProto::Ip6::kRandomHost) {
hostHi = qrand() & ~maskHi;
hostLo = qrand() & ~maskLo;
// XXX: qrand is int (32bit) not 64bit, some stdlib
// implementations have RAND_MAX as low as 0x7FFF
host = UInt128(qrand(), qrand()) & ~mask;
}
dstHi = prefixHi | hostHi;
dstLo = prefixLo | hostLo;
dst = prefix | host;
break;
default:
qWarning("Unhandled dst_addr_mode = %d",
@ -446,10 +417,9 @@ QVariant Ip6Protocol::fieldData(int index, FieldAttrib attrib,
{
QByteArray fv;
fv.resize(16);
qToBigEndian(dstHi, (uchar*) fv.data());
qToBigEndian(dstLo, (uchar*) (fv.data() + 8));
qToBigEndian(dst, (uchar*) fv.data());
if (attrib == FieldTextValue)
return QHostAddress((quint8*)fv.constData()).toString();
return QHostAddress(dst.toArray()).toString();
else
return fv;
}
@ -797,3 +767,25 @@ quint32 Ip6Protocol::protocolFrameCksum(int streamIndex,
return AbstractProtocol::protocolFrameCksum(streamIndex, cksumType);
}
bool Ip6Protocol::hasErrors(QStringList *errors) const
{
bool result = false;
if ((data.dst_addr_hi() == 0ULL) && (data.dst_addr_lo() == 0ULL)
&& (data.dst_addr_mode() == OstProto::Ip6::kFixed)) {
if (errors)
*errors << QObject::tr("Frames with Destination IP :: (all zeroes) "
"are likely to be dropped");
result = true;
}
if ((data.src_addr_hi() == 0ULL) && (data.src_addr_lo() == 0ULL)
&& (data.src_addr_mode() == OstProto::Ip6::kFixed)) {
if (errors)
*errors << QObject::tr("Frames with Source IP :: (all zeroes) "
"are likely to be dropped");
result = true;
}
return result;
}

View File

@ -104,6 +104,8 @@ public:
virtual quint32 protocolFrameCksum(int streamIndex = 0,
CksumType cksumType = CksumIp) const;
virtual bool hasErrors(QStringList *errors = nullptr) const;
private:
OstProto::Ip6 data;
};

View File

@ -215,7 +215,7 @@ void Ip6ConfigForm::storeWidget(AbstractProtocol *ip6Proto)
srcAddrCount->text());
ip6Proto->setFieldData(
Ip6Protocol::ip6_srcAddrPrefix,
srcAddrPrefix->text());
srcAddrPrefix->text().remove('/'));
ip6Proto->setFieldData(
Ip6Protocol::ip6_dstAddress,
@ -228,6 +228,6 @@ void Ip6ConfigForm::storeWidget(AbstractProtocol *ip6Proto)
dstAddrCount->text());
ip6Proto->setFieldData(
Ip6Protocol::ip6_dstAddrPrefix,
dstAddrPrefix->text());
dstAddrPrefix->text().remove('/'));
}

View File

@ -50,7 +50,7 @@ inline QWidget* IPv4AddressDelegate::createEditor(QWidget *parent,
ipEdit = static_cast<QLineEdit*>(QItemDelegate::createEditor(
parent, option, index));
ipEdit->setInputMask("009.009.009.009;"); // FIXME: use validator
ipEdit->setInputMask("000.000.000.000;"); // FIXME: use validator
return ipEdit;
}

View File

@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "mac.h"
#include "framevalueattrib.h"
#include "../common/streambase.h"
#include <QRegExp>
@ -371,3 +372,52 @@ int MacProtocol::protocolFrameVariableCount() const
return count;
}
QByteArray MacProtocol::protocolFrameValue(int streamIndex, bool /*forCksum*/,
FrameValueAttrib *attrib) const
{
QByteArray ba;
ba.resize(12);
quint64 dstMac = fieldData(mac_dstAddr, FieldValue, streamIndex)
.toULongLong();
quint64 srcMac = fieldData(mac_srcAddr, FieldValue, streamIndex)
.toULongLong();
char *p = ba.data();
*(quint32*)(p ) = qToBigEndian(quint32(dstMac >> 16));
*(quint16*)(p + 4) = qToBigEndian(quint16(dstMac & 0xffff));
*(quint32*)(p + 6) = qToBigEndian(quint32(srcMac >> 16));
*(quint16*)(p + 10) = qToBigEndian(quint16(srcMac & 0xffff));
if (attrib) {
if (!dstMac && data.dst_mac_mode() == OstProto::Mac::e_mm_resolve)
attrib->errorFlags |= FrameValueAttrib::UnresolvedDstMacError;
if (!srcMac && data.src_mac_mode() == OstProto::Mac::e_mm_resolve)
attrib->errorFlags |= FrameValueAttrib::UnresolvedSrcMacError;
}
return ba;
}
bool MacProtocol::hasErrors(QStringList *errors) const
{
bool result = false;
if ((data.dst_mac() == 0ULL)
&& (data.dst_mac_mode() != OstProto::Mac::e_mm_resolve)) {
if (errors)
*errors << QObject::tr("Frames with Destination Mac "
"00:00:00:00:00:00 are likely "
"to be dropped");
result = true;
}
if ((data.src_mac() == 0ULL)
&& (data.src_mac_mode() != OstProto::Mac::e_mm_resolve)) {
if (errors)
*errors << QObject::tr("Frames with Source Mac "
"00:00:00:00:00:00 are likely "
"to be dropped");
result = true;
}
return result;
}

View File

@ -65,6 +65,10 @@ public:
virtual int protocolFrameVariableCount() const;
virtual QByteArray protocolFrameValue(int streamIndex = 0,
bool forCksum = false, FrameValueAttrib *attrib = nullptr) const;
virtual bool hasErrors(QStringList *errors = nullptr) const;
private:
OstProto::Mac data;
mutable bool forResolve_;

View File

@ -33,13 +33,13 @@ message Mac {
// Dst Mac
optional uint64 dst_mac = 1;
optional MacAddrMode dst_mac_mode = 2 [default = e_mm_fixed];
optional MacAddrMode dst_mac_mode = 2 [default = e_mm_resolve];
optional uint32 dst_mac_count = 3 [default = 16];
optional uint32 dst_mac_step = 4 [default = 1];
// Src Mac
optional uint64 src_mac = 5;
optional MacAddrMode src_mac_mode = 6 [default = e_mm_fixed];
optional MacAddrMode src_mac_mode = 6 [default = e_mm_resolve];
optional uint32 src_mac_count = 7 [default = 16];
optional uint32 src_mac_step = 8 [default = 1];
}

View File

@ -15,16 +15,16 @@
</property>
<layout class="QGridLayout">
<item row="0" column="1">
<widget class="QLabel" name="label_6">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Address</string>
<string>Mode</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_2">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Mode</string>
<string>Address</string>
</property>
</widget>
</item>
@ -50,16 +50,6 @@
</widget>
</item>
<item row="1" column="1">
<widget class="MacEdit" name="leDstMac">
<property name="minimumSize">
<size>
<width>120</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QComboBox" name="cmbDstMacMode">
<item>
<property name="text">
@ -83,6 +73,16 @@
</item>
</widget>
</item>
<item row="1" column="2">
<widget class="MacEdit" name="leDstMac">
<property name="minimumSize">
<size>
<width>120</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="IntEdit" name="leDstMacCount">
<property name="enabled">
@ -105,9 +105,6 @@
</widget>
</item>
<item row="2" column="1">
<widget class="MacEdit" name="leSrcMac"/>
</item>
<item row="2" column="2">
<widget class="QComboBox" name="cmbSrcMacMode">
<item>
<property name="text">
@ -131,6 +128,9 @@
</item>
</widget>
</item>
<item row="2" column="2">
<widget class="MacEdit" name="leSrcMac"/>
</item>
<item row="2" column="3">
<widget class="IntEdit" name="leSrcMacCount">
<property name="enabled">

View File

@ -225,9 +225,9 @@ QVariant MldProtocol::fieldData(int index, FieldAttrib attrib,
fv.resize(16);
for (int i = 0; i < data.sources_size(); i++)
{
qToBigEndian(data.sources(i).v6_hi(),
qToBigEndian(quint64(data.sources(i).v6_hi()),
(uchar*)fv.data());
qToBigEndian(data.sources(i).v6_lo(),
qToBigEndian(quint64(data.sources(i).v6_lo()),
(uchar*)fv.data()+8);
list << QHostAddress((quint8*)fv.constData()).toString();
@ -240,9 +240,9 @@ QVariant MldProtocol::fieldData(int index, FieldAttrib attrib,
fv.resize(16 * data.sources_size());
for (int i = 0; i < data.sources_size(); i++)
{
qToBigEndian(data.sources(i).v6_hi(),
qToBigEndian(quint64(data.sources(i).v6_hi()),
(uchar*)(fv.data() + i*16));
qToBigEndian(data.sources(i).v6_lo(),
qToBigEndian(quint64(data.sources(i).v6_lo()),
(uchar*)(fv.data() + i*16 + 8));
}
return fv;
@ -254,9 +254,9 @@ QVariant MldProtocol::fieldData(int index, FieldAttrib attrib,
fv.resize(16);
for (int i = 0; i < data.sources_size(); i++)
{
qToBigEndian(data.sources(i).v6_hi(),
qToBigEndian(quint64(data.sources(i).v6_hi()),
(uchar*)fv.data());
qToBigEndian(data.sources(i).v6_lo(),
qToBigEndian(quint64(data.sources(i).v6_lo()),
(uchar*)fv.data()+8);
list << QHostAddress((quint8*)fv.constData()).toString();
@ -295,9 +295,9 @@ QVariant MldProtocol::fieldData(int index, FieldAttrib attrib,
QStringList sl;
for (int j = 0; j < rec.sources_size(); j++)
{
qToBigEndian(rec.sources(j).v6_hi(),
qToBigEndian(quint64(rec.sources(j).v6_hi()),
(uchar*)(ip.data()));
qToBigEndian(rec.sources(j).v6_lo(),
qToBigEndian(quint64(rec.sources(j).v6_lo()),
(uchar*)(ip.data() + 8));
sl.append(QHostAddress(
(quint8*)ip.constData()).toString());
@ -322,15 +322,15 @@ QVariant MldProtocol::fieldData(int index, FieldAttrib attrib,
QByteArray rv = list.at(i).toByteArray();
rv.insert(4, QByteArray(16+16*rec.sources_size(), char(0)));
qToBigEndian(rec.group_address().v6_hi(),
qToBigEndian(quint64(rec.group_address().v6_hi()),
(uchar*)(rv.data()+4));
qToBigEndian(rec.group_address().v6_lo(),
qToBigEndian(quint64(rec.group_address().v6_lo()),
(uchar*)(rv.data()+4+8));
for (int j = 0; j < rec.sources_size(); j++)
{
qToBigEndian(rec.sources(j).v6_hi(),
qToBigEndian(quint64(rec.sources(j).v6_hi()),
(uchar*)(rv.data()+20+16*j));
qToBigEndian(rec.sources(j).v6_lo(),
qToBigEndian(quint64(rec.sources(j).v6_lo()),
(uchar*)(rv.data()+20+16*j+8));
}
@ -352,9 +352,9 @@ QVariant MldProtocol::fieldData(int index, FieldAttrib attrib,
QString recStr = list.at(i);
QString str;
qToBigEndian(rec.group_address().v6_hi(),
qToBigEndian(quint64(rec.group_address().v6_hi()),
(uchar*)(ip.data()));
qToBigEndian(rec.group_address().v6_lo(),
qToBigEndian(quint64(rec.group_address().v6_lo()),
(uchar*)(ip.data() + 8));
str.append(QString("Group: %1").arg(
QHostAddress((quint8*)ip.constData()).toString()));
@ -363,9 +363,9 @@ QVariant MldProtocol::fieldData(int index, FieldAttrib attrib,
QStringList sl;
for (int j = 0; j < rec.sources_size(); j++)
{
qToBigEndian(rec.sources(j).v6_hi(),
qToBigEndian(quint64(rec.sources(j).v6_hi()),
(uchar*)(ip.data()));
qToBigEndian(rec.sources(j).v6_lo(),
qToBigEndian(quint64(rec.sources(j).v6_lo()),
(uchar*)(ip.data() + 8));
sl.append(QHostAddress(
(quint8*)ip.constData()).toString());

29
common/netdefs.h Normal file
View File

@ -0,0 +1,29 @@
/*
Copyright (C) 2018 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 _NET_DEFS_H
static const quint64 kBcastMac = 0xffffffffffffULL;
static const quint16 kEthTypeIp4 = 0x0800;
static const quint16 kEthTypeArp = 0x0806;
static const quint16 kEthTypeIp6 = 0x86dd;
static const int kIp6HdrLen = 40;
static const quint8 kIpProtoIcmp6 = 58;
#endif

View File

@ -232,24 +232,9 @@ int PayloadProtocol::protocolFrameVariableCount() const
int count = AbstractProtocol::protocolFrameVariableCount();
if (data.pattern_mode() == OstProto::Payload::e_dp_random)
{
switch(mpStream->sendUnit())
{
case OstProto::StreamControl::e_su_packets:
return mpStream->numPackets();
return mpStream->frameCount();
case OstProto::StreamControl::e_su_bursts:
return int(mpStream->numBursts()
* mpStream->burstSize()
* mpStream->burstRate());
}
}
if (mpStream->lenMode() != StreamBase::e_fl_fixed)
{
count = AbstractProtocol::lcm(count,
mpStream->frameLenMax() - mpStream->frameLenMin() + 1);
}
count = AbstractProtocol::lcm(count, mpStream->frameSizeVariableCount());
return count;
}

View File

@ -94,6 +94,7 @@ bool PcapFileFormat::open(const QString fileName,
qint64 byteCount = 0;
qint64 byteTotal;
QByteArray pktBuf;
bool tryConvert = true;
if (!file.open(QIODevice::ReadOnly))
goto _err_open;
@ -144,6 +145,7 @@ bool PcapFileFormat::open(const QString fileName,
fd_.setDevice(&file);
}
_retry:
byteTotal = fd_.device()->size() - sizeof(fileHdr);
emit status("Reading File Header...");
@ -153,7 +155,11 @@ bool PcapFileFormat::open(const QString fileName,
qDebug("magic = %08x", magic);
if (magic == kPcapFileMagicSwapped)
if (magic == kPcapFileMagic)
{
// Do nothing
}
else if (magic == kPcapFileMagicSwapped)
{
// Toggle Byte order
if (fd_.byteOrder() == QDataStream::BigEndian)
@ -161,8 +167,37 @@ bool PcapFileFormat::open(const QString fileName,
else
fd_.setByteOrder(QDataStream::BigEndian);
}
else if (magic != kPcapFileMagic)
else // Not a pcap file
{
if (tryConvert)
{
// Close and reopen the temp file to be safe
file2.close();
if (!file2.open())
{
error.append("Unable to open temporary file to convert to PCAP\n");
goto _err_convert2pcap;
}
fd_.setDevice(0); // disconnect data stream from file
if (convertToStandardPcap(fileName, file2.fileName(), error))
{
fd_.setDevice(&file2);
tryConvert = false;
goto _retry;
}
else
{
error = QString(tr("Unable to convert %1 to standard PCAP format"))
.arg(fileName);
goto _err_convert2pcap;
}
}
else
goto _err_bad_magic;
}
qDebug("reading filehdr");
fd_ >> fileHdr.versionMajor;
fd_ >> fileHdr.versionMinor;
@ -171,6 +206,7 @@ bool PcapFileFormat::open(const QString fileName,
fd_ >> fileHdr.snapLen;
fd_ >> fileHdr.network;
qDebug("version check");
if ((fileHdr.versionMajor != kPcapFileVersionMajor) ||
(fileHdr.versionMinor != kPcapFileVersionMinor))
goto _err_unsupported_version;
@ -183,6 +219,7 @@ bool PcapFileFormat::open(const QString fileName,
pktBuf.resize(fileHdr.snapLen);
qDebug("pdml check");
if (importOptions_.value("ViaPdml").toBool())
{
QProcess tshark;
@ -276,6 +313,7 @@ bool PcapFileFormat::open(const QString fileName,
QStringList()
<< QString("-r%1").arg(fileName)
<< "-otcp.desegment_tcp_streams:FALSE"
<< "-P"
<< "-x");
if (!tshark.waitForStarted(-1))
{
@ -336,6 +374,7 @@ bool PcapFileFormat::open(const QString fileName,
QStringList()
<< QString("-r%1").arg(importedPcapFile.fileName())
<< "-otcp.desegment_tcp_streams:FALSE"
<< "-P"
<< "-x");
if (!tshark.waitForStarted(-1))
{
@ -404,7 +443,7 @@ bool PcapFileFormat::open(const QString fileName,
diffFile.close();
if (diffFile.size())
{
error.append("There is a diff between the original and imported streams. See details for diff.\n\n\n\n");
error.append(tr("<p>There is a diff between the original and imported streams. See details to review the diff.</p><p>Why a diff? See <a href='%1'>possible reasons</a>.</p>\n\n\n\n").arg("https://jump.ostinato.org/pcapdiff"));
diffFile.open();
diffFile.seek(0);
error.append(QString(diffFile.readAll()));
@ -441,7 +480,7 @@ _non_pdml:
stream->mutable_control()->set_num_packets(1);
// setup packet rate to the timing in pcap (as close as possible)
const uint kUsecsInSec = uint(1e6);
const double kUsecsInSec = 1e6;
uint usec = (pktHdr.tsSec*kUsecsInSec + pktHdr.tsUsec);
uint delta = usec - lastUsec;
@ -500,15 +539,53 @@ _err_reading_magic:
error = QString(tr("Unable to read magic from %1")).arg(fileName);
goto _exit;
_err_convert2pcap:
goto _exit;
_err_open:
error = QString(tr("Unable to open file: %1")).arg(fileName);
goto _exit;
_exit:
if (!error.isEmpty())
qDebug("%s", qPrintable(error));
file.close();
return isOk;
}
/*!
Converts a non-PCAP capture file to standard PCAP file format using tshark
Returns true if conversion was successful, false otherwise.
*/
bool PcapFileFormat::convertToStandardPcap(
QString fileName, QString outputFileName, QString &error)
{
qDebug("converting to PCAP %s", qPrintable(outputFileName));
emit status("Unsupported format. Converting to standard PCAP format...");
emit target(0);
QProcess tshark;
tshark.start(OstProtoLib::tsharkPath(),
QStringList()
<< QString("-r%1").arg(fileName)
<< "-Fpcap"
<< QString("-w%1").arg(outputFileName));
if (!tshark.waitForStarted(-1))
{
error.append(QString("Unable to start tshark. Check path in preferences.\n"));
return false;
}
if (!tshark.waitForFinished(-1))
{
error.append(QString("Error running tshark\n"));
return false;
}
return true;
}
/*!
Reads packet meta data into pktHdr and packet content into buf.

View File

@ -75,6 +75,8 @@ private:
quint32 origLen; /* actual length of packet */
} PcapPacketHeader;
bool convertToStandardPcap(QString fileName, QString outputFileName,
QString &error);
bool readPacket(PcapPacketHeader &pktHdr, QByteArray &pktBuf);
QDataStream fd_;

View File

@ -165,6 +165,7 @@ void PdmlProtocol::fieldHandler(QString name,
qPrintable(name),
qPrintable(valueHexStr));
if (!valueHexStr.isEmpty())
knownFieldHandler(name, valueHexStr, pbProto);
}
else

View File

@ -177,7 +177,7 @@ void PdmlFrameProtocol::unknownFieldHandler(QString name, int /*pos*/,
if (decimal >= 0)
{
const uint kNsecsInSec = 1000000000;
const double kNsecsInSec = 1e9;
uint sec = delta.left(decimal).toUInt();
uint nsec = delta.mid(decimal+1).toUInt();
uint ipg = sec*kNsecsInSec + nsec;

View File

@ -175,7 +175,12 @@ message Void {
}
message Ack {
//! \todo (LOW) do we need any fields in 'Ack'
enum RpcStatus {
kRpcSuccess = 0;
kRpcError = 1;
}
required RpcStatus status = 1;
optional string notes = 2;
}
message PortId {
@ -299,6 +304,9 @@ message Notification {
optional PortIdList port_id_list = 6;
}
message BuildConfig {
required PortId port_id = 1;
}
/*
* Protocol Emulation
@ -388,6 +396,8 @@ service OstService {
rpc getStreamStats(StreamGuidList) returns (StreamStatsList);
rpc clearStreamStats(StreamGuidList) returns (Ack);
rpc build(BuildConfig) returns (Ack);
// XXX: Add new RPCs at the end only to preserve backward compatibility
}

View File

@ -252,7 +252,7 @@ void PythonFileFormat::writeStandardImports(QTextStream &out)
<< " revision " << revision << "\n"
<< "# The script should work out of the box mostly,\n"
<< "# but occassionally might need minor tweaking\n"
<< "# Please report any bugs at http://ostinato.org\n";
<< "# Please report any bugs at https://ostinato.org\n";
out << "\n";
out << "# standard modules\n";
out << "import logging\n";
@ -275,9 +275,9 @@ void PythonFileFormat::writePrologue(QTextStream &out)
out << "\n";
out << "# get inputs, if required\n";
out << "while len(host_name) == 0:\n";
out << " host_name = raw_input('Drone\\'s Hostname/IP: ')\n";
out << " host_name = input('Drone\\'s Hostname/IP: ')\n";
out << "while tx_port_number < 0:\n";
out << " tx_port_number = int(raw_input('Tx Port Number: '))\n";
out << " tx_port_number = int(input('Tx Port Number: '))\n";
out << "\n";
out << "drone = DroneProxy(host_name)\n";
out << "\n";

35
common/qtport.h Normal file
View File

@ -0,0 +1,35 @@
/*
Copyright (C) 2018 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 _QT_PORT_H
#define _QT_PORT_H
//
// Make Qt stuff portable across Qt versions
//
#if QT_VERSION < 0x050700
template <typename T>
T qFromBigEndian(const void *src)
{
return qFromBigEndian<T>((const uchar*)src);
}
#endif
#endif

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