Commit Graph

1570 Commits

Author SHA1 Message Date
Qt Submodule Update Bot 752ffff2dd Update dependencies on 'dev' in qt/qtgrpc
Change-Id: I9502e1255dbed9f7951d4bd66bef152f456307e7
Reviewed-by: Qt Submodule Update Bot <qt_submodule_update_bot@qt-project.org>
2025-10-03 10:52:06 +00:00
Dennis Oberst efaba18e4a QGrpcHttp2Channel: Implement dataframe decompression
Implement the missing decompression handling. We were advertising an
accepted encoding of GrpcAcceptEncodingValue("identity,deflate,gzip")
but only ever handled identity (i.e. no compression) !

We now check the negotiated content-encoding in the initialMetadata to
set up the appropriate decompression. Since compression can vary per
message, dynamic processing is required.

[ChangeLog][QGrpcHttp2Channel] Added missing decompression handling for
'deflate' and 'gzip'.

Fixes: QTBUG-129286
Pick-to: 6.10 6.9 6.8
Change-Id: I3ed4af7b21b51c52bccc3e6c314ae166e80c94a8
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
2025-10-02 07:41:39 +02:00
Dennis Oberst dbf2b504c7 QGrpcHttp2Channel: Report data loss for incomplete trailing messages
In case we receive the endStream flag when processing received data, we
now return a non-ok QGrpcStatus if there's still unprocessed data in the
intermediate container.

[ChangeLog][QGrpcHttp2Channel][Important Behavior Changes] finishes the
communication with a non-ok QGrpcStatus (DataLoss) when the stream is
closed with unprocessed trailing data.

Pick-to: 6.10 6.9 6.8
Change-Id: I53d36c012d6cab57f4d8b652518ff5ccd5be24f2
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
2025-10-02 07:41:35 +02:00
Dennis Oberst 88b73b67b0 QGrpcHttp2Channel: Replace ExpectedData with GrpcDataParser
We are missing functionality! We are omitting any compressed messages.
This patch doesn't add the compression handling but a more structured
and sane way of encapsulating the data parsing. Added the compression
flag extraction to the parsing.

Task-number: QTBUG-129286
Pick-to: 6.10 6.9 6.8
Change-Id: I699fdb6fb0279453a367930e950c2af3e992063d
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
2025-10-02 07:41:32 +02:00
Dennis Oberst be670fea5b QGrpcOperation: add serverInitialMetadataReceived signal
The initial metadata is received when the call starts, often at a
different time then when the RPC finishes. Interceptors provide a way to
access this globally for all RPC but we should add this signal for our
users who are only interested in specific handlers.

[ChangeLog][QGrpcOperation] Added the serverInitialMetadataReceived
signal.

Change-Id: I362a601675029a21d7721275a1b8174ded07fabf
Fixes: QTBUG-139558
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
2025-10-01 12:07:15 +02:00
Dennis Oberst bc052fbd51 Long Live mockserver!
Our current test strategy relies on having a dedicated server running.
While this tests the most 'accurate' flow of the system, it is really
hard to verify the server behavior due to missing flexibility.

Introduce the mockserver to solve this problem. It wraps the low-level
async gRPC server API and allows for non-blocking processing of
operations. This allows it to run nicely together with the Qt Even Loop,
which is blocking.

Provide the server-handling code by using CallbackTags with Lambdas, or
by subclassing the AbstractRpcTag interface.

Change-Id: I086cbd1d6d8542c717ae036bdf54ba96c55afb58
Fixes: QTBUG-139285
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
2025-10-01 12:07:12 +02:00
Dennis Oberst 60446214bb QGrpc{Call,Channel}Options: provide equality operators
It makes sense to provide the equality operators to these classes.
However, omit the otherwise common 'qHash' functionality as it makes no
sense to hash a options class.

[ChangeLog][QGrpcCallOptions/QGrpcChannelOptions] Made the options
classes equality comparable.

