MailJD nbsp;·nbsp; Test Dashboard nbsp;·nbsp; Coverage
LCOV - code coverage report
Current view: top level - service - ImapService.h (source / functions) Coverage Total Hit
Test: MailJD Coverage (Unit + E2E) Lines: 100.0 % 8 8
Test Date: 2026-07-27 17:53:44 Functions: 88.9 % 9 8
Legend: Lines:     hit not hit

            Line data    Source code
       1              : #pragma once
       2              : 
       3              : #include <QDate>
       4              : #include <QElapsedTimer>
       5              : #include <QList>
       6              : #include <QMap>
       7              : #include <QObject>
       8              : #include <QPair>
       9              : #include <QQueue>
      10              : #include <QSslSocket>
      11              : #include <QStringList>
      12              : #include <QTimer>
      13              : 
      14              : #include <functional>
      15              : 
      16              : #include "data/AccountConfig.h"
      17              : #include "data/Models.h"
      18              : 
      19              : // ImapService manages a single IMAP connection.
      20              : // Communicates asynchronously via Qt signals/slots.
      21              : // All network I/O happens on the caller's thread (main thread in Sprint 01).
      22              : class ImapService : public QObject {
      23              :   Q_OBJECT
      24              : #ifdef MAILJD_UNIT_TEST
      25              :   friend class TestImapServiceSerialization;
      26              :   friend class TestSprint55Controller;
      27              :   friend class TestConnectionHealth;
      28              :   friend class TestSprint79Imap;
      29              :   friend class TestSprint79CrossFolder;
      30              : #endif
      31              : 
      32              : public:
      33              :   enum class State {
      34              :     Disconnected,
      35              :     Connecting,
      36              :     Connected,      // TCP connected, waiting for server greeting
      37              :     Greeting,       // Server greeting received
      38              :     Capability,     // CAPABILITY response received
      39              :     StartingTLS,    // STARTTLS in progress
      40              :     Authenticating, // LOGIN sent, waiting for response
      41              :     Authenticated,  // LOGIN successful, ready for commands
      42              :     Selected,       // Folder selected, ready for FETCH commands
      43              :     Idling,         // IDLE command active, waiting for server push
      44              :     Error
      45              :   };
      46            3 :   Q_ENUM(State)
      47              : 
      48              :   explicit ImapService(QObject *parent = nullptr);
      49              :   ~ImapService() override;
      50              : 
      51              :   void connectToServer(const ImapConfig &config);
      52              :   void disconnect();
      53              : 
      54              :   // Commands (only valid when Authenticated or Selected)
      55              :   void listFolders();
      56              :   void selectFolder(const QString &folderPath);
      57              :   void fetchHeaders(qint64 uidFrom = 1);
      58              :   void fetchBody(qint64 uid);
      59              :   void fetchBody(qint64 uid, qint64 maxBytes);
      60              : 
      61              :   // Full-message downloads are deliberately bounded below the protocol hard
      62              :   // cap. Users can raise/lower the limit in Settings; callers with a tighter
      63              :   // protocol payload (for example settings sync) may pass an explicit limit.
      64              :   static constexpr int DefaultMaxMessageSizeMiB = 25;
      65              :   static constexpr int MinMaxMessageSizeMiB = 1;
      66              :   static constexpr int MaxMaxMessageSizeMiB = 64;
      67              :   static qint64 configuredMaxMessageBytes();
      68              : 
      69              :   // T-205: Pipeline SELECT + FETCH BODY in one burst (for body connection).
      70              :   void selectAndFetchBody(const QString &folderPath, qint64 uid);
      71              :   void markSeen(qint64 uid);
      72              :   void markUnseen(qint64 uid);
      73              :   void markAllSeen(); // T-200: UID STORE 1:* +FLAGS (\Seen)
      74              : 
      75              :   // Generic flag store: add or remove any flag (system flag or keyword)
      76              :   // e.g. storeFlag(uid, "\\Flagged", true) or storeFlag(uid, "$label1", false)
      77              :   void storeFlag(qint64 uid, const QString &flag, bool add);
      78              : 
      79              :   // T-100: Move/copy messages between folders
      80              :   void moveMessage(qint64 uid, const QString &targetFolder);
      81              :   void moveMessages(const QList<qint64> &uids, const QString &targetFolder);
      82              :   void copyMessage(qint64 uid, const QString &targetFolder);
      83              : 
      84              :   // Search UIDs in the selected folder (fromUid=1 → all, fromUid>1 → delta).
      85              :   void searchAllUids(qint64 fromUid = 1);
      86              : 
      87              :   // T-187: Text-based IMAP SEARCH in the selected folder.
      88              :   // criteria: "TEXT", "SUBJECT", "FROM", "TO", "BODY"
      89              :   void searchText(const QString &query,
      90              :                   const QString &criteria = QStringLiteral("TEXT"));
      91              : 
      92              :   // Sprint 59 (S2): composite IMAP SEARCH for the visual search facets.
      93              :   // ImapService stays independent of MailCache — the caller (MailController)
      94              :   // translates SearchFilter into this server-friendly subset. Tri-state mirrors
      95              :   // SearchFilter::Tri: Any adds no constraint.
      96              :   enum class SearchTri { Any, Yes, No };
      97              :   struct SearchCriteria {
      98              :     QString text;    // TEXT "..."
      99              :     QString from;    // FROM "..."
     100              :     QString to;      // TO "..."
     101              :     QString subject; // SUBJECT "..."
     102              :     QDate since;     // SINCE dd-MMM-yyyy (inclusive)
     103              :     QDate before;    // BEFORE dd-MMM-yyyy (exclusive per RFC 3501)
     104              :     SearchTri unread = SearchTri::Any;   // Yes ⇒ UNSEEN, No ⇒ SEEN
     105              :     SearchTri flagged = SearchTri::Any;  // Yes ⇒ FLAGGED, No ⇒ UNFLAGGED
     106              :     SearchTri answered = SearchTri::Any; // Yes ⇒ ANSWERED, No ⇒ UNANSWERED
     107              :     QStringList keywords;                // KEYWORD "..." per entry
     108              : 
     109              :     // True when nothing constrains the search — such criteria are not sent.
     110              :     bool isEmpty() const;
     111              :   };
     112              : 
     113              :   // Run a composite SEARCH built from the criteria. No-op when isEmpty().
     114              :   void search(const SearchCriteria &criteria);
     115              : 
     116              :   // T-211: Search by Message-ID header (for undo-move).
     117              :   void searchByMessageId(const QString &messageId);
     118              : 
     119              :   // Fetch headers for a specific set of UIDs (comma-separated).
     120              :   void fetchHeadersByUids(const QList<qint64> &uids);
     121              : 
     122              :   // T-176: IMAP APPEND – Upload a complete RFC-2822 message to a folder.
     123              :   // flags: e.g. "\\Seen", "\\Draft", or "\\Seen \\Draft"
     124              :   void appendMessage(const QString &folder, const QByteArray &rfcMessage,
     125              :                      const QString &flags = {});
     126              : 
     127              :   // T-176: EXPUNGE – Permanently remove messages marked \Deleted in the
     128              :   // currently selected folder.  Used by T-177 (Drafts) to delete old drafts
     129              :   // after re-saving.
     130              :   void expunge();
     131              : 
     132              :   // T-281: Folder management commands (RFC 3501 §6.3.3–§6.3.5)
     133              :   void createFolder(const QString &folderPath);
     134              :   void deleteFolder(const QString &folderPath);
     135              :   void renameFolder(const QString &oldPath, const QString &newPath);
     136              : 
     137              :   // IDLE support (RFC 2177)
     138              :   void startIdle(); // Enter IDLE mode (must be in Selected state)
     139              :   void stopIdle();  // Send DONE to exit IDLE mode
     140           68 :   bool isIdling() const { return m_isIdling; }
     141              :   bool hasIdleCapability() const;
     142              :   bool hasCondstoreCapability() const; // T-208: CONDSTORE support
     143              : 
     144              :   // T-320: NOTIFY support (RFC 5465) — replaces IDLE + STATUS polling
     145              :   void startNotify(const QStringList &subscribedFolders);
     146              :   void stopNotify();  // Send NOTIFY NONE
     147           79 :   bool isNotifying() const { return m_isNotifying; }
     148              :   bool hasNotifyCapability() const;
     149              :   void executeAfterNotify(std::function<void()> command);
     150              : 
     151              :   // T-205: Disable automatic IDLE entry after FETCH_BODY/STORE.
     152              :   // Set to false for connections that only do body fetches.
     153           41 :   void setAutoIdle(bool enabled) { m_autoIdle = enabled; }
     154              : 
     155              :   // Execute a command after safely stopping IDLE or NOTIFY.
     156              :   // If not idling/notifying: executes immediately.
     157              :   // If idling: sends DONE, queues command, executes on IDLE OK.
     158              :   // If notifying: sends NOTIFY NONE, queues command, executes on NOTIFY OK.
     159              :   void executeAfterIdle(std::function<void()> command);
     160              : 
     161              :   // T-114: Clear all pending deferred commands (e.g. on folder switch).
     162              :   // Prevents stale SELECTs/FETCHes from executing after the user
     163              :   // switches folders rapidly.
     164              :   void clearDeferredCommands();
     165              : 
     166              :   // Flag sync: fetch only UID+FLAGS for all messages in selected folder.
     167              :   void fetchFlags();
     168              : 
     169              :   // T-207: Pipeline SELECT + FETCH FLAGS in one burst (saves one round-trip).
     170              :   // Sends both commands immediately; server processes them in-order per RFC 3501.
     171              :   void selectAndFetchFlags(const QString &folderPath);
     172              : 
     173              :   // T-208: Incremental flag sync using CONDSTORE (RFC 4551).
     174              :   // Only fetches flags changed since the given HIGHESTMODSEQ value.
     175              :   void fetchFlagsChanged(quint64 modseq);
     176              : 
     177              :   // Fetch UID+FLAGS for a single sequence number (used after IDLE flag push).
     178              :   void fetchUidForSeqNo(int seqNo);
     179              : 
     180              :   // Folder status: query UNSEEN/MESSAGES/RECENT without selecting.
     181              :   void statusFolder(const QString &folderPath);
     182              : 
     183              :   // T-540: NOOP command for keep-alive (body connection)
     184              :   void sendNoop();
     185              : 
     186              :   // T-720: Liveness probe API for ConnectionHealthMonitor.
     187              :   // Idempotent while a probe is already running. The concrete probe depends
     188              :   // on the current state:
     189              :   //   Authenticated/Selected → tagged NOOP with a 15 s watchdog
     190              :   //   Idling                  → DONE/OK round-trip with a 15 s watchdog
     191              :   //   Connecting/Auth/Error/Disconnected → no-op (handled elsewhere)
     192              :   // On watchdog timeout the connection is failed and the monitor schedules
     193              :   // a reconnect.
     194              :   void requestLivenessProbe(const QString &reason);
     195              : 
     196              :   // T-720: Public wrapper around private failConnection() so the
     197              :   // ConnectionHealthMonitor can force a teardown + reconnect on
     198              :   // suspend/resume or a network change. Validates/logs the reason.
     199              :   void abortForReconnect(const QString &reason);
     200              : 
     201              :   // T-720: Test seam — true after the TCP-keepalive tuning helper ran
     202              :   // (i.e. onConnected()/onEncrypted() ran the platform setsockopt path).
     203            3 :   bool keepAliveTuned() const { return m_keepAliveTuned; }
     204              : 
     205              :   // T-720: Test seam — true while a liveness probe is in flight.
     206            6 :   bool isLivenessProbeInFlight() const { return !m_probeTag.isEmpty(); }
     207              : 
     208              :   // Currently selected folder path.
     209           17 :   QString selectedFolder() const { return m_selectedFolder; }
     210              : 
     211         1130 :   State state() const { return m_state; }
     212              : 
     213              : signals:
     214              :   void stateChanged(ImapService::State newState);
     215              :   void errorOccurred(const QString &error);
     216              :   void folderListReceived(const QList<FolderInfo> &folders);
     217              :   void folderSelected(const QString &path, int messageCount,
     218              :                       quint32 uidValidity, quint64 highestModseq = 0); // T-208
     219              :   // Emitted when a SELECT is rejected by the server (e.g. \Noselect folder).
     220              :   // Lets multi-folder flows (server search) skip the folder instead of
     221              :   // waiting forever for a folderSelected that will never arrive.
     222              :   void folderSelectFailed(const QString &path);
     223              :   void headersReceived(const QList<MailHeader> &headers);
     224              :   void headerFetchComplete(); // All header batches delivered
     225              :   void rawBodyReceived(qint64 uid, const QByteArray &rawBody);
     226              :   void bodyFetchTooLarge(qint64 uid, qint64 maxBytes);
     227              : 
     228              :   // IDLE push signals
     229              :   void idleNewMessages(int newCount);               // * N EXISTS (N > previous)
     230              :   void idleMessageExpunged(int seqNo);              // * N EXPUNGE
     231              :   void idleFlagsChanged(qint64 uid, quint32 flags); // * N FETCH (FLAGS ...)
     232              :   void idleFlagsNeedRefetch(int seqNo); // * N FETCH (FLAGS ...) without UID
     233              : 
     234              :   // Flag sync result
     235              :   void flagsReceived(const QList<QPair<qint64, quint32>> &uidFlags);
     236              : 
     237              :   // Search result: list of UIDs matching the search query.
     238              :   void searchResultReceived(const QList<qint64> &uids);
     239              : 
     240              :   // Folder status result
     241              :   void folderStatusReceived(const StatusResult &result);
     242              :   void storeComplete();
     243              : 
     244              :   // T-100: Move/copy result signals
     245              :   void messageMoved(qint64 uid, const QString &targetFolder);
     246              :   void messagesMoved(const QList<qint64> &uids, const QString &targetFolder);
     247              :   void messageCopied(qint64 uid, const QString &targetFolder);
     248              :   void moveError(const QString &error);
     249              : 
     250              :   // T-176: APPEND result signals
     251              :   void messageAppended(const QString &folder, qint64 uid); // uid=0 if no UIDPLUS
     252              :   void appendError(const QString &error);
     253              : 
     254              :   // T-176: EXPUNGE result
     255              :   void expungeComplete();
     256              : 
     257              :   // T-281: Folder management result signals
     258              :   void folderCreated(const QString &folderPath);
     259              :   void folderDeleted(const QString &folderPath);
     260              :   void folderRenamed(const QString &oldPath, const QString &newPath);
     261              :   void folderOperationError(const QString &operation, const QString &error);
     262              : 
     263              : private slots:
     264              :   void onConnected();
     265              :   void onEncrypted();
     266              :   void onReadyRead();
     267              :   void onSocketError(QAbstractSocket::SocketError error);
     268              :   void onTimeout();
     269              :   void onCommandTimeout();
     270              :   void onCommandDeadline();
     271              :   void onIdleRenew();
     272              :   // T-720: Liveness-probe watchdog slots.
     273              :   void onLivenessProbeTimeout();
     274              :   void onIdleRenewWatchdogTimeout();
     275              : 
     276              : private:
     277              :   void setState(State newState);
     278              :   void sendCommand(const QString &type, const QString &command);
     279              :   // T-720: like sendCommand() but exposes the generated tag (for probes).
     280              :   QString sendTaggedCommand(const QString &type, const QString &command);
     281              :   // T-720: Send a probe NOOP and remember its tag. Does not enqueue —
     282              :   // call only in a state that allows commands (Authenticated/Selected).
     283              :   void sendProbeNoop();
     284              :   // T-720: Apply SO_KEEPALIVE + platform-specific interval tuning to the
     285              :   // connected socket. Called once from onConnected()/onEncrypted().
     286              :   void tuneKeepAlive();
     287              :   bool beginLogin();
     288              :   void processLine(const QString &line);
     289              :   void handleUntagged(const QString &line);
     290              :   void handleTagged(const QString &line);
     291              :   void restartPush(); // T-320: restart NOTIFY or IDLE based on capabilities
     292              :   bool hasStatefulCommandInFlight() const;
     293              :   bool hasTimeoutTrackedCommandInFlight() const;
     294              :   void enqueueSerializedCommand(std::function<void()> command);
     295              :   void runNextSerializedCommand();
     296              :   void refreshCommandTimeout();
     297              :   void refreshCommandDeadline();
     298              :   void resetCommandAccumulators();
     299              :   bool accountCommandResponseBytes(qint64 bytes);
     300              :   bool accountCommandLiteral();
     301              :   bool accountCommandResultItems(qint64 items = 1);
     302              :   void completeLiteral();
     303              :   void clearCredentials();
     304              :   void invalidateBodyFetch();
     305              :   void fetchBodyWithLimits(qint64 uid, qint64 acceptedBytes,
     306              :                            qint64 requestedBytes);
     307              :   void failConnection(const QString &error);
     308              :   QString nextTag();
     309              :   static QString buildFetchBodyCommand(qint64 uid, qint64 maxBytes = -1);
     310              :   static QString quoteImapString(const QString &str);
     311              :   static bool isValidImapFlag(const QString &flag);
     312              :   // Sprint 59 (S2): assemble "UID SEARCH <criteria…>" from the facet criteria.
     313              :   // Returns an empty string when no criterion is set. Pure + testable.
     314              :   static QString buildSearchCommand(const SearchCriteria &criteria);
     315              : 
     316              :   QSslSocket *m_socket = nullptr;
     317              :   QTimer *m_timeoutTimer = nullptr;
     318              :   QTimer *m_commandTimeoutTimer = nullptr;
     319              :   QTimer *m_commandDeadlineTimer = nullptr;
     320              :   QTimer *m_idleRenewTimer = nullptr;
     321              :   // T-720: Liveness-probe watchdogs (single-shot). PROBE_WATCHDOG_MS is the
     322              :   // budget for a probe NOOP or an IDLE DONE/OK round-trip; it is shorter
     323              :   // than COMMAND_TIMEOUT_MS so the monitor can claim a ~30 s worst-case
     324              :   // detection window.
     325              :   QTimer *m_livenessProbeWatchdog = nullptr;
     326              :   QTimer *m_idleRenewWatchdog = nullptr;
     327              :   QString m_probeTag;       // T-720: tag of the in-flight probe NOOP
     328              :   QString m_lastProbeReason; // T-720: reason string for diagnostic logging
     329              :   // T-720: True once tuneKeepAlive() ran (test seam for native tuning).
     330              :   bool m_keepAliveTuned = false;
     331              : 
     332              :   State m_state = State::Disconnected;
     333              :   ImapConfig m_config;
     334              : 
     335              :   int m_tagCounter = 0;
     336              :   QByteArray m_readBuffer;
     337              : 
     338              :   // Track pending commands: tag → command-type ("CAPABILITY", "LOGIN", "LIST")
     339              :   QMap<QString, QString> m_pendingCommands;
     340              : 
     341              :   // Accumulate LIST responses until the tagged OK
     342              :   QList<FolderInfo> m_pendingFolders;
     343              : 
     344              :   // Accumulate FETCH responses until the tagged OK
     345              :   QList<MailHeader> m_pendingHeaders;
     346              : 
     347              :   // Accumulate FETCH FLAGS responses
     348              :   QList<QPair<qint64, quint32>> m_pendingFlags;
     349              : 
     350              :   // Accumulate SEARCH responses
     351              :   QList<qint64> m_pendingSearchUids;
     352              : 
     353              :   // SELECT state
     354              :   QString m_selectedFolder;
     355              :   QString m_pendingSelectFolder; // Bug 34: assigned on SELECT OK, not before
     356              :   int m_selectedMessageCount = 0;
     357              :   quint32 m_selectedUidValidity = 0;
     358              :   quint64 m_selectedHighestModseq = 0; // T-208: CONDSTORE HIGHESTMODSEQ
     359              : 
     360              :   QStringList m_capabilities;
     361              :   bool m_autoIdle = true; // T-205: false for body-fetch connections
     362              : 
     363              :   // IDLE state
     364              :   bool m_isIdling = false;
     365              :   QString m_idleTag; // Tag of the IDLE command (for matching tagged OK)
     366              : 
     367              :   // T-320: NOTIFY state (RFC 5465)
     368              :   bool m_isNotifying = false;
     369              :   QString m_notifyTag;  // Tag of the NOTIFY SET command
     370              :   QStringList m_notifyFolders; // Folders being watched via NOTIFY
     371              : 
     372              :   QString m_pendingStatusFolder; // Folder path for STATUS command in-flight
     373              :   QString m_pendingMoveTarget;            // T-100: Target folder for MOVE/COPY
     374              :   QList<qint64> m_pendingMoveUids;        // T-100: UIDs being moved/copied
     375              : 
     376              :   // T-176: APPEND literal state
     377              :   QByteArray m_pendingAppendData;  // RFC-2822 message bytes to send after +
     378              :   QString m_pendingAppendFolder;   // Target folder for APPEND
     379              : 
     380              :   // T-281: Folder management pending state
     381              :   QString m_pendingFolderOp;       // Folder path for CREATE/DELETE/RENAME
     382              :   QString m_pendingFolderNewPath;  // New path for RENAME
     383              : 
     384              :   // Deferred command queue: commands to execute after IDLE stops
     385              :   QQueue<std::function<void()>> m_deferredCommands;
     386              :   QQueue<std::function<void()>> m_serializedCommands;
     387              : 
     388              :   // IMAP literal accumulation: {N}\r\n followed by N raw bytes
     389              :   qint64 m_literalBytesRemaining = 0; // T-510: was int, overflow on >2GB
     390              :   QString m_literalLine;    // line that triggered the literal
     391              :   QByteArray m_literalData; // accumulated raw bytes
     392              :   bool m_discardingOversizedBody = false;
     393              :   bool m_skipFetchLiteralRemainder = false;
     394              : 
     395              :   // Body literal bypass: when fetching BODY[], keep raw QByteArray
     396              :   // instead of converting through QString (which corrupts binary MIME data)
     397              :   bool m_isBodyLiteral = false;
     398              :   qint64 m_bodyLiteralUid = -1;
     399              :   bool m_discardingInvalidBody = false;
     400              :   qint64 m_activeBodyFetchUid = -1;
     401              :   qint64 m_activeBodyFetchLimit = 0;
     402              :   qint64 m_activeBodyFetchRequestBytes = 0;
     403              :   bool m_bodyFetchRequiresSelect = false;
     404              : 
     405              :   // A command budget spans one serialized command or one intentionally
     406              :   // pipelined command pair (SELECT+FETCH). It is reset only after the final
     407              :   // non-IDLE tagged response, so slow-drip progress cannot evade it.
     408              :   qint64 m_commandResponseBytes = 0;
     409              :   qint64 m_commandLiteralCount = 0;
     410              :   qint64 m_commandResultItems = 0;
     411              : 
     412              :   // T-210: Per-command timing — maps tag → elapsed timer
     413              :   QMap<QString, QElapsedTimer> m_commandTimers;
     414              : 
     415              :   static constexpr int TIMEOUT_MS = 10000;             // 10 seconds
     416              :   // SEC-2026-07-21-12: deadline for the pre-auth setup phases (greeting,
     417              :   // TLS handshake, CAPABILITY, LOGIN). Without this, a silent server can
     418              :   // stall the account indefinitely after TCP connect — no error, no
     419              :   // reconnect. The timer is re-armed on each setup-state transition and
     420              :   // stopped once Authenticated is reached (command timeout takes over).
     421              :   static constexpr int SESSION_SETUP_TIMEOUT_MS = 30000; // 30 seconds
     422              :   static constexpr int COMMAND_TIMEOUT_MS = 30000;     // 30 s inactivity
     423              :   static constexpr int COMMAND_DEADLINE_MS = 120000;   // 2 min absolute
     424              :   // T-720: Liveness probe budget. The monitor's 15 s probe interval + this
     425              :   // 15 s watchdog yields ~30 s worst-case dead-socket detection.
     426              :   static constexpr int PROBE_WATCHDOG_MS = 15000;
     427              :   static constexpr qint64 MAX_SOCKET_READ_BUFFER_SIZE = 1024LL * 1024;
     428              :   static constexpr qint64 MAX_READ_BUFFER_SIZE = 4LL * 1024 * 1024;
     429              :   static constexpr qint64 MAX_LITERAL_SIZE =
     430              :       (static_cast<qint64>(MaxMaxMessageSizeMiB) * 1024 * 1024) + 1;
     431              :   static constexpr qint64 MAX_COMMAND_RESPONSE_SIZE = 96LL * 1024 * 1024;
     432              :   static constexpr qint64 MAX_LITERALS_PER_COMMAND = 50000;
     433              :   static constexpr qint64 MAX_RESULT_ITEMS_PER_COMMAND = 50000;
     434              :   // Hard cap for one folded logical response. Unlike a lifetime transfer
     435              :   // quota, this protects command-free phases without disconnecting healthy
     436              :   // long-running IDLE/NOTIFY sessions after normal cumulative traffic.
     437              :   static constexpr qint64 MAX_NON_BODY_LITERAL_LINE_SIZE = 8LL * 1024 * 1024;
     438              :   static constexpr int IDLE_RENEW_MS = 25 * 60 * 1000; // T-271: 25 min (was 28)
     439              :   static constexpr int HEADER_BATCH_SIZE = 50;         // Streaming batch
     440              : };
        

Generated by: LCOV version 2.0-1