diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ea98d7a8d..30d2136d0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -192,7 +192,7 @@ jobs: TEST_USER: ${{ secrets.TEST_USER }} TEST_PWD: ${{ secrets.TEST_PWD }} QT_ASSUME_STDERR_HAS_CONSOLE: 1 # Windows needs this for meaningful debug output - QT_LOGGING_RULES: 'quotient.main.debug=true;quotient.jobs.debug=true;quotient.events.debug=true' + QT_LOGGING_RULES: 'quotient.*.debug=true;quotient.jobs.sync.debug=false;quotient.events.ephemeral.debug=false;quotient.events.state.debug=false;quotient.events.members.debug=false' QT_MESSAGE_PATTERN: '%{time h:mm:ss.zzz}|%{category}|%{if-debug}D%{endif}%{if-info}I%{endif}%{if-warning}W%{endif}%{if-critical}C%{endif}%{if-fatal}F%{endif}|%{message}' run: | CTEST_ARGS="--test-dir $BUILD_PATH --output-on-failure" diff --git a/Quotient/accountregistry.cpp b/Quotient/accountregistry.cpp index caf980856..61cfa43ee 100644 --- a/Quotient/accountregistry.cpp +++ b/Quotient/accountregistry.cpp @@ -56,7 +56,7 @@ bool AccountRegistry::isLoggedIn(const QString &userId) const QVariant AccountRegistry::data(const QModelIndex& index, int role) const { - if (!index.isValid() || index.row() >= count()) + if (!index.isValid() || index.row() >= size()) return {}; switch (role) { @@ -71,7 +71,7 @@ QVariant AccountRegistry::data(const QModelIndex& index, int role) const int AccountRegistry::rowCount(const QModelIndex& parent) const { - return parent.isValid() ? 0 : count(); + return parent.isValid() ? 0 : size(); } QHash AccountRegistry::roleNames() const diff --git a/Quotient/connection.cpp b/Quotient/connection.cpp index a1652be1f..ed508ae53 100644 --- a/Quotient/connection.cpp +++ b/Quotient/connection.cpp @@ -172,8 +172,7 @@ void Connection::loginWithToken(const QString& loginToken, void Connection::assumeIdentity(const QString& mxId, const QString& deviceId, const QString& accessToken) { - d->data->setDeviceId(deviceId); - d->completeSetup(mxId); + d->completeSetup(mxId, deviceId); d->ensureHomeserver(mxId).then([this, mxId, accessToken] { d->data->setToken(accessToken.toLatin1()); callApi().onResult([this, mxId](const GetTokenOwnerJob* job) { @@ -289,24 +288,22 @@ void Connection::Private::dropAccessToken() template void Connection::Private::loginToServer(LoginArgTs&&... loginArgs) { - auto loginJob = - q->callApi(std::forward(loginArgs)...); - connect(loginJob, &BaseJob::success, q, [this, loginJob] { - data->setToken(loginJob->accessToken().toLatin1()); - data->setDeviceId(loginJob->deviceId()); - completeSetup(loginJob->userId()); - saveAccessTokenToKeychain(); - if (encryptionData) - encryptionData->database.clear(); - }); - connect(loginJob, &BaseJob::failure, q, [this, loginJob] { - emit q->loginError(loginJob->errorString(), loginJob->rawDataSample()); - }); + q->callApi(std::forward(loginArgs)...) + .onResult([this](const LoginJob* loginJob) { + if (loginJob->status().good()) { + data->setToken(loginJob->accessToken().toLatin1()); + completeSetup(loginJob->userId(), loginJob->deviceId()); + saveAccessTokenToKeychain(); + if (encryptionData) + encryptionData->database.clear(); + } else + emit q->loginError(loginJob->errorString(), loginJob->rawDataSample()); + }); } -void Connection::Private::completeSetup(const QString& mxId, bool mock) +void Connection::Private::completeSetup(const QString& mxId, const QString& deviceId, bool mock) { - data->setUserId(mxId); + data->setIdentity(mxId, deviceId); q->setObjectName(data->userId() % u'/' % data->deviceId()); qCDebug(MAIN) << "Using server" << data->baseUrl().toDisplayString() << "by user" << data->userId() @@ -800,7 +797,7 @@ JobHandle Connection::createRoom( creationContent, initialState, presetName, isDirect) .then(this, [this, invites, isDirect](const QString& roomId) { auto* room = provideRoom(roomId, JoinState::Join); - if (ALARM_X(!room, "Failed to create a room")) + if (QUO_ALARM_X(!room, "Failed to create a room object locally")) return; emit createdRoom(room); @@ -818,7 +815,7 @@ void Connection::requestDirectChat(const QString& userId) QFuture Connection::getDirectChat(const QString& otherUserId) { auto* u = user(otherUserId); - if (ALARM_X(!u, u"Couldn't get a user object for" % otherUserId)) + if (QUO_ALARM_X(!u, u"Couldn't get a user object for" % otherUserId)) return {}; // There can be more than one DC; find the first valid (existing and @@ -1924,7 +1921,7 @@ Connection* Connection::makeMockConnection(const QString& mxId, { auto* c = new Connection; c->enableEncryption(enableEncryption); - c->d->completeSetup(mxId, true); + c->d->completeSetup(mxId, {}, true); return c; } diff --git a/Quotient/connection_p.h b/Quotient/connection_p.h index f72da4088..0caa06122 100644 --- a/Quotient/connection_p.h +++ b/Quotient/connection_p.h @@ -93,7 +93,7 @@ class Q_DECL_HIDDEN Quotient::Connection::Private { QFuture ensureHomeserver(const QString& userId, const std::optional& flow = {}); template void loginToServer(LoginArgTs&&... loginArgs); - void completeSetup(const QString &mxId, bool mock = false); + void completeSetup(const QString& mxId, const QString& deviceId, bool mock = false); void removeRoom(const QString& roomId); void consumeRoomData(SyncDataList&& roomDataList, bool fromCache); diff --git a/Quotient/connectiondata.cpp b/Quotient/connectiondata.cpp index 3bc5e24d6..176096d55 100644 --- a/Quotient/connectiondata.cpp +++ b/Quotient/connectiondata.cpp @@ -104,7 +104,7 @@ QUrl ConnectionData::baseUrl() const { return d->baseUrl; } HomeserverData ConnectionData::homeserverData() const { - return { d->baseUrl, d->supportedSpecVersions }; + return { d->baseUrl, d->supportedSpecVersions, d->accessToken }; } NetworkAccessManager* ConnectionData::nam() const @@ -134,12 +134,11 @@ const QString& ConnectionData::deviceId() const { return d->deviceId; } const QString& ConnectionData::userId() const { return d->userId; } -void ConnectionData::setDeviceId(const QString& deviceId) -{ - d->deviceId = deviceId; -} +void ConnectionData::setDeviceId(const QString& deviceId) { d->deviceId = deviceId; } -void ConnectionData::setUserId(const QString& userId) +void ConnectionData::setUserId(const QString& userId) { setIdentity(userId, d->deviceId); } + +void ConnectionData::setIdentity(const QString& userId, const QString& deviceId) { if (d->baseUrl.isValid()) { if (d->userId != userId) @@ -150,13 +149,14 @@ void ConnectionData::setUserId(const QString& userId) } } d->userId = userId; + d->deviceId = deviceId; } void ConnectionData::setSupportedSpecVersions(QStringList versions) { qCInfo(MAIN).noquote() << "CS API versions:" << versions.join(u' '); d->supportedSpecVersions = std::move(versions); - if (!ALARM(d->userId.isEmpty()) && !ALARM(!d->baseUrl.isValid())) + if (QUO_CHECK(!d->userId.isEmpty()) && QUO_CHECK(d->baseUrl.isValid())) NetworkAccessManager::updateAccountSpecVersions(d->userId, d->supportedSpecVersions); } diff --git a/Quotient/connectiondata.h b/Quotient/connectiondata.h index 1b8518e02..51d9c3e28 100644 --- a/Quotient/connectiondata.h +++ b/Quotient/connectiondata.h @@ -33,8 +33,11 @@ class QUOTIENT_API ConnectionData { void setBaseUrl(QUrl baseUrl); void setToken(QByteArray accessToken); + [[deprecated("Use setIdentity() instead")]] void setDeviceId(const QString& deviceId); + [[deprecated("Use setIdentity() instead")]] void setUserId(const QString& userId); + void setIdentity(const QString& userId, const QString& deviceId); void setSupportedSpecVersions(QStringList versions); QString lastEvent() const; diff --git a/Quotient/e2ee/cryptoutils.cpp b/Quotient/e2ee/cryptoutils.cpp index 983afa1e6..0a6ab6975 100644 --- a/Quotient/e2ee/cryptoutils.cpp +++ b/Quotient/e2ee/cryptoutils.cpp @@ -65,16 +65,17 @@ inline std::pair checkedSize( // NOLINTBEGIN(cppcoreguidelines-pro-bounds-array-to-pointer-decay) // TODO: remove NOLINT brackets once we're on clang-tidy 18 -#define CLAMP_SIZE(SizeVar_, ByteArray_, ...) \ - const auto [SizeVar_, ByteArray_##Clamped] = \ - checkedSize((ByteArray_).size() __VA_OPT__(, ) __VA_ARGS__); \ - if (ALARM_X(ByteArray_##Clamped, \ - qPrintable(QStringLiteral(#ByteArray_ " is %1 bytes long, too much for OpenSSL" \ - " and overall suspicious") \ - .arg((ByteArray_).size())))) \ - return SslPayloadTooLong; \ - do {} while (false) \ - // End of macro +#define CLAMP_SIZE(SizeVar_, ByteArray_, ...) \ + const auto [SizeVar_, ByteArray_##Clamped] = \ + checkedSize((ByteArray_).size() __VA_OPT__(, ) __VA_ARGS__); \ + if (QUO_ALARM_X(ByteArray_##Clamped, \ + QStringLiteral( \ + #ByteArray_ \ + " is %1 bytes long, too much for OpenSSL and overall suspicious") \ + .arg((ByteArray_).size()))) \ + return SslPayloadTooLong; \ + do { \ + } while (false) // End of macro #define CALL_OPENSSL(Call_) \ do { \ @@ -106,7 +107,7 @@ SslExpected Quotient::aesCtr256Encrypt(const QByteArray& plaintext, CLAMP_SIZE(plaintextSize, plaintext); const ContextHolder ctx(EVP_CIPHER_CTX_new(), &EVP_CIPHER_CTX_free); - if (ALARM_X(!ctx, QByteArrayLiteral("failed to create SSL context: ") + if (QUO_ALARM_X(!ctx, QByteArrayLiteral("failed to create SSL context: ") + ERR_error_string(ERR_get_error(), nullptr))) return ERR_get_error(); @@ -311,7 +312,7 @@ std::vector Quotient::base58Decode(const QByteArray& encoded) } } - for (auto i = 0; i < encoded.length() && encoded[i] == '1'; ++i) { + for (auto i = 0; i < encoded.size() && encoded[i] == '1'; ++i) { result.push_back(0x0); } @@ -323,7 +324,8 @@ QByteArray Quotient::sign(const QByteArray& key, const QByteArray& data) { auto context = makeCStruct(olm_pk_signing, olm_pk_signing_size, olm_clear_pk_signing); QByteArray pubKey(olm_pk_signing_public_key_length(), 0); - olm_pk_signing_key_from_seed(context.get(), pubKey.data(), pubKey.length(), key.data(), key.length()); + olm_pk_signing_key_from_seed(context.get(), pubKey.data(), unsignedSize(pubKey), key.data(), + unsignedSize(key)); Q_ASSERT(context); const auto signatureLength = olm_pk_signature_length(); diff --git a/Quotient/e2ee/e2ee_common.h b/Quotient/e2ee/e2ee_common.h index e0ac2e8d6..8bb3ea8ba 100644 --- a/Quotient/e2ee/e2ee_common.h +++ b/Quotient/e2ee/e2ee_common.h @@ -182,8 +182,6 @@ class QUOTIENT_API FixedBufferBase { return { std::bit_cast(data_), untilPos }; } - // TODO, 0.9: merge the overloads - QByteArray toBase64() const { return viewAsByteArray().toBase64(); } QByteArray toBase64(QByteArray::Base64Options options) const { @@ -218,11 +216,7 @@ class QUOTIENT_API FixedBuffer : public FixedBufferBase { static_assert(extent == std::dynamic_extent || (extent < TotalSecureHeapSize / 2 && extent % 4 == 0)); - FixedBuffer() // TODO, 0.9: merge with the next constructor - requires(extent != std::dynamic_extent) - : FixedBuffer(FillWithZeros) - {} - explicit FixedBuffer(InitOptions fillMode) + explicit FixedBuffer(InitOptions fillMode = FillWithZeros) requires(extent != std::dynamic_extent) : FixedBufferBase(ExtentN, fillMode) {} diff --git a/Quotient/e2ee/qolmaccount.cpp b/Quotient/e2ee/qolmaccount.cpp index 34bd19cfb..8fe442b05 100644 --- a/Quotient/e2ee/qolmaccount.cpp +++ b/Quotient/e2ee/qolmaccount.cpp @@ -251,13 +251,12 @@ QOlmExpected QOlmAccount::createOutboundSession( const QByteArray& theirIdentityKey, const QByteArray& theirOneTimeKey) const { QOlmSession olmOutboundSession{}; - if (const auto randomLength = olm_create_outbound_session_random_length( - olmOutboundSession.olmData); - olm_create_outbound_session( - olmOutboundSession.olmData, olmData, theirIdentityKey.data(), - theirIdentityKey.length(), theirOneTimeKey.data(), - theirOneTimeKey.length(), getRandom(randomLength).data(), - randomLength) + if (const auto randomLength = + olm_create_outbound_session_random_length(olmOutboundSession.olmData); + olm_create_outbound_session(olmOutboundSession.olmData, olmData, theirIdentityKey.data(), + unsignedSize(theirIdentityKey), theirOneTimeKey.data(), + unsignedSize(theirOneTimeKey), getRandom(randomLength).data(), + randomLength) == olm_error()) { const auto errorCode = olmOutboundSession.lastErrorCode(); QOLM_FAIL_OR_LOG_X(errorCode == OLM_NOT_ENOUGH_RANDOM, diff --git a/Quotient/jobs/basejob.cpp b/Quotient/jobs/basejob.cpp index aab07d728..873434ee4 100644 --- a/Quotient/jobs/basejob.cpp +++ b/Quotient/jobs/basejob.cpp @@ -9,6 +9,7 @@ #include "../connectiondata.h" #include "../networkaccessmanager.h" +#include #include #include #include @@ -467,9 +468,9 @@ bool checkContentType(const QByteArray& type, const QByteArrayList& patterns) return true; auto patternParts = pattern.split('/'); - if (ALARM_X(patternParts.size() > 2, - "Expected content type should have up to two /-separated parts; violating pattern: " - % pattern)) + if (QUO_ALARM_X(patternParts.size() > 2, "Expected content type should have up to two " + "parts separated by `/`; violating pattern: " + % pattern)) return false; if (ctype.split('/').front() == patternParts.front() @@ -806,6 +807,8 @@ void BaseJob::abandon() if (d->reply) d->reply->disconnect(this); emit finished(this); + if (QLibraryInfo::version() < QVersionNumber(6, 5)) + future().cancel(); // Qt 6.4 didn't do it on the promise destruction, see QTBUG-103992 deleteLater(); // The promise will cancel itself on deletion } diff --git a/Quotient/jobs/basejob.h b/Quotient/jobs/basejob.h index b24aebd5d..9e3c444ac 100644 --- a/Quotient/jobs/basejob.h +++ b/Quotient/jobs/basejob.h @@ -37,14 +37,12 @@ class QUOTIENT_API BaseJob : public QObject { } public: - /*! The status code of a job - * - * Every job is created in Unprepared status; upon calling prepare() - * from Connection (if things are fine) it go to Pending status. After - * that, the next transition comes after the reply arrives and its contents - * are analysed. At any point in time the job can be abandon()ed, causing - * it to switch to status Abandoned for a brief period before deletion. - */ + //! \brief Job status codes + //! + //! Every job is created in Unprepared status; upon calling Connection::prepare(), if things are + //! fine, it becomes Pending and remains so until the reply arrives; then the status code is + //! set according to the job result. At any point in time the job can be abandon()ed, causing + //! it to become Abandoned for a brief period before deletion. enum StatusCode { Success = 0, NoError = Success, @@ -81,15 +79,13 @@ class QUOTIENT_API BaseJob : public QObject { return (QByteArray() % ... % encodeIfParam(parts)); } - /*! - * This structure stores the status of a server call job. The status - * consists of a code, that is described (but not delimited) by the - * respective enum, and a freeform message. - * - * To extend the list of error codes, define an (anonymous) enum - * along the lines of StatusCode, with additional values - * starting at UserDefinedError - */ + //! \brief The status of a job + //! + //! The status consists of a code that is described (but not delimited) by StatusCode, and + //! a freeform message. + //! + //! To extend the list of error codes, define an (anonymous) enum along the lines of StatusCode, + //! with additional values starting at UserDefinedError. struct Status { Status(StatusCode c) : code(c) {} Status(int c, QString m) : code(c), message(std::move(m)) {} @@ -138,56 +134,45 @@ class QUOTIENT_API BaseJob : public QObject { QUrl requestUrl() const; bool isBackground() const; - /** Current status of the job */ + //! Current status of the job Status status() const; - /** Short human-friendly message on the job status */ + //! Short human-friendly message on the job status QString statusCaption() const; - /*! Get first bytes of the raw response body as received from the server - * - * \param bytesAtMost the number of leftmost bytes to return - * - * \sa rawDataSample - */ + //! \brief Get first bytes of the raw response body as received from the server + //! \param bytesAtMost the number of leftmost bytes to return + //! \sa rawDataSample QByteArray rawData(int bytesAtMost) const; - /*! Access the whole response body as received from the server */ + //! Access the whole response body as received from the server const QByteArray& rawData() const; - /** Get UI-friendly sample of raw data - * - * This is almost the same as rawData but appends the "truncated" - * suffix if not all data fit in bytesAtMost. This call is - * recommended to present a sample of raw data as "details" next to - * error messages. Note that the default \p bytesAtMost value is - * also tailored to UI cases. - * - * \sa rawData - */ + //! \brief Get UI-friendly sample of raw data + //! + //! This is almost the same as rawData but appends the "truncated" suffix if not all data fit in + //! bytesAtMost. This call is recommended to present a sample of raw data as "details" next to + //! error messages. Note that the default \p bytesAtMost value is also tailored to UI cases. + //! + //! \sa //! rawData QString rawDataSample(int bytesAtMost = 65535) const; - /** Get the response body as a JSON object - * - * If the job's returned content type is not `application/json` - * or if the top-level JSON entity is not an object, an empty object - * is returned. - */ + //! \brief Get the response body as a JSON object + //! + //! If the job's returned content type is not `application/json` or if the top-level JSON entity + //! is not an object, an empty object is returned. QJsonObject jsonData() const; - /** Get the response body as a JSON array - * - * If the job's returned content type is not `application/json` - * or if the top-level JSON entity is not an array, an empty array - * is returned. - */ + //! \brief Get the response body as a JSON array + //! + //! If the job's returned content type is not `application/json` or if the top-level JSON entity + //! is not an array, an empty array is returned. QJsonArray jsonItems() const; - /** Load the property from the JSON response assuming a given C++ type - * - * If there's no top-level JSON object in the response or if there's - * no node with the key \p keyName, \p defaultValue is returned. - */ + //! \brief Load the property from the JSON response assuming a given C++ type + //! + //! If there's no top-level JSON object in the response or if there's + //! no node with the key \p keyName, \p defaultValue is returned. template T loadFromJson(const StrT& keyName, T&& defaultValue = {}) const { @@ -196,11 +181,10 @@ class QUOTIENT_API BaseJob : public QObject { : fromJson(jv); } - /** Load the property from the JSON response and delete it from JSON - * - * If there's no top-level JSON object in the response or if there's - * no node with the key \p keyName, \p defaultValue is returned. - */ + //! \brief Load the property from the JSON response and delete it from JSON + //! + //! If there's no top-level JSON object in the response or if there's + //! no node with the key \p keyName, \p defaultValue is returned. template T takeFromJson(const QString& key, T&& defaultValue = {}) { @@ -210,16 +194,16 @@ class QUOTIENT_API BaseJob : public QObject { return std::forward(defaultValue); } - /** Error (more generally, status) code - * Equivalent to status().code - * \sa status - */ + //! \brief Error (more generally, status) code + //! + //! Equivalent to status().code + //! \sa status, StatusCode int error() const; - /** Error-specific message, as returned by the server */ + //! Error-specific message, as returned by the server virtual QString errorString() const; - /** A URL to help/clarify the error, if provided by the server */ + //! A URL to help/clarify the error, if provided by the server QUrl errorUrl() const; int maxRetries() const; @@ -264,73 +248,60 @@ public Q_SLOTS: //! \sa setRequestHeaders, setRequestQuery void aboutToSendRequest(QNetworkRequest* req); - /** The job has sent a network request */ + //! The job has sent a network request void sentRequest(); - /** The job has changed its status */ + //! The job has changed its status void statusChanged(Quotient::BaseJob::Status newStatus); - /** - * The previous network request has failed; the next attempt will - * be done in the specified time - * @param nextAttempt the 1-based number of attempt (will always be more - * than 1) - * @param inMilliseconds the interval after which the next attempt will be - * taken - */ - void retryScheduled(int nextAttempt, - Quotient::BaseJob::duration_ms_t inMilliseconds); - - /** - * The previous network request has been rate-limited; the next attempt - * will be queued and run sometime later. Since other jobs may already - * wait in the queue, it's not possible to predict the wait time. - */ + //! \brief A retry of the network request is scheduled after the previous request failed + //! \param nextAttempt the 1-based number of attempt (will always be more than 1) + //! \param inMilliseconds the interval after which the next attempt will be taken + void retryScheduled(int nextAttempt, Quotient::BaseJob::duration_ms_t inMilliseconds); + + //! \brief The job has been rate-limited + //! + //! The previous network request has been rate-limited; the next attempt will be queued and run + //! sometime later. Since other jobs may already wait in the queue, it's not possible to predict + //! the wait time. void rateLimited(); - /** - * Emitted when the job is finished, in any case. It is used to notify - * observers that the job is terminated and that progress can be hidden. - * - * This should not be emitted directly by subclasses; - * use finishJob() instead. - * - * In general, to be notified of a job's completion, client code - * should connect to result(), success(), or failure() - * rather than finished(). However if you need to track the job's - * lifecycle you should connect to this instead of result(); - * in particular, only this signal will be emitted on abandoning. - * - * @param job the job that emitted this signal - * - * @see result, success, failure - */ + //! \brief The job has finished - either with a result, or abandoned + //! + //! Emitted when the job is finished, in any case. It is used to notify + //! observers that the job is terminated and that progress can be hidden. + //! + //! This should not be emitted directly by subclasses; use finishJob() instead. + //! + //! In general, to be notified of a job's completion, client code should connect to result(), + //! success(), or failure() rather than finished(). However if you need to track the job's + //! lifecycle you should connect to this instead of result(); in particular, only this signal + //! will be emitted on abandoning, the others won't. + //! + //! \param job the job that emitted this signal + //! + //! \sa result, success, failure void finished(Quotient::BaseJob* job); - /** - * Emitted when the job is finished (except when abandoned). - * - * Use error() to know if the job was finished with error. - * - * @param job the job that emitted this signal - * - * @see success, failure - */ + //! \brief The job has finished with a result, successful or unsuccessful + //! + //! Use error() or status().good() to know if the job has finished successfully. + //! + //! \param job the job that emitted this signal + //! + //! \sa success, failure void result(Quotient::BaseJob* job); - /** - * Emitted together with result() in case there's no error. - * - * @see result, failure - */ + //! \brief The job has finished with a successful result + //! \sa result, failure void success(Quotient::BaseJob*); - /** - * Emitted together with result() if there's an error. - * Similar to result(), this won't be emitted in case of abandon(). - * - * @see result, success - */ + //! \brief The job has finished with a failure result + //! Emitted together with result() when the job resulted in an error. Mutually exclusive with + //! success(): after result() is emitted, exactly one of success() and failure() will be emitted + //! next. Will not be emitted in case of abandon()ing. + //! + //! \sa result, success void failure(Quotient::BaseJob*); void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); @@ -359,66 +330,75 @@ public Q_SLOTS: const QNetworkReply* reply() const; QNetworkReply* reply(); - /** Construct a URL out of baseUrl, path and query - * - * The function ensures exactly one '/' between the path component of - * \p baseUrl and \p path. The query component of \p baseUrl is ignored. - */ + //! \brief Construct a URL out of baseUrl, path and query + //! + //! The function ensures exactly one '/' between the path component of + //! \p baseUrl and \p path. The query component of \p baseUrl is ignored. + //! \note Unlike most of BaseJob, this function is thread-safe static QUrl makeRequestUrl(const HomeserverData& hsData, const QByteArray& encodedPath, const QUrlQuery& query = {}); - /*! Prepares the job for execution - * - * This method is called no more than once per job lifecycle, - * when it's first scheduled for execution; in particular, it is not called - * on retries. - */ + //! \brief Prepare the job for execution + //! + //! This method is called no more than once per job lifecycle, when it's first scheduled + //! for execution; in particular, it is not called on retries. virtual void doPrepare(const ConnectionData*); - /*! Postprocessing after the network request has been sent - * - * This method is called every time the job receives a running - * QNetworkReply object from NetworkAccessManager - basically, after - * successfully sending a network request (including retries). - */ + //! \brief Postprocessing after the network request has been sent + //! + //! This method is called every time the job receives a running + //! QNetworkReply object from NetworkAccessManager - basically, after + //! successfully sending a network request (including retries). virtual void onSentRequest(QNetworkReply*); + virtual void beforeAbandon(); - /*! \brief An extension point for additional reply processing. - * - * The base implementation does nothing and returns Success. - * - * \sa gotReply - */ + //! \brief Check the pending or received reply for upfront issues + //! + //! This is invoked when headers are first received and also once the complete reply is + //! obtained; the base implementation checks the HTTP headers to detect general issues such as + //! network errors or access denial and it's strongly recommended to call it from overrides, as + //! early as possible. + //! + //! This slot is const and cannot read the response body from the reply. If you need to read the + //! body on the fly, override onSentRequest() and connect in it to reply->readyRead(); and if + //! you only need to validate the body after it fully arrived, use prepareResult() for that. + //! Returning anything except NoError/Success switches further processing from prepareResult() + //! to prepareError(). + //! + //! \return the result of checking the reply + //! + //! \sa gotReply + virtual Status checkReply(const QNetworkReply* reply) const; + + //! \brief An extension point for additional reply processing + //! + //! The base implementation simply returns Success without doing anything else. + //! + //! \sa gotReply virtual Status prepareResult(); - /*! \brief Process details of the error - * - * The function processes the reply in case when status from checkReply() - * was not good (usually because of an unsuccessful HTTP code). - * The base implementation assumes Matrix JSON error object in the body; - * overrides are strongly recommended to call it for all stock Matrix - * responses as early as possible and only then process custom errors, - * with JSON or non-JSON payload. - * - * \return updated (if necessary) job status - */ + //! \brief Process details of the error + //! + //! The function processes the reply in case when status from checkReply() was not good (usually + //! because of an unsuccessful HTTP code). The base implementation assumes Matrix JSON error + //! object in the body; overrides are strongly recommended to call it for all stock Matrix + //! responses as early as possible and only then process custom errors, with JSON or non-JSON + //! payload. + //! + //! \return updated (if necessary) job status virtual Status prepareError(Status currentStatus); - /*! \brief Get direct access to the JSON response object in the job - * - * This allows to implement deserialisation with "move" semantics for parts - * of the response. Assuming that the response body is a valid JSON object, - * the function calls QJsonObject::take(key) on it and returns the result. - * - * \return QJsonValue::Null, if the response content type is not - * advertised as `application/json`; - * QJsonValue::Undefined, if the response is a JSON object but - * doesn't have \p key; - * the value for \p key otherwise. - * - * \sa takeFromJson - */ + //! \brief Retrieve a value for one specific key and delete it from the JSON response object + //! + //! This allows to implement deserialisation with "move" semantics for parts + //! of the response. Assuming that the response body is a valid JSON object, + //! the function calls QJsonObject::take(key) on it and returns the result. + //! + //! \return QJsonValue::Undefined if the response content is not a JSON object or it doesn't + //! have \p key; the value for \p key otherwise. + //! + //! \sa takeFromJson QJsonValue takeValueFromJson(const QString& key); void setStatus(Status s); @@ -443,26 +423,6 @@ public Q_SLOTS: protected Q_SLOTS: void timeout(); - /*! \brief Check the pending or received reply for upfront issues - * - * This is invoked when headers are first received and also once - * the complete reply is obtained; the base implementation checks the HTTP - * headers to detect general issues such as network errors or access denial - * and it's strongly recommended to call it from overrides, - * as early as possible. - * This slot is const and cannot read the response body. If you need to read - * the body on the fly, override onSentRequest() and connect in it - * to reply->readyRead(); and if you only need to validate the body after - * it fully arrived, use prepareResult() for that). Returning anything - * except NoError/Success switches further processing from prepareResult() - * to prepareError(). - * - * @return the result of checking the reply - * - * @see gotReply - */ - virtual Status checkReply(const QNetworkReply *reply) const; - private Q_SLOTS: void sendRequest(); void gotReply(); diff --git a/Quotient/keyverificationsession.cpp b/Quotient/keyverificationsession.cpp index 8d85c1f9a..ebead3af1 100644 --- a/Quotient/keyverificationsession.cpp +++ b/Quotient/keyverificationsession.cpp @@ -504,7 +504,7 @@ void KeyVerificationSession::trustKeys() m_connection->reloadDevices(); } - if (m_pendingMasterKey.length() > 0) { + if (!m_pendingMasterKey.isEmpty()) { if (m_remoteUserId == m_connection->userId()) { const auto selfSigningKey = m_connection->database()->loadEncrypted("m.cross_signing.self_signing"_ls); if (!selfSigningKey.isEmpty()) { diff --git a/Quotient/networkaccessmanager.cpp b/Quotient/networkaccessmanager.cpp index d413a1b5d..dbf3ba5de 100644 --- a/Quotient/networkaccessmanager.cpp +++ b/Quotient/networkaccessmanager.cpp @@ -47,8 +47,8 @@ class { const QWriteLocker _(&namLock); auto it = std::ranges::find(connectionData, accountId, &ConnectionData::accountId); - if (ALARM_X(it == connectionData.end(), "Quotient::NAM: Trying to save supported spec " - "versions on an inexistent account")) + if (QUO_ALARM_X(it == connectionData.end(), "Quotient::NAM: Trying to save supported spec " + "versions on an inexistent account")) return; it->hsData.supportedSpecVersions = std::move(versions); @@ -195,7 +195,7 @@ QStringList NetworkAccessManager::supportedSchemesImplementation() const << QStringLiteral("mxc"); } -void NetworkAccessManager::setAccessToken(const QString& userId, const QByteArray& accessToken) +void NetworkAccessManager::setAccessToken(const QString& userId, const QByteArray& token) { - d.setAccessToken(userId, accessToken); + d.setAccessToken(userId, token); } diff --git a/Quotient/room.cpp b/Quotient/room.cpp index 3a2d4996b..23a48ee2e 100644 --- a/Quotient/room.cpp +++ b/Quotient/room.cpp @@ -2001,7 +2001,7 @@ const PendingEventItem& Room::Private::doSendEvent(PendingEvents::iterator event void Room::Private::onEventReachedServer(PendingEvents::iterator eventItemIter, const QString& eventId) { - if (ALARM(eventItemIter == unsyncedEvents.end())) + if (QUO_ALARM(eventItemIter == unsyncedEvents.end())) return; if (eventItemIter->deliveryStatus() != EventStatus::ReachedServer) { diff --git a/Quotient/util.h b/Quotient/util.h index 45abb21f5..c6ae7036a 100644 --- a/Quotient/util.h +++ b/Quotient/util.h @@ -79,10 +79,13 @@ inline bool alarmX(bool alarmCondition, const auto& msg, //! words, \p Message is sent to logs (and, in Debug configuration, the assertion fails) //! if \p AlarmCondition holds, not the other way around. //! -//! This macro is a trivial wrapper around alarmX(), provided for API uniformity with ALARM() -#define ALARM_X(AlarmCondition, Message) alarmX((AlarmCondition), (Message)) +//! This macro is a trivial wrapper around alarmX(), provided for API uniformity with QUO_ALARM() +#define QUO_ALARM_X(...) alarmX(__VA_ARGS__) -#define ALARM(AlarmCondition) alarmX((AlarmCondition), "Alarm: " #AlarmCondition) +#define QUO_ALARM(...) alarmX((__VA_ARGS__) ? true : false, "Alarm: " #__VA_ARGS__) + +//! Evaluate the boolean expression and, in Debug mode, assert it to be true +#define QUO_CHECK(...) !alarmX(!(__VA_ARGS__) ? true : false, "Failing expression: " #__VA_ARGS__) #if Quotient_VERSION_MAJOR == 0 && Quotient_VERSION_MINOR < 10 /// This is only to make UnorderedMap alias work until we get rid of it diff --git a/autotests/callcandidateseventtest.cpp b/autotests/callcandidateseventtest.cpp index 4b170ba48..453983425 100644 --- a/autotests/callcandidateseventtest.cpp +++ b/autotests/callcandidateseventtest.cpp @@ -47,7 +47,7 @@ void TestCallCandidatesEvent::fromJson() QCOMPARE(callCandidatesEvent->version(), 0); QCOMPARE(callCandidatesEvent->callId(), QStringLiteral("12345")); - QCOMPARE(callCandidatesEvent->candidates().count(), 1); + QCOMPARE(callCandidatesEvent->candidates().size(), 1); const auto& candidate = callCandidatesEvent->candidates().at(0).toObject(); QCOMPARE(candidate.value("sdpMid"_ls).toString(), QStringLiteral("audio")); diff --git a/autotests/testolmaccount.cpp b/autotests/testolmaccount.cpp index dd60cee09..6ef6361e6 100644 --- a/autotests/testolmaccount.cpp +++ b/autotests/testolmaccount.cpp @@ -75,7 +75,7 @@ void TestOlmAccount::oneTimeKeysValid() olmAccount.generateOneTimeKeys(20); const auto oneTimeKeysFilled = olmAccount.oneTimeKeys(); - QCOMPARE(20, oneTimeKeysFilled.curve25519().count()); + QCOMPARE(20, oneTimeKeysFilled.curve25519().size()); } void TestOlmAccount::deviceKeys() @@ -167,7 +167,7 @@ void TestOlmAccount::encryptedFile() QCOMPARE(file.key.alg, "A256CTR"_ls); QCOMPARE(file.key.ext, true); QCOMPARE(file.key.k, "aWF6-32KGYaC3A_FEUCk1Bt0JA37zP0wrStgmdCaW-0"_ls); - QCOMPARE(file.key.keyOps.count(), 2); + QCOMPARE(file.key.keyOps.size(), 2); QCOMPARE(file.key.kty, "oct"_ls); } diff --git a/autotests/testolmutility.cpp b/autotests/testolmutility.cpp index 69cb9c38b..d0c5924b3 100644 --- a/autotests/testolmutility.cpp +++ b/autotests/testolmutility.cpp @@ -64,7 +64,7 @@ void TestOlmUtility::verifySignedOneTimeKey() auto utility = olm_utility(utilityBuf); - QByteArray signatureBuf1(sig.length(), '\0'); + QByteArray signatureBuf1(sig.size(), '\0'); std::copy(sig.begin(), sig.end(), signatureBuf1.begin()); auto res = diff --git a/quotest/quotest.cpp b/quotest/quotest.cpp index facee88a4..0ba5481f3 100644 --- a/quotest/quotest.cpp +++ b/quotest/quotest.cpp @@ -645,13 +645,13 @@ TEST_IMPL(addAndRemoveTag) // of the tag change. const QSignalSpy spy(targetRoom, &Room::tagsChanged); targetRoom->addTag(TestTag); - if (spy.count() != 1 || !targetRoom->tags().contains(TestTag)) { + if (spy.size() != 1 || !targetRoom->tags().contains(TestTag)) { clog << "Tag adding failed" << endl; FAIL_TEST(); } clog << "Test tag set, removing it now" << endl; targetRoom->removeTag(TestTag); - FINISH_TEST(spy.count() == 2 && !targetRoom->tags().contains(TestTag)); + FINISH_TEST(spy.size() == 2 && !targetRoom->tags().contains(TestTag)); } bool TestSuite::checkDirectChat() const @@ -673,7 +673,7 @@ TEST_IMPL(markDirectChat) const QSignalSpy spy(connection(), &Connection::directChatsListChanged); clog << "Marking the room as a direct chat" << endl; connection()->addToDirectChats(targetRoom, connection()->user()->id()); - if (spy.count() != 1 || !checkDirectChat()) + if (spy.size() != 1 || !checkDirectChat()) FAIL_TEST(); // Check that the first argument (added DCs) actually contains the room @@ -686,7 +686,7 @@ TEST_IMPL(markDirectChat) clog << "Unmarking the direct chat" << endl; connection()->removeFromDirectChats(targetRoom->id(), connection()->user()->id()); - if (spy.count() != 2 && checkDirectChat()) + if (spy.size() != 2 && checkDirectChat()) FAIL_TEST(); // Check that the second argument (removed DCs) actually contains the room @@ -729,13 +729,13 @@ TEST_IMPL(visitResources) FAIL_TEST(); } ud.visitResource(connection(), uriString); - if (spy.count() != 1) { - clog << "Wrong number of signal emissions (" << spy.count() + if (spy.size() != 1) { + clog << "Wrong number of signal emissions (" << spy.size() << ')' << endl; FAIL_TEST(); } const auto& emission = spy.front(); - Q_ASSERT(emission.count() >= 2); + Q_ASSERT(emission.size() >= 2); if (emission.front().value() != target) { clog << "Signal emitted with an incorrect target" << endl; FAIL_TEST();