Merge branch 'master' into sign

This commit is contained in:
Srivats P 2017-12-03 12:59:26 +05:30
commit 6dd6511269
74 changed files with 4065 additions and 2563 deletions

View File

@ -15,7 +15,7 @@ matrix:
compiler: gcc
before_install:
- "if [ $TRAVIS_OS_NAME = 'osx' ]; then brew update && brew tap cartr/qt4 && brew tap-pin cartr/qt4 && brew install qt && brew install protobuf; fi"
- "if [ $TRAVIS_OS_NAME = 'osx' ]; then brew update && brew tap cartr/qt4 && brew tap-pin cartr/qt4 && brew install qt@4 && brew install protobuf && ls -lR /usr/local/include; fi"
addons:
apt:

View File

@ -2,7 +2,7 @@
[![Build Status](https://travis-ci.org/pstavirs/ostinato.svg?branch=master)](https://travis-ci.org/pstavirs/ostinato)
Ostinato is an open-source, cross-platform network packet crafter/traffic generator and analyzer with a friendly GUI. Craft and send packets of several streams with different protocols at different rates.
Ostinato is an open-source, cross-platform network packet crafter/traffic generator and analyzer with a friendly GUI and powerful python API. Craft and send packets of several streams with different protocols at different rates.
Ostinato aims to be "Wireshark in Reverse" and become complementary to Wireshark.

View File

@ -14,6 +14,11 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
"""
This is the core module for the Ostinato Python API.
All drone configuration is done by creating an instance of the
`DroneProxy` class and calling its various methods subsequently.
"""
import os
from rpc import OstinatoRpcChannel, OstinatoRpcController, RpcError
@ -22,8 +27,16 @@ import protocols.emulproto_pb2 as emul
from __init__ import __version__
class DroneProxy(object):
"""
DroneProxy acts as a proxy to a Drone instance. A method invoked on this
class will be trigerred on the actual Drone instance being proxied
"""
def __init__(self, host_name, port_number=7878):
"""
Create a DroneProxy object as a proxy to the Drone instance
running at the specified host and port
"""
self.host = host_name
self.port = port_number
self.channel = OstinatoRpcChannel()
@ -34,14 +47,26 @@ class DroneProxy(object):
fn = lambda request=self.void, method_name=method.name: \
self.callRpcMethod(method_name, request)
self.__dict__[method.name] = fn
self.__dict__[method.name].__doc__ = 'This is a protobuf API'
def hostName(self):
"""
Returns the hostname of the Drone which is being proxied by
this DroneProxy object
"""
return self.host
def portNumber(self):
"""
Returns the TCP port number of the Drone which is being proxied by
this DroneProxy object
"""
return self.port
def connect(self):
"""
Connect to the Drone instance
"""
self.channel.connect(self.host, self.port)
ver = ost_pb.VersionInfo()
ver.client_name = 'python-ostinato'
@ -52,6 +77,9 @@ class DroneProxy(object):
(ver.version, compat.notes))
def disconnect(self):
"""
Disconnect from the Drone instance
"""
self.channel.disconnect()
def callRpcMethod(self, method_name, request):
@ -61,6 +89,9 @@ class DroneProxy(object):
return controller.response
def saveCaptureBuffer(self, buffer, file_name):
"""
Save the capture buffer in a PCAP file
"""
f= open(file_name, 'wb')
f.write(buffer)
f.flush()

View File

@ -170,13 +170,13 @@ void DevicesWidget::on_deviceInfo_toggled(bool checked)
void DevicesWidget::on_actionNewDeviceGroup_triggered()
{
// In case nothing is selected, insert 1 row at the top
int row = 0, count = 1;
QItemSelection selection = deviceGroupList->selectionModel()->selection();
if (!portGroups_)
return;
// In case nothing is selected, insert 1 row at the end
int row = portGroups_->getDeviceGroupModel()->rowCount(), count = 1;
QItemSelection selection = deviceGroupList->selectionModel()->selection();
// In case we have a single range selected; insert as many rows as
// in the singe selected range before the top of the selected range
if (selection.size() == 1) {

View File

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DevicesWidget</class>
<widget class="QWidget" name="DevicesWidget">
@ -13,16 +14,7 @@
<string>Form</string>
</property>
<layout class="QVBoxLayout">
<property name="leftMargin" >
<number>0</number>
</property>
<property name="topMargin" >
<number>0</number>
</property>
<property name="rightMargin" >
<number>0</number>
</property>
<property name="bottomMargin" >
<property name="margin">
<number>0</number>
</property>
<item>
@ -49,7 +41,7 @@
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" >
<property name="sizeHint" stdset="0">
<size>
<width>131</width>
<height>23</height>
@ -69,17 +61,25 @@
<string/>
</property>
<property name="icon">
<iconset resource="ostinato.qrc" >:/icons/refresh.png</iconset>
<iconset resource="ostinato.qrc">
<normaloff>:/icons/refresh.png</normaloff>:/icons/refresh.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTableView" name="deviceGroupList" >
<widget class="XTableView" name="deviceGroupList">
<property name="contextMenuPolicy">
<enum>Qt::ActionsContextMenu</enum>
</property>
<property name="whatsThis">
<string>This is the device group list for the selected port
A device group is a set of one or more devices/hosts which will be emulated by Ostinato
Right-click to create/edit a device group</string>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
@ -95,20 +95,28 @@
</widget>
</item>
<item>
<widget class="QTableView" name="deviceList" >
<widget class="XTableView" name="deviceList">
<property name="sizePolicy">
<sizepolicy vsizetype="Expanding" hsizetype="Expanding" >
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="whatsThis">
<string>No devices being emulated
To emulate a device, click on Configuration and create a device group</string>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
</widget>
</item>
<item>
<widget class="QTableView" name="deviceDetail" >
<widget class="XTableView" name="deviceDetail">
<property name="whatsThis">
<string>IP neighbor cache is empty</string>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
@ -117,7 +125,8 @@
</layout>
<action name="actionNewDeviceGroup">
<property name="icon">
<iconset resource="ostinato.qrc" >:/icons/devicegroup_add.png</iconset>
<iconset resource="ostinato.qrc">
<normaloff>:/icons/devicegroup_add.png</normaloff>:/icons/devicegroup_add.png</iconset>
</property>
<property name="text">
<string>New Device Group</string>
@ -125,7 +134,8 @@
</action>
<action name="actionDeleteDeviceGroup">
<property name="icon">
<iconset resource="ostinato.qrc" >:/icons/devicegroup_delete.png</iconset>
<iconset resource="ostinato.qrc">
<normaloff>:/icons/devicegroup_delete.png</normaloff>:/icons/devicegroup_delete.png</iconset>
</property>
<property name="text">
<string>Delete Device Group</string>
@ -133,13 +143,21 @@
</action>
<action name="actionEditDeviceGroup">
<property name="icon">
<iconset resource="ostinato.qrc" >:/icons/devicegroup_edit.png</iconset>
<iconset resource="ostinato.qrc">
<normaloff>:/icons/devicegroup_edit.png</normaloff>:/icons/devicegroup_edit.png</iconset>
</property>
<property name="text">
<string>Edit Device Group</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
<class>XTableView</class>
<extends>QTableView</extends>
<header>xtableview.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="ostinato.qrc"/>
</resources>

View File

@ -48,7 +48,7 @@ private:
void populateDump(QByteArray &dump, int &selOfs, int &selSize,
QModelIndex parent = QModelIndex());
bool inline isPrintable(char c)
{if ((c > 48) && (c < 126)) return true; else return false; }
{if ((c >= 32) && (c <= 126)) return true; else return false; }
private:
QRect mOffsetPaneTopRect;

BIN
client/icons/help.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 786 B

38
client/jumpurl.h Normal file
View File

@ -0,0 +1,38 @@
/*
Copyright (C) 2017 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 _JUMP_URL_H
#define _JUMP_URL_H
#include <QString>
inline QString jumpUrl(
QString keyword,
QString source="app",
QString medium="hint",
QString name="help")
{
return QString("http://jump.ostinato.org/" + keyword + "?"
+ "utm_source=" + source + "&"
+ "utm_medium=" + medium + "&"
+ "utm_campaign=" + name);
}
#endif

View File

@ -21,6 +21,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "../common/ostprotolib.h"
#include "../common/protocolmanager.h"
#include "../common/protocolwidgetfactory.h"
#include "params.h"
#include "preferences.h"
#include "settings.h"
@ -37,6 +38,7 @@ extern const char* revision;
extern ProtocolManager *OstProtocolManager;
extern ProtocolWidgetFactory *OstProtocolWidgetFactory;
Params appParams;
QSettings *appSettings;
QMainWindow *mainWindow;
@ -50,6 +52,8 @@ int main(int argc, char* argv[])
app.setProperty("version", version);
app.setProperty("revision", revision);
appParams.parseCommandLine(argc, argv);
OstProtocolManager = new ProtocolManager();
OstProtocolWidgetFactory = new ProtocolWidgetFactory();

View File

@ -23,6 +23,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "dbgthread.h"
#endif
#include "jumpurl.h"
#include "params.h"
#include "portgrouplist.h"
#include "portstatswindow.h"
#include "portswindow.h"
@ -40,8 +42,16 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include <QMessageBox>
#include <QProcess>
#include <QProgressDialog>
#include <QTimer>
#include <QUrl>
#ifdef Q_OS_WIN32
#define WIN32_NO_STATUS
#include <windows.h>
#undef WIN32_NO_STATUS
#include <ntstatus.h>
#endif
extern const char* version;
extern const char* revision;
@ -50,23 +60,33 @@ PortGroupList *pgl;
MainWindow::MainWindow(QWidget *parent)
: QMainWindow (parent)
{
QString serverApp = QCoreApplication::applicationDirPath();
Updater *updater = new Updater();
if (appParams.optLocalDrone()) {
QString serverApp = QCoreApplication::applicationDirPath();
#ifdef Q_OS_MAC
// applicationDirPath() does not return bundle, but executable inside bundle
// applicationDirPath() does not return bundle,
// but executable inside bundle
serverApp.replace("Ostinato.app", "drone.app");
#endif
#ifdef Q_OS_WIN32
serverApp.append("/drone.exe");
#else
serverApp.append("/drone");
#endif
qDebug("staring local server - %s", qPrintable(serverApp));
localServer_ = new QProcess(this);
connect(localServer_, SIGNAL(finished(int, QProcess::ExitStatus)),
SLOT(onLocalServerFinished(int, QProcess::ExitStatus)));
connect(localServer_, SIGNAL(error(QProcess::ProcessError)),
SLOT(onLocalServerError(QProcess::ProcessError)));
localServer_->setProcessChannelMode(QProcess::ForwardedChannels);
localServer_->start(serverApp, QStringList());
QTimer::singleShot(5000, this, SLOT(stopLocalServerMonitor()));
}
else
localServer_ = NULL;
pgl = new PortGroupList;
@ -114,6 +134,16 @@ MainWindow::MainWindow(QWidget *parent)
connect(updater, SIGNAL(newVersionAvailable(QString)),
this, SLOT(onNewVersion(QString)));
updater->checkForNewVersion();
if (appParams.argumentCount()) {
QString fileName = appParams.argument(0);
if (QFile::exists(fileName))
on_actionOpenSession_triggered(fileName);
else
QMessageBox::information(NULL, qApp->applicationName(),
QString("File not found: " + fileName));
}
#if 0
{
DbgThread *dbg = new DbgThread(pgl);
@ -124,12 +154,15 @@ MainWindow::MainWindow(QWidget *parent)
MainWindow::~MainWindow()
{
stopLocalServerMonitor();
if (localServer_) {
#ifdef Q_OS_WIN32
//! \todo - find a way to terminate cleanly
localServer_->kill();
#else
localServer_->terminate();
#endif
}
delete pgl;
@ -144,22 +177,26 @@ MainWindow::~MainWindow()
appSettings->setValue(kApplicationWindowLayout, layout);
appSettings->setValue(kApplicationWindowGeometryKey, geometry());
if (localServer_) {
localServer_->waitForFinished();
delete localServer_;
}
}
void MainWindow::on_actionOpenSession_triggered()
void MainWindow::on_actionOpenSession_triggered(QString fileName)
{
qDebug("Open Session Action");
qDebug("Open Session Action (%s)", qPrintable(fileName));
static QString dirName;
QString fileName;
QStringList fileTypes = SessionFileFormat::supportedFileTypes(
SessionFileFormat::kOpenFile);
QString fileType;
QString errorStr;
bool ret;
if (!fileName.isEmpty())
goto _skip_prompt;
if (portsWindow->portGroupCount()) {
if (QMessageBox::question(this,
tr("Open Session"),
@ -177,6 +214,7 @@ void MainWindow::on_actionOpenSession_triggered()
if (fileName.isEmpty())
goto _exit;
_skip_prompt:
ret = openSession(fileName, errorStr);
if (!ret || !errorStr.isEmpty()) {
QMessageBox msgBox(this);
@ -293,7 +331,7 @@ void MainWindow::on_actionViewRestoreDefaults_triggered()
void MainWindow::on_actionHelpOnline_triggered()
{
QDesktopServices::openUrl(QUrl("http://ostinato.org/docs"));
QDesktopServices::openUrl(QUrl(jumpUrl("help", "app", "menu")));
}
void MainWindow::on_actionHelpAbout_triggered()
@ -310,10 +348,76 @@ void MainWindow::on_actionHelpAbout_triggered()
delete aboutDialog;
}
void MainWindow::stopLocalServerMonitor()
{
// We are only interested in startup errors
disconnect(localServer_, SIGNAL(error(QProcess::ProcessError)),
this, SLOT(onLocalServerError(QProcess::ProcessError)));
disconnect(localServer_, SIGNAL(finished(int, QProcess::ExitStatus)),
this, SLOT(onLocalServerFinished(int, QProcess::ExitStatus)));
}
void MainWindow::onLocalServerFinished(int exitCode,
QProcess::ExitStatus /*exitStatus*/)
{
if (exitCode)
reportLocalServerError();
}
void MainWindow::onLocalServerError(QProcess::ProcessError /*error*/)
{
reportLocalServerError();
}
void MainWindow::reportLocalServerError()
{
QMessageBox msgBox(this);
msgBox.setIcon(QMessageBox::Warning);
msgBox.setTextFormat(Qt::RichText);
msgBox.setStyleSheet("messagebox-text-interaction-flags: 5"); // mouse copy
QString errorStr = tr("<p>Failed to start the local drone agent - "
"error 0x%1, exit status 0x%2 exit code 0x%3.</p>")
.arg(localServer_->error(), 0, 16)
.arg(localServer_->exitStatus(), 0, 16)
.arg(localServer_->exitCode(), 0, 16);
if (localServer_->error() == QProcess::FailedToStart)
errorStr.append(tr("<p>The drone program does not exist at %1 or you "
"don't have sufficient permissions to execute it."
"</p>")
.arg(QCoreApplication::applicationDirPath()));
if (localServer_->exitCode() == 1)
errorStr.append(tr("<p>The drone program was not able to bind to "
"TCP port 7878 - maybe a drone process is already "
"running?</p>"));
#ifdef Q_OS_WIN32
if (localServer_->exitCode() == STATUS_DLL_NOT_FOUND)
errorStr.append(tr("<p>This is most likely because Packet.dll "
"was not found - make sure you have "
"<a href='%1'>WinPcap"
"</a> installed.</p>")
.arg(jumpUrl("winpcap")));
#endif
msgBox.setText(errorStr);
msgBox.setInformativeText(tr("Try running drone directly."));
msgBox.exec();
QMessageBox::information(this, QString(),
tr("<p>If you have remote drone agents running, you can still add "
"and connect to them.</p>"
"<p>If you don't want to start the local drone agent at startup, "
"provide the <b>-s</b> option to Ostinato on the command line.</p>"
"<p>Learn about Ostinato's <a href='%1'>Controller-Agent "
"architecture</a></p>").arg(jumpUrl("arch")));
}
void MainWindow::onNewVersion(QString newVersion)
{
statusBar()->showMessage(QString("New Ostinato version %1 available. "
"Visit http://ostinato.org to download").arg(newVersion));
QLabel *msg = new QLabel(tr("New Ostinato version %1 available. Visit "
"<a href='%2'>ostinato.org</a> to download")
.arg(newVersion)
.arg(jumpUrl("download", "app", "status", "update")));
msg->setOpenExternalLinks(true);
statusBar()->addPermanentWidget(msg);
}
//! Returns true on success (or user cancel) and false on failure

View File

@ -22,6 +22,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "ui_mainwindow.h"
#include <QMainWindow>
#include <QProcess>
class PortsWindow;
class PortStatsWindow;
@ -51,7 +52,7 @@ public:
~MainWindow();
public slots:
void on_actionOpenSession_triggered();
void on_actionOpenSession_triggered(QString fileName = QString());
void on_actionSaveSession_triggered();
void on_actionPreferences_triggered();
void on_actionViewRestoreDefaults_triggered();
@ -59,6 +60,10 @@ public slots:
void on_actionHelpAbout_triggered();
private slots:
void stopLocalServerMonitor();
void onLocalServerFinished(int exitCode, QProcess::ExitStatus exitStatus);
void onLocalServerError(QProcess::ProcessError error);
void reportLocalServerError();
void onNewVersion(QString version);
};

View File

@ -104,6 +104,9 @@
</property>
</action>
<action name="actionHelpOnline" >
<property name="icon" >
<iconset resource="ostinato.qrc" >:/icons/help.png</iconset>
</property>
<property name="text" >
<string>Help (Online)</string>
</property>

View File

@ -90,6 +90,7 @@ SOURCES += \
mainwindow.cpp \
ndpstatusmodel.cpp \
packetmodel.cpp \
params.cpp \
port.cpp \
portconfigdialog.cpp \
portgroup.cpp \

View File

@ -20,6 +20,7 @@
<file>icons/devicegroup_edit.png</file>
<file>icons/exit.png</file>
<file>icons/gaps.png</file>
<file>icons/help.png</file>
<file>icons/logo.png</file>
<file>icons/magnifier.png</file>
<file>icons/name.png</file>

65
client/params.cpp Normal file
View File

@ -0,0 +1,65 @@
/*
Copyright (C) 2016 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 "params.h"
#include <unistd.h>
Params::Params()
{
localDrone_ = true;
}
int Params::parseCommandLine(int argc, char* argv[])
{
int c, n = 0;
opterr = 0;
while ((c = getopt (argc, argv, "s")) != -1) {
switch (c)
{
case 's':
localDrone_ = false;
break;
default:
qDebug("ignoring unrecognized option (%c)", c);
}
n++;
}
for (int i = optind; i < argc; i++, n++)
args_ << argv[i];
return n;
}
bool Params::optLocalDrone()
{
return localDrone_;
}
int Params::argumentCount()
{
return args_.size();
}
QString Params::argument(int index)
{
return index < args_.size() ? args_.at(index) : QString();
}

43
client/params.h Normal file
View File

@ -0,0 +1,43 @@
/*
Copyright (C) 2016 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 _PARAMS_H
#define _PARAMS_H
#include <QStringList>
class Params {
public:
Params();
int parseCommandLine(int argc, char* argv[]);
bool optLocalDrone();
int argumentCount();
QString argument(int index);
private:
bool localDrone_;
QStringList args_;
};
extern Params appParams;
#endif

View File

@ -57,6 +57,7 @@ Port::Port(quint32 id, quint32 portGroupId)
stats.mutable_port_id()->set_id(id);
mPortGroupId = portGroupId;
capFile_ = NULL;
dirty_ = false;
}
Port::~Port()
@ -100,6 +101,15 @@ void Port::reorderStreamsByOrdinals()
qSort(mStreams.begin(), mStreams.end(), StreamBase::StreamLessThan);
}
void Port::setDirty(bool dirty)
{
if (dirty == dirty_)
return;
dirty_ = dirty;
emit localConfigChanged(mPortGroupId, mPortId, dirty_);
}
void Port::recalculateAverageRates()
{
double pps = 0;
@ -209,6 +219,7 @@ void Port::setAveragePacketRate(double packetsPerSec)
Q_ASSERT(false); // Unreachable!!
}
numActiveStreams_ = n;
setDirty(true);
}
else
avgPacketsPerSec_ = avgBitsPerSec_ = numActiveStreams_ = 0;
@ -282,6 +293,7 @@ void Port::setAverageBitRate(double bitsPerSec)
Q_ASSERT(false); // Unreachable!!
}
numActiveStreams_ = n;
setDirty(true);
}
else
avgPacketsPerSec_ = avgBitsPerSec_ = numActiveStreams_ = 0;
@ -305,6 +317,7 @@ bool Port::newStreamAt(int index, OstProto::Stream const *stream)
mStreams.insert(index, s);
updateStreamOrdinalsFromIndex();
recalculateAverageRates();
setDirty(true);
return true;
}
@ -317,6 +330,7 @@ bool Port::deleteStreamAt(int index)
delete mStreams.takeAt(index);
updateStreamOrdinalsFromIndex();
recalculateAverageRates();
setDirty(true);
return true;
}
@ -506,6 +520,8 @@ void Port::when_syncComplete()
deviceGroups_.at(i)->device_group_id().id());
}
modifiedDeviceGroupList_.clear();
setDirty(false);
}
void Port::updateStats(OstProto::PortStats *portStats)
@ -543,6 +559,7 @@ void Port::duplicateStreams(const QList<int> &list, int count)
insertAt++;
}
}
setDirty(true);
emit streamListChanged(mPortGroupId, mPortId);
}
@ -625,6 +642,7 @@ bool Port::openStreams(QString fileName, bool append, QString &error)
if (i % 32 == 0)
qApp->processEvents();
}
setDirty(true);
_user_cancel:
emit streamListChanged(mPortGroupId, mPortId);
@ -743,11 +761,12 @@ OstProto::DeviceGroup* Port::mutableDeviceGroupByIndex(int index)
// Caller can modify DeviceGroup - assume she will
modifiedDeviceGroupList_.insert(devGrp->device_group_id().id());
setDirty(true);
return devGrp;
}
OstProto::DeviceGroup* Port::deviceGroupById(uint deviceGroupId)
const OstProto::DeviceGroup* Port::deviceGroupById(uint deviceGroupId) const
{
for (int i = 0; i < deviceGroups_.size(); i++) {
OstProto::DeviceGroup *devGrp = deviceGroups_.at(i);
@ -776,6 +795,7 @@ bool Port::newDeviceGroupAt(int index, const OstProto::DeviceGroup *deviceGroup)
devGrp->mutable_device_group_id()->set_id(newDeviceGroupId());
deviceGroups_.insert(index, devGrp);
modifiedDeviceGroupList_.insert(devGrp->device_group_id().id());
setDirty(true);
return true;
}
@ -788,6 +808,7 @@ bool Port::deleteDeviceGroupAt(int index)
OstProto::DeviceGroup *devGrp = deviceGroups_.takeAt(index);
modifiedDeviceGroupList_.remove(devGrp->device_group_id().id());
delete devGrp;
setDirty(true);
return true;
}
@ -818,7 +839,12 @@ bool Port::updateDeviceGroup(
uint deviceGroupId,
OstProto::DeviceGroup *deviceGroup)
{
OstProto::DeviceGroup *devGrp = deviceGroupById(deviceGroupId);
using OstProto::DeviceGroup;
// XXX: We should not call mutableDeviceGroupById() because that will
// implicitly set the port as dirty, so we use a const_cast hack instead
DeviceGroup *devGrp = const_cast<DeviceGroup*>(
deviceGroupById(deviceGroupId));
if (!devGrp) {
qDebug("%s: deviceGroup id %u does not exist", __FUNCTION__,

View File

@ -50,6 +50,7 @@ class Port : public QObject {
quint32 mPortId;
quint32 mPortGroupId;
QString mUserAlias; // user defined
bool dirty_;
double avgPacketsPerSec_;
double avgBitsPerSec_;
@ -70,6 +71,7 @@ class Port : public QObject {
void updateStreamOrdinalsFromIndex();
void reorderStreamsByOrdinals();
void setDirty(bool dirty);
public:
enum AdminStatus { AdminDisable, AdminEnable };
@ -92,17 +94,17 @@ public:
{ return QString().fromStdString(d.notes()); }
const QString userName() const
{ return QString().fromStdString(d.user_name()); }
AdminStatus adminStatus()
AdminStatus adminStatus() const
{ return (d.is_enabled()?AdminEnable:AdminDisable); }
bool hasExclusiveControl()
bool hasExclusiveControl() const
{ return d.is_exclusive_control(); }
OstProto::TransmitMode transmitMode()
OstProto::TransmitMode transmitMode() const
{ return d.transmit_mode(); }
bool trackStreamStats()
bool trackStreamStats() const
{ return d.is_tracking_stream_stats(); }
double averagePacketRate()
double averagePacketRate() const
{ return avgPacketsPerSec_; }
double averageBitRate()
double averageBitRate() const
{ return avgBitsPerSec_; }
//void setAdminEnable(AdminStatus status) { mAdminStatus = status; }
@ -110,11 +112,22 @@ public:
//void setExclusive(bool flag);
int numStreams() { return mStreams.size(); }
Stream* streamByIndex(int index)
const Stream* streamByIndex(int index) const
{
Q_ASSERT(index < mStreams.size());
return mStreams[index];
}
Stream* mutableStreamByIndex(int index, bool assumeChange = true)
{
Q_ASSERT(index < mStreams.size());
if (assumeChange)
setDirty(true);
return mStreams[index];
}
void setLocalConfigChanged(bool changed)
{
setDirty(changed);
}
OstProto::LinkState linkState()
{ return stats.state().link_state(); }
@ -131,6 +144,7 @@ public:
void protoDataCopyInto(OstProto::Port *data);
//! Used when config received from server
// FIXME(MED): naming inconsistency - PortConfig/Stream; also retVal
void updatePortConfig(OstProto::Port *port);
@ -146,6 +160,7 @@ public:
bool updateStream(uint streamId, OstProto::Stream *stream);
//@}
bool isDirty() { return dirty_; }
void getDeletedStreamsSinceLastSync(OstProto::StreamIdList &streamIdList);
void getNewStreamsSinceLastSync(OstProto::StreamIdList &streamIdList);
void getModifiedStreamsSinceLastSync(
@ -180,7 +195,7 @@ public:
int numDeviceGroups() const;
const OstProto::DeviceGroup* deviceGroupByIndex(int index) const;
OstProto::DeviceGroup* mutableDeviceGroupByIndex(int index);
OstProto::DeviceGroup* deviceGroupById(uint deviceGroupId);
const OstProto::DeviceGroup* deviceGroupById(uint deviceGroupId) const;
//! Used by StreamModel
//@{
@ -218,11 +233,20 @@ public:
void deviceInfoRefreshed();
signals:
//! Used when local config changed and when config received from server
void portRateChanged(int portGroupId, int portId);
void portDataChanged(int portGroupId, int portId);
void streamListChanged(int portGroupId, int portId);
void deviceInfoChanged();
//! Used by MyService::Stub to update from config received from server
//@{
void portDataChanged(int portGroupId, int portId);
void deviceInfoChanged();
//@}
//! Used when local config changed
//@{
void streamListChanged(int portGroupId, int portId);
void localConfigChanged(int portGroupId, int portId, bool changed);
//@}
};
#endif

View File

@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "portgroup.h"
#include "jumpurl.h"
#include "settings.h"
#include "emulproto.pb.h"
@ -200,6 +201,19 @@ void PortGroup::processVersionCompatibility(PbRpcController *controller)
qPrintable(QString::fromStdString(verCompat->notes())));
compat = kIncompatible;
emit portGroupDataChanged(mPortGroupId);
QMessageBox msgBox;
msgBox.setIcon(QMessageBox::Warning);
msgBox.setTextFormat(Qt::RichText);
msgBox.setStyleSheet("messagebox-text-interaction-flags: 5");
msgBox.setText(tr("The Drone agent at %1:%2 is incompatible with this "
"Ostinato version - %3")
.arg(serverName())
.arg(int(serverPort()))
.arg(version));
msgBox.setInformativeText(QString::fromStdString(verCompat->notes()));
msgBox.exec();
goto _error_exit;
}
@ -302,23 +316,22 @@ void PortGroup::on_rpcChannel_notification(int notifType,
void PortGroup::when_portListChanged(quint32 /*portGroupId*/)
{
QString faq("http://ostinato.org/docs/faq#q-port-group-has-no-interfaces");
if (state() == QAbstractSocket::ConnectedState && numPorts() <= 0)
{
if (QMessageBox::warning(NULL, tr("No ports in portgroup"),
QString("The portgroup %1:%2 does not contain any ports!\n\n"
"Packet Transmit/Capture requires elevated privileges. "
"Please ensure that you are running 'drone' - the server "
"component of Ostinato with admin/root OR setuid privilege.\n\n"
"For help see the Ostinato FAQ (%3)")
QMessageBox msgBox;
msgBox.setIcon(QMessageBox::Warning);
msgBox.setTextFormat(Qt::RichText);
msgBox.setStyleSheet("messagebox-text-interaction-flags: 5");
QString msg = tr("<p>The portgroup %1:%2 does not contain any ports!<p>"
"<p>Packet Transmit/Capture requires special privileges. "
"Please ensure that you are running 'drone' - the agent "
"component of Ostinato with required privileges.<p>")
.arg(serverName())
.arg(int(serverPort()))
.arg(faq.remove(QRegExp("#.*$"))),
QMessageBox::Ok | QMessageBox::Help,
QMessageBox::Ok) == QMessageBox::Help)
{
QDesktopServices::openUrl(QUrl(faq));
}
.arg(int(serverPort()));
msgBox.setText(msg);
msgBox.setInformativeText(tr("See the <a href='%1'>Ostinato FAQ</a> "
"for instructions to fix this problem").arg(jumpUrl("noports")));
msgBox.exec();
}
}
@ -347,6 +360,8 @@ void PortGroup::processPortIdList(PbRpcController *controller)
p = new Port(portIdList->port_id(i).id(), mPortGroupId);
connect(p, SIGNAL(portDataChanged(int, int)),
this, SIGNAL(portGroupDataChanged(int, int)));
connect(p, SIGNAL(localConfigChanged(int, int, bool)),
this, SIGNAL(portGroupDataChanged(int, int)));
qDebug("before port append\n");
mPorts.append(p);
atConnectPortConfig_.append(NULL); // will be filled later

