Merge branch 'master' into ci-dev
76
.github/CODE_OF_CONDUCT.md
vendored
Normal 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
@ -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.
|
@ -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
|
||||
|
16
README.md
@ -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
|
||||
|
@ -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><a href="http://ostinato.org">http://ostinato.org</a><br><a href="http://twitter.com/ostinato">@ostinato</a></string>
|
||||
<string><a href="https://ostinato.org">ostinato.org</a><br><a href="https://twitter.com/ostinato">@ostinato</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><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.</p><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.</p><p>You should have received a copy of the GNU General Public License along with this program. If not, see <a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a></p></string>
|
||||
<string><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.</p><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.</p><p>You should have received a copy of the GNU General Public License along with this program. If not, see <a href="https://www.gnu.org/licenses/">https://www.gnu.org/licenses/</a></p></string>
|
||||
</property>
|
||||
<property name="textFormat" >
|
||||
<enum>Qt::RichText</enum>
|
||||
|
86
client/applymsg.h
Normal 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
|
||||
|
@ -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();
|
||||
|
@ -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
@ -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
@ -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
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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_;
|
||||
};
|
||||
|
@ -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>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 3.5 KiB |
BIN
client/icons/anime_error.gif
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
client/icons/anime_warn.gif
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
client/icons/copy.png
Normal file
After Width: | Height: | Size: 663 B |
BIN
client/icons/cut.png
Normal file
After Width: | Height: | Size: 648 B |
BIN
client/icons/donate.png
Normal file
After Width: | Height: | Size: 732 B |
23
client/icons/error.svg
Normal 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 |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 34 KiB |
BIN
client/icons/paste.png
Normal file
After Width: | Height: | Size: 703 B |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.3 KiB |
25
client/icons/warn.svg
Normal 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 |
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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>&Edit</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuView" >
|
||||
<property name="title" >
|
||||
<string>&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" />
|
||||
|
@ -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();
|
||||
|
@ -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:
|
||||
|
@ -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 \
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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();
|
||||
|
@ -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_;
|
||||
};
|
||||
|
||||
|
@ -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());
|
||||
|
@ -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())
|
||||
|
@ -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));
|
||||
|
@ -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);
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -29,6 +29,7 @@ public:
|
||||
PortStatsProxyModel(QObject *parent = 0)
|
||||
: QSortFilterProxyModel(parent)
|
||||
{
|
||||
setFilterRegExp(QRegExp(".*"));
|
||||
}
|
||||
|
||||
protected:
|
||||
|
@ -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
|
||||
|
@ -38,6 +38,7 @@ public:
|
||||
~PortStatsWindow();
|
||||
|
||||
public slots:
|
||||
void clearCurrentSelection();
|
||||
void showMyReservedPortsOnly(bool enabled);
|
||||
|
||||
private slots:
|
||||
|
@ -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>
|
||||
|
@ -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!!!
|
||||
|
@ -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();
|
||||
|
@ -51,13 +51,13 @@
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string><p><b>Welcome to Ostinato</b></p>
|
||||
<string><p><b>Welcome to Ostinato!</b></p>
|
||||
<p>The port list on the left contains all the ports on which you can transmit packets.</p>
|
||||
<p>Ports belong to a port group. Make sure the Port Group has a <img src=":/icons/bullet_green.png"/> next to it, then double click the port group to show or hide the ports in the port group.</p>
|
||||
<p>To generate packets, you need to create and configure packet streams. A stream is a sequence of one or more packets.</p>
|
||||
<p>To create a stream, select the port on which you want to send packets.</p>
|
||||
<hr/>
|
||||
<p>Don't see the port that you want (or any ports at all) inside the port group? <a href="http://jump.ostinato.org/noports">Get Help!</a></p></string>
|
||||
<p>Don't see the port that you want (or any ports at all) inside the port group? <a href="https://jump.ostinato.org/noports">Get Help!</a></p></string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
@ -96,7 +96,7 @@
|
||||
<p>To generate packets, you need to create and configure packet streams. A stream is a sequence of one or more packets.</p>
|
||||
<p>To create a stream, select the port on which you want to send packets. </p>
|
||||
<hr/>
|
||||
<p>Don't see the port that you want (or any ports at all) inside the port group? <a href="http://jump.ostinato.org/noports">Get Help!</a></p></string>
|
||||
<p>Don't see the port that you want (or any ports at all) inside the port group? <a href="https://jump.ostinato.org/noports">Get Help!</a></p></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>
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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());
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
116
common/ip6.cpp
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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('/'));
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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_;
|
||||
|
@ -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];
|
||||
}
|
||||
|
@ -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">
|
||||
|
@ -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
@ -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
|
@ -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;
|
||||
}
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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_;
|
||||
|
@ -165,6 +165,7 @@ void PdmlProtocol::fieldHandler(QString name,
|
||||
qPrintable(name),
|
||||
qPrintable(valueHexStr));
|
||||
|
||||
if (!valueHexStr.isEmpty())
|
||||
knownFieldHandler(name, valueHexStr, pbProto);
|
||||
}
|
||||
else
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
@ -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
|