kirr has posted comments on this change by kirr. ( https://gerrit.osmocom.org/c/osmocom-bb/+/39327?usp=email )
Change subject: trx_toolkit/clck_gen.py: Fix clock generator not to accumulate timing error
......................................................................
Patch Set 1:
(1 comment)
Patchset:
PS1:
The build failed with
```
Cloning into 'libosmocore'...
fatal: unable to access 'https://gerrit.osmocom.org/libosmocore/': Could not resolve host: gerrit.osmocom.org
```
and then jenkins says there should be a retrigger:
```
Find the Retrigger button here:
https://jenkins.osmocom.org/jenkins/job/gerrit-osmocom-bb/1550/
```
but if I register to jenkins.osmocom.org and go to that page there is no Retrigger action.
So is it maybe a problem with permissions or do I miss something?
Thanks beforehand,
Kirill
--
To view, visit https://gerrit.osmocom.org/c/osmocom-bb/+/39327?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: comment
Gerrit-Project: osmocom-bb
Gerrit-Branch: master
Gerrit-Change-Id: I928801422c9af80c368261f617b91d7ecfedbabf
Gerrit-Change-Number: 39327
Gerrit-PatchSet: 1
Gerrit-Owner: kirr <kirr(a)nexedi.com>
Gerrit-Reviewer: Jenkins Builder
Gerrit-CC: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-CC: osmith <osmith(a)sysmocom.de>
Gerrit-CC: pespin <pespin(a)sysmocom.de>
Gerrit-Comment-Date: Wed, 15 Jan 2025 08:57:04 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
Jenkins Builder has posted comments on this change by kirr. ( https://gerrit.osmocom.org/c/osmocom-bb/+/39328?usp=email )
Change subject: [WIP] Revert "Revert "trx_toolkit/transceiver.py: implement the transmit burst queue""
......................................................................
Patch Set 1:
(1 comment)
File src/target/trx_toolkit/transceiver.py:
Robot Comment from checkpatch (run ID jenkins-gerrit-lint-20980):
https://gerrit.osmocom.org/c/osmocom-bb/+/39328/comment/9860b02f_f3e5feb8?u… :
PS1, Line 134: a L12TRX message on the TRXD (data) inteface, so it gets queued by
'inteface' may be misspelled - perhaps 'interface'?
--
To view, visit https://gerrit.osmocom.org/c/osmocom-bb/+/39328?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: comment
Gerrit-Project: osmocom-bb
Gerrit-Branch: master
Gerrit-Change-Id: I41291708effdd2c767be680fff22ffbd9a56815e
Gerrit-Change-Number: 39328
Gerrit-PatchSet: 1
Gerrit-Owner: kirr <kirr(a)nexedi.com>
Gerrit-CC: Jenkins Builder
Gerrit-CC: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-CC: osmith <osmith(a)sysmocom.de>
Gerrit-CC: pespin <pespin(a)sysmocom.de>
Gerrit-Comment-Date: Wed, 15 Jan 2025 08:44:15 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
kirr has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmocom-bb/+/39328?usp=email )
Change subject: [WIP] Revert "Revert "trx_toolkit/transceiver.py: implement the transmit burst queue""
......................................................................
[WIP] Revert "Revert "trx_toolkit/transceiver.py: implement the transmit burst queue""
This reverts commit d4ed09df57b3461470af501e9687ddd80eb78838,
reinstating tx queue into fake_trx.
It is ok to do so because, as explained in the previous patch, the
reason for GSM clock jitter problem was timing error accumulation in
CLCKgen, not problems with py threading.Event.
Note: this restores original tx queue implementation basically as-is
with only resolve minor conflicts during the revert. The original tx
queue implementation wastes CPU cycles though because it linearly scans
the whole tx queue at every TDMA frame. If that CPU usage becomes a real
problem it should be straightforward to fix by reworking tx queue to use
priority queue instead of unordered array via heapq module from standard
library. See https://docs.python.org/3/library/heapq.html for details.
(WIP because I did not tested this patch fully as I could not yet
organize a working fake_trx setup due to not fully done homework yet)
Related: OS#4658, OS#6672
Change-Id: I41291708effdd2c767be680fff22ffbd9a56815e
---
M src/target/trx_toolkit/fake_trx.py
M src/target/trx_toolkit/transceiver.py
2 files changed, 72 insertions(+), 3 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/osmocom-bb refs/changes/28/39328/1
diff --git a/src/target/trx_toolkit/fake_trx.py b/src/target/trx_toolkit/fake_trx.py
index 0daecb4..21bd189 100755
--- a/src/target/trx_toolkit/fake_trx.py
+++ b/src/target/trx_toolkit/fake_trx.py
@@ -398,6 +398,8 @@
# Init shared clock generator
self.clck_gen = CLCKGen([])
+ # This method will be called on each TDMA frame
+ self.clck_gen.clck_handler = self.clck_handler
# Power measurement emulation
# Noise: -120 .. -105
@@ -463,14 +465,18 @@
for trx in self.trx_list.trx_list:
# DATA interface
if trx.data_if.sock in r_event:
- msg = trx.recv_data_msg()
- if msg is not None:
- self.burst_fwd.forward_msg(trx, msg)
+ trx.recv_data_msg()
# CTRL interface
if trx.ctrl_if.sock in r_event:
trx.ctrl_if.handle_rx()
+ # This method will be called by the clock thread
+ def clck_handler(self, fn):
+ # We assume that this list is immutable at run-time
+ for trx in self.trx_list.trx_list:
+ trx.clck_tick(self.burst_fwd, fn)
+
def shutdown(self):
log.info("Shutting down...")
diff --git a/src/target/trx_toolkit/transceiver.py b/src/target/trx_toolkit/transceiver.py
index ffd18ab..4e1cb87 100644
--- a/src/target/trx_toolkit/transceiver.py
+++ b/src/target/trx_toolkit/transceiver.py
@@ -19,6 +19,7 @@
# GNU General Public License for more details.
import logging as log
+import threading
from ctrl_if_trx import CTRLInterfaceTRX
from data_if import DATAInterface
@@ -109,6 +110,37 @@
Transceiver and all its timeslots, so using in for the BTS side
does not make any sense (imagine BCCH hopping together with DCCH).
+ == The transmit burst queue
+
+ According to 3GPP 45.002, the time difference between Uplink and
+ Downlink corresponds to three TDMA timeslot periods. However,
+ in general the L1 implementations (such as osmo-bts-trx and trxcon)
+ never schedule to be transmitted bursts for the current TDMA frame
+ immediately. Instead, they are being scheduled prematurely.
+
+ The rationale is that both transceiver and the L1 implementation
+ are separete processes that are not perfectly synchronized in time.
+ Moreover, the transceiver needs some time to prepare a burst for
+ transmission. This is why the time difference between Uplink and
+ Downlink is actually much higher on practice (20 TDMA frame periods
+ by default, at the moment of writing this patch).
+
+ In order to reflect that delay in a virtual environment, this
+ implementation, just like a normal transceiver (e.g. osmo-trx),
+ queues all to be transmitted (L12TRX) bursts, so hey remain in
+ the transmit queue until the appropriate time of transmission.
+
+ The API user is supposed to call recv_data_msg() in order to obtain
+ a L12TRX message on the TRXD (data) inteface, so it gets queued by
+ this function. Then, to ensure the timeous transmission, the user
+ of this implementation needs to call clck_tick() on each TDMA
+ frame. Both functions are thread-safe (queue mutex).
+
+ In a multi-trx configuration, the use of queue additionally ensures
+ proper burst aggregation on multiple TRXD connections, so all L12TRX
+ messages are guaranteed to be sent in the right order, i.e. with
+ monolithically-increasing TDMA frame numbers.
+
"""
def __init__(self, bind_addr, remote_addr, base_port, **kwargs):
@@ -161,6 +193,10 @@
# List of child transceivers
self.child_trx_list = TRXList()
+ # Tx (L12TRX) burst queue and mutex
+ self._tx_queue_lock = threading.Lock()
+ self._tx_queue = []
+
def __str__(self):
desc = "%s:%d" % (self.remote_addr, self.base_port)
if self.child_idx > 0:
@@ -220,6 +256,7 @@
for trx in trx_list:
trx.running = poweron
if not poweron:
+ trx.tx_queue_clear()
trx.disable_fh()
# Trigger clock generator if required
@@ -251,8 +288,34 @@
"is not running => dropping..." % (self, msg.desc_hdr()))
return None
+ # Enque the message, it will be sent later
+ self.tx_queue_append(msg)
return msg
def handle_data_msg(self, msg):
# TODO: make legacy mode configurable (via argv?)
self.data_if.send_msg(msg, legacy = True)
+
+ def tx_queue_append(self, msg):
+ with self._tx_queue_lock:
+ self._tx_queue.append(msg)
+
+ def tx_queue_clear(self):
+ with self._tx_queue_lock:
+ self._tx_queue.clear()
+
+ def clck_tick(self, fwd, fn):
+ if not self.running:
+ return
+
+ self._tx_queue_lock.acquire()
+
+ for msg in self._tx_queue:
+ if msg.fn == fn:
+ fwd.forward_msg(self, msg)
+ elif msg.fn < fn:
+ log.warning("(%s) Stale TRXD message (fn=%u): %s"
+ % (self, fn, msg.desc_hdr()))
+
+ self._tx_queue = [msg for msg in self._tx_queue if msg.fn > fn]
+ self._tx_queue_lock.release()
--
To view, visit https://gerrit.osmocom.org/c/osmocom-bb/+/39328?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: osmocom-bb
Gerrit-Branch: master
Gerrit-Change-Id: I41291708effdd2c767be680fff22ffbd9a56815e
Gerrit-Change-Number: 39328
Gerrit-PatchSet: 1
Gerrit-Owner: kirr <kirr(a)nexedi.com>
Gerrit-CC: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-CC: osmith <osmith(a)sysmocom.de>
Gerrit-CC: pespin <pespin(a)sysmocom.de>
kirr has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmocom-bb/+/39327?usp=email )
Change subject: trx_toolkit/clck_gen.py: Fix clock generator not to accumulate timing error
......................................................................
trx_toolkit/clck_gen.py: Fix clock generator not to accumulate timing error
CLCKGen currently works as follows:
sleep(ctr_interval)
some work
sleep(ctr_interval)
some work
sleep(ctr_interval)
some work
...
The intent here is to do some work at timestamps that are multiple of ctr_interval,
however the implementation does not match the intent, because
1) sleep(ctr_interval) is not guaranteed by the OS to be ideal, so there
will always be some jitter in actually slept time without any
guarantee that the error will fluctuate over zero without accumulating.
2) "some work" takes some time to run and that time adds again and again
to the current time when next sleep(ctr_interval) starts. As the
result even if sleep implementation would be ideal, then n'th sleep
would start not at
t₀ + n·ctr_interval
but instead at
t₀ + n·ctr_interval + Σ1..n t(work_i)
where trailing Σ term adds over and over as the timing error which can
be seen as e.g. increasing trend of received GSM clock jitter in
https://osmocom.org/issues/4658#note-10 .
The thinko in the clock generator logic is not so much visible if "some
work" takes only a bit of time or is done infrequently. That was
actually the case before fake_trx added tx queueing in 6e1c82d2
(trx_toolkit/transceiver.py: implement the transmit burst queue) because
before that commit some work was only "send IND CLOCK data every ~ 100th
tick". However after 6e1c82d2 the work was adjusted to do linear scan of
tx queue over and over at every tick which amplified error accumulation
and highlighted the problem.
With that tx queuing in fake_trx was disabled in d4ed09df (Revert
"trx_toolkit/transceiver.py: implement the transmit burst queue") with
the rationale being most likely, as https://osmocom.org/issues/4658#note-10 says,
Unfortunately, Python is not fast enough to handle the queues in time.
Despite the relatively low CPU usage, fake_trx.py fails to scheduler
everything during one TDMA frame period. This causes some of our TTCN-3
test cases to fail.
...
Most likely, the problem is that Python's threading.Event is not
accurate enough. Running with SCHED_RR does not change anything.
However with the above analysis we can see that it is the logic in
CLCKgen that needs fixing, not threading.Event . For the reference
threading.Event indeed used dumb timeout implementation on Python2:
https://github.com/python/cpython/blob/2.7-0-g8d21aa21f2c/Lib/threading.py#…https://github.com/python/cpython/blob/2.7-0-g8d21aa21f2c/Lib/threading.py#…
but on Python3 it essentially uses plain Lock.acquire(timeout) which,
under the hood, uses PyThread_acquire_lock_timed - a plain wrapper over
sem_timedwait:
https://github.com/python/cpython/blob/v3.11.9-9-g1b0e63c81b5/Lib/threading…https://github.com/python/cpython/blob/v3.11.9-9-g1b0e63c81b5/Modules/_thre…https://github.com/python/cpython/blob/v3.11.9-9-g1b0e63c81b5/Python/thread…
so at least with py3 there should be no question about threading.Event .
-> Fix timing error accumulation by reworking the clock generator loop
to compensate observed jitter, caused by OS noise and the work
taking time, by adjusting to-sleep δt each tick accordingly.
This is generally good for correctness and will allow us to reinstate
tx queueing in fake_trx.
Without the fix added test fails as
FAIL: test_no_timing_error_accumulated (test_clck_gen.CLCKGen_Test.test_no_timing_error_accumulated)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/kirr/src/osmocom/bb/src/target/trx_toolkit/test_clck_gen.py", line 60, in test_no_timing_error_accumulated
self.assertTrue((ntick+1)*clck.ctr_interval > δT, "tick #%d: time overrun by %dµs total" %
AssertionError: False is not true : tick #200: time overrun by 572478µs total
Change-Id: I928801422c9af80c368261f617b91d7ecfedbabf
Related: OS#4658, OS#6672
---
M src/target/trx_toolkit/clck_gen.py
A src/target/trx_toolkit/test_clck_gen.py
2 files changed, 87 insertions(+), 1 deletion(-)
git pull ssh://gerrit.osmocom.org:29418/osmocom-bb refs/changes/27/39327/1
diff --git a/src/target/trx_toolkit/clck_gen.py b/src/target/trx_toolkit/clck_gen.py
index 427eb88..7480d44 100755
--- a/src/target/trx_toolkit/clck_gen.py
+++ b/src/target/trx_toolkit/clck_gen.py
@@ -22,6 +22,7 @@
import logging as log
import threading
+import time
import signal
from app_common import ApplicationBase
@@ -85,7 +86,24 @@
self._breaker.clear()
def _worker(self):
- while not self._breaker.wait(self.ctr_interval):
+ # run .send_clck_ind() every .ctr_interval
+ # be careful not to accumulate timing error when organizing the clock loop
+ ns = 1e-9
+ us = 1e-6
+ t_tick = int(self.ctr_interval // ns)
+ t_next = time.monotonic_ns()
+ while 1:
+ t_next += t_tick
+ t = time.monotonic_ns()
+ δt = (t_next - t)
+ if δt < 0:
+ log.warning("CLCKGen: time overrun by %dus; resetting the clock" % (δt * ns // us))
+ t_next = time.monotonic_ns()
+ δt = 0
+
+ if self._breaker.wait(δt * ns):
+ break
+
self.send_clck_ind()
def send_clck_ind(self):
diff --git a/src/target/trx_toolkit/test_clck_gen.py b/src/target/trx_toolkit/test_clck_gen.py
new file mode 100644
index 0000000..915b3fd
--- /dev/null
+++ b/src/target/trx_toolkit/test_clck_gen.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# TRX Toolkit
+# Unit test for CLCKGen
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+import threading
+import time
+import unittest
+
+import clck_gen
+
+
+class CLCKGen_Test(unittest.TestCase):
+
+ # verify that timing error is not accumulated in the clock gen loop
+ def test_no_timing_error_accumulated(self):
+ # observe many ticks with spending some noticeable time in clock handler each tick
+ # assert that t(last) - t(first) ≈ ntick·δt(tick)
+ # this will break if the clock generator is not careful to prevent timing error from accumulating
+ clck = clck_gen.CLCKGen([])
+ ntick = 200 # ~ 1 s
+ tick = 0
+ tstart = tend = None
+ done = threading.Event()
+
+ def _(fn):
+ nonlocal tick, tstart, tend
+ if tick == 0:
+ tstart = time.monotonic()
+ if tick == ntick:
+ tend = time.monotonic()
+ done.set()
+ tick += 1
+ time.sleep(clck.ctr_interval / 2)
+ clck.clck_handler = _
+
+ clck.start()
+ try:
+ ok = done.wait(10)
+ self.assertTrue(ok, "clck_gen stuck")
+
+ self.assertIsNotNone(tstart)
+ self.assertIsNotNone(tend)
+ δT = tend - tstart
+ δTok = ntick * clck.ctr_interval
+ δTerr = δT - δTok
+ self.assertTrue((ntick-1)*clck.ctr_interval < δT, "tick #%d: time underrun by %dµs total" %
+ (ntick, δTerr // 1e-6))
+ self.assertTrue((ntick+1)*clck.ctr_interval > δT, "tick #%d: time overrun by %dµs total" %
+ (ntick, δTerr // 1e-6))
+
+ finally:
+ clck.stop()
+
+
+if __name__ == '__main__':
+ unittest.main()
--
To view, visit https://gerrit.osmocom.org/c/osmocom-bb/+/39327?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: newchange
Gerrit-Project: osmocom-bb
Gerrit-Branch: master
Gerrit-Change-Id: I928801422c9af80c368261f617b91d7ecfedbabf
Gerrit-Change-Number: 39327
Gerrit-PatchSet: 1
Gerrit-Owner: kirr <kirr(a)nexedi.com>
Gerrit-CC: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-CC: osmith <osmith(a)sysmocom.de>
Gerrit-CC: pespin <pespin(a)sysmocom.de>
Attention is currently required from: daniel.
pespin has posted comments on this change by pespin. ( https://gerrit.osmocom.org/c/osmo-mgw/+/39239?usp=email )
Change subject: mgw: MDCX: Split mgcp header pars parsing into a previous step
......................................................................
Patch Set 4:
(1 comment)
File src/libosmo-mgcp/mgcp_protocol.c:
https://gerrit.osmocom.org/c/osmo-mgw/+/39239/comment/d05c364e_35ff71ac?usp… :
PS4, Line 1122: remote_osmux_cid = mgcp_osmux_setup(endp, line);
> `handle_create_con()` contains the following code: […]
It is not needed and it was actually a bug due to messy code before this.
See how osmux_init_conn() is only called during handle_create_conn(), which means a conn can only be setup as osmux during CRCX.
In turn, that means, during MDCX, that if a connection was already osmux it means the trunk would already have been configured through mgcp_trunk_osmux_init_if_needed()/mgcp_osmux_setup().
In MDCX, mgcp_osmux_setup() was only really being called to obtain the osmux CID in case the conn was already specified as being Osmux during previous CRCX.
--
To view, visit https://gerrit.osmocom.org/c/osmo-mgw/+/39239?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: comment
Gerrit-Project: osmo-mgw
Gerrit-Branch: master
Gerrit-Change-Id: I6ecb41fc2cc737c3a161b6bc98bd08ae01909655
Gerrit-Change-Number: 39239
Gerrit-PatchSet: 4
Gerrit-Owner: pespin <pespin(a)sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-CC: daniel <dwillmann(a)sysmocom.de>
Gerrit-Attention: daniel <dwillmann(a)sysmocom.de>
Gerrit-Comment-Date: Tue, 14 Jan 2025 20:00:39 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
Comment-In-Reply-To: daniel <dwillmann(a)sysmocom.de>
Attention is currently required from: laforge, lynxis lazus.
pespin has posted comments on this change by lynxis lazus. ( https://gerrit.osmocom.org/c/osmo-iuh/+/38945?usp=email )
Change subject: iu_client: refactor LAC/RAC handling
......................................................................
Patch Set 4:
(1 comment)
File src/iu_client.c:
https://gerrit.osmocom.org/c/osmo-iuh/+/38945/comment/326ffe4f_1d522b2f?usp… :
PS4, Line 453: ra_id.rac = 0xfe;
> I tried to sneak it through. Yes, we should have a define here. […]
@laforge@gnumonks.org may know from the top of his head?
--
To view, visit https://gerrit.osmocom.org/c/osmo-iuh/+/38945?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: comment
Gerrit-Project: osmo-iuh
Gerrit-Branch: master
Gerrit-Change-Id: Ie38fa3751cfce1c981d8d0bed1b8ff891593a638
Gerrit-Change-Number: 38945
Gerrit-PatchSet: 4
Gerrit-Owner: lynxis lazus <lynxis(a)fe80.eu>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
Gerrit-CC: daniel <dwillmann(a)sysmocom.de>
Gerrit-CC: laforge <laforge(a)osmocom.org>
Gerrit-CC: neels <nhofmeyr(a)sysmocom.de>
Gerrit-Attention: laforge <laforge(a)osmocom.org>
Gerrit-Attention: lynxis lazus <lynxis(a)fe80.eu>
Gerrit-Comment-Date: Tue, 14 Jan 2025 18:24:20 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
Comment-In-Reply-To: pespin <pespin(a)sysmocom.de>
Comment-In-Reply-To: lynxis lazus <lynxis(a)fe80.eu>
Attention is currently required from: lynxis lazus.
pespin has posted comments on this change by lynxis lazus. ( https://gerrit.osmocom.org/c/osmo-iuh/+/38947?usp=email )
Change subject: iu_client: add a new event NEW_AREA
......................................................................
Patch Set 8:
(1 comment)
File include/osmocom/ranap/iu_client.h:
https://gerrit.osmocom.org/c/osmo-iuh/+/38947/comment/49999e94_a3a58a08?usp… :
PS8, Line 46: struct ranap_iu_event_new_area {
afaiu your are breaking API and ABI here...
--
To view, visit https://gerrit.osmocom.org/c/osmo-iuh/+/38947?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: comment
Gerrit-Project: osmo-iuh
Gerrit-Branch: master
Gerrit-Change-Id: I8b1b8c58bf72b00e2705ca87a89a91481bac3470
Gerrit-Change-Number: 38947
Gerrit-PatchSet: 8
Gerrit-Owner: lynxis lazus <lynxis(a)fe80.eu>
Gerrit-Reviewer: Jenkins Builder
Gerrit-CC: pespin <pespin(a)sysmocom.de>
Gerrit-Attention: lynxis lazus <lynxis(a)fe80.eu>
Gerrit-Comment-Date: Tue, 14 Jan 2025 18:22:43 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No