Initial commit

This commit is contained in:
vlad 2022-01-24 16:18:24 +03:00
commit 2864edb21b
9 changed files with 754 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
# Project exclude paths
/cmake-build-debug/

21
CMakeLists.txt Normal file
View File

@ -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})

26
sdp-sniffer-qt.pro Normal file
View File

@ -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

249
src/MainWindow.cpp Normal file
View File

@ -0,0 +1,249 @@
#include "MainWindow.h"
#include "ui_MainWindow.h"
#include <QSerialPortInfo>
#include <QMessageBox>
#include <QDateTime>
#include <fstream>
#include <QFileDialog>
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("<font style=\"color:green\">[INFO] Файл c данными прочитан. Там " + QString::number(messages.size()) + " записей</font>\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("<font style=\"color:red\">[ERR] Порт не выбран!</font>\n");
} else {
ui->log->appendHtml("<font style=\"color:green\">[INFO] Попытка открыть порт '" + port + "'</font>\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("<font style=\"color:green\">[INFO] Успех!</font>\n");
} else {
ui->log->appendHtml("<font style=\"color:red\">[ERR] Не удалось открыть порт!</font>\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("<font style=\"color:green\">[INFO] Порт <font style=\"color:red\">отключен</font>!</font>\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("<font style=\"color:green\">[INFO] Файл '" + path +
"' записан. Там " + QString::number(messages.size()) + " записей</font>\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("<font style=\"color:green\">[INFO] Файл '" + path +
"' прочитан. Там " + QString::number(messages.size()) + " записей</font>\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("<font style=\"color:green\">[INFO] Файл '" + path +
"' записан. Там " + QString::number(messages.size()) + " записей</font>\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());
}
}

49
src/MainWindow.h Normal file
View File

@ -0,0 +1,49 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QSerialPort>
#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<MessageStruct> 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

156
src/MainWindow.ui Normal file
View File

@ -0,0 +1,156 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>710</width>
<height>328</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Выбрать порт</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="portSelect">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="updatePortsButton">
<property name="text">
<string>Обновить список</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="connectButton">
<property name="text">
<string>Соедениться</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="disconnect">
<property name="text">
<string>Отключиться</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>692</width>
<height>206</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QPlainTextEdit" name="log"/>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="clearLog">
<property name="text">
<string>Очистить</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="storeToFile">
<property name="text">
<string>Сохранить в файл</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="loadFromFile">
<property name="text">
<string>Загрузить файл</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="generateTextFile">
<property name="text">
<string>Сгенерить текстовый файл</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>710</width>
<height>21</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>

196
src/MessageDecoder.cpp Normal file
View File

@ -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;
}

44
src/MessageDecoder.h Normal file
View File

@ -0,0 +1,44 @@
//
// Created by vlad on 21.01.2022.
//
#ifndef SDP_SNIFFER_QT_MESSAGEDECODER_H
#define SDP_SNIFFER_QT_MESSAGEDECODER_H
#include <cstdint>
#include <string>
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

11
src/main.cpp Normal file
View File

@ -0,0 +1,11 @@
#include "MainWindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return QApplication::exec();
}