morph 0.1.0
Asynchronous bridge between GUI thread and business models
Loading...
Searching...
No Matches
outbound_ledger.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4#include <QtGlobal>
5#include <chrono>
6#include <cstddef>
7#include <deque>
8#include <optional>
9
10/// @file
11/// @brief Which frames handed to a socket it has written, told from the
12/// socket's own byte counts.
13
14namespace morph::qt::detail {
15
16/// @brief Frames handed to one socket and not yet written, oldest first, each
17/// with the position in the socket's running count of written bytes at
18/// which its last byte is out.
19///
20/// A frame's position is taken when it is handed over: the bytes written so
21/// far, plus every byte the socket still holds, the frame's own included. So
22/// it is measured, not computed from a frame layout, and covers whatever the
23/// socket queued for itself ahead of it. Over TLS the bytes held must include
24/// the ciphertext not yet sent as well as the plaintext not yet encrypted;
25/// plaintext is held unencrypted only briefly, so a frame can be released
26/// ahead of its last byte by at most the record overhead on what was then
27/// unencrypted, and the error does not accumulate: each position is taken
28/// afresh.
29///
30/// Not synchronised: owned by the socket's thread.
31class OutboundLedger {
32public:
33 /// @brief Records a frame just handed to the socket.
34 /// @param pending Bytes the socket holds after taking the frame,
35 /// the frame's own included.
36 /// @param counted What the frame added to the connection's backlog.
37 /// @param queuedAt When the frame was handed over.
38 void push(qint64 pending, std::size_t counted, std::chrono::steady_clock::time_point queuedAt) {
39 _frames.push_back(Frame{.end = _written + pending, .counted = counted, .queuedAt = queuedAt});
40 }
41
42 /// @brief Advances the written count and releases every frame it completes.
43 /// @param bytes Bytes the socket just reported written.
44 /// @param pending Bytes the socket still holds after writing them. `0`
45 /// releases every frame, whatever its position.
46 /// @return The sum of the released frames' `counted`.
47 [[nodiscard]] std::size_t written(qint64 bytes, qint64 pending) {
48 _written += bytes;
49 std::size_t released = 0;
50 while (!_frames.empty() && (pending == 0 || _frames.front().end <= _written)) {
51 released += _frames.front().counted;
52 _frames.pop_front();
53 }
54 return released;
55 }
56
57 /// @brief Forgets every frame, as when the connection goes away.
58 /// @return The sum of the forgotten frames' `counted`.
59 [[nodiscard]] std::size_t clear() {
60 std::size_t dropped = 0;
61 for (auto const& frame : _frames) {
62 dropped += frame.counted;
63 }
64 _frames.clear();
65 return dropped;
66 }
67
68 /// @brief When the oldest unwritten frame was handed over.
69 /// @return Its time, or `std::nullopt` when every frame is written.
70 [[nodiscard]] std::optional<std::chrono::steady_clock::time_point> oldest() const {
71 if (_frames.empty()) {
72 return std::nullopt;
73 }
74 return _frames.front().queuedAt;
75 }
76
77private:
78 /// @brief One frame handed to the socket and not yet written.
79 struct Frame {
80 /// @brief Written count at which the frame's last byte is out.
81 qint64 end = 0;
82 /// @brief What the frame added to the backlog: its envelope's size.
83 std::size_t counted = 0;
84 /// @brief When the frame was handed over.
85 std::chrono::steady_clock::time_point queuedAt;
86 };
87
88 std::deque<Frame> _frames;
89 qint64 _written = 0;
90};
91
92} // namespace morph::qt::detail