721 lines
14 KiB
C++
721 lines
14 KiB
C++
/***************************************************************************
|
|
* Copyright (C) 2009 by Valerio Pilo *
|
|
* valerio@kmess.org *
|
|
* *
|
|
* Copyright (C) 2009 by Adam Goossens *
|
|
* adam@kmess.org *
|
|
* *
|
|
* 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 2.1 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 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, write to the *
|
|
* Free Software Foundation, Inc., *
|
|
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
|
***************************************************************************/
|
|
|
|
#include "datasource.h"
|
|
|
|
#include "isfqt-internal.h"
|
|
|
|
|
|
using namespace Isf;
|
|
using namespace Isf::Compress;
|
|
|
|
|
|
|
|
/**
|
|
* Empty constructor.
|
|
*/
|
|
DataSource::DataSource()
|
|
: currentBitIndex_( 8 ) // Invalid position in the current byte
|
|
{
|
|
buffer_.open( QBuffer::ReadWrite );
|
|
|
|
currentByte_.resize( 8 );
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param data Byte array to copy and use as data source
|
|
*/
|
|
DataSource::DataSource( const QByteArray& data )
|
|
: currentBitIndex_( 8 )
|
|
{
|
|
buffer_.setData( data );
|
|
buffer_.open( QBuffer::ReadWrite );
|
|
buffer_.seek( 0 );
|
|
|
|
currentByte_.resize( 8 );
|
|
|
|
// Move the first byte of data in the bit array
|
|
moveByteToBitArray();
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Destructor.
|
|
*/
|
|
DataSource::~DataSource()
|
|
{
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Insert a byte at the end of the data.
|
|
*
|
|
* @param byte The byte to append
|
|
*/
|
|
void DataSource::append( char byte )
|
|
{
|
|
bool wasEmpty = ( buffer_.size() == 0 );
|
|
|
|
if( ! wasEmpty )
|
|
{
|
|
buffer_.seek( buffer_.size() );
|
|
}
|
|
|
|
if( buffer_.write( &byte, 1 ) != 1 )
|
|
{
|
|
qWarning() << "DataSource::append() - Write failed at buffer position" << buffer_.pos();
|
|
return;
|
|
}
|
|
|
|
// Prepare the first byte to be read
|
|
if( wasEmpty )
|
|
{
|
|
currentBitIndex_ = 8;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Insert bits at the end of the data.
|
|
*
|
|
* @param constBits The list of bits to append
|
|
*/
|
|
void DataSource::append( const QBitArray& constBits )
|
|
{
|
|
if( constBits.size() == 0 )
|
|
{
|
|
#ifdef ISFQT_DEBUG_VERBOSE
|
|
qDebug() << "Not writing an empty bit array, skipping.";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
if( buffer_.size() != 0 )
|
|
{
|
|
buffer_.seek( buffer_.size() );
|
|
}
|
|
|
|
quint8 initialPos = 0;
|
|
quint8 size = constBits.size();
|
|
|
|
// If there are unwritten bits, add them to the input bit array
|
|
if( currentBitIndex_ != 8 )
|
|
{
|
|
size += currentBitIndex_;
|
|
}
|
|
|
|
QBitArray bits( size );
|
|
|
|
// Copy on the input bit array the current byte then the input bits
|
|
if( currentBitIndex_ != 8 )
|
|
{
|
|
initialPos = currentBitIndex_;
|
|
for( quint8 idx = 0; idx < currentBitIndex_; ++idx )
|
|
{
|
|
bits[ idx ] = currentByte_[ idx ];
|
|
}
|
|
}
|
|
for( quint8 idx = 0; idx < constBits.size(); ++idx )
|
|
{
|
|
bits[ idx + initialPos ] = constBits[ idx ];
|
|
}
|
|
|
|
quint8 bit = 7;
|
|
quint8 byte = 0;
|
|
QByteArray bytes;
|
|
quint8 byteWiseSize = bits.size() - ( bits.size() % 8 );
|
|
|
|
// Skip writing if there are no full bytes
|
|
if( byteWiseSize != 0 )
|
|
{
|
|
for( quint8 idx = 0; idx < byteWiseSize; ++idx )
|
|
{
|
|
byte |= bits[ idx ] << bit;
|
|
|
|
// A byte was filled, push it to the bytearray
|
|
if( bit-- == 0 )
|
|
{
|
|
bytes.append( byte );
|
|
bit = 7;
|
|
byte = 0;
|
|
}
|
|
}
|
|
|
|
// Write the bytes in the buffer
|
|
if( bytes.size() > 0 && buffer_.write( bytes ) != bytes.size() )
|
|
{
|
|
qWarning() << "DataSource::append() - Write failed at buffer position" << buffer_.pos();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Move any remaining bits to the current byte
|
|
quint8 remainingBitSize = ( bits.size() - byteWiseSize );
|
|
if( remainingBitSize > 0 )
|
|
{
|
|
for( currentBitIndex_ = 0; currentBitIndex_ < remainingBitSize; ++currentBitIndex_ )
|
|
{
|
|
currentByte_[ currentBitIndex_ ] = bits[ currentBitIndex_ + byteWiseSize ];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
currentByte_.clear();
|
|
currentByte_.resize( 8 );
|
|
currentBitIndex_ = 8;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Insert bytes at the end of the data.
|
|
*
|
|
* @param bytes The bytes to append
|
|
*/
|
|
void DataSource::append( const QByteArray& bytes )
|
|
{
|
|
bool wasEmpty = ( buffer_.size() == 0 );
|
|
|
|
if( ! wasEmpty )
|
|
{
|
|
buffer_.seek( buffer_.size() );
|
|
}
|
|
|
|
if( buffer_.write( bytes ) != bytes.size() )
|
|
{
|
|
qWarning() << "DataSource::append() - Write of" << bytes.size() << "bytes failed at buffer position" << buffer_.pos();
|
|
return;
|
|
}
|
|
|
|
// Prepare the first byte to be read
|
|
if( wasEmpty )
|
|
{
|
|
currentBitIndex_ = 8;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Get whether the buffer is finished.
|
|
*
|
|
* The buffer is finished (at the end) when there are no more
|
|
* bytes to read. If the considerBits parameter is set, the
|
|
* buffer is considered finished when all the bits have been
|
|
* read.
|
|
*
|
|
* @param considerBits Whether to consider the data source as a bits source
|
|
* @return bool
|
|
*/
|
|
bool DataSource::atEnd( bool considerBits ) const
|
|
{
|
|
if( considerBits )
|
|
{
|
|
// There's no more data only when both the buffer AND all the bits
|
|
// have been read
|
|
return buffer_.atEnd() && ( currentBitIndex_ >= 8 );
|
|
}
|
|
|
|
return buffer_.atEnd();
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Clear the data buffer.
|
|
*/
|
|
void DataSource::clear()
|
|
{
|
|
buffer_.close();
|
|
buffer_.setData( QByteArray() );
|
|
buffer_.open( QIODevice::ReadWrite );
|
|
buffer_.seek( 0 );
|
|
|
|
currentBitIndex_ = 8;
|
|
|
|
currentByte_.clear();
|
|
currentByte_.resize( 8 );
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Return a reference to the internal data array.
|
|
*
|
|
* @return byte array
|
|
*/
|
|
const QByteArray& DataSource::data() const
|
|
{
|
|
return buffer_.data();
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Flush the current byte to the buffer.
|
|
*
|
|
* When in bit mode, this method can be used to save less than 8 bits
|
|
* in the stream. The unsed bits are set to 0.
|
|
*/
|
|
void DataSource::flush()
|
|
{
|
|
if( currentBitIndex_ >= 8 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
quint8 bit = 7;
|
|
quint8 byte = 0;
|
|
for( quint8 index = 0; index < currentBitIndex_; ++index )
|
|
{
|
|
byte |= currentByte_[ index ] << bit--;
|
|
}
|
|
|
|
// Write the bytes in the buffer
|
|
if( buffer_.write( (char*)&byte, 1 ) != 1 )
|
|
{
|
|
qWarning() << "DataSource::flush() - Write failed at buffer position" << buffer_.pos();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Retrieve the index of the current bit.
|
|
*
|
|
* @return Current position within the byte we're reading at the moment
|
|
*/
|
|
quint8 DataSource::getBitIndex()
|
|
{
|
|
return currentBitIndex_;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Retrieve the next bit from the data.
|
|
*
|
|
* @param ok If set, it will contain whether the call was successful or not.
|
|
* @return bool
|
|
*/
|
|
bool DataSource::getBit( bool* ok )
|
|
{
|
|
if( currentBitIndex_ >= 8 )
|
|
{
|
|
// Also check the index again, in case somehow moveByteToBitArray()
|
|
// fails to move the bit index
|
|
if( ( ! moveByteToBitArray() ) || currentBitIndex_ >= 8 )
|
|
{
|
|
if( ok != 0 )
|
|
{
|
|
*ok = false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Q_ASSERT( currentBitIndex_ == 0 );
|
|
}
|
|
|
|
Q_ASSERT( currentByte_.size() == 8 );
|
|
|
|
if( ok != 0 )
|
|
{
|
|
*ok = true;
|
|
}
|
|
|
|
++currentBitIndex_;
|
|
|
|
return currentByte_.at( currentBitIndex_ - 1 );
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Retrieve the next [amount] bits from the data.
|
|
*
|
|
* @param amount Number of bits to read
|
|
* @param ok If set, it will contain whether the call was successful or not.
|
|
* @return quint64 with the set of read bits
|
|
*/
|
|
quint64 DataSource::getBits( quint8 amount, bool* ok )
|
|
{
|
|
if( amount > 64 )
|
|
{
|
|
qWarning() << "DataSource:getBits() - Cannot retrieve" << amount << "bits, the maximum is 64 bits!";
|
|
|
|
if( ok != 0 )
|
|
{
|
|
*ok = false;
|
|
}
|
|
|
|
return 0LL;
|
|
}
|
|
|
|
bool gotBitOk = false;
|
|
quint8 bitIndex = 0;
|
|
quint64 value = 0;
|
|
|
|
while( ! atEnd( true ) && bitIndex < amount )
|
|
{
|
|
bool bit = getBit( &gotBitOk );
|
|
|
|
if( ! gotBitOk )
|
|
{
|
|
if( ok != 0 )
|
|
{
|
|
*ok = false;
|
|
}
|
|
|
|
return 0LL;
|
|
}
|
|
|
|
if( bit )
|
|
{
|
|
#if Q_BYTE_ORDER == Q_BIG_ENDIAN
|
|
value |= 1 << bitIndex;
|
|
#else
|
|
value |= 1 << ( amount - bitIndex - 1 );
|
|
#endif
|
|
}
|
|
|
|
++bitIndex;
|
|
}
|
|
|
|
// If more bits must be read, but are not available, abort
|
|
#ifdef ISFQT_DEBUG_VERBOSE
|
|
if( ( bitIndex < amount ) && atEnd( true ) )
|
|
{
|
|
qDebug() << "Unable to read all bits: only" << bitIndex << "of" << amount << "were available.";
|
|
}
|
|
#endif
|
|
|
|
// Done!
|
|
if( ok != 0 )
|
|
{
|
|
*ok = true;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Retrieve the next byte from the data
|
|
*
|
|
* @param ok If set, it will contain whether the call was successful or not.
|
|
* @return char
|
|
*/
|
|
char DataSource::getByte( bool* ok )
|
|
{
|
|
bool gotBitOk;
|
|
quint8 pos = 0;
|
|
qint8 byte = 0;
|
|
|
|
while( pos < 8 )
|
|
{
|
|
bool bit = getBit( &gotBitOk );
|
|
|
|
if( ! gotBitOk )
|
|
{
|
|
if( ok != 0 )
|
|
{
|
|
*ok = false;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if Q_BYTE_ORDER == Q_BIG_ENDIAN
|
|
byte |= ( bit << pos++ );
|
|
#else
|
|
byte |= ( bit << ( 7 - pos++ ) );
|
|
#endif
|
|
}
|
|
|
|
// Done!
|
|
if( ok != 0 )
|
|
{
|
|
*ok = true;
|
|
}
|
|
return byte;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Retrieve the next [amount] bytes from the data.
|
|
*
|
|
* @param amount Number of bytes to read
|
|
* @param ok If set, it will contain whether the call was successful or not.
|
|
* @return byte array of read bytes
|
|
*/
|
|
QByteArray DataSource::getBytes( quint8 amount, bool* ok )
|
|
{
|
|
QByteArray bytes;
|
|
bool gotByteOk;
|
|
quint8 index = 0;
|
|
|
|
while( ! atEnd() && index < amount )
|
|
{
|
|
quint8 byte = getByte( &gotByteOk );
|
|
|
|
if( ! gotByteOk )
|
|
{
|
|
if( ok != 0 )
|
|
{
|
|
*ok = false;
|
|
}
|
|
|
|
return QByteArray();
|
|
}
|
|
|
|
bytes.append( byte );
|
|
++index;
|
|
}
|
|
|
|
// Done!
|
|
if( ok != 0 )
|
|
{
|
|
*ok = true;
|
|
}
|
|
|
|
return bytes;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Move a byte from the buffer into the 'current byte' bit array.
|
|
*
|
|
* @return bool
|
|
*/
|
|
bool DataSource::moveByteToBitArray()
|
|
{
|
|
quint8 byte = 0;
|
|
|
|
if( buffer_.size() == 0 )
|
|
{
|
|
qWarning() << "DataSource:moveByteToBitArray() - The buffer is empty!";
|
|
currentByte_.clear();
|
|
currentBitIndex_ = 8;
|
|
return false;
|
|
}
|
|
|
|
if( buffer_.atEnd() )
|
|
{
|
|
qWarning() << "DataSource:moveByteToBitArray() - The buffer was completely parsed!";
|
|
currentByte_.clear();
|
|
currentBitIndex_ = 8;
|
|
return false;
|
|
}
|
|
|
|
if( buffer_.getChar( (char*)&byte ) != 1 )
|
|
{
|
|
qWarning() << "DataSource::moveByteToBitArray() - Read failed at buffer position" << buffer_.pos();
|
|
return false;
|
|
}
|
|
|
|
for( qint8 i = 0; i < 8; i++ )
|
|
{
|
|
#if Q_BYTE_ORDER == Q_BIG_ENDIAN
|
|
currentByte_.setBit( i, byte & ( 1 << i ) );
|
|
#else
|
|
currentByte_.setBit( 7 - i, byte & ( 1 << i ) );
|
|
#endif
|
|
}
|
|
|
|
currentBitIndex_ = 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Get the current position within the internal data buffer.
|
|
*
|
|
* @return qint64
|
|
*/
|
|
qint64 DataSource::pos() const
|
|
{
|
|
return buffer_.pos();
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Insert a byte at the beginning of the data.
|
|
*
|
|
* @param byte Byte to add
|
|
*/
|
|
void DataSource::prepend( char byte )
|
|
{
|
|
buffer_.buffer().prepend( byte );
|
|
|
|
bool wasAtBeginning = ( buffer_.pos() == 0 );
|
|
|
|
seekRelative( 1 );
|
|
|
|
if( wasAtBeginning )
|
|
{
|
|
moveByteToBitArray();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Insert bytes at the beginning of the data.
|
|
*
|
|
* @param bytes Bytes to add
|
|
*/
|
|
void DataSource::prepend( const QByteArray& bytes )
|
|
{
|
|
buffer_.buffer().prepend( bytes );
|
|
|
|
bool wasAtBeginning = ( buffer_.pos() == 0 );
|
|
|
|
seekRelative( bytes.size() );
|
|
|
|
if( wasAtBeginning )
|
|
{
|
|
moveByteToBitArray();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//
|
|
/**
|
|
* Return to the start of the buffer.
|
|
*/
|
|
void DataSource::reset()
|
|
{
|
|
buffer_.reset();
|
|
currentBitIndex_ = 8;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
/**
|
|
* Seek back and forth in the stream.
|
|
*
|
|
* A negative pos will move backwards within the stream.
|
|
*
|
|
* @param pos Relative position where to move
|
|
*/
|
|
void DataSource::seekRelative( int pos )
|
|
{
|
|
if( pos < 0 && buffer_.pos() <= 0 )
|
|
{
|
|
#ifdef ISFQT_DEBUG_VERBOSE
|
|
qWarning() << "Cannot seek back!";
|
|
#endif
|
|
return;
|
|
}
|
|
if( pos > 0 && ( buffer_.size() - buffer_.pos() ) < pos )
|
|
{
|
|
#ifdef ISFQT_DEBUG_VERBOSE
|
|
qWarning() << "Cannot seek forward!";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
buffer_.seek( buffer_.pos() + pos );
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Replace the data byte array with another.
|
|
*
|
|
* @param data New array to copy and use as the data source
|
|
*/
|
|
void DataSource::setData( const QByteArray& data )
|
|
{
|
|
buffer_.close();
|
|
buffer_.setData( data );
|
|
buffer_.open( QIODevice::ReadWrite );
|
|
buffer_.seek( 0 );
|
|
|
|
currentBitIndex_ = 8;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Skip the rest of the current byte and read the next one.
|
|
*
|
|
* When using the data source in bit mode, this skips the
|
|
* bits remaining in the current byte and prepares to read
|
|
* the next byte.
|
|
*/
|
|
void DataSource::skipToNextByte()
|
|
{
|
|
if( currentBitIndex_ == 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
moveByteToBitArray();
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Skip the rest of the current byte and read the previous one.
|
|
*
|
|
* When using the data source in bit mode, this skips the
|
|
* bits remaining in the current byte and prepares to read
|
|
* the next byte.
|
|
*/
|
|
void DataSource::skipToPrevByte()
|
|
{
|
|
currentBitIndex_ = 8;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
/**
|
|
* Get the size of the data.
|
|
*
|
|
* @return qint64
|
|
*/
|
|
qint64 DataSource::size() const
|
|
{
|
|
return buffer_.size();
|
|
}
|
|
|
|
|