MailJDTourDownloadDocsQualityTestsCoverage

Which source lines and branches the tests execute. Lines 91.5 %, branches 75.0 % (minimum 85 % and 72 %). main @ 399ffe3d · 21 October 2026 · pipeline 7238.

GCC Code Coverage Report


Directory: src/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 88.4% 252 / 0 / 285
Functions: 92.3% 36 / 0 / 39
Branches: 67.9% 318 / 0 / 468

service/UnifiedPushReceiver.cpp
Line Branch Exec Source
1 #include "UnifiedPushReceiver.h"
2 #include "WebPushPayloadCodec.h"
3
4 #include <QDBusReply>
5
6 #include <QDBusConnection>
7 #include <QDBusConnectionInterface>
8 #include <QDBusMessage>
9 #include <QDBusPendingCallWatcher>
10 #include <QDBusPendingReply>
11 #include <QDBusServiceWatcher>
12 #include <QDateTime>
13 #include <QLoggingCategory>
14 #include <algorithm>
15
16
4/7
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 8 taken 27 times.
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 8 not taken.
✓ Branch 5 → 6 taken 1 time.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
28 Q_LOGGING_CATEGORY(lcUnifiedPush, "mailjd.davpush.unifiedpush")
17
18 namespace {
19 const QString kDistributorPath = QStringLiteral("/org/unifiedpush/Distributor");
20 const QString kDistributor2 = QStringLiteral("org.unifiedpush.Distributor2");
21 const QString kDistributor1 = QStringLiteral("org.unifiedpush.Distributor1");
22 const QString kConnectorPath = QStringLiteral("/org/unifiedpush/Connector");
23 const QString kKdeDistributor = QStringLiteral("org.unifiedpush.Distributor.kde");
24 const QString kSucceeded = QStringLiteral("REGISTRATION_SUCCEEDED");
25 // The distributor answers Register after its own provider registration
26 // (network); one bounded wait instead of D-Bus's default 25 s.
27 constexpr int kRegisterTimeoutMs = 120000;
28 constexpr int kMaxRecentIds = 64;
29
30 28 qint64 nowMs() { return QDateTime::currentMSecsSinceEpoch(); }
31
32 1 QString describeReason(const QString &reason) {
33
1/2
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 10 not taken.
2 if (reason == QLatin1String("ACTION_REQUIRED")) return QStringLiteral("the system push service needs to be set up");
34 ✗ if (reason == QLatin1String("NETWORK")) return QStringLiteral("the system push service cannot reach its server");
35 ✗ if (reason == QLatin1String("UNAUTHORIZED")) return QStringLiteral("the system push service refused MailJD");
36 ✗ if (reason == QLatin1String("VAPID_REQUIRED")) return QStringLiteral("the system push service requires a VAPID key");
37 ✗ return QStringLiteral("the system push service reported an internal error");
38 }
39 } // namespace
40
41 // --- discovery ----------------------------------------------------------------
42
43 826 QString UnifiedPushReceiver::findDistributor() {
44
2/2
✓ Branch 2 → 3 taken 826 times.
✓ Branch 3 → 4 taken 826 times.
826 QDBusConnectionInterface *bus = QDBusConnection::sessionBus().interface();
45
1/2
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 826 times.
826 if (!bus) return {};
46
1/1
✓ Branch 7 → 8 taken 826 times.
826 QStringList names = bus->registeredServiceNames().value();
47
1/1
✓ Branch 10 → 11 taken 826 times.
826 const QStringList activatable = bus->activatableServiceNames().value();
48
2/2
✓ Branch 21 → 15 taken 7434 times.
✓ Branch 21 → 22 taken 826 times.
8260 for (const QString &name : activatable)
49
3/3
✓ Branch 17 → 18 taken 5072 times.
✓ Branch 17 → 19 taken 2362 times.
✓ Branch 18 → 19 taken 5072 times.
7434 if (!names.contains(name)) names.append(name);
50 826 QStringList distributors;
51
2/2
✓ Branch 37 → 25 taken 11265 times.
✓ Branch 37 → 38 taken 826 times.
12091 for (const QString &name : std::as_const(names))
52
7/8
✓ Branch 26 → 27 taken 11265 times.
✓ Branch 27 → 28 taken 6 times.
✓ Branch 27 → 32 taken 11259 times.
✓ Branch 30 → 31 taken 6 times.
✗ Branch 30 → 32 not taken.
✓ Branch 33 → 34 taken 6 times.
✓ Branch 33 → 35 taken 11259 times.
✓ Branch 34 → 35 taken 6 times.
11265 if (name.startsWith(kDistributorPrefix) && name.size() > kDistributorPrefix.size()) distributors.append(name);
53
2/2
✓ Branch 39 → 40 taken 823 times.
✓ Branch 39 → 41 taken 3 times.
826 if (distributors.isEmpty()) return {};
54
1/1
✓ Branch 41 → 42 taken 3 times.
3 const QString preferred = qEnvironmentVariable("UNIFIEDPUSH_DISTRIBUTOR");
55
2/2
✓ Branch 43 → 44 taken 1 time.
✓ Branch 43 → 45 taken 2 times.
3 if (distributors.contains(preferred)) return preferred;
56
1/2
✗ Branch 46 → 47 not taken.
✓ Branch 46 → 48 taken 2 times.
2 if (distributors.contains(kKdeDistributor)) return kKdeDistributor;
57
3/3
✓ Branch 48 → 49 taken 2 times.
✓ Branch 49 → 50 taken 2 times.
✓ Branch 50 → 51 taken 2 times.
2 std::sort(distributors.begin(), distributors.end());
58
1/1
✓ Branch 51 → 52 taken 2 times.
2 return distributors.first();
59 826 }
60
61 18 QString UnifiedPushReceiver::connectorService() {
62 // Reverse DNS of the project domain; tests may run beside a real MailJD.
63
1/1
✓ Branch 2 → 3 taken 18 times.
18 const QString override = qEnvironmentVariable("MAILJD_UNIFIEDPUSH_SERVICE");
64
2/4
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 8 taken 18 times.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 18 times.
36 return override.isEmpty() ? QStringLiteral("de.jdsoft.MailJD") : override;
65 18 }
66
67 1 void UnifiedPushReceiver::unregisterToken(const QString &distributor, const QString &token) {
68
3/6
✓ Branch 3 → 4 taken 1 time.
✗ Branch 3 → 6 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 1 time.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 1 time.
1 if (distributor.isEmpty() || token.isEmpty()) return;
69
1/1
✓ Branch 13 → 14 taken 1 time.
1 QDBusMessage call = QDBusMessage::createMethodCall(distributor, kDistributorPath, kDistributor2, QStringLiteral("Unregister"));
70
3/5
✓ Branch 22 → 23 taken 1 time.
✓ Branch 26 → 27 taken 1 time.
✓ Branch 26 → 28 taken 1 time.
✗ Branch 58 → 59 not taken.
✗ Branch 58 → 60 not taken.
3 call << QVariantMap{{QStringLiteral("token"), token}};
71
2/2
✓ Branch 30 → 31 taken 1 time.
✓ Branch 31 → 32 taken 1 time.
1 const QDBusPendingCall pending = QDBusConnection::sessionBus().asyncCall(call, 30000);
72
3/6
✓ Branch 33 → 34 taken 1 time.
✓ Branch 34 → 35 taken 1 time.
✗ Branch 35 → 36 not taken.
✓ Branch 35 → 37 taken 1 time.
✗ Branch 78 → 79 not taken.
✗ Branch 78 → 80 not taken.
1 auto *watcher = new QDBusPendingCallWatcher(pending);
73
1/3
✓ Branch 39 → 40 taken 1 time.
✗ Branch 83 → 84 not taken.
✗ Branch 83 → 85 not taken.
1 QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher, [distributor, token](QDBusPendingCallWatcher *w) {
74
1/1
✓ Branch 2 → 3 taken 1 time.
1 w->deleteLater();
75
2/3
✓ Branch 3 → 4 taken 1 time.
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 17 not taken.
1 if (!w->isError()) {
76
6/6
✓ Branch 5 → 6 taken 1 time.
✓ Branch 9 → 10 taken 1 time.
✓ Branch 10 → 11 taken 1 time.
✓ Branch 11 → 12 taken 1 time.
✓ Branch 15 → 7 taken 1 time.
✓ Branch 15 → 16 taken 1 time.
2 qCInfo(lcUnifiedPush) << "Unregistered a replaced receiver at" << distributor;
77 1 return;
78 }
79 // Older distributors: Distributor1.Unregister(s token).
80 ✗ QDBusMessage v1 = QDBusMessage::createMethodCall(distributor, kDistributorPath, kDistributor1, QStringLiteral("Unregister"));
81 ✗ v1 << token;
82 ✗ QDBusConnection::sessionBus().asyncCall(v1, 30000);
83 ✗ });
84 3 }
85
86 820 QObject *UnifiedPushReceiver::watchDistributors(QObject *parent, std::function<void()> changed) {
87
1/1
✓ Branch 4 → 5 taken 820 times.
2460 auto *watcher = new QDBusServiceWatcher(QStringLiteral("org.unifiedpush.Distributor*"), QDBusConnection::sessionBus(),
88
2/5
✓ Branch 8 → 9 taken 820 times.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 820 times.
✗ Branch 30 → 31 not taken.
✗ Branch 30 → 32 not taken.
2460 QDBusServiceWatcher::WatchForOwnerChange, parent);
89
1/1
✓ Branch 15 → 16 taken 820 times.
820 QObject::connect(watcher, &QDBusServiceWatcher::serviceOwnerChanged, parent,
90
1/1
✓ Branch 14 → 15 taken 820 times.
1640 [changed](const QString &, const QString &, const QString &) { changed(); });
91 820 return watcher;
92 }
93
94 // --- receiver -------------------------------------------------------------------
95
96
1/1
✓ Branch 5 → 6 taken 11 times.
11 UnifiedPushReceiver::UnifiedPushReceiver(QObject *parent) : PushReceiver(parent) {
97
1/1
✓ Branch 7 → 8 taken 11 times.
11 m_retry.setSingleShot(true);
98
1/1
✓ Branch 8 → 9 taken 11 times.
11 connect(&m_retry, &QTimer::timeout, this, &UnifiedPushReceiver::registerWithDistributor);
99 11 }
100
101 12 UnifiedPushReceiver::~UnifiedPushReceiver() {
102 11 stop();
103 12 }
104
105 9 void UnifiedPushReceiver::start(const Config &config) {
106 9 stop();
107 9 m_config = config;
108 9 m_running = true;
109 9 m_attempts = 0;
110 9 m_connectedOnce = false;
111 9 m_lostDistributor = false;
112 9 auto *connector = UnifiedPushConnector::instance();
113
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 14 taken 9 times.
9 if (!connector->available()) {
114 ✗ setState(DavPushReceiverState::Failed, Failure::Protocol,
115 ✗ QStringLiteral("another MailJD instance owns the push connector"));
116 ✗ return;
117 }
118 9 connector->attach(m_config.channel, this);
119
1/3
✓ Branch 17 → 18 taken 9 times.
✗ Branch 40 → 41 not taken.
✗ Branch 40 → 43 not taken.
18 m_watcher = new QDBusServiceWatcher(m_config.server, QDBusConnection::sessionBus(),
120
2/3
✓ Branch 18 → 19 taken 9 times.
✗ Branch 20 → 21 not taken.
✓ Branch 20 → 23 taken 9 times.
27 QDBusServiceWatcher::WatchForOwnerChange, this);
121 9 connect(m_watcher, &QDBusServiceWatcher::serviceOwnerChanged, this,
122
1/1
✓ Branch 23 → 24 taken 9 times.
11 [this](const QString &, const QString &, const QString &newOwner) { onDistributorOwner(newOwner); });
123
1/1
✓ Branch 26 → 27 taken 9 times.
9 setState(DavPushReceiverState::Connecting);
124 9 registerWithDistributor();
125 }
126
127 23 void UnifiedPushReceiver::stop() {
128
2/2
✓ Branch 2 → 3 taken 14 times.
✓ Branch 2 → 4 taken 9 times.
23 if (!m_running) return;
129 9 m_running = false;
130 9 m_registered = false;
131 9 m_retry.stop();
132
1/2
✓ Branch 5 → 6 taken 9 times.
✗ Branch 5 → 7 not taken.
9 delete m_watcher;
133 9 m_watcher = nullptr;
134 9 UnifiedPushConnector::instance()->detach(m_config.channel, this);
135 9 m_connectedSince = 0;
136
1/1
✓ Branch 10 → 11 taken 9 times.
9 setState(DavPushReceiverState::Stopped);
137 }
138
139 10 void UnifiedPushReceiver::registerWithDistributor() {
140
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 10 times.
10 if (!m_running) return;
141 10 ++m_attempts;
142
1/1
✓ Branch 7 → 8 taken 10 times.
20 QVariantMap args{{QStringLiteral("service"), connectorService()},
143 20 {QStringLiteral("token"), m_config.channel},
144
2/4
✓ Branch 22 → 23 taken 30 times.
✓ Branch 22 → 24 taken 10 times.
✗ Branch 69 → 70 not taken.
✗ Branch 69 → 71 not taken.
60 {QStringLiteral("description"), QStringLiteral("MailJD: calendar, task and contact changes")}};
145
2/3
✓ Branch 34 → 35 taken 10 times.
✗ Branch 34 → 44 not taken.
✓ Branch 39 → 40 taken 10 times.
20 if (!m_config.vapidKey.isEmpty()) args.insert(QStringLiteral("vapid"), m_config.vapidKey);
146
1/1
✓ Branch 47 → 48 taken 10 times.
10 QDBusMessage call = QDBusMessage::createMethodCall(m_config.server, kDistributorPath, kDistributor2, QStringLiteral("Register"));
147
1/1
✓ Branch 51 → 52 taken 10 times.
10 call << args;
148
5/8
✓ Branch 53 → 54 taken 10 times.
✓ Branch 54 → 55 taken 10 times.
✓ Branch 55 → 56 taken 10 times.
✓ Branch 56 → 57 taken 10 times.
✗ Branch 59 → 60 not taken.
✓ Branch 59 → 61 taken 10 times.
✗ Branch 129 → 130 not taken.
✗ Branch 129 → 131 not taken.
10 auto *watcher = new QDBusPendingCallWatcher(QDBusConnection::sessionBus().asyncCall(call, kRegisterTimeoutMs), this);
149
1/1
✓ Branch 61 → 62 taken 10 times.
10 connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *w) {
150
1/1
✓ Branch 2 → 3 taken 10 times.
10 w->deleteLater();
151
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 10 times.
11 if (!m_running) return;
152
1/1
✓ Branch 5 → 6 taken 10 times.
10 QDBusPendingReply<QVariantMap> reply = *w;
153
3/3
✓ Branch 6 → 7 taken 10 times.
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 44 taken 9 times.
10 if (reply.isError()) {
154
2/2
✓ Branch 8 → 9 taken 1 time.
✓ Branch 9 → 10 taken 1 time.
1 const QString name = reply.error().name();
155
2/4
✓ Branch 13 → 14 taken 1 time.
✗ Branch 13 → 17 not taken.
✓ Branch 16 → 17 taken 1 time.
✗ Branch 16 → 18 not taken.
2 if (name == QLatin1String("org.freedesktop.DBus.Error.UnknownMethod") ||
156
1/2
✓ Branch 19 → 20 taken 1 time.
✗ Branch 19 → 22 not taken.
2 name == QLatin1String("org.freedesktop.DBus.Error.UnknownInterface")) {
157
1/1
✓ Branch 20 → 21 taken 1 time.
1 registerVersion1();
158 1 return;
159 }
160 ✗ qCInfo(lcUnifiedPush) << "Register at" << m_config.server << "failed:" << name;
161 ✗ retryLater(Failure::Network, QStringLiteral("the system push service does not answer"));
162 ✗ return;
163 1 }
164
1/1
✓ Branch 44 → 45 taken 9 times.
9 const QVariantMap result = reply.value();
165
3/3
✓ Branch 55 → 56 taken 9 times.
✓ Branch 56 → 57 taken 9 times.
✓ Branch 58 → 59 taken 9 times.
18 onRegistered(result.value(QStringLiteral("success")).toString() == kSucceeded,
166
2/2
✓ Branch 49 → 50 taken 9 times.
✓ Branch 50 → 51 taken 9 times.
27 result.value(QStringLiteral("reason")).toString());
167
2/2
✓ Branch 72 → 73 taken 9 times.
✓ Branch 72 → 75 taken 1 time.
10 });
168 50 }
169
170 1 void UnifiedPushReceiver::registerVersion1() {
171
1/1
✓ Branch 5 → 6 taken 1 time.
1 QDBusMessage call = QDBusMessage::createMethodCall(m_config.server, kDistributorPath, kDistributor1, QStringLiteral("Register"));
172
4/4
✓ Branch 8 → 9 taken 1 time.
✓ Branch 10 → 11 taken 1 time.
✓ Branch 12 → 13 taken 1 time.
✓ Branch 17 → 18 taken 1 time.
2 call << connectorService() << m_config.channel << QStringLiteral("MailJD: calendar, task and contact changes");
173
5/8
✓ Branch 24 → 25 taken 1 time.
✓ Branch 25 → 26 taken 1 time.
✓ Branch 26 → 27 taken 1 time.
✓ Branch 27 → 28 taken 1 time.
✗ Branch 30 → 31 not taken.
✓ Branch 30 → 32 taken 1 time.
✗ Branch 66 → 67 not taken.
✗ Branch 66 → 68 not taken.
1 auto *watcher = new QDBusPendingCallWatcher(QDBusConnection::sessionBus().asyncCall(call, kRegisterTimeoutMs), this);
174
1/1
✓ Branch 32 → 33 taken 1 time.
1 connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *w) {
175
1/1
✓ Branch 2 → 3 taken 1 time.
1 w->deleteLater();
176
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 1 time.
1 if (!m_running) return;
177
1/1
✓ Branch 5 → 6 taken 1 time.
1 QDBusPendingReply<QString, QString> reply = *w;
178
2/3
✓ Branch 6 → 7 taken 1 time.
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 15 taken 1 time.
1 if (reply.isError()) {
179 ✗ retryLater(Failure::Network, QStringLiteral("the system push service does not answer"));
180 ✗ return;
181 }
182
3/3
✓ Branch 15 → 16 taken 1 time.
✓ Branch 16 → 17 taken 1 time.
✓ Branch 18 → 19 taken 1 time.
1 onRegistered(reply.argumentAt<0>() == kSucceeded, reply.argumentAt<1>());
183
1/2
✓ Branch 23 → 24 taken 1 time.
✗ Branch 23 → 26 not taken.
1 });
184 1 }
185
186 10 void UnifiedPushReceiver::onRegistered(bool success, const QString &reason) {
187
2/2
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 35 taken 9 times.
10 if (!success) {
188
7/7
✓ Branch 3 → 4 taken 1 time.
✓ Branch 7 → 8 taken 1 time.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 9 → 10 taken 1 time.
✓ Branch 10 → 11 taken 1 time.
✓ Branch 14 → 5 taken 1 time.
✓ Branch 14 → 15 taken 1 time.
2 qCInfo(lcUnifiedPush) << "Registration refused by" << m_config.server << reason;
189
4/8
✓ Branch 17 → 18 taken 1 time.
✗ Branch 17 → 23 not taken.
✓ Branch 20 → 21 taken 1 time.
✗ Branch 20 → 23 not taken.
✗ Branch 22 → 23 not taken.
✓ Branch 22 → 24 taken 1 time.
✗ Branch 25 → 26 not taken.
✓ Branch 25 → 30 taken 1 time.
1 if (reason == QLatin1String("NETWORK") || reason == QLatin1String("INTERNAL_ERROR") || reason.isEmpty()) {
190 ✗ retryLater(Failure::Network, describeReason(reason));
191 } else {
192 // Needs the user (setup, authorisation, key): no automatic retry.
193
2/2
✓ Branch 30 → 31 taken 1 time.
✓ Branch 31 → 32 taken 1 time.
1 setState(DavPushReceiverState::Failed, Failure::Authentication, describeReason(reason));
194 }
195 1 return;
196 }
197 9 m_registered = true;
198 9 m_attempts = 0;
199
6/6
✓ Branch 35 → 36 taken 9 times.
✓ Branch 39 → 40 taken 9 times.
✓ Branch 40 → 41 taken 9 times.
✓ Branch 41 → 42 taken 9 times.
✓ Branch 45 → 37 taken 9 times.
✓ Branch 45 → 46 taken 9 times.
18 qCInfo(lcUnifiedPush) << "Registered at" << m_config.server;
200 // Connected once the endpoint is known (it may arrive before the reply).
201
2/2
✓ Branch 47 → 48 taken 1 time.
✓ Branch 47 → 49 taken 8 times.
9 if (!m_endpoint.isEmpty()) deliverEndpoint(m_endpoint);
202 }
203
204 ✗ void UnifiedPushReceiver::retryLater(Failure failure, const QString &detail) {
205 // 30 s doubling to 15 min, like the ntfy receiver's backoff.
206 ✗ const int seconds = qMin(900, 30 << qMin(m_attempts - 1, 5));
207 ✗ m_nextAttemptAt = nowMs() + qint64(seconds) * 1000;
208 ✗ m_retry.start(seconds * 1000);
209 ✗ setState(DavPushReceiverState::Backoff, failure, detail);
210 ✗ }
211
212 2 void UnifiedPushReceiver::onDistributorOwner(const QString &newOwner) {
213
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 2 times.
2 if (!m_running) return;
214
2/2
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 26 taken 1 time.
2 if (newOwner.isEmpty()) {
215
7/7
✓ Branch 6 → 7 taken 1 time.
✓ Branch 10 → 11 taken 1 time.
✓ Branch 11 → 12 taken 1 time.
✓ Branch 12 → 13 taken 1 time.
✓ Branch 13 → 14 taken 1 time.
✓ Branch 17 → 8 taken 1 time.
✓ Branch 17 → 18 taken 1 time.
2 qCInfo(lcUnifiedPush) << "System push service" << m_config.server << "stopped";
216 1 m_registered = false;
217 1 m_lostDistributor = true;
218 1 m_connectedSince = 0;
219 1 m_retry.stop();
220
1/1
✓ Branch 22 → 23 taken 1 time.
2 setState(DavPushReceiverState::Backoff, Failure::Network, QStringLiteral("the system push service stopped"));
221 1 return;
222 }
223
7/7
✓ Branch 26 → 27 taken 1 time.
✓ Branch 30 → 31 taken 1 time.
✓ Branch 31 → 32 taken 1 time.
✓ Branch 32 → 33 taken 1 time.
✓ Branch 33 → 34 taken 1 time.
✓ Branch 37 → 28 taken 1 time.
✓ Branch 37 → 38 taken 1 time.
2 qCInfo(lcUnifiedPush) << "System push service" << m_config.server << "is back";
224 1 m_attempts = 0;
225
1/1
✓ Branch 39 → 40 taken 1 time.
1 setState(DavPushReceiverState::Connecting);
226 1 registerWithDistributor();
227 }
228
229 11 void UnifiedPushReceiver::deliverEndpoint(const QString &endpoint) {
230
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 11 times.
11 if (!m_running) return;
231 11 const bool changed = endpoint != m_endpoint;
232 11 m_endpoint = endpoint;
233
10/10
✓ Branch 6 → 7 taken 9 times.
✓ Branch 6 → 22 taken 2 times.
✓ Branch 7 → 8 taken 9 times.
✓ Branch 11 → 12 taken 9 times.
✓ Branch 12 → 13 taken 9 times.
✓ Branch 13 → 14 taken 1 time.
✓ Branch 13 → 15 taken 8 times.
✓ Branch 16 → 17 taken 9 times.
✓ Branch 20 → 9 taken 9 times.
✓ Branch 20 → 21 taken 9 times.
20 if (changed) qCInfo(lcUnifiedPush) << "Push endpoint" << (m_connectedOnce ? "changed" : "received");
234
1/4
✗ Branch 22 → 23 not taken.
✓ Branch 22 → 25 taken 11 times.
✗ Branch 23 → 24 not taken.
✗ Branch 23 → 25 not taken.
11 if (!m_registered && !changed) return;
235 11 m_lastActivity = nowMs();
236
6/6
✓ Branch 27 → 28 taken 2 times.
✓ Branch 27 → 29 taken 9 times.
✓ Branch 28 → 29 taken 1 time.
✓ Branch 28 → 30 taken 1 time.
✓ Branch 31 → 32 taken 10 times.
✓ Branch 31 → 40 taken 1 time.
11 if (state() != DavPushReceiverState::Connected || changed) {
237 10 m_connectedSince = nowMs();
238 10 m_nextAttemptAt = 0;
239 10 const bool afterGap = m_lostDistributor;
240 10 m_lostDistributor = false;
241
1/1
✓ Branch 34 → 35 taken 10 times.
10 setState(DavPushReceiverState::Connected);
242
2/2
✓ Branch 36 → 37 taken 9 times.
✓ Branch 36 → 38 taken 1 time.
10 if (changed) emit stateChanged(); // owners compare endpoint() on every state signal
243 10 emit streamResumed(afterGap);
244 10 m_connectedOnce = true;
245 }
246 }
247
248 7 void UnifiedPushReceiver::deliverMessage(const QByteArray &message, const QString &id) {
249
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 7 times.
7 if (!m_running) return;
250 7 m_lastActivity = nowMs();
251
1/2
✓ Branch 6 → 7 taken 7 times.
✗ Branch 6 → 15 not taken.
7 if (!id.isEmpty()) {
252
2/2
✓ Branch 8 → 9 taken 1 time.
✓ Branch 8 → 10 taken 6 times.
7 if (m_recentIds.contains(id)) return;
253 6 m_recentIds.append(id);
254
1/2
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 6 times.
6 if (m_recentIds.size() > kMaxRecentIds) m_recentIds.removeFirst();
255 6 m_lastMessageId = id;
256 }
257 6 emit bytesReceived(message.size());
258 6 emit messageReceived(message, id);
259 }
260
261 1 void UnifiedPushReceiver::deliverUnregistered() {
262
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 1 time.
1 if (!m_running) return;
263
5/5
✓ Branch 4 → 5 taken 1 time.
✓ Branch 8 → 9 taken 1 time.
✓ Branch 9 → 10 taken 1 time.
✓ Branch 13 → 6 taken 1 time.
✓ Branch 13 → 14 taken 1 time.
2 qCInfo(lcUnifiedPush) << "The system push service removed MailJD's registration";
264 1 m_registered = false;
265 1 m_endpoint.clear();
266 1 m_connectedSince = 0;
267 // Removed by the user in the system settings: no automatic re-register.
268
1/1
✓ Branch 18 → 19 taken 1 time.
2 setState(DavPushReceiverState::Failed, Failure::Authentication,
269 2 QStringLiteral("the registration was removed in the system push service"));
270 }
271
272 // --- connector ----------------------------------------------------------------
273
274 20 UnifiedPushConnector *UnifiedPushConnector::instance() {
275
6/12
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 10 taken 19 times.
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 10 not taken.
✓ Branch 5 → 6 taken 1 time.
✓ Branch 6 → 7 taken 1 time.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 1 time.
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 14 not taken.
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 17 not taken.
20 static UnifiedPushConnector *connector = new UnifiedPushConnector();
276 20 return connector;
277 }
278
279
1/1
✓ Branch 3 → 4 taken 1 time.
1 UnifiedPushConnector::UnifiedPushConnector() {
280
1/1
✓ Branch 7 → 8 taken 1 time.
1 QDBusConnection bus = QDBusConnection::sessionBus();
281
2/3
✓ Branch 8 → 9 taken 1 time.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 21 taken 1 time.
1 if (!bus.isConnected()) {
282 ✗ qCInfo(lcUnifiedPush) << "No session bus: system push service unavailable";
283 ✗ return;
284 }
285
3/6
✓ Branch 21 → 22 taken 1 time.
✓ Branch 22 → 23 taken 1 time.
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 25 taken 1 time.
✗ Branch 77 → 78 not taken.
✗ Branch 77 → 79 not taken.
1 new UnifiedPushConnector2Adaptor(this);
286
3/6
✓ Branch 25 → 26 taken 1 time.
✓ Branch 26 → 27 taken 1 time.
✗ Branch 27 → 28 not taken.
✓ Branch 27 → 29 taken 1 time.
✗ Branch 80 → 81 not taken.
✗ Branch 80 → 82 not taken.
1 new UnifiedPushConnector1Adaptor(this);
287
2/3
✓ Branch 30 → 31 taken 1 time.
✗ Branch 31 → 32 not taken.
✓ Branch 31 → 43 taken 1 time.
1 if (!bus.registerObject(kConnectorPath, this, QDBusConnection::ExportAdaptors)) {
288 ✗ qCWarning(lcUnifiedPush) << "Cannot export the push connector object";
289 ✗ return;
290 }
291
2/2
✓ Branch 43 → 44 taken 1 time.
✓ Branch 44 → 45 taken 1 time.
1 m_available = bus.registerService(UnifiedPushReceiver::connectorService());
292
1/1
✓ Branch 47 → 48 taken 1 time.
1 connect(bus.interface(), &QDBusConnectionInterface::serviceOwnerChanged, this,
293
1/1
✓ Branch 46 → 47 taken 1 time.
1 &UnifiedPushConnector::onNameOwnerChanged);
294
1/2
✗ Branch 49 → 50 not taken.
✓ Branch 49 → 65 taken 1 time.
1 if (!m_available)
295 ✗ qCWarning(lcUnifiedPush) << "Push connector name" << UnifiedPushReceiver::connectorService() << "is taken";
296 1 }
297
298 11 void UnifiedPushConnector::attach(const QString &token, UnifiedPushReceiver *receiver) {
299
2/2
✓ Branch 2 → 3 taken 11 times.
✓ Branch 3 → 4 taken 11 times.
11 m_receivers.insert(token, receiver);
300 11 }
301
302 10 void UnifiedPushConnector::detach(const QString &token, UnifiedPushReceiver *receiver) {
303
1/2
✓ Branch 5 → 6 taken 10 times.
✗ Branch 5 → 7 not taken.
10 if (m_receivers.value(token) == receiver) m_receivers.remove(token);
304 10 }
305
306 24 UnifiedPushReceiver *UnifiedPushConnector::receiverFrom(const QString &token, const QString &caller) const {
307 // S106-065: a guarded pointer across the owner lookup. The lookup is a
308 // blocking bus round trip whose nested event loop can run the deleteLater()
309 // of a receiver being replaced; a raw copy was returned dangling.
310 24 const QPointer<UnifiedPushReceiver> receiver = m_receivers.value(token);
311
3/6
✓ Branch 4 → 5 taken 24 times.
✗ Branch 4 → 7 not taken.
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 24 times.
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 24 taken 24 times.
24 if (!receiver || caller.isEmpty()) {
312 ✗ if (receiver) qCWarning(lcUnifiedPush) << "Ignored a push call from outside a bus call";
313 ✗ return nullptr;
314 }
315 24 const QString service = receiver->distributorService();
316
1/1
✓ Branch 26 → 27 taken 24 times.
24 const QString owner = ownerOf(service);
317
7/8
✓ Branch 28 → 29 taken 23 times.
✓ Branch 28 → 32 taken 1 time.
✗ Branch 31 → 32 not taken.
✓ Branch 31 → 33 taken 23 times.
✓ Branch 34 → 35 taken 23 times.
✓ Branch 34 → 36 taken 1 time.
✓ Branch 36 → 37 taken 1 time.
✓ Branch 36 → 48 taken 23 times.
24 if (!receiver || m_receivers.value(token) != receiver) {
318
5/5
✓ Branch 37 → 38 taken 1 time.
✓ Branch 41 → 42 taken 1 time.
✓ Branch 42 → 43 taken 1 time.
✓ Branch 46 → 39 taken 1 time.
✓ Branch 46 → 47 taken 1 time.
2 qCInfo(lcUnifiedPush) << "Push call dropped: its receiver was replaced during the caller check";
319 1 return nullptr;
320 }
321
5/6
✓ Branch 49 → 50 taken 23 times.
✗ Branch 49 → 52 not taken.
✓ Branch 51 → 52 taken 4 times.
✓ Branch 51 → 53 taken 19 times.
✓ Branch 54 → 55 taken 4 times.
✓ Branch 54 → 66 taken 19 times.
23 if (owner.isEmpty() || owner != caller) {
322
5/5
✓ Branch 55 → 56 taken 4 times.
✓ Branch 59 → 60 taken 4 times.
✓ Branch 60 → 61 taken 4 times.
✓ Branch 64 → 57 taken 4 times.
✓ Branch 64 → 65 taken 4 times.
8 qCWarning(lcUnifiedPush) << "Ignored a push call from a bus client that is not the registered distributor";
323 4 return nullptr;
324 }
325 19 return receiver.data();
326 24 }
327
328 // S106-066: the owner of a distributor name is looked up once and then
329 // followed through NameOwnerChanged — every connector call used to pay a
330 // blocking bus round trip, which a flood of calls multiplied.
331 26 QString UnifiedPushConnector::ownerOf(const QString &service) const {
332
2/2
✓ Branch 5 → 6 taken 19 times.
✓ Branch 5 → 8 taken 7 times.
26 if (const auto cached = m_owners.constFind(service); cached != m_owners.constEnd())
333 19 return *cached;
334 7 QString owner;
335
2/2
✓ Branch 9 → 11 taken 3 times.
✓ Branch 9 → 15 taken 4 times.
7 if (m_ownerLookup) {
336
1/1
✓ Branch 11 → 12 taken 3 times.
3 owner = m_ownerLookup(service);
337 } else {
338
3/3
✓ Branch 15 → 16 taken 4 times.
✓ Branch 16 → 17 taken 4 times.
✓ Branch 17 → 18 taken 4 times.
4 const QDBusReply<QString> reply = QDBusConnection::sessionBus().interface()->serviceOwner(service);
339
2/3
✓ Branch 19 → 20 taken 4 times.
✓ Branch 20 → 21 taken 4 times.
✗ Branch 20 → 22 not taken.
4 owner = reply.isValid() ? reply.value() : QString();
340 4 }
341
2/3
✓ Branch 28 → 29 taken 7 times.
✗ Branch 28 → 30 not taken.
✓ Branch 29 → 30 taken 7 times.
7 if (!owner.isEmpty()) m_owners.insert(service, owner);
342 7 return owner;
343 7 }
344
345 4 void UnifiedPushConnector::onNameOwnerChanged(const QString &name, const QString &, const QString &newOwner) {
346
2/2
✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 5 taken 2 times.
4 if (newOwner.isEmpty()) m_owners.remove(name);
347
1/2
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 8 taken 2 times.
2 else if (m_owners.contains(name)) m_owners.insert(name, newOwner);
348 4 }
349
350 1 UnifiedPushConnector2Adaptor::UnifiedPushConnector2Adaptor(UnifiedPushConnector *connector)
351 1 : QDBusAbstractAdaptor(connector), m_connector(connector) {}
352
353 8 QVariantMap UnifiedPushConnector2Adaptor::Message(const QVariantMap &args) {
354
2/2
✓ Branch 6 → 7 taken 8 times.
✓ Branch 7 → 8 taken 8 times.
16 const QString id = args.value(QStringLiteral("id")).toString();
355 // S106-066: an oversized message is dropped before any lookup (the
356 // decrypt path accepts at most kMaxEncryptedBytes anyway).
357
4/4
✓ Branch 16 → 17 taken 8 times.
✓ Branch 17 → 18 taken 8 times.
✓ Branch 24 → 25 taken 1 time.
✓ Branch 24 → 42 taken 7 times.
16 if (args.value(QStringLiteral("message")).toByteArray().size() > WebPushPayloadCodec::kMaxEncryptedBytes)
358
6/18
✗ Branch 26 → 27 not taken.
✓ Branch 26 → 28 taken 1 time.
✓ Branch 33 → 34 taken 1 time.
✗ Branch 33 → 37 not taken.
✓ Branch 35 → 36 taken 1 time.
✓ Branch 35 → 37 taken 1 time.
✓ Branch 37 → 38 taken 1 time.
✗ Branch 37 → 39 not taken.
✓ Branch 39 → 40 taken 1 time.
✗ Branch 39 → 41 not taken.
✗ Branch 111 → 112 not taken.
✗ Branch 111 → 115 not taken.
✗ Branch 113 → 114 not taken.
✗ Branch 113 → 115 not taken.
✗ Branch 117 → 118 not taken.
✗ Branch 117 → 119 not taken.
✗ Branch 121 → 122 not taken.
✗ Branch 121 → 123 not taken.
4 return id.isEmpty() ? QVariantMap{} : QVariantMap{{QStringLiteral("id"), id}};
359
6/6
✓ Branch 42 → 43 taken 7 times.
✓ Branch 47 → 48 taken 7 times.
✓ Branch 48 → 49 taken 7 times.
✓ Branch 49 → 50 taken 7 times.
✓ Branch 56 → 57 taken 6 times.
✓ Branch 56 → 70 taken 1 time.
14 if (auto *receiver = m_connector->receiverFrom(args.value(QStringLiteral("token")).toString(), m_connector->callingService()))
360
3/3
✓ Branch 61 → 62 taken 6 times.
✓ Branch 62 → 63 taken 6 times.
✓ Branch 63 → 64 taken 6 times.
12 receiver->deliverMessage(args.value(QStringLiteral("message")).toByteArray(), id);
361 7 QVariantMap result;
362
2/3
✓ Branch 71 → 72 taken 7 times.
✗ Branch 71 → 81 not taken.
✓ Branch 76 → 77 taken 7 times.
14 if (!id.isEmpty()) result.insert(QStringLiteral("id"), id); // acknowledges the message
363 7 return result;
364 10 }
365
366 10 QVariantMap UnifiedPushConnector2Adaptor::NewEndpoint(const QVariantMap &args) {
367
6/6
✓ Branch 2 → 3 taken 10 times.
✓ Branch 7 → 8 taken 10 times.
✓ Branch 8 → 9 taken 10 times.
✓ Branch 9 → 10 taken 10 times.
✓ Branch 16 → 17 taken 9 times.
✓ Branch 16 → 30 taken 1 time.
20 if (auto *receiver = m_connector->receiverFrom(args.value(QStringLiteral("token")).toString(), m_connector->callingService()))
368
3/3
✓ Branch 21 → 22 taken 9 times.
✓ Branch 22 → 23 taken 9 times.
✓ Branch 23 → 24 taken 9 times.
18 receiver->deliverEndpoint(args.value(QStringLiteral("endpoint")).toString());
369 10 return {};
370 }
371
372 2 QVariantMap UnifiedPushConnector2Adaptor::Unregistered(const QVariantMap &args) {
373
6/6
✓ Branch 2 → 3 taken 2 times.
✓ Branch 7 → 8 taken 2 times.
✓ Branch 8 → 9 taken 2 times.
✓ Branch 9 → 10 taken 2 times.
✓ Branch 16 → 17 taken 1 time.
✓ Branch 16 → 18 taken 1 time.
4 if (auto *receiver = m_connector->receiverFrom(args.value(QStringLiteral("token")).toString(), m_connector->callingService()))
374 1 receiver->deliverUnregistered();
375 2 return {};
376 }
377
378 1 UnifiedPushConnector1Adaptor::UnifiedPushConnector1Adaptor(UnifiedPushConnector *connector)
379 1 : QDBusAbstractAdaptor(connector), m_connector(connector) {}
380
381 1 void UnifiedPushConnector1Adaptor::Message(const QString &token, const QByteArray &message, const QString &id) {
382
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 1 time.
1 if (message.size() > WebPushPayloadCodec::kMaxEncryptedBytes) return; // S106-066
383
3/4
✓ Branch 5 → 6 taken 1 time.
✓ Branch 6 → 7 taken 1 time.
✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 10 not taken.
1 if (auto *receiver = m_connector->receiverFrom(token, m_connector->callingService()))
384 1 receiver->deliverMessage(message, id);
385 }
386
387 1 void UnifiedPushConnector1Adaptor::NewEndpoint(const QString &token, const QString &endpoint) {
388
3/4
✓ Branch 2 → 3 taken 1 time.
✓ Branch 3 → 4 taken 1 time.
✓ Branch 5 → 6 taken 1 time.
✗ Branch 5 → 7 not taken.
1 if (auto *receiver = m_connector->receiverFrom(token, m_connector->callingService())) receiver->deliverEndpoint(endpoint);
389 1 }
390
391 ✗ void UnifiedPushConnector1Adaptor::Unregistered(const QString &token) {
392 ✗ if (auto *receiver = m_connector->receiverFrom(token, m_connector->callingService())) receiver->deliverUnregistered();
393 ✗ }
394