View File

@ -19,6 +19,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "portgrouplist.h"
#include "params.h"
// TODO(LOW): Remove
#include <modeltest.h>
@ -29,8 +31,6 @@ PortGroupList::PortGroupList()
mDeviceGroupModel(this),
mDeviceModel(this)
{
PortGroup *pg;
#ifdef QT_NO_DEBUG
streamModelTester_ = NULL;
portModelTester_ = NULL;
@ -46,9 +46,11 @@ PortGroupList::PortGroupList()
#endif
// Add the "Local" Port Group
pg = new PortGroup;
if (appParams.optLocalDrone()) {
PortGroup *pg = new PortGroup;
addPortGroup(*pg);
}
}
PortGroupList::~PortGroupList()
{

View File

@ -187,6 +187,10 @@ QVariant PortModel::data(const QModelIndex &index, int role) const
{
return portIconFactory[port->linkState()][port->hasExclusiveControl()];
}
else if ((role == Qt::ForegroundRole))
{
return port->isDirty() ? QBrush(Qt::red) : QVariant();
}
else
{
DBG0("Exit PortModel data 6\n");

View File

@ -53,6 +53,12 @@ PortStatsWindow::PortStatsWindow(PortGroupList *pgl, QWidget *parent)
tvPortStats->verticalHeader()->setDefaultSectionSize(
tvPortStats->verticalHeader()->minimumSectionSize());
connect(tvPortStats->selectionModel(),
SIGNAL(selectionChanged(
const QItemSelection&, const QItemSelection&)),
SLOT(when_tvPortStats_selectionChanged(
const QItemSelection&, const QItemSelection&)));
when_tvPortStats_selectionChanged(QItemSelection(), QItemSelection());
}
PortStatsWindow::~PortStatsWindow()
@ -76,12 +82,43 @@ void PortStatsWindow::showMyReservedPortsOnly(bool enabled)
}
/* ------------- SLOTS (private) -------------- */
void PortStatsWindow::when_tvPortStats_selectionChanged(
const QItemSelection& /*selected*/,
const QItemSelection& /*deselected*/)
{
QModelIndexList indexList =
tvPortStats->selectionModel()->selectedColumns();
if (proxyStatsModel) {
selectedColumns.clear();
foreach(QModelIndex index, indexList)
selectedColumns.append(proxyStatsModel->mapToSource(index));
}
else
selectedColumns = indexList;
bool isEmpty = selectedColumns.isEmpty();
tbStartTransmit->setDisabled(isEmpty);
tbStopTransmit->setDisabled(isEmpty);
tbStartCapture->setDisabled(isEmpty);
tbStopCapture->setDisabled(isEmpty);
tbViewCapture->setDisabled(isEmpty);
tbClear->setDisabled(isEmpty);
tbResolveNeighbors->setDisabled(isEmpty);
tbClearNeighbors->setDisabled(isEmpty);
}
void PortStatsWindow::on_tbStartTransmit_clicked()
{
QList<PortStatsModel::PortGroupAndPortList> pgpl;
// Get selected ports
model->portListFromIndex(selectedColumns(), pgpl);
model->portListFromIndex(selectedColumns, pgpl);
// Clear selected ports, portgroup by portgroup
for (int i = 0; i < pgpl.size(); i++)
@ -96,7 +133,7 @@ void PortStatsWindow::on_tbStopTransmit_clicked()
QList<PortStatsModel::PortGroupAndPortList> pgpl;
// Get selected ports
model->portListFromIndex(selectedColumns(), pgpl);
model->portListFromIndex(selectedColumns, pgpl);
// Clear selected ports, portgroup by portgroup
for (int i = 0; i < pgpl.size(); i++)
@ -112,7 +149,7 @@ void PortStatsWindow::on_tbStartCapture_clicked()
QList<PortStatsModel::PortGroupAndPortList> pgpl;
// Get selected ports
model->portListFromIndex(selectedColumns(), pgpl);
model->portListFromIndex(selectedColumns, pgpl);
// Clear selected ports, portgroup by portgroup
for (int i = 0; i < pgpl.size(); i++)
@ -128,7 +165,7 @@ void PortStatsWindow::on_tbStopCapture_clicked()
QList<PortStatsModel::PortGroupAndPortList> pgpl;
// Get selected ports
model->portListFromIndex(selectedColumns(), pgpl);
model->portListFromIndex(selectedColumns, pgpl);
// Clear selected ports, portgroup by portgroup
for (int i = 0; i < pgpl.size(); i++)
@ -144,7 +181,7 @@ void PortStatsWindow::on_tbViewCapture_clicked()
QList<PortStatsModel::PortGroupAndPortList> pgpl;
// Get selected ports
model->portListFromIndex(selectedColumns(), pgpl);
model->portListFromIndex(selectedColumns, pgpl);
// Clear selected ports, portgroup by portgroup
for (int i = 0; i < pgpl.size(); i++)
@ -159,7 +196,7 @@ void PortStatsWindow::on_tbResolveNeighbors_clicked()
QList<PortStatsModel::PortGroupAndPortList> portList;
// Get selected ports
model->portListFromIndex(selectedColumns(), portList);
model->portListFromIndex(selectedColumns, portList);
// Resolve ARP/ND for selected ports, portgroup by portgroup
for (int i = 0; i < portList.size(); i++)
@ -174,7 +211,7 @@ void PortStatsWindow::on_tbClearNeighbors_clicked()
QList<PortStatsModel::PortGroupAndPortList> portList;
// Get selected ports
model->portListFromIndex(selectedColumns(), portList);
model->portListFromIndex(selectedColumns, portList);
// Clear ARP/ND for ports, portgroup by portgroup
for (int i = 0; i < portList.size(); i++)
@ -189,7 +226,7 @@ void PortStatsWindow::on_tbClear_clicked()
QList<PortStatsModel::PortGroupAndPortList> portList;
// Get selected ports
model->portListFromIndex(selectedColumns(), portList);
model->portListFromIndex(selectedColumns, portList);
// Clear selected ports, portgroup by portgroup
for (int i = 0; i < portList.size(); i++)
@ -235,7 +272,7 @@ void PortStatsWindow::on_tbGetStreamStats_clicked()
StreamStatsModel *streamStatsModel;
// Get selected ports
model->portListFromIndex(selectedColumns(), portList);
model->portListFromIndex(selectedColumns, portList);
if (portList.size()) {
QDockWidget *dock = new QDockWidget(mainWindow);
@ -298,20 +335,3 @@ void PortStatsWindow::on_tbFilter_clicked()
hv->moveSection(hv->visualIndex(newColumns.at(vi)), vi);
}
}
/* ------------ Private Methods -------------- */
QModelIndexList PortStatsWindow::selectedColumns()
{
QModelIndexList indexList =
tvPortStats->selectionModel()->selectedColumns();
QModelIndexList sourceIndexList;
if (!proxyStatsModel)
return indexList;
foreach(QModelIndex index, indexList)
sourceIndexList.append(proxyStatsModel->mapToSource(index));
return sourceIndexList;
}

View File

@ -40,6 +40,9 @@ public slots:
void showMyReservedPortsOnly(bool enabled);
private slots:
void when_tvPortStats_selectionChanged(const QItemSelection &selected,
const QItemSelection &deselected);
void on_tbStartTransmit_clicked();
void on_tbStopTransmit_clicked();
@ -57,11 +60,10 @@ private slots:
void on_tbFilter_clicked();
private:
QModelIndexList selectedColumns();
PortGroupList *pgl;
PortStatsModel *model;
QSortFilterProxyModel *proxyStatsModel;
QModelIndexList selectedColumns;
};

View File

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PortStatsWindow</class>
<widget class="QWidget" name="PortStatsWindow">
@ -22,6 +23,13 @@
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Transmit</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="tbStartTransmit">
<property name="toolTip">
@ -31,10 +39,11 @@
<string>Starts transmit on selected port(s)</string>
</property>
<property name="text">
<string>Start Transmit</string>
<string>Start</string>
</property>
<property name="icon">
<iconset resource="ostinato.qrc" >:/icons/control_play.png</iconset>
<iconset resource="ostinato.qrc">
<normaloff>:/icons/control_play.png</normaloff>:/icons/control_play.png</iconset>
</property>
</widget>
</item>
@ -47,10 +56,25 @@
<string>Stops transmit on selected port(s)</string>
</property>
<property name="text">
<string>Stop Trasmit</string>
<string>Stop</string>
</property>
<property name="icon">
<iconset resource="ostinato.qrc" >:/icons/control_stop.png</iconset>
<iconset resource="ostinato.qrc">
<normaloff>:/icons/control_stop.png</normaloff>:/icons/control_stop.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Stats</string>
</property>
</widget>
</item>
@ -66,7 +90,8 @@
<string>Clear</string>
</property>
<property name="icon">
<iconset resource="ostinato.qrc" >:/icons/portstats_clear.png</iconset>
<iconset resource="ostinato.qrc">
<normaloff>:/icons/portstats_clear.png</normaloff>:/icons/portstats_clear.png</iconset>
</property>
</widget>
</item>
@ -82,7 +107,8 @@
<string>Clear All</string>
</property>
<property name="icon">
<iconset resource="ostinato.qrc" >:/icons/portstats_clear_all.png</iconset>
<iconset resource="ostinato.qrc">
<normaloff>:/icons/portstats_clear_all.png</normaloff>:/icons/portstats_clear_all.png</iconset>
</property>
</widget>
</item>
@ -102,6 +128,20 @@
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Capture</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="tbStartCapture" >
<property name="toolTip" >
@ -111,10 +151,11 @@
<string>Captures packets on the selected port(s)</string>
</property>
<property name="text">
<string>Start Capture</string>
<string>Start</string>
</property>
<property name="icon">
<iconset resource="ostinato.qrc" >:/icons/sound_none.png</iconset>
<iconset resource="ostinato.qrc">
<normaloff>:/icons/sound_none.png</normaloff>:/icons/sound_none.png</iconset>
</property>
</widget>
</item>
@ -127,10 +168,11 @@
<string>End capture on selecteed port(s)</string>
</property>
<property name="text">
<string>Stop Capture</string>
<string>Stop</string>
</property>
<property name="icon">
<iconset resource="ostinato.qrc" >:/icons/sound_mute.png</iconset>
<iconset resource="ostinato.qrc">
<normaloff>:/icons/sound_mute.png</normaloff>:/icons/sound_mute.png</iconset>
</property>
</widget>
</item>
@ -143,10 +185,11 @@
<string>View captured packets on selected port(s)</string>
</property>
<property name="text">
<string>View Capture</string>
<string>View</string>
</property>
<property name="icon">
<iconset resource="ostinato.qrc" >:/icons/magnifier.png</iconset>
<iconset resource="ostinato.qrc">
<normaloff>:/icons/magnifier.png</normaloff>:/icons/magnifier.png</iconset>
</property>
</widget>
</item>
@ -157,6 +200,13 @@
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>ARP/ND</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="tbResolveNeighbors">
<property name="toolTip">
@ -169,7 +219,8 @@
<string>Resolve Neighbors</string>
</property>
<property name="icon">
<iconset resource="ostinato.qrc" >:/icons/neighbor_resolve.png</iconset>
<iconset resource="ostinato.qrc">
<normaloff>:/icons/neighbor_resolve.png</normaloff>:/icons/neighbor_resolve.png</iconset>
</property>
</widget>
</item>
@ -185,7 +236,8 @@
<string>Clear Neighbors</string>
</property>
<property name="icon">
<iconset resource="ostinato.qrc" >:/icons/neighbor_clear.png</iconset>
<iconset resource="ostinato.qrc">
<normaloff>:/icons/neighbor_clear.png</normaloff>:/icons/neighbor_clear.png</iconset>
</property>
</widget>
</item>
@ -201,7 +253,7 @@
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" >
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
@ -218,7 +270,8 @@
<string>Filter</string>
</property>
<property name="icon">
<iconset resource="ostinato.qrc" >:/icons/portstats_filter.png</iconset>
<iconset resource="ostinato.qrc">
<normaloff>:/icons/portstats_filter.png</normaloff>:/icons/portstats_filter.png</iconset>
</property>
</widget>
</item>
@ -226,7 +279,11 @@
</widget>
</item>
<item>
<widget class="QTableView" name="tvPortStats" />
<widget class="QTableView" name="tvPortStats">
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectColumns</enum>
</property>
</widget>
</item>
</layout>
</widget>

View File

