diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 59de349..9a2304d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -155,6 +155,8 @@ jobs: ) release_msg="${release_msg//$'\n'/'-'}" release_msg="${release_msg//$'\r'/'-'}" + release_msg="${release_msg//$'('/'-'}" + release_msg="${release_msg//$')'/'-'}" echo $release_msg echo "::set-output name=msg::$release_msg" diff --git a/SavvyCAN.pro b/SavvyCAN.pro index ee9ac6f..9df479e 100644 --- a/SavvyCAN.pro +++ b/SavvyCAN.pro @@ -92,7 +92,8 @@ SOURCES += main.cpp\ re/sniffer/SnifferDelegate.cpp \ connections/newconnectiondialog.cpp \ re/temporalgraphwindow.cpp \ - filterutility.cpp + filterutility.cpp \ + pcaplite.cpp HEADERS += mainwindow.h \ can_structs.h \ @@ -178,7 +179,8 @@ HEADERS += mainwindow.h \ re/sniffer/SnifferDelegate.h \ connections/newconnectiondialog.h \ re/temporalgraphwindow.h \ - filterutility.h + filterutility.h \ + pcaplite.h FORMS += ui/candatagrid.ui \ ui/dbccomparatorwindow.ui \ diff --git a/framefileio.cpp b/framefileio.cpp index e9d0e81..5dc82d4 100644 --- a/framefileio.cpp +++ b/framefileio.cpp @@ -8,6 +8,7 @@ #include #include #include +#include "pcaplite.h" #include "utility.h" #include "blfhandler.h" @@ -189,6 +190,7 @@ bool FrameFileIO::loadFrameFile(QString &fileName, QVector* frameCache filters.append(QString(tr("Tesla Autopilot Snapshot (*.CAN *.can)"))); filters.append(QString(tr("CLX000 (*.txt *.TXT)"))); filters.append(QString(tr("CANServer Binary Log (*.log *.LOG)"))); + filters.append(QString(tr("Wireshark (*.pcap *.PCAP *.pcapng *.PCAPNG)"))); dialog.setDirectory(settings.value("FileIO/LoadSaveDirectory", dialog.directory().path()).toString()); dialog.setFileMode(QFileDialog::ExistingFile); @@ -234,6 +236,7 @@ bool FrameFileIO::loadFrameFile(QString &fileName, QVector* frameCache if (selectedNameFilter == filters[21]) result = loadTeslaAPFile(filename, frameCache); if (selectedNameFilter == filters[22]) result = loadCLX000File(filename, frameCache); if (selectedNameFilter == filters[23]) result = loadCANServerFile(filename, frameCache); + if (selectedNameFilter == filters[24]) result = loadWiresharkFile(filename, frameCache); progress.cancel(); @@ -305,6 +308,16 @@ bool FrameFileIO::autoDetectLoadFile(QString filename, QVector* frames } } + qDebug() << "Attempting Wireshark Log"; + if (isWiresharkFile(filename)) + { + if (loadWiresharkFile(filename, frames)) + { + qDebug() << "Loaded as Wireshark Log successfully!"; + return true; + } + } + qDebug() << "Attempting canalyzer ASC"; if (isCanalyzerASC(filename)) { @@ -4825,3 +4838,86 @@ bool FrameFileIO::loadCANServerFile(QString filename, QVector* frames) delete inFile; return !foundErrors; } + +bool FrameFileIO::loadWiresharkFile(QString filename, QVector* frames) +{ + pcap_t *pcap_data_file; + CANFrame thisFrame; + long long startTimestamp = 0; + long long timeStamp; + int lineCounter = 0; + bool foundErrors = false; + pcap_pkthdr packetHeader; + const char *packetData = NULL; + char errbuf[PCAP_ERRBUF_SIZE]; + + thisFrame.setFrameType(QCanBusFrame::DataFrame); + + QByteArray ba = filename.toLocal8Bit(); + + pcap_data_file = pcap_open_offline(ba.data(), errbuf); + if (!pcap_data_file) { + return false; + } + + packetData = (const char*)pcap_next(pcap_data_file, &packetHeader); + + while (packetData) { + lineCounter++; + if (lineCounter > 100) + { + qApp->processEvents(); + lineCounter = 0; + } + + timeStamp = packetHeader.ts.tv_sec * 1000000 + packetHeader.ts.tv_usec; + if (0 == startTimestamp) + { + startTimestamp = timeStamp; + } + + timeStamp -= startTimestamp; + + thisFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, timeStamp)); + thisFrame.isReceived = true; // TODO: check if tx detection is possible + + thisFrame.setFrameType(QCanBusFrame::DataFrame); + thisFrame.setFrameId((0xff & *(packetData+17)) << 8 | (0xff & *(packetData+16))); + if (thisFrame.frameId() <= 0x7FF) thisFrame.setExtendedFrameFormat(false); + else thisFrame.setExtendedFrameFormat(true); + thisFrame.bus = 0; + int numBytes = *(packetData+20); + QByteArray bytes(numBytes, 0); + if (thisFrame.payload().length() > 8) thisFrame.payload().resize(8); + for (int d = 0; d < numBytes; d++) + { + bytes[d] = *(packetData + 24 + d); + } + thisFrame.setPayload(bytes); + frames->append(thisFrame); + + packetData = (const char*)pcap_next(pcap_data_file, &packetHeader); + } + + pcap_close(pcap_data_file); + pcap_data_file = NULL; + + return !foundErrors; +} + +bool FrameFileIO::isWiresharkFile(QString filename) +{ + pcap_t *pcap_data_file; + char errbuf[PCAP_ERRBUF_SIZE]; + QByteArray ba = filename.toLocal8Bit(); + + pcap_data_file = pcap_open_offline(ba.data(), errbuf); + if (!pcap_data_file) { + return false; + } + + pcap_close(pcap_data_file); + pcap_data_file = NULL; + + return true; +} diff --git a/framefileio.h b/framefileio.h index 39f4d5f..3d52517 100644 --- a/framefileio.h +++ b/framefileio.h @@ -53,6 +53,7 @@ public: static bool loadTeslaAPFile(QString filename, QVector* frames); static bool loadCLX000File(QString filename, QVector* frames); static bool loadCANServerFile(QString filename, QVector* frames); + static bool loadWiresharkFile(QString filename, QVector* frames); //functions that pre-scan a file to try to figure out if they could read it. Used to automatically determine //file type and load it. @@ -78,6 +79,7 @@ public: static bool isTeslaAPFile(QString filename); static bool isCLX000File(QString filename); static bool isCANServerFile(QString filename); + static bool isWiresharkFile(QString filename); static bool saveCRTDFile(QString, const QVector*); static bool saveNativeCSVFile(QString, const QVector*); diff --git a/pcaplite.cpp b/pcaplite.cpp new file mode 100644 index 0000000..27c72d4 --- /dev/null +++ b/pcaplite.cpp @@ -0,0 +1,214 @@ +#include +#include "pcaplite.h" + +#define MAGIC_NG 0x0A0D0D0A +#define MACIG 0xA1B2C3D4 + +#define MAX_CAN_PACKET_SIZE 256 + +// some pcap format constants +#define PCAP_FILE_HEADER_LENGTH 24 +#define PCAP_FRAME_HEADER_LENGTH 16 +#define PCAP_CAP_FRAME_LENGTH_OFFSET 8 +#define PCAP_FRAME_LENGTH_OFFSET 12 + +// some pcapng format constants +#define INTERFACE_DESCRITION_BLOCK 0x01 +#define ENCHANCED_PACKET_BLOCK 0x06 + +static unsigned char pcap_buffer[MAX_CAN_PACKET_SIZE]; +static pcap_t p; + +pcap *pcap_open_offline(const char *filename, char *error_text) { + FILE *file; + + snprintf(error_text, PCAP_ERRBUF_SIZE, "OK"); + + file = fopen(filename,"rb"); + + if (NULL == file) { + snprintf(error_text, PCAP_ERRBUF_SIZE, + "Cannot open input file"); + return NULL; + } + + unsigned int magic; + size_t bytes_read; + + bytes_read = fread(&magic, 1, sizeof(magic), file); + if (bytes_read != sizeof(magic)) { + snprintf(error_text, PCAP_ERRBUF_SIZE, "Cannot read magic word"); + fclose(file); + return (NULL); + } + + if (magic != MACIG && magic != MAGIC_NG) { + snprintf(error_text, PCAP_ERRBUF_SIZE, "Not a supported format %04x", magic); + fclose(file); + return (NULL); + } + + // set the format + // and seek past file header + if (MAGIC_NG == magic) { + p.is_ng = 1; + fseek(file, sizeof(magic), SEEK_SET); + unsigned int section_length; + bytes_read = fread(§ion_length, 1, sizeof(section_length), file); + if (bytes_read != sizeof(section_length)) { + snprintf(error_text, PCAP_ERRBUF_SIZE, "Cannot read section length"); + fclose(file); + return (NULL); + } + fseek(file, section_length, SEEK_SET); + + } else { + p.is_ng = 0; + fseek(file, PCAP_FILE_HEADER_LENGTH, SEEK_SET); + } + + p.file = file; + + return(&p); +} + +const unsigned char *pcap_next_ng(pcap_t *p, struct pcap_pkthdr *h) { + // this is for ENCHANCED_PACKET_BLOCK + struct block_header { + unsigned int block_type; + unsigned int block_size; + unsigned int interface_id; + unsigned int timestamp_hi; + unsigned int timestamp_lo; + unsigned int cap_len; + unsigned int len; + } bh; + + struct option_header { + unsigned short option_type; + unsigned short option_length; + } oh; + + size_t bytes_read; + + static double timestamp_multiplier = 0.000001; //microseconds resolution + + long fpos = ftell(p->file); + + do { + bytes_read = fread(&bh, 1, sizeof(bh), p->file); + if (bytes_read != sizeof(bh)) { + //probably EOF + return (NULL); + } + + if (bh.block_type != ENCHANCED_PACKET_BLOCK) { + if (INTERFACE_DESCRITION_BLOCK == bh.block_type) { + // Seek to start of options part in the header + fseek(p->file, fpos + 16, SEEK_SET); + do { + bytes_read = fread(&oh, 1, sizeof(oh), p->file); + if (bytes_read != sizeof(oh)) { + //probably EOF + return (NULL); + } + + // calcualte option length aligned on 4 byte boundary + unsigned int option_file_length = 4*(oh.option_length/4) + ((oh.option_length%4) ? 4:0); + + + // read in the buffer if it's large enough or skip + if (option_file_length <= sizeof(pcap_buffer)) { + bytes_read = fread(pcap_buffer, 1, option_file_length, p->file); + if (bytes_read != option_file_length) { + //probably EOF + return (NULL); + } + //check if time resolution option + if (0x09 == oh.option_type) { + unsigned int res = *(unsigned int*)pcap_buffer; + if ((0x80000000 & res) == 0) { + timestamp_multiplier = 1/pow(10, res); + } else { + timestamp_multiplier = 1/pow(2, (res & 0x7fffffff)); + } + } + } else if (fseek(p->file, option_file_length, SEEK_CUR)) { + //probably EOF + return (NULL); + } + + } while(ftell(p->file) < fpos + bh.block_size - sizeof(bh.block_size)); + + if (fseek(p->file, sizeof(bh.block_size), SEEK_CUR)) { + //probably EOF + return (NULL); + } + } + + // skip to the next block + if (fseek(p->file, fpos + bh.block_size, SEEK_SET)) { + //probably EOF + return (NULL); + } + fpos = ftell(p->file); + } + } while (bh.block_type != ENCHANCED_PACKET_BLOCK); + + h->caplen = bh.cap_len; + + double timestamp = ((unsigned long long)bh.timestamp_hi << 32 | bh.timestamp_lo) * timestamp_multiplier; + double fractional, integer; + + fractional = modf(timestamp, &integer); + h->ts.tv_sec = (long)integer; + h->ts.tv_usec = (long)(fractional * 1000000); + + bytes_read = fread(pcap_buffer, 1, h->caplen, p->file); + if (bytes_read != h->caplen) { + //probably EOF + return (NULL); + } + + //seek to the next block (go back with bytes read of the block and seek fwd with the block size) + if (fseek(p->file, fpos + bh.block_size, SEEK_SET)) { + //probably EOF + return (NULL); + } + + return pcap_buffer; +} + + +const unsigned char *pcap_next(pcap_t *p, struct pcap_pkthdr *h) +{ + if (p->is_ng) { + return pcap_next_ng(p, h); + } + + size_t bytes_read; + + bytes_read = fread(pcap_buffer, 1, PCAP_FRAME_HEADER_LENGTH, p->file); + if (bytes_read != PCAP_FRAME_HEADER_LENGTH) { + //probably EOF + return (NULL); + } + + h->ts.tv_sec = *(unsigned int*)(pcap_buffer); + h->ts.tv_usec = *(unsigned int*)(pcap_buffer + 4); + + h->caplen = *(unsigned int*)(pcap_buffer + PCAP_CAP_FRAME_LENGTH_OFFSET); + h->len = *(unsigned int*)(pcap_buffer + PCAP_FRAME_LENGTH_OFFSET); + + bytes_read = fread(pcap_buffer, 1, h->caplen, p->file); + if (bytes_read != h->caplen) { + //probably EOF + return (NULL); + } + + return pcap_buffer; +} + +void pcap_close(pcap_t *p) { + fclose(p->file); +} diff --git a/pcaplite.h b/pcaplite.h new file mode 100644 index 0000000..8a48153 --- /dev/null +++ b/pcaplite.h @@ -0,0 +1,32 @@ +#ifndef PCAPLITE_H +#define PCAPLITE_H + +#include +#if defined(unix) || defined __APPLE__ +#include +#else +#include +#endif + +#define PCAP_ERRBUF_SIZE 256 + +struct pcap_pkthdr { + struct timeval ts; /* time stamp */ + unsigned int caplen; /* length of portion present */ + unsigned int len; /* length of this packet (off wire) */ +}; + +struct pcap { + FILE *file; + bool is_ng; +}; + +typedef struct pcap pcap_t; + +pcap *pcap_open_offline(const char *, char *); + +const unsigned char *pcap_next(pcap_t *, struct pcap_pkthdr *); + +void pcap_close(pcap_t *); + +#endif// PCAPLITE_H