diff --git a/.travis.yml b/.travis.yml index c0818ab..9ec0896 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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: diff --git a/README.md b/README.md index 998e57c..2854442 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/binding/core.py b/binding/core.py index 3e5339b..7a41486 100644 --- a/binding/core.py +++ b/binding/core.py @@ -14,6 +14,11 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see +""" +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,11 +89,14 @@ class DroneProxy(object): return controller.response def saveCaptureBuffer(self, buffer, file_name): - f= open(file_name, 'wb') - f.write(buffer) - f.flush() - os.fsync(f.fileno()) - f.close() + """ + Save the capture buffer in a PCAP file + """ + f= open(file_name, 'wb') + f.write(buffer) + f.flush() + os.fsync(f.fileno()) + f.close() def getStreamStatsDict(self, stream_guid_list): """ diff --git a/client/deviceswidget.cpp b/client/deviceswidget.cpp index 7f84a2c..64bbbfe 100644 --- a/client/deviceswidget.cpp +++ b/client/deviceswidget.cpp @@ -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) { diff --git a/client/deviceswidget.ui b/client/deviceswidget.ui index 14d8d7e..2e09917 100644 --- a/client/deviceswidget.ui +++ b/client/deviceswidget.ui @@ -1,7 +1,8 @@ - + + DevicesWidget - - + + 0 0 @@ -9,47 +10,38 @@ 328 - + Form - - - 0 - - - 0 - - - 0 - - + + 0 - + - - + + Configuration - + true - - + + Information - + Qt::Horizontal - + 131 23 @@ -58,90 +50,116 @@ - - + + Refresh information - + Refresh device and neighbor information - + - - :/icons/refresh.png + + + :/icons/refresh.png:/icons/refresh.png - - + + Qt::ActionsContextMenu - + + 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 + + QFrame::StyledPanel - + 1 - + QAbstractItemView::ExtendedSelection - + QAbstractItemView::SelectRows - - - + + + 0 1 - + + No devices being emulated + +To emulate a device, click on Configuration and create a device group + + QAbstractItemView::SelectRows - - + + + IP neighbor cache is empty + + QAbstractItemView::SingleSelection - - - :/icons/devicegroup_add.png + + + + :/icons/devicegroup_add.png:/icons/devicegroup_add.png - + New Device Group - - - :/icons/devicegroup_delete.png + + + + :/icons/devicegroup_delete.png:/icons/devicegroup_delete.png - + Delete Device Group - - - :/icons/devicegroup_edit.png + + + + :/icons/devicegroup_edit.png:/icons/devicegroup_edit.png - + Edit Device Group + + + XTableView + QTableView +
xtableview.h
+
+
- +
diff --git a/client/dumpview.h b/client/dumpview.h index b170cd0..c99e34a 100644 --- a/client/dumpview.h +++ b/client/dumpview.h @@ -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; diff --git a/client/icons/help.png b/client/icons/help.png new file mode 100644 index 0000000..5c87017 Binary files /dev/null and b/client/icons/help.png differ diff --git a/client/jumpurl.h b/client/jumpurl.h new file mode 100644 index 0000000..d91a3be --- /dev/null +++ b/client/jumpurl.h @@ -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 +*/ + +#ifndef _JUMP_URL_H +#define _JUMP_URL_H + +#include + +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 + diff --git a/client/main.cpp b/client/main.cpp index 34f689e..1507eef 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -21,6 +21,7 @@ along with this program. If not, see #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(); diff --git a/client/mainwindow.cpp b/client/mainwindow.cpp index b3769a9..fd8020f 100644 --- a/client/mainwindow.cpp +++ b/client/mainwindow.cpp @@ -23,6 +23,8 @@ along with this program. If not, see #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 #include #include #include +#include #include +#ifdef Q_OS_WIN32 +#define WIN32_NO_STATUS +#include +#undef WIN32_NO_STATUS +#include +#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 - serverApp.replace("Ostinato.app", "drone.app"); + // applicationDirPath() does not return bundle, + // but executable inside bundle + serverApp.replace("Ostinato.app", "drone.app"); #endif - #ifdef Q_OS_WIN32 - serverApp.append("/drone.exe"); + serverApp.append("/drone.exe"); #else - serverApp.append("/drone"); + serverApp.append("/drone"); #endif - localServer_ = new QProcess(this); - localServer_->setProcessChannelMode(QProcess::ForwardedChannels); - localServer_->start(serverApp, QStringList()); + 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(); + //! \todo - find a way to terminate cleanly + localServer_->kill(); #else - localServer_->terminate(); + localServer_->terminate(); #endif + } delete pgl; @@ -144,22 +177,26 @@ MainWindow::~MainWindow() appSettings->setValue(kApplicationWindowLayout, layout); appSettings->setValue(kApplicationWindowGeometryKey, geometry()); - localServer_->waitForFinished(); - delete localServer_; + 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("

Failed to start the local drone agent - " + "error 0x%1, exit status 0x%2 exit code 0x%3.

") + .arg(localServer_->error(), 0, 16) + .arg(localServer_->exitStatus(), 0, 16) + .arg(localServer_->exitCode(), 0, 16); + if (localServer_->error() == QProcess::FailedToStart) + errorStr.append(tr("

The drone program does not exist at %1 or you " + "don't have sufficient permissions to execute it." + "

") + .arg(QCoreApplication::applicationDirPath())); + if (localServer_->exitCode() == 1) + errorStr.append(tr("

The drone program was not able to bind to " + "TCP port 7878 - maybe a drone process is already " + "running?

")); +#ifdef Q_OS_WIN32 + if (localServer_->exitCode() == STATUS_DLL_NOT_FOUND) + errorStr.append(tr("

This is most likely because Packet.dll " + "was not found - make sure you have " + "WinPcap" + " installed.

") + .arg(jumpUrl("winpcap"))); +#endif + msgBox.setText(errorStr); + msgBox.setInformativeText(tr("Try running drone directly.")); + msgBox.exec(); + + QMessageBox::information(this, QString(), + tr("

If you have remote drone agents running, you can still add " + "and connect to them.

" + "

If you don't want to start the local drone agent at startup, " + "provide the -s option to Ostinato on the command line.

" + "

Learn about Ostinato's Controller-Agent " + "architecture

").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 " + "ostinato.org 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 diff --git a/client/mainwindow.h b/client/mainwindow.h index ba16ed6..8f1f22b 100644 --- a/client/mainwindow.h +++ b/client/mainwindow.h @@ -22,6 +22,7 @@ along with this program. If not, see #include "ui_mainwindow.h" #include +#include 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); }; diff --git a/client/mainwindow.ui b/client/mainwindow.ui index dc3cb50..141c193 100644 --- a/client/mainwindow.ui +++ b/client/mainwindow.ui @@ -104,6 +104,9 @@ + + :/icons/help.png + Help (Online) diff --git a/client/ostinato.pro b/client/ostinato.pro index fd3b86c..800f46c 100644 --- a/client/ostinato.pro +++ b/client/ostinato.pro @@ -90,6 +90,7 @@ SOURCES += \ mainwindow.cpp \ ndpstatusmodel.cpp \ packetmodel.cpp \ + params.cpp \ port.cpp \ portconfigdialog.cpp \ portgroup.cpp \ diff --git a/client/ostinato.qrc b/client/ostinato.qrc index df7075b..2b0277e 100644 --- a/client/ostinato.qrc +++ b/client/ostinato.qrc @@ -20,6 +20,7 @@ icons/devicegroup_edit.png icons/exit.png icons/gaps.png + icons/help.png icons/logo.png icons/magnifier.png icons/name.png diff --git a/client/params.cpp b/client/params.cpp new file mode 100644 index 0000000..8af44f2 --- /dev/null +++ b/client/params.cpp @@ -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 +*/ + +#include "params.h" + +#include + +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(); +} diff --git a/client/params.h b/client/params.h new file mode 100644 index 0000000..a5ebc2f --- /dev/null +++ b/client/params.h @@ -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 +*/ + +#ifndef _PARAMS_H +#define _PARAMS_H + +#include + +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 + diff --git a/client/port.cpp b/client/port.cpp index 50f1716..e9da599 100644 --- a/client/port.cpp +++ b/client/port.cpp @@ -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 &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( + deviceGroupById(deviceGroupId)); if (!devGrp) { qDebug("%s: deviceGroup id %u does not exist", __FUNCTION__, diff --git a/client/port.h b/client/port.h index 3421cbd..d794480 100644 --- a/client/port.h +++ b/client/port.h @@ -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 diff --git a/client/portgroup.cpp b/client/portgroup.cpp index 23d4d33..f52ee05 100644 --- a/client/portgroup.cpp +++ b/client/portgroup.cpp @@ -19,6 +19,7 @@ along with this program. If not, see #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("

The portgroup %1:%2 does not contain any ports!

" + "

Packet Transmit/Capture requires special privileges. " + "Please ensure that you are running 'drone' - the agent " + "component of Ostinato with required privileges.

") .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 Ostinato FAQ " + "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 diff --git a/client/portgrouplist.cpp b/client/portgrouplist.cpp index 6e380a0..79f500d 100644 --- a/client/portgrouplist.cpp +++ b/client/portgrouplist.cpp @@ -19,6 +19,8 @@ along with this program. If not, see #include "portgrouplist.h" +#include "params.h" + // TODO(LOW): Remove #include @@ -29,8 +31,6 @@ PortGroupList::PortGroupList() mDeviceGroupModel(this), mDeviceModel(this) { - PortGroup *pg; - #ifdef QT_NO_DEBUG streamModelTester_ = NULL; portModelTester_ = NULL; @@ -46,8 +46,10 @@ PortGroupList::PortGroupList() #endif // Add the "Local" Port Group - pg = new PortGroup; - addPortGroup(*pg); + if (appParams.optLocalDrone()) { + PortGroup *pg = new PortGroup; + addPortGroup(*pg); + } } PortGroupList::~PortGroupList() diff --git a/client/portmodel.cpp b/client/portmodel.cpp index cd1c9d7..e1e0f06 100644 --- a/client/portmodel.cpp +++ b/client/portmodel.cpp @@ -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"); diff --git a/client/portstatswindow.cpp b/client/portstatswindow.cpp index 079cc8d..42092a1 100644 --- a/client/portstatswindow.cpp +++ b/client/portstatswindow.cpp @@ -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 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 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 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 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 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 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 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 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; -} diff --git a/client/portstatswindow.h b/client/portstatswindow.h index 717d913..6efb263 100644 --- a/client/portstatswindow.h +++ b/client/portstatswindow.h @@ -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; }; diff --git a/client/portstatswindow.ui b/client/portstatswindow.ui index 5d9e986..7c9b8e1 100644 --- a/client/portstatswindow.ui +++ b/client/portstatswindow.ui @@ -1,7 +1,8 @@ - + + PortStatsWindow - - + + 0 0 @@ -9,80 +10,105 @@ 415 - + Form - + - - + + QFrame::Panel - + QFrame::Raised - + - - + + + Transmit + + + + + + Start Tx - + Starts transmit on selected port(s) - - Start Transmit + + Start - - :/icons/control_play.png + + + :/icons/control_play.png:/icons/control_play.png - - + + Stop Tx - + Stops transmit on selected port(s) - - Stop Trasmit + + Stop - - :/icons/control_stop.png + + + :/icons/control_stop.png:/icons/control_stop.png - - + + + Qt::Vertical + + + + + + + Stats + + + + + + Clear Selected Port Stats - + Clears statistics of the selected port(s) - + Clear - - :/icons/portstats_clear.png + + + :/icons/portstats_clear.png:/icons/portstats_clear.png - - + + Clear All Ports Stats - + Clears statistics of all ports - + Clear All - - :/icons/portstats_clear_all.png + + + :/icons/portstats_clear_all.png:/icons/portstats_clear_all.png @@ -103,105 +129,131 @@ - - - Start Capture - - - Captures packets on the selected port(s) - - - Start Capture - - - :/icons/sound_none.png - - - - - - - Stop Capture - - - End capture on selecteed port(s) - - - Stop Capture - - - :/icons/sound_mute.png - - - - - - - View Capture Buffer - - - View captured packets on selected port(s) - - - View Capture - - - :/icons/magnifier.png - - - - - - + + Qt::Vertical - + + + Capture + + + + + + Start Capture + + + Captures packets on the selected port(s) + + + Start + + + + :/icons/sound_none.png:/icons/sound_none.png + + + + + + + Stop Capture + + + End capture on selecteed port(s) + + + Stop + + + + :/icons/sound_mute.png:/icons/sound_mute.png + + + + + + + View Capture Buffer + + + View captured packets on selected port(s) + + + View + + + + :/icons/magnifier.png:/icons/magnifier.png + + + + + + + Qt::Vertical + + + + + + + ARP/ND + + + + + + Resolve Neighbors - + Resolve Device Neighbors on selected port(s) - + Resolve Neighbors - - :/icons/neighbor_resolve.png + + + :/icons/neighbor_resolve.png:/icons/neighbor_resolve.png - - + + Clear Neighbors - + Clear Device Neighbors on selected port(s) - + Clear Neighbors - - :/icons/neighbor_clear.png + + + :/icons/neighbor_clear.png:/icons/neighbor_clear.png - - + + Qt::Vertical - + Qt::Horizontal - + 40 20 @@ -210,15 +262,16 @@ - - + + Select which ports to view - + Filter - - :/icons/portstats_filter.png + + + :/icons/portstats_filter.png:/icons/portstats_filter.png @@ -226,12 +279,16 @@ - + + + QAbstractItemView::SelectColumns + + - + diff --git a/client/portswindow.cpp b/client/portswindow.cpp index 7679fe3..10fb3b7 100644 --- a/client/portswindow.cpp +++ b/client/portswindow.cpp @@ -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 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,12 +499,7 @@ 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); + actionEdit_Stream->setEnabled(true); } // Duplicate/Delete are always enabled as long as we have a selection @@ -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 - " + "click Apply " + "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 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 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); } } diff --git a/client/portswindow.h b/client/portswindow.h index b407270..f072cc7 100644 --- a/client/portswindow.h +++ b/client/portswindow.h @@ -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(); diff --git a/client/portswindow.ui b/client/portswindow.ui index cbab508..d6de594 100644 --- a/client/portswindow.ui +++ b/client/portswindow.ui @@ -1,7 +1,8 @@ - + + PortsWindow - - + + 0 0 @@ -9,83 +10,138 @@ 352 - + Form - - - - + + + + Qt::Horizontal - + false - - - + + + 1 0 - + Qt::ActionsContextMenu - + QAbstractItemView::SingleSelection - - - + + + 2 0 - - 0 + + 2 - - - - 0 - - - 0 - - - 0 - - + + + + + + <p><b>Welcome to Ostinato</b></p> +<p>The port list on the left contains all the ports on which you can transmit packets.</p> +<p>Ports belong to a port group. Make sure the Port Group has a <img src=":/icons/bullet_green.png"/> next to it, then double click the port group to show or hide the ports in the port group.</p> +<p>To generate packets, you need to create and configure packet streams. A stream is a sequence of one or more packets.</p> +<p>To create a stream, select the port on which you want to send packets.</p> +<hr/> +<p>Don't see the port that you want (or any ports at all) inside the port group? <a href="http://jump.ostinato.org/noports">Get Help!</a></p> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + <p>You have selected a port group in the port list on the left.</p> +<p>You can transmit packets on any of the ports within the port group.</p> +<p>Make sure the port group has a <img src=":/icons/bullet_green.png"/> next to it and then double click the port group to show or hide the ports in the port group.</p> +<p>To generate packets, you need to create and configure packet streams. A stream is a sequence of one or more packets.</p> +<p>To create a stream, select the port on which you want to send packets. </p> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + Qt::Vertical + + + + 20 + 177 + + + + + + + + + 0 - - + + QFrame::Panel - + QFrame::Raised - - - 3 - - - 3 - - - 3 - - + + 3 - + Qt::Horizontal - + 40 20 @@ -94,8 +150,28 @@ - - + + + Apply Hint + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + Apply @@ -104,50 +180,50 @@ - - + + 0 - - + + Streams - + - + - - + + Avg pps - + true - + - - + + Avg bps - - + + false - + Qt::Horizontal - + 40 20 @@ -158,39 +234,46 @@ - - - + + + 0 1 - + Qt::ActionsContextMenu - + + 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 + + QFrame::StyledPanel - + 1 - + QAbstractItemView::ExtendedSelection - + QAbstractItemView::SelectRows - - + + Devices - + - + @@ -198,109 +281,102 @@ - - - - - - Select a port to configure streams - - - Qt::AlignCenter - - - - - - - - - :/icons/portgroup_add.png + + + + :/icons/portgroup_add.png:/icons/portgroup_add.png - + New Port Group - - - :/icons/portgroup_delete.png + + + + :/icons/portgroup_delete.png:/icons/portgroup_delete.png - + Delete Port Group - - - :/icons/portgroup_connect.png + + + + :/icons/portgroup_connect.png:/icons/portgroup_connect.png - + Connect Port Group - - - :/icons/portgroup_disconnect.png + + + + :/icons/portgroup_disconnect.png:/icons/portgroup_disconnect.png - + Disconnect Port Group - - - :/icons/stream_add.png + + + + :/icons/stream_add.png:/icons/stream_add.png - + New Stream - - - :/icons/stream_delete.png + + + + :/icons/stream_delete.png:/icons/stream_delete.png - + Delete Stream - - - :/icons/stream_edit.png + + + + :/icons/stream_edit.png:/icons/stream_edit.png - + Edit Stream - - + + true - + Exclusive Port Control (EXPERIMENTAL) - - + + Open Streams ... - - + + Save Streams ... - - + + Port Configuration ... - - - :/icons/stream_duplicate.png + + + + :/icons/stream_duplicate.png:/icons/stream_duplicate.png - + Duplicate Stream @@ -312,9 +388,19 @@