@ -302,26 +302,26 @@ void PortsWindow::showMyReservedPortsOnly(bool enabled)
void PortsWindow::on_tvStreamList_activated(const QModelIndex & index)
{
QModelIndex currentPort = tvPortList->currentIndex();
StreamConfigDialog *scd;
int ret;
if (proxyPortModel)
currentPort = proxyPortModel->mapToSource(currentPort);
if (!index.isValid())
{
qDebug("%s: invalid index", __FUNCTION__);
return;
}
scd = new StreamConfigDialog(plm->port(currentPort), index.row(), this);
qDebug("stream list activated\n");
ret = scd->exec();
if (ret == QDialog::Accepted)
plm->port(currentPort).recalculateAverageRates();
Port &curPort = plm->port(proxyPortModel ?
proxyPortModel->mapToSource(tvPortList->currentIndex()) :
tvPortList->currentIndex());
delete scd;
QList<Stream*> streams;
streams.append(curPort.mutableStreamByIndex(index.row(), false));
StreamConfigDialog scd(streams, curPort, this);
if (scd.exec() == QDialog::Accepted) {
curPort.recalculateAverageRates();
curPort.setLocalConfigChanged(true);
}
}
void PortsWindow::when_portView_currentChanged(const QModelIndex& currentIndex,
@ -345,12 +345,16 @@ void PortsWindow::when_portView_currentChanged(const QModelIndex& currentIndex,
{
disconnect(&(plm->port(previous)), SIGNAL(portRateChanged(int, int)),
this, SLOT(updatePortRates()));
disconnect(&(plm->port(previous)),
SIGNAL(localConfigChanged(int, int, bool)),
this,
SLOT(updateApplyHint(int, int, bool)));
}
if (!current.isValid())
{
qDebug("setting stacked widget to blank page");
swDetail->setCurrentIndex(2); // blank page
qDebug("setting stacked widget to welcome page");
swDetail->setCurrentIndex(0); // welcome page
}
else
{
@ -360,10 +364,21 @@ void PortsWindow::when_portView_currentChanged(const QModelIndex& currentIndex,
}
else if (plm->isPort(current))
{
swDetail->setCurrentIndex(0); // port detail page
swDetail->setCurrentIndex(2); // port detail page
updatePortRates();
connect(&(plm->port(current)), SIGNAL(portRateChanged(int, int)),
SLOT(updatePortRates()));
connect(&(plm->port(current)),
SIGNAL(localConfigChanged(int, int, bool)),
SLOT(updateApplyHint(int, int, bool)));
if (plm->port(current).isDirty())
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");
else
applyHint->setText("");
}
}
@ -373,7 +388,22 @@ void PortsWindow::when_portView_currentChanged(const QModelIndex& currentIndex,
void PortsWindow::when_portModel_dataChanged(const QModelIndex& topLeft,
const QModelIndex& bottomRight)
{
qDebug("In %s", __FUNCTION__);
qDebug("In %s %d:(%d, %d) - %d:(%d, %d)", __FUNCTION__,
topLeft.parent().isValid(), topLeft.row(), topLeft.column(),
bottomRight.parent().isValid(), bottomRight.row(), bottomRight.column());
if (!topLeft.isValid() || !bottomRight.isValid())
return;
if (topLeft.parent() != bottomRight.parent())
return;
// If a port has changed, expand the port group
if (topLeft.parent().isValid())
tvPortList->expand(proxyPortModel ?
proxyPortModel->mapFromSource(topLeft.parent()) :
topLeft.parent());
#if 0 // not sure why the >= <= operators are not overloaded in QModelIndex
if ((tvPortList->currentIndex() >= topLeft) &&
(tvPortList->currentIndex() <= bottomRight))
@ -469,11 +499,6 @@ void PortsWindow::updateStreamViewActions()
else
{
actionNew_Stream->setEnabled(true);
// Enable "Edit" only if the single range has a single row
if (tvStreamList->selectionModel()->selection().at(0).height() > 1)
actionEdit_Stream->setDisabled(true);
else
actionEdit_Stream->setEnabled(true);
}
@ -496,6 +521,18 @@ void PortsWindow::updateStreamViewActions()
actionSave_Streams->setEnabled(tvStreamList->model()->rowCount() > 0);
}
void PortsWindow::updateApplyHint(int /*portGroupId*/, int /*portId*/,
bool configChanged)
{
if (configChanged)
applyHint->setText("Configuration has changed - "
"<font color='red'><b>click Apply</b></font> "
"to activate the changes");
else
applyHint->setText("Configuration activated. Use the Statistics "
"window to transmit packets");
}
void PortsWindow::updatePortViewActions(const QModelIndex& currentIndex)
{
QModelIndex current = currentIndex;
@ -740,30 +777,59 @@ void PortsWindow::on_actionNew_Stream_triggered()
{
qDebug("New Stream Action");
// In case nothing is selected, insert 1 row at the top
int row = 0, count = 1;
QItemSelectionModel* selectionModel = tvStreamList->selectionModel();
if (selectionModel->selection().size() > 1) {
qDebug("%s: Unexpected selection size %d, can't add", __FUNCTION__,
selectionModel->selection().size());
return;
}
// In case nothing is selected, insert 1 row at the end
StreamModel *streamModel = plm->getStreamModel();
int row = streamModel->rowCount(), count = 1;
// In case we have a single range selected; insert as many rows as
// in the singe selected range before the top of the selected range
if (tvStreamList->selectionModel()->selection().size() == 1)
if (selectionModel->selection().size() == 1)
{
row = tvStreamList->selectionModel()->selection().at(0).top();
count = tvStreamList->selectionModel()->selection().at(0).height();
row = selectionModel->selection().at(0).top();
count = selectionModel->selection().at(0).height();
}
plm->getStreamModel()->insertRows(row, count);
Port &curPort = plm->port(proxyPortModel ?
proxyPortModel->mapToSource(tvPortList->currentIndex()) :
tvPortList->currentIndex());
QList<Stream*> streams;
for (int i = 0; i < count; i++)
streams.append(new Stream);
StreamConfigDialog scd(streams, curPort, this);
scd.setWindowTitle(tr("Add Stream"));
if (scd.exec() == QDialog::Accepted)
streamModel->insert(row, streams);
}
void PortsWindow::on_actionEdit_Stream_triggered()
{
qDebug("Edit Stream Action");
// Ensure we have only one range selected which contains only one row
if ((tvStreamList->selectionModel()->selection().size() == 1) &&
(tvStreamList->selectionModel()->selection().at(0).height() == 1))
{
on_tvStreamList_activated(tvStreamList->selectionModel()->
selection().at(0).topLeft());
QItemSelectionModel* streamModel = tvStreamList->selectionModel();
if (!streamModel->hasSelection())
return;
Port &curPort = plm->port(proxyPortModel ?
proxyPortModel->mapToSource(tvPortList->currentIndex()) :
tvPortList->currentIndex());
QList<Stream*> streams;
foreach(QModelIndex index, streamModel->selectedRows())
streams.append(curPort.mutableStreamByIndex(index.row(), false));
StreamConfigDialog scd(streams, curPort, this);
if (scd.exec() == QDialog::Accepted) {
curPort.recalculateAverageRates();
curPort.setLocalConfigChanged(true);
}
}

View File

@ -66,6 +66,7 @@ public slots:
void showMyReservedPortsOnly(bool enabled);
private slots:
void updateApplyHint(int portGroupId, int portId, bool configChanged);
void updatePortViewActions(const QModelIndex& currentIndex);
void updateStreamViewActions();

View File

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PortsWindow</class>
<widget class="QWidget" name="PortsWindow">
@ -21,9 +22,9 @@
<property name="childrenCollapsible">
<bool>false</bool>
</property>
<widget class="QTreeView" name="tvPortList" >
<widget class="XTreeView" name="tvPortList">
<property name="sizePolicy">
<sizepolicy vsizetype="Expanding" hsizetype="Expanding" >
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
@ -37,26 +38,90 @@
</widget>
<widget class="QStackedWidget" name="swDetail">
<property name="sizePolicy">
<sizepolicy vsizetype="Preferred" hsizetype="Preferred" >
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>2</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="currentIndex">
<number>0</number>
<number>2</number>
</property>
<widget class="QWidget" name="blankPage">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>&lt;p&gt;&lt;b&gt;Welcome to Ostinato&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;The port list on the left contains all the ports on which you can transmit packets.&lt;/p&gt;
&lt;p&gt;Ports belong to a port group. Make sure the Port Group has a &lt;img src=&quot;:/icons/bullet_green.png&quot;/&gt; next to it, then double click the port group to show or hide the ports in the port group.&lt;/p&gt;
&lt;p&gt;To generate packets, you need to create and configure packet streams. A stream is a sequence of one or more packets.&lt;/p&gt;
&lt;p&gt;To create a stream, select the port on which you want to send packets.&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;Don't see the port that you want (or any ports at all) inside the port group? &lt;a href=&quot;http://jump.ostinato.org/noports&quot;&gt;Get Help!&lt;/a&gt;&lt;/p&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="portGroupDetail">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>&lt;p&gt;You have selected a port group in the port list on the left.&lt;/p&gt;
&lt;p&gt;You can transmit packets on any of the ports within the port group.&lt;/p&gt;
&lt;p&gt;Make sure the port group has a &lt;img src=&quot;:/icons/bullet_green.png&quot;/&gt; next to it and then double click the port group to show or hide the ports in the port group.&lt;/p&gt;
&lt;p&gt;To generate packets, you need to create and configure packet streams. A stream is a sequence of one or more packets.&lt;/p&gt;
&lt;p&gt;To create a stream, select the port on which you want to send packets. &lt;/p&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>177</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="portDetail">
<layout class="QVBoxLayout">
<property name="leftMargin" >
<number>0</number>
</property>
<property name="topMargin" >
<number>0</number>
</property>
<property name="rightMargin" >
<number>0</number>
</property>
<property name="bottomMargin" >
<property name="margin">
<number>0</number>
</property>
<item>
@ -68,16 +133,7 @@
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout">
<property name="leftMargin" >
<number>3</number>
</property>
<property name="topMargin" >
<number>3</number>
</property>
<property name="rightMargin" >
<number>3</number>
</property>
<property name="bottomMargin" >
<property name="margin">
<number>3</number>
</property>
<item>
@ -85,7 +141,27 @@
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" >
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="applyHint">
<property name="text">
<string>Apply Hint</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
@ -147,7 +223,7 @@
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" >
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
@ -158,9 +234,9 @@
</layout>
</item>
<item>
<widget class="QTableView" name="tvStreamList" >
<widget class="XTableView" name="tvStreamList">
<property name="sizePolicy">
<sizepolicy vsizetype="Expanding" hsizetype="Expanding" >
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
@ -168,6 +244,13 @@
<property name="contextMenuPolicy">
<enum>Qt::ActionsContextMenu</enum>
</property>
<property name="whatsThis">
<string>This is the stream list for the selected port
A stream is a sequence of one or more packets
Right-click to create a stream</string>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
@ -190,7 +273,7 @@
</attribute>
<layout class="QVBoxLayout">
<item>
<widget class="DevicesWidget" native="1" name="devicesWidget" />
<widget class="DevicesWidget" name="devicesWidget" native="true"/>
</item>
</layout>
</widget>
@ -198,28 +281,14 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="portGroupDetail" >
<layout class="QHBoxLayout" >
<item>
<widget class="QLabel" name="label_5" >
<property name="text" >
<string>Select a port to configure streams</string>
</property>
<property name="alignment" >
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="blankPage" />
</widget>
</widget>
</item>
</layout>
<action name="actionNew_Port_Group">
<property name="icon">
<iconset resource="ostinato.qrc" >:/icons/portgroup_add.png</iconset>
<iconset resource="ostinato.qrc">
<normaloff>:/icons/portgroup_add.png</normaloff>:/icons/portgroup_add.png</iconset>
</property>
<property name="text">
<string>New Port Group</string>
@ -227,7 +296,8 @@
</action>
<action name="actionDelete_Port_Group">
<property name="icon">
<iconset resource="ostinato.qrc" >:/icons/portgroup_delete.png</iconset>
<iconset resource="ostinato.qrc">
<normaloff>:/icons/portgroup_delete.png</normaloff>:/icons/portgroup_delete.png</iconset>
</property>
<property name="text">
<string>Delete Port Group</string>
@ -235,7 +305,8 @@
</action>
<action name="actionConnect_Port_Group">
<property name="icon">
<iconset resource="ostinato.qrc" >:/icons/portgroup_connect.png</iconset>
<iconset resource="ostinato.qrc">
<normaloff>:/icons/portgroup_connect.png</normaloff>:/icons/portgroup_connect.png</iconset>
</property>
<property name="text">
<string>Connect Port Group</string>
@ -243,7 +314,8 @@
</action>
<action name="actionDisconnect_Port_Group">
<property name="icon">
<iconset resource="ostinato.qrc" >:/icons/portgroup_disconnect.png</iconset>
<iconset resource="ostinato.qrc">
<normaloff>:/icons/portgroup_disconnect.png</normaloff>:/icons/portgroup_disconnect.png</iconset>
</property>
<property name="text">
<string>Disconnect Port Group</string>
@ -251,7 +323,8 @@
</action>
<action name="actionNew_Stream">
<property name="icon">
<iconset resource="ostinato.qrc" >:/icons/stream_add.png</iconset>
<iconset resource="ostinato.qrc">
<normaloff>:/icons/stream_add.png</normaloff>:/icons/stream_add.png</iconset>
</property>
<property name="text">
<string>New Stream</string>
@ -259,7 +332,8 @@
</action>
<action name="actionDelete_Stream">
<property name="icon">
<iconset resource="ostinato.qrc" >:/icons/stream_delete.png</iconset>
<iconset resource="ostinato.qrc">
<normaloff>:/icons/stream_delete.png</normaloff>:/icons/stream_delete.png</iconset>
</property>
<property name="text">
<string>Delete Stream</string>
@ -267,7 +341,8 @@
</action>
<action name="actionEdit_Stream">
<property name="icon">
<iconset resource="ostinato.qrc" >:/icons/stream_edit.png</iconset>
<iconset resource="ostinato.qrc">
<normaloff>:/icons/stream_edit.png</normaloff>:/icons/stream_edit.png</iconset>
</property>
<property name="text">
<string>Edit Stream</string>
@ -298,7 +373,8 @@
</action>
<action name="actionDuplicate_Stream">
<property name="icon">
<iconset resource="ostinato.qrc" >:/icons/stream_duplicate.png</iconset>
<iconset resource="ostinato.qrc">
<normaloff>:/icons/stream_duplicate.png</normaloff>:/icons/stream_duplicate.png</iconset>
</property>
<property name="text">
<string>Duplicate Stream</string>
@ -312,6 +388,16 @@
<header>deviceswidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>XTreeView</class>
<extends>QTreeView</extends>
<header>xtreeview.h</header>
</customwidget>
<customwidget>
<class>XTableView</class>
<extends>QTableView</extends>
<header>xtableview.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="ostinato.qrc"/>

View File

@ -39,20 +39,36 @@ int StreamConfigDialog::lastProtocolDataIndex = 0;
static const uint kEthFrameOverHead = 20;
StreamConfigDialog::StreamConfigDialog(Port &port, uint streamIndex,
QWidget *parent) : QDialog (parent), mPort(port)
StreamConfigDialog::StreamConfigDialog(
QList<Stream*> &streamList,
const Port &port,
QWidget *parent)
: QDialog (parent), _userStreamList(streamList), mPort(port)
{
mCurrentStreamIndex = 0;
Q_ASSERT(_userStreamList.size() > 0);
// Create a copy of the user provided stream list
// We need a copy because user may edit multiple streams and then
// discard the edit - in this case the user provided stream list
// should not be modified on return
foreach(Stream* stm, _userStreamList) {
OstProto::Stream s;
mCurrentStreamIndex = streamIndex;
mpStream = new Stream;
mPort.streamByIndex(mCurrentStreamIndex)->protoDataCopyInto(s);
mpStream->protoDataCopyFrom(s);
stm->protoDataCopyInto(s);
_streamList.append(new Stream());
_streamList.last()->protoDataCopyFrom(s);
}
mpStream = _streamList.at(mCurrentStreamIndex);
_iter = mpStream->createProtocolListIterator();
isUpdateInProgress = false;
setupUi(this);
setupUiExtra();
_windowTitle = windowTitle();
for (int i = ProtoMin; i < ProtoMax; i++)
{
bgProto[i]->setProperty("ProtocolLevel", i);
@ -154,8 +170,6 @@ StreamConfigDialog::StreamConfigDialog(Port &port, uint streamIndex,
this, SLOT(when_lvSelectedProtocols_currentChanged(const QModelIndex&,
const QModelIndex&)));
variableFieldsWidget->setStream(mpStream);
LoadCurrentStream();
mpPacketModel = new PacketModel(this);
tvPacketTree->setModel(mpPacketModel);
@ -168,10 +182,9 @@ StreamConfigDialog::StreamConfigDialog(Port &port, uint streamIndex,
vwPacketDump->setModel(mpPacketModel);
vwPacketDump->setSelectionModel(tvPacketTree->selectionModel());
// TODO(MED):
//! \todo Enable navigation of streams
pbPrev->setHidden(true);
pbNext->setHidden(true);
pbPrev->setDisabled(mCurrentStreamIndex == 0);
pbNext->setDisabled(int(mCurrentStreamIndex) == (_streamList.size()-1));
//! \todo Support Goto Stream Id
leStreamId->setHidden(true);
disconnect(rbActionGotoStream, SIGNAL(toggled(bool)), leStreamId, SLOT(setEnabled(bool)));
@ -332,7 +345,14 @@ StreamConfigDialog::~StreamConfigDialog()
}
delete _iter;
delete mpStream;
while (!_streamList.isEmpty())
delete _streamList.takeFirst();
}
void StreamConfigDialog::setWindowTitle(const QString &title)
{
_windowTitle = title;
QDialog::setWindowTitle(title);
}
void StreamConfigDialog::loadProtocolWidgets()
@ -413,30 +433,6 @@ void StreamConfigDialog::on_cmbPktLenMode_currentIndexChanged(QString mode)
}
}
void StreamConfigDialog::on_pbPrev_clicked()
{
#if 0
StoreCurrentStream(currStreamIdx);
currStreamIdx--;
LoadCurrentStream(currStreamIdx);
pbPrev->setDisabled((currStreamIdx == 0));
pbNext->setDisabled((currStreamIdx == 2));
#endif
}
void StreamConfigDialog::on_pbNext_clicked()
{
#if 0
StoreCurrentStream(currStreamIdx);
currStreamIdx++;
LoadCurrentStream(currStreamIdx);
pbPrev->setDisabled((currStreamIdx == 0));
pbNext->setDisabled((currStreamIdx == 2));
#endif
}
void StreamConfigDialog::on_tbSelectProtocols_currentChanged(int index)
{
qDebug("%s, index = %d", __FUNCTION__, index);
@ -990,9 +986,16 @@ void StreamConfigDialog::LoadCurrentStream()
QString str;
qDebug("loading mpStream %p", mpStream);
variableFieldsWidget->setStream(mpStream);
QDialog::setWindowTitle(QString("%1 [%2]").arg(_windowTitle)
.arg(mpStream->name().isEmpty() ?
tr("<unnamed>") : mpStream->name()));
// Meta Data
{
name->setText(mpStream->name());
enabled->setChecked(mpStream->isEnabled());
cmbPktLenMode->setCurrentIndex(mpStream->lenMode());
lePktLen->setText(str.setNum(mpStream->frameLen()));
lePktLenMin->setText(str.setNum(mpStream->frameLenMin()));
@ -1009,6 +1012,7 @@ void StreamConfigDialog::LoadCurrentStream()
// Variable Fields
{
variableFieldsWidget->clear();
variableFieldsWidget->load();
}
@ -1077,6 +1081,8 @@ void StreamConfigDialog::StoreCurrentStream()
qDebug("storing pStream %p", pStream);
// Meta Data
pStream->setName(name->text());
pStream->setEnabled(enabled->isChecked());
pStream->setLenMode((Stream::FrameLengthMode) cmbPktLenMode->currentIndex());
pStream->setFrameLen(lePktLen->text().toULong(&isOk));
pStream->setFrameLenMin(lePktLenMin->text().toULong(&isOk));
@ -1225,41 +1231,99 @@ void StreamConfigDialog::on_leBitsPerSec_textEdited(const QString &text)
}
}
void StreamConfigDialog::on_pbOk_clicked()
bool StreamConfigDialog::isCurrentStreamValid()
{
QString log;
OstProto::Stream s;
// Store dialog contents into stream
StoreCurrentStream();
QStringList log;
if ((mPort.transmitMode() == OstProto::kInterleavedTransmit)
&& (mpStream->isFrameVariable()))
{
log += "* In 'Interleaved Streams' transmit mode, the count for "
"varying fields at transmit time may not be same as configured\n";
log << tr("In 'Interleaved Streams' transmit mode, the count for "
"varying fields at transmit time may not be same as configured");
}
if (!mPort.trackStreamStats()
&& mpStream->hasProtocol(OstProto::Protocol::kSignFieldNumber))
{
log += "* Stream contains special signature, but per stream statistics "
"will not be available till it is enabled on the port\n";
log << tr("Stream contains special signature, but per stream statistics "
"will not be available till it is enabled on the port");
}
mpStream->preflightCheck(log);
if (log.length())
if (log.size())
{
if (QMessageBox::warning(this, "Preflight Check", log + "\nContinue?",
if (QMessageBox::warning(this, "Preflight Check",
tr("<p>We found possible problems with this stream -</p>")
+ "<ul>"
+ log.replaceInStrings(QRegExp("(.*)"), "<li>\\1</li>")
.join("\n")
+ "</ul>"
+ tr("<p>Ignore?</p>"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
== QMessageBox::No)
return;
return false;
}
// Copy the data from the "local working copy of stream" to "actual stream"
mpStream->protoDataCopyInto(s);
mPort.streamByIndex(mCurrentStreamIndex)->protoDataCopyFrom(s);
return true;
}
void StreamConfigDialog::on_pbPrev_clicked()
{
Q_ASSERT(mCurrentStreamIndex > 0);
StoreCurrentStream();
if (!isCurrentStreamValid())
return;
delete _iter;
mpStream = _streamList.at(--mCurrentStreamIndex);
_iter = mpStream->createProtocolListIterator();
LoadCurrentStream();
on_twTopLevel_currentChanged(twTopLevel->currentIndex());
pbPrev->setDisabled(mCurrentStreamIndex == 0);
pbNext->setDisabled(int(mCurrentStreamIndex) == (_streamList.size()-1));
}
void StreamConfigDialog::on_pbNext_clicked()
{
Q_ASSERT(int(mCurrentStreamIndex) < (_streamList.size()-1));
StoreCurrentStream();
if (!isCurrentStreamValid())
return;
delete _iter;
mpStream = _streamList.at(++mCurrentStreamIndex);
_iter = mpStream->createProtocolListIterator();
LoadCurrentStream();
on_twTopLevel_currentChanged(twTopLevel->currentIndex());
pbPrev->setDisabled(mCurrentStreamIndex == 0);
pbNext->setDisabled(int(mCurrentStreamIndex) == (_streamList.size()-1));
}
void StreamConfigDialog::on_pbOk_clicked()
{
// Store dialog contents into current stream
StoreCurrentStream();
if (!isCurrentStreamValid())
return;
// Copy the working copy of streams to user provided streams
Q_ASSERT(_userStreamList.size() == _streamList.size());
for (int i = 0; i < _streamList.size(); i++) {
OstProto::Stream s;
_streamList.at(i)->protoDataCopyInto(s);
_userStreamList[i]->protoDataCopyFrom(s);
}
qDebug("stream stored");

View File

@ -43,9 +43,12 @@ class StreamConfigDialog : public QDialog, public Ui::StreamConfigDialog
{
Q_OBJECT
public:
StreamConfigDialog(Port &port, uint streamIndex, QWidget *parent = 0);
StreamConfigDialog(QList<Stream*> &streamList, const Port &port,
QWidget *parent = 0);
~StreamConfigDialog();
void setWindowTitle(const QString &title);
private:
enum ButtonId
@ -74,7 +77,10 @@ private:
QStringListModel *mpAvailableProtocolsModel;
QStringListModel *mpSelectedProtocolsModel;
Port& mPort;
QList<Stream*> _userStreamList;
QList<Stream*> _streamList;
const Port& mPort;
QString _windowTitle;
uint mCurrentStreamIndex;
Stream *mpStream;
@ -94,6 +100,7 @@ private:
static int lastProtocolDataIndex;
void setupUiExtra();
bool isCurrentStreamValid();
void LoadCurrentStream();
void StoreCurrentStream();
void loadProtocolWidgets();

View File

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>StreamConfigDialog</class>
<widget class="QDialog" name="StreamConfigDialog">
@ -13,7 +14,7 @@
</rect>
</property>
<property name="sizePolicy">
<sizepolicy vsizetype="Preferred" hsizetype="Preferred" >
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
@ -22,14 +23,15 @@
<string>Edit Stream</string>
</property>
<property name="windowIcon">
<iconset resource="ostinato.qrc" >:/icons/stream_edit.png</iconset>
<iconset resource="ostinato.qrc">
<normaloff>:/icons/stream_edit.png</normaloff>:/icons/stream_edit.png</iconset>
</property>
<property name="styleSheet">
<string>QLineEdit:enabled[inputMask = "HH; "],&#xd;
QLineEdit:enabled[inputMask = "HH HH; "],&#xd;
QLineEdit:enabled[inputMask = "HH HH HH; "],&#xd;
QLineEdit:enabled[inputMask = "HH HH HH HH; "], &#xd;
QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff } &#xd;
<string>QLineEdit:enabled[inputMask = &quot;HH; &quot;],
QLineEdit:enabled[inputMask = &quot;HH HH; &quot;],
QLineEdit:enabled[inputMask = &quot;HH HH HH; &quot;],
QLineEdit:enabled[inputMask = &quot;HH HH HH HH; &quot;],
QLineEdit:enabled[inputMask = &quot;HH HH HH HH HH HH; &quot;] { background-color: #ccccff }
</string>
</property>
<property name="modal">
@ -49,20 +51,36 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff
<string>Protocol Selection</string>
</attribute>
<layout class="QGridLayout">
<item row="0" column="1">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Basics</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<spacer>
<property name="orientation" >
<enum>Qt::Horizontal</enum>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Name</string>
</property>
<property name="sizeHint" >
<size>
<width>241</width>
<height>20</height>
</size>
<property name="buddy">
<cstring>name</cstring>
</property>
</spacer>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="name"/>
</item>
<item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="enabled">
<property name="text">
<string>Enabled</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="0" column="2">
<widget class="QGroupBox" name="gbFrameLength">
<property name="title">
<string>Frame Length (including FCS)</string>
@ -136,7 +154,7 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff
</layout>
</widget>
</item>
<item row="1" column="0" colspan="2" >
<item row="1" column="0" colspan="3">
<widget class="QToolBox" name="tbSelectProtocols">
<property name="currentIndex">
<number>0</number>
@ -698,7 +716,7 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" >
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
@ -712,10 +730,11 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff
<bool>false</bool>
</property>
<property name="text">
<string>></string>
<string>&gt;</string>
</property>
<property name="icon">
<iconset resource="ostinato.qrc" >:/icons/arrow_right.png</iconset>
<iconset resource="ostinato.qrc">
<normaloff>:/icons/arrow_right.png</normaloff>:/icons/arrow_right.png</iconset>
</property>
</widget>
</item>
@ -724,7 +743,7 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" >
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
@ -754,7 +773,8 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff
<string>^</string>
</property>
<property name="icon">
<iconset resource="ostinato.qrc" >:/icons/arrow_up.png</iconset>
<iconset resource="ostinato.qrc">
<normaloff>:/icons/arrow_up.png</normaloff>:/icons/arrow_up.png</iconset>
</property>
</widget>
</item>
@ -767,7 +787,8 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff
<string>v</string>
</property>
<property name="icon">
<iconset resource="ostinato.qrc" >:/icons/arrow_down.png</iconset>
<iconset resource="ostinato.qrc">
<normaloff>:/icons/arrow_down.png</normaloff>:/icons/arrow_down.png</iconset>
</property>
</widget>
</item>
@ -780,7 +801,8 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff
<string>-</string>
</property>
<property name="icon">
<iconset resource="ostinato.qrc" >:/icons/delete.png</iconset>
<iconset resource="ostinato.qrc">
<normaloff>:/icons/delete.png</normaloff>:/icons/delete.png</iconset>
</property>
</widget>
</item>
@ -789,7 +811,7 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" >
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
@ -834,7 +856,7 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff
</attribute>
<layout class="QHBoxLayout">
<item>
<widget class="VariableFieldsWidget" native="1" name="variableFieldsWidget" />
<widget class="VariableFieldsWidget" name="variableFieldsWidget" native="true"/>
</item>
</layout>
</widget>
@ -869,7 +891,7 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff
</layout>
</widget>
</item>
<item rowspan="2" row="0" column="1" >
<item row="0" column="1" rowspan="2">
<widget class="QGroupBox" name="groupBox_13">
<property name="title">
<string>Numbers</string>
@ -941,7 +963,7 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff
</layout>
</widget>
</item>
<item rowspan="2" row="0" column="2" >
<item row="0" column="2" rowspan="2">
<widget class="QGroupBox" name="groupBox_14">
<property name="title">
<string>Rate</string>
@ -1010,7 +1032,7 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff
</layout>
</widget>
</item>
<item rowspan="2" row="0" column="3" >
<item row="0" column="3" rowspan="2">
<widget class="QGroupBox" name="nextWhat">
<property name="title">
<string>After this stream</string>
@ -1053,12 +1075,12 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff
</layout>
</widget>
</item>
<item rowspan="2" row="0" column="4" >
<item row="0" column="4" rowspan="2">
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" >
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>41</height>
@ -1185,7 +1207,7 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" >
<property name="sizeHint" stdset="0">
<size>
<width>153</width>
<height>21</height>
@ -1216,7 +1238,7 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff
<bool>true</bool>
</property>
</widget>
<widget class="DumpView" native="1" name="vwPacketDump" />
<widget class="DumpView" name="vwPacketDump" native="true"/>
</widget>
</item>
</layout>
@ -1244,7 +1266,7 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" >
<property name="sizeHint" stdset="0">
<size>
<width>191</width>
<height>20</height>

View File

@ -155,19 +155,21 @@ bool StreamModel::setData(const QModelIndex &index, const QVariant &value, int r
{
// Edit Supported Fields
case StreamName:
mCurrentPort->streamByIndex(index.row())->setName(value.toString());
mCurrentPort->mutableStreamByIndex(index.row())
->setName(value.toString());
emit(dataChanged(index, index));
return true;
case StreamStatus:
mCurrentPort->streamByIndex(index.row())->setEnabled(value.toBool());
mCurrentPort->mutableStreamByIndex(index.row())
->setEnabled(value.toBool());
emit(dataChanged(index, index));
return true;
case StreamNextWhat:
if (role == Qt::EditRole)
{
mCurrentPort->streamByIndex(index.row())->setNextWhat(
mCurrentPort->mutableStreamByIndex(index.row())->setNextWhat(
(Stream::NextWhat)value.toInt());
emit(dataChanged(index, index));
return true;
@ -221,6 +223,30 @@ QVariant StreamModel::headerData(int section, Qt::Orientation orientation, int r
return QVariant();
}
/*!
* Inserts streams before the given row
*
* StreamModel takes ownership of the passed streams; caller should
* not try to access them after calling this function
*/
bool StreamModel::insert(int row, QList<Stream*> &streams)
{
int count = streams.size();
qDebug("insert row = %d", row);
qDebug("insert count = %d", count);
beginInsertRows(QModelIndex(), row, row+count-1);
for (int i = 0; i < count; i++) {
OstProto::Stream s;
streams.at(i)->protoDataCopyInto(s);
mCurrentPort->newStreamAt(row+i, &s);
delete streams.at(i);
}
streams.clear();
endInsertRows();
return true;
}
bool StreamModel::insertRows(int row, int count, const QModelIndex &/*parent*/)
{
qDebug("insertRows() row = %d", row);

View File

@ -25,6 +25,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "port.h"
class PortGroupList;
class Stream;
class StreamModel : public QAbstractTableModel
{
@ -44,6 +45,7 @@ class StreamModel : public QAbstractTableModel
int role = Qt::EditRole);
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const;
bool insert(int row, QList<Stream*> &streams);
bool insertRows (int row, int count,
const QModelIndex & parent = QModelIndex());
bool removeRows (int row, int count,

48
client/xtableview.h Normal file
View File

@ -0,0 +1,48 @@
/*
Copyright (C) 2017 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 _X_TABLE_VIEW_H
#define _X_TABLE_VIEW_H
#include <QTableView>
#include <QPainter>
class XTableView : public QTableView
{
public:
XTableView(QWidget *parent) : QTableView(parent) {}
virtual ~XTableView() {}
protected:
virtual void paintEvent(QPaintEvent *event)
{
if (!model()->hasChildren()) {
QPainter painter(viewport());
style()->drawItemText(&painter, viewport()->rect(),
layoutDirection() | Qt::AlignCenter, palette(),
true, whatsThis(), QPalette::WindowText);
}
else
QTableView::paintEvent(event);
}
};
#endif

50
client/xtreeview.h Normal file
View File

@ -0,0 +1,50 @@
/*
Copyright (C) 2017 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 _X_TREE_VIEW_H
#define _X_TREE_VIEW_H
#include <QTreeView>
#include <QMouseEvent>
#if QT_VERSION >= 0x050000
#error "Do we even need this anymore?"
#endif
class XTreeView : public QTreeView
{
public:
XTreeView(QWidget *parent) : QTreeView(parent) {}
virtual ~XTreeView() {}
private:
virtual void mousePressEvent(QMouseEvent *event)
{
QModelIndex item = indexAt(event->pos());
if (!item.isValid())
setCurrentIndex(QModelIndex());
QTreeView::mousePressEvent(event);
}
};
#endif

View File

@ -1,3 +1,4 @@
/// (802.2 LLC)
/*
Copyright (C) 2010 Srivats P.
@ -23,7 +24,6 @@ import "llc.proto";
package OstProto;
// 802.2 LLC
message Dot2Llc {
// Empty since this is a 'combo' protocol
}

View File

@ -1,3 +1,4 @@
/// (802.2 SNAP)
/*
Copyright (C) 2010 Srivats P.

View File

@ -1,3 +1,4 @@
/// (802.3)
/*
Copyright (C) 2010 Srivats P.

View File

@ -88,7 +88,7 @@ message File {
// FieldNumber 1 is reserved and MUST not be used!
required bytes magic_value = 2;
required FileMetaData meta_data = 3;
optional FileContent content_matter = 9;
optional FileContentMatter content_matter = 9;
required fixed32 checksum_value = 15;
}

View File

@ -21,7 +21,7 @@ import "protocol.proto";
package OstProto;
// Group Management Protocol (i.e. IGMP and MLD)
/// Group Management Protocol (i.e. IGMP and MLD)
message Gmp {
//
// Common fields for both ASM and SSM messages

View File

@ -112,6 +112,7 @@ AbstractProtocol::FieldFlags Ip4Protocol::fieldFlags(int index) const
case ip4_srcAddr:
case ip4_dstAddr:
case ip4_options:
break;
case ip4_isOverrideVer:
@ -168,7 +169,9 @@ QVariant Ip4Protocol::fieldData(int index, FieldAttrib attrib,
{
int hdrlen;
hdrlen = data.is_override_hdrlen() ? data.ver_hdrlen() & 0x0F : 5;
hdrlen = data.is_override_hdrlen() ?
data.ver_hdrlen() : 5 + data.options().length()/4;
hdrlen &= 0x0F;
switch(attrib)
{
@ -205,6 +208,7 @@ QVariant Ip4Protocol::fieldData(int index, FieldAttrib attrib,
break;
case ip4_totLen:
{
int ipLen = 20 + data.options().length();
switch(attrib)
{
case FieldName:
@ -213,7 +217,7 @@ QVariant Ip4Protocol::fieldData(int index, FieldAttrib attrib,
{
int totlen;
totlen = data.is_override_totlen() ? data.totlen() :
(protocolFramePayloadSize(streamIndex) + 20);
(protocolFramePayloadSize(streamIndex) + ipLen);
return totlen;
}
case FieldFrameValue:
@ -221,7 +225,7 @@ QVariant Ip4Protocol::fieldData(int index, FieldAttrib attrib,
QByteArray fv;
int totlen;
totlen = data.is_override_totlen() ? data.totlen() :
(protocolFramePayloadSize(streamIndex) + 20);
(protocolFramePayloadSize(streamIndex) + ipLen);
fv.resize(2);
qToBigEndian((quint16) totlen, (uchar*) fv.data());
return fv;
@ -230,7 +234,7 @@ QVariant Ip4Protocol::fieldData(int index, FieldAttrib attrib,
{
int totlen;
totlen = data.is_override_totlen() ? data.totlen() :
(protocolFramePayloadSize(streamIndex) + 20);
(protocolFramePayloadSize(streamIndex) + ipLen);
return QString("%1").arg(totlen);
}
case FieldBitSize:
@ -509,6 +513,32 @@ QVariant Ip4Protocol::fieldData(int index, FieldAttrib attrib,
}
break;
}
case ip4_options:
{
QByteArray ba;
switch(attrib)
{
case FieldValue:
case FieldFrameValue:
case FieldTextValue:
ba.append(QString().fromStdString(data.options()));
default:
break;
}
switch(attrib)
{
case FieldName:
return QString("Options");
case FieldValue:
case FieldFrameValue:
return ba;
case FieldTextValue:
return ba.toHex();
default:
break;
}
break;
}
// Meta fields
case ip4_isOverrideVer:
@ -695,6 +725,16 @@ bool Ip4Protocol::setFieldData(int index, const QVariant &value,
data.set_dst_ip(dstIp);
break;
}
case ip4_options:
{
QByteArray ba = value.toByteArray();
int pad = (4 - (ba.size() % 4)) % 4;
if (pad)
ba.append(QByteArray(pad, 0));
data.set_options(ba.constData(), ba.size());
isOk = true;
break;
}
// Meta-fields
case ip4_isOverrideVer:

View File

@ -44,6 +44,7 @@ public:
ip4_cksum,
ip4_srcAddr,
ip4_dstAddr,
ip4_options,
// Meta-fields
ip4_isOverrideVer,

View File

@ -58,7 +58,7 @@ message Ip4 {
optional uint32 dst_ip_count = 20 [default = 16];
optional fixed32 dst_ip_mask = 21 [default = 0xFFFFFF00];
//! \todo (LOW) IPv4 Options
optional bytes options = 22;
}
extend Protocol {

View File

@ -366,14 +366,7 @@ Length (x4)</string>
</widget>
</item>
<item>
<widget class="QLineEdit" name="leIpOptions" >
<property name="enabled" >
<bool>false</bool>
</property>
<property name="text" >
<string>TODO</string>
</property>
</widget>
<widget class="QLineEdit" name="leIpOptions" />
</item>
<item>
<widget class="QToolButton" name="tbIpOptionsEdit" >

View File

@ -28,6 +28,8 @@ Ip4ConfigForm::Ip4ConfigForm(QWidget *parent)
setupUi(this);
leIpVersion->setValidator(new QIntValidator(0, 15, this));
leIpOptions->setValidator(new QRegExpValidator(QRegExp("[0-9a-fA-F]*"),
this));
connect(cmbIpSrcAddrMode, SIGNAL(currentIndexChanged(int)),
this, SLOT(on_cmbIpSrcAddrMode_currentIndexChanged(int)));
@ -176,6 +178,11 @@ void Ip4ConfigForm::loadWidget(AbstractProtocol *proto)
Ip4Protocol::ip4_dstAddrMask,
AbstractProtocol::FieldValue
).toUInt()).toString());
leIpOptions->setText(
proto->fieldData(
Ip4Protocol::ip4_options,
AbstractProtocol::FieldValue
).toByteArray().toHex());
}
void Ip4ConfigForm::storeWidget(AbstractProtocol *proto)
@ -263,6 +270,9 @@ void Ip4ConfigForm::storeWidget(AbstractProtocol *proto)
proto->setFieldData(
Ip4Protocol::ip4_dstAddrMask,
QHostAddress(leIpDstAddrMask->text()).toIPv4Address());
proto->setFieldData(
Ip4Protocol::ip4_options,
QByteArray::fromHex(QByteArray().append(leIpOptions->text())));
}
/*

View File

@ -26,7 +26,6 @@ PdmlIp4Protocol::PdmlIp4Protocol()
{
ostProtoId_ = OstProto::Protocol::kIp4FieldNumber;
fieldMap_.insert("ip.version", OstProto::Ip4::kVerHdrlenFieldNumber);
fieldMap_.insert("ip.dsfield", OstProto::Ip4::kTosFieldNumber);
fieldMap_.insert("ip.len", OstProto::Ip4::kTotlenFieldNumber);
fieldMap_.insert("ip.id", OstProto::Ip4::kIdFieldNumber);
@ -50,7 +49,18 @@ void PdmlIp4Protocol::unknownFieldHandler(QString name, int /*pos*/,
{
bool isOk;
if ((name == "ip.options") ||
if (name == "ip.version")
{
OstProto::Ip4 *ip4 = pbProto->MutableExtension(OstProto::ip4);
if (!attributes.value("unmaskedvalue").isEmpty())
ip4->set_ver_hdrlen(attributes.value("unmaskedvalue")
.toString().toUInt(&isOk, kBaseHex));
else
ip4->set_ver_hdrlen(attributes.value("value")
.toString().toUInt(&isOk, kBaseHex));
}
else if ((name == "ip.options") ||
attributes.value("show").toString().startsWith("Options"))
{
options_ = QByteArray::fromHex(

View File

@ -26,7 +26,9 @@ PdmlIp6Protocol::PdmlIp6Protocol()
ostProtoId_ = OstProto::Protocol::kIp6FieldNumber;
fieldMap_.insert("ipv6.version", OstProto::Ip6::kVersionFieldNumber);
// Tshark 1.x uses .class while 2.x uses .tclass - we'll use either
fieldMap_.insert("ipv6.class", OstProto::Ip6::kTrafficClassFieldNumber);
fieldMap_.insert("ipv6.tclass", OstProto::Ip6::kTrafficClassFieldNumber);
fieldMap_.insert("ipv6.flow", OstProto::Ip6::kFlowLabelFieldNumber);
fieldMap_.insert("ipv6.plen", OstProto::Ip6::kPayloadLengthFieldNumber);
fieldMap_.insert("ipv6.nxt", OstProto::Ip6::kNextHeaderFieldNumber);

View File

@ -143,7 +143,7 @@ QVariant MacProtocol::fieldData(int index, FieldAttrib attrib,
switch(attrib)
{
case FieldName:
return QString("Desination");
return QString("Destination");
case FieldValue:
return dstMac;
case FieldTextValue:
@ -278,15 +278,13 @@ bool MacProtocol::setFieldData(int index, const QVariant &value,
{
case mac_dstAddr:
{
quint64 mac = value.toString().toULongLong(&isOk, BASE_HEX);
if (isOk)
quint64 mac = value.toULongLong();
data.set_dst_mac(mac);
break;
}
case mac_srcAddr:
{
quint64 mac = value.toString().toULongLong(&isOk, BASE_HEX);
if (isOk)
quint64 mac = value.toULongLong();
data.set_src_mac(mac);
break;
}

View File

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>mac</class>
<widget class="QWidget" name="mac">
@ -49,19 +50,13 @@
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="leDstMac" >
<widget class="MacEdit" name="leDstMac">
<property name="minimumSize">
<size>
<width>120</width>
<height>0</height>
</size>
</property>
<property name="inputMask" >
<string>>HH HH HH HH HH HH; </string>
</property>
<property name="text" >
<string> </string>
</property>
</widget>
</item>
<item row="1" column="2">
@ -89,29 +84,17 @@
</widget>
</item>
<item row="1" column="3">
<widget class="QLineEdit" name="leDstMacCount" >
<widget class="IntEdit" name="leDstMacCount">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text" >
<string/>
</property>
<property name="cursorPosition" >
<number>0</number>
</property>
</widget>
</item>
<item row="1" column="4">
<widget class="QLineEdit" name="leDstMacStep" >
<widget class="IntEdit" name="leDstMacStep">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text" >
<string/>
</property>
<property name="cursorPosition" >
<number>0</number>
</property>
</widget>
</item>
<item row="2" column="0">
@ -122,14 +105,7 @@
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="leSrcMac" >
<property name="inputMask" >
<string>>HH HH HH HH HH HH; </string>
</property>
<property name="text" >
<string> </string>
</property>
</widget>
<widget class="MacEdit" name="leSrcMac"/>
</item>
<item row="2" column="2">
<widget class="QComboBox" name="cmbSrcMacMode">
@ -156,26 +132,17 @@
</widget>
</item>
<item row="2" column="3">
<widget class="QLineEdit" name="leSrcMacCount" >
<widget class="IntEdit" name="leSrcMacCount">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text" >
<string/>
</property>
</widget>
</item>
<item row="2" column="4">
<widget class="QLineEdit" name="leSrcMacStep" >
<widget class="IntEdit" name="leSrcMacStep">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text" >
<string/>
</property>
<property name="cursorPosition" >
<number>0</number>
</property>
</widget>
</item>
<item row="3" column="0" colspan="5">
@ -193,7 +160,7 @@
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" >
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
@ -203,6 +170,18 @@
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>IntEdit</class>
<extends>QSpinBox</extends>
<header>intedit.h</header>
</customwidget>
<customwidget>
<class>MacEdit</class>
<extends>QLineEdit</extends>
<header>macedit.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -20,12 +20,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>
#include "macconfig.h"
#include "mac.h"
#define MAX_MAC_ITER_COUNT 256
MacConfigForm::MacConfigForm(QWidget *parent)
: AbstractProtocolConfigForm(parent)
{
QRegExp reMac("([0-9,a-f,A-F]{2,2}[:-]){5,5}[0-9,a-f,A-F]{2,2}");
setupUi(this);
resolveInfo->hide();
@ -34,10 +31,10 @@ MacConfigForm::MacConfigForm(QWidget *parent)
resolveInfo->setPixmap(resolveInfo->style()->standardIcon(
QStyle::SP_MessageBoxInformation).pixmap(128));
#endif
leDstMac->setValidator(new QRegExpValidator(reMac, this));
leSrcMac->setValidator(new QRegExpValidator(reMac, this));
leDstMacCount->setValidator(new QIntValidator(1, MAX_MAC_ITER_COUNT, this));
leSrcMacCount->setValidator(new QIntValidator(1, MAX_MAC_ITER_COUNT, this));
leDstMacCount->setMinimum(1);
leSrcMacCount->setMinimum(1);
leDstMacStep->setMinimum(0);
leSrcMacStep->setMinimum(0);
}
MacConfigForm::~MacConfigForm()
@ -100,75 +97,75 @@ void MacConfigForm::on_cmbSrcMacMode_currentIndexChanged(int index)
void MacConfigForm::loadWidget(AbstractProtocol *proto)
{
leDstMac->setText(
leDstMac->setValue(
proto->fieldData(
MacProtocol::mac_dstAddr,
AbstractProtocol::FieldTextValue
).toString());
AbstractProtocol::FieldValue
).toULongLong());
cmbDstMacMode->setCurrentIndex(
proto->fieldData(
MacProtocol::mac_dstMacMode,
AbstractProtocol::FieldValue
).toUInt());
leDstMacCount->setText(
leDstMacCount->setValue(
proto->fieldData(
MacProtocol::mac_dstMacCount,
AbstractProtocol::FieldValue
).toString());
leDstMacStep->setText(
).toUInt());
leDstMacStep->setValue(
proto->fieldData(
MacProtocol::mac_dstMacStep,
AbstractProtocol::FieldValue
).toString());
).toUInt());
leSrcMac->setText(
leSrcMac->setValue(
proto->fieldData(
MacProtocol::mac_srcAddr,
AbstractProtocol::FieldTextValue
).toString());
AbstractProtocol::FieldValue
).toULongLong());
cmbSrcMacMode->setCurrentIndex(
proto->fieldData(
MacProtocol::mac_srcMacMode,
AbstractProtocol::FieldValue
).toUInt());
leSrcMacCount->setText(
leSrcMacCount->setValue(
proto->fieldData(
MacProtocol::mac_srcMacCount,
AbstractProtocol::FieldValue
).toString());
leSrcMacStep->setText(
).toUInt());
leSrcMacStep->setValue(
proto->fieldData(
MacProtocol::mac_srcMacStep,
AbstractProtocol::FieldValue
).toString());
).toUInt());
}
void MacConfigForm::storeWidget(AbstractProtocol *proto)
{
proto->setFieldData(
MacProtocol::mac_dstAddr,
leDstMac->text().remove(QChar(' ')));
leDstMac->value());
proto->setFieldData(
MacProtocol::mac_dstMacMode,
cmbDstMacMode->currentIndex());
proto->setFieldData(
MacProtocol::mac_dstMacCount,
leDstMacCount->text());
leDstMacCount->value());
proto->setFieldData(
MacProtocol::mac_dstMacStep,
leDstMacStep->text());
leDstMacStep->value());
proto->setFieldData(
MacProtocol::mac_srcAddr,
leSrcMac->text().remove(QChar(' ')));
leSrcMac->value());
proto->setFieldData(
MacProtocol::mac_srcMacMode,
cmbSrcMacMode->currentIndex());
proto->setFieldData(
MacProtocol::mac_srcMacCount,
leSrcMacCount->text());
leSrcMacCount->value());
proto->setFieldData(
MacProtocol::mac_srcMacStep,
leSrcMacStep->text());
leSrcMacStep->value());
}

View File

@ -52,8 +52,8 @@ message StreamCore {
optional bool is_enabled = 2;
optional uint32 ordinal = 3;
// Frame Length (includes CRC)
optional FrameLengthMode len_mode = 14 [default = e_fl_fixed];
/// Frame Length (includes CRC)
optional uint32 frame_len = 15 [default = 64];
optional uint32 frame_len_min = 16 [default = 64];
optional uint32 frame_len_max = 17 [default = 1518];
@ -78,7 +78,7 @@ message StreamControl {
optional SendUnit unit = 1 [default = e_su_packets];
optional SendMode mode = 2 [default = e_sm_fixed];
optional uint32 num_packets = 3 [default = 1];
optional uint32 num_packets = 3 [default = 10];
optional uint32 num_bursts = 4 [default = 1];
optional uint32 packets_per_burst = 5 [default = 10];
optional NextWhat next = 6 [default = e_nw_goto_next];

View File

@ -40,5 +40,5 @@ message Stp {
}
extend Protocol {
optional Stp stp = 210;
optional Stp stp = 209;
}

View File

@ -154,7 +154,7 @@ ProtocolListIterator* StreamBase::createProtocolListIterator() const
return new ProtocolListIterator(*currentFrameProtocols);
}
quint32 StreamBase::id()
quint32 StreamBase::id() const
{
return mStreamId->id();
}
@ -598,7 +598,7 @@ quint64 StreamBase::neighborMacAddress(int frameIndex) const
All errors found are returned. However, each type of error is reported
only once, even if multiple packets may have that error.
*/
bool StreamBase::preflightCheck(QString &result) const
bool StreamBase::preflightCheck(QStringList &result) const
{
bool pass = true;
bool chkTrunc = true;
@ -611,8 +611,8 @@ bool StreamBase::preflightCheck(QString &result) const
if (chkTrunc && (pktLen < (frameProtocolLength(i) + kFcsSize)))
{
result += QString("* One or more frames may be truncated - "
"frame length should be at least %1\n")
result << QObject::tr("One or more frames may be truncated - "
"frame length should be at least %1")
.arg(frameProtocolLength(i) + kFcsSize);
chkTrunc = false;
pass = false;
@ -620,9 +620,8 @@ bool StreamBase::preflightCheck(QString &result) const
if (chkJumbo && (pktLen > 1522))
{
result += QString("* Jumbo frames may be truncated or dropped "
"if not supported by the hardware\n");
chkJumbo = false;
result << QObject::tr("Jumbo frames may be truncated or dropped "
"if not supported by the hardware");
pass = false;
}
@ -632,6 +631,20 @@ bool StreamBase::preflightCheck(QString &result) const
break;
}
if (frameCount() <= averagePacketRate() && nextWhat() != e_nw_goto_id)
{
result << QObject::tr("Only %L1 frames at the rate of "
"%L2 frames/sec are configured to be transmitted. "
"Transmission will last for only %L3 second - "
"to transmit for a longer duration, "
"increase the number of packets (bursts) and/or "
"set the 'After this stream' action as 'Goto First'")
.arg(frameCount())
.arg(averagePacketRate(), 0, 'f', 2)
.arg(frameCount()/averagePacketRate(), 0, 'f');
pass = false;
}
return pass;
}

View File

@ -69,7 +69,7 @@ public:
e_nw_goto_id
};
quint32 id();
quint32 id() const;
bool setId(quint32 id);
#if 0 // FIXME(HI): needed?
@ -141,7 +141,7 @@ public:
quint64 deviceMacAddress(int frameIndex) const;
quint64 neighborMacAddress(int frameIndex) const;
bool preflightCheck(QString &result) const;
bool preflightCheck(QStringList &result) const;
static bool StreamLessThan(StreamBase* stream1, StreamBase* stream2);

View File

@ -55,7 +55,7 @@ void TcpProtocol::protoDataCopyFrom(const OstProto::Protocol &protocol)
QString TcpProtocol::name() const
{
return QString("Transmission Control Protocol");
return QString("Transmission Control Protocol (state less)");
}
QString TcpProtocol::shortName() const

View File

@ -196,6 +196,29 @@
</property>
</spacer>
</item>
<item row="7" column="0" colspan="5">
<widget class="QLabel" name="label">
<property name="text">
<string>&lt;i&gt;Note: Ostinato is stateless- it cannot establish a TCP connection and generate seq/ack numbers accordingly&lt;/i&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="8" column="2" colspan="2" >
<spacer>
<property name="orientation" >
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" >
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>

View File

@ -37,6 +37,7 @@ Updater::Updater()
Q_ASSERT(isVersionNewer("10.1", "2") == true);
Q_ASSERT(isVersionNewer("0.10", "0.2") == true);
Q_ASSERT(isVersionNewer("1.10.1", "1.2.3") == true);
Q_ASSERT(isVersionNewer("0.7.1", "0.8") == false);
#endif
}
@ -54,7 +55,7 @@ void Updater::checkForNewVersion()
file_ = new QTemporaryFile();
reqHdr.setValue("Host", host);
reqHdr.setValue("UserAgent", userAgent());
reqHdr.setValue("User-Agent", userAgent());
connect(http_, SIGNAL(responseHeaderReceived(QHttpResponseHeader)),
this, SLOT(responseReceived(QHttpResponseHeader)));
@ -120,8 +121,12 @@ bool Updater::isVersionNewer(QString newVersion, QString curVersion)
for (int i = 0; i < qMin(curVer.size(), newVer.size()); i++) {
bool isOk;
if (newVer.at(i).toUInt(&isOk) > curVer.at(i).toUInt(&isOk))
uint n = newVer.at(i).toUInt(&isOk);
uint c = curVer.at(i).toUInt(&isOk);
if (n > c)
return true;
else if (n < c)
return false;
}
if (newVer.size() > curVer.size())

View File

@ -1,12 +1,17 @@
TEMPLATE = lib
CONFIG += qt staticlib warn_on
QT += core gui
HEADERS = src/commands.h\
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
VERSION = 4.0.0
DEFINES += QHEXEDIT_EXPORTS
HEADERS = src/chunks.h\
src/commands.h \
src/qhexedit.h \
src/qhexedit_p.h \
src/xbytearray.h
SOURCES = src/commands.cpp \
src/qhexedit.cpp \
src/qhexedit_p.cpp \
src/xbytearray.cpp
SOURCES = src/chunks.cpp \
src/commands.cpp \
src/qhexedit.cpp

View File

@ -0,0 +1 @@
Release 0.8.4, 2017-01-16

View File

@ -0,0 +1,323 @@
#include "chunks.h"
#include <limits.h>
#define NORMAL 0
#define HIGHLIGHTED 1
#define BUFFER_SIZE 0x10000
#define CHUNK_SIZE 0x1000
#define READ_CHUNK_MASK Q_INT64_C(0xfffffffffffff000)
// ***************************************** Constructors and file settings
Chunks::Chunks(QObject *parent): QObject(parent)
{
QBuffer *buf = new QBuffer(this);
setIODevice(*buf);
}
Chunks::Chunks(QIODevice &ioDevice, QObject *parent): QObject(parent)
{
setIODevice(ioDevice);
}
bool Chunks::setIODevice(QIODevice &ioDevice)
{
_ioDevice = &ioDevice;
bool ok = _ioDevice->open(QIODevice::ReadOnly);
if (ok) // Try to open IODevice
{
_size = _ioDevice->size();
_ioDevice->close();
}
else // Fallback is an empty buffer
{
QBuffer *buf = new QBuffer(this);
_ioDevice = buf;
_size = 0;
}
_chunks.clear();
_pos = 0;
return ok;
}
// ***************************************** Getting data out of Chunks
QByteArray Chunks::data(qint64 pos, qint64 maxSize, QByteArray *highlighted)
{
qint64 ioDelta = 0;
int chunkIdx = 0;
Chunk chunk;
QByteArray buffer;
// Do some checks and some arrangements
if (highlighted)
highlighted->clear();
if (pos >= _size)
return buffer;
if (maxSize < 0)
maxSize = _size;
else
if ((pos + maxSize) > _size)
maxSize = _size - pos;
_ioDevice->open(QIODevice::ReadOnly);
while (maxSize > 0)
{
chunk.absPos = LLONG_MAX;
bool chunksLoopOngoing = true;
while ((chunkIdx < _chunks.count()) && chunksLoopOngoing)
{
// In this section, we track changes before our required data and
// we take the editdet data, if availible. ioDelta is a difference
// counter to justify the read pointer to the original data, if
// data in between was deleted or inserted.
chunk = _chunks[chunkIdx];
if (chunk.absPos > pos)
chunksLoopOngoing = false;
else
{
chunkIdx += 1;
qint64 count;
qint64 chunkOfs = pos - chunk.absPos;
if (maxSize > ((qint64)chunk.data.size() - chunkOfs))
{
count = (qint64)chunk.data.size() - chunkOfs;
ioDelta += CHUNK_SIZE - chunk.data.size();
}
else
count = maxSize;
if (count > 0)
{
buffer += chunk.data.mid(chunkOfs, (int)count);
maxSize -= count;
pos += count;
if (highlighted)
*highlighted += chunk.dataChanged.mid(chunkOfs, (int)count);
}
}
}
if ((maxSize > 0) && (pos < chunk.absPos))
{
// In this section, we read data from the original source. This only will
// happen, whe no copied data is available
qint64 byteCount;
QByteArray readBuffer;
if ((chunk.absPos - pos) > maxSize)
byteCount = maxSize;
else
byteCount = chunk.absPos - pos;
maxSize -= byteCount;
_ioDevice->seek(pos + ioDelta);
readBuffer = _ioDevice->read(byteCount);
buffer += readBuffer;
if (highlighted)
*highlighted += QByteArray(readBuffer.size(), NORMAL);
pos += readBuffer.size();
}
}
_ioDevice->close();
return buffer;
}
bool Chunks::write(QIODevice &iODevice, qint64 pos, qint64 count)
{
if (count == -1)
count = _size;
bool ok = iODevice.open(QIODevice::WriteOnly);
if (ok)
{
for (qint64 idx=pos; idx < count; idx += BUFFER_SIZE)
{
QByteArray ba = data(idx, BUFFER_SIZE);
iODevice.write(ba);
}
iODevice.close();
}
return ok;
}
// ***************************************** Set and get highlighting infos
void Chunks::setDataChanged(qint64 pos, bool dataChanged)
{
if ((pos < 0) || (pos >= _size))
return;
int chunkIdx = getChunkIndex(pos);
qint64 posInBa = pos - _chunks[chunkIdx].absPos;
_chunks[chunkIdx].dataChanged[(int)posInBa] = char(dataChanged);
}
bool Chunks::dataChanged(qint64 pos)
{
QByteArray highlighted;
data(pos, 1, &highlighted);
return bool(highlighted.at(0));
}
// ***************************************** Search API
qint64 Chunks::indexOf(const QByteArray &ba, qint64 from)
{
qint64 result = -1;
QByteArray buffer;
for (qint64 pos=from; (pos < _size) && (result < 0); pos += BUFFER_SIZE)
{
buffer = data(pos, BUFFER_SIZE + ba.size() - 1);
int findPos = buffer.indexOf(ba);
if (findPos >= 0)
result = pos + (qint64)findPos;
}
return result;
}
qint64 Chunks::lastIndexOf(const QByteArray &ba, qint64 from)
{
qint64 result = -1;
QByteArray buffer;
for (qint64 pos=from; (pos > 0) && (result < 0); pos -= BUFFER_SIZE)
{
qint64 sPos = pos - BUFFER_SIZE - (qint64)ba.size() + 1;
if (sPos < 0)
sPos = 0;
buffer = data(sPos, pos - sPos);
int findPos = buffer.lastIndexOf(ba);
if (findPos >= 0)
result = sPos + (qint64)findPos;
}
return result;
}
// ***************************************** Char manipulations
bool Chunks::insert(qint64 pos, char b)
{
if ((pos < 0) || (pos > _size))
return false;
int chunkIdx;
if (pos == _size)
chunkIdx = getChunkIndex(pos-1);
else
chunkIdx = getChunkIndex(pos);
qint64 posInBa = pos - _chunks[chunkIdx].absPos;
_chunks[chunkIdx].data.insert(posInBa, b);
_chunks[chunkIdx].dataChanged.insert(posInBa, char(1));
for (int idx=chunkIdx+1; idx < _chunks.size(); idx++)
_chunks[idx].absPos += 1;
_size += 1;
_pos = pos;
return true;
}
bool Chunks::overwrite(qint64 pos, char b)
{
if ((pos < 0) || (pos >= _size))
return false;
int chunkIdx = getChunkIndex(pos);
qint64 posInBa = pos - _chunks[chunkIdx].absPos;
_chunks[chunkIdx].data[(int)posInBa] = b;
_chunks[chunkIdx].dataChanged[(int)posInBa] = char(1);
_pos = pos;
return true;
}
bool Chunks::removeAt(qint64 pos)
{
if ((pos < 0) || (pos >= _size))
return false;
int chunkIdx = getChunkIndex(pos);
qint64 posInBa = pos - _chunks[chunkIdx].absPos;
_chunks[chunkIdx].data.remove(posInBa, 1);
_chunks[chunkIdx].dataChanged.remove(posInBa, 1);
for (int idx=chunkIdx+1; idx < _chunks.size(); idx++)
_chunks[idx].absPos -= 1;
_size -= 1;
_pos = pos;
return true;
}
// ***************************************** Utility functions
char Chunks::operator[](qint64 pos)
{
return data(pos, 1)[0];
}
qint64 Chunks::pos()
{
return _pos;
}
qint64 Chunks::size()
{
return _size;
}
int Chunks::getChunkIndex(qint64 absPos)
{
// This routine checks, if there is already a copied chunk available. If os, it
// returns a reference to it. If there is no copied chunk available, original
// data will be copied into a new chunk.
int foundIdx = -1;
int insertIdx = 0;
qint64 ioDelta = 0;
for (int idx=0; idx < _chunks.size(); idx++)
{
Chunk chunk = _chunks[idx];
if ((absPos >= chunk.absPos) && (absPos < (chunk.absPos + chunk.data.size())))
{
foundIdx = idx;
break;
}
if (absPos < chunk.absPos)
{
insertIdx = idx;
break;
}
ioDelta += chunk.data.size() - CHUNK_SIZE;
insertIdx = idx + 1;
}
if (foundIdx == -1)
{
Chunk newChunk;
qint64 readAbsPos = absPos - ioDelta;
qint64 readPos = (readAbsPos & READ_CHUNK_MASK);
_ioDevice->open(QIODevice::ReadOnly);
_ioDevice->seek(readPos);
newChunk.data = _ioDevice->read(CHUNK_SIZE);
_ioDevice->close();
newChunk.absPos = absPos - (readAbsPos - readPos);
newChunk.dataChanged = QByteArray(newChunk.data.size(), char(0));
_chunks.insert(insertIdx, newChunk);
foundIdx = insertIdx;
}
return foundIdx;
}
#ifdef MODUL_TEST
int Chunks::chunkSize()
{
return _chunks.size();
}
#endif

View File

@ -0,0 +1,77 @@
#ifndef CHUNKS_H
#define CHUNKS_H
/** \cond docNever */
/*! The Chunks class is the storage backend for QHexEdit.
*
* When QHexEdit loads data, Chunks access them using a QIODevice interface. When the app uses
* a QByteArray interface, QBuffer is used to provide again a QIODevice like interface. No data
* will be changed, therefore Chunks opens the QIODevice in QIODevice::ReadOnly mode. After every
* access Chunks closes the QIODevice, that's why external applications can overwrite files while
* QHexEdit shows them.
*
* When the the user starts to edit the data, Chunks creates a local copy of a chunk of data (4
* kilobytes) and notes all changes there. Parallel to that chunk, there is a second chunk,
* which keep track of which bytes are changed and which not.
*
*/
#include <QtCore>
struct Chunk
{
QByteArray data;
QByteArray dataChanged;
qint64 absPos;
};
class Chunks: public QObject
{
Q_OBJECT
public:
// Constructors and file settings
Chunks(QObject *parent);
Chunks(QIODevice &ioDevice, QObject *parent);
bool setIODevice(QIODevice &ioDevice);
// Getting data out of Chunks
QByteArray data(qint64 pos=0, qint64 count=-1, QByteArray *highlighted=0);
bool write(QIODevice &iODevice, qint64 pos=0, qint64 count=-1);
// Set and get highlighting infos
void setDataChanged(qint64 pos, bool dataChanged);
bool dataChanged(qint64 pos);
// Search API
qint64 indexOf(const QByteArray &ba, qint64 from);
qint64 lastIndexOf(const QByteArray &ba, qint64 from);
// Char manipulations
bool insert(qint64 pos, char b);
bool overwrite(qint64 pos, char b);
bool removeAt(qint64 pos);
// Utility functions
char operator[](qint64 pos);
qint64 pos();
qint64 size();
private:
int getChunkIndex(qint64 absPos);
QIODevice * _ioDevice;
qint64 _pos;
qint64 _size;
QList<Chunk> _chunks;
#ifdef MODUL_TEST
public:
int chunkSize();
#endif
};
/** \endcond docNever */
#endif // CHUNKS_H

View File

@ -1,9 +1,34 @@
#include "commands.h"
#include <QUndoCommand>
CharCommand::CharCommand(XByteArray * xData, Cmd cmd, int charPos, char newChar, QUndoCommand *parent)
// Helper class to store single byte commands
class CharCommand : public QUndoCommand
{
public:
enum CCmd {insert, removeAt, overwrite};
CharCommand(Chunks * chunks, CCmd cmd, qint64 charPos, char newChar,
QUndoCommand *parent=0);
void undo();
void redo();
bool mergeWith(const QUndoCommand *command);
int id() const { return 1234; }
private:
Chunks * _chunks;
qint64 _charPos;
bool _wasChanged;
char _newChar;
char _oldChar;
CCmd _cmd;
};
CharCommand::CharCommand(Chunks * chunks, CCmd cmd, qint64 charPos, char newChar, QUndoCommand *parent)
: QUndoCommand(parent)
{
_xData = xData;
_chunks = chunks;
_charPos = charPos;
_newChar = newChar;
_cmd = cmd;
@ -14,9 +39,9 @@ bool CharCommand::mergeWith(const QUndoCommand *command)
const CharCommand *nextCommand = static_cast<const CharCommand *>(command);
bool result = false;
if (_cmd != remove)
if (_cmd != CharCommand::removeAt)
{
if (nextCommand->_cmd == replace)
if (nextCommand->_cmd == overwrite)
if (nextCommand->_charPos == _charPos)
{
_newChar = nextCommand->_newChar;
@ -31,15 +56,15 @@ void CharCommand::undo()
switch (_cmd)
{
case insert:
_xData->remove(_charPos, 1);
_chunks->removeAt(_charPos);
break;
case replace:
_xData->replace(_charPos, _oldChar);
_xData->setDataChanged(_charPos, _wasChanged);
case overwrite:
_chunks->overwrite(_charPos, _oldChar);
_chunks->setDataChanged(_charPos, _wasChanged);
break;
case remove:
_xData->insert(_charPos, _oldChar);
_xData->setDataChanged(_charPos, _wasChanged);
case removeAt:
_chunks->insert(_charPos, _oldChar);
_chunks->setDataChanged(_charPos, _wasChanged);
break;
}
}
@ -49,67 +74,92 @@ void CharCommand::redo()
switch (_cmd)
{
case insert:
_xData->insert(_charPos, _newChar);
_chunks->insert(_charPos, _newChar);
break;
case replace:
_oldChar = _xData->data()[_charPos];
_wasChanged = _xData->dataChanged(_charPos);
_xData->replace(_charPos, _newChar);
case overwrite:
_oldChar = (*_chunks)[_charPos];
_wasChanged = _chunks->dataChanged(_charPos);
_chunks->overwrite(_charPos, _newChar);
break;
case remove:
_oldChar = _xData->data()[_charPos];
_wasChanged = _xData->dataChanged(_charPos);
_xData->remove(_charPos, 1);
case removeAt:
_oldChar = (*_chunks)[_charPos];
_wasChanged = _chunks->dataChanged(_charPos);
_chunks->removeAt(_charPos);
break;
}
}
ArrayCommand::ArrayCommand(XByteArray * xData, Cmd cmd, int baPos, QByteArray newBa, int len, QUndoCommand *parent)
: QUndoCommand(parent)
UndoStack::UndoStack(Chunks * chunks, QObject * parent)
: QUndoStack(parent)
{
_cmd = cmd;
_xData = xData;
_baPos = baPos;
_newBa = newBa;
_len = len;
_chunks = chunks;
_parent = parent;
}
void ArrayCommand::undo()
void UndoStack::insert(qint64 pos, char c)
{
switch (_cmd)
if ((pos >= 0) && (pos <= _chunks->size()))
{
case insert:
_xData->remove(_baPos, _newBa.length());
break;
case replace:
_xData->replace(_baPos, _oldBa);
_xData->setDataChanged(_baPos, _wasChanged);
break;
case remove:
_xData->insert(_baPos, _oldBa);
_xData->setDataChanged(_baPos, _wasChanged);
break;
QUndoCommand *cc = new CharCommand(_chunks, CharCommand::insert, pos, c);
this->push(cc);
}
}
void ArrayCommand::redo()
void UndoStack::insert(qint64 pos, const QByteArray &ba)
{
switch (_cmd)
if ((pos >= 0) && (pos <= _chunks->size()))
{
case insert:
_xData->insert(_baPos, _newBa);
break;
case replace:
_oldBa = _xData->data().mid(_baPos, _len);
_wasChanged = _xData->dataChanged(_baPos, _len);
_xData->replace(_baPos, _newBa);
break;
case remove:
_oldBa = _xData->data().mid(_baPos, _len);
_wasChanged = _xData->dataChanged(_baPos, _len);
_xData->remove(_baPos, _len);
break;
QString txt = QString(tr("Inserting %1 bytes")).arg(ba.size());
beginMacro(txt);
for (int idx=0; idx < ba.size(); idx++)
{
QUndoCommand *cc = new CharCommand(_chunks, CharCommand::insert, pos + idx, ba.at(idx));
this->push(cc);
}
endMacro();
}
}
void UndoStack::removeAt(qint64 pos, qint64 len)
{
if ((pos >= 0) && (pos < _chunks->size()))
{
if (len==1)
{
QUndoCommand *cc = new CharCommand(_chunks, CharCommand::removeAt, pos, char(0));
this->push(cc);
}
else
{
QString txt = QString(tr("Delete %1 chars")).arg(len);
beginMacro(txt);
for (qint64 cnt=0; cnt<len; cnt++)
{
QUndoCommand *cc = new CharCommand(_chunks, CharCommand::removeAt, pos, char(0));
push(cc);
}
endMacro();
}
}
}
void UndoStack::overwrite(qint64 pos, char c)
{
if ((pos >= 0) && (pos < _chunks->size()))
{
QUndoCommand *cc = new CharCommand(_chunks, CharCommand::overwrite, pos, c);
this->push(cc);
}
}
void UndoStack::overwrite(qint64 pos, int len, const QByteArray &ba)
{
if ((pos >= 0) && (pos < _chunks->size()))
{
QString txt = QString(tr("Overwrite %1 chars")).arg(len);
beginMacro(txt);
removeAt(pos, len);
insert(pos, ba);
endMacro();
}
}

View File

@ -3,66 +3,43 @@
/** \cond docNever */
#include <QUndoCommand>
#include <QUndoStack>
#include "xbytearray.h"
#include "chunks.h"
/*! CharCommand is a class to prived undo/redo functionality in QHexEdit.
/*! CharCommand is a class to provid undo/redo functionality in QHexEdit.
A QUndoCommand represents a single editing action on a document. CharCommand
is responsable for manipulations on single chars. It can insert. replace and
remove characters. A manipulation stores allways to actions
is responsable for manipulations on single chars. It can insert. overwrite and
remove characters. A manipulation stores allways two actions
1. redo (or do) action
2. undo action.
CharCommand also supports command compression via mergeWidht(). This allows
the user to execute a undo command contation e.g. 3 steps in a single command.
If you for example insert a new byt "34" this means for the editor doing 3
steps: insert a "00", replace it with "03" and the replace it with "34". These
steps: insert a "00", overwrite it with "03" and the overwrite it with "34". These
3 steps are combined into a single step, insert a "34".
The byte array oriented commands are just put into a set of single byte commands,
which are pooled together with the macroBegin() and macroEnd() functionality of
Qt's QUndoStack.
*/
class CharCommand : public QUndoCommand
class UndoStack : public QUndoStack
{
Q_OBJECT
public:
enum { Id = 1234 };
enum Cmd {insert, remove, replace};
CharCommand(XByteArray * xData, Cmd cmd, int charPos, char newChar,
QUndoCommand *parent=0);
void undo();
void redo();
bool mergeWith(const QUndoCommand *command);
int id() const { return Id; }
UndoStack(Chunks *chunks, QObject * parent=0);
void insert(qint64 pos, char c);
void insert(qint64 pos, const QByteArray &ba);
void removeAt(qint64 pos, qint64 len=1);
void overwrite(qint64 pos, char c);
void overwrite(qint64 pos, int len, const QByteArray &ba);
private:
XByteArray * _xData;
int _charPos;
bool _wasChanged;
char _newChar;
char _oldChar;
Cmd _cmd;
};
/*! ArrayCommand provides undo/redo functionality for handling binary strings. It
can undo/redo insert, replace and remove binary strins (QByteArrays).
*/
class ArrayCommand : public QUndoCommand
{
public:
enum Cmd {insert, remove, replace};
ArrayCommand(XByteArray * xData, Cmd cmd, int baPos, QByteArray newBa=QByteArray(), int len=0,
QUndoCommand *parent=0);
void undo();
void redo();
private:
Cmd _cmd;
XByteArray * _xData;
int _baPos;
int _len;
QByteArray _wasChanged;
QByteArray _newBa;
QByteArray _oldBa;
Chunks * _chunks;
QObject * _parent;
};
/** \endcond docNever */

File diff suppressed because it is too large Load Diff

View File

@ -1,21 +1,33 @@
#ifndef QHEXEDIT_H
#define QHEXEDIT_H
#include <QtGui>
#include "qhexedit_p.h"
#include <QAbstractScrollArea>
#include <QPen>
#include <QBrush>
#include "chunks.h"
#include "commands.h"
#ifdef QHEXEDIT_EXPORTS
#define QHEXEDIT_API Q_DECL_EXPORT
#elif QHEXEDIT_IMPORTS
#define QHEXEDIT_API Q_DECL_IMPORT
#else
#define QHEXEDIT_API
#endif
/** \mainpage
QHexEdit is a binary editor widget for Qt.
\version Version 0.6.1
\image html hexedit.png
\version Version 0.8.3
\image html qhexedit.png
*/
/*! QHexEdit is a hex editor widget written in C++ for the Qt (Qt4) framework.
/** QHexEdit is a hex editor widget written in C++ for the Qt (Qt4, Qt5) framework.
It is a simple editor for binary data, just like QPlainTextEdit is for text
data. There are sip configuration files included, so it is easy to create
bindings for PyQt and you can use this widget also in python.
bindings for PyQt and you can use this widget also in python 2 and 3.
QHexEdit takes the data of a QByteArray (setData()) and shows it. You can use
the mouse or the keyboard to navigate inside the widget. If you hit the keys
@ -36,44 +48,78 @@ characters will be ignored.
QHexEdit comes with undo/redo functionality. All changes can be undone, by
pressing the undo-key (usually ctr-z). They can also be redone afterwards.
The undo/redo framework is cleared, when setData() sets up a new
content for the editor.
content for the editor. You can search data inside the content with indexOf()
and lastIndexOf(). The replace() function is to change located subdata. This
'replaced' data can also be undone by the undo/redo framework.
This widget can only handle small amounts of data. The size has to be below 10
megabytes, otherwise the scroll sliders ard not shown and you can't scroll any
more.
QHexEdit is based on QIODevice, that's why QHexEdit can handle big amounts of
data. The size of edited data can be more then two gigabytes without any
restrictions.
*/
class QHexEdit : public QScrollArea
class QHEXEDIT_API QHexEdit : public QAbstractScrollArea
{
Q_OBJECT
/*! Property data holds the content of QHexEdit. Call setData() to set the
content of QHexEdit, data() returns the actual content.
*/
Q_PROPERTY(QByteArray data READ data WRITE setData)
/*! Property addressOffset is added to the Numbers of the Address Area.
A offset in the address area (left side) is sometimes usefull, whe you show
only a segment of a complete memory picture. With setAddressOffset() you set
this property - with addressOffset() you get the actual value.
/*! Property address area switch the address area on or off. Set addressArea true
(show it), false (hide it).
*/
Q_PROPERTY(int addressOffset READ addressOffset WRITE setAddressOffset)
Q_PROPERTY(bool addressArea READ addressArea WRITE setAddressArea)
/*! Property address area color sets (setAddressAreaColor()) the backgorund
color of address areas. You can also read the color (addressaAreaColor()).
*/
Q_PROPERTY(QColor addressAreaColor READ addressAreaColor WRITE setAddressAreaColor)
/*! Property addressOffset is added to the Numbers of the Address Area.
A offset in the address area (left side) is sometimes usefull, whe you show
only a segment of a complete memory picture. With setAddressOffset() you set
this property - with addressOffset() you get the current value.
*/
Q_PROPERTY(qint64 addressOffset READ addressOffset WRITE setAddressOffset)
/*! Set and get the minimum width of the address area, width in characters.
*/
Q_PROPERTY(int addressWidth READ addressWidth WRITE setAddressWidth)
/*! Switch the ascii area on (true, show it) or off (false, hide it).
*/
Q_PROPERTY(bool asciiArea READ asciiArea WRITE setAsciiArea)
/*! Set and get bytes number per line.*/
Q_PROPERTY(int bytesPerLine READ bytesPerLine WRITE setBytesPerLine)
/*! Porperty cursorPosition sets or gets the position of the editor cursor
in QHexEdit. Every byte in data has to cursor positions: the lower and upper
Nibble. Maximum cursor position is factor two of data.size().
*/
Q_PROPERTY(qint64 cursorPosition READ cursorPosition WRITE setCursorPosition)
/*! Property data holds the content of QHexEdit. Call setData() to set the
content of QHexEdit, data() returns the actual content. When calling setData()
with a QByteArray as argument, QHexEdit creates a internal copy of the data
If you want to edit big files please use setData(), based on QIODevice.
*/
Q_PROPERTY(QByteArray data READ data WRITE setData NOTIFY dataChanged)
/*! That property defines if the hex values looks as a-f if the value is false(default)
or A-F if value is true.
*/
Q_PROPERTY(bool hexCaps READ hexCaps WRITE setHexCaps)
/*! Property defines the dynamic calculation of bytesPerLine parameter depends of width of widget.
set this property true to avoid horizontal scrollbars and show the maximal possible data. defalut value is false*/
Q_PROPERTY(bool dynamicBytesPerLine READ dynamicBytesPerLine WRITE setDynamicBytesPerLine)
/*! Switch the highlighting feature on or of: true (show it), false (hide it).
*/
Q_PROPERTY(bool highlighting READ highlighting WRITE setHighlighting)
/*! Property highlighting color sets (setHighlightingColor()) the backgorund
color of highlighted text areas. You can also read the color
(highlightingColor()).
*/
Q_PROPERTY(QColor highlightingColor READ highlightingColor WRITE setHighlightingColor)
/*! Property selection color sets (setSelectionColor()) the backgorund
color of selected text areas. You can also read the color
(selectionColor()).
*/
Q_PROPERTY(QColor selectionColor READ selectionColor WRITE setSelectionColor)
/*! Porperty overwrite mode sets (setOverwriteMode()) or gets (overwriteMode()) the mode
in which the editor works. In overwrite mode the user will overwrite existing data. The
size of data will be constant. In insert mode the size will grow, when inserting
@ -81,6 +127,12 @@ more.
*/
Q_PROPERTY(bool overwriteMode READ overwriteMode WRITE setOverwriteMode)
/*! Property selection color sets (setSelectionColor()) the backgorund
color of selected text areas. You can also read the color
(selectionColor()).
*/
Q_PROPERTY(QColor selectionColor READ selectionColor WRITE setSelectionColor)
/*! Porperty readOnly sets (setReadOnly()) or gets (isReadOnly) the mode
in which the editor works. In readonly mode the the user can only navigate
through the data and select data; modifying is not possible. This
@ -91,62 +143,115 @@ more.
/*! Set the font of the widget. Please use fixed width fonts like Mono or Courier.*/
Q_PROPERTY(QFont font READ font WRITE setFont)
public:
/*! Creates an instance of QHexEdit.
\param parent Parent widget of QHexEdit.
*/
QHexEdit(QWidget *parent=0);
/*! Inserts a byte array.
\param i Index position, where to insert
\param ba byte array, which is to insert
In overwrite mode, the existing data will be overwritten, in insertmode ba will be
insertet and size of data grows.
// Access to data of qhexedit
/*! Sets the data of QHexEdit. The QIODevice will be opend just before reading
and closed immediately afterwards. This is to allow other programs to rewrite
the file while editing it.
*/
void insert(int i, const QByteArray & ba);
bool setData(QIODevice &iODevice);
/*! Givs back the data as a QByteArray starting at position \param pos and
delivering \param count bytes.
*/
QByteArray dataAt(qint64 pos, qint64 count=-1);
/*! Givs back the data into a \param iODevice starting at position \param pos
and delivering \param count bytes.
*/
bool write(QIODevice &iODevice, qint64 pos=0, qint64 count=-1);
// Char handling
/*! Inserts a char.
\param i Index position, where to insert
\param pos Index position, where to insert
\param ch Char, which is to insert
In overwrite mode, the existing data will be overwritten, in insertmode ba will be
insertet and size of data grows.
The char will be inserted and size of data grows.
*/
void insert(int i, char ch);
void insert(qint64 pos, char ch);
/*! Removes len bytes from the content.
\param pos Index position, where to remove
\param len Amount of bytes to remove
In overwrite mode, the existing bytes will be overwriten with 0x00.
*/
void remove(int pos, int len=1);
void remove(qint64 pos, qint64 len=1);
/*! Gives back a formatted image of the content of QHexEdit
/*! Replaces a char.
\param pos Index position, where to overwrite
\param ch Char, which is to insert
The char will be overwritten and size remains constant.
*/
QString toReadableString();
void replace(qint64 pos, char ch);
// ByteArray handling
/*! Inserts a byte array.
\param pos Index position, where to insert
\param ba QByteArray, which is to insert
The QByteArray will be inserted and size of data grows.
*/
void insert(qint64 pos, const QByteArray &ba);
/*! Replaces \param len bytes with a byte array \param ba
\param pos Index position, where to overwrite
\param ba QByteArray, which is inserted
\param len count of bytes to overwrite
The data is overwritten and size of data may change.
*/
void replace(qint64 pos, qint64 len, const QByteArray &ba);
// Utility functioins
/*! Calc cursor position from graphics position
* \param point from where the cursor position should be calculated
* \return Cursor postioin
*/
qint64 cursorPosition(QPoint point);
/*! Ensure the cursor to be visble
*/
void ensureVisible();
/*! Find first occurence of ba in QHexEdit data
* \param ba Data to find
* \param from Point where the search starts
* \return pos if fond, else -1
*/
qint64 indexOf(const QByteArray &ba, qint64 from);
/*! Returns if any changes where done on document
* \return true when document is modified else false
*/
bool isModified();
/*! Find last occurence of ba in QHexEdit data
* \param ba Data to find
* \param from Point where the search starts
* \return pos if fond, else -1
*/
qint64 lastIndexOf(const QByteArray &ba, qint64 from);
/*! Gives back a formatted image of the selected content of QHexEdit
*/
QString selectionToReadableString();
/*! \cond docNever */
void setAddressOffset(int offset);
int addressOffset();
void setData(QByteArray const &data);
QByteArray data();
void setAddressAreaColor(QColor const &color);
QColor addressAreaColor();
void setHighlightingColor(QColor const &color);
QColor highlightingColor();
void setSelectionColor(QColor const &color);
QColor selectionColor();
void setOverwriteMode(bool);
bool overwriteMode();
void setReadOnly(bool);
bool isReadOnly();
const QFont &font() const;
void setFont(const QFont &);
/*! \endcond docNever */
/*! Set Font of QHexEdit
* \param font
*/
void setFont(const QFont &font);
/*! Gives back a formatted image of the content of QHexEdit
*/
QString toReadableString();
public slots:
/*! Redoes the last operation. If there is no operation to redo, i.e.
@ -154,26 +259,6 @@ public slots:
*/
void redo();
/*! Set the minimum width of the address area.
\param addressWidth Width in characters.
*/
void setAddressWidth(int addressWidth);
/*! Switch the address area on or off.
\param addressArea true (show it), false (hide it).
*/
void setAddressArea(bool addressArea);
/*! Switch the ascii area on or off.
\param asciiArea true (show it), false (hide it).
*/
void setAsciiArea(bool asciiArea);
/*! Switch the highlighting feature on or of.
\param mode true (show it), false (hide it).
*/
void setHighlighting(bool mode);
/*! Undoes the last operation. If there is no operation to undo, i.e.
there is no undo step in the undo/redo history, nothing happens.
*/
@ -182,24 +267,153 @@ public slots:
signals:
/*! Contains the address, where the cursor is located. */
void currentAddressChanged(int address);
void currentAddressChanged(qint64 address);
/*! Contains the size of the data to edit. */
void currentSizeChanged(int size);
void currentSizeChanged(qint64 size);
/*! The signal is emited every time, the data is changed. */
/*! The signal is emitted every time, the data is changed. */
void dataChanged();
/*! The signal is emited every time, the overwrite mode is changed. */
/*! The signal is emitted every time, the overwrite mode is changed. */
void overwriteModeChanged(bool state);
private:
/*! \cond docNever */
QHexEditPrivate *qHexEdit_p;
QHBoxLayout *layout;
QScrollArea *scrollArea;
public:
~QHexEdit();
// Properties
bool addressArea();
void setAddressArea(bool addressArea);
QColor addressAreaColor();
void setAddressAreaColor(const QColor &color);
qint64 addressOffset();
void setAddressOffset(qint64 addressArea);
int addressWidth();
void setAddressWidth(int addressWidth);
bool asciiArea();
void setAsciiArea(bool asciiArea);
int bytesPerLine();
void setBytesPerLine(int count);
qint64 cursorPosition();
void setCursorPosition(qint64 position);
QByteArray data();
void setData(const QByteArray &ba);
void setHexCaps(const bool isCaps);
bool hexCaps();
void setDynamicBytesPerLine(const bool isDynamic);
bool dynamicBytesPerLine();
bool highlighting();
void setHighlighting(bool mode);
QColor highlightingColor();
void setHighlightingColor(const QColor &color);
bool overwriteMode();
void setOverwriteMode(bool overwriteMode);
bool isReadOnly();
void setReadOnly(bool readOnly);
QColor selectionColor();
void setSelectionColor(const QColor &color);
protected:
// Handle events
void keyPressEvent(QKeyEvent *event);
void mouseMoveEvent(QMouseEvent * event);
void mousePressEvent(QMouseEvent * event);
void paintEvent(QPaintEvent *event);
void resizeEvent(QResizeEvent *);
virtual bool focusNextPrevChild(bool next);
private:
// Handle selections
void resetSelection(qint64 pos); // set selectionStart and selectionEnd to pos
void resetSelection(); // set selectionEnd to selectionStart
void setSelection(qint64 pos); // set min (if below init) or max (if greater init)
int getSelectionBegin();
int getSelectionEnd();
// Private utility functions
void init();
void readBuffers();
QString toReadable(const QByteArray &ba);
private slots:
void adjust(); // recalc pixel positions
void dataChangedPrivate(int idx=0); // emit dataChanged() signal
void refresh(); // ensureVisible() and readBuffers()
void updateCursor(); // update blinking cursor
private:
// Name convention: pixel positions start with _px
int _pxCharWidth, _pxCharHeight; // char dimensions (dpendend on font)
int _pxPosHexX; // X-Pos of HeaxArea
int _pxPosAdrX; // X-Pos of Address Area
int _pxPosAsciiX; // X-Pos of Ascii Area
int _pxGapAdr; // gap left from AddressArea
int _pxGapAdrHex; // gap between AddressArea and HexAerea
int _pxGapHexAscii; // gap between HexArea and AsciiArea
int _pxCursorWidth; // cursor width
int _pxSelectionSub; // offset selection rect
int _pxCursorX; // current cursor pos
int _pxCursorY; // current cursor pos
// Name convention: absolute byte positions in chunks start with _b
qint64 _bSelectionBegin; // first position of Selection
qint64 _bSelectionEnd; // end of Selection
qint64 _bSelectionInit; // memory position of Selection
qint64 _bPosFirst; // position of first byte shown
qint64 _bPosLast; // position of last byte shown
qint64 _bPosCurrent; // current position
// variables to store the property values
bool _addressArea; // left area of QHexEdit
QColor _addressAreaColor;
int _addressWidth;
bool _asciiArea;
qint64 _addressOffset;
int _bytesPerLine;
int _hexCharsInLine;
bool _highlighting;
bool _overwriteMode;
QBrush _brushSelection;
QPen _penSelection;
QBrush _brushHighlighted;
QPen _penHighlighted;
bool _readOnly;
bool _hexCaps;
bool _dynamicBytesPerLine;
// other variables
bool _editAreaIsAscii; // flag about the ascii mode edited
int _addrDigits; // real no of addressdigits, may be > addressWidth
bool _blink; // help get cursor blinking
QBuffer _bData; // buffer, when setup with QByteArray
Chunks *_chunks; // IODevice based access to data
QTimer _cursorTimer; // for blinking cursor
qint64 _cursorPosition; // absolute positioin of cursor, 1 Byte == 2 tics
QRect _cursorRect; // physical dimensions of cursor
QByteArray _data; // QHexEdit's data, when setup with QByteArray
QByteArray _dataShown; // data in the current View
QByteArray _hexDataShown; // data in view, transformed to hex
qint64 _lastEventSize; // size, which was emitted last time
QByteArray _markedShown; // marked data in view
bool _modified; // Is any data in editor modified?
int _rowsShown; // lines of text shown
UndoStack * _undoStack; // Stack to store edit actions for undo/redo
/*! \endcond docNever */
};
#endif
#endif // QHEXEDIT_H

View File

@ -1,800 +0,0 @@
#include <QtGui>
#include "qhexedit_p.h"
#include "commands.h"
const int HEXCHARS_IN_LINE = 47;
const int GAP_ADR_HEX = 10;
const int GAP_HEX_ASCII = 16;
const int BYTES_PER_LINE = 16;
QHexEditPrivate::QHexEditPrivate(QScrollArea *parent) : QWidget(parent)
{
_undoStack = new QUndoStack(this);
_scrollArea = parent;
setAddressWidth(4);
setAddressOffset(0);
setAddressArea(true);
setAsciiArea(true);
setHighlighting(true);
setOverwriteMode(true);
setReadOnly(false);
setAddressAreaColor(QColor(0xd4, 0xd4, 0xd4, 0xff));
setHighlightingColor(QColor(0xff, 0xff, 0x99, 0xff));
setSelectionColor(QColor(0x6d, 0x9e, 0xff, 0xff));
setFont(QFont("Courier", 10));
_size = 0;
resetSelection(0);
setFocusPolicy(Qt::StrongFocus);
connect(&_cursorTimer, SIGNAL(timeout()), this, SLOT(updateCursor()));
_cursorTimer.setInterval(500);
_cursorTimer.start();
}
void QHexEditPrivate::setAddressOffset(int offset)
{
_xData.setAddressOffset(offset);
adjust();
}
int QHexEditPrivate::addressOffset()
{
return _xData.addressOffset();
}
void QHexEditPrivate::setData(const QByteArray &data)
{
_xData.setData(data);
_undoStack->clear();
adjust();
setCursorPos(0);
}
QByteArray QHexEditPrivate::data()
{
return _xData.data();
}
void QHexEditPrivate::setAddressAreaColor(const QColor &color)
{
_addressAreaColor = color;
update();
}
QColor QHexEditPrivate::addressAreaColor()
{
return _addressAreaColor;
}
void QHexEditPrivate::setHighlightingColor(const QColor &color)
{
_highlightingColor = color;
update();
}
QColor QHexEditPrivate::highlightingColor()
{
return _highlightingColor;
}
void QHexEditPrivate::setSelectionColor(const QColor &color)
{
_selectionColor = color;
update();
}
QColor QHexEditPrivate::selectionColor()
{
return _selectionColor;
}
void QHexEditPrivate::setReadOnly(bool readOnly)
{
_readOnly = readOnly;
}
bool QHexEditPrivate::isReadOnly()
{
return _readOnly;
}
XByteArray & QHexEditPrivate::xData()
{
return _xData;
}
void QHexEditPrivate::insert(int index, const QByteArray & ba)
{
if (ba.length() > 0)
{
if (_overwriteMode)
{
QUndoCommand *arrayCommand= new ArrayCommand(&_xData, ArrayCommand::replace, index, ba, ba.length());
_undoStack->push(arrayCommand);
emit dataChanged();
}
else
{
QUndoCommand *arrayCommand= new ArrayCommand(&_xData, ArrayCommand::insert, index, ba, ba.length());
_undoStack->push(arrayCommand);
emit dataChanged();
}
}
}
void QHexEditPrivate::insert(int index, char ch)
{
QUndoCommand *charCommand = new CharCommand(&_xData, CharCommand::insert, index, ch);
_undoStack->push(charCommand);
emit dataChanged();
}
void QHexEditPrivate::remove(int index, int len)
{
if (len > 0)
{
if (len == 1)
{
if (_overwriteMode)
{
QUndoCommand *charCommand = new CharCommand(&_xData, CharCommand::replace, index, char(0));
_undoStack->push(charCommand);
emit dataChanged();
}
else
{
QUndoCommand *charCommand = new CharCommand(&_xData, CharCommand::remove, index, char(0));
_undoStack->push(charCommand);
emit dataChanged();
}
}
else
{
QByteArray ba = QByteArray(len, char(0));
if (_overwriteMode)
{
QUndoCommand *arrayCommand = new ArrayCommand(&_xData, ArrayCommand::replace, index, ba, ba.length());
_undoStack->push(arrayCommand);
emit dataChanged();
}
else
{
QUndoCommand *arrayCommand= new ArrayCommand(&_xData, ArrayCommand::remove, index, ba, len);
_undoStack->push(arrayCommand);
emit dataChanged();
}
}
}
}
void QHexEditPrivate::replace(int index, char ch)
{
QUndoCommand *charCommand = new CharCommand(&_xData, CharCommand::replace, index, ch);
_undoStack->push(charCommand);
emit dataChanged();
}
void QHexEditPrivate::replace(int index, const QByteArray & ba)
{
QUndoCommand *arrayCommand= new ArrayCommand(&_xData, ArrayCommand::replace, index, ba, ba.length());
_undoStack->push(arrayCommand);
emit dataChanged();
}
void QHexEditPrivate::setAddressArea(bool addressArea)
{
_addressArea = addressArea;
adjust();
setCursorPos(_cursorPosition);
}
void QHexEditPrivate::setAddressWidth(int addressWidth)
{
_xData.setAddressWidth(addressWidth);
setCursorPos(_cursorPosition);
}
void QHexEditPrivate::setAsciiArea(bool asciiArea)
{
_asciiArea = asciiArea;
adjust();
}
void QHexEditPrivate::setFont(const QFont &font)
{
QWidget::setFont(font);
adjust();
}
void QHexEditPrivate::setHighlighting(bool mode)
{
_highlighting = mode;
update();
}
void QHexEditPrivate::setOverwriteMode(bool overwriteMode)
{
_overwriteMode = overwriteMode;
}
bool QHexEditPrivate::overwriteMode()
{
return _overwriteMode;
}
void QHexEditPrivate::redo()
{
_undoStack->redo();
emit dataChanged();
setCursorPos(_cursorPosition);
update();
}
void QHexEditPrivate::undo()
{
_undoStack->undo();
emit dataChanged();
setCursorPos(_cursorPosition);
update();
}
QString QHexEditPrivate::toRedableString()
{
return _xData.toRedableString();
}
QString QHexEditPrivate::selectionToReadableString()
{
return _xData.toRedableString(getSelectionBegin(), getSelectionEnd());
}
void QHexEditPrivate::keyPressEvent(QKeyEvent *event)
{
int charX = (_cursorX - _xPosHex) / _charWidth;
int posX = (charX / 3) * 2 + (charX % 3);
int posBa = (_cursorY / _charHeight) * BYTES_PER_LINE + posX / 2;
/*****************************************************************************/
/* Cursor movements */
/*****************************************************************************/
if (event->matches(QKeySequence::MoveToNextChar))
{
setCursorPos(_cursorPosition + 1);
resetSelection(_cursorPosition);
}
if (event->matches(QKeySequence::MoveToPreviousChar))
{
setCursorPos(_cursorPosition - 1);
resetSelection(_cursorPosition);
}
if (event->matches(QKeySequence::MoveToEndOfLine))
{
setCursorPos(_cursorPosition | (2 * BYTES_PER_LINE -1));
resetSelection(_cursorPosition);
}
if (event->matches(QKeySequence::MoveToStartOfLine))
{
setCursorPos(_cursorPosition - (_cursorPosition % (2 * BYTES_PER_LINE)));
resetSelection(_cursorPosition);
}
if (event->matches(QKeySequence::MoveToPreviousLine))
{
setCursorPos(_cursorPosition - (2 * BYTES_PER_LINE));
resetSelection(_cursorPosition);
}
if (event->matches(QKeySequence::MoveToNextLine))
{
setCursorPos(_cursorPosition + (2 * BYTES_PER_LINE));
resetSelection(_cursorPosition);
}
if (event->matches(QKeySequence::MoveToNextPage))
{
setCursorPos(_cursorPosition + (((_scrollArea->viewport()->height() / _charHeight) - 1) * 2 * BYTES_PER_LINE));
resetSelection(_cursorPosition);
}
if (event->matches(QKeySequence::MoveToPreviousPage))
{
setCursorPos(_cursorPosition - (((_scrollArea->viewport()->height() / _charHeight) - 1) * 2 * BYTES_PER_LINE));
resetSelection(_cursorPosition);
}
if (event->matches(QKeySequence::MoveToEndOfDocument))
{
setCursorPos(_xData.size() * 2);
resetSelection(_cursorPosition);
}
if (event->matches(QKeySequence::MoveToStartOfDocument))
{
setCursorPos(0);
resetSelection(_cursorPosition);
}
/*****************************************************************************/
/* Select commands */
/*****************************************************************************/
if (event->matches(QKeySequence::SelectAll))
{
resetSelection(0);
setSelection(2*_xData.size() + 1);
}
if (event->matches(QKeySequence::SelectNextChar))
{
int pos = _cursorPosition + 1;
setCursorPos(pos);
setSelection(pos);
}
if (event->matches(QKeySequence::SelectPreviousChar))
{
int pos = _cursorPosition - 1;
setSelection(pos);
setCursorPos(pos);
}
if (event->matches(QKeySequence::SelectEndOfLine))
{
int pos = _cursorPosition - (_cursorPosition % (2 * BYTES_PER_LINE)) + (2 * BYTES_PER_LINE);
setCursorPos(pos);
setSelection(pos);
}
if (event->matches(QKeySequence::SelectStartOfLine))
{
int pos = _cursorPosition - (_cursorPosition % (2 * BYTES_PER_LINE));
setCursorPos(pos);
setSelection(pos);
}
if (event->matches(QKeySequence::SelectPreviousLine))
{
int pos = _cursorPosition - (2 * BYTES_PER_LINE);
setCursorPos(pos);
setSelection(pos);
}
if (event->matches(QKeySequence::SelectNextLine))
{
int pos = _cursorPosition + (2 * BYTES_PER_LINE);
setCursorPos(pos);
setSelection(pos);
}
if (event->matches(QKeySequence::SelectNextPage))
{
int pos = _cursorPosition + (((_scrollArea->viewport()->height() / _charHeight) - 1) * 2 * BYTES_PER_LINE);
setCursorPos(pos);
setSelection(pos);
}
if (event->matches(QKeySequence::SelectPreviousPage))
{
int pos = _cursorPosition - (((_scrollArea->viewport()->height() / _charHeight) - 1) * 2 * BYTES_PER_LINE);
setCursorPos(pos);
setSelection(pos);
}
if (event->matches(QKeySequence::SelectEndOfDocument))
{
int pos = _xData.size() * 2;
setCursorPos(pos);
setSelection(pos);
}
if (event->matches(QKeySequence::SelectStartOfDocument))
{
int pos = 0;
setCursorPos(pos);
setSelection(pos);
}
/*****************************************************************************/
/* Edit Commands */
/*****************************************************************************/
if (!_readOnly)
{
/* Hex input */
int key = int(event->text()[0].toAscii());
if ((key>='0' && key<='9') || (key>='a' && key <= 'f'))
{
if (getSelectionBegin() != getSelectionEnd())
{
posBa = getSelectionBegin();
remove(posBa, getSelectionEnd() - posBa);
setCursorPos(2*posBa);
resetSelection(2*posBa);
}
// If insert mode, then insert a byte
if (_overwriteMode == false)
if ((charX % 3) == 0)
{
insert(posBa, char(0));
}
// Change content
if (_xData.size() > 0)
{
QByteArray hexValue = _xData.data().mid(posBa, 1).toHex();
if ((charX % 3) == 0)
hexValue[0] = key;
else
hexValue[1] = key;
replace(posBa, QByteArray().fromHex(hexValue)[0]);
setCursorPos(_cursorPosition + 1);
resetSelection(_cursorPosition);
}
}
/* Cut & Paste */
if (event->matches(QKeySequence::Cut))
{
QString result = QString();
for (int idx = getSelectionBegin(); idx < getSelectionEnd(); idx++)
{
result += _xData.data().mid(idx, 1).toHex() + " ";
if ((idx % 16) == 15)
result.append("\n");
}
remove(getSelectionBegin(), getSelectionEnd() - getSelectionBegin());
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(result);
setCursorPos(getSelectionBegin());
resetSelection(getSelectionBegin());
}
if (event->matches(QKeySequence::Paste))
{
QClipboard *clipboard = QApplication::clipboard();
QByteArray ba = QByteArray().fromHex(clipboard->text().toLatin1());
insert(_cursorPosition / 2, ba);
setCursorPos(_cursorPosition + 2 * ba.length());
resetSelection(getSelectionBegin());
}
/* Delete char */
if (event->matches(QKeySequence::Delete))
{
if (getSelectionBegin() != getSelectionEnd())
{
posBa = getSelectionBegin();
remove(posBa, getSelectionEnd() - posBa);
setCursorPos(2*posBa);
resetSelection(2*posBa);
}
else
{
if (_overwriteMode)
replace(posBa, char(0));
else
remove(posBa, 1);
}
}
/* Backspace */
if ((event->key() == Qt::Key_Backspace) && (event->modifiers() == Qt::NoModifier))
{
if (getSelectionBegin() != getSelectionEnd())
{
posBa = getSelectionBegin();
remove(posBa, getSelectionEnd() - posBa);
setCursorPos(2*posBa);
resetSelection(2*posBa);
}
else
{
if (posBa > 0)
{
if (_overwriteMode)
replace(posBa - 1, char(0));
else
remove(posBa - 1, 1);
setCursorPos(_cursorPosition - 2);
}
}
}
/* undo */
if (event->matches(QKeySequence::Undo))
{
undo();
}
/* redo */
if (event->matches(QKeySequence::Redo))
{
redo();
}
}
if (event->matches(QKeySequence::Copy))
{
QString result = QString();
for (int idx = getSelectionBegin(); idx < getSelectionEnd(); idx++)
{
result += _xData.data().mid(idx, 1).toHex() + " ";
if ((idx % 16) == 15)
result.append('\n');
}
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(result);
}
// Switch between insert/overwrite mode
if ((event->key() == Qt::Key_Insert) && (event->modifiers() == Qt::NoModifier))
{
_overwriteMode = !_overwriteMode;
setCursorPos(_cursorPosition);
overwriteModeChanged(_overwriteMode);
}
_scrollArea->ensureVisible(_cursorX, _cursorY + _charHeight/2, 3, _charHeight/2 + 2);
update();
}
void QHexEditPrivate::mouseMoveEvent(QMouseEvent * event)
{
_blink = false;
update();
int actPos = cursorPos(event->pos());
setCursorPos(actPos);
setSelection(actPos);
}
void QHexEditPrivate::mousePressEvent(QMouseEvent * event)
{
_blink = false;
update();
int cPos = cursorPos(event->pos());
resetSelection(cPos);
setCursorPos(cPos);
}
void QHexEditPrivate::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
// draw some patterns if needed
painter.fillRect(event->rect(), this->palette().color(QPalette::Base));
if (_addressArea)
painter.fillRect(QRect(_xPosAdr, event->rect().top(), _xPosHex - GAP_ADR_HEX + 2, height()), _addressAreaColor);
if (_asciiArea)
{
int linePos = _xPosAscii - (GAP_HEX_ASCII / 2);
painter.setPen(Qt::gray);
painter.drawLine(linePos, event->rect().top(), linePos, height());
}
painter.setPen(this->palette().color(QPalette::WindowText));
// calc position
int firstLineIdx = ((event->rect().top()/ _charHeight) - _charHeight) * BYTES_PER_LINE;
if (firstLineIdx < 0)
firstLineIdx = 0;
int lastLineIdx = ((event->rect().bottom() / _charHeight) + _charHeight) * BYTES_PER_LINE;
if (lastLineIdx > _xData.size())
lastLineIdx = _xData.size();
int yPosStart = ((firstLineIdx) / BYTES_PER_LINE) * _charHeight + _charHeight;
// paint address area
if (_addressArea)
{
for (int lineIdx = firstLineIdx, yPos = yPosStart; lineIdx < lastLineIdx; lineIdx += BYTES_PER_LINE, yPos +=_charHeight)
{
QString address = QString("%1")
.arg(lineIdx + _xData.addressOffset(), _xData.realAddressNumbers(), 16, QChar('0'));
painter.drawText(_xPosAdr, yPos, address);
}
}
// paint hex area
QByteArray hexBa(_xData.data().mid(firstLineIdx, lastLineIdx - firstLineIdx + 1).toHex());
QBrush highLighted = QBrush(_highlightingColor);
QPen colHighlighted = QPen(this->palette().color(QPalette::WindowText));
QBrush selected = QBrush(_selectionColor);
QPen colSelected = QPen(Qt::white);
QPen colStandard = QPen(this->palette().color(QPalette::WindowText));
painter.setBackgroundMode(Qt::TransparentMode);
for (int lineIdx = firstLineIdx, yPos = yPosStart; lineIdx < lastLineIdx; lineIdx += BYTES_PER_LINE, yPos +=_charHeight)
{
QByteArray hex;
int xPos = _xPosHex;
for (int colIdx = 0; ((lineIdx + colIdx) < _xData.size() and (colIdx < BYTES_PER_LINE)); colIdx++)
{
int posBa = lineIdx + colIdx;
if ((getSelectionBegin() <= posBa) && (getSelectionEnd() > posBa))
{
painter.setBackground(selected);
painter.setBackgroundMode(Qt::OpaqueMode);
painter.setPen(colSelected);
}
else
{
if (_highlighting)
{
// hilight diff bytes
painter.setBackground(highLighted);
if (_xData.dataChanged(posBa))
{
painter.setPen(colHighlighted);
painter.setBackgroundMode(Qt::OpaqueMode);
}
else
{
painter.setPen(colStandard);
painter.setBackgroundMode(Qt::TransparentMode);
}
}
}
// render hex value
if (colIdx == 0)
{
hex = hexBa.mid((lineIdx - firstLineIdx) * 2, 2);
painter.drawText(xPos, yPos, hex);
xPos += 2 * _charWidth;
} else {
hex = hexBa.mid((lineIdx + colIdx - firstLineIdx) * 2, 2).prepend(" ");
painter.drawText(xPos, yPos, hex);
xPos += 3 * _charWidth;
}
}
}
painter.setBackgroundMode(Qt::TransparentMode);
painter.setPen(this->palette().color(QPalette::WindowText));
// paint ascii area
if (_asciiArea)
{
for (int lineIdx = firstLineIdx, yPos = yPosStart; lineIdx < lastLineIdx; lineIdx += BYTES_PER_LINE, yPos +=_charHeight)
{
int xPosAscii = _xPosAscii;
for (int colIdx = 0; ((lineIdx + colIdx) < _xData.size() and (colIdx < BYTES_PER_LINE)); colIdx++)
{
painter.drawText(xPosAscii, yPos, _xData.asciiChar(lineIdx + colIdx));
xPosAscii += _charWidth;
}
}
}
// paint cursor
if (_blink)
{
if (_overwriteMode)
painter.fillRect(_cursorX, _cursorY + _charHeight - 2, _charWidth, 2, this->palette().color(QPalette::WindowText));
else
painter.fillRect(_cursorX, _cursorY, 2, _charHeight, this->palette().color(QPalette::WindowText));
}
if (_size != _xData.size())
{
_size = _xData.size();
emit currentSizeChanged(_size);
}
}
void QHexEditPrivate::setCursorPos(int position)
{
// delete cursor
_blink = false;
update();
// cursor in range?
if (_overwriteMode)
{
if (position > (_xData.size() * 2 - 1))
position = _xData.size() * 2 - 1;
} else {
if (position > (_xData.size() * 2))
position = _xData.size() * 2;
}
if (position < 0)
position = 0;
// calc position
_cursorPosition = position;
_cursorY = (position / (2 * BYTES_PER_LINE)) * _charHeight + 4;
int x = (position % (2 * BYTES_PER_LINE));
_cursorX = (((x / 2) * 3) + (x % 2)) * _charWidth + _xPosHex;
// immiadately draw cursor
_blink = true;
update();
emit currentAddressChanged(_cursorPosition/2);
}
int QHexEditPrivate::cursorPos(QPoint pos)
{
int result = -1;
// find char under cursor
if ((pos.x() >= _xPosHex) and (pos.x() < (_xPosHex + HEXCHARS_IN_LINE * _charWidth)))
{
int x = (pos.x() - _xPosHex) / _charWidth;
if ((x % 3) == 0)
x = (x / 3) * 2;
else
x = ((x / 3) * 2) + 1;
int y = ((pos.y() - 3) / _charHeight) * 2 * BYTES_PER_LINE;
result = x + y;
}
return result;
}
int QHexEditPrivate::cursorPos()
{
return _cursorPosition;
}
void QHexEditPrivate::resetSelection(int pos)
{
if (pos < 0)
pos = 0;
pos = pos / 2;
_selectionInit = pos;
_selectionBegin = pos;
_selectionEnd = pos;
}
void QHexEditPrivate::setSelection(int pos)
{
if (pos < 0)
pos = 0;
pos = pos / 2;
if (pos >= _selectionInit)
{
_selectionEnd = pos;
_selectionBegin = _selectionInit;
}
else
{
_selectionBegin = pos;
_selectionEnd = _selectionInit;
}
}
int QHexEditPrivate::getSelectionBegin()
{
return _selectionBegin;
}
int QHexEditPrivate::getSelectionEnd()
{
return _selectionEnd;
}
void QHexEditPrivate::updateCursor()
{
if (_blink)
_blink = false;
else
_blink = true;
update(_cursorX, _cursorY, _charWidth, _charHeight);
}
void QHexEditPrivate::adjust()
{
_charWidth = fontMetrics().width(QLatin1Char('9'));
_charHeight = fontMetrics().height();
_xPosAdr = 0;
if (_addressArea)
_xPosHex = _xData.realAddressNumbers()*_charWidth + GAP_ADR_HEX;
else
_xPosHex = 0;
_xPosAscii = _xPosHex + HEXCHARS_IN_LINE * _charWidth + GAP_HEX_ASCII;
// tell QAbstractScollbar, how big we are
setMinimumHeight(((_xData.size()/16 + 1) * _charHeight) + 5);
setMinimumWidth(_xPosAscii + (BYTES_PER_LINE * _charWidth));
update();
}

View File

@ -1,120 +0,0 @@
#ifndef QHEXEDIT_P_H
#define QHEXEDIT_P_H
/** \cond docNever */
#include <QtGui>
#include "xbytearray.h"
class QHexEditPrivate : public QWidget
{
Q_OBJECT
public:
QHexEditPrivate(QScrollArea *parent);
void setAddressAreaColor(QColor const &color);
QColor addressAreaColor();
void setAddressOffset(int offset);
int addressOffset();
void setCursorPos(int position);
int cursorPos();
void setData(QByteArray const &data);
QByteArray data();
void setHighlightingColor(QColor const &color);
QColor highlightingColor();
void setOverwriteMode(bool overwriteMode);
bool overwriteMode();
void setReadOnly(bool readOnly);
bool isReadOnly();
void setSelectionColor(QColor const &color);
QColor selectionColor();
XByteArray & xData();
void insert(int index, const QByteArray & ba);
void insert(int index, char ch);
void remove(int index, int len=1);
void replace(int index, char ch);
void replace(int index, const QByteArray & ba);
void setAddressArea(bool addressArea);
void setAddressWidth(int addressWidth);
void setAsciiArea(bool asciiArea);
void setHighlighting(bool mode);
virtual void setFont(const QFont &font);
void undo();
void redo();
QString toRedableString();
QString selectionToReadableString();
signals:
void currentAddressChanged(int address);
void currentSizeChanged(int size);
void dataChanged();
void overwriteModeChanged(bool state);
protected:
void keyPressEvent(QKeyEvent * event);
void mouseMoveEvent(QMouseEvent * event);
void mousePressEvent(QMouseEvent * event);
void paintEvent(QPaintEvent *event);
int cursorPos(QPoint pos); // calc cursorpos from graphics position. DOES NOT STORE POSITION
void resetSelection(int pos);
void setSelection(int pos); // set min (if below init) or max (if greater init)
int getSelectionBegin();
int getSelectionEnd();
private slots:
void updateCursor();
private:
void adjust();
QColor _addressAreaColor;
QColor _highlightingColor;
QColor _selectionColor;
QScrollArea *_scrollArea;
QTimer _cursorTimer;
QUndoStack *_undoStack;
XByteArray _xData; // Hält den Inhalt des Hex Editors
bool _blink; // true: then cursor blinks
bool _renderingRequired; // Flag to store that rendering is necessary
bool _addressArea; // left area of QHexEdit
bool _asciiArea; // medium area
bool _highlighting; // highlighting of changed bytes
bool _overwriteMode;
bool _readOnly; // true: the user can only look and navigate
int _charWidth, _charHeight; // char dimensions (dpendend on font)
int _cursorX, _cursorY; // graphics position of the cursor
int _cursorPosition; // charakter positioin in stream (on byte ends in to steps)
int _xPosAdr, _xPosHex, _xPosAscii; // graphics x-position of the areas
int _selectionBegin; // First selected char
int _selectionEnd; // Last selected char
int _selectionInit; // That's, where we pressed the mouse button
int _size;
};
/** \endcond docNever */
#endif

View File

@ -1,167 +0,0 @@
#include "xbytearray.h"
XByteArray::XByteArray()
{
_oldSize = -99;
_addressNumbers = 4;
_addressOffset = 0;
}
int XByteArray::addressOffset()
{
return _addressOffset;
}
void XByteArray::setAddressOffset(int offset)
{
_addressOffset = offset;
}
int XByteArray::addressWidth()
{
return _addressNumbers;
}
void XByteArray::setAddressWidth(int width)
{
if ((width >= 0) and (width<=6))
{
_addressNumbers = width;
}
}
QByteArray & XByteArray::data()
{
return _data;
}
void XByteArray::setData(QByteArray data)
{
_data = data;
_changedData = QByteArray(data.length(), char(0));
}
bool XByteArray::dataChanged(int i)
{
return bool(_changedData[i]);
}
QByteArray XByteArray::dataChanged(int i, int len)
{
return _changedData.mid(i, len);
}
void XByteArray::setDataChanged(int i, bool state)
{
_changedData[i] = char(state);
}
void XByteArray::setDataChanged(int i, const QByteArray & state)
{
int length = state.length();
int len;
if ((i + length) > _changedData.length())
len = _changedData.length() - i;
else
len = length;
_changedData.replace(i, len, state);
}
int XByteArray::realAddressNumbers()
{
if (_oldSize != _data.size())
{
// is addressNumbers wide enought?
QString test = QString("%1")
.arg(_data.size() + _addressOffset, _addressNumbers, 16, QChar('0'));
_realAddressNumbers = test.size();
}
return _realAddressNumbers;
}
int XByteArray::size()
{
return _data.size();
}
QByteArray & XByteArray::insert(int i, char ch)
{
_data.insert(i, ch);
_changedData.insert(i, char(1));
return _data;
}
QByteArray & XByteArray::insert(int i, const QByteArray & ba)
{
_data.insert(i, ba);
_changedData.insert(i, QByteArray(ba.length(), char(1)));
return _data;
}
QByteArray & XByteArray::remove(int i, int len)
{
_data.remove(i, len);
_changedData.remove(i, len);
return _data;
}
QByteArray & XByteArray::replace(int index, char ch)
{
_data[index] = ch;
_changedData[index] = char(1);
return _data;
}
QByteArray & XByteArray::replace(int index, const QByteArray & ba)
{
int len = ba.length();
return replace(index, len, ba);
}
QByteArray & XByteArray::replace(int index, int length, const QByteArray & ba)
{
int len;
if ((index + length) > _data.length())
len = _data.length() - index;
else
len = length;
_data.replace(index, len, ba.mid(0, len));
_changedData.replace(index, len, QByteArray(len, char(1)));
return _data;
}
QChar XByteArray::asciiChar(int index)
{
char ch = _data[index];
if ((ch < 0x20) or (ch > 0x7e))
ch = '.';
return QChar(ch);
}
QString XByteArray::toRedableString(int start, int end)
{
int adrWidth = realAddressNumbers();
if (_addressNumbers > adrWidth)
adrWidth = _addressNumbers;
if (end < 0)
end = _data.size();
QString result;
for (int i=start; i < end; i += 16)
{
QString adrStr = QString("%1").arg(_addressOffset + i, adrWidth, 16, QChar('0'));
QString hexStr;
QString ascStr;
for (int j=0; j<16; j++)
{
if ((i + j) < _data.size())
{
hexStr.append(" ").append(_data.mid(i+j, 1).toHex());
ascStr.append(asciiChar(i+j));
}
}
result += adrStr + " " + QString("%1").arg(hexStr, -48) + " " + QString("%1").arg(ascStr, -17) + "\n";
}
return result;
}

View File

@ -1,66 +0,0 @@
#ifndef XBYTEARRAY_H
#define XBYTEARRAY_H
/** \cond docNever */
#include <QtCore>
/*! XByteArray represents the content of QHexEcit.
XByteArray comprehend the data itself and informations to store if it was
changed. The QHexEdit component uses these informations to perform nice
rendering of the data
XByteArray also provides some functionality to insert, replace and remove
single chars and QByteArras. Additionally some functions support rendering
and converting to readable strings.
*/
class XByteArray
{
public:
explicit XByteArray();
int addressOffset();
void setAddressOffset(int offset);
int addressWidth();
void setAddressWidth(int width);
QByteArray & data();
void setData(QByteArray data);
bool dataChanged(int i);
QByteArray dataChanged(int i, int len);
void setDataChanged(int i, bool state);
void setDataChanged(int i, const QByteArray & state);
int realAddressNumbers();
int size();
QByteArray & insert(int i, char ch);
QByteArray & insert(int i, const QByteArray & ba);
QByteArray & remove(int pos, int len);
QByteArray & replace(int index, char ch);
QByteArray & replace(int index, const QByteArray & ba);
QByteArray & replace(int index, int length, const QByteArray & ba);
QChar asciiChar(int index);
QString toRedableString(int start=0, int end=-1);
signals:
public slots:
private:
QByteArray _data;
QByteArray _changedData;
int _addressNumbers; // wanted width of address area
int _addressOffset; // will be added to the real addres inside bytearray
int _realAddressNumbers; // real width of address area (can be greater then wanted width)
int _oldSize; // size of data
};
/** \endcond docNever */
#endif // XBYTEARRAY_H

View File

@ -410,11 +410,16 @@ void AbstractPort::updatePacketListInterleaved()
qDebug("In %s", __FUNCTION__);
clearPacketList();
if (streamList_.size() == 0)
{
isSendQueueDirty_ = false;
return;
}
// First sort the streams by ordinalValue
qSort(streamList_.begin(), streamList_.end(), StreamBase::StreamLessThan);
clearPacketList();
for (int i = 0; i < streamList_.size(); i++)
{
if (!streamList_[i]->isEnabled())

View File

@ -111,6 +111,10 @@ void Device::setIp4(quint32 address, int prefixLength, quint32 gateway)
ip4PrefixLength_ = prefixLength;
ip4Gateway_ = gateway;
hasIp4_ = true;
// Precalculate our mask 'n subnet to avoid doing so at pkt rx/tx time
ip4Mask_ = ~0 << (32 - ip4PrefixLength_);
ip4Subnet_ = ip4_ & ip4Mask_;
}
void Device::setIp6(UInt128 address, int prefixLength, UInt128 gateway)
@ -119,6 +123,10 @@ void Device::setIp6(UInt128 address, int prefixLength, UInt128 gateway)
ip6PrefixLength_ = prefixLength;
ip6Gateway_ = gateway;
hasIp6_ = true;
// Precalculate our mask 'n subnet to avoid doing so at pkt rx/tx time
ip6Mask_ = ~UInt128(0, 0) << (128 - ip6PrefixLength_);
ip6Subnet_ = ip6_ & ip6Mask_;
}
void Device::getConfig(OstEmul::Device *deviceConfig)
@ -405,7 +413,7 @@ quint64 Device::neighborMac(const PacketBuffer *pktBuf)
// We know only about IP packets
if ((ethType == 0x0800) && hasIp4_) { // IPv4
int ipHdrLen = (pktData[0] & 0x0F) << 2;
quint32 dstIp, tgtIp, mask;
quint32 dstIp, tgtIp;
if (pktBuf->length() < ipHdrLen) {
qDebug("incomplete IPv4 header: expected %d, actual %d",
@ -418,14 +426,12 @@ quint64 Device::neighborMac(const PacketBuffer *pktBuf)
qDebug("mcast dst %x", dstIp);
return (quint64(0x01005e) << 24) | (dstIp & 0x7FFFFF);
}
mask = ~0 << (32 - ip4PrefixLength_);
qDebug("dst %x mask %x self %x", dstIp, mask, ip4_);
tgtIp = ((dstIp & mask) == (ip4_ & mask)) ? dstIp : ip4Gateway_;
tgtIp = ((dstIp & ip4Mask_) == ip4Subnet_) ? dstIp : ip4Gateway_;
return arpTable_.value(tgtIp);
}
else if ((ethType == kEthTypeIp6) && hasIp6_) { // IPv6
UInt128 dstIp, tgtIp, mask;
UInt128 dstIp, tgtIp;
if (pktBuf->length() < (kIp6HdrLen+2)) {
qDebug("incomplete IPv6 header: expected %d, actual %d",
@ -439,12 +445,7 @@ quint64 Device::neighborMac(const PacketBuffer *pktBuf)
qPrintable(QHostAddress(dstIp.toArray()).toString()));
return (quint64(0x3333) << 32) | (dstIp.lo64() & 0xFFFFFFFF);
}
mask = ~UInt128(0, 0) << (128 - ip6PrefixLength_);
qDebug("dst %s mask %s self %s",
qPrintable(QHostAddress(dstIp.toArray()).toString()),
qPrintable(QHostAddress(mask.toArray()).toString()),
qPrintable(QHostAddress(ip6_.toArray()).toString()));
tgtIp = ((dstIp & mask) == (ip6_ & mask)) ? dstIp : ip6Gateway_;
tgtIp = ((dstIp & ip6Mask_) == ip6Subnet_) ? dstIp : ip6Gateway_;
return ndpTable_.value(tgtIp);
}
@ -568,7 +569,7 @@ void Device::sendArpRequest(PacketBuffer *pktBuf)
{
uchar *pktData = pktBuf->data();
int ipHdrLen = (pktData[0] & 0x0F) << 2;
quint32 srcIp = ip4_, dstIp, mask, tgtIp;
quint32 dstIp, tgtIp;
if (pktBuf->length() < ipHdrLen) {
qDebug("incomplete IPv4 header: expected %d, actual %d",
@ -578,9 +579,7 @@ void Device::sendArpRequest(PacketBuffer *pktBuf)
dstIp = qFromBigEndian<quint32>(pktData + ipHdrLen - 4);
mask = ~0 << (32 - ip4PrefixLength_);
qDebug("dst %x src %x mask %x", dstIp, srcIp, mask);
tgtIp = ((dstIp & mask) == (srcIp & mask)) ? dstIp : ip4Gateway_;
tgtIp = ((dstIp & ip4Mask_) == ip4Subnet_) ? dstIp : ip4Gateway_;
sendArpRequest(tgtIp);
@ -684,16 +683,18 @@ void Device::sendIp4Reply(PacketBuffer *pktBuf)
uchar *pktData = pktBuf->push(20);
uchar origTtl = pktData[8];
uchar ipProto = pktData[9];
quint32 srcIp, dstIp;
quint32 srcIp, dstIp, tgtIp, mask;
quint32 sum;
// Swap src/dst IP addresses
dstIp = qFromBigEndian<quint32>(pktData + 12); // srcIp in original pkt
srcIp = qFromBigEndian<quint32>(pktData + 16); // dstIp in original pkt
if (!arpTable_.contains(dstIp)) {
tgtIp = ((dstIp & ip4Mask_) == ip4Subnet_) ? dstIp : ip4Gateway_;
if (!arpTable_.contains(tgtIp)) {
qWarning("%s: mac not found for %s; unable to send IPv4 packet",
__FUNCTION__, qPrintable(QHostAddress(dstIp).toString()));
__FUNCTION__, qPrintable(QHostAddress(tgtIp).toString()));
return;
}
@ -712,7 +713,7 @@ void Device::sendIp4Reply(PacketBuffer *pktBuf)
sum = (sum & 0xFFFF) + (sum >> 16);
*(quint16*)(pktData + 10) = qToBigEndian(quint16(~sum));
encap(pktBuf, arpTable_.value(dstIp), 0x0800);
encap(pktBuf, arpTable_.value(tgtIp), 0x0800);
transmitPacket(pktBuf);
}
@ -796,7 +797,7 @@ bool Device::sendIp6(PacketBuffer *pktBuf, UInt128 dstIp, quint8 protocol)
{
int payloadLen = pktBuf->length();
uchar *p = pktBuf->push(kIp6HdrLen);
quint64 dstMac = ndpTable_.value(dstIp);
quint64 dstMac;
if (!p) {
qWarning("%s: failed to push %d bytes [0x%p, 0x%p]", __FUNCTION__,
@ -807,6 +808,10 @@ bool Device::sendIp6(PacketBuffer *pktBuf, UInt128 dstIp, quint8 protocol)
// In case of mcast, derive dstMac
if ((dstIp.hi64() >> 56) == 0xff)
dstMac = (quint64(0x3333) << 32) | (dstIp.lo64() & 0xffffffff);
else {
UInt128 tgtIp = ((dstIp & ip6Mask_) == ip6Subnet_)? dstIp : ip6Gateway_;
dstMac = ndpTable_.value(tgtIp);
}
if (!dstMac) {
qWarning("%s: mac not found for %s; unable to send IPv6 packet",
@ -841,16 +846,17 @@ _error_exit:
void Device::sendIp6Reply(PacketBuffer *pktBuf)
{
uchar *pktData = pktBuf->push(kIp6HdrLen);
UInt128 srcIp, dstIp;
UInt128 srcIp, dstIp, tgtIp;
// Swap src/dst IP addresses
dstIp = qFromBigEndian<UInt128>(pktData + 8); // srcIp in original pkt
srcIp = qFromBigEndian<UInt128>(pktData + 24); // dstIp in original pkt
if (!ndpTable_.contains(dstIp)) {
tgtIp = ((dstIp & ip6Mask_) == ip6Subnet_) ? dstIp : ip6Gateway_;
if (!ndpTable_.contains(tgtIp)) {
qWarning("%s: mac not found for %s; unable to send IPv6 packet",
__FUNCTION__,
qPrintable(QHostAddress(dstIp.toArray()).toString()));
qPrintable(QHostAddress(tgtIp.toArray()).toString()));
return;
}
@ -860,7 +866,7 @@ void Device::sendIp6Reply(PacketBuffer *pktBuf)
// Reset TTL
pktData[7] = 64;
encap(pktBuf, ndpTable_.value(dstIp), 0x86dd);
encap(pktBuf, ndpTable_.value(tgtIp), 0x86dd);
transmitPacket(pktBuf);
}
@ -950,7 +956,7 @@ _invalid_exit:
void Device::sendNeighborSolicit(PacketBuffer *pktBuf)
{
uchar *pktData = pktBuf->data();
UInt128 srcIp = ip6_, dstIp, mask, tgtIp;
UInt128 dstIp, tgtIp;
if (pktBuf->length() < kIp6HdrLen) {
qDebug("incomplete IPv6 header: expected %d, actual %d",
@ -960,12 +966,7 @@ void Device::sendNeighborSolicit(PacketBuffer *pktBuf)
dstIp = qFromBigEndian<UInt128>(pktData + 24);
mask = ~UInt128(0, 0) << (128 - ip6PrefixLength_);
qDebug("%s: dst %s src %s mask %s", __FUNCTION__,
qPrintable(QHostAddress(dstIp.toArray()).toString()),
qPrintable(QHostAddress(srcIp.toArray()).toString()),
qPrintable(QHostAddress(mask.toArray()).toString()));
tgtIp = ((dstIp & mask) == (srcIp & mask)) ? dstIp : ip6Gateway_;
tgtIp = ((dstIp & ip6Mask_) == ip6Subnet_) ? dstIp : ip6Gateway_;
sendNeighborSolicit(tgtIp);
}

View File

@ -107,11 +107,15 @@ private: // data
quint32 ip4_;
int ip4PrefixLength_;
quint32 ip4Gateway_;
quint32 ip4Mask_;
quint32 ip4Subnet_;
bool hasIp6_;
UInt128 ip6_;
int ip6PrefixLength_;
UInt128 ip6Gateway_;
UInt128 ip6Mask_;
UInt128 ip6Subnet_;
DeviceKey key_;

View File

@ -77,7 +77,7 @@ int main(int argc, char *argv[])
if (!drone->init())
{
exitCode = -1;
exitCode = 1;
goto _exit;
}

View File

@ -662,7 +662,7 @@ void MyService::checkVersion(::google::protobuf::RpcController* controller,
}
else {
response->set_result(OstProto::VersionCompatibility::kIncompatible);
response->set_notes(QString("Drone needs client version %1.%2.x")
response->set_notes(QString("Drone needs controller version %1.%2.x")
.arg(my[0], my[1]).toStdString());
static_cast<PbRpcController*>(controller)->TriggerDisconnect();
}

View File

@ -21,6 +21,7 @@ from rpc import RpcError
from protocols.mac_pb2 import mac, Mac
from protocols.ip4_pb2 import ip4, Ip4
from protocols.ip6_pb2 import ip6, Ip6
from protocols.icmp_pb2 import icmp, Icmp
from protocols.vlan_pb2 import vlan
use_defaults = True
@ -348,6 +349,106 @@ def dut_vlans(request, dut_ports):
request.addfinalizer(delete_vdev)
@pytest.fixture
def ping(request, drone, ip_ver, port_id, src_ip, dst_ip):
# create ICMP stream
stream_id = ost_pb.StreamIdList()
stream_id.port_id.CopyFrom(port_id)
stream_id.stream_id.add().id = 0
log.info('adding ping tx_stream %d' % stream_id.stream_id[0].id)
drone.addStream(stream_id)
# configure the ICMP echo tx stream(s)
stream_cfg = ost_pb.StreamConfigList()
stream_cfg.port_id.CopyFrom(port_id)
s = stream_cfg.stream.add()
s.stream_id.id = stream_id.stream_id[0].id
s.core.is_enabled = True
s.core.frame_len = 128
s.control.packets_per_sec = 1
s.control.num_packets = 3
# setup stream protocols as mac:eth2:ip:icmp:payload
p = s.protocol.add()
p.protocol_id.id = ost_pb.Protocol.kMacFieldNumber
p.Extensions[mac].dst_mac_mode = Mac.e_mm_resolve
p.Extensions[mac].src_mac_mode = Mac.e_mm_resolve
p = s.protocol.add()
p.protocol_id.id = ost_pb.Protocol.kEth2FieldNumber
if ip_ver == 4:
p = s.protocol.add()
p.protocol_id.id = ost_pb.Protocol.kIp4FieldNumber
ip = None
ip = p.Extensions[ip4]
ip.src_ip = src_ip
ip.dst_ip = dst_ip
elif ip_ver == 6:
p = s.protocol.add()
p.protocol_id.id = ost_pb.Protocol.kIp6FieldNumber
ip = p.Extensions[ip6]
ip.src_addr_hi = src_ip.hi
ip.src_addr_lo = src_ip.lo
ip.dst_addr_hi = dst_ip.hi
ip.dst_addr_lo = dst_ip.lo
else:
assert False # unreachable
p = s.protocol.add()
p.protocol_id.id = ost_pb.Protocol.kIcmpFieldNumber
if ip_ver == 6:
p.Extensions[icmp].icmp_version = Icmp.kIcmp6
p.Extensions[icmp].type = 128 # icmpv6 echo request
s.protocol.add().protocol_id.id = ost_pb.Protocol.kPayloadFieldNumber
log.info('configuring ping tx_stream %d' % stream_id.stream_id[0].id)
drone.modifyStream(stream_cfg)
# send ping packets
ports = ost_pb.PortIdList()
ports.port_id.add().id = port_id.id
drone.startCapture(ports)
drone.startTransmit(ports)
time.sleep(5)
drone.stopCapture(ports)
# delete ping stream
drone.deleteStream(stream_id)
# FIXME: workaround for bug#179
stream_cfg.ClearField("stream")
drone.modifyStream(stream_cfg)
# verify ICMP Replies are received
buff = drone.getCaptureBuffer(port_id)
drone.saveCaptureBuffer(buff, 'capture.pcap')
log.info('dumping Rx capture buffer (all)')
cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap'])
print(cap_pkts)
if ip_ver == 4:
filter = '(icmp.type == 0)' \
' && (icmp.code == 0)' \
' && (ip.src == ' + str(ipaddress.ip_address(dst_ip)) + ')' \
' && (ip.dst == ' + str(ipaddress.ip_address(src_ip)) + ')' \
' && !expert.severity'
elif ip_ver == 6:
filter = '(icmpv6.type == 129)' \
' && (icmpv6.code == 0)' \
' && (ipv6.src == ' \
+ str(ip6_address(dst_ip)) + ')' \
' && (ipv6.dst == ' \
+ str(ip6_address(src_ip)) + ')' \
' && !expert.severity'
log.info('dumping Rx capture buffer (filtered)')
print filter
cap_pkts = subprocess.check_output([tshark, '-nr', 'capture.pcap',
'-Y', filter])
print(cap_pkts)
return cap_pkts.count('\n') > 1
# ================================================================= #
# ----------------------------------------------------------------- #
@ -360,7 +461,7 @@ def dut_vlans(request, dut_ports):
{'ip_ver': [6], 'mac_step': 1, 'ip_step': 1},
{'ip_ver': [4, 6], 'mac_step': 2, 'ip_step': 5},
])
def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip,
def test_multiEmulDevNoVlan(request, drone, ports, dut, dut_ports, dut_ip,
stream_clear, emul_ports, dgid_list, dev_cfg):
# ----------------------------------------------------------------- #
# TESTCASE: Emulate multiple IPv4 devices (no vlans)
@ -381,9 +482,9 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip,
ip_step = dev_cfg['ip_step']
# configure the tx device(s)
devgrp_cfg = ost_pb.DeviceGroupConfigList()
devgrp_cfg.port_id.CopyFrom(ports.tx.port_id[0])
dg = devgrp_cfg.device_group.add()
tx_devgrp_cfg = ost_pb.DeviceGroupConfigList()
tx_devgrp_cfg.port_id.CopyFrom(ports.tx.port_id[0])
dg = tx_devgrp_cfg.device_group.add()
dg.device_group_id.id = dgid_list.tx.device_group_id[0].id
dg.core.name = "Host1"
dg.device_count = num_devs
@ -406,12 +507,12 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip,
ip.step.CopyFrom(ip6_address(ip_step).ip6)
ip.default_gateway.CopyFrom(ip6addr.gateway)
drone.modifyDeviceGroup(devgrp_cfg)
drone.modifyDeviceGroup(tx_devgrp_cfg)
# configure the rx device(s)
devgrp_cfg = ost_pb.DeviceGroupConfigList()
devgrp_cfg.port_id.CopyFrom(ports.rx.port_id[0])
dg = devgrp_cfg.device_group.add()
rx_devgrp_cfg = ost_pb.DeviceGroupConfigList()
rx_devgrp_cfg.port_id.CopyFrom(ports.rx.port_id[0])
dg = rx_devgrp_cfg.device_group.add()
dg.device_group_id.id = dgid_list.rx.device_group_id[0].id
dg.core.name = "Host1"
dg.device_count = num_devs
@ -434,7 +535,25 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip,
ip.step.CopyFrom(ip6_address(ip_step).ip6)
ip.default_gateway.CopyFrom(ip6addr.gateway)
drone.modifyDeviceGroup(devgrp_cfg)
drone.modifyDeviceGroup(rx_devgrp_cfg)
# test end-to-end reachability - after resolving ARP/NDP
# FIXME: if and when ping RPC is added, move to where we ping DUT below
# FIXME: also add ping test to vlan case
time.sleep(10) # wait for DAD? otherwise we don't get replies for NS
drone.clearDeviceNeighbors(emul_ports)
drone.resolveDeviceNeighbors(emul_ports)
time.sleep(3) # wait for ARP resolution
# FIXME: if ping6/ping4 order is swapped, DUT does not send NS - have
# spent several hours trying to figure out why - to no avail :(
if has_ip6:
assert ping(request, drone, 6, emul_ports.port_id[0],
tx_devgrp_cfg.device_group[0].Extensions[emul.ip6].address,
rx_devgrp_cfg.device_group[0].Extensions[emul.ip6].address)
if has_ip4:
assert ping(request, drone, 4, emul_ports.port_id[0],
tx_devgrp_cfg.device_group[0].Extensions[emul.ip4].address,
rx_devgrp_cfg.device_group[0].Extensions[emul.ip4].address)
# add the tx stream(s) - we may need more than one
stream_id = ost_pb.StreamIdList()