Task-number: QTBUG-128338
Change-Id: Idb7ff6946dfef373eba32839ac4b3faf2ff4e5c2
Reviewed-by: Ivan Solovev <ivan.solovev@qt.io>
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
2025-10-01 12:07:03 +02:00
Dennis Oberst 30e9066edf QGrpcOperationPrivate: add missing includes
Pick-to: 6.10 6.9 6.8
Change-Id: I446455abf26abc78ad89d1811114263749c08b4f
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
2025-10-01 12:05:46 +02:00
Dennis Oberst 4f1b19c584 QGrpcOperation: add private slots for handling context signals
More structured way of handling the code that complements the
interceptors patch.

Pick-to: 6.10 6.9 6.8
Change-Id: I6660e9a95ff8faef8e50966d912fdf9f1a7967c9
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
2025-10-01 12:05:39 +02:00
Qt Submodule Update Bot 94e8326137 Update dependencies on 'dev' in qt/qtgrpc
Change-Id: I237308db13dd0c12b51dd7ebb47cbb1217a94ca9
Reviewed-by: Qt Submodule Update Bot <qt_submodule_update_bot@qt-project.org>
2025-09-30 22:12:50 +00:00
Qt Submodule Update Bot d7ebc075d1 Update dependencies on 'dev' in qt/qtgrpc
Change-Id: Ia7c234855f42a1194441b82f0cddda80fedcc05c
Reviewed-by: Qt Submodule Update Bot <qt_submodule_update_bot@qt-project.org>
2025-09-29 10:46:28 +00:00
Qt Submodule Update Bot 4ae4d7f45b Update dependencies on 'dev' in qt/qtgrpc
Change-Id: I995704b6ac3a261fde3921291135b9d04009a553
Reviewed-by: Qt Submodule Update Bot <qt_submodule_update_bot@qt-project.org>
2025-09-26 18:22:26 +00:00
Qt Submodule Update Bot 765cf7e078 Update dependencies on 'dev' in qt/qtgrpc
Change-Id: Ic5b602729fdc0ab9e336d65177f134e97995cd02
Reviewed-by: Qt Submodule Update Bot <qt_submodule_update_bot@qt-project.org>
2025-09-20 14:14:34 +00:00
Qt Submodule Update Bot 056c41d018 Update dependencies on 'dev' in qt/qtgrpc
Change-Id: I620c58612fceed2da1ebdaf220735da0ea886798
Reviewed-by: Qt Submodule Update Bot <qt_submodule_update_bot@qt-project.org>
2025-09-18 10:38:09 +00:00
Alexey Edelev 44b3d7ca87 Reapply "Enable the use of VCPKG for qtgrpc"
This reverts commit e09bbcbeba.

Change-Id: Ia7127662c72c27660e7b66a2946e606c887d3433
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
2025-09-17 11:28:04 +02:00
Dennis Oberst 1ea43abdd3 QGrpcOperation: delete rvalue-this version of metadata getters
These should not be called.

Change-Id: I6889df51affce6d1ed53b7033cb500434c1595ce
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
2025-09-16 12:29:42 +00:00
Alexey Edelev 76f8c2ffb8 Avoid checking the protoc availablity when crosscompiling
Move the protocolCompilerAvailableToRun check under the
SKIP_COMMAND_LINE_TESTS guard.

Pick-to: 6.8 6.9 6.10
Change-Id: Ibd2ce706e7d73d368a89f9f486f3f2fc0f59e7e4
Reviewed-by: Dennis Oberst <dennis.oberst@qt.io>
2025-09-09 10:51:21 +02:00
Alexey Edelev 6de39ba5eb Guard tst_grpc_client_test_common build
The libraty cannot be built if grpc_testserver is missing.

