service/ImapCommandTable.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "ImapCommandTable.h" | ||
| 2 | |||
| 3 | #include <QLoggingCategory> | ||
| 4 | #include <QSet> | ||
| 5 | #include <QSslSocket> | ||
| 6 | #include <QTimer> | ||
| 7 | |||
| 8 | Q_DECLARE_LOGGING_CATEGORY(lcImap) | ||
| 9 | |||
| 10 | 1401 | ImapCommandTable::ImapCommandTable(QObject *owner, QSslSocket *socket, | |
| 11 | 1401 | const QString &selectedFolder, Deps deps) | |
| 12 | 1401 | : m_socket(socket), m_selectedFolder(selectedFolder), m_deps(std::move(deps)), | |
| 13 |
3/6✓ Branch 4 → 5 taken 1401 times.
✓ Branch 5 → 6 taken 1401 times.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 1401 times.
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 18 not taken.
|
1401 | m_commandTimeoutTimer(new QTimer(owner)), |
| 14 |
3/6✓ Branch 8 → 9 taken 1401 times.
✓ Branch 9 → 10 taken 1401 times.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 1401 times.
✗ Branch 19 → 20 not taken.
✗ Branch 19 → 21 not taken.
|
1401 | m_commandDeadlineTimer(new QTimer(owner)) {} |
| 15 | |||
| 16 | 1274 | void ImapCommandTable::executeAfterIdle(std::function<void()> command) { | |
| 17 |
2/2✓ Branch 3 → 4 taken 4 times.
✓ Branch 3 → 5 taken 1270 times.
|
1274 | if (m_deps.isNotifying()) { |
| 18 | // T-320: NOTIFY doesn't block — execute immediately, no stop needed | ||
| 19 | 4 | command(); | |
| 20 |
2/2✓ Branch 6 → 7 taken 12 times.
✓ Branch 6 → 10 taken 1258 times.
|
1270 | } else if (m_deps.isIdling()) { |
| 21 | // Queue command and stop IDLE – command runs on IDLE OK | ||
| 22 | 12 | m_deferredCommands.enqueue(std::move(command)); | |
| 23 | 12 | m_deps.stopIdle(); | |
| 24 | } else { | ||
| 25 | // Not idling/notifying – execute immediately | ||
| 26 | 1258 | command(); | |
| 27 | } | ||
| 28 | 1274 | } | |
| 29 | |||
| 30 | 31 | void ImapCommandTable::runNextDeferredCommand() { | |
| 31 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 31 times.
|
31 | if (m_deferredCommands.isEmpty()) return; |
| 32 | // A deferred command waits for IDLE to end. A completion observer of the | ||
| 33 | // tagged response being handled may already have started IDLE again (the | ||
| 34 | // settings sync starts its IDLE watch on SELECT, then queues an APPEND); | ||
| 35 | // running the command now would send it inside IDLE and fail ("Cannot | ||
| 36 | // APPEND: not authenticated"). The IDLE tagged OK drains the queue. | ||
| 37 |
2/3✓ Branch 5 → 6 taken 31 times.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 21 taken 31 times.
|
31 | if (m_deps.isIdling()) { |
| 38 | ✗ | qCInfo(lcImap) << "Deferred command waits for the IDLE response (" | |
| 39 | ✗ | << m_deferredCommands.size() << "queued)"; | |
| 40 | ✗ | return; | |
| 41 | } | ||
| 42 |
1/1✓ Branch 21 → 22 taken 31 times.
|
31 | auto command = m_deferredCommands.dequeue(); |
| 43 |
1/1✓ Branch 22 → 23 taken 31 times.
|
31 | command(); |
| 44 | 31 | } | |
| 45 | |||
| 46 | 28 | void ImapCommandTable::clearDeferredCommands() { | |
| 47 |
2/2✓ Branch 3 → 4 taken 3 times.
✓ Branch 3 → 18 taken 25 times.
|
28 | if (!m_deferredCommands.isEmpty()) { |
| 48 |
6/6✓ Branch 4 → 5 taken 3 times.
✓ Branch 8 → 9 taken 3 times.
✓ Branch 9 → 10 taken 3 times.
✓ Branch 11 → 12 taken 3 times.
✓ Branch 16 → 6 taken 3 times.
✓ Branch 16 → 17 taken 3 times.
|
6 | qCInfo(lcImap) << "Clearing" << m_deferredCommands.size() |
| 49 |
1/1✓ Branch 12 → 13 taken 3 times.
|
3 | << "deferred commands (folder switch)"; |
| 50 | 3 | m_deferredCommands.clear(); | |
| 51 | } | ||
| 52 | 28 | } | |
| 53 | |||
| 54 | 23 | QString ImapCommandTable::sendTaggedCommand(const QString &type, | |
| 55 | const QString &command, | ||
| 56 | ImapPendingCommand record) { | ||
| 57 | // Thin wrapper around sendCommand() that exposes the generated tag, so | ||
| 58 | // the probe can match its response in handleTagged(). | ||
| 59 | 23 | QString tag; | |
| 60 | // Inline copy of sendCommand() but return tag (cannot refactor callers | ||
| 61 | // without disturbing the existing command-timeout/reflow behaviour). | ||
| 62 | const bool startsCommandBudget = | ||
| 63 |
2/12✓ Branch 6 → 7 taken 23 times.
✗ Branch 6 → 10 not taken.
✓ Branch 13 → 14 taken 23 times.
✗ Branch 13 → 15 not taken.
✗ Branch 51 → 52 not taken.
✗ Branch 51 → 53 not taken.
✗ Branch 55 → 56 not taken.
✗ Branch 55 → 57 not taken.
✗ Branch 62 → 63 not taken.
✗ Branch 62 → 64 not taken.
✗ Branch 66 → 67 not taken.
✗ Branch 66 → 68 not taken.
|
92 | type != QStringLiteral("IDLE") && |
| 64 |
4/5✓ Branch 7 → 8 taken 23 times.
✓ Branch 8 → 9 taken 21 times.
✓ Branch 8 → 10 taken 2 times.
✓ Branch 11 → 12 taken 23 times.
✗ Branch 11 → 13 not taken.
|
46 | !hasTimeoutTrackedCommandInFlight(); |
| 65 |
2/2✓ Branch 15 → 16 taken 21 times.
✓ Branch 15 → 17 taken 2 times.
|
23 | if (startsCommandBudget) |
| 66 | 21 | resetCommandAccumulators(); | |
| 67 | |||
| 68 |
1/1✓ Branch 17 → 18 taken 23 times.
|
23 | tag = nextTag(); |
| 69 | 23 | record.type = type; | |
| 70 | 23 | QElapsedTimer timer; | |
| 71 | 23 | timer.start(); | |
| 72 | 23 | record.timer = timer; | |
| 73 |
1/1✓ Branch 24 → 25 taken 23 times.
|
23 | [[maybe_unused]] const auto inserted = m_pending.insert(tag, std::move(record)); |
| 74 |
3/3✓ Branch 25 → 26 taken 23 times.
✓ Branch 26 → 27 taken 23 times.
✓ Branch 27 → 28 taken 23 times.
|
23 | auto fullCommand = tag + " " + command + "\r\n"; |
| 75 |
7/7✓ Branch 30 → 31 taken 23 times.
✓ Branch 34 → 35 taken 23 times.
✓ Branch 35 → 36 taken 23 times.
✓ Branch 36 → 37 taken 23 times.
✓ Branch 37 → 38 taken 23 times.
✓ Branch 42 → 32 taken 23 times.
✓ Branch 42 → 43 taken 23 times.
|
46 | qCDebug(lcImap) << ">>>" << fullCommand.trimmed(); |
| 76 | #ifdef MAILJD_E2E_TESTING | ||
| 77 |
3/3✓ Branch 44 → 45 taken 19 times.
✓ Branch 49 → 50 taken 19 times.
✓ Branch 50 → 51 taken 19 times.
|
57 | m_deps.reportE2eTransportPhase(QStringLiteral("command-write:%1").arg(type), |
| 78 | 19 | m_deps.e2eRequestTokenFor(*inserted)); | |
| 79 | #endif | ||
| 80 |
4/4✓ Branch 43 → 44 taken 4 times.
✓ Branch 44 → 45 taken 4 times.
✓ Branch 54 → 55 taken 19 times.
✓ Branch 55 → 56 taken 19 times.
|
23 | m_socket->write(fullCommand.toUtf8()); |
| 81 |
2/2✓ Branch 46 → 47 taken 4 times.
✓ Branch 57 → 58 taken 19 times.
|
23 | refreshCommandTimeout(); |
| 82 |
2/2✓ Branch 47 → 48 taken 4 times.
✓ Branch 58 → 59 taken 19 times.
|
23 | refreshCommandDeadline(); |
| 83 | 23 | return tag; | |
| 84 | 23 | } | |
| 85 | |||
| 86 | 1458 | bool ImapCommandTable::hasStatefulCommandInFlight() const { | |
| 87 | static const QSet<QString> statefulTypes = { | ||
| 88 | 14 | QStringLiteral("LIST"), QStringLiteral("SELECT"), | |
| 89 | 14 | QStringLiteral("FETCH_HEADERS"), QStringLiteral("FETCH_BODY"), | |
| 90 | 14 | QStringLiteral("FETCH_FLAGS"), QStringLiteral("FETCH_SEQNO"), | |
| 91 | 14 | QStringLiteral("FETCH_FLAGS_CHANGED"), QStringLiteral("SEARCH_MEMBERSHIP"), | |
| 92 | 14 | QStringLiteral("SEARCH"), QStringLiteral("STATUS"), | |
| 93 | 14 | QStringLiteral("STORE"), QStringLiteral("MOVE"), | |
| 94 | 14 | QStringLiteral("COPY"), QStringLiteral("STORE_DELETE"), | |
| 95 | 14 | QStringLiteral("EXPUNGE_MOVE"), QStringLiteral("COPY_ONLY"), | |
| 96 | 14 | QStringLiteral("EXPUNGE"), QStringLiteral("APPEND"), | |
| 97 | 14 | QStringLiteral("CREATE"), QStringLiteral("DELETE"), | |
| 98 |
5/10✓ Branch 2 → 3 taken 14 times.
✓ Branch 2 → 100 taken 1444 times.
✓ Branch 4 → 5 taken 14 times.
✗ Branch 4 → 100 not taken.
✓ Branch 75 → 76 taken 308 times.
✓ Branch 75 → 77 taken 14 times.
✗ Branch 113 → 114 not taken.
✗ Branch 113 → 115 not taken.
✗ Branch 188 → 189 not taken.
✗ Branch 188 → 190 not taken.
|
1794 | QStringLiteral("RENAME"), QStringLiteral("NOOP")}; |
| 99 | |||
| 100 |
4/4✓ Branch 100 → 101 taken 1458 times.
✓ Branch 101 → 102 taken 1458 times.
✓ Branch 109 → 103 taken 498 times.
✓ Branch 109 → 110 taken 1024 times.
|
1522 | for (const ImapPendingCommand &record : m_pending) { |
| 101 |
2/2✓ Branch 105 → 106 taken 434 times.
✓ Branch 105 → 107 taken 64 times.
|
498 | if (statefulTypes.contains(record.type)) |
| 102 | 434 | return true; | |
| 103 | } | ||
| 104 | 1024 | return false; | |
| 105 | 322 | } | |
| 106 | |||
| 107 | 108974 | bool ImapCommandTable::hasTimeoutTrackedCommandInFlight() const { | |
| 108 |
4/4✓ Branch 2 → 3 taken 108974 times.
✓ Branch 3 → 4 taken 108974 times.
✓ Branch 16 → 5 taken 7046 times.
✓ Branch 16 → 17 taken 101944 times.
|
108990 | for (const ImapPendingCommand &record : m_pending) { |
| 109 |
2/2✓ Branch 12 → 13 taken 7030 times.
✓ Branch 12 → 14 taken 16 times.
|
7046 | if (record.type != QStringLiteral("IDLE")) |
| 110 | 7030 | return true; | |
| 111 | } | ||
| 112 | 101944 | return false; | |
| 113 | } | ||
| 114 | |||
| 115 | // A queued UID command must run in the mailbox that was selected when it was | ||
| 116 | // issued. A SELECT queued ahead of it would otherwise retarget the UID: the | ||
| 117 | // server answers OK for a UID STORE/MOVE of a UID that does not exist there | ||
| 118 | // (or acts on a different message that happens to share the UID). Journal | ||
| 119 | // tokens are rejected as "Mailbox changed before dispatch"; the controller | ||
| 120 | // requeues them without a user-visible notice. | ||
| 121 | 15 | bool ImapCommandTable::queuedMailboxStillSelected(const QString &issuedFolder, | |
| 122 | const QList<qint64> &mutationTokens, | ||
| 123 | const char *command) { | ||
| 124 |
2/2✓ Branch 3 → 4 taken 11 times.
✓ Branch 3 → 5 taken 4 times.
|
15 | if (m_selectedFolder == issuedFolder) |
| 125 | 11 | return true; | |
| 126 |
6/6✓ Branch 5 → 6 taken 4 times.
✓ Branch 9 → 10 taken 4 times.
✓ Branch 10 → 11 taken 4 times.
✓ Branch 11 → 12 taken 4 times.
✓ Branch 19 → 7 taken 4 times.
✓ Branch 19 → 20 taken 4 times.
|
8 | qCWarning(lcImap) << "Dropping queued" << command |
| 127 |
1/1✓ Branch 12 → 13 taken 4 times.
|
4 | << "because the selected mailbox changed from" |
| 128 |
3/3✓ Branch 13 → 14 taken 4 times.
✓ Branch 14 → 15 taken 4 times.
✓ Branch 15 → 16 taken 4 times.
|
4 | << issuedFolder << "to" << m_selectedFolder; |
| 129 |
2/2✓ Branch 31 → 22 taken 4 times.
✓ Branch 31 → 32 taken 4 times.
|
8 | for (qint64 token : mutationTokens) |
| 130 |
1/1✓ Branch 26 → 27 taken 4 times.
|
8 | m_deps.completeMutation(token, ImapService::MutationOutcome::Rejected, |
| 131 | 8 | QStringLiteral("Mailbox changed before dispatch")); | |
| 132 | 4 | return false; | |
| 133 | } | ||
| 134 | |||
| 135 | 105 | void ImapCommandTable::enqueueSerializedCommand(std::function<void()> command) { | |
| 136 | 105 | m_serializedCommands.enqueue(std::move(command)); | |
| 137 |
5/5✓ Branch 4 → 5 taken 105 times.
✓ Branch 8 → 9 taken 105 times.
✓ Branch 9 → 10 taken 105 times.
✓ Branch 15 → 6 taken 105 times.
✓ Branch 15 → 16 taken 105 times.
|
210 | qCDebug(lcImap) << "Queued stateful IMAP command; queue size:" |
| 138 |
1/1✓ Branch 11 → 12 taken 105 times.
|
105 | << m_serializedCommands.size(); |
| 139 | 105 | } | |
| 140 | |||
| 141 | 482 | void ImapCommandTable::runNextSerializedCommand() { | |
| 142 |
6/6✓ Branch 18 → 19 taken 366 times.
✓ Branch 18 → 22 taken 154 times.
✓ Branch 20 → 21 taken 38 times.
✓ Branch 20 → 22 taken 328 times.
✓ Branch 23 → 3 taken 38 times.
✓ Branch 23 → 24 taken 482 times.
|
520 | while (!hasStatefulCommandInFlight() && !m_serializedCommands.isEmpty()) { |
| 143 |
1/1✓ Branch 3 → 4 taken 38 times.
|
38 | auto command = m_serializedCommands.dequeue(); |
| 144 |
1/1✓ Branch 6 → 7 taken 38 times.
|
38 | executeAfterIdle(std::move(command)); |
| 145 |
2/3✓ Branch 8 → 9 taken 38 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 38 times.
|
38 | if (m_deps.isIdling()) |
| 146 | ✗ | return; | |
| 147 | 38 | } | |
| 148 |
3/4✓ Branch 27 → 28 taken 2 times.
✓ Branch 27 → 33 taken 2 times.
✓ Branch 29 → 30 taken 2 times.
✗ Branch 29 → 33 not taken.
|
488 | if (m_deps.idleStartPending() && !hasStatefulCommandInFlight() && |
| 149 |
5/6✓ Branch 25 → 26 taken 4 times.
✓ Branch 25 → 33 taken 478 times.
✓ Branch 31 → 32 taken 2 times.
✗ Branch 31 → 33 not taken.
✓ Branch 34 → 35 taken 2 times.
✓ Branch 34 → 36 taken 480 times.
|
488 | m_serializedCommands.isEmpty() && m_deferredCommands.isEmpty()) |
| 150 | 2 | m_deps.startIdle(); | |
| 151 | } | ||
| 152 | |||
| 153 | 3922 | void ImapCommandTable::refreshCommandTimeout() { | |
| 154 |
2/2✓ Branch 3 → 4 taken 516 times.
✓ Branch 3 → 6 taken 3406 times.
|
3922 | if (!hasTimeoutTrackedCommandInFlight()) { |
| 155 | 516 | m_commandTimeoutTimer->stop(); | |
| 156 | 516 | return; | |
| 157 | } | ||
| 158 | 3406 | m_commandTimeoutTimer->start(COMMAND_TIMEOUT_MS); | |
| 159 | } | ||
| 160 | |||
| 161 | 953 | void ImapCommandTable::refreshCommandDeadline() { | |
| 162 |
3/3✓ Branch 2 → 3 taken 953 times.
✓ Branch 3 → 4 taken 456 times.
✓ Branch 3 → 6 taken 497 times.
|
953 | if (!hasTimeoutTrackedCommandInFlight()) { |
| 163 |
1/1✓ Branch 4 → 5 taken 456 times.
|
456 | m_commandDeadlineTimer->stop(); |
| 164 | 456 | return; | |
| 165 | } | ||
| 166 | // Absolute deadline: incoming progress may cause this helper to be called, | ||
| 167 | // but it can only shorten the remaining timer. It never grants a fresh | ||
| 168 | // window to a slow-drip server. | ||
| 169 | 497 | qint64 shortestRemaining = COMMAND_DEADLINE_MS; | |
| 170 |
4/4✓ Branch 6 → 7 taken 497 times.
✓ Branch 7 → 8 taken 497 times.
✓ Branch 33 → 9 taken 534 times.
✓ Branch 33 → 34 taken 497 times.
|
1031 | for (const ImapPendingCommand &record : m_pending) { |
| 171 |
1/2✗ Branch 16 → 17 not taken.
✓ Branch 16 → 18 taken 534 times.
|
534 | if (record.type == QStringLiteral("IDLE")) |
| 172 | ✗ | continue; | |
| 173 |
5/6✓ Branch 19 → 20 taken 524 times.
✓ Branch 19 → 23 taken 10 times.
✗ Branch 22 → 23 not taken.
✓ Branch 22 → 24 taken 524 times.
✓ Branch 25 → 26 taken 10 times.
✓ Branch 25 → 27 taken 524 times.
|
534 | if (!record.timer || !record.timer->isValid()) |
| 174 | 10 | continue; | |
| 175 | 524 | shortestRemaining = | |
| 176 | 1048 | qMin(shortestRemaining, | |
| 177 | 524 | static_cast<qint64>(COMMAND_DEADLINE_MS) - record.timer->elapsed()); | |
| 178 | } | ||
| 179 | 994 | m_commandDeadlineTimer->start( | |
| 180 |
1/1✓ Branch 35 → 36 taken 497 times.
|
497 | static_cast<int>(qBound<qint64>(qint64(1), shortestRemaining, |
| 181 | 994 | qint64(COMMAND_DEADLINE_MS)))); | |
| 182 | } | ||
| 183 | |||
| 184 | 3388 | void ImapCommandTable::resetCommandAccumulators() { | |
| 185 | 3388 | m_commandResponseBytes = 0; | |
| 186 | 3388 | m_commandLiteralCount = 0; | |
| 187 | 3388 | m_commandResultItems = 0; | |
| 188 | 3388 | } | |
| 189 | |||
| 190 | 2965 | bool ImapCommandTable::accountCommandResponseBytes(qint64 bytes) { | |
| 191 |
6/6✓ Branch 2 → 3 taken 2935 times.
✓ Branch 2 → 5 taken 30 times.
✓ Branch 4 → 5 taken 60 times.
✓ Branch 4 → 6 taken 2875 times.
✓ Branch 7 → 8 taken 90 times.
✓ Branch 7 → 9 taken 2875 times.
|
2965 | if (bytes <= 0 || !hasTimeoutTrackedCommandInFlight()) |
| 192 | 90 | return true; | |
| 193 |
2/2✓ Branch 9 → 10 taken 1 time.
✓ Branch 9 → 17 taken 2874 times.
|
2875 | if (bytes > ImapService::MAX_COMMAND_RESPONSE_SIZE - m_commandResponseBytes) { |
| 194 |
1/1✓ Branch 13 → 14 taken 1 time.
|
2 | m_deps.failConnection(QStringLiteral("IMAP command response byte budget exceeded"), |
| 195 | ImapService::DisconnectReason::ProtocolError); | ||
| 196 | 1 | return false; | |
| 197 | } | ||
| 198 | 2874 | m_commandResponseBytes += bytes; | |
| 199 | 2874 | return true; | |
| 200 | } | ||
| 201 | |||
| 202 | 100045 | bool ImapCommandTable::accountCommandLiteral() { | |
| 203 |
2/2✓ Branch 3 → 4 taken 100003 times.
✓ Branch 3 → 5 taken 42 times.
|
100045 | if (!hasTimeoutTrackedCommandInFlight()) |
| 204 | 100003 | return true; | |
| 205 |
2/2✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 13 taken 41 times.
|
42 | if (m_commandLiteralCount >= ImapService::MAX_LITERALS_PER_COMMAND) { |
| 206 |
1/1✓ Branch 9 → 10 taken 1 time.
|
2 | m_deps.failConnection(QStringLiteral("IMAP command literal budget exceeded"), |
| 207 | ImapService::DisconnectReason::ProtocolError); | ||
| 208 | 1 | return false; | |
| 209 | } | ||
| 210 | 41 | ++m_commandLiteralCount; | |
| 211 | 41 | return true; | |
| 212 | } | ||
| 213 | |||
| 214 | 160 | bool ImapCommandTable::accountCommandResultItems(qint64 items) { | |
| 215 |
3/6✓ Branch 2 → 3 taken 160 times.
✗ Branch 2 → 5 not taken.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 160 times.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 160 times.
|
160 | if (items <= 0 || !hasTimeoutTrackedCommandInFlight()) |
| 216 | ✗ | return true; | |
| 217 |
2/2✓ Branch 9 → 10 taken 1 time.
✓ Branch 9 → 17 taken 159 times.
|
160 | if (items > ImapService::MAX_RESULT_ITEMS_PER_COMMAND - m_commandResultItems) { |
| 218 |
1/1✓ Branch 13 → 14 taken 1 time.
|
2 | m_deps.failConnection(QStringLiteral("IMAP command result item budget exceeded"), |
| 219 | ImapService::DisconnectReason::ProtocolError); | ||
| 220 | 1 | return false; | |
| 221 | } | ||
| 222 | 159 | m_commandResultItems += items; | |
| 223 | 159 | return true; | |
| 224 | } | ||
| 225 | |||
| 226 | 451 | void ImapCommandTable::sendCommand(const QString &type, const QString &command, | |
| 227 | ImapPendingCommand record) { | ||
| 228 | const bool startsCommandBudget = | ||
| 229 |
2/12✓ Branch 6 → 7 taken 451 times.
✗ Branch 6 → 10 not taken.
✓ Branch 13 → 14 taken 451 times.
✗ Branch 13 → 15 not taken.
✗ Branch 70 → 71 not taken.
✗ Branch 70 → 72 not taken.
✗ Branch 74 → 75 not taken.
✗ Branch 74 → 76 not taken.
✗ Branch 81 → 82 not taken.
✗ Branch 81 → 83 not taken.
✗ Branch 85 → 86 not taken.
✗ Branch 85 → 87 not taken.
|
1804 | type != QStringLiteral("IDLE") && |
| 230 |
4/5✓ Branch 7 → 8 taken 451 times.
✓ Branch 8 → 9 taken 429 times.
✓ Branch 8 → 10 taken 22 times.
✓ Branch 11 → 12 taken 451 times.
✗ Branch 11 → 13 not taken.
|
902 | !hasTimeoutTrackedCommandInFlight(); |
| 231 |
2/2✓ Branch 15 → 16 taken 429 times.
✓ Branch 15 → 17 taken 22 times.
|
451 | if (startsCommandBudget) |
| 232 | 429 | resetCommandAccumulators(); | |
| 233 | |||
| 234 |
1/1✓ Branch 17 → 18 taken 451 times.
|
451 | auto tag = nextTag(); |
| 235 | 451 | record.type = type; | |
| 236 | |||
| 237 | // T-210: Start timing for this command | ||
| 238 | 451 | QElapsedTimer timer; | |
| 239 | 451 | timer.start(); | |
| 240 | 451 | record.timer = timer; | |
| 241 |
1/1✓ Branch 22 → 23 taken 451 times.
|
451 | [[maybe_unused]] const auto inserted = m_pending.insert(tag, std::move(record)); |
| 242 | |||
| 243 |
3/3✓ Branch 23 → 24 taken 451 times.
✓ Branch 24 → 25 taken 451 times.
✓ Branch 25 → 26 taken 451 times.
|
451 | auto fullCommand = tag + " " + command + "\r\n"; |
| 244 | // T-400/Bug 6: Mask credentials in LOGIN commands | ||
| 245 |
2/2✓ Branch 34 → 35 taken 49 times.
✓ Branch 34 → 48 taken 402 times.
|
451 | if (type == QStringLiteral("LOGIN")) { |
| 246 |
7/7✓ Branch 35 → 36 taken 49 times.
✓ Branch 39 → 40 taken 49 times.
✓ Branch 40 → 41 taken 49 times.
✓ Branch 41 → 42 taken 49 times.
✓ Branch 42 → 43 taken 49 times.
✓ Branch 46 → 37 taken 49 times.
✓ Branch 46 → 47 taken 49 times.
|
98 | qCDebug(lcImap) << ">>>" << tag << "LOGIN <user> <***>"; |
| 247 | } else { | ||
| 248 |
7/7✓ Branch 48 → 49 taken 402 times.
✓ Branch 52 → 53 taken 402 times.
✓ Branch 53 → 54 taken 402 times.
✓ Branch 54 → 55 taken 402 times.
✓ Branch 55 → 56 taken 402 times.
✓ Branch 60 → 50 taken 402 times.
✓ Branch 60 → 61 taken 402 times.
|
804 | qCDebug(lcImap) << ">>>" << fullCommand.trimmed(); |
| 249 | } | ||
| 250 | #ifdef MAILJD_E2E_TESTING | ||
| 251 |
3/3✓ Branch 63 → 64 taken 374 times.
✓ Branch 68 → 69 taken 374 times.
✓ Branch 69 → 70 taken 374 times.
|
1122 | m_deps.reportE2eTransportPhase(QStringLiteral("command-write:%1").arg(type), |
| 252 | 374 | m_deps.e2eRequestTokenFor(*inserted)); | |
| 253 | #endif | ||
| 254 |
4/4✓ Branch 62 → 63 taken 77 times.
✓ Branch 63 → 64 taken 77 times.
✓ Branch 73 → 74 taken 374 times.
✓ Branch 74 → 75 taken 374 times.
|
451 | m_socket->write(fullCommand.toUtf8()); |
| 255 |
2/2✓ Branch 65 → 66 taken 77 times.
✓ Branch 76 → 77 taken 374 times.
|
451 | refreshCommandTimeout(); |
| 256 |
2/2✓ Branch 66 → 67 taken 77 times.
✓ Branch 77 → 78 taken 374 times.
|
451 | refreshCommandDeadline(); |
| 257 | 451 | } | |
| 258 | |||
| 259 | 544 | QString ImapCommandTable::nextTag() { | |
| 260 |
2/2✓ Branch 2 → 3 taken 544 times.
✓ Branch 4 → 5 taken 544 times.
|
544 | return QString("A%1").arg(++m_tagCounter, 3, 10, QChar('0')); |
| 261 | } | ||
| 262 | |||
| 263 | 2 | void ImapCommandTable::onCommandTimeout() { | |
| 264 |
2/3✓ Branch 2 → 3 taken 2 times.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 2 times.
|
2 | if (!hasTimeoutTrackedCommandInFlight()) { |
| 265 | ✗ | m_commandTimeoutTimer->stop(); | |
| 266 | ✗ | return; | |
| 267 | } | ||
| 268 | |||
| 269 | 2 | QStringList pending; | |
| 270 |
4/4✓ Branch 6 → 7 taken 2 times.
✓ Branch 28 → 29 taken 4 times.
✓ Branch 30 → 8 taken 2 times.
✓ Branch 30 → 31 taken 2 times.
|
4 | for (auto it = m_pending.cbegin(); it != m_pending.cend(); ++it) { |
| 271 |
1/2✓ Branch 15 → 16 taken 2 times.
✗ Branch 15 → 27 not taken.
|
2 | if (it->type != QStringLiteral("IDLE")) |
| 272 |
2/2✓ Branch 21 → 22 taken 2 times.
✓ Branch 22 → 23 taken 2 times.
|
2 | pending.append(QStringLiteral("%1:%2").arg(it.key(), it->type)); |
| 273 | } | ||
| 274 | |||
| 275 |
1/1✓ Branch 38 → 39 taken 2 times.
|
4 | m_deps.failConnection( |
| 276 | 4 | QStringLiteral("IMAP command timeout (inactivity): %1") | |
| 277 |
2/2✓ Branch 36 → 37 taken 2 times.
✓ Branch 37 → 38 taken 2 times.
|
4 | .arg(pending.join(',')), |
| 278 | ImapService::DisconnectReason::CommandTimeout); | ||
| 279 | 2 | } | |
| 280 | |||
| 281 | 2 | void ImapCommandTable::onCommandDeadline() { | |
| 282 |
2/3✓ Branch 2 → 3 taken 2 times.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 2 times.
|
2 | if (!hasTimeoutTrackedCommandInFlight()) { |
| 283 | ✗ | m_commandDeadlineTimer->stop(); | |
| 284 | ✗ | return; | |
| 285 | } | ||
| 286 | |||
| 287 | 2 | QStringList pending; | |
| 288 |
4/4✓ Branch 6 → 7 taken 2 times.
✓ Branch 28 → 29 taken 4 times.
✓ Branch 30 → 8 taken 2 times.
✓ Branch 30 → 31 taken 2 times.
|
4 | for (auto it = m_pending.cbegin(); it != m_pending.cend(); ++it) { |
| 289 |
1/2✓ Branch 15 → 16 taken 2 times.
✗ Branch 15 → 27 not taken.
|
2 | if (it->type != QStringLiteral("IDLE")) |
| 290 |
2/2✓ Branch 21 → 22 taken 2 times.
✓ Branch 22 → 23 taken 2 times.
|
2 | pending.append(QStringLiteral("%1:%2").arg(it.key(), it->type)); |
| 291 | } | ||
| 292 | |||
| 293 |
1/1✓ Branch 38 → 39 taken 2 times.
|
4 | m_deps.failConnection( |
| 294 | 4 | QStringLiteral("IMAP command absolute deadline exceeded: %1") | |
| 295 |
2/2✓ Branch 36 → 37 taken 2 times.
✓ Branch 37 → 38 taken 2 times.
|
4 | .arg(pending.join(',')), |
| 296 | ImapService::DisconnectReason::CommandTimeout); | ||
| 297 | 2 | } | |
| 298 | |||
| 299 | 2482 | void ImapCommandTable::clearCommands() { | |
| 300 | 2482 | m_pending.clear(); | |
| 301 | 2482 | m_deferredCommands.clear(); | |
| 302 | 2482 | m_serializedCommands.clear(); | |
| 303 | 2482 | resetCommandAccumulators(); | |
| 304 | 2482 | } | |
| 305 | |||
| 306 | 100346 | bool ImapCommandTable::hasCommandType(const QString &type) const { | |
| 307 |
4/4✓ Branch 2 → 3 taken 100346 times.
✓ Branch 3 → 4 taken 100346 times.
✓ Branch 11 → 5 taken 315 times.
✓ Branch 11 → 12 taken 100078 times.
|
100393 | for (const ImapPendingCommand &record : m_pending) { |
| 308 |
2/2✓ Branch 7 → 8 taken 268 times.
✓ Branch 7 → 9 taken 47 times.
|
315 | if (record.type == type) |
| 309 | 268 | return true; | |
| 310 | } | ||
| 311 | 100078 | return false; | |
| 312 | } | ||
| 313 | |||
| 314 | 5045 | ImapPendingCommand *ImapCommandTable::recordOfType(const QString &type) { | |
| 315 |
4/4✓ Branch 2 → 3 taken 5045 times.
✓ Branch 3 → 4 taken 5045 times.
✓ Branch 11 → 5 taken 285 times.
✓ Branch 11 → 12 taken 4947 times.
|
5232 | for (ImapPendingCommand &record : m_pending) { |
| 316 |
2/2✓ Branch 7 → 8 taken 98 times.
✓ Branch 7 → 9 taken 187 times.
|
285 | if (record.type == type) |
| 317 | 98 | return &record; | |
| 318 | } | ||
| 319 | 4947 | return nullptr; | |
| 320 | } | ||
| 321 | |||
| 322 | 475 | ImapPendingCommand ImapCommandTable::takeRecord(const QString &tag) { | |
| 323 | 475 | return m_pending.take(tag); | |
| 324 | } | ||
| 325 | |||
| 326 | 13 | void ImapCommandTable::insertRecord(const QString &tag, ImapPendingCommand record) { | |
| 327 | 13 | m_pending.insert(tag, std::move(record)); | |
| 328 | 13 | } | |
| 329 | |||
| 330 | 2 | void ImapCommandTable::eraseType(const QString &type) { | |
| 331 |
4/4✓ Branch 2 → 3 taken 2 times.
✓ Branch 11 → 12 taken 4 times.
✓ Branch 13 → 4 taken 2 times.
✓ Branch 13 → 14 taken 2 times.
|
4 | for (auto it = m_pending.begin(); it != m_pending.end();) { |
| 332 |
1/2✓ Branch 6 → 7 taken 2 times.
✗ Branch 6 → 10 not taken.
|
2 | if (it->type == type) |
| 333 |
1/1✓ Branch 8 → 9 taken 2 times.
|
2 | it = m_pending.erase(it); |
| 334 | else | ||
| 335 | ✗ | ++it; | |
| 336 | } | ||
| 337 | 2 | } | |
| 338 |