From 2864edb21bfd6e0a91e73416cb04197174f825b8 Mon Sep 17 00:00:00 2001 From: vlad Date: Mon, 24 Jan 2022 16:18:24 +0300 Subject: [PATCH] Initial commit --- .gitignore | 2 + CMakeLists.txt | 21 ++++ sdp-sniffer-qt.pro | 26 +++++ src/MainWindow.cpp | 249 +++++++++++++++++++++++++++++++++++++++++ src/MainWindow.h | 49 ++++++++ src/MainWindow.ui | 156 ++++++++++++++++++++++++++ src/MessageDecoder.cpp | 196 ++++++++++++++++++++++++++++++++ src/MessageDecoder.h | 44 ++++++++ src/main.cpp | 11 ++ 9 files changed, 754 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 sdp-sniffer-qt.pro create mode 100644 src/MainWindow.cpp create mode 100644 src/MainWindow.h create mode 100644 src/MainWindow.ui create mode 100644 src/MessageDecoder.cpp create mode 100644 src/MessageDecoder.h create mode 100644 src/main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..da6ed1a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Project exclude paths +/cmake-build-debug/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e2ce043 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.21) +project(sdp_sniffer_qt) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) + +set(QT_VERSION 5) +set(REQUIRED_LIBS Core Gui Widgets SerialPort) +set(REQUIRED_LIBS_QUALIFIED Qt5::Core Qt5::Gui Qt5::Widgets Qt5::SerialPort) + +file(GLOB_RECURSE SOURCES "src/*.*") +include_directories( + src +) +add_executable(${PROJECT_NAME}.elf ${SOURCES}) + +find_package(Qt${QT_VERSION} COMPONENTS ${REQUIRED_LIBS} REQUIRED) +target_link_libraries(${PROJECT_NAME}.elf ${REQUIRED_LIBS_QUALIFIED}) + diff --git a/sdp-sniffer-qt.pro b/sdp-sniffer-qt.pro new file mode 100644 index 0000000..cfba9e6 --- /dev/null +++ b/sdp-sniffer-qt.pro @@ -0,0 +1,26 @@ +QT += core gui serialport + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +CONFIG += c++20 + +# You can make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + src/main.cpp \ + src/MainWindow.cpp \ + src/MessageDecoder.cpp + +HEADERS += \ + src/MainWindow.h \ + src/MessageDecoder.h + +FORMS += \ + src/MainWindow.ui + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp new file mode 100644 index 0000000..2e0e892 --- /dev/null +++ b/src/MainWindow.cpp @@ -0,0 +1,249 @@ +#include "MainWindow.h" +#include "ui_MainWindow.h" +#include +#include +#include +#include +#include + + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) { + serial = nullptr; + ui->setupUi(this); + ui->log->document()->setTextWidth(20); + ui->log->setReadOnly(true); + setWindowTitle("Podval Sniffer soft"); + updatePortsList(); + + // это перегон текстовика с заранее сохраненными пакетами + std::fstream f("/home/vlad/Documents/sniffer-dir/test6.txt", std::ios_base::in | std::ios_base::binary); + if (f.good()) { + do { + char chars[22]; + f.read((char*) chars, 22); + if (f.good()) { + for (char c: chars) { + printf("%c", c); + putCharToMessage(c); + } + } else { + break; + } + } while (!f.eof()); + ui->log->appendHtml("[INFO] Файл c данными прочитан. Там " + QString::number(messages.size()) + " записей\n"); + } + f.close(); +} + +MainWindow::~MainWindow() { + delete serial; + delete ui; +} + +void MainWindow::on_updatePortsButton_clicked() { + updatePortsList(); +} + +void MainWindow::on_connectButton_clicked() { + auto port = ui->portSelect->currentText(); + if (port.isEmpty()) { + ui->log->appendHtml("[ERR] Порт не выбран!\n"); + } else { + ui->log->appendHtml("[INFO] Попытка открыть порт '" + port + "'\n"); + if (serial) { + serial->close(); + } + serial = new QSerialPort(port); + connect(serial, &QSerialPort::readyRead, this, &MainWindow::readDataHandler); + + serial->setBaudRate(500000); + serial->setDataBits(QSerialPort::Data8); + serial->setFlowControl(QSerialPort::NoFlowControl); + serial->setStopBits(QSerialPort::OneStop); + + if (serial->open(QIODevice::ReadWrite)) { + ui->log->appendHtml("[INFO] Успех!\n"); + } else { + ui->log->appendHtml("[ERR] Не удалось открыть порт!\n"); + QMessageBox::critical(this, tr("Error"), serial->errorString()); + } + } +} + +void MainWindow::on_disconnect_clicked() { + if (serial) { + if (serial->isOpen()) { + serial->close(); + } + delete serial; + serial = nullptr; + } + ui->log->appendHtml("[INFO] Порт отключен!\n"); +} + +void MainWindow::on_clearLog_clicked() { + ui->log->clear(); + messages.clear(); +} + +static std::string messageStdIdToString(uint16_t stdId) { + char groupBuff[64]; + if ((stdId & 0x7C0) == 0x7C0) { + // group = 4; + sprintf(groupBuff, "(%03X) Group 4, MsgId=0x%02X ", stdId, (stdId & 0x3F)); + } else if ((stdId & 0x600) == 0x600) { + // group = 3; + sprintf(groupBuff, "(%03X) Group 3, MsgId=0x%02X, SrcMAC=0x%02X", + stdId, ((stdId & 0x1C0) >> 6), (stdId & 0x3F)); + } else if ((stdId & 0x600) == 0x400) { + // group = 2; + sprintf(groupBuff, "(%03X) Group 2, MsgId=0x%02X, MAC-ID=0x%02X", + stdId, (stdId & 0x7), ((stdId & 0x1F8) >> 3)); + } else if ((stdId & 0x400) == 0) { + // group = 1; + sprintf(groupBuff, "(%03X) Group 1, MsgId=0x%02X, SrcMAC=0x%02X", + stdId, ((stdId & 0x3C0) >> 6), (stdId & 0x3F)); + } else { + sprintf(groupBuff, "(%03X) Group is incorrect!", stdId); + } + return groupBuff; +} + +void MainWindow::on_storeToFile_clicked() { + QString path = "sniff-" + QDateTime::currentDateTime().toString("yyyy.MM.dd-hh:mm:ss") + ".bin"; + std::fstream f(path.toStdString(), std::ios_base::out | std::ios_base::binary); + if (f.good()) { + for (const auto m : messages) { + f.write((const char*) &m, sizeof(m)); + } + } + f.close(); + ui->log->appendHtml("[INFO] Файл '" + path + + "' записан. Там " + QString::number(messages.size()) + " записей\n"); +} + +void MainWindow::on_loadFromFile_clicked() { + messages.clear(); + auto path = QFileDialog::getOpenFileName(this, "Open Dialog", "", "*.bin"); + if (!path.isEmpty()) { + std::fstream f(path.toStdString(), std::ios_base::in | std::ios_base::binary); + if (f.good()) { + do { + MessageStruct m{}; + f.read((char*) &m, sizeof(m)); + if (f.good()) { + messages.push_back(m); + } else { + break; + } + } while (!f.eof()); + ui->log->appendHtml("[INFO] Файл '" + path + + "' прочитан. Там " + QString::number(messages.size()) + " записей\n"); + } + f.close(); + } +} + +void MainWindow::on_generateTextFile_clicked() { + QString path = "sniff-" + QDateTime::currentDateTime().toString("yyyy.MM.dd-hh:mm:ss") + ".txt"; + std::fstream f(path.toStdString(), std::ios_base::out); + if (f.good()) { + for (const auto m : messages) { + std::string text = messageToString(m).toStdString() + "\n"; + f.write(text.c_str(), text.size()); + } + } + f.close(); + ui->log->appendHtml("[INFO] Файл '" + path + + "' записан. Там " + QString::number(messages.size()) + " записей\n"); +} + +QString MainWindow::messageToString(const MessageStruct& msg) { + char buff[1024]; + std::string time = QDateTime::fromMSecsSinceEpoch((qint64) msg.milliseconds). + toString("yyyy.MM.dd hh:mm:ss.zzz").toStdString(); + MessageDecoder decoder(msg); + sprintf(buff, "%s\n[%s]: %38s, DataLen=%X, Data={%02X %02X %02X %02X %02X %02X %02X %02X}\n", + decoder.toString().c_str(), time.c_str(), messageStdIdToString(msg.stdId).c_str(), msg.dataLen, + msg.data[0], msg.data[1], msg.data[2], msg.data[3], msg.data[4], msg.data[5], msg.data[6], msg.data[7]); + return {buff}; +} + +static uint8_t charToNum(char data) { + if (data >= '0' && data <= '9') return data - '0'; + else if (data >= 'a' && data <= 'f') return data - 'a' + 10; + else if (data >= 'A' && data <= 'F') return data - 'A' + 10; + else return 0; +} + +void MainWindow::putCharToMessage(char data) { + if (data == '>') { + // начало посылки + automat.message.milliseconds = QDateTime::currentMSecsSinceEpoch(); + automat.currPointer = 0; + automat.discard = false; + } else if (data == '\n') { + // конец посылки + if (!automat.discard) { + // обработка пакетов + if (automat.currPointer != 20) { + // ошибка + ui->log->appendHtml("[ERR] *** packet crush ***!\n"); + } else { + // вот тут дрочильня с пакетом + // сначала парсим StdId + automat.message.stdId = charToNum(automat.text[2]) | + (charToNum(automat.text[1]) << 4) | + (charToNum(automat.text[0]) << 8); + + // теперь длинну посылки + automat.message.dataLen = charToNum(automat.text[3]); + + // теперь саму посылку, она все равно всегда передается как 8 байт + for (int i = 0; i < 8; i++) { + automat.message.data[i] = charToNum(automat.text[i + 5]) | + (charToNum(automat.text[i + 4]) << 4); + } + + messages.push_back(automat.message); + ui->log->appendPlainText(messageToString(automat.message)); + +// printf(">%03X%X%02X%02X%02X%02X%02X%02X%02X%02X\n", +// automat.message.stdId, automat.message.dataLen, +// automat.message.data[0], automat.message.data[1], automat.message.data[2], automat.message.data[3], +// automat.message.data[4], automat.message.data[5], automat.message.data[6], automat.message.data[7]); + } + } + + automat.discard = true; // так, на всякий случай + } else { + if (!automat.discard) { + if (automat.currPointer != 20) { + automat.text[automat.currPointer++] = data; + } else { + automat.discard = true; + } + } + } +} + +void MainWindow::readDataHandler() { + if (serial) { + auto array = serial->readAll(); + for (char i : array) { + putCharToMessage(i); + } + } +} + +void MainWindow::updatePortsList() { + auto ports = QSerialPortInfo::availablePorts(); + + ui->portSelect->clear(); + for (const auto& p : ports) { + ui->portSelect->addItem(p.portName()); + } +} + diff --git a/src/MainWindow.h b/src/MainWindow.h new file mode 100644 index 0000000..3cff9b2 --- /dev/null +++ b/src/MainWindow.h @@ -0,0 +1,49 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include "MessageDecoder.h" + +QT_BEGIN_NAMESPACE +namespace Ui { class MainWindow; } +QT_END_NAMESPACE + +struct MessageStateAutomat { + char text[20]; // SidL0011223344556677 + int currPointer; + bool discard; // отбрасывать сообщение или нет + MessageStruct message; +}; + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MessageStateAutomat automat = {{0}, -1, true, {}}; + std::vector messages; + + explicit MainWindow(QWidget *parent = nullptr); + + ~MainWindow() override; + +private slots: + void on_updatePortsButton_clicked(); + void on_connectButton_clicked(); + void on_disconnect_clicked(); + void on_clearLog_clicked(); + void on_storeToFile_clicked(); + void on_loadFromFile_clicked(); + void on_generateTextFile_clicked(); + +private: + static QString messageToString(const MessageStruct& msg); + void putCharToMessage(char data); + void readDataHandler(); + void updatePortsList(); + + Ui::MainWindow *ui; + QSerialPort* serial; +}; +#endif // MAINWINDOW_H diff --git a/src/MainWindow.ui b/src/MainWindow.ui new file mode 100644 index 0000000..bef4697 --- /dev/null +++ b/src/MainWindow.ui @@ -0,0 +1,156 @@ + + + MainWindow + + + + 0 + 0 + 710 + 328 + + + + MainWindow + + + + + + + + + Выбрать порт + + + + + + + + 0 + 0 + + + + + + + + Обновить список + + + + + + + Соедениться + + + + + + + Отключиться + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + true + + + + + 0 + 0 + 692 + 206 + + + + + + + + + + + + + + + + Очистить + + + + + + + Сохранить в файл + + + + + + + Загрузить файл + + + + + + + Сгенерить текстовый файл + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + 0 + 0 + 710 + 21 + + + + + + + + diff --git a/src/MessageDecoder.cpp b/src/MessageDecoder.cpp new file mode 100644 index 0000000..c3e97a0 --- /dev/null +++ b/src/MessageDecoder.cpp @@ -0,0 +1,196 @@ +// +// Created by vlad on 21.01.2022. +// + +#include "MessageDecoder.h" + +MessageDecoder::MessageDecoder(const MessageStruct &message) : msg(message) {} + +int MessageDecoder::getGroup() const { + if ((msg.stdId & 0x7C0) == 0x7C0) { + // group = 4; + return 4; + } else if ((msg.stdId & 0x600) == 0x600) { + // group = 3; + return 3; + } else if ((msg.stdId & 0x600) == 0x400) { + // group = 2; + return 2; + } else if ((msg.stdId & 0x400) == 0) { + // group = 1; + return 1; + } else { + return -1; + } +} + +int MessageDecoder::getMsgId() const { + switch (getGroup()) { + case 1: return (msg.stdId & 0x3C0) >> 6; + case 2: return msg.stdId & 0x7; + case 3: return (msg.stdId & 0x1C0) >> 6; + case 4: return msg.stdId & 0x3F; + } + + return 0; +} + +int MessageDecoder::getSrcMac() const { + switch (getGroup()) { + case 3: + case 1: return msg.stdId & 0x3F; + case 2: + switch (getMsgId()) { + case 0: + case 1: + case 3: return (msg.stdId & 0x1F8) >> 3; + case 2: + case 4: + case 5: + case 6: // 6-ой и 7-ой ID кстати не описан в том документе... + case 7: + // данные должны быть, но не обязаны + if (msg.dataLen != 0) + return msg.data[0] & 0x3F; + else + return -1; + default: return -1; + } + case 4: + if (msg.dataLen != 0) + return msg.data[0] & 0x3F; + else + return -1; + } + return -1; +} + +int MessageDecoder::getDestMac() const { + switch (getGroup()) { + case 3: + case 1: + if (msg.dataLen != 0) + return msg.data[0] & 0x3F; + else + return -1; + case 2: + switch (getMsgId()) { + case 0: + case 1: + case 3: + if (msg.dataLen != 0) + return msg.data[0] & 0x3F; + else + return -1; + case 2: + case 4: + case 5: + case 6: // 6-ой и 7-ой ID кстати не описан в том документе... + case 7: + // данные должны быть, но не обязаны + return (msg.stdId & 0x1F8) >> 3; + default: return -1; + } + case 4: + return -1; + } + return -1; +} + +std::string MessageDecoder::getMessageDescription() const { + switch (getGroup()) { + case 1: + switch (getMsgId()) { + case 0x8: return "Slave I/O Multicast poll response"; + case 0xC: return "Slave I/O Change of state or cyclic message"; + case 0xE: return "Slave I/O Bit-Strobe response message"; + case 0xF: return "Slave I/O Poll response or COS/Cyclic Ack message"; + default: return "group 2 message"; + } + case 2: + switch (getMsgId()) { + case 0x0: return "Master I/O Bit-Strobe command message"; + case 0x1: return "Master I/O Multicast poll Group ID"; + case 0x2: return "Master Change of state or Cyclic Ack message"; + case 0x3: return "Slave Explicit/Unconnected response message"; + case 0x4: return "Master Explicit request message"; + case 0x5: return "Master I/O Poll command/COS/Cyclic message"; + // а это уже из таблички из вк, потому что в документе эти сообщения не описаны + case 0x6: return "Group 2 only Unconnected explicit request messages"; + case 0x7: return "Duplicate MAC-ID check message"; + default: return "Group 2 undefined message"; + } + case 3: return "Group 3 message"; + case 4: return "Group 4 message"; + } + return "undefined description"; +} + +static std::string buildDataArray(const MessageStruct& msg) { + std::string out = "{"; + for (int i = 0; i < msg.dataLen; i++) { + char tmp[8]; + if (i == msg.dataLen - 1) { + sprintf(tmp, "0x%02X", msg.data[i]); + } else { + sprintf(tmp, "0x%02X ", msg.data[i]); + } + out += tmp; + } + return out + "}"; +} +static std::string buildExplicitMessageFrame(const MessageStruct& msg) { + if (msg.dataLen == 0) return "empty explicit frame"; + char buff[256]; + sprintf(buff, "{{Frag=%d, XID=%d, MAC=%d}, ", + (msg.data[0] & 0x80) != 0, (msg.data[0] & 0x40) != 0, msg.data[0] & 0x3F); + + MessageStruct tmpMsg = msg; + if (tmpMsg.dataLen >= 1) { + // убираем первый байт, чтобы посылка была + tmpMsg.dataLen--; + for (int i = 0; i < tmpMsg.dataLen; i++) { + tmpMsg.data[i] = tmpMsg.data[i + 1]; + } + } + return buff + buildDataArray(tmpMsg) + "}"; +} +static std::string buildBitIOStrobeFrame(const MessageStruct& msg) { return "bit IO strobe frame " + buildDataArray(msg); } +static std::string buildChangeOfStateFrame(const MessageStruct& msg) { return "change of state frame " + buildDataArray(msg); } +static std::string buildIOPollFrame(const MessageStruct& msg) { return "change of state frame " + buildDataArray(msg); } + +std::string MessageDecoder::getDataFrameDescription() const { + if (msg.dataLen == 0) { + return "empty frame"; + } else { + switch (getGroup()) { + case 1: + return "Group 1 frame (IO) " + buildDataArray(msg); + case 2: + switch (getMsgId()) { + case 0: return buildBitIOStrobeFrame(msg); + case 1: return buildIOPollFrame(msg); + case 2: return buildChangeOfStateFrame(msg); + case 3: + case 4: return buildExplicitMessageFrame(msg); + case 5: return buildIOPollFrame(msg); + case 6: + case 7: return buildExplicitMessageFrame(msg); + } + return "Group 2 frame" + buildDataArray(msg); + case 3: + return buildExplicitMessageFrame(msg); + case 4: + return "Group 4 frame " + buildDataArray(msg); + } + } + return "unknown frame " + buildDataArray(msg); +} + +std::string MessageDecoder::toString() const { + char buff[1024]; + sprintf(buff, "// StdId=0x%03X, DataLen=%d, Group=%d, MsgId=%d, SrcMac=%d, DestMac=%d (%s)\n" + "// Frame: %s", msg.stdId, msg.dataLen, getGroup(), getMsgId(), getSrcMac(), getDestMac(), + getMessageDescription().c_str(), getDataFrameDescription().c_str()); + return buff; +} diff --git a/src/MessageDecoder.h b/src/MessageDecoder.h new file mode 100644 index 0000000..202dc45 --- /dev/null +++ b/src/MessageDecoder.h @@ -0,0 +1,44 @@ +// +// Created by vlad on 21.01.2022. +// + +#ifndef SDP_SNIFFER_QT_MESSAGEDECODER_H +#define SDP_SNIFFER_QT_MESSAGEDECODER_H + +#include +#include + +struct MessageStruct { + uint16_t stdId; + uint8_t dataLen; + uint8_t data[8]; + uint64_t milliseconds; +}; + +// MAC - все доступные +#define MAC_ID_MULTICAST (-1) + +// MAC - не определен +#define MAC_ID_UNDEFINED (-2) + + +class MessageDecoder { +private: + MessageStruct msg; +public: + explicit MessageDecoder(const MessageStruct& message); + + int getGroup() const; + int getMsgId() const; + int getSrcMac() const; + int getDestMac() const; + std::string getMessageDescription() const; + std::string getDataFrameDescription() const; + + std::string toString() const; + + ~MessageDecoder() = default; +}; + + +#endif //SDP_SNIFFER_QT_MESSAGEDECODER_H diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..64607f8 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,11 @@ +#include "MainWindow.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + return QApplication::exec(); +}