From 9ad83fb2c64caf43b3f565cc79def6d43d30a5c1 Mon Sep 17 00:00:00 2001 From: Manuel Nickschas Date: Sun, 16 Feb 2014 22:22:52 +0100 Subject: [PATCH 1/1] Introduce basic (not-yet-compressing) implementation of Compressor This class is intended to encapsulate the streaming compression between the socket and the rest of RemotePeer; however, actual compression is not yet implemented. The Compressor class sits on top of the socket, providing a rudimentary API for reading and writing data, and will transparently handle the (de)compression once it's finished. --- src/common/CMakeLists.txt | 2 + src/common/compressor.cpp | 118 ++++++++++++++++++++++++++++++++++++++ src/common/compressor.h | 80 ++++++++++++++++++++++++++ 3 files changed, 200 insertions(+) create mode 100644 src/common/compressor.cpp create mode 100644 src/common/compressor.h diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 9eeb91ce..f1dc2f93 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -12,6 +12,7 @@ set(SOURCES bufferviewconfig.cpp bufferviewmanager.cpp cliparser.cpp + compressor.cpp coreinfo.cpp ctcpevent.cpp event.cpp @@ -52,6 +53,7 @@ set(MOC_HDRS buffersyncer.h bufferviewconfig.h bufferviewmanager.h + compressor.h coreinfo.h eventmanager.h identity.h diff --git a/src/common/compressor.cpp b/src/common/compressor.cpp new file mode 100644 index 00000000..9ee877c2 --- /dev/null +++ b/src/common/compressor.cpp @@ -0,0 +1,118 @@ +/*************************************************************************** + * Copyright (C) 2005-2014 by the Quassel Project * + * devel@quassel-irc.org * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) version 3. * + * * + * 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "compressor.h" + +#include +#include + +const qint64 maxBufferSize = 64 * 1024 * 1024; // protect us from zip bombs + +Compressor::Compressor(QTcpSocket *socket, Compressor::CompressionLevel level, QObject *parent) + : QObject(parent), + _socket(socket), + _level(level) +{ + _level = NoCompression; // compression not implemented yet + + connect(socket, SIGNAL(readyRead()), SLOT(readData())); + + // It's possible that more data has already arrived during the handshake, so readyRead() wouldn't be triggered. + // However, we want to give RemotePeer a chance to connect to our signals, so trigger this asynchronously. + if (socket->bytesAvailable()) + QTimer::singleShot(0, this, SLOT(readData())); +} + + +qint64 Compressor::bytesAvailable() const +{ + return _readBuffer.size(); +} + + +qint64 Compressor::read(char *data, qint64 maxSize) +{ + if (maxSize <= 0) + maxSize = _readBuffer.size(); + + qint64 n = qMin(maxSize, (qint64)_readBuffer.size()); + memcpy(data, _readBuffer.constData(), n); + + // TODO: don't copy for every read + if (n == _readBuffer.size()) + _readBuffer.clear(); + else + _readBuffer = _readBuffer.mid(n); + + // If there's still data left in the socket buffer, make sure to schedule a read + if (_socket->bytesAvailable()) + QTimer::singleShot(0, this, SLOT(readData())); + + return n; +} + + +// The usual usage pattern is to write a blocksize first, followed by the actual data. +// By setting NoFlush, one can indicate that the write buffer should not immediately be +// written, which should make things a bit more efficient. +qint64 Compressor::write(const char *data, qint64 count, WriteBufferHint flush) +{ + int pos = _writeBuffer.size(); + _writeBuffer.resize(pos + count); + memcpy(_writeBuffer.data() + pos, data, count); + + if (flush != NoFlush) + writeData(); + + return count; +} + + +void Compressor::readData() +{ + // don't try to read more data if we're already closing + if (_socket->state() != QAbstractSocket::ConnectedState) + return; + + if (!_socket->bytesAvailable() || _readBuffer.size() >= maxBufferSize) + return; + + if (compressionLevel() == NoCompression) + _readBuffer.append(_socket->read(maxBufferSize - _readBuffer.size())); + + emit readyRead(); +} + + +void Compressor::writeData() +{ + if (compressionLevel() == NoCompression) { + _socket->write(_writeBuffer); + _writeBuffer.clear(); + } +} + + +void Compressor::flush() +{ + if (compressionLevel() == NoCompression && _socket->state() == QAbstractSocket::ConnectedState) + _socket->flush(); + +} diff --git a/src/common/compressor.h b/src/common/compressor.h new file mode 100644 index 00000000..b8b9e718 --- /dev/null +++ b/src/common/compressor.h @@ -0,0 +1,80 @@ +/*************************************************************************** + * Copyright (C) 2005-2014 by the Quassel Project * + * devel@quassel-irc.org * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) version 3. * + * * + * 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef COMPRESSOR_H +#define COMPRESSOR_H + +#include + +class QTcpSocket; + +class Compressor : public QObject +{ + Q_OBJECT + +public: + enum CompressionLevel { + NoCompression, + DefaultCompression, + BestCompression, + BestSpeed + }; + + enum Error { + NoError, + StreamError, + DeviceError + }; + + enum WriteBufferHint { + NoFlush, + Flush + }; + + Compressor(QTcpSocket *socket, CompressionLevel level, QObject *parent = 0); + + CompressionLevel compressionLevel() const { return _level; } + + qint64 bytesAvailable() const; + + qint64 read(char *data, qint64 maxSize); + qint64 write(const char *data, qint64 count, WriteBufferHint flush = Flush); + + void flush(); + +signals: + void readyRead(); + void error(Compressor::Error errorCode); + +private slots: + void readData(); + +private: + void writeData(); + +private: + QTcpSocket *_socket; + CompressionLevel _level; + + QByteArray _readBuffer; + QByteArray _writeBuffer; +}; + +#endif -- 2.20.1