diff options
author | Toni Uhlig <matzeton@googlemail.com> | 2023-07-30 20:55:52 +0200 |
---|---|---|
committer | Toni Uhlig <matzeton@googlemail.com> | 2023-07-30 20:55:52 +0200 |
commit | 2da24be2e8f8ffa60e371557d75183b67fd2ea24 (patch) | |
tree | ec436bf94ad75fed1e0779d35a2bd7a64833babd | |
parent | 98c665b409595238d73f550f95c7bf83ec6142da (diff) |
Added UI to insert/delete bytes into a packet.
Signed-off-by: Toni Uhlig <matzeton@googlemail.com>
-rw-r--r-- | CMakeLists.txt | 10 | ||||
-rw-r--r-- | bytewindow.cpp | 50 | ||||
-rw-r--r-- | bytewindow.h | 31 | ||||
-rw-r--r-- | bytewindow.ui | 125 | ||||
-rw-r--r-- | mainwindow.cpp | 138 | ||||
-rw-r--r-- | mainwindow.h | 15 | ||||
-rw-r--r-- | mainwindow.ui | 6 | ||||
-rw-r--r-- | qhexedit2/src/qhexedit.h | 3 |
8 files changed, 361 insertions, 17 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 2df0801..88f7c74 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,12 @@ set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CPACK_PACKAGE_CONTACT "toni@impl.cc") +set(CPACK_DEBIAN_PACKAGE_NAME "pcap-editor") +set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) + include(ExternalProject) +include(CPack) find_package(QT NAMES Qt6 REQUIRED COMPONENTS Core Widgets) find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Widgets) @@ -42,10 +47,15 @@ endif() set(PROJECT_SOURCES main.cpp + mainwindow.cpp mainwindow.h mainwindow.ui + bytewindow.cpp + bytewindow.h + bytewindow.ui + pcapplusplus.cpp pcapplusplus.h diff --git a/bytewindow.cpp b/bytewindow.cpp new file mode 100644 index 0000000..444399b --- /dev/null +++ b/bytewindow.cpp @@ -0,0 +1,50 @@ +#include "bytewindow.h" +#include "ui_bytewindow.h" + +ByteWindow::ByteWindow(QWidget *parent) : + QDialog(parent), + ui(new Ui::ByteWindow) +{ + ui->setupUi(this); +} + +ByteWindow::~ByteWindow() +{ + delete ui; +} + +void ByteWindow::set(ByteWindowOption option, int offset, int size) +{ + switch (option) { + case ByteWindowOption::BWO_UNKNOWN: + case ByteWindowOption::BWO_INSERT: + ui->radioButtonInsert->click(); + break; + case ByteWindowOption::BWO_DELETE: + ui->radioButtonDelete->click(); + break; + } + + ui->spinBoxIndex->setValue(offset); + ui->spinBoxSize->setValue(size); +} + +ByteWindowOption ByteWindow::getOption() +{ + if (ui->radioButtonInsert->isChecked()) + return ByteWindowOption::BWO_INSERT; + if (ui->radioButtonDelete->isChecked()) + return ByteWindowOption::BWO_DELETE; + + return ByteWindowOption::BWO_UNKNOWN; +} + +int ByteWindow::getOffset() +{ + return ui->spinBoxIndex->value(); +} + +int ByteWindow::getSize() +{ + return ui->spinBoxSize->value(); +} diff --git a/bytewindow.h b/bytewindow.h new file mode 100644 index 0000000..e087a1e --- /dev/null +++ b/bytewindow.h @@ -0,0 +1,31 @@ +#ifndef BYTEWINDOW_H +#define BYTEWINDOW_H + +#include <QDialog> + +QT_BEGIN_NAMESPACE +namespace Ui { class ByteWindow; } +QT_END_NAMESPACE + +enum class ByteWindowOption { + BWO_UNKNOWN, + BWO_INSERT, + BWO_DELETE +}; + +class ByteWindow : public QDialog +{ + Q_OBJECT + +public: + explicit ByteWindow(QWidget *parent = nullptr); + ~ByteWindow(); + void set(ByteWindowOption option, int offset, int size); + ByteWindowOption getOption(); + int getOffset(); + int getSize(); + +private: + Ui::ByteWindow *ui; +}; +#endif // BYTEWINDOW_H diff --git a/bytewindow.ui b/bytewindow.ui new file mode 100644 index 0000000..011207a --- /dev/null +++ b/bytewindow.ui @@ -0,0 +1,125 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>ByteWindow</class> + <widget class="QDialog" name="ByteWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>300</width> + <height>200</height> + </rect> + </property> + <property name="minimumSize"> + <size> + <width>300</width> + <height>200</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>16777215</height> + </size> + </property> + <property name="windowTitle"> + <string>Bytes Modification</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="2" column="1"> + <widget class="QSpinBox" name="spinBoxSize"> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>65535</number> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="labelSize"> + <property name="text"> + <string>Size:</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QDialogButtonBox" name="buttonOk"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QSpinBox" name="spinBoxIndex"> + <property name="maximum"> + <number>65535</number> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="labelIndex"> + <property name="text"> + <string>Index:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QRadioButton" name="radioButtonInsert"> + <property name="text"> + <string>Insert</string> + </property> + </widget> + </item> + <item> + <widget class="QRadioButton" name="radioButtonDelete"> + <property name="text"> + <string>Delete</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonOk</sender> + <signal>accepted()</signal> + <receiver>ByteWindow</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonOk</sender> + <signal>rejected()</signal> + <receiver>ByteWindow</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/mainwindow.cpp b/mainwindow.cpp index a375d91..0060fe4 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -4,6 +4,8 @@ #include <exception> #include <qfiledialog.h> +#include <iostream> + MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , myHexEdit() @@ -11,7 +13,93 @@ MainWindow::MainWindow(QWidget *parent) , ppp(nullptr) { ui->setupUi(this); - ui->gridLayout->addWidget(&myHexEdit); + ui->gridLayout->addWidget(&myHexEdit.editor); + + const auto& enableButtons = [](MainWindow *mainwindow, bool enable, bool ui_edit_only = false) { + if (!ui_edit_only) + mainwindow->ui->actionSave->setEnabled(enable); + mainwindow->myHexEdit.prependBytes.setEnabled(enable); + mainwindow->myHexEdit.appendBytes.setEnabled(enable); + mainwindow->myHexEdit.deleteBytes.setEnabled(enable); + mainwindow->myHexEdit.deleteSelection.setEnabled(enable); + }; + enableButtons(this, false); + + myHexEdit.editor.setContextMenuPolicy(Qt::CustomContextMenu); + myHexEdit.prependBytes.setText("Prepend byte(s).."); + myHexEdit.appendBytes.setText("Append byte(s).."); + myHexEdit.deleteBytes.setText("Delete byte(s).."); + myHexEdit.deleteSelection.setText("Delete Selection"); + myHexEdit.contextMenu.addAction(&myHexEdit.prependBytes); + myHexEdit.contextMenu.addAction(&myHexEdit.appendBytes); + myHexEdit.contextMenu.addAction(&myHexEdit.deleteBytes); + myHexEdit.contextMenu.addAction(&myHexEdit.deleteSelection); + + connect(&myHexEdit.bytewindow, &QDialog::finished, this, [&](int result) { + if (result == 0) + return; + + const auto option = myHexEdit.bytewindow.getOption(); + const auto offset = myHexEdit.bytewindow.getOffset(); + const auto size = myHexEdit.bytewindow.getSize(); + + const auto &selected = ui->tableWidget->selectedItems(); + if (selected.empty()) + return; + auto &rawPacket = ppp->getRawPacket(selected.last()->row()); + + switch (option) { + case ByteWindowOption::BWO_UNKNOWN: + case ByteWindowOption::BWO_INSERT: { + uint8_t *new_bytes = new uint8_t[size]; + if (!new_bytes) + break; + memset(new_bytes, 0, size); + rawPacket.insertData(offset, new_bytes, size); + myHexEdit.editor.setData(QByteArray::fromRawData(reinterpret_cast<const char *>(rawPacket.getRawData()), rawPacket.getRawDataLen())); + delete[] new_bytes; + break; + } + case ByteWindowOption::BWO_DELETE: + rawPacket.removeData(offset, size); + myHexEdit.editor.setData(QByteArray::fromRawData(reinterpret_cast<const char *>(rawPacket.getRawData()), rawPacket.getRawDataLen())); + break; + } + }); + + connect(&myHexEdit.editor, &QAbstractScrollArea::customContextMenuRequested, this, [&](const QPoint& pos){ + myHexEdit.deleteSelection.setEnabled(myHexEdit.editor.selectedData().size() != 0); + + auto globalPos = myHexEdit.editor.mapToGlobal(pos); + auto selectedItem = myHexEdit.contextMenu.exec(globalPos); + const auto cursorPos = myHexEdit.editor.cursorPosition() / 2; + const auto& selectedLength = myHexEdit.editor.getSelectionEnd() - myHexEdit.editor.getSelectionBegin(); + const auto& showByteWindow = [&](const ByteWindowOption& opt, int offset) { + myHexEdit.bytewindow.set(opt, offset, selectedLength > 0 ? selectedLength : 1); + myHexEdit.bytewindow.show(); + }; + + if (selectedItem == &myHexEdit.prependBytes) { + showByteWindow(ByteWindowOption::BWO_INSERT, cursorPos); + } else if (selectedItem == &myHexEdit.appendBytes) { + showByteWindow(ByteWindowOption::BWO_INSERT, cursorPos + 1); + } else if (selectedItem == &myHexEdit.deleteBytes) { + showByteWindow(ByteWindowOption::BWO_DELETE, cursorPos); + } else if (selectedItem == &myHexEdit.deleteSelection) { + const auto &selected = ui->tableWidget->selectedItems(); + if (selected.empty()) + return; + + auto &rawPacket = ppp->getRawPacket(selected.last()->row()); + if (selectedLength == rawPacket.getRawDataLen()) + return; + + rawPacket.removeData(myHexEdit.editor.getSelectionBegin(), selectedLength); + myHexEdit.editor.setData(QByteArray::fromRawData(reinterpret_cast<const char *>(rawPacket.getRawData()), rawPacket.getRawDataLen())); + } else if (selectedItem) { + throw std::runtime_error("Unknown context menu " + selectedItem->text().toStdString()); + } + }); connect(ui->actionOpen, &QAction::triggered, this, [&](bool){ QString fileName = QFileDialog::getOpenFileName(this, tr("Open PCAP File"), "", tr("PCAP Files (*.pcap)")); @@ -20,6 +108,9 @@ MainWindow::MainWindow(QWidget *parent) delete ppp; ppp = nullptr; } + ui->lineEdit->clear(); + ui->tableWidget->clear(); + ui->tableWidget->setRowCount(0); ppp = new PcapPlusPlus(fileName.toStdString()); if (ppp) emit processPcap(); } @@ -44,14 +135,16 @@ MainWindow::MainWindow(QWidget *parent) }); connect(ui->actionSave, &QAction::triggered, this, [&](bool){ - QString fileName = QFileDialog::getSaveFileName(this, tr("Save PCAP File"), "", tr("PCAP Files (*.pcap)")); - if (fileName.length() > 0) { - pcpp::PcapFileWriterDevice pcapWriter(fileName.toStdString(), pcpp::LINKTYPE_ETHERNET); - if (!pcapWriter.open()) - throw std::runtime_error("Could not open file " + fileName.toStdString() + " for writing."); - { - for (auto rawPacket = ppp->rawPacketsBegin(); rawPacket < ppp->rawPacketsEnd(); rawPacket++) { - pcapWriter.writePacket(*rawPacket); + if (ppp) { + QString fileName = QFileDialog::getSaveFileName(this, tr("Save PCAP File"), "", tr("PCAP Files (*.pcap)")); + if (fileName.length() > 0) { + pcpp::PcapFileWriterDevice pcapWriter(fileName.toStdString(), pcpp::LINKTYPE_ETHERNET); + if (!pcapWriter.open()) + throw std::runtime_error("Could not open file " + fileName.toStdString() + " for writing."); + { + for (auto rawPacket = ppp->rawPacketsBegin(); rawPacket < ppp->rawPacketsEnd(); rawPacket++) { + pcapWriter.writePacket(*rawPacket); + } } } } @@ -128,15 +221,34 @@ MainWindow::MainWindow(QWidget *parent) connect(ui->tableWidget, &QTableWidget::cellPressed, this, [&] { const auto &selected = ui->tableWidget->selectedItems(); + if (selected.empty()) + return; + const auto &rawPacket = ppp->getRawPacket(selected.last()->row()); - myHexEdit.setData(QByteArray::fromRawData(reinterpret_cast<const char *>(rawPacket.getRawData()), rawPacket.getRawDataLen())); + myHexEdit.editor.setData(QByteArray::fromRawData(reinterpret_cast<const char *>(rawPacket.getRawData()), rawPacket.getRawDataLen())); + }); + + connect(ui->tableWidget, &QTableWidget::itemSelectionChanged, this, [&]() { + enableButtons(this, ui->tableWidget->selectedItems().size() > 0, true); + if (ui->tableWidget->selectedItems().size() == 0 && myHexEdit.editor.data().size() > 0) + myHexEdit.editor.setData(QByteArray()); }); - connect(&myHexEdit, &QHexEdit::dataChanged, this, [&] { + connect(&myHexEdit.editor, &QHexEdit::dataChanged, this, [&] { const auto &selected = ui->tableWidget->selectedItems(); + if (selected.empty()) + return; + auto &rawPacket = ppp->getRawPacket(selected.last()->row()); - const auto cursorPos = myHexEdit.cursorPosition() / 2; - const auto cursorData = myHexEdit.dataAt(cursorPos, 1); + const auto& cursorPos = myHexEdit.editor.cursorPosition() / 2; + auto cursorData = myHexEdit.editor.dataAt(cursorPos, 1); + if (myHexEdit.editor.cursorPosition() % 2 != 0 && myHexEdit.editor.cursorPosition() / 2 == myHexEdit.editor.data().size() - 1) { + const uint8_t new_byte = 0x00; + rawPacket.insertData(rawPacket.getRawDataLen(), &new_byte, sizeof(new_byte)); + myHexEdit.editor.setData(QByteArray::fromRawData(reinterpret_cast<const char *>(rawPacket.getRawData()), rawPacket.getRawDataLen())); + myHexEdit.editor.setCursorPosition(cursorPos * 2 + 1); + } + rawPacket.removeData(cursorPos, 1); rawPacket.insertData(cursorPos, reinterpret_cast<const uint8_t *>(cursorData.data()), cursorData.size()); }); diff --git a/mainwindow.h b/mainwindow.h index 242ca65..74a6376 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -1,10 +1,12 @@ #ifndef MAINWINDOW_H #define MAINWINDOW_H +#include "bytewindow.h" #include "pcapplusplus.h" #include "qhexedit2/src/qhexedit.h" #include <QMainWindow> +#include <QMenu> QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } @@ -19,10 +21,19 @@ public: ~MainWindow(); private: - QHexEdit myHexEdit; + struct { + QMenu contextMenu; + QAction prependBytes; + QAction appendBytes; + QAction deleteBytes; + QAction deleteSelection; + QHexEdit editor; + ByteWindow bytewindow; + } myHexEdit; + time_t firstPacketTs; - Ui::MainWindow *ui; PcapPlusPlus *ppp; + Ui::MainWindow *ui; signals: void processPcap(); diff --git a/mainwindow.ui b/mainwindow.ui index a0829c8..340b454 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -53,9 +53,15 @@ border-style: outset; border-width: 2px; border-color: beige;</string> </property> + <property name="selectionMode"> + <enum>QAbstractItemView::ExtendedSelection</enum> + </property> <property name="selectionBehavior"> <enum>QAbstractItemView::SelectRows</enum> </property> + <property name="cornerButtonEnabled"> + <bool>true</bool> + </property> <property name="columnCount"> <number>12</number> </property> diff --git a/qhexedit2/src/qhexedit.h b/qhexedit2/src/qhexedit.h index 1a5801a..f06aac5 100644 --- a/qhexedit2/src/qhexedit.h +++ b/qhexedit2/src/qhexedit.h @@ -365,7 +365,7 @@ public: QColor selectionColor(); void setSelectionColor(const QColor &color); -protected: +public: // Handle events void keyPressEvent(QKeyEvent *event); void mouseMoveEvent(QMouseEvent * event); @@ -373,7 +373,6 @@ protected: void paintEvent(QPaintEvent *event); void resizeEvent(QResizeEvent *); virtual bool focusNextPrevChild(bool next); -private: // Handle selections void resetSelection(qint64 pos); // set selectionStart and selectionEnd to pos void resetSelection(); // set selectionEnd to selectionStart |