Pick-to: 6.10 6.9 6.8
Change-Id: I34f6a419e2726ec23ac78f0b95f0974df2080547
Reviewed-by: Dennis Oberst <dennis.oberst@qt.io>
2025-09-09 10:51:18 +02:00
Kai Köhne b00344793d Doc: Move QtGrpc Chat example to Networking category
This is a better place then the 'Application Examples' category that
should be reserved for higher-level examples that work out of the box.

Also highlight the example in the Networking category.

Pick-to: 6.8 6.9 6.10
Task-number: QTBUG-137988
Change-Id: I75f630daeba350043f6d0aa3a90b30bee967e6b1
Reviewed-by: Dennis Oberst <dennis.oberst@qt.io>
2025-09-09 07:10:59 +00:00
Dennis Oberst 6f589e1d5c docs: Add common http2-metadata description and distribute it
Our documentation of what the QGrpcHttp2Channel should accept as
metadata was incomplete. Add the missing bits and give readers a link to
the original information, which is coming from RFC 7540.

Task-number: QTBUG-139558
Pick-to: 6.10 6.9 6.8
Change-Id: Ie170fd1f5300597b31dd49d47d5e01c469bf6ec3
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
2025-09-05 17:01:12 +02:00
Dennis Oberst 56999b3605 QGrpcOperation: move Private into separate file
For private access.

Pick-to: 6.10 6.9 6.8
Change-Id: I9d8436c9f283cfe1c2ecb6ba98481ee94d2f5b3e
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
2025-09-05 17:01:12 +02:00
Dennis Oberst b86f9cb5d9 QGrpcHttp2Channel: fix illegal access when printing
We are constructing 'hostUri' at this point. Don't use it for printing.

Coverity-Id: 894268
Pick-to: 6.10 6.9 6.8
Change-Id: I2b478de2d3865112978202959e1af07b17f7751a
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
2025-09-05 17:01:12 +02:00
Qt Submodule Update Bot e95d0041f1 Update dependencies on 'dev' in qt/qtgrpc
Change-Id: I5014b038e7165a62856303b8be5ceb0214f91a27
Reviewed-by: Qt Submodule Update Bot <qt_submodule_update_bot@qt-project.org>
2025-09-05 13:31:29 +00:00
Dennis Oberst bfa2607a72 GrpcToolsMacros.cmake: continue on invalid files
Previously the helper returned early in case no service section was
found in the proto file. If we supply multiple files, this function
should simply skip those. Otherwise it's not possible to process such a
list variable.

Pick-to: 6.10 6.9 6.8
Change-Id: Iab31fa81404f2df2ec4ad628810ff7f948b3eb5f
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
2025-09-02 15:26:58 +02:00
Dennis Oberst 49c5c7d82c Http2Handler: Modify and add debug logs
For understanding the flow better, if needed. Change the log for failed
cancellations to be a warning. This information is usefull to see
without special logging rules!

Pick-to: 6.10 6.9 6.8
Change-Id: I5e30962381c4f476d8e9e1258f5af10c7586942c
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
2025-09-02 15:26:55 +02:00
Dennis Oberst 15cef4fd14 QGrpcHttp2Channel: Guarantee transportation scheme
Currently, if TLS or QLocalSocket is explicitly requested but
unavailable, we fall back to TCP. This is not a safe approach.

This patch asserts on that condition and uses qCFatal to indicate the
mismatch instead of silently falling back to another transport.

Given the security aspects of this file, this strengthens guarantees by
ensuring the requested transport is actually used.

Also: Make construction initialization friendly!

Separated concerns by splitting the large constructor into smaller
segments, achieving the same result through helper functions in the
initializer list.

Added a SocketType enum to clearly identify the transport method. Since
m_isLocalSocket was already removed, this provides more detail without
increasing storage.

[ChangeLog][QGrpcHttp2Channel][Important Behavior Changes]
Requesting TLS or QLocalSocket now fails with a fatal error if the
requested transport is unavailable.

