From 5da7d3fca6647c8a67c5d6a1d057ca0367bf9d4b Mon Sep 17 00:00:00 2001 From: Felix Geyer Date: Sat, 18 Sep 2010 17:19:42 +0200 Subject: [PATCH] Implement writing in SymmetricCipherStream and add a unit test. --- src/core/Endian.cpp | 66 +++++++++++++++++++ src/core/Endian.h | 8 +++ src/streams/HashedBlockStream.cpp | 104 ++++++++++++++++++++++++++++-- src/streams/HashedBlockStream.h | 6 +- tests/CMakeLists.txt | 3 + tests/TestHashedBlockStream.cpp | 74 +++++++++++++++++++++ 6 files changed, 255 insertions(+), 6 deletions(-) create mode 100644 tests/TestHashedBlockStream.cpp diff --git a/src/core/Endian.cpp b/src/core/Endian.cpp index 8fca50f1..177c3a7f 100644 --- a/src/core/Endian.cpp +++ b/src/core/Endian.cpp @@ -131,4 +131,70 @@ quint64 readUInt64(QIODevice* device, QSysInfo::Endian byteOrder, bool* ok) return readInt64(device, byteOrder, ok); } +QByteArray int16ToBytes(qint16 num, QSysInfo::Endian byteOrder) +{ + QByteArray ba; + ba.resize(2); + + if (byteOrder == QSysInfo::LittleEndian) { + qToLittleEndian(num, reinterpret_cast(ba.data())); + } + else { + qToBigEndian(num, reinterpret_cast(ba.data())); + } + + return ba; +} + +QByteArray int32ToBytes(qint32 num, QSysInfo::Endian byteOrder) +{ + QByteArray ba; + ba.resize(4); + + if (byteOrder == QSysInfo::LittleEndian) { + qToLittleEndian(num, reinterpret_cast(ba.data())); + } + else { + qToBigEndian(num, reinterpret_cast(ba.data())); + } + + return ba; +} + +QByteArray int64ToBytes(qint64 num, QSysInfo::Endian byteOrder) +{ + QByteArray ba; + ba.resize(8); + + if (byteOrder == QSysInfo::LittleEndian) { + qToLittleEndian(num, reinterpret_cast(ba.data())); + } + else { + qToBigEndian(num, reinterpret_cast(ba.data())); + } + + return ba; +} + +bool writeInt16(qint16 num, QIODevice* device, QSysInfo::Endian byteOrder) +{ + QByteArray ba = int16ToBytes(num, byteOrder); + int bytesWritten = device->write(ba); + return (bytesWritten == ba.size()); +} + +bool writeInt32(qint32 num, QIODevice* device, QSysInfo::Endian byteOrder) +{ + QByteArray ba = int32ToBytes(num, byteOrder); + int bytesWritten = device->write(ba); + return (bytesWritten == ba.size()); +} + +bool writeInt64(qint64 num, QIODevice* device, QSysInfo::Endian byteOrder) +{ + QByteArray ba = int64ToBytes(num, byteOrder); + int bytesWritten = device->write(ba); + return (bytesWritten == ba.size()); +} + } // namespace Endian diff --git a/src/core/Endian.h b/src/core/Endian.h index 8958cc40..023df646 100644 --- a/src/core/Endian.h +++ b/src/core/Endian.h @@ -38,6 +38,14 @@ namespace Endian quint32 readUInt32(QIODevice* device, QSysInfo::Endian byteOrder, bool* ok); qint64 readInt64(QIODevice* device, QSysInfo::Endian byteOrder, bool* ok); quint64 readUInt64(QIODevice* device, QSysInfo::Endian byteOrder, bool* ok); + + QByteArray int16ToBytes(qint16 num, QSysInfo::Endian byteOrder); + QByteArray int32ToBytes(qint32 num, QSysInfo::Endian byteOrder); + QByteArray int64ToBytes(qint64 num, QSysInfo::Endian byteOrder); + + bool writeInt16(qint16 num, QIODevice* device, QSysInfo::Endian byteOrder); + bool writeInt32(qint32 num, QIODevice* device, QSysInfo::Endian byteOrder); + bool writeInt64(qint64 num, QIODevice* device, QSysInfo::Endian byteOrder); }; #endif // KEEPASSX_ENDIAN_H diff --git a/src/streams/HashedBlockStream.cpp b/src/streams/HashedBlockStream.cpp index a63dba2c..c758fc4e 100644 --- a/src/streams/HashedBlockStream.cpp +++ b/src/streams/HashedBlockStream.cpp @@ -19,6 +19,7 @@ #include +#include "core/Endian.h" #include "crypto/CryptoHash.h" const QSysInfo::Endian HashedBlockStream::BYTEORDER = QSysInfo::LittleEndian; @@ -42,10 +43,41 @@ void HashedBlockStream::init() m_bufferPos = 0; m_blockIndex = 0; m_eof = false; + m_error = false; +} + +bool HashedBlockStream::reset() +{ + if (isWritable()) { + if (!m_buffer.isEmpty()) { + if (!writeHashedBlock()) { + return false; + } + } + + // write empty final block + if (!writeHashedBlock()) { + return false; + } + } + + init(); + m_buffer.clear(); + + return true; } void HashedBlockStream::close() { + if (isWritable()) { + if (!m_buffer.isEmpty()) { + writeHashedBlock(); + } + + // write empty final block + writeHashedBlock(); + } + LayeredStream::close(); } @@ -134,12 +166,76 @@ bool HashedBlockStream::readHashedBlock() qint64 HashedBlockStream::writeData(const char* data, qint64 maxSize) { - // TODO implement - return 0; + Q_ASSERT(maxSize >= 0); + + if (m_error) { + return 0; + } + + qint64 bytesRemaining = maxSize; + qint64 offset = 0; + + while (bytesRemaining > 0) { + int bytesToCopy = qMin(bytesRemaining, static_cast(m_blockSize - m_buffer.size())); + + m_buffer.append(data + offset, bytesToCopy); + + offset += bytesToCopy; + bytesRemaining -= bytesToCopy; + + if (m_buffer.size() == m_blockSize) { + if (!writeHashedBlock()) { + if (m_error) { + return -1; + } + else { + return maxSize - bytesRemaining; + } + } + } + } + + return maxSize; } bool HashedBlockStream::writeHashedBlock() { - // TODO implement - return false; + if (!Endian::writeInt32(m_blockIndex, m_baseDevice, BYTEORDER)) { + // TODO error + Q_ASSERT(false); + return false; + } + m_blockIndex++; + + QByteArray hash; + if (!m_buffer.isEmpty()) { + hash = CryptoHash::hash(m_buffer, CryptoHash::Sha256); + } + else { + hash.fill(0, 32); + } + + if (m_baseDevice->write(hash) != hash.size()) { + // TODO error + Q_ASSERT(false); + return false; + } + + if (!Endian::writeInt32(m_buffer.size(), m_baseDevice, BYTEORDER)) { + // TODO error + Q_ASSERT(false); + return false; + } + + if (!m_buffer.isEmpty()) { + if (m_baseDevice->write(m_buffer) != m_buffer.size()) { + // TODO error + Q_ASSERT(false); + return false; + } + + m_buffer.clear(); + } + + return true; } diff --git a/src/streams/HashedBlockStream.h b/src/streams/HashedBlockStream.h index 62697487..3aa23bf2 100644 --- a/src/streams/HashedBlockStream.h +++ b/src/streams/HashedBlockStream.h @@ -18,9 +18,9 @@ #ifndef KEEPASSX_HASHEDBLOCKSTREAM_H #define KEEPASSX_HASHEDBLOCKSTREAM_H +#include + #include "LayeredStream.h" -#include "core/Endian.h" -#include "crypto/CryptoHash.h" class HashedBlockStream : public LayeredStream { @@ -30,6 +30,7 @@ public: explicit HashedBlockStream(QIODevice* baseDevice); HashedBlockStream(QIODevice* baseDevice, qint32 blockSize); + bool reset(); void close(); protected: @@ -47,6 +48,7 @@ private: int m_bufferPos; quint32 m_blockIndex; bool m_eof; + bool m_error; }; #endif // KEEPASSX_HASHEDBLOCKSTREAM_H diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 04fe54b4..cbcdaf38 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -82,3 +82,6 @@ target_link_libraries( testcryptohash keepassx_core ${QT_QTCORE_LIBRARY} ${QT_QT add_unit_test( testsymmetriccipher TestSymmetricCipher.cpp ) target_link_libraries( testsymmetriccipher keepassx_core ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} ${QT_QTTEST_LIBRARY} ${LIBGCRYPT_LIBS} ) + +add_unit_test( testhashedblockstream TestHashedBlockStream.cpp ) +target_link_libraries( testhashedblockstream keepassx_core ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY} ${QT_QTTEST_LIBRARY} ${LIBGCRYPT_LIBS} ) diff --git a/tests/TestHashedBlockStream.cpp b/tests/TestHashedBlockStream.cpp new file mode 100644 index 00000000..662578ac --- /dev/null +++ b/tests/TestHashedBlockStream.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2010 Felix Geyer + * + * 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 or (at your option) + * version 3 of the License. + * + * 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, see . + */ + +#include +#include + +#include "streams/HashedBlockStream.h" + +class TestHashedBlockStream : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testWriteRead(); +}; + +void TestHashedBlockStream::testWriteRead() +{ + QByteArray data = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"); + + QBuffer buffer; + buffer.open(QIODevice::ReadWrite); + + HashedBlockStream writer(&buffer, 16); + writer.open(QIODevice::WriteOnly); + + HashedBlockStream reader(&buffer); + reader.open(QIODevice::ReadOnly); + + writer.write(data.left(16)); + QVERIFY(writer.reset()); + buffer.reset(); + QCOMPARE(reader.read(17), data.left(16)); + QVERIFY(reader.reset()); + buffer.reset(); + buffer.buffer().clear(); + + writer.write(data.left(10)); + QVERIFY(writer.reset()); + buffer.reset(); + QCOMPARE(reader.read(5), data.left(5)); + QCOMPARE(reader.read(5), data.mid(5, 5)); + QCOMPARE(reader.read(1).size(), 0); + QVERIFY(reader.reset()); + buffer.reset(); + buffer.buffer().clear(); + + writer.write(data.left(20)); + QVERIFY(writer.reset()); + buffer.reset(); + QCOMPARE(reader.read(20), data.left(20)); + QCOMPARE(reader.read(1).size(), 0); + QVERIFY(reader.reset()); + buffer.reset(); + buffer.buffer().clear(); +} + +QTEST_MAIN(TestHashedBlockStream); + +#include "TestHashedBlockStream.moc"