Long live QCryptographicHash::hashInto()!

Until now, QCryptographicHash had a big drawback over the underlying
C APIs: at least one extra memory allocation: Either you created a
QCryptographicHash object (allocates a Private), and enjoy noexcept
addData() and resultView(), or you used the static hash() function
(which creates a Private on the stack, so doesn't need to allocate
one), but then we needed to return in an owning container.

Enter QSpan and hashInto(), which allow the user to provide a buffer
into which to write, and therefore can be completely noexcept and
(apart from a missing optimization which, however, doesn't affect the
API) deliver near C-API efficiency.

We don't have QMutableByteArrayView, which would deal with the
different types of equivalent char types for us, so we overload for
QSpan<std::bytes>, QSpan<char> and QSpan<uchar>, which makes the
overload set accept roughly what QByteArrayView would accept, too.

Return by QByteArrayView because that has a richer API than QSpan,
which lacks (on purpose) string-ish API.

[ChangeLog][QtCore][QCryptographicHash] Added hashInto().

Task-number: QTBUG-125431
Change-Id: I80ecc7151d3418a36c4d5db6d22d0b82c869b19f
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
Marc Mutz 2024-05-17 15:29:12 +02:00
parent 39189190fe
commit c70c81b371
3 changed files with 44 additions and 1 deletions

View File

@ -1153,13 +1153,42 @@ void QCryptographicHashPrivate::State::finalizeUnchecked(QCryptographicHash::Alg
\note In Qt versions prior to 6.3, this function took QByteArray,
not QByteArrayView.
\sa hashInto()
*/
QByteArray QCryptographicHash::hash(QByteArrayView data, Algorithm method)
{
QByteArray ba(hashLengthInternal(method), Qt::Uninitialized);
[[maybe_unused]] const auto r = hashInto(ba, data, method);
Q_ASSERT(r.size() == ba.size());
return ba;
}
/*!
\since 6.8
\fn QCryptographicHash::hashInto(QSpan<char> buffer, QByteArrayView data, Algorithm method);
\fn QCryptographicHash::hashInto(QSpan<uchar> buffer, QByteArrayView data, Algorithm method);
\fn QCryptographicHash::hashInto(QSpan<std::byte> buffer, QByteArrayView data, Algorithm method);
Returns the hash of \a data using \a method, using \a buffer to store the result.
The return value will be a sub-span of \a buffer, unless \a buffer is of
insufficient size, in which case a null QByteArrayView is returned.
\sa hash()
*/
QByteArrayView QCryptographicHash::hashInto(QSpan<std::byte> buffer, QByteArrayView data,
Algorithm method) noexcept
{
QCryptographicHashPrivate hash(method);
hash.addData(data);
hash.finalizeUnchecked(); // no mutex needed: no-one but us has access to 'hash'
return hash.resultView().toByteArray();
auto result = hash.resultView();
if (buffer.size() < result.size())
return {}; // buffer too small
// ### optimize: have the method directly write into `buffer`
memcpy(buffer.data(), result.data(), result.size());
return buffer.first(result.size());
}
/*!

View File

@ -8,6 +8,7 @@
#include <QtCore/qbytearray.h>
#include <QtCore/qobjectdefs.h>
#include <QtCore/qspan.h>
QT_BEGIN_NAMESPACE
@ -91,6 +92,13 @@ public:
static QByteArray hash(const QByteArray &data, Algorithm method);
#endif
static QByteArray hash(QByteArrayView data, Algorithm method);
static QByteArrayView hashInto(QSpan<char> buffer, QByteArrayView data, Algorithm method) noexcept
{ return hashInto(as_writable_bytes(buffer), data, method); }
static QByteArrayView hashInto(QSpan<uchar> buffer, QByteArrayView data, Algorithm method) noexcept
{ return hashInto(as_writable_bytes(buffer), data, method); }
static QByteArrayView hashInto(QSpan<std::byte> buffer, QByteArrayView data, Algorithm method) noexcept;
static int hashLength(Algorithm method);
static bool supportsAlgorithm(Algorithm method);
private:

View File

@ -209,6 +209,9 @@ void tst_QCryptographicHash::static_hash()
const auto _algo = QCryptographicHash::Algorithm(algo);
QCOMPARE(QCryptographicHash::hash(first, _algo), hash_first);
std::byte buffer[1024];
QCOMPARE(QCryptographicHash::hashInto(buffer, first, _algo), hash_first);
}
@ -511,6 +514,9 @@ void tst_QCryptographicHash::hashLength()
expectedSize = 0;
} else {
expectedSize = QCryptographicHash::hash("test", algorithm).size();
std::byte buffer[1024];
QCOMPARE(QCryptographicHash::hashInto(buffer, "foo", algorithm).size(), expectedSize);
}
QCOMPARE(QCryptographicHash::hashLength(algorithm), expectedSize);
}