Fixes: QTBUG-139597
Pick-to: 6.10 6.9 6.8
Change-Id: I2914d8a5764436d08b4502aa7d8cf6c4fdeb60d0
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
2025-09-02 15:26:52 +02:00
Dennis Oberst 3afc1866d2 QGrpcHttp2ChannelPrivate: remove unused serializer
Pick-to: 6.10 6.9 6.8
Change-Id: I7104266217c867d74470317ea02f5db75b5d00f7
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
2025-09-02 15:26:49 +02:00
Qt Submodule Update Bot 463205cdf2 Update dependencies on 'dev' in qt/qtgrpc
Change-Id: Ic07e6fed756de50cd37f45d098328f585fe4bcb4
Reviewed-by: Qt Submodule Update Bot <qt_submodule_update_bot@qt-project.org>
2025-09-01 20:59:19 +00:00
Jerome Pasion c6cd23770d Doc: Add security considerations page
-A list of basic security topics when implementing gRPC
-Add links to other modules and relevant pages.

Task-number: QTBUG-138812
Pick-to: 6.9 6.10
Change-Id: I523f07cb516641771eb1457091f381a2e09b0026
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
Reviewed-by: Dennis Oberst <dennis.oberst@qt.io>
2025-09-01 17:42:54 +02:00
Jaishree Vyas 49c0107975 Doc: Review QtGRPC chat example
As part of reviewing the application examples and following a structure, this
change covers:
-Language changes
-Follows the strucutre
-Includes \sa link to All Qt Examples

Fixes: QTBUG-137964
Pick-to: 6.10 6.9 6.8
Change-Id: Ic14d7042e6db277c1584b69ba46f7748f53642ab
Reviewed-by: Dennis Oberst <dennis.oberst@qt.io>
2025-09-01 08:45:54 +00:00
Dennis Oberst b2d9291983 QGrpcHttp2Channel: Avoid warning on receiving multiple SETTINGS frames
Remove the warning that incorrectly triggers when a second HTTP/2
SETTINGS frame is received.

According to RFC, Section 6.5, a SETTINGS frame "MAY be sent at any
other time by either endpoint over the lifetime of the connection."

Ref: https://www.rfc-editor.org/rfc/rfc7540#section-6.5

Pick-to: 6.10 6.9 6.8
Change-Id: I83b0238405801a853c20f22629b12f67edce26e2
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
2025-08-21 13:51:43 +02:00
Dennis Oberst 528469eece Http2Handler: let finish() stop the deadlineTimer
It's the single point where a RPC finishes and the correct place to stop
the timer.

Pick-to: 6.10 6.9 6.8
Change-Id: I2965a938da32a8d78ccfb48f7f5ad37f63ee4363
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
2025-08-20 14:59:39 +02:00
Dennis Oberst 02c989e3e0 QGrpcHttp2ChannelPrivate: remove unused isLocalSocket()
It's not used anywhere in the code

Pick-to: 6.10 6.9 6.8
Change-Id: I388800f73f67f9f37f008590c9f190aa091d9c6b
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
2025-08-20 14:59:36 +02:00
Alexey Edelev 7da4e6886a Move the socket error handling to the common place
Instead of connecting each and every Http2Handler to the socket error,
use QGrpcHttp2ChannelPrivate::handleSocketError to iterate over
alive Http2Handler and send errors right from this handler. This saves
some memory and time on handling errorOccurred signal.

Pick-to: 6.10 6.9 6.8
Change-Id: I907e24425aafe3dccca19100d02fe7adffb1fdaa
Reviewed-by: Dennis Oberst <dennis.oberst@qt.io>
2025-08-15 14:37:46 +02:00
Dennis Oberst 66e4a79353 protobuf: fix deserializeList() warnings and improve clarity
Fixes a compiler warning from implicit conversion by introducing a safe
qsizetype cast. Also improves readability and efficiency by renaming
variables and pre-allocating list storage aswell as adding a missing
move.

