service/ImapPushSession.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | |||
| 3 | #include <QAbstractSocket> | ||
| 4 | #include <QElapsedTimer> | ||
| 5 | #include <QString> | ||
| 6 | #include <QStringList> | ||
| 7 | |||
| 8 | #include <functional> | ||
| 9 | #include <optional> | ||
| 10 | |||
| 11 | #include "service/ImapResponseParser.h" | ||
| 12 | #include "service/ImapService.h" | ||
| 13 | |||
| 14 | class ImapCommandTable; | ||
| 15 | class QObject; | ||
| 16 | class QSslSocket; | ||
| 17 | class QTimer; | ||
| 18 | |||
| 19 | // Push session and liveness of one IMAP connection (Sprint 92 S92.1.4, | ||
| 20 | // extracted from ImapService): IDLE (enter, 25-minute renew, DONE and its | ||
| 21 | // round-trip watchdog), NOTIFY SET/NONE, the push restart after commands | ||
| 22 | // (restartPush drains queued work first), keep-alive NOOP and the | ||
| 23 | // liveness probe of ConnectionHealthMonitor (NOOP with a watchdog, or an | ||
| 24 | // IDLE DONE/OK round trip). Part E's detection rules live here. | ||
| 25 | // | ||
| 26 | // Owner: ImapService (std::unique_ptr, at the former position of the IDLE | ||
| 27 | // renew timer and both watchdogs, which it creates in the same order), | ||
| 28 | // connection thread. Plain class; the service constructor connects its | ||
| 29 | // timers to it at the former positions. State changes, connection failure, | ||
| 30 | // the capability checks and E2E phases go through Deps; commands use the | ||
| 31 | // command table; the session state is read by reference. | ||
| 32 | class ImapPushSession { | ||
| 33 | public: | ||
| 34 | using State = ImapService::State; | ||
| 35 | using DisconnectReason = ImapService::DisconnectReason; | ||
| 36 | |||
| 37 | struct Deps { | ||
| 38 | std::function<void(State state)> setState; | ||
| 39 | std::function<void(const QString &error, DisconnectReason reason)> failConnection; | ||
| 40 | std::function<bool()> hasIdleCapability; | ||
| 41 | std::function<bool()> hasNotifyCapability; | ||
| 42 | // S92.5.2: milliseconds since the connection last received data. | ||
| 43 | std::function<qint64()> quietMs; | ||
| 44 | // MAILJD_E2E_TESTING only (present in every build for a stable layout). | ||
| 45 | std::function<void(const QString &phase, quint64 requestToken)> reportE2eTransportPhase; | ||
| 46 | }; | ||
| 47 | ImapPushSession(QObject *owner, QSslSocket *socket, ImapCommandTable &commands, | ||
| 48 | const State &state, Deps deps); | ||
| 49 | |||
| 50 | void startIdle(); | ||
| 51 | void stopIdle(); | ||
| 52 | void onIdleRenew(); | ||
| 53 | void restartPush(); // T-320: restart NOTIFY or IDLE based on capabilities | ||
| 54 | void startNotify(const QStringList &subscribedFolders); | ||
| 55 | void stopNotify(); | ||
| 56 | void executeAfterNotify(std::function<void()> command); | ||
| 57 | void sendNoop(); | ||
| 58 | void requestLivenessProbe(const QString &reason); | ||
| 59 | void onLivenessProbeTimeout(); | ||
| 60 | void onIdleRenewWatchdogTimeout(); | ||
| 61 |
3/4✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 6 taken 125 times.
✓ Branch 4 → 5 taken 2 times.
✗ Branch 4 → 6 not taken.
|
127 | bool usesNotifyPush() const { return m_allowNotifyPush && hasNotifyCapability(); } |
| 62 | // S92.5.3: RTT-aware liveness budget. Samples come from the probe NOOP and | ||
| 63 | // the IDLE DONE/OK round trip; a new session starts without samples. | ||
| 64 | void noteRoundTrip(qint64 elapsedMs); | ||
| 65 | int probeWatchdogMs() const; | ||
| 66 | std::optional<double> smoothedRttMs() const { return m_smoothedRttMs; } | ||
| 67 | void resetRoundTrip(); | ||
| 68 | // S92.5.2: probe first when a user-visible command meets a quiet connection. | ||
| 69 | bool probeBeforeUse(const char *command); | ||
| 70 | // Quiet time that makes a user-visible command probe first. Below the | ||
| 71 | // secondary monitors' 120 s probe interval, so an idle body/search | ||
| 72 | // connection is checked when it is used. | ||
| 73 | static constexpr qint64 PROBE_BEFORE_USE_QUIET_MS = 20000; | ||
| 74 | // S92.5.6: another connection of the account found the network path dead. | ||
| 75 | // Keep this one if it received data within PATH_ALIVE_MS; fail it when a | ||
| 76 | // command is waiting in the silent path; probe it otherwise. | ||
| 77 | void verifyAfterPathFailure(const QString &reason); | ||
| 78 | static constexpr qint64 PATH_ALIVE_MS = 3000; | ||
| 79 | // Tagged completions of IDLE, NOTIFY SET and NOTIFY NONE. | ||
| 80 | void handleTaggedIdle(const TaggedResponse &response); | ||
| 81 | void handleTaggedNotify(const TaggedResponse &response); | ||
| 82 | void handleTaggedNotifyNone(const TaggedResponse &response); | ||
| 83 | |||
| 84 | // Push/probe state shared with the service (single owner: this unit). | ||
| 85 | 3945 | bool &isIdling() { return m_isIdling; } | |
| 86 | 2968 | bool &idleStartPending() { return m_idleStartPending; } | |
| 87 | 157 | QString &idleTag() { return m_idleTag; } | |
| 88 | 1514 | bool &isNotifying() { return m_isNotifying; } | |
| 89 | 142 | QString ¬ifyTag() { return m_notifyTag; } | |
| 90 | 2 | QStringList ¬ifyFolders() { return m_notifyFolders; } | |
| 91 | 97 | bool &autoIdle() { return m_autoIdle; } | |
| 92 | 3 | bool &allowNotifyPush() { return m_allowNotifyPush; } | |
| 93 | 2928 | QString &probeTag() { return m_probeTag; } | |
| 94 | 12 | QString &lastProbeReason() { return m_lastProbeReason; } | |
| 95 | 2868 | QTimer *idleRenewTimer() const { return m_idleRenewTimer; } | |
| 96 | 5229 | QTimer *livenessProbeWatchdog() const { return m_livenessProbeWatchdog; } | |
| 97 | 5229 | QTimer *idleRenewWatchdog() const { return m_idleRenewWatchdog; } | |
| 98 | |||
| 99 | // T-720: Liveness probe budget: the ceiling, and the value until the | ||
| 100 | // first round trip of a session was measured (S92.5.3). | ||
| 101 | static constexpr int PROBE_WATCHDOG_MS = 15000; | ||
| 102 | // S92.5.3: floor and RTT factor of the measured budget. 10 x SRTT leaves | ||
| 103 | // an order of magnitude for server jitter; 3 s covers a 300 ms RTT link. | ||
| 104 | static constexpr int PROBE_WATCHDOG_MIN_MS = 3000; | ||
| 105 | static constexpr int PROBE_WATCHDOG_RTT_FACTOR = 10; | ||
| 106 | static constexpr int IDLE_RENEW_MS = 25 * 60 * 1000; // T-271: 25 min (was 28) | ||
| 107 | |||
| 108 | private: | ||
| 109 | // T-720: Send a probe NOOP and remember its tag. Does not enqueue — | ||
| 110 | // call only in a state that allows commands (Authenticated/Selected). | ||
| 111 | void sendProbeNoop(); | ||
| 112 | 28 | void setState(State state) { m_deps.setState(state); } | |
| 113 | 9 | void failConnection(const QString &error, | |
| 114 | DisconnectReason reason = DisconnectReason::ProtocolError) { | ||
| 115 | 9 | m_deps.failConnection(error, reason); | |
| 116 | 9 | } | |
| 117 | 97 | bool hasIdleCapability() const { return m_deps.hasIdleCapability(); } | |
| 118 | 8 | bool hasNotifyCapability() const { return m_deps.hasNotifyCapability(); } | |
| 119 | 28 | void reportE2eTransportPhase(const QString &phase, quint64 requestToken = 0) { | |
| 120 | 28 | m_deps.reportE2eTransportPhase(phase, requestToken); | |
| 121 | 28 | } | |
| 122 | |||
| 123 | QSslSocket *m_socket; | ||
| 124 | ImapCommandTable &m_commands; | ||
| 125 | const State &m_state; // ImapService::m_state | ||
| 126 | Deps m_deps; | ||
| 127 | |||
| 128 | QTimer *m_idleRenewTimer = nullptr; | ||
| 129 | // T-720: Liveness-probe watchdogs (single-shot). PROBE_WATCHDOG_MS is the | ||
| 130 | // budget for a probe NOOP or an IDLE DONE/OK round-trip; it is shorter | ||
| 131 | // than the command timeout so the monitor can claim a ~30 s worst-case | ||
| 132 | // detection window. | ||
| 133 | QTimer *m_livenessProbeWatchdog = nullptr; | ||
| 134 | QTimer *m_idleRenewWatchdog = nullptr; | ||
| 135 | std::optional<double> m_smoothedRttMs; // S92.5.3 | ||
| 136 | QElapsedTimer m_doneSent; // DONE written, IDLE OK pending | ||
| 137 | QString m_probeTag; // T-720: tag of the in-flight probe NOOP | ||
| 138 | QString m_lastProbeReason; // T-720: reason string for diagnostic logging | ||
| 139 | |||
| 140 | bool m_autoIdle = true; // T-205: false for body-fetch connections | ||
| 141 | bool m_allowNotifyPush = false; | ||
| 142 | |||
| 143 | // IDLE state | ||
| 144 | bool m_isIdling = false; | ||
| 145 | bool m_idleStartPending = false; | ||
| 146 | QString m_idleTag; // Tag of the IDLE command (for matching tagged OK) | ||
| 147 | |||
| 148 | // T-320: NOTIFY state (RFC 5465) | ||
| 149 | bool m_isNotifying = false; | ||
| 150 | QString m_notifyTag; // Tag of the NOTIFY SET command | ||
| 151 | QStringList m_notifyFolders; // Folders being watched via NOTIFY | ||
| 152 | }; | ||
| 153 |