From c70c81b371993ca865d523bb5f37eac4eb8a972b Mon Sep 17 00:00:00 2001 From: Marc Mutz Date: Fri, 17 May 2024 15:29:12 +0200 Subject: [PATCH] Long live QCryptographicHash::hashInto()! MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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, QSpan and QSpan, 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 --- src/corelib/tools/qcryptographichash.cpp | 31 ++++++++++++++++++- src/corelib/tools/qcryptographichash.h | 8 +++++ .../tst_qcryptographichash.cpp | 6 ++++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/corelib/tools/qcryptographichash.cpp b/src/corelib/tools/qcryptographichash.cpp index 8baec8ecc8..3807e32a72 100644 --- a/src/corelib/tools/qcryptographichash.cpp +++ b/src/corelib/tools/qcryptographichash.cpp @@ -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 buffer, QByteArrayView data, Algorithm method); + \fn QCryptographicHash::hashInto(QSpan buffer, QByteArrayView data, Algorithm method); + \fn QCryptographicHash::hashInto(QSpan 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 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()); } /*! diff --git a/src/corelib/tools/qcryptographichash.h b/src/corelib/tools/qcryptographichash.h index 294453adce..3d91c1f917 100644 --- a/src/corelib/tools/qcryptographichash.h +++ b/src/corelib/tools/qcryptographichash.h @@ -8,6 +8,7 @@ #include #include +#include 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 buffer, QByteArrayView data, Algorithm method) noexcept + { return hashInto(as_writable_bytes(buffer), data, method); } + static QByteArrayView hashInto(QSpan buffer, QByteArrayView data, Algorithm method) noexcept + { return hashInto(as_writable_bytes(buffer), data, method); } + static QByteArrayView hashInto(QSpan buffer, QByteArrayView data, Algorithm method) noexcept; + static int hashLength(Algorithm method); static bool supportsAlgorithm(Algorithm method); private: diff --git a/tests/auto/corelib/tools/qcryptographichash/tst_qcryptographichash.cpp b/tests/auto/corelib/tools/qcryptographichash/tst_qcryptographichash.cpp index fe1a9b828a..47e0ead270 100644 --- a/tests/auto/corelib/tools/qcryptographichash/tst_qcryptographichash.cpp +++ b/tests/auto/corelib/tools/qcryptographichash/tst_qcryptographichash.cpp @@ -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); }