deviceswidget.h
1 + + XTreeView + QTreeView +
xtreeview.h
+
+ + XTableView + QTableView +
xtableview.h
+
- + @@ -323,11 +409,11 @@ averagePacketsPerSec setEnabled(bool) - + 326 80 - + 454 79 @@ -339,11 +425,11 @@ averageBitsPerSec setEnabled(bool) - + 523 80 - + 651 88 diff --git a/client/streamconfigdialog.cpp b/client/streamconfigdialog.cpp index 46f0dae..6fb378c 100644 --- a/client/streamconfigdialog.cpp +++ b/client/streamconfigdialog.cpp @@ -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 &streamList, + const Port &port, + QWidget *parent) + : QDialog (parent), _userStreamList(streamList), mPort(port) { - OstProto::Stream s; - mCurrentStreamIndex = streamIndex; - mpStream = new Stream; - mPort.streamByIndex(mCurrentStreamIndex)->protoDataCopyInto(s); - mpStream->protoDataCopyFrom(s); + 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; + 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("") : 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("

We found possible problems with this stream -

") + + "
    " + + log.replaceInStrings(QRegExp("(.*)"), "
  • \\1
  • ") + .join("\n") + + "
" + + tr("

Ignore?

"), 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"); diff --git a/client/streamconfigdialog.h b/client/streamconfigdialog.h index 399f7ba..ffe1039 100644 --- a/client/streamconfigdialog.h +++ b/client/streamconfigdialog.h @@ -43,9 +43,12 @@ class StreamConfigDialog : public QDialog, public Ui::StreamConfigDialog { Q_OBJECT public: - StreamConfigDialog(Port &port, uint streamIndex, QWidget *parent = 0); + StreamConfigDialog(QList &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 _userStreamList; + QList _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(); diff --git a/client/streamconfigdialog.ui b/client/streamconfigdialog.ui index d81d68d..a5fbc90 100644 --- a/client/streamconfigdialog.ui +++ b/client/streamconfigdialog.ui @@ -1,10 +1,11 @@ - + + StreamConfigDialog - - + + Qt::ApplicationModal - + 0 0 @@ -12,123 +13,140 @@ 549 - - + + 0 0 - + Edit Stream - - :/icons/stream_edit.png + + + :/icons/stream_edit.png:/icons/stream_edit.png - - QLineEdit:enabled[inputMask = "HH; "], -QLineEdit:enabled[inputMask = "HH HH; "], -QLineEdit:enabled[inputMask = "HH HH HH; "], -QLineEdit:enabled[inputMask = "HH HH HH HH; "], -QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff } + + QLineEdit:enabled[inputMask = "HH; "], +QLineEdit:enabled[inputMask = "HH HH; "], +QLineEdit:enabled[inputMask = "HH HH HH; "], +QLineEdit:enabled[inputMask = "HH HH HH HH; "], +QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff } - + true - - - - + + + + - + 0 - - + + Protocol Selection - - - - - Qt::Horizontal + + + + + Basics - - - 241 - 20 - - - + + + + + Name + + + name + + + + + + + + + + Enabled + + + + + - - - + + + Frame Length (including FCS) - - - + + + - + Fixed - + Increment - + Decrement - + Random - - - + + + Min - - - + + + false - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - + + + Max - - - + + + false - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -136,13 +154,13 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - - - + + + 0 - - + + 0 0 @@ -150,42 +168,42 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff 311 - + Simple - - - - + + + + L1 - + - - + + None - + true - - + + Mac - + false - - + + false - + Other @@ -198,60 +216,60 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff true - + L2 - + - - + + None - + true - - + + Ethernet II - + false - - + + 802.3 Raw - - + + 802.3 LLC - + false - - + + 802.3 LLC SNAP - - + + false - + Other @@ -259,116 +277,116 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - - - + + + true - + L3 - - - - + + + + None - + true - - - + + + false - + ARP - - - + + + false - + IPv4 - + false - - - + + + false - + IPv6 - - - + + + false - + IP 6over4 - + false - - - + + + false - + IP 4over6 - + false - - - + + + false - + IP 4over4 - + false - - - + + + false - + IP 6over6 - + false - - - + + + false - + Other @@ -381,36 +399,36 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff true - + VLAN - + false - + false - + - - + + Untagged - + true - - + + Tagged - - + + Stacked @@ -418,81 +436,81 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - - - + + + true - + L4 - - - - + + + + None - + true - - - + + + false - + ICMP - - - + + + false - + IGMP - - - + + + false - + TCP - - - + + + false - + UDP - - - + + + false - + Other - - - + + + false - + MLD @@ -547,43 +565,43 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff true - + Payload - + - - + + None - + true - - + + Pattern - + false - - + + Hex Dump - - + + false - + Other @@ -654,8 +672,8 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - - + + 0 0 @@ -663,28 +681,28 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff 135 - + Advanced - + - + - - + + Available Protocols - - + + true - + QAbstractItemView::ExtendedSelection - + QAbstractItemView::SelectRows @@ -692,13 +710,13 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - + - + Qt::Vertical - + 20 40 @@ -707,24 +725,25 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - - + + false - - > + + > - - :/icons/arrow_right.png + + + :/icons/arrow_right.png:/icons/arrow_right.png - + Qt::Vertical - + 20 40 @@ -735,61 +754,64 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - + - - + + Selected Protocols - + - - + + false - + ^ - - :/icons/arrow_up.png + + + :/icons/arrow_up.png:/icons/arrow_up.png - - + + false - + v - - :/icons/arrow_down.png + + + :/icons/arrow_down.png:/icons/arrow_down.png - - + + false - + - - - :/icons/delete.png + + + :/icons/delete.png:/icons/delete.png - + Qt::Horizontal - + 40 20 @@ -800,8 +822,8 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - - + + QAbstractItemView::SelectRows @@ -814,54 +836,54 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - - + + Protocol Data - + - - + + -1 - - + + Variable Fields - + - + - - + + Stream Control - - - - + + + + Send - + - - + + Packets - + true - - + + Bursts @@ -869,71 +891,71 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - - - + + + Numbers - + - - + + Number of Packets - + leNumPackets - - + + - + - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + Number of Bursts - + leNumBursts - - + + false - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + Packets per Burst - + lePacketsPerBurst - - + + false - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -941,68 +963,68 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - - - + + + Rate - + false - + false - + - - + + Packets/Sec - + true - - + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + false - + Bursts/Sec - - + + false - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - + + Bits/Sec - - + + false - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -1010,42 +1032,42 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - - - + + + After this stream - + - - + + Stop - - + + Goto Next Stream - + true - - + + Goto First - - + + false - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -1053,12 +1075,12 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - + - + Qt::Horizontal - + 20 41 @@ -1066,25 +1088,25 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - - - + + + Mode - + - - + + Fixed - + true - - + + Continuous @@ -1092,87 +1114,87 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - - - + + + true - + Gaps (in seconds) - + false - + false - - - - + + + + - - :/icons/gaps.png + + :/icons/gaps.png - - - + + + ISG - + leGapIsg - - - + + + false - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - + + + IBG - + leGapIbg - - - + + + false - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - + + + IPG - + leGapIpg - - - + + + false - + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -1180,12 +1202,12 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - + - + Qt::Vertical - + 153 21 @@ -1195,56 +1217,56 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - - + + Packet View - + - - + + Qt::Vertical - - + + QAbstractItemView::SelectItems - + QAbstractItemView::ScrollPerPixel - + true - + - - + + - - + + Prev - - + + Next - + Qt::Horizontal - + 191 20 @@ -1253,18 +1275,18 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff - - + + OK - + true - - + + Cancel @@ -1359,7 +1381,7 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff pbCancel - + @@ -1368,11 +1390,11 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff StreamConfigDialog reject() - + 623 496 - + 533 466 @@ -1384,11 +1406,11 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff leStreamId setEnabled(bool) - + 463 143 - + 463 177 @@ -1400,11 +1422,11 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff rbPacketsPerSec setEnabled(bool) - + 30 68 - + 299 82 @@ -1416,11 +1438,11 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff rbBurstsPerSec setEnabled(bool) - + 30 95 - + 299 132 @@ -1432,11 +1454,11 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff lePacketsPerBurst setEnabled(bool) - + 30 95 - + 134 189 @@ -1448,11 +1470,11 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff lePacketsPerSec setEnabled(bool) - + 299 82 - + 299 108 @@ -1464,11 +1486,11 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff leBurstsPerSec setEnabled(bool) - + 299 132 - + 299 158 @@ -1480,11 +1502,11 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff leBitsPerSec setEnabled(bool) - + 299 182 - + 299 208 @@ -1496,11 +1518,11 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff rbPacketsPerSec setChecked(bool) - + 95 70 - + 299 82 @@ -1512,11 +1534,11 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff rbBurstsPerSec setChecked(bool) - + 96 98 - + 299 132 @@ -1528,11 +1550,11 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff leNumPackets setDisabled(bool) - + 73 196 - + 164 108 @@ -1544,11 +1566,11 @@ QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff leNumBursts setDisabled(bool) - + 96 199 - + 226 155 diff --git a/client/streammodel.cpp b/client/streammodel.cpp index c66f02c..a97acde 100644 --- a/client/streammodel.cpp +++ b/client/streammodel.cpp @@ -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 &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); diff --git a/client/streammodel.h b/client/streammodel.h index d559618..57cdb97 100644 --- a/client/streammodel.h +++ b/client/streammodel.h @@ -25,6 +25,7 @@ along with this program. If not, see #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 &streams); bool insertRows (int row, int count, const QModelIndex & parent = QModelIndex()); bool removeRows (int row, int count, diff --git a/client/xtableview.h b/client/xtableview.h new file mode 100644 index 0000000..296ec54 --- /dev/null +++ b/client/xtableview.h @@ -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 +*/ + +#ifndef _X_TABLE_VIEW_H +#define _X_TABLE_VIEW_H + +#include + +#include + +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 + diff --git a/client/xtreeview.h b/client/xtreeview.h new file mode 100644 index 0000000..b69eebb --- /dev/null +++ b/client/xtreeview.h @@ -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 +*/ + +#ifndef _X_TREE_VIEW_H +#define _X_TREE_VIEW_H + +#include + +#include + +#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 + diff --git a/common/dot2llc.proto b/common/dot2llc.proto index 8223650..1b4ebf5 100644 --- a/common/dot2llc.proto +++ b/common/dot2llc.proto @@ -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 } diff --git a/common/dot2snap.proto b/common/dot2snap.proto index d49059f..a71d5c1 100644 --- a/common/dot2snap.proto +++ b/common/dot2snap.proto @@ -1,3 +1,4 @@ +/// (802.2 SNAP) /* Copyright (C) 2010 Srivats P. diff --git a/common/dot3.proto b/common/dot3.proto index f20f120..907ecdb 100644 --- a/common/dot3.proto +++ b/common/dot3.proto @@ -1,3 +1,4 @@ +/// (802.3) /* Copyright (C) 2010 Srivats P. diff --git a/common/fileformat.proto b/common/fileformat.proto index 10339d7..36e6cce 100644 --- a/common/fileformat.proto +++ b/common/fileformat.proto @@ -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; } diff --git a/common/gmp.proto b/common/gmp.proto index f1fbf56..65265e6 100755 --- a/common/gmp.proto +++ b/common/gmp.proto @@ -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 diff --git a/common/ip4.cpp b/common/ip4.cpp index 6377bd9..cdeea98 100644 --- a/common/ip4.cpp +++ b/common/ip4.cpp @@ -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: diff --git a/common/ip4.h b/common/ip4.h index 1d4dd39..5666a9c 100644 --- a/common/ip4.h +++ b/common/ip4.h @@ -44,6 +44,7 @@ public: ip4_cksum, ip4_srcAddr, ip4_dstAddr, + ip4_options, // Meta-fields ip4_isOverrideVer, diff --git a/common/ip4.proto b/common/ip4.proto index be7391d..33ebf5c 100644 --- a/common/ip4.proto +++ b/common/ip4.proto @@ -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 { diff --git a/common/ip4.ui b/common/ip4.ui index 3e98d7c..8d31ed6 100644 --- a/common/ip4.ui +++ b/common/ip4.ui @@ -366,14 +366,7 @@ Length (x4) - - - false - - - TODO - - + diff --git a/common/ip4config.cpp b/common/ip4config.cpp index 261da7a..1e7d743 100644 --- a/common/ip4config.cpp +++ b/common/ip4config.cpp @@ -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()))); } /* diff --git a/common/ip4pdml.cpp b/common/ip4pdml.cpp index f355260..eefbd6e 100644 --- a/common/ip4pdml.cpp +++ b/common/ip4pdml.cpp @@ -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( diff --git a/common/ip6pdml.cpp b/common/ip6pdml.cpp index 2f3a7f8..f0507e5 100644 --- a/common/ip6pdml.cpp +++ b/common/ip6pdml.cpp @@ -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); diff --git a/common/mac.cpp b/common/mac.cpp index 3a7df21..7d540b7 100644 --- a/common/mac.cpp +++ b/common/mac.cpp @@ -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,16 +278,14 @@ bool MacProtocol::setFieldData(int index, const QVariant &value, { case mac_dstAddr: { - quint64 mac = value.toString().toULongLong(&isOk, BASE_HEX); - if (isOk) - data.set_dst_mac(mac); + quint64 mac = value.toULongLong(); + data.set_dst_mac(mac); break; } case mac_srcAddr: { - quint64 mac = value.toString().toULongLong(&isOk, BASE_HEX); - if (isOk) - data.set_src_mac(mac); + quint64 mac = value.toULongLong(); + data.set_src_mac(mac); break; } diff --git a/common/mac.ui b/common/mac.ui index f3604bb..dfa8247 100644 --- a/common/mac.ui +++ b/common/mac.ui @@ -1,7 +1,8 @@ - + + mac - - + + 0 0 @@ -9,191 +10,157 @@ 200 - + Form - - - - + + + + Address - - - + + + Mode - - - + + + Count - - - + + + Step - - - + + + Destination - - - + + + 120 0 - - >HH HH HH HH HH HH; - - - - - - + + - + Fixed - + Increment - + Decrement - + Resolve - - - + + + false - - - - - 0 - - - - + + + false - - - - - 0 - - - - + + + Source - - - - >HH HH HH HH HH HH; - - - - - + + - - + + - + Fixed - + Increment - + Decrement - + Resolve - - - + + + false - - - - - - + + + false - - - - - 0 - - - - + + + Please ensure that a corresponding device is configured on the port to enable source/destination mac address resolution. A corresponding device is one which has VLANs and source/gateway IP corresponding to this stream. - + true - + - + Qt::Vertical - + 20 40 @@ -203,6 +170,18 @@ + + + IntEdit + QSpinBox +
intedit.h
+
+ + MacEdit + QLineEdit +
macedit.h
+
+
diff --git a/common/macconfig.cpp b/common/macconfig.cpp index 1e50854..1b01284 100644 --- a/common/macconfig.cpp +++ b/common/macconfig.cpp @@ -20,12 +20,9 @@ along with this program. If not, see #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()); } diff --git a/common/protocol.proto b/common/protocol.proto index 0d0d430..86d0bfc 100644 --- a/common/protocol.proto +++ b/common/protocol.proto @@ -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]; diff --git a/common/stp.proto b/common/stp.proto index 82d0e07..89a5223 100644 --- a/common/stp.proto +++ b/common/stp.proto @@ -40,5 +40,5 @@ message Stp { } extend Protocol { - optional Stp stp = 210; + optional Stp stp = 209; } diff --git a/common/streambase.cpp b/common/streambase.cpp index 86c5359..1430c30 100644 --- a/common/streambase.cpp +++ b/common/streambase.cpp @@ -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; } diff --git a/common/streambase.h b/common/streambase.h index 907fc8d..7493dc4 100644 --- a/common/streambase.h +++ b/common/streambase.h @@ -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); diff --git a/common/tcp.cpp b/common/tcp.cpp index 3714a5d..58ebfac 100644 --- a/common/tcp.cpp +++ b/common/tcp.cpp @@ -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 diff --git a/common/tcp.ui b/common/tcp.ui index 6f3eee8..97673f7 100644 --- a/common/tcp.ui +++ b/common/tcp.ui @@ -196,6 +196,29 @@
+ + + + <i>Note: Ostinato is stateless- it cannot establish a TCP connection and generate seq/ack numbers accordingly</i> + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + +
diff --git a/common/updater.cpp b/common/updater.cpp index 69436b4..b758b3a 100644 --- a/common/updater.cpp +++ b/common/updater.cpp @@ -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()) diff --git a/extra/qhexedit2/qhexedit2.pro b/extra/qhexedit2/qhexedit2.pro index 89479e7..7870031 100644 --- a/extra/qhexedit2/qhexedit2.pro +++ b/extra/qhexedit2/qhexedit2.pro @@ -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 diff --git a/extra/qhexedit2/src/VERSION b/extra/qhexedit2/src/VERSION new file mode 100644 index 0000000..8caded8 --- /dev/null +++ b/extra/qhexedit2/src/VERSION @@ -0,0 +1 @@ +Release 0.8.4, 2017-01-16 diff --git a/extra/qhexedit2/src/chunks.cpp b/extra/qhexedit2/src/chunks.cpp new file mode 100644 index 0000000..a4e420b --- /dev/null +++ b/extra/qhexedit2/src/chunks.cpp @@ -0,0 +1,323 @@ +#include "chunks.h" +#include + +#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 diff --git a/extra/qhexedit2/src/chunks.h b/extra/qhexedit2/src/chunks.h new file mode 100644 index 0000000..43df76c --- /dev/null +++ b/extra/qhexedit2/src/chunks.h @@ -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 + +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 _chunks; + +#ifdef MODUL_TEST +public: + int chunkSize(); +#endif +}; + +/** \endcond docNever */ + +#endif // CHUNKS_H diff --git a/extra/qhexedit2/src/commands.cpp b/extra/qhexedit2/src/commands.cpp index 303091d..e9530d5 100644 --- a/extra/qhexedit2/src/commands.cpp +++ b/extra/qhexedit2/src/commands.cpp @@ -1,9 +1,34 @@ #include "commands.h" +#include -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(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= 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(); } } diff --git a/extra/qhexedit2/src/commands.h b/extra/qhexedit2/src/commands.h index 9931b3f..9c34683 100644 --- a/extra/qhexedit2/src/commands.h +++ b/extra/qhexedit2/src/commands.h @@ -3,66 +3,43 @@ /** \cond docNever */ -#include +#include -#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 */ diff --git a/extra/qhexedit2/src/qhexedit.cpp b/extra/qhexedit2/src/qhexedit.cpp index b6dd38d..df99332 100644 --- a/extra/qhexedit2/src/qhexedit.cpp +++ b/extra/qhexedit2/src/qhexedit.cpp @@ -1,152 +1,1123 @@ -#include +#include +#include +#include +#include +#include #include "qhexedit.h" +#include -QHexEdit::QHexEdit(QWidget *parent) : QScrollArea(parent) +// ********************************************************************** Constructor, destructor + +QHexEdit::QHexEdit(QWidget *parent) : QAbstractScrollArea(parent) { - qHexEdit_p = new QHexEditPrivate(this); - setWidget(qHexEdit_p); - setWidgetResizable(true); + _addressArea = true; + _addressWidth = 4; + _asciiArea = true; + _overwriteMode = true; + _highlighting = true; + _readOnly = false; + _cursorPosition = 0; + _lastEventSize = 0; + _hexCharsInLine = 47; + _bytesPerLine = 16; + _editAreaIsAscii = false; + _hexCaps = false; + _dynamicBytesPerLine = false; + + _chunks = new Chunks(this); + _undoStack = new UndoStack(_chunks, this); +#ifdef Q_OS_WIN32 + setFont(QFont("Courier", 10)); +#else + setFont(QFont("Monospace", 10)); +#endif + setAddressAreaColor(this->palette().alternateBase().color()); + setHighlightingColor(QColor(0xff, 0xff, 0x99, 0xff)); + setSelectionColor(this->palette().highlight().color()); + + connect(&_cursorTimer, SIGNAL(timeout()), this, SLOT(updateCursor())); + connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(adjust())); + connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(adjust())); + connect(_undoStack, SIGNAL(indexChanged(int)), this, SLOT(dataChangedPrivate(int))); + + _cursorTimer.setInterval(500); + _cursorTimer.start(); + + setAddressWidth(4); + setAddressArea(true); + setAsciiArea(true); + setOverwriteMode(true); + setHighlighting(true); + setReadOnly(false); + + init(); - connect(qHexEdit_p, SIGNAL(currentAddressChanged(int)), this, SIGNAL(currentAddressChanged(int))); - connect(qHexEdit_p, SIGNAL(currentSizeChanged(int)), this, SIGNAL(currentSizeChanged(int))); - connect(qHexEdit_p, SIGNAL(dataChanged()), this, SIGNAL(dataChanged())); - connect(qHexEdit_p, SIGNAL(overwriteModeChanged(bool)), this, SIGNAL(overwriteModeChanged(bool))); - setFocusPolicy(Qt::NoFocus); } -void QHexEdit::insert(int i, const QByteArray & ba) +QHexEdit::~QHexEdit() { - qHexEdit_p->insert(i, ba); } -void QHexEdit::insert(int i, char ch) -{ - qHexEdit_p->insert(i, ch); -} - -void QHexEdit::remove(int pos, int len) -{ - qHexEdit_p->remove(pos, len); -} - -QString QHexEdit::toReadableString() -{ - return qHexEdit_p->toRedableString(); -} - -QString QHexEdit::selectionToReadableString() -{ - return qHexEdit_p->selectionToReadableString(); -} +// ********************************************************************** Properties void QHexEdit::setAddressArea(bool addressArea) { - qHexEdit_p->setAddressArea(addressArea); + _addressArea = addressArea; + adjust(); + setCursorPosition(_cursorPosition); + viewport()->update(); } -void QHexEdit::redo() +bool QHexEdit::addressArea() { - qHexEdit_p->redo(); -} - -void QHexEdit::undo() -{ - qHexEdit_p->undo(); -} - -void QHexEdit::setAddressWidth(int addressWidth) -{ - qHexEdit_p->setAddressWidth(addressWidth); -} - -void QHexEdit::setAsciiArea(bool asciiArea) -{ - qHexEdit_p->setAsciiArea(asciiArea); -} - -void QHexEdit::setHighlighting(bool mode) -{ - qHexEdit_p->setHighlighting(mode); -} - -void QHexEdit::setAddressOffset(int offset) -{ - qHexEdit_p->setAddressOffset(offset); -} - -int QHexEdit::addressOffset() -{ - return qHexEdit_p->addressOffset(); -} - -void QHexEdit::setData(const QByteArray &data) -{ - qHexEdit_p->setData(data); -} - -QByteArray QHexEdit::data() -{ - return qHexEdit_p->data(); + return _addressArea; } void QHexEdit::setAddressAreaColor(const QColor &color) { - qHexEdit_p->setAddressAreaColor(color); + _addressAreaColor = color; + viewport()->update(); } QColor QHexEdit::addressAreaColor() { - return qHexEdit_p->addressAreaColor(); + return _addressAreaColor; +} + +void QHexEdit::setAddressOffset(qint64 addressOffset) +{ + _addressOffset = addressOffset; + adjust(); + setCursorPosition(_cursorPosition); + viewport()->update(); +} + +qint64 QHexEdit::addressOffset() +{ + return _addressOffset; +} + +void QHexEdit::setAddressWidth(int addressWidth) +{ + _addressWidth = addressWidth; + adjust(); + setCursorPosition(_cursorPosition); + viewport()->update(); +} + +int QHexEdit::addressWidth() +{ + qint64 size = _chunks->size(); + int n = 1; + if (size > Q_INT64_C(0x100000000)){ n += 8; size /= Q_INT64_C(0x100000000);} + if (size > 0x10000){ n += 4; size /= 0x10000;} + if (size > 0x100){ n += 2; size /= 0x100;} + if (size > 0x10){ n += 1; size /= 0x10;} + + if (n > _addressWidth) + return n; + else + return _addressWidth; +} + +void QHexEdit::setAsciiArea(bool asciiArea) +{ + if (!asciiArea) + _editAreaIsAscii = false; + _asciiArea = asciiArea; + adjust(); + setCursorPosition(_cursorPosition); + viewport()->update(); +} + +bool QHexEdit::asciiArea() +{ + return _asciiArea; +} + +void QHexEdit::setBytesPerLine(int count) +{ + _bytesPerLine = count; + _hexCharsInLine = count * 3 - 1; + + adjust(); + setCursorPosition(_cursorPosition); + viewport()->update(); +} + +int QHexEdit::bytesPerLine() +{ + return _bytesPerLine; +} + +void QHexEdit::setCursorPosition(qint64 position) +{ + // 1. delete old cursor + _blink = false; + viewport()->update(_cursorRect); + + // 2. Check, if cursor in range? + if (position > (_chunks->size() * 2 - 1)) + position = _chunks->size() * 2 - (_overwriteMode ? 1 : 0); + + if (position < 0) + position = 0; + + // 3. Calc new position of cursor + _bPosCurrent = position / 2; + _pxCursorY = ((position / 2 - _bPosFirst) / _bytesPerLine + 1) * _pxCharHeight; + int x = (position % (2 * _bytesPerLine)); + if (_editAreaIsAscii) + { + _pxCursorX = x / 2 * _pxCharWidth + _pxPosAsciiX; + _cursorPosition = position & 0xFFFFFFFFFFFFFFFELL; + } else { + _pxCursorX = (((x / 2) * 3) + (x % 2)) * _pxCharWidth + _pxPosHexX; + _cursorPosition = position; + } + + if (_overwriteMode) + _cursorRect = QRect(_pxCursorX - horizontalScrollBar()->value(), _pxCursorY + _pxCursorWidth, _pxCharWidth, _pxCursorWidth); + else + _cursorRect = QRect(_pxCursorX - horizontalScrollBar()->value(), _pxCursorY - _pxCharHeight + 4, _pxCursorWidth, _pxCharHeight); + + // 4. Immediately draw new cursor + _blink = true; + viewport()->update(_cursorRect); + emit currentAddressChanged(_bPosCurrent); +} + +qint64 QHexEdit::cursorPosition(QPoint pos) +{ + // Calc cursor position depending on a graphical position + qint64 result = -1; + int posX = pos.x() + horizontalScrollBar()->value(); + int posY = pos.y() - 3; + if ((posX >= _pxPosHexX) && (posX < (_pxPosHexX + (1 + _hexCharsInLine) * _pxCharWidth))) + { + _editAreaIsAscii = false; + int x = (posX - _pxPosHexX) / _pxCharWidth; + x = (x / 3) * 2 + x % 3; + int y = (posY / _pxCharHeight) * 2 * _bytesPerLine; + result = _bPosFirst * 2 + x + y; + } + else + if (_asciiArea && (posX >= _pxPosAsciiX) && (posX < (_pxPosAsciiX + (1 + _bytesPerLine) * _pxCharWidth))) + { + _editAreaIsAscii = true; + int x = 2 * (posX - _pxPosAsciiX) / _pxCharWidth; + int y = (posY / _pxCharHeight) * 2 * _bytesPerLine; + result = _bPosFirst * 2 + x + y; + } + return result; +} + +qint64 QHexEdit::cursorPosition() +{ + return _cursorPosition; +} + +void QHexEdit::setData(const QByteArray &ba) +{ + _data = ba; + _bData.setData(_data); + setData(_bData); +} + +QByteArray QHexEdit::data() +{ + return _chunks->data(0, -1); +} + +void QHexEdit::setHighlighting(bool highlighting) +{ + _highlighting = highlighting; + viewport()->update(); +} + +bool QHexEdit::highlighting() +{ + return _highlighting; } void QHexEdit::setHighlightingColor(const QColor &color) { - qHexEdit_p->setHighlightingColor(color); + _brushHighlighted = QBrush(color); + _penHighlighted = QPen(viewport()->palette().color(QPalette::WindowText)); + viewport()->update(); } QColor QHexEdit::highlightingColor() { - return qHexEdit_p->highlightingColor(); -} - -void QHexEdit::setSelectionColor(const QColor &color) -{ - qHexEdit_p->setSelectionColor(color); -} - -QColor QHexEdit::selectionColor() -{ - return qHexEdit_p->selectionColor(); + return _brushHighlighted.color(); } void QHexEdit::setOverwriteMode(bool overwriteMode) { - qHexEdit_p->setOverwriteMode(overwriteMode); + _overwriteMode = overwriteMode; + emit overwriteModeChanged(overwriteMode); } bool QHexEdit::overwriteMode() { - return qHexEdit_p->overwriteMode(); + return _overwriteMode; } -void QHexEdit::setReadOnly(bool readOnly) +void QHexEdit::setSelectionColor(const QColor &color) { - qHexEdit_p->setReadOnly(readOnly); + _brushSelection = QBrush(color); + _penSelection = QPen(Qt::white); + viewport()->update(); +} + +QColor QHexEdit::selectionColor() +{ + return _brushSelection.color(); } bool QHexEdit::isReadOnly() { - return qHexEdit_p->isReadOnly(); + return _readOnly; +} + +void QHexEdit::setReadOnly(bool readOnly) +{ + _readOnly = readOnly; +} + +void QHexEdit::setHexCaps(const bool isCaps) +{ + if (_hexCaps != isCaps) + { + _hexCaps = isCaps; + viewport()->update(); + } +} + +bool QHexEdit::hexCaps() +{ + return _hexCaps; +} + +void QHexEdit::setDynamicBytesPerLine(const bool isDynamic) +{ + _dynamicBytesPerLine = isDynamic; + resizeEvent(NULL); +} + +bool QHexEdit::dynamicBytesPerLine() +{ + return _dynamicBytesPerLine; +} + +// ********************************************************************** Access to data of qhexedit +bool QHexEdit::setData(QIODevice &iODevice) +{ + bool ok = _chunks->setIODevice(iODevice); + init(); + dataChangedPrivate(); + return ok; +} + +QByteArray QHexEdit::dataAt(qint64 pos, qint64 count) +{ + return _chunks->data(pos, count); +} + +bool QHexEdit::write(QIODevice &iODevice, qint64 pos, qint64 count) +{ + return _chunks->write(iODevice, pos, count); +} + +// ********************************************************************** Char handling +void QHexEdit::insert(qint64 index, char ch) +{ + _undoStack->insert(index, ch); + refresh(); +} + +void QHexEdit::remove(qint64 index, qint64 len) +{ + _undoStack->removeAt(index, len); + refresh(); +} + +void QHexEdit::replace(qint64 index, char ch) +{ + _undoStack->overwrite(index, ch); + refresh(); +} + +// ********************************************************************** ByteArray handling +void QHexEdit::insert(qint64 pos, const QByteArray &ba) +{ + _undoStack->insert(pos, ba); + refresh(); +} + +void QHexEdit::replace(qint64 pos, qint64 len, const QByteArray &ba) +{ + _undoStack->overwrite(pos, len, ba); + refresh(); +} + +// ********************************************************************** Utility functions +void QHexEdit::ensureVisible() +{ + if (_cursorPosition < (_bPosFirst * 2)) + verticalScrollBar()->setValue((int)(_cursorPosition / 2 / _bytesPerLine)); + if (_cursorPosition > ((_bPosFirst + (_rowsShown - 1)*_bytesPerLine) * 2)) + verticalScrollBar()->setValue((int)(_cursorPosition / 2 / _bytesPerLine) - _rowsShown + 1); + if (_pxCursorX < horizontalScrollBar()->value()) + horizontalScrollBar()->setValue(_pxCursorX); + if ((_pxCursorX + _pxCharWidth) > (horizontalScrollBar()->value() + viewport()->width())) + horizontalScrollBar()->setValue(_pxCursorX + _pxCharWidth - viewport()->width()); + viewport()->update(); +} + +qint64 QHexEdit::indexOf(const QByteArray &ba, qint64 from) +{ + qint64 pos = _chunks->indexOf(ba, from); + if (pos > -1) + { + qint64 curPos = pos*2; + setCursorPosition(curPos + ba.length()*2); + resetSelection(curPos); + setSelection(curPos + ba.length()*2); + ensureVisible(); + } + return pos; +} + +bool QHexEdit::isModified() +{ + return _modified; +} + +qint64 QHexEdit::lastIndexOf(const QByteArray &ba, qint64 from) +{ + qint64 pos = _chunks->lastIndexOf(ba, from); + if (pos > -1) + { + qint64 curPos = pos*2; + setCursorPosition(curPos - 1); + resetSelection(curPos); + setSelection(curPos + ba.length()*2); + ensureVisible(); + } + return pos; +} + +void QHexEdit::redo() +{ + _undoStack->redo(); + setCursorPosition(_chunks->pos()*(_editAreaIsAscii ? 1 : 2)); + refresh(); +} + +QString QHexEdit::selectionToReadableString() +{ + QByteArray ba = _chunks->data(getSelectionBegin(), getSelectionEnd() - getSelectionBegin()); + return toReadable(ba); } void QHexEdit::setFont(const QFont &font) { - qHexEdit_p->setFont(font); + QWidget::setFont(font); + _pxCharWidth = fontMetrics().width(QLatin1Char('2')); + _pxCharHeight = fontMetrics().height(); + _pxGapAdr = _pxCharWidth / 2; + _pxGapAdrHex = _pxCharWidth; + _pxGapHexAscii = 2 * _pxCharWidth; + _pxCursorWidth = _pxCharHeight / 7; + _pxSelectionSub = _pxCharHeight / 5; + viewport()->update(); } -const QFont & QHexEdit::font() const +QString QHexEdit::toReadableString() { - return qHexEdit_p->font(); + QByteArray ba = _chunks->data(); + return toReadable(ba); +} + +void QHexEdit::undo() +{ + _undoStack->undo(); + setCursorPosition(_chunks->pos()*(_editAreaIsAscii ? 1 : 2)); + refresh(); +} + +// ********************************************************************** Handle events +void QHexEdit::keyPressEvent(QKeyEvent *event) +{ + // Cursor movements + if (event->matches(QKeySequence::MoveToNextChar)) + { + qint64 pos = _cursorPosition + 1; + if (_editAreaIsAscii) + pos += 1; + setCursorPosition(pos); + resetSelection(pos); + } + if (event->matches(QKeySequence::MoveToPreviousChar)) + { + qint64 pos = _cursorPosition - 1; + if (_editAreaIsAscii) + pos -= 1; + setCursorPosition(pos); + resetSelection(pos); + } + if (event->matches(QKeySequence::MoveToEndOfLine)) + { + qint64 pos = _cursorPosition - (_cursorPosition % (2 * _bytesPerLine)) + (2 * _bytesPerLine) - 1; + setCursorPosition(pos); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToStartOfLine)) + { + qint64 pos = _cursorPosition - (_cursorPosition % (2 * _bytesPerLine)); + setCursorPosition(pos); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToPreviousLine)) + { + setCursorPosition(_cursorPosition - (2 * _bytesPerLine)); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToNextLine)) + { + setCursorPosition(_cursorPosition + (2 * _bytesPerLine)); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToNextPage)) + { + setCursorPosition(_cursorPosition + (((_rowsShown - 1) * 2 * _bytesPerLine))); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToPreviousPage)) + { + setCursorPosition(_cursorPosition - (((_rowsShown - 1) * 2 * _bytesPerLine))); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToEndOfDocument)) + { + setCursorPosition(_chunks->size() * 2 ); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToStartOfDocument)) + { + setCursorPosition(0); + resetSelection(_cursorPosition); + } + + // Select commands + if (event->matches(QKeySequence::SelectAll)) + { + resetSelection(0); + setSelection(2 * _chunks->size() + 1); + } + if (event->matches(QKeySequence::SelectNextChar)) + { + qint64 pos = _cursorPosition + 1; + if (_editAreaIsAscii) + pos += 1; + setCursorPosition(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectPreviousChar)) + { + qint64 pos = _cursorPosition - 1; + if (_editAreaIsAscii) + pos -= 1; + setSelection(pos); + setCursorPosition(pos); + } + if (event->matches(QKeySequence::SelectEndOfLine)) + { + qint64 pos = _cursorPosition - (_cursorPosition % (2 * _bytesPerLine)) + (2 * _bytesPerLine) - 1; + setCursorPosition(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectStartOfLine)) + { + qint64 pos = _cursorPosition - (_cursorPosition % (2 * _bytesPerLine)); + setCursorPosition(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectPreviousLine)) + { + qint64 pos = _cursorPosition - (2 * _bytesPerLine); + setCursorPosition(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectNextLine)) + { + qint64 pos = _cursorPosition + (2 * _bytesPerLine); + setCursorPosition(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectNextPage)) + { + qint64 pos = _cursorPosition + (((viewport()->height() / _pxCharHeight) - 1) * 2 * _bytesPerLine); + setCursorPosition(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectPreviousPage)) + { + qint64 pos = _cursorPosition - (((viewport()->height() / _pxCharHeight) - 1) * 2 * _bytesPerLine); + setCursorPosition(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectEndOfDocument)) + { + qint64 pos = _chunks->size() * 2; + setCursorPosition(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectStartOfDocument)) + { + qint64 pos = 0; + setCursorPosition(pos); + setSelection(pos); + } + + // Edit Commands + if (!_readOnly) + { + /* Cut */ + if (event->matches(QKeySequence::Cut)) + { + QByteArray ba = _chunks->data(getSelectionBegin(), getSelectionEnd() - getSelectionBegin()).toHex(); + for (qint64 idx = 32; idx < ba.size(); idx +=33) + ba.insert(idx, "\n"); + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(ba); + if (_overwriteMode) + { + qint64 len = getSelectionEnd() - getSelectionBegin(); + replace(getSelectionBegin(), (int)len, QByteArray((int)len, char(0))); + } + else + { + remove(getSelectionBegin(), getSelectionEnd() - getSelectionBegin()); + } + setCursorPosition(2 * getSelectionBegin()); + resetSelection(2 * getSelectionBegin()); + } else + + /* Paste */ + if (event->matches(QKeySequence::Paste)) + { + QClipboard *clipboard = QApplication::clipboard(); + QByteArray ba = QByteArray().fromHex(clipboard->text().toLatin1()); + if (_overwriteMode) + { + ba = ba.left(std::min(ba.size(), (_chunks->size() - _bPosCurrent))); + replace(_bPosCurrent, ba.size(), ba); + } + else + insert(_bPosCurrent, ba); + setCursorPosition(_cursorPosition + 2 * ba.size()); + resetSelection(getSelectionBegin()); + } else + + /* Delete char */ + if (event->matches(QKeySequence::Delete)) + { + if (getSelectionBegin() != getSelectionEnd()) + { + _bPosCurrent = getSelectionBegin(); + if (_overwriteMode) + { + QByteArray ba = QByteArray(getSelectionEnd() - getSelectionBegin(), char(0)); + replace(_bPosCurrent, ba.size(), ba); + } + else + { + remove(_bPosCurrent, getSelectionEnd() - getSelectionBegin()); + } + } + else + { + if (_overwriteMode) + replace(_bPosCurrent, char(0)); + else + remove(_bPosCurrent, 1); + } + setCursorPosition(2 * _bPosCurrent); + resetSelection(2 * _bPosCurrent); + } else + + /* Backspace */ + if ((event->key() == Qt::Key_Backspace) && (event->modifiers() == Qt::NoModifier)) + { + if (getSelectionBegin() != getSelectionEnd()) + { + _bPosCurrent = getSelectionBegin(); + setCursorPosition(2 * _bPosCurrent); + if (_overwriteMode) + { + QByteArray ba = QByteArray(getSelectionEnd() - getSelectionBegin(), char(0)); + replace(_bPosCurrent, ba.size(), ba); + } + else + { + remove(_bPosCurrent, getSelectionEnd() - getSelectionBegin()); + } + resetSelection(2 * _bPosCurrent); + } + else + { + bool behindLastByte = false; + if ((_cursorPosition / 2) == _chunks->size()) + behindLastByte = true; + + _bPosCurrent -= 1; + if (_overwriteMode) + replace(_bPosCurrent, char(0)); + else + remove(_bPosCurrent, 1); + + if (!behindLastByte) + _bPosCurrent -= 1; + + setCursorPosition(2 * _bPosCurrent); + resetSelection(2 * _bPosCurrent); + } + } else + + /* undo */ + if (event->matches(QKeySequence::Undo)) + { + undo(); + } else + + /* redo */ + if (event->matches(QKeySequence::Redo)) + { + redo(); + } else + + if ((QApplication::keyboardModifiers() == Qt::NoModifier) || + (QApplication::keyboardModifiers() == Qt::KeypadModifier) || + (QApplication::keyboardModifiers() == Qt::ShiftModifier) || + (QApplication::keyboardModifiers() == (Qt::AltModifier | Qt::ControlModifier)) || + (QApplication::keyboardModifiers() == Qt::GroupSwitchModifier)) + { + /* Hex and ascii input */ + int key; + if (_editAreaIsAscii) + key = (uchar)event->text()[0].toLatin1(); + else + key = int(event->text()[0].toLower().toLatin1()); + + if ((((key >= '0' && key <= '9') || (key >= 'a' && key <= 'f')) && _editAreaIsAscii == false) + || (key >= ' ' && _editAreaIsAscii)) + { + if (getSelectionBegin() != getSelectionEnd()) + { + if (_overwriteMode) + { + qint64 len = getSelectionEnd() - getSelectionBegin(); + replace(getSelectionBegin(), (int)len, QByteArray((int)len, char(0))); + } else + { + remove(getSelectionBegin(), getSelectionEnd() - getSelectionBegin()); + _bPosCurrent = getSelectionBegin(); + } + setCursorPosition(2 * _bPosCurrent); + resetSelection(2 * _bPosCurrent); + } + + // If insert mode, then insert a byte + if (_overwriteMode == false) + if ((_cursorPosition % 2) == 0) + insert(_bPosCurrent, char(0)); + + // Change content + if (_chunks->size() > 0) + { + char ch = key; + if (!_editAreaIsAscii){ + QByteArray hexValue = _chunks->data(_bPosCurrent, 1).toHex(); + if ((_cursorPosition % 2) == 0) + hexValue[0] = key; + else + hexValue[1] = key; + ch = QByteArray().fromHex(hexValue)[0]; + } + replace(_bPosCurrent, ch); + if (_editAreaIsAscii) + setCursorPosition(_cursorPosition + 2); + else + setCursorPosition(_cursorPosition + 1); + resetSelection(_cursorPosition); + } + } + } + + + } + + /* Copy */ + if (event->matches(QKeySequence::Copy)) + { + QByteArray ba = _chunks->data(getSelectionBegin(), getSelectionEnd() - getSelectionBegin()).toHex(); + for (qint64 idx = 32; idx < ba.size(); idx +=33) + ba.insert(idx, "\n"); + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(ba); + } + + // Switch between insert/overwrite mode + if ((event->key() == Qt::Key_Insert) && (event->modifiers() == Qt::NoModifier)) + { + setOverwriteMode(!overwriteMode()); + setCursorPosition(_cursorPosition); + } + + // switch from hex to ascii edit + if (event->key() == Qt::Key_Tab && !_editAreaIsAscii){ + _editAreaIsAscii = true; + setCursorPosition(_cursorPosition); + } + + // switch from ascii to hex edit + if (event->key() == Qt::Key_Backtab && _editAreaIsAscii){ + _editAreaIsAscii = false; + setCursorPosition(_cursorPosition); + } + + refresh(); +} + +void QHexEdit::mouseMoveEvent(QMouseEvent * event) +{ + _blink = false; + viewport()->update(); + qint64 actPos = cursorPosition(event->pos()); + if (actPos >= 0) + { + setCursorPosition(actPos); + setSelection(actPos); + } +} + +void QHexEdit::mousePressEvent(QMouseEvent * event) +{ + _blink = false; + viewport()->update(); + qint64 cPos = cursorPosition(event->pos()); + if (cPos >= 0) + { + resetSelection(cPos); + setCursorPosition(cPos); + } +} + +void QHexEdit::paintEvent(QPaintEvent *event) +{ + QPainter painter(viewport()); + int pxOfsX = horizontalScrollBar()->value(); + + if (event->rect() != _cursorRect) + { + int pxPosStartY = _pxCharHeight; + + // draw some patterns if needed + painter.fillRect(event->rect(), viewport()->palette().color(QPalette::Base)); + if (_addressArea) + painter.fillRect(QRect(-pxOfsX, event->rect().top(), _pxPosHexX - _pxGapAdrHex/2, height()), _addressAreaColor); + if (_asciiArea) + { + int linePos = _pxPosAsciiX - (_pxGapHexAscii / 2); + painter.setPen(Qt::gray); + painter.drawLine(linePos - pxOfsX, event->rect().top(), linePos - pxOfsX, height()); + } + + painter.setPen(viewport()->palette().color(QPalette::WindowText)); + + // paint address area + if (_addressArea) + { + QString address; + for (int row=0, pxPosY = _pxCharHeight; row <= (_dataShown.size()/_bytesPerLine); row++, pxPosY +=_pxCharHeight) + { + address = QString("%1").arg(_bPosFirst + row*_bytesPerLine + _addressOffset, _addrDigits, 16, QChar('0')); + painter.drawText(_pxPosAdrX - pxOfsX, pxPosY, address); + } + } + + // paint hex and ascii area + QPen colStandard = QPen(viewport()->palette().color(QPalette::WindowText)); + + painter.setBackgroundMode(Qt::TransparentMode); + + for (int row = 0, pxPosY = pxPosStartY; row <= _rowsShown; row++, pxPosY +=_pxCharHeight) + { + QByteArray hex; + int pxPosX = _pxPosHexX - pxOfsX; + int pxPosAsciiX2 = _pxPosAsciiX - pxOfsX; + qint64 bPosLine = row * _bytesPerLine; + for (int colIdx = 0; ((bPosLine + colIdx) < _dataShown.size() && (colIdx < _bytesPerLine)); colIdx++) + { + QColor c = viewport()->palette().color(QPalette::Base); + painter.setPen(colStandard); + + qint64 posBa = _bPosFirst + bPosLine + colIdx; + if ((getSelectionBegin() <= posBa) && (getSelectionEnd() > posBa)) + { + c = _brushSelection.color(); + painter.setPen(_penSelection); + } + else + { + if (_highlighting) + if (_markedShown.at((int)(posBa - _bPosFirst))) + { + c = _brushHighlighted.color(); + painter.setPen(_penHighlighted); + } + } + + // render hex value + QRect r; + if (colIdx == 0) + r.setRect(pxPosX, pxPosY - _pxCharHeight + _pxSelectionSub, 2*_pxCharWidth, _pxCharHeight); + else + r.setRect(pxPosX - _pxCharWidth, pxPosY - _pxCharHeight + _pxSelectionSub, 3*_pxCharWidth, _pxCharHeight); + painter.fillRect(r, c); + hex = _hexDataShown.mid((bPosLine + colIdx) * 2, 2); + painter.drawText(pxPosX, pxPosY, hexCaps()?hex.toUpper():hex); + pxPosX += 3*_pxCharWidth; + + // render ascii value + if (_asciiArea) + { + int ch = (uchar)_dataShown.at(bPosLine + colIdx); + if ( ch < 0x20 ) + ch = '.'; + r.setRect(pxPosAsciiX2, pxPosY - _pxCharHeight + _pxSelectionSub, _pxCharWidth, _pxCharHeight); + painter.fillRect(r, c); + painter.drawText(pxPosAsciiX2, pxPosY, QChar(ch)); + pxPosAsciiX2 += _pxCharWidth; + } + } + } + painter.setBackgroundMode(Qt::TransparentMode); + painter.setPen(viewport()->palette().color(QPalette::WindowText)); + } + + // paint cursor + if (_blink && !_readOnly && hasFocus()) + painter.fillRect(_cursorRect, this->palette().color(QPalette::WindowText)); + else + { + painter.fillRect(QRect(_pxCursorX - pxOfsX, _pxCursorY - _pxCharHeight, _pxCharWidth, _pxCharHeight), viewport()->palette().color(QPalette::Base)); + if (_editAreaIsAscii) { + QByteArray ba = _dataShown.mid((_cursorPosition - _bPosFirst) / 2, 1); + if (ba != "") + { + if (ba.at(0) <= ' ') + ba[0] = '.'; + painter.drawText(_pxCursorX - pxOfsX, _pxCursorY, ba); + } + } else { + painter.drawText(_pxCursorX - pxOfsX, _pxCursorY, _hexDataShown.mid(_cursorPosition - _bPosFirst, 1)); + } + } + + // emit event, if size has changed + if (_lastEventSize != _chunks->size()) + { + _lastEventSize = _chunks->size(); + emit currentSizeChanged(_lastEventSize); + } +} + +void QHexEdit::resizeEvent(QResizeEvent *) +{ + if (_dynamicBytesPerLine) + { + int pxFixGaps = 0; + if (_addressArea) + pxFixGaps = addressWidth() * _pxCharWidth + _pxGapAdr; + pxFixGaps += _pxGapAdrHex; + if (_asciiArea) + pxFixGaps += _pxGapHexAscii; + + // +1 because the last hex value do not have space. so it is effective one char more + int charWidth = (viewport()->width() - pxFixGaps ) / _pxCharWidth + 1; + + // 2 hex alfa-digits 1 space 1 ascii per byte = 4; if ascii is disabled then 3 + // to prevent devision by zero use the min value 1 + setBytesPerLine(std::max(charWidth / (_asciiArea ? 4 : 3),1)); + } + adjust(); +} + +bool QHexEdit::focusNextPrevChild(bool next) +{ + if (_addressArea) + { + if ( (next && _editAreaIsAscii) || (!next && !_editAreaIsAscii )) + return QWidget::focusNextPrevChild(next); + else + return false; + } + else + { + return QWidget::focusNextPrevChild(next); + } +} + +// ********************************************************************** Handle selections +void QHexEdit::resetSelection() +{ + _bSelectionBegin = _bSelectionInit; + _bSelectionEnd = _bSelectionInit; +} + +void QHexEdit::resetSelection(qint64 pos) +{ + pos = pos / 2 ; + if (pos < 0) + pos = 0; + if (pos > _chunks->size()) + pos = _chunks->size(); + + _bSelectionInit = pos; + _bSelectionBegin = pos; + _bSelectionEnd = pos; +} + +void QHexEdit::setSelection(qint64 pos) +{ + pos = pos / 2; + if (pos < 0) + pos = 0; + if (pos > _chunks->size()) + pos = _chunks->size(); + + if (pos >= _bSelectionInit) + { + _bSelectionEnd = pos; + _bSelectionBegin = _bSelectionInit; + } + else + { + _bSelectionBegin = pos; + _bSelectionEnd = _bSelectionInit; + } +} + +int QHexEdit::getSelectionBegin() +{ + return _bSelectionBegin; +} + +int QHexEdit::getSelectionEnd() +{ + return _bSelectionEnd; +} + +// ********************************************************************** Private utility functions +void QHexEdit::init() +{ + _undoStack->clear(); + setAddressOffset(0); + resetSelection(0); + setCursorPosition(0); + verticalScrollBar()->setValue(0); + _modified = false; +} + +void QHexEdit::adjust() +{ + // recalc Graphics + if (_addressArea) + { + _addrDigits = addressWidth(); + _pxPosHexX = _pxGapAdr + _addrDigits*_pxCharWidth + _pxGapAdrHex; + } + else + _pxPosHexX = _pxGapAdrHex; + _pxPosAdrX = _pxGapAdr; + _pxPosAsciiX = _pxPosHexX + _hexCharsInLine * _pxCharWidth + _pxGapHexAscii; + + // set horizontalScrollBar() + int pxWidth = _pxPosAsciiX; + if (_asciiArea) + pxWidth += _bytesPerLine*_pxCharWidth; + horizontalScrollBar()->setRange(0, pxWidth - viewport()->width()); + horizontalScrollBar()->setPageStep(viewport()->width()); + + // set verticalScrollbar() + _rowsShown = ((viewport()->height()-4)/_pxCharHeight); + int lineCount = (int)(_chunks->size() / (qint64)_bytesPerLine) + 1; + verticalScrollBar()->setRange(0, lineCount - _rowsShown); + verticalScrollBar()->setPageStep(_rowsShown); + + int value = verticalScrollBar()->value(); + _bPosFirst = (qint64)value * _bytesPerLine; + _bPosLast = _bPosFirst + (qint64)(_rowsShown * _bytesPerLine) - 1; + if (_bPosLast >= _chunks->size()) + _bPosLast = _chunks->size() - 1; + readBuffers(); + setCursorPosition(_cursorPosition); +} + +void QHexEdit::dataChangedPrivate(int) +{ + _modified = _undoStack->index() != 0; + adjust(); + emit dataChanged(); +} + +void QHexEdit::refresh() +{ + ensureVisible(); + readBuffers(); +} + +void QHexEdit::readBuffers() +{ + _dataShown = _chunks->data(_bPosFirst, _bPosLast - _bPosFirst + _bytesPerLine + 1, &_markedShown); + _hexDataShown = QByteArray(_dataShown.toHex()); +} + +QString QHexEdit::toReadable(const QByteArray &ba) +{ + QString result; + + for (int i=0; i < ba.size(); i += 16) + { + QString addrStr = QString("%1").arg(_addressOffset + i, addressWidth(), 16, QChar('0')); + QString hexStr; + QString ascStr; + for (int j=0; j<16; j++) + { + if ((i + j) < ba.size()) + { + hexStr.append(" ").append(ba.mid(i+j, 1).toHex()); + char ch = ba[i + j]; + if ((ch < 0x20) || (ch > 0x7e)) + ch = '.'; + ascStr.append(QChar(ch)); + } + } + result += addrStr + " " + QString("%1").arg(hexStr, -48) + " " + QString("%1").arg(ascStr, -17) + "\n"; + } + return result; +} + +void QHexEdit::updateCursor() +{ + if (_blink) + _blink = false; + else + _blink = true; + viewport()->update(_cursorRect); } diff --git a/extra/qhexedit2/src/qhexedit.h b/extra/qhexedit2/src/qhexedit.h index b5a9601..0bac26f 100644 --- a/extra/qhexedit2/src/qhexedit.h +++ b/extra/qhexedit2/src/qhexedit.h @@ -1,21 +1,33 @@ #ifndef QHEXEDIT_H #define QHEXEDIT_H -#include -#include "qhexedit_p.h" +#include +#include +#include + +#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); + 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); + +/*! \cond docNever */ +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: - /*! \cond docNever */ - QHexEditPrivate *qHexEdit_p; - QHBoxLayout *layout; - QScrollArea *scrollArea; + // 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 diff --git a/extra/qhexedit2/src/qhexedit_p.cpp b/extra/qhexedit2/src/qhexedit_p.cpp deleted file mode 100644 index 2f046bb..0000000 --- a/extra/qhexedit2/src/qhexedit_p.cpp +++ /dev/null @@ -1,800 +0,0 @@ -#include - -#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(); -} diff --git a/extra/qhexedit2/src/qhexedit_p.h b/extra/qhexedit2/src/qhexedit_p.h deleted file mode 100644 index b802af3..0000000 --- a/extra/qhexedit2/src/qhexedit_p.h +++ /dev/null @@ -1,120 +0,0 @@ -#ifndef QHEXEDIT_P_H -#define QHEXEDIT_P_H - -/** \cond docNever */ - - -#include -#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 - diff --git a/extra/qhexedit2/src/xbytearray.cpp b/extra/qhexedit2/src/xbytearray.cpp deleted file mode 100644 index ec8bf3d..0000000 --- a/extra/qhexedit2/src/xbytearray.cpp +++ /dev/null @@ -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; -} diff --git a/extra/qhexedit2/src/xbytearray.h b/extra/qhexedit2/src/xbytearray.h deleted file mode 100644 index 2b67c61..0000000 --- a/extra/qhexedit2/src/xbytearray.h +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef XBYTEARRAY_H -#define XBYTEARRAY_H - -/** \cond docNever */ - -#include - -/*! 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 diff --git a/server/abstractport.cpp b/server/abstractport.cpp index 74d50c4..86533ad 100644 --- a/server/abstractport.cpp +++ b/server/abstractport.cpp @@ -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()) diff --git a/server/device.cpp b/server/device.cpp index b1469ca..56a7c1a 100644 --- a/server/device.cpp +++ b/server/device.cpp @@ -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(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(pktData + 12); // srcIp in original pkt srcIp = qFromBigEndian(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(pktData + 8); // srcIp in original pkt srcIp = qFromBigEndian(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(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); } diff --git a/server/device.h b/server/device.h index f77615b..cdc4cec 100644 --- a/server/device.h +++ b/server/device.h @@ -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_; diff --git a/server/drone_main.cpp b/server/drone_main.cpp index f80e62d..4ce6c7b 100644 --- a/server/drone_main.cpp +++ b/server/drone_main.cpp @@ -77,7 +77,7 @@ int main(int argc, char *argv[]) if (!drone->init()) { - exitCode = -1; + exitCode = 1; goto _exit; } diff --git a/server/myservice.cpp b/server/myservice.cpp index 35e8bc4..1599189 100644 --- a/server/myservice.cpp +++ b/server/myservice.cpp @@ -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(controller)->TriggerDisconnect(); } diff --git a/test/emultest.py b/test/emultest.py index 1124354..4f52993 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -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()