Revision control
Copy as Markdown
// Copyright (C) 2021 The Qt Company Ltd.
// Copyright (C) 2015 Alex Trotsenko <alex1973tr@gmail.com>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "private/qringbuffer_p.h"
#include <type_traits>
#include <string.h>
QT_BEGIN_NAMESPACE
static_assert(std::is_nothrow_default_constructible_v<QRingChunk>);
static_assert(std::is_nothrow_move_constructible_v<QRingChunk>);
static_assert(std::is_nothrow_move_assignable_v<QRingChunk>);
void QRingChunk::allocate(qsizetype alloc)
{
Q_ASSERT(alloc > 0 && size() == 0);
if (chunk.size() < alloc || isShared())
chunk = QByteArray(alloc, Qt::Uninitialized);
}
void QRingChunk::detach()
{
Q_ASSERT(isShared());
const qsizetype chunkSize = size();
chunk = QByteArray(std::as_const(*this).data(), chunkSize);
headOffset = 0;
tailOffset = chunkSize;
}
QByteArray QRingChunk::toByteArray() &&
{
// ### Replace with std::move(chunk).sliced(head(), size()) once sliced()&& is available
if (headOffset != 0 || tailOffset != chunk.size()) {
if (isShared())
return chunk.sliced(head(), size());
chunk.resize(tailOffset);
chunk.remove(0, headOffset);
}
return std::move(chunk);
}
/*!
\internal
Access the bytes at a specified position the out-variable length will
contain the amount of bytes readable from there, e.g. the amount still
the same QByteArray
*/
const char *QRingBuffer::readPointerAtPosition(qint64 pos, qint64 &length) const
{
Q_ASSERT(pos >= 0);
for (const QRingChunk &chunk : buffers) {
length = chunk.size();
if (length > pos) {
length -= pos;
return chunk.data() + pos;
}
pos -= length;
}
length = 0;
return nullptr;
}
void QRingBuffer::free(qint64 bytes)
{
Q_ASSERT(bytes <= bufferSize);
while (bytes > 0) {
const qint64 chunkSize = buffers.constFirst().size();
if (buffers.size() == 1 || chunkSize > bytes) {
QRingChunk &chunk = buffers.first();
// keep a single block around if it does not exceed
// the basic block size, to avoid repeated allocations
// between uses of the buffer
if (bufferSize == bytes) {
if (chunk.capacity() <= basicBlockSize && !chunk.isShared()) {
chunk.reset();
bufferSize = 0;
} else {
clear(); // try to minify/squeeze us
}
} else {
Q_ASSERT(bytes < QByteArray::maxSize());
chunk.advance(bytes);
bufferSize -= bytes;
}
return;
}
bufferSize -= chunkSize;
bytes -= chunkSize;
buffers.removeFirst();
}
}
char *QRingBuffer::reserve(qint64 bytes)
{
Q_ASSERT(bytes > 0 && bytes < QByteArray::maxSize());
const qsizetype chunkSize = qMax(qint64(basicBlockSize), bytes);
qsizetype tail = 0;
if (bufferSize == 0) {
if (buffers.isEmpty())
buffers.append(QRingChunk(chunkSize));
else
buffers.first().allocate(chunkSize);
} else {
const QRingChunk &chunk = buffers.constLast();
// if need a new buffer
if (basicBlockSize == 0 || chunk.isShared() || bytes > chunk.available())
buffers.append(QRingChunk(chunkSize));
else
tail = chunk.size();
}
buffers.last().grow(bytes);
bufferSize += bytes;
return buffers.last().data() + tail;
}
/*!
\internal
Allocate data at buffer head
*/
char *QRingBuffer::reserveFront(qint64 bytes)
{
Q_ASSERT(bytes > 0 && bytes < QByteArray::maxSize());
const qsizetype chunkSize = qMax(qint64(basicBlockSize), bytes);
if (bufferSize == 0) {
if (buffers.isEmpty())
buffers.prepend(QRingChunk(chunkSize));
else
buffers.first().allocate(chunkSize);
buffers.first().grow(chunkSize);
buffers.first().advance(chunkSize - bytes);
} else {
const QRingChunk &chunk = buffers.constFirst();
// if need a new buffer
if (basicBlockSize == 0 || chunk.isShared() || bytes > chunk.head()) {
buffers.prepend(QRingChunk(chunkSize));
buffers.first().grow(chunkSize);
buffers.first().advance(chunkSize - bytes);
} else {
buffers.first().advance(-bytes);
}
}
bufferSize += bytes;
return buffers.first().data();
}
void QRingBuffer::chop(qint64 bytes)
{
Q_ASSERT(bytes <= bufferSize);
while (bytes > 0) {
const qsizetype chunkSize = buffers.constLast().size();
if (buffers.size() == 1 || chunkSize > bytes) {
QRingChunk &chunk = buffers.last();
// keep a single block around if it does not exceed
// the basic block size, to avoid repeated allocations
// between uses of the buffer
if (bufferSize == bytes) {
if (chunk.capacity() <= basicBlockSize && !chunk.isShared()) {
chunk.reset();
bufferSize = 0;
} else {
clear(); // try to minify/squeeze us
}
} else {
Q_ASSERT(bytes < QByteArray::maxSize());
chunk.grow(-bytes);
bufferSize -= bytes;
}
return;
}
bufferSize -= chunkSize;
bytes -= chunkSize;
buffers.removeLast();
}
}
void QRingBuffer::clear()
{
if (buffers.isEmpty())
return;
buffers.erase(buffers.begin() + 1, buffers.end());
buffers.first().clear();
bufferSize = 0;
}
qint64 QRingBuffer::indexOf(char c, qint64 maxLength, qint64 pos) const
{
Q_ASSERT(maxLength >= 0 && pos >= 0);
if (maxLength == 0)
return -1;
qint64 index = -pos;
for (const QRingChunk &chunk : buffers) {
const qint64 nextBlockIndex = qMin(index + chunk.size(), maxLength);
if (nextBlockIndex > 0) {
const char *ptr = chunk.data();
if (index < 0) {
ptr -= index;
index = 0;
}
const char *findPtr = reinterpret_cast<const char *>(memchr(ptr, c,
nextBlockIndex - index));
if (findPtr)
return qint64(findPtr - ptr) + index + pos;
if (nextBlockIndex == maxLength)
return -1;
}
index = nextBlockIndex;
}
return -1;
}
qint64 QRingBuffer::read(char *data, qint64 maxLength)
{
const qint64 bytesToRead = qMin(size(), maxLength);
qint64 readSoFar = 0;
while (readSoFar < bytesToRead) {
const qint64 bytesToReadFromThisBlock = qMin(bytesToRead - readSoFar,
nextDataBlockSize());
if (data)
memcpy(data + readSoFar, readPointer(), bytesToReadFromThisBlock);
readSoFar += bytesToReadFromThisBlock;
free(bytesToReadFromThisBlock);
}
return readSoFar;
}
/*!
\internal
Read an unspecified amount (will read the first buffer)
*/
QByteArray QRingBuffer::read()
{
if (bufferSize == 0)
return QByteArray();
bufferSize -= buffers.constFirst().size();
return buffers.takeFirst().toByteArray();
}
/*!
\internal
Peek the bytes from a specified position
*/
qint64 QRingBuffer::peek(char *data, qint64 maxLength, qint64 pos) const
{
Q_ASSERT(maxLength >= 0 && pos >= 0);
qint64 readSoFar = 0;
for (const QRingChunk &chunk : buffers) {
if (readSoFar == maxLength)
break;
qint64 blockLength = chunk.size();
if (pos < blockLength) {
blockLength = qMin(blockLength - pos, maxLength - readSoFar);
memcpy(data + readSoFar, chunk.data() + pos, blockLength);
readSoFar += blockLength;
pos = 0;
} else {
pos -= blockLength;
}
}
return readSoFar;
}
/*!
\internal
Append bytes from data to the end
*/
void QRingBuffer::append(const char *data, qint64 size)
{
Q_ASSERT(size >= 0);
if (size == 0)
return;
char *writePointer = reserve(size);
if (size == 1)
*writePointer = *data;
else
::memcpy(writePointer, data, size);
}
/*!
\internal
Append a new buffer to the end
*/
void QRingBuffer::append(const QByteArray &qba)
{
if (bufferSize != 0 || buffers.isEmpty())
buffers.append(QRingChunk(qba));
else
buffers.last().assign(qba);
bufferSize += qba.size();
}
/*!
\internal
Append a new buffer to the end
*/
void QRingBuffer::append(QByteArray &&qba)
{
const auto qbaSize = qba.size();
if (bufferSize != 0 || buffers.isEmpty())
buffers.emplace_back(std::move(qba));
else
buffers.last().assign(std::move(qba));
bufferSize += qbaSize;
}
qint64 QRingBuffer::readLineWithoutTerminatingNull(char *data, qint64 maxLength)
{
Q_ASSERT(data != nullptr && maxLength > 0);
qint64 i = indexOf('\n', maxLength);
i = read(data, i >= 0 ? (i + 1) : maxLength);
return i;
}
qint64 QRingBuffer::readLine(char *data, qint64 maxLength)
{
Q_ASSERT(maxLength > 1);
qint64 i = readLineWithoutTerminatingNull(data, maxLength - 1);
// Terminate it.
data[i] = '\0';
return i;
}
QT_END_NAMESPACE