1c7fc39b6c
in almost all methods removed timeouts in milliseconds, replaced to PISystemTime PITimer rewrite, remove internal impl, now only thread implementation, API similar to PIThread PITimer API no longer pass void* PIPeer, PIConnection improved stability on reinit and exit PISystemTime new methods pisd now exit without hanging
330 lines
10 KiB
C++
330 lines
10 KiB
C++
/*
|
|
PIP - Platform Independent Primitives
|
|
Packets extractor
|
|
Ivan Pelipenko peri4ko@yandex.ru, Andrey Bychkov work.a.b@yandex.ru
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "pipacketextractor.h"
|
|
|
|
#include "piliterals.h"
|
|
|
|
|
|
/** \class PIPacketExtractor
|
|
* \brief Packets extractor
|
|
* \details
|
|
* \section PIPacketExtractor_main Synopsis
|
|
* This class implements packet recognition by various algorithms and custom
|
|
* validating from data stream. Stream is formed from child %PIIODevice
|
|
* passed from contructor or with function \a setDevice().
|
|
*
|
|
* \section PIPacketExtractor_work Principle of work
|
|
* %PIPacketExtractor works with child %PIIODevice. \a read and \a write
|
|
* functions directly call child device functions. You should start threaded
|
|
* read of \b extractor (not child device) to proper work. Extractor read data
|
|
* from child device, try to detect packet from readed data and raise
|
|
* \a packetReceived() event on success.
|
|
*
|
|
* \section PIPacketExtractor_algorithms Algorithms
|
|
* There are 6 algorithms: \n
|
|
* * PIPacketExtractor::None \n
|
|
* Packet is successfully received on every read without any validation. \n \n
|
|
* * PIPacketExtractor::Header \n
|
|
* Wait for at least \a header() bytes + \a payloadSize(), then validate
|
|
* header with virtual function \a validateHeader() and if it fail, shifts
|
|
* for next 1 byte. If header is successfully validated check payload with
|
|
* function \a validatePayload() and if it fail, shifts for next 1 byte. If
|
|
* all validations were successful raise \a packetReceived() event. \n \n
|
|
* * PIPacketExtractor::Footer \n
|
|
* This algorithm similar to previous, but instead of \a header() first validate
|
|
* \a footer() at after \a payloadSize() bytes with function \a validateFooter(). \n \n
|
|
* * PIPacketExtractor::HeaderAndFooter \n
|
|
* Wait for at least \a header() bytes + \a footer() bytes, then validate
|
|
* header with virtual function \a validateHeader() and if it fail, shifts
|
|
* for next 1 byte. If header is successfully validated check footer with
|
|
* function \a validateFooter() and if it fail, shifts footer position for
|
|
* next 1 byte. Then validate payload and if it fail, search header again,
|
|
* starts from next byte of previous header. If all validations were successful
|
|
* raise \a packetReceived() event. \n \n
|
|
* * PIPacketExtractor::Size \n
|
|
* Wait for at least \a packetSize() bytes, then validate packet with function
|
|
* \a validatePayload() and if it fail, shifts for next 1 byte. If validating
|
|
* was successfull raise \a packetReceived() event. \n \n
|
|
* * PIPacketExtractor::Timeout \n
|
|
* Wait for first read, then read for \a timeout() milliseconds and raise
|
|
* \a packetReceived() event. \n
|
|
*
|
|
* \section PIPacketExtractor_control Control validating
|
|
* There are three parameters:
|
|
* * header content
|
|
* * header size
|
|
* * payload size
|
|
*
|
|
* Extractor can detect packet with compare your header with readed data.
|
|
* It is default implementation of function \a packetHeaderValidate().
|
|
* If header validating passed, function \a packetValidate() will be called.
|
|
* If either of this function return \b false extractor shifts by one byte
|
|
* and takes next header. If both functions returns \b true extractor shifts
|
|
* by whole packet size.
|
|
* \image html packet_detection.png
|
|
*
|
|
* */
|
|
|
|
REGISTER_DEVICE(PIPacketExtractor)
|
|
|
|
|
|
PIPacketExtractor::PIPacketExtractor(PIIODevice * device_, PIPacketExtractor::SplitMode mode) {
|
|
construct();
|
|
setDevice(device_);
|
|
setSplitMode(mode);
|
|
}
|
|
|
|
|
|
void PIPacketExtractor::construct() {
|
|
func_header = nullptr;
|
|
func_footer = nullptr;
|
|
func_payload = nullptr;
|
|
setPayloadSize(0);
|
|
setTimeout(100_ms);
|
|
#ifdef MICRO_PIP
|
|
setThreadedReadBufferSize(512);
|
|
#else
|
|
setThreadedReadBufferSize(64_KiB);
|
|
#endif
|
|
setDevice(nullptr);
|
|
setSplitMode(None);
|
|
missed = footerInd = 0;
|
|
header_found = false;
|
|
}
|
|
|
|
|
|
void PIPacketExtractor::propertyChanged(const char *) {
|
|
mode_ = (SplitMode)(property("splitMode").toInt());
|
|
dataSize = property("payloadSize").toInt();
|
|
src_header = property("header").toByteArray();
|
|
src_footer = property("footer").toByteArray();
|
|
time_ = property("timeout").toSystemTime();
|
|
}
|
|
|
|
|
|
ssize_t PIPacketExtractor::readDevice(void * read_to, ssize_t max_size) {
|
|
if (dev) return dev->read(read_to, max_size);
|
|
return -1;
|
|
}
|
|
|
|
|
|
ssize_t PIPacketExtractor::writeDevice(const void * data, ssize_t max_size) {
|
|
if (dev) return dev->write(data, max_size);
|
|
return -1;
|
|
}
|
|
|
|
|
|
void PIPacketExtractor::setDevice(PIIODevice * device_) {
|
|
dev = device_;
|
|
}
|
|
|
|
|
|
ssize_t PIPacketExtractor::bytesAvailable() const {
|
|
if (dev)
|
|
return dev->bytesAvailable();
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
|
|
void PIPacketExtractor::setPayloadSize(int size) {
|
|
setProperty("payloadSize", size);
|
|
dataSize = size;
|
|
}
|
|
|
|
|
|
void PIPacketExtractor::setHeader(const PIByteArray & data) {
|
|
setProperty("header", data);
|
|
src_header = data;
|
|
}
|
|
|
|
|
|
void PIPacketExtractor::setFooter(const PIByteArray & data) {
|
|
setProperty("footer", data);
|
|
src_footer = data;
|
|
}
|
|
|
|
|
|
int PIPacketExtractor::validateHeader(const uchar * src, const uchar * rec, int size) {
|
|
if (func_header) return func_header(src, rec, size);
|
|
for (int i = 0; i < size; ++i) {
|
|
if (src[i] != rec[i]) return -1;
|
|
}
|
|
return dataSize;
|
|
}
|
|
|
|
|
|
bool PIPacketExtractor::validateFooter(const uchar * src, const uchar * rec, int size) {
|
|
if (func_footer) return func_footer(src, rec, size);
|
|
for (int i = 0; i < size; ++i) {
|
|
if (src[i] != rec[i]) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
bool PIPacketExtractor::validatePayload(const uchar * rec, int size) {
|
|
if (func_payload) return func_payload(rec, size);
|
|
return true;
|
|
}
|
|
|
|
|
|
bool PIPacketExtractor::threadedRead(const uchar * readed, ssize_t size_) {
|
|
// piCoutObj << "readed" << size_;
|
|
int ss;
|
|
tmpbuf.append(readed, size_);
|
|
switch (mode_) {
|
|
case PIPacketExtractor::None:
|
|
tmpbuf.clear();
|
|
if (validatePayload(readed, size_)) {
|
|
packetReceived(readed, size_);
|
|
} else {
|
|
missed += size_;
|
|
}
|
|
break;
|
|
case PIPacketExtractor::Header:
|
|
if (src_header.isEmpty()) return PIIODevice::threadedRead(readed, size_);
|
|
while (tmpbuf.size() >= src_header.size()) {
|
|
if (!header_found) {
|
|
packetSize_pending = validateHeader(src_header.data(), tmpbuf.data(), src_header.size_s());
|
|
while (packetSize_pending < 0) {
|
|
tmpbuf.pop_front();
|
|
++missed;
|
|
if (tmpbuf.size() < src_header.size()) return PIIODevice::threadedRead(readed, size_);
|
|
packetSize_pending = validateHeader(src_header.data(), tmpbuf.data(), src_header.size_s());
|
|
}
|
|
header_found = true;
|
|
}
|
|
if (tmpbuf.size_s() < src_header.size_s() + packetSize_pending) return PIIODevice::threadedRead(readed, size_);
|
|
if (!validatePayload(tmpbuf.data(src_header.size_s()), packetSize_pending)) {
|
|
tmpbuf.pop_front();
|
|
++missed;
|
|
header_found = false;
|
|
continue;
|
|
}
|
|
packetReceived(tmpbuf.data(), src_header.size_s() + packetSize_pending);
|
|
tmpbuf.remove(0, src_header.size_s() + packetSize_pending);
|
|
header_found = false;
|
|
}
|
|
break;
|
|
case PIPacketExtractor::Footer:
|
|
if (src_footer.isEmpty()) return PIIODevice::threadedRead(readed, size_);
|
|
ss = src_footer.size_s() + dataSize;
|
|
while (tmpbuf.size_s() >= ss) {
|
|
while (!validateFooter(src_footer.data(), tmpbuf.data(dataSize), src_footer.size_s())) {
|
|
tmpbuf.pop_front();
|
|
++missed;
|
|
if (tmpbuf.size_s() < ss) return PIIODevice::threadedRead(readed, size_);
|
|
}
|
|
if (!validatePayload(tmpbuf.data(), dataSize)) {
|
|
tmpbuf.pop_front();
|
|
++missed;
|
|
continue;
|
|
}
|
|
packetReceived(tmpbuf.data(), ss);
|
|
tmpbuf.remove(0, ss);
|
|
}
|
|
break;
|
|
case PIPacketExtractor::HeaderAndFooter:
|
|
ss = src_header.size_s() + src_footer.size_s();
|
|
while (tmpbuf.size_s() >= ss && ss > 0) {
|
|
if (!header_found) {
|
|
if (tmpbuf.size_s() < ss) return PIIODevice::threadedRead(readed, size_);
|
|
while (validateHeader(src_header.data(), tmpbuf.data(), src_header.size_s()) < 0) {
|
|
tmpbuf.pop_front();
|
|
++missed;
|
|
if (tmpbuf.size_s() < ss) return PIIODevice::threadedRead(readed, size_);
|
|
}
|
|
header_found = true;
|
|
footerInd = src_header.size_s();
|
|
} else {
|
|
if (tmpbuf.size_s() < footerInd + src_footer.size_s()) return PIIODevice::threadedRead(readed, size_);
|
|
while (!validateFooter(src_footer.data(), tmpbuf.data(footerInd), src_footer.size_s())) {
|
|
++footerInd;
|
|
if (tmpbuf.size_s() < footerInd + src_footer.size_s()) return PIIODevice::threadedRead(readed, size_);
|
|
}
|
|
// piCout << "footer found at" << footerInd;
|
|
header_found = false;
|
|
if (!validatePayload(tmpbuf.data(src_header.size_s()), footerInd - src_header.size_s())) {
|
|
tmpbuf.pop_front();
|
|
++missed;
|
|
continue;
|
|
}
|
|
packetReceived(tmpbuf.data(), footerInd + src_footer.size_s());
|
|
tmpbuf.remove(0, footerInd + src_footer.size_s());
|
|
footerInd = src_header.size_s();
|
|
}
|
|
}
|
|
break;
|
|
case PIPacketExtractor::Size:
|
|
if (dataSize <= 0) {
|
|
tmpbuf.clear();
|
|
return PIIODevice::threadedRead(readed, size_);
|
|
}
|
|
while (tmpbuf.size_s() >= dataSize) {
|
|
if (!validatePayload(tmpbuf.data(), dataSize)) {
|
|
tmpbuf.pop_front();
|
|
++missed;
|
|
continue;
|
|
}
|
|
packetReceived(tmpbuf.data(), dataSize);
|
|
tmpbuf.remove(0, dataSize);
|
|
}
|
|
break;
|
|
case PIPacketExtractor::Timeout:
|
|
tmpbuf.append(dev->readForTime(time_));
|
|
if (tmpbuf.size() > 0) {
|
|
packetReceived(tmpbuf.data(), tmpbuf.size());
|
|
tmpbuf.clear();
|
|
}
|
|
break;
|
|
};
|
|
return PIIODevice::threadedRead(readed, size_);
|
|
}
|
|
|
|
|
|
PIString PIPacketExtractor::constructFullPathDevice() const {
|
|
return "";
|
|
}
|
|
|
|
|
|
bool PIPacketExtractor::openDevice() {
|
|
if (dev) {
|
|
if (!dev->isOpened()) return dev->open();
|
|
return dev->isOpened();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
bool PIPacketExtractor::closeDevice() {
|
|
if (dev) {
|
|
if (dev->isOpened()) return dev->close();
|
|
return !dev->isOpened();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
PIIODevice::DeviceInfoFlags PIPacketExtractor::deviceInfoFlags() const {
|
|
if (dev) return dev->infoFlags();
|
|
return 0;
|
|
}
|