Pick-to: 6.10 6.9 6.8
Change-Id: I5eb34621690893e06e68c7b821e93e3462388595
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
2025-08-15 11:06:11 +02:00
Alexey Edelev c764a9b223 Allow enums be non-packed
Repeated enum fields should not ignore the 'packed' protobuf attribute,
so if 'packed' is set to 'false', FieldFlag::NonPacked should land to
the message field attributes, indicating to serializer the expected
output format.

Fixes: QTBUG-138683
Pick-to: 6.8 6.9 6.10
Change-Id: I1c2d2bee32b42f60310307e1f3638ba2b3c236a3
Reviewed-by: Dennis Oberst <dennis.oberst@qt.io>
2025-08-13 10:27:13 +02:00
Qt Submodule Update Bot 764ddd4a38 Update dependencies on 'dev' in qt/qtgrpc
Change-Id: Ic3c9c53c5d4b28f53a064509dc8dcd7fb1b1b771
Reviewed-by: Qt Submodule Update Bot <qt_submodule_update_bot@qt-project.org>
2025-08-08 22:40:41 +00:00
Dennis Oberst 0af1daa639 QAbstractGrpcChannel: Add missing move when starting the RPC
Coverity-Id: 479433
Pick-to: 6.10 6.9 6.8
Change-Id: Ieb543fbd6643514e4862d3ad524865196acbacb7
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
2025-08-08 17:45:43 +02:00
Topi Reinio bfc975db58 Doc: Fix incorrect usage of \generatelist
The \generatelist command takes an optional argument
that affect how the members are listed, for example,
`classesbymodule` which creates an annotated list of
the classes in a named C++ module.

`groupsbymodule` is not recognized or documented. QDoc
falls back to generating a list of all members of a
group in this case. While the end result is correct,
this relies on an undocumented feature that is likely
to change and break.

Replace these instances with an explicit \annotatedlist
command that produces the same output.

Pick-to: 6.10
Task-number: QTBUG-138901
Change-Id: Iffa5720e53177d1b996d49f9a607e2a1fa91bb0a
Reviewed-by: Paul Wicking <paul.wicking@qt.io>
2025-08-06 09:15:56 +00:00
Kai Köhne d62f437efa Doc: Fix botched linking to QImage
Pick-to: 6.5 6.8 6.9 6.10
Change-Id: I50ba5ed2628ecb753e18dd22c457e749168b6b01
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
2025-08-06 08:25:16 +02:00
Dennis Oberst bd1fccb26a grpc benchmarks: fix compiler warnings
Fix unused variables and add missing include

Pick-to: 6.10 6.9 6.8
Change-Id: Idc756938daa63a9cfd4d336598c7919de999a888
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
2025-08-05 19:23:35 +02:00
Dennis Oberst eb703a45a1 Remove unnecessary QObject:: namespace to improve readability
Removed redundant 'QObject::' namespace to streamline code and enhance
readability, especially in lambda handlers.

Due to our formatting, all lambda handlers will be unnecessarily aligned
to the very right side. Given that connections and lambda handlers are
very common in this codebase this improves the readability of those
lambda handlers.

Pick-to: 6.10 6.9 6.8
Change-Id: I178a838c7702382b4b3845c729d7c11eeeb1c8d1
Reviewed-by: Tatiana Borisova <tatiana.borisova@qt.io>
2025-08-05 19:23:32 +02:00
Dennis Oberst 7e06d79d6d QGrpcHttp2Channel: add more debug logging
Easily allows to have a deeper inspection of the system by adding more
debug prints, which can be enabled with the logging category.

Pick-to: 6.10 6.9 6.8
Change-Id: I86d7f6c0c53c412a79a3d66f515fa1cd6757a023
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
2025-08-05 19:23:27 +02:00
Dennis Oberst f1200375f0 QGrpcHttp2Channel: use logging categories and printf style logging
Given the complexity of the http2channel implementation it comes natural
to extend its logging clarity. This aligns with best practices in Qt
development. Also this makes it easier to debug the system remotely.

