Jump to content

Custom IO Device

From Qt Wiki
Revision as of 09:51, 24 February 2015 by Maintenance script (talk | contribs)


English | German

[toc align_right="yes" depth="2"]

Writing a Custom I/O Device

This is a port of the article in "Qt Quarterly 12 about writing a custom QIODevice":http://doc.qt.nokia.com/qq/qq12-iodevice.html

Usage:

The following code snippet shows how we would use the custom I/O device to encrypt data and store the result in a file:


QFile file("output.dat");
CryptDevice cryptDevice(&file)
QTextStream out(&cryptDevice);
cryptDevice.open(QIODevice::WriteOnly);
out << "Hello World&quot;;

And on the possible usage (in our example code in git "qtdevnet-wiki-mvc/qtdevnet-custom-iodevice&quot;:http://www.gitorious.org/qtdevnet-wiki-mvc/qtdevnet-custom-iodevice)

&#32;

Encryption

<br /> QByteArray dataArray;

QBuffer bufferUsedLikeAFile&amp;amp;#40;&amp;dataArray&amp;amp;#41;;<br /> CryptDevice deviceFilter(&amp;bufferUsedLikeAFile);<br /> deviceFilter.open(QIODevice::WriteOnly);<br /> QTextStream stream(&amp;deviceFilter);<br /> QString szText = rawText-&gt;toPlainText();<br /> stream &lt;&lt; szText;<br />

&#32;

Decryption


QBuffer bufferUsedLikeAFile&amp;#40;&dataArray&amp;#41;;
CryptDevice deviceFilter(&bufferUsedLikeAFile);
deviceFilter.open(QIODevice::ReadOnly);
QTextStream stream(&deviceFilter);
QString szText = stream.readAll();
decryptedText->setPlainText(szText);

Example image of the test app:

Test app

The Custom I/O Device

Writing a custom QIODevice class in Qt 4 involves inheriting QIODevice and then reimplementing a set of virtual functions.

There is a big difference regarding writing a custom IO device compared to Qt 3: you only have to rewrite 2 functions:

  • qint64 QIODevice::readData ( char * data, qint64 maxSize )
  • qint64 QIODevice::writeData ( const char * data, qint64 maxSize )

Our CryptDevice class will be a sequential I/O device. Whether it's synchronous or asynchronous depends on the underlying QIODevice.

Source Code

The class definition looks like this:


class CryptDevice : public QIODevice
{
Q_OBJECT
public:
CryptDevice(QIODevice* deviceToUse, QObject* parent = 0);
bool open(OpenMode mode);
void close();
bool isSequential() const;
protected:
qint64 readData(char* data, qint64 maxSize);
qint64 writeData(const char* data, qint64 maxSize);
private:
QIODevice* underlyingDevice;
Q_DISABLE_COPY(CryptDevice)
};

The constructor definition is pretty straightforward


CryptDevice::CryptDevice(QIODevice* deviceToUse, QObject* parent) :
QIODevice(parent),
underlyingDevice(deviceToUse)
{
}

As our device should be sequential, we re-implement isSequential

bool CryptDevice::isSequential() const
{
return true;
}

In <code&gt;open()</code&gt;, we open the underlying device if it's not already open and set the device state to mode.

<br />bool CryptDevice::open(OpenMode mode)<br />{<br /> bool underlyingOk;<br /> if (underlyingDevice-&gt;isOpen())<br /> underlyingOk = (underlyingDevice-&gt;openMode() != mode);<br /> else<br /> underlyingOk = underlyingDevice-&gt;open(mode);

if (underlyingOk)<br /> {<br /> setOpenMode(mode);<br /> return true;<br /> }<br /> return false;<br />}<br />

Closing is trivial.


void CryptDevice::close()
{
underlyingDevice->close();
setOpenMode(NotOpen);
}

When reading a block, we call <code&gt;read()</code&gt; on the underlying device. At the end, we XOR each byte read from the device with the magic constant 0x5E.

<br />qint64 CryptDevice::readData(char* data, qint64 maxSize)<br />{<br /> qint64 deviceRead = underlyingDevice-&gt;read(data, maxSize);<br /> if (deviceRead == 1)<br /> return <s>1;<br /> for (qint64 i = 0; i &lt; deviceRead; +''i)<br /> data[i] = data[i] ^ 0x5E;
<br /> return deviceRead;<br />}<br />


When writing a block, we create a temporary buffer with the XOR'd data. A more efficient implementation would use a 4096-byte buffer on the stack and call <code&gt;write()</code&gt; multiple times if size is larger than the buffer.

qint64 CryptDevice::writeData(const char* data, qint64 maxSize)
{
QByteArray buffer((int)maxSize, 0);
for (int i = 0; i < (int)maxSize;+i)
buffer[i] = data[i] ^ 0x5E;
return underlyingDevice>write(buffer.data(), maxSize);
}