1/*
2 SPDX-FileCopyrightText: 2009 Will Stephenson <wstephenson@kde.org>
3 SPDX-FileCopyrightText: 2012-2013 Jan Grulich <jgrulich@redhat.com>
4
5 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6*/
7
8#include <QRegularExpression>
9
10#include "time.h"
11#include "utils.h"
12
13QHostAddress NetworkManager::ipv6AddressAsHostAddress(const QByteArray &address)
14{
15 // Q_ASSERT(address.size() == 16);
16 Q_IPV6ADDR tmp;
17 for (int i = 0; i < 16; ++i) {
18 tmp[i] = address[i];
19 }
20 QHostAddress hostaddress(tmp);
21 Q_ASSERT(hostaddress.protocol() == QAbstractSocket::IPv6Protocol);
22
23 return hostaddress;
24}
25
26QByteArray NetworkManager::ipv6AddressFromHostAddress(const QHostAddress &address)
27{
28 // Q_ASSERT(address.protocol() == QAbstractSocket::IPv6Protocol);
29 Q_IPV6ADDR tmp = address.toIPv6Address();
30 QByteArray assembledAddress;
31 assembledAddress.reserve(asize: 16);
32 for (int i = 0; i < 16; ++i) {
33 assembledAddress.push_back(c: tmp[i]);
34 }
35
36 return assembledAddress;
37}
38
39QString NetworkManager::macAddressAsString(const QByteArray &ba)
40{
41 QStringList mac;
42
43 for (int i = 0; i < ba.size(); ++i) {
44 mac << QString("%1").arg(a: (quint8)ba[i], fieldWidth: 2, base: 16, fillChar: QLatin1Char('0')).toUpper();
45 }
46
47 return mac.join(sep: ":");
48}
49
50QByteArray NetworkManager::macAddressFromString(const QString &s)
51{
52 const QStringList macStringList = s.split(sep: ':');
53 // Q_ASSERT(macStringList.size() == 6);
54 QByteArray ba;
55 if (!s.isEmpty()) {
56 ba.resize(size: 6);
57 int i = 0;
58
59 for (const QString &macPart : macStringList) {
60 ba[i++] = macPart.toUInt(ok: nullptr, base: 16);
61 }
62 }
63 return ba;
64}
65
66bool NetworkManager::macAddressIsValid(const QString &macAddress)
67{
68 QRegularExpression macAddressCheck(QStringLiteral("([a-fA-F0-9][a-fA-F0-9]:){5}[0-9a-fA-F][0-9a-fA-F]"));
69
70 return macAddressCheck.match(subject: macAddress).hasMatch();
71}
72
73bool NetworkManager::macAddressIsValid(const QByteArray &macAddress)
74{
75 return macAddressIsValid(macAddress: macAddressAsString(ba: macAddress));
76}
77
78int NetworkManager::findChannel(int freq)
79{
80 int channel;
81 if (freq < 2500) {
82 channel = 0;
83 int i = 0;
84 QList<QPair<int, int>> bFreqs = getBFreqs();
85 while (i < bFreqs.size()) {
86 if (bFreqs.at(i).second <= freq) {
87 channel = bFreqs.at(i).first;
88 } else {
89 break;
90 }
91 i++;
92 }
93 return channel;
94 }
95 channel = 0;
96 int i = 0;
97 QList<QPair<int, int>> aFreqs = getAFreqs();
98 while (i < aFreqs.size()) {
99 if (aFreqs.at(i).second <= freq) {
100 channel = aFreqs.at(i).first;
101 if (aFreqs.at(i).second == freq) {
102 break;
103 }
104 } else {
105 break;
106 }
107 i++;
108 }
109
110 return channel;
111}
112
113NetworkManager::WirelessSetting::FrequencyBand NetworkManager::findFrequencyBand(int freq)
114{
115 if (freq < 2500) {
116 return WirelessSetting::Bg;
117 }
118
119 return WirelessSetting::A;
120}
121
122bool NetworkManager::deviceSupportsApCiphers(NetworkManager::WirelessDevice::Capabilities interfaceCaps,
123 NetworkManager::AccessPoint::WpaFlags apCiphers,
124 WirelessSecurityType type)
125{
126 bool havePair = false;
127 bool haveGroup = true;
128
129 if (type == NetworkManager::StaticWep) {
130 havePair = true;
131 } else {
132 if (interfaceCaps.testFlag(flag: NetworkManager::WirelessDevice::Wep40) && apCiphers.testFlag(flag: NetworkManager::AccessPoint::PairWep40)) {
133 havePair = true;
134 }
135 if (interfaceCaps.testFlag(flag: NetworkManager::WirelessDevice::Wep104) && apCiphers.testFlag(flag: NetworkManager::AccessPoint::PairWep104)) {
136 havePair = true;
137 }
138 if (interfaceCaps.testFlag(flag: NetworkManager::WirelessDevice::Tkip) && apCiphers.testFlag(flag: NetworkManager::AccessPoint::PairTkip)) {
139 havePair = true;
140 }
141 if (interfaceCaps.testFlag(flag: NetworkManager::WirelessDevice::Ccmp) && apCiphers.testFlag(flag: NetworkManager::AccessPoint::PairCcmp)) {
142 havePair = true;
143 }
144 }
145
146 if (interfaceCaps.testFlag(flag: NetworkManager::WirelessDevice::Wep40) && apCiphers.testFlag(flag: NetworkManager::AccessPoint::GroupWep40)) {
147 haveGroup = true;
148 }
149 if (interfaceCaps.testFlag(flag: NetworkManager::WirelessDevice::Wep104) && apCiphers.testFlag(flag: NetworkManager::AccessPoint::GroupWep104)) {
150 haveGroup = true;
151 }
152 if (type != StaticWep) {
153 if (interfaceCaps.testFlag(flag: NetworkManager::WirelessDevice::Tkip) && apCiphers.testFlag(flag: NetworkManager::AccessPoint::GroupTkip)) {
154 haveGroup = true;
155 }
156 if (interfaceCaps.testFlag(flag: NetworkManager::WirelessDevice::Ccmp) && apCiphers.testFlag(flag: NetworkManager::AccessPoint::GroupCcmp)) {
157 haveGroup = true;
158 }
159 }
160
161 return (havePair && haveGroup);
162}
163
164// Keep this in sync with NetworkManager/libnm-core/nm-utils.c:nm_utils_security_valid()
165bool NetworkManager::securityIsValid(WirelessSecurityType type,
166 NetworkManager::WirelessDevice::Capabilities interfaceCaps,
167 bool haveAp,
168 bool adhoc,
169 NetworkManager::AccessPoint::Capabilities apCaps,
170 NetworkManager::AccessPoint::WpaFlags apWpa,
171 NetworkManager::AccessPoint::WpaFlags apRsn)
172{
173 bool good = true;
174
175 // kDebug() << "type(" << type << ") interfaceCaps(" << interfaceCaps << ") haveAp(" << haveAp << ") adhoc(" << adhoc << ") apCaps(" << apCaps << ") apWpa("
176 // << apWpa << " apRsn(" << apRsn << ")";
177
178 if (!haveAp) {
179 if (type == NoneSecurity) {
180 return true;
181 }
182 if ((type == StaticWep) //
183 || ((type == DynamicWep) && !adhoc) //
184 || ((type == Leap) && !adhoc)) {
185 if (interfaceCaps.testFlag(flag: NetworkManager::WirelessDevice::Wep40) || interfaceCaps.testFlag(flag: NetworkManager::WirelessDevice::Wep104)) {
186 return true;
187 } else {
188 return false;
189 }
190 }
191
192 // apCaps.testFlag(Privacy) == true for StaticWep, Leap and DynamicWep
193 // see libs/internals/wirelessinterfaceconnectionhelpers.cpp
194
195 // TODO: this is not in nm-utils.c
196 // if (type == Knm::WirelessSecurity::WpaPsk
197 // || ((type == Knm::WirelessSecurity::WpaEap) && !adhoc)) {
198 // if (interfaceCaps.testFlag(NetworkManager::WirelessDevice::Wpa) &&
199 // !apCaps.testFlag(NetworkManager::AccessPoint::Privacy)) {
200 // return true;
201 // }
202 // }
203 // if (type == Knm::WirelessSecurity::Wpa2Psk
204 // || ((type == Knm::WirelessSecurity::Wpa2Eap) && !adhoc)) {
205 // if (interfaceCaps.testFlag(NetworkManager::WirelessDevice::Rsn) &&
206 // !apCaps.testFlag(NetworkManager::AccessPoint::Privacy)) {
207 // return true;
208 // }
209 // }
210 }
211
212 switch (type) {
213 case NoneSecurity:
214 Q_ASSERT(haveAp);
215 if (apCaps.testFlag(flag: NetworkManager::AccessPoint::Privacy)) {
216 return false;
217 }
218 if (apWpa || apRsn) {
219 return false;
220 }
221 break;
222 case Leap: /* require PRIVACY bit for LEAP? */
223 if (adhoc) {
224 return false;
225 }
226 /* Fall through */
227 case StaticWep:
228 Q_ASSERT(haveAp);
229 if (!apCaps.testFlag(flag: NetworkManager::AccessPoint::Privacy)) {
230 return false;
231 }
232 if (apWpa || apRsn) {
233 if (!deviceSupportsApCiphers(interfaceCaps, apCiphers: apWpa, type: StaticWep)) {
234 if (!deviceSupportsApCiphers(interfaceCaps, apCiphers: apRsn, type: StaticWep)) {
235 return false;
236 }
237 }
238 }
239 break;
240 case DynamicWep:
241 if (adhoc) {
242 return false;
243 }
244 Q_ASSERT(haveAp);
245 if (apRsn || !(apCaps.testFlag(flag: NetworkManager::AccessPoint::Privacy))) {
246 return false;
247 }
248 /* Some APs broadcast minimal WPA-enabled beacons that must be handled */
249 if (apWpa) {
250 if (!apWpa.testFlag(flag: NetworkManager::AccessPoint::KeyMgmt8021x)) {
251 return false;
252 }
253 if (!deviceSupportsApCiphers(interfaceCaps, apCiphers: apWpa, type: DynamicWep)) {
254 return false;
255 }
256 }
257 break;
258 case WpaPsk:
259 if (adhoc) {
260 return false;
261 }
262
263 if (!interfaceCaps.testFlag(flag: NetworkManager::WirelessDevice::Wpa)) {
264 return false;
265 }
266 if (haveAp) {
267 if (apWpa.testFlag(flag: NetworkManager::AccessPoint::KeyMgmtPsk)) {
268 if (apWpa.testFlag(flag: NetworkManager::AccessPoint::PairTkip) //
269 && interfaceCaps.testFlag(flag: NetworkManager::WirelessDevice::Tkip)) {
270 return true;
271 }
272 if (apWpa.testFlag(flag: NetworkManager::AccessPoint::PairCcmp) //
273 && interfaceCaps.testFlag(flag: NetworkManager::WirelessDevice::Ccmp)) {
274 return true;
275 }
276 }
277 return false;
278 }
279 break;
280 case Wpa2Psk:
281 if (!interfaceCaps.testFlag(flag: NetworkManager::WirelessDevice::Rsn)) {
282 return false;
283 }
284 if (haveAp) {
285 if (adhoc) {
286 if (!interfaceCaps.testFlag(flag: NetworkManager::WirelessDevice::IBSSRsn)) {
287 return false;
288 }
289 if (apRsn.testFlag(flag: NetworkManager::AccessPoint::PairCcmp) //
290 && interfaceCaps.testFlag(flag: NetworkManager::WirelessDevice::Ccmp)) {
291 return true;
292 }
293 } else {
294 if (apRsn.testFlag(flag: NetworkManager::AccessPoint::KeyMgmtPsk)) {
295 if (apRsn.testFlag(flag: NetworkManager::AccessPoint::PairTkip) //
296 && interfaceCaps.testFlag(flag: NetworkManager::WirelessDevice::Tkip)) {
297 return true;
298 }
299 if (apRsn.testFlag(flag: NetworkManager::AccessPoint::PairCcmp) //
300 && interfaceCaps.testFlag(flag: NetworkManager::WirelessDevice::Ccmp)) {
301 return true;
302 }
303 }
304 }
305 return false;
306 }
307 break;
308 case WpaEap:
309 if (adhoc) {
310 return false;
311 }
312 if (!interfaceCaps.testFlag(flag: NetworkManager::WirelessDevice::Wpa)) {
313 return false;
314 }
315 if (haveAp) {
316 if (!apWpa.testFlag(flag: NetworkManager::AccessPoint::KeyMgmt8021x)) {
317 return false;
318 }
319 /* Ensure at least one WPA cipher is supported */
320 if (!deviceSupportsApCiphers(interfaceCaps, apCiphers: apWpa, type: WpaEap)) {
321 return false;
322 }
323 }
324 break;
325 case Wpa2Eap:
326 if (adhoc) {
327 return false;
328 }
329 if (!interfaceCaps.testFlag(flag: NetworkManager::WirelessDevice::Rsn)) {
330 return false;
331 }
332 if (haveAp) {
333 if (!apRsn.testFlag(flag: NetworkManager::AccessPoint::KeyMgmt8021x)) {
334 return false;
335 }
336 /* Ensure at least one WPA cipher is supported */
337 if (!deviceSupportsApCiphers(interfaceCaps, apCiphers: apRsn, type: Wpa2Eap)) {
338 return false;
339 }
340 }
341 break;
342 case SAE:
343 if (!interfaceCaps.testFlag(flag: NetworkManager::WirelessDevice::Rsn)) {
344 return false;
345 }
346 if (haveAp) {
347 if (adhoc) {
348 if (!interfaceCaps.testFlag(flag: NetworkManager::WirelessDevice::IBSSRsn)) {
349 return false;
350 }
351 if (apRsn.testFlag(flag: NetworkManager::AccessPoint::PairCcmp) //
352 && interfaceCaps.testFlag(flag: NetworkManager::WirelessDevice::Ccmp)) {
353 return true;
354 }
355 } else {
356 if (apRsn.testFlag(flag: NetworkManager::AccessPoint::KeyMgmtSAE)) {
357 if (apRsn.testFlag(flag: NetworkManager::AccessPoint::PairTkip) //
358 && interfaceCaps.testFlag(flag: NetworkManager::WirelessDevice::Tkip)) {
359 return true;
360 }
361 if (apRsn.testFlag(flag: NetworkManager::AccessPoint::PairCcmp) //
362 && interfaceCaps.testFlag(flag: NetworkManager::WirelessDevice::Ccmp)) {
363 return true;
364 }
365 }
366 }
367 return false;
368 }
369 break;
370 case Wpa3SuiteB192:
371 if (adhoc) {
372 return false;
373 }
374 if (!interfaceCaps.testFlag(flag: NetworkManager::WirelessDevice::Rsn)) {
375 return false;
376 }
377 if (haveAp && !apRsn.testFlag(flag: NetworkManager::AccessPoint::KeyMgmtEapSuiteB192)) {
378 return false;
379 }
380 break;
381 default:
382 good = false;
383 break;
384 }
385
386 return good;
387}
388
389NetworkManager::WirelessSecurityType NetworkManager::findBestWirelessSecurity(NetworkManager::WirelessDevice::Capabilities interfaceCaps,
390 bool haveAp,
391 bool adHoc,
392 NetworkManager::AccessPoint::Capabilities apCaps,
393 NetworkManager::AccessPoint::WpaFlags apWpa,
394 NetworkManager::AccessPoint::WpaFlags apRsn)
395{
396 // The ordering of this list is a pragmatic combination of security level and popularity.
397 // Therefore static WEP is before LEAP and Dynamic WEP because there is no way to detect
398 // if an AP is capable of Dynamic WEP and showing Dynamic WEP first would confuse
399 // Static WEP users.
400 const QList<NetworkManager::WirelessSecurityType> types = {NetworkManager::Wpa3SuiteB192,
401 NetworkManager::SAE,
402 NetworkManager::Wpa2Eap,
403 NetworkManager::Wpa2Psk,
404 NetworkManager::WpaEap,
405 NetworkManager::WpaPsk,
406 NetworkManager::StaticWep,
407 NetworkManager::DynamicWep,
408 NetworkManager::Leap,
409 NetworkManager::NoneSecurity};
410
411 for (NetworkManager::WirelessSecurityType type : types) {
412 if (NetworkManager::securityIsValid(type, interfaceCaps, haveAp, adhoc: adHoc, apCaps, apWpa, apRsn)) {
413 return type;
414 }
415 }
416 return NetworkManager::UnknownSecurity;
417}
418
419bool NetworkManager::wepKeyIsValid(const QString &key, NetworkManager::WirelessSecuritySetting::WepKeyType type)
420{
421 if (key.isEmpty()) {
422 return false;
423 }
424
425 const int keylen = key.length();
426
427 if (type != WirelessSecuritySetting::NotSpecified) {
428 if (type == WirelessSecuritySetting::Hex) {
429 if (keylen == 10 || keylen == 26) {
430 /* Hex key */
431 for (int i = 0; i < keylen; ++i) {
432 if (!(key.at(i).isDigit() || (key.at(i) >= 'A' && key.at(i) <= 'F') || (key.at(i) >= 'a' && key.at(i) <= 'f'))) {
433 return false;
434 }
435 }
436 return true;
437 } else if (keylen == 5 || keylen == 13) {
438 /* ASCII KEY */
439 for (int i = 0; i < keylen; ++i) {
440 if (!key.at(i).isPrint()) {
441 return false;
442 }
443 }
444 return true;
445 }
446
447 return false;
448 } else if (type == WirelessSecuritySetting::Passphrase) {
449 if (!keylen || keylen > 64) {
450 return false;
451 }
452
453 return true;
454 }
455 }
456
457 return false;
458}
459
460bool NetworkManager::wpaPskIsValid(const QString &psk)
461{
462 if (psk.isEmpty()) {
463 return false;
464 }
465
466 const int psklen = psk.length();
467
468 if (psklen < 8 || psklen > 64) {
469 return false;
470 }
471
472 if (psklen == 64) {
473 /* Hex PSK */
474 for (int i = 0; i < psklen; ++i) {
475 if (!psk.at(i).isLetterOrNumber()) {
476 return false;
477 }
478 }
479 }
480
481 return true;
482}
483
484NetworkManager::WirelessSecurityType NetworkManager::securityTypeFromConnectionSetting(const NetworkManager::ConnectionSettings::Ptr &settings)
485{
486 NetworkManager::WirelessSecuritySetting::Ptr wifiSecuritySetting = settings->setting(Setting::WirelessSecurity).dynamicCast<WirelessSecuritySetting>();
487 if (wifiSecuritySetting->keyMgmt() == WirelessSecuritySetting::Wep) {
488 return StaticWep;
489 } else if (wifiSecuritySetting->keyMgmt() == WirelessSecuritySetting::Ieee8021x) {
490 if (wifiSecuritySetting->authAlg() == WirelessSecuritySetting::Leap) {
491 return Leap;
492 } else {
493 return DynamicWep;
494 }
495 } else if (wifiSecuritySetting->keyMgmt() == WirelessSecuritySetting::WpaPsk) {
496 if (wifiSecuritySetting->proto().contains(t: WirelessSecuritySetting::Wpa) && !wifiSecuritySetting->proto().contains(t: WirelessSecuritySetting::Rsn)) {
497 return WpaPsk;
498 }
499 return Wpa2Psk;
500 } else if (wifiSecuritySetting->keyMgmt() == WirelessSecuritySetting::WpaEap) {
501 if (wifiSecuritySetting->proto().contains(t: WirelessSecuritySetting::Wpa) && !wifiSecuritySetting->proto().contains(t: WirelessSecuritySetting::Rsn)) {
502 return WpaEap;
503 }
504 return Wpa2Eap;
505 } else if (wifiSecuritySetting->keyMgmt() == WirelessSecuritySetting::SAE) {
506 return SAE;
507 } else if (wifiSecuritySetting->keyMgmt() == WirelessSecuritySetting::WpaEapSuiteB192) {
508 return Wpa3SuiteB192;
509 }
510
511 return NoneSecurity;
512}
513
514QList<QPair<int, int>> NetworkManager::getBFreqs()
515{
516 QList<QPair<int, int>> freqs;
517
518 freqs.append(t: QPair<int, int>(1, 2412));
519 freqs.append(t: QPair<int, int>(2, 2417));
520 freqs.append(t: QPair<int, int>(3, 2422));
521 freqs.append(t: QPair<int, int>(4, 2427));
522 freqs.append(t: QPair<int, int>(5, 2432));
523 freqs.append(t: QPair<int, int>(6, 2437));
524 freqs.append(t: QPair<int, int>(7, 2442));
525 freqs.append(t: QPair<int, int>(8, 2447));
526 freqs.append(t: QPair<int, int>(9, 2452));
527 freqs.append(t: QPair<int, int>(10, 2457));
528 freqs.append(t: QPair<int, int>(11, 2462));
529 freqs.append(t: QPair<int, int>(12, 2467));
530 freqs.append(t: QPair<int, int>(13, 2472));
531 freqs.append(t: QPair<int, int>(14, 2484));
532
533 return freqs;
534}
535
536QList<QPair<int, int>> NetworkManager::getAFreqs()
537{
538 QList<QPair<int, int>> freqs;
539
540 freqs.append(t: QPair<int, int>(7, 5035));
541 freqs.append(t: QPair<int, int>(8, 5040));
542 freqs.append(t: QPair<int, int>(9, 5045));
543 freqs.append(t: QPair<int, int>(11, 5055));
544 freqs.append(t: QPair<int, int>(12, 5060));
545 freqs.append(t: QPair<int, int>(16, 5080));
546 freqs.append(t: QPair<int, int>(34, 5170));
547 freqs.append(t: QPair<int, int>(36, 5180));
548 freqs.append(t: QPair<int, int>(38, 5190));
549 freqs.append(t: QPair<int, int>(40, 5200));
550 freqs.append(t: QPair<int, int>(42, 5210));
551 freqs.append(t: QPair<int, int>(44, 5220));
552 freqs.append(t: QPair<int, int>(46, 5230));
553 freqs.append(t: QPair<int, int>(48, 5240));
554 freqs.append(t: QPair<int, int>(52, 5260));
555 freqs.append(t: QPair<int, int>(56, 5280));
556 freqs.append(t: QPair<int, int>(60, 5300));
557 freqs.append(t: QPair<int, int>(64, 5320));
558 freqs.append(t: QPair<int, int>(100, 5500));
559 freqs.append(t: QPair<int, int>(104, 5520));
560 freqs.append(t: QPair<int, int>(108, 5540));
561 freqs.append(t: QPair<int, int>(112, 5560));
562 freqs.append(t: QPair<int, int>(116, 5580));
563 freqs.append(t: QPair<int, int>(120, 5600));
564 freqs.append(t: QPair<int, int>(124, 5620));
565 freqs.append(t: QPair<int, int>(128, 5640));
566 freqs.append(t: QPair<int, int>(132, 5660));
567 freqs.append(t: QPair<int, int>(136, 5680));
568 freqs.append(t: QPair<int, int>(140, 5700));
569 freqs.append(t: QPair<int, int>(149, 5745));
570 freqs.append(t: QPair<int, int>(153, 5765));
571 freqs.append(t: QPair<int, int>(157, 5785));
572 freqs.append(t: QPair<int, int>(161, 5805));
573 freqs.append(t: QPair<int, int>(165, 5825));
574 freqs.append(t: QPair<int, int>(183, 4915));
575 freqs.append(t: QPair<int, int>(184, 4920));
576 freqs.append(t: QPair<int, int>(185, 4925));
577 freqs.append(t: QPair<int, int>(187, 4935));
578 freqs.append(t: QPair<int, int>(188, 4940));
579 freqs.append(t: QPair<int, int>(189, 4945));
580 freqs.append(t: QPair<int, int>(192, 4960));
581 freqs.append(t: QPair<int, int>(196, 4980));
582
583 return freqs;
584}
585
586QDateTime NetworkManager::clockBootTimeToDateTime(qlonglong clockBootime)
587{
588 clockid_t clk_id = CLOCK_BOOTTIME;
589 struct timespec tp;
590 int r;
591
592 // now is used as a point of reference
593 // with the timespec that contains the number of msec since boot
594 QDateTime now = QDateTime::currentDateTime();
595 r = clock_gettime(clock_id: clk_id, tp: &tp);
596 if (r == -1 && errno == EINVAL) {
597 clk_id = CLOCK_MONOTONIC;
598 r = clock_gettime(clock_id: clk_id, tp: &tp);
599 }
600
601 // convert to msecs
602 long now_msecs = tp.tv_sec * 1000 + tp.tv_nsec / 1000000;
603
604 // diff the msecs and construct a QDateTime based on the offset
605 QDateTime res;
606 if (clockBootime > now_msecs) {
607 qlonglong offset = clockBootime - now_msecs;
608 res = QDateTime::fromMSecsSinceEpoch(msecs: now.toMSecsSinceEpoch() + offset);
609 } else {
610 qlonglong offset = now_msecs - clockBootime;
611 res = QDateTime::fromMSecsSinceEpoch(msecs: now.toMSecsSinceEpoch() - offset);
612 }
613
614 return res;
615}
616

source code of networkmanager-qt/src/utils.cpp