Pick-to: 6.10 6.9 6.8
Change-Id: Idf00020408b678fc6b17a25db538abd5a838bced
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
2025-08-05 19:23:24 +02:00
Dennis Oberst 4504e44869 Http2Handler: improve readability for dataReceived handler
1. Remove nesting by reversing the state check.
2. Split the messageReceived calculation to be outside of the function
   call. Given that this is an important calculation it should be easily
   understandable.

Pick-to: 6.10 6.9 6.8
Change-Id: Icfaa29a5dc92eb5b1c86469639a3deb90b5aacd0
Reviewed-by: Tatiana Borisova <tatiana.borisova@qt.io>
2025-08-05 19:23:20 +02:00
Dennis Oberst 4cf1dd3093 QGrpcHttp2Channel: unify socket error handler
De-duplicate the logic for the error handlers.

Pick-to: 6.10 6.9 6.8
Change-Id: I1f8745046ba996ba81eb1bf2f5c3882b6c31059b
Reviewed-by: Tatiana Borisova <tatiana.borisova@qt.io>
2025-08-05 19:23:13 +02:00
Dennis Oberst 1ac37b787a Http2Handler: Re-order ctor args and rename m_operation
Re-order the argument to the Http2Handler ctor. The parent should come
first. Furthermore rename the m_operation to m_context as I think
context is a more fitting name for this important member.

This improves the readability and makes it easier to follow the code.

Pick-to: 6.10 6.9 6.8
Change-Id: I396e205ec345d80a8cf2cfebe43625f72d39ac6e
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
2025-08-05 19:23:08 +02:00
Dennis Oberst 5bc854e901 QGrpcHttp2Channel: implement filterServerMetadata option
Implement the filtering of internal and reserved keys for the server
metadata. gRPC over HTTP2 responses are well defined and such
information shouldn't be provided to users. At least not by default.

Ref: https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#responses

This patch changes the filtering to be applied by default.

[ChangeLog][QGrpcHttp2Channel/QGrpcOperation][Important Behavior Changes]
QGrpcOperation::serverInitialMetadata() and
QGrpcOperation::serverTrailingMetadata() no longer include any internal
gRPC or HTTP/2 pseudo‑headers by default.

Fixes: QTBUG-138363
Change-Id: I4af9e8abe60799e817f47faa5de4c2d0e41854be
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
2025-08-05 19:23:05 +02:00
Dennis Oberst 25782e8866 QGrpc{Call,Channel}Options: add 'filterServerMetadata'
This option enables to control the filtering of the received server
metadata. Both options provide it as optional<bool>, so that
QAbstractGrpcChannel implementations should choose a reasonable default.

This has not been provided to the QQml*Options, as there is currently no
available API to access the server metadata.

[ChangeLog][QGrpc{Call,Channel}Options] Added the filterServerMetadata
property.

Task-number: QTBUG-138363
Change-Id: I325a3b4f6e68d63f0828a6deb1a7be883247614b
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
2025-08-05 19:23:00 +02:00
Dennis Oberst 7695e0fc90 QGrpc{Call,Channel}Options: provide noexcept guarantees to metadata()
Before, we did a lazy initialization in the deprecated noexcept getters
to provide a way of not storing the metadata twice. There is just no way
to preserve correctness whilst providing this option.

Whilst const-ref return values provide the most efficient way to access
them, it also most tightly constrains the implementation. Let that be a
lesson ...

We also remove the free-standing 'operator==' as we're comparing with
the matching containers now, there is no need for them (They should also
not be provided like this from QtGrpc, even though private).

Amends: 778371b8ea.

Pick-to: 6.10
Change-Id: I05c4a1f7d2eab00f41ebcad54d9a096b2dcdf540
Reviewed-by: Alexey Edelev <alexey.edelev@qt.io>
Reviewed-by: Marc Mutz <marc.mutz@qt.io>
2025-08-05 19:22:56 +02:00