| Commit message (Collapse) | Author | Age | Files | Lines |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The link-state routing component now has a protocol debugging option,
activated by enabling the DEBUG_PROTO_LS flag at build time in a
debug build.
Example output (sender and receiver):
LSU [81:8a:1f:9b -- 50:c6:2d:03 seq: 000000000] --> 50:c6:2d:03
LSU [81:8a:1f:9b -- 50:c6:2d:03 seq: 000000000] <-- 50:c6:2d:03
In larger networks, forwarded LSUs are marked as such:
LSU [ee:53:ae:f8 -- e5:33:e4:8d seq: 000000006] --> e5:33:e4:8d [forwarded]
This also aligns the address printing using a similar ethernet-like
formatting such as the DHT component. Small code cleanup in the graph
component.
Note: eventually the link state dissemination should move to a
broadcast Layer instead of the link state component doing the
forwarding.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
This is a rewrite of the DHT for name-to-address resolution in the
unicast layer. It is now integrated as a proper directory policy. The
dir_wait_running function is removed, instead the a DHT peer is passed
on during IPCP enrolment.
Each DHT request/response gets a random 64-bit ID ('cookie'). DHT
messages to the same peer are deduped, except in the case when the DHT
is low on contacts. In that case, it will contact the per it received
at enrolment for more contacts. To combat packet loss, these messages
are not deduped by means of a 'magic cookie', chosen at random when
the DHT starts.
The DHT parameters (Kademlia) can be set using the configfile or the
IRM command line tools:
if DIRECTORY_POLICY == DHT
[dht_alpha <search factor> (default: 3)]
[dht_k <replication factor> (default: 8)]
[dht_t_expire <expiration (s)> (default: 86400)]
[dht_t_refresh <contact refresh (s)> (default: 900)]
[dht_t_replicate <replication (s)> (default: 900)]
This commit also adds support for a protocol debug level (PP).
Protocol debugging for the DHT can be enabled using the
DEBUG_PROTO_DHT build flag.
The DHT has the following message types:
DHT_STORE, sent to k peers. Not acknowledged.
DHT_STORE --> [2861814146dbf9b5|ed:d9:e2:c4].
key: bcc236ab6ec69e65 [32 bytes]
val: 00000000c4e2d9ed [8 bytes]
exp: 2025-08-03 17:29:44 (UTC).
DHT_FIND_NODE_REQ, sent to 'alpha' peers, with a corresponding
response. This is used to update the peer routing table to iteratively
look for the nodes with IDs closest to the requested key.
DHT_FIND_NODE_REQ --> [a62f92abffb451c4|ed:d9:e2:c4].
cookie: 2d4b7acef8308210
key: a62f92abffb451c4 [32 bytes]
DHT_FIND_NODE_RSP <-- [2861814146dbf9b5|ed:d9:e2:c4].
cookie: 2d4b7acef8308210
key: a62f92abffb451c4 [32 bytes]
contacts: [1]
[a62f92abffb451c4|9f:0d:c1:fb]
DHT_FIND_VALUE_REQ, sent to 'k' peers, with a corresponding
response. Used to find a value for a key. Will also send its closest
known peers in the response.
DHT_FIND_VALUE_REQ --> [2861814146dbf9b5|ed:d9:e2:c4].
cookie: 80a1adcb09a2ff0a
key: 42dee3b0415b4f69 [32 bytes]
DHT_FIND_VALUE_RSP <-- [2861814146dbf9b5|ed:d9:e2:c4].
cookie: 80a1adcb09a2ff0a
key: 42dee3b0415b4f69 [32 bytes]
values: [1]
00000000c4e2d9ed [8 bytes]
contacts: [1]
[a62f92abffb451c4|9f:0d:c1:fb]
Also removes ubuntu 20 from appveyor config as it is not supported anymore.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
This adds a DEBUG_PROTO_OAP option to show some information in the OAP
headers received by the IRMd during flow allocation.
Example for unencrypted flow (send):
OAP_HDR [528b7bf81df8e9bc @ 2025-08-05 17:05:29 (UTC) ] -->
Certificate: <none>
Ephemeral Public Key: <none>
Data: <none>
Signature: <none>
Example for encrypted flow (rcv):
OAP_HDR [4ff83072e0ed54b3 @ 2025-08-05 17:06:23 (UTC) ] <--
Certificate: <none>
Ephemeral Public Key [91 bytes]:
Data: <none>
Signature: <none>
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
There were two timeout bugs in the flow allocation steps.
Found these when debugging why some links were always timing out in
one direction.
The first bug only affects flows going through the connection manager
in the IPCP (such as the management flow used by the link state
routing component. The connection manager was setting a timeout on the
flow for its handling connection establishment, but didn't reset that
timeout before handing the flow to the internal component after the
CEP was complete. The link-state routing component was showing flow
read timeouts (ETIMEDOUT) without setting one. This was not causing
the links to time out.
The second one was that the IPCP was keeping the qosspec from on the
np1 flow, which, when the qosspec requested for the applicatoin had a
keepalive set, would always time out. This bug was the root cause for
some link advertisements not to be read and links to time out in the
link-state routing component.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
| |
The test had a root certificate that expired today. All test
certificates updated, now valid until 2045.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
| |
The TPM was just printing when debugging, but it's cleaner to just log
it. Cleaned up the debug code a bit as well. Fixed timing bug in IRMd.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
| |
The device MTU was not retrieved on those platforms. Fixed that while
refactoring the bootstrap function.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
|
|
|
| |
The static analyzer is complaining about a leak of strdup in
reg_list_names. The reg_lists_ipcps function has identical logic and
does not trigger it.
Both tests succeed with address sanitizer.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
|
| |
This is how the ProtoBufCBinary data type is defined, so it will allow
easier conversion until we get rid of it. But it makes sense, as the
size_t will always be aligned.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
| |
The ipcpi (IPCP instance) is now cleanly tucked away within its source
file instead of exposed all over the place.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
|
| |
The timestamp check was not correcly updated when changing MPL from
seconds to milliseconds. IRMd now also warns if the allocation was
sent from the future.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
This adds the initial version for the flow allocation protocol header
between IRMd instances. This is a step towards flow authentication.
The header supports secure and authenticated flow allocation,
supporting certificate-based authentication and ephemeral key
exchange for end-to-end encryption.
id: 128-bit identifier for the entity.
timestamp: 64-bit timestamp (replay protection).
certificate: Certificate for authentication.
public key: ECDHE public key for key exchange.
data: Application data.
signature: Signature for integrity/authenticity.
Authentication and encryption require OpenSSL to be installed.
The IRMd compares the allocation request delay with the MPL of the
Layer over which the flow allocation was sent. MPL is now reported by
the Layer in ms instead of seconds.
Time functions revised for consistency and adds some tests.
The TPM can now print thread running times in Debug builds
(TPM_DEBUG_REPORT_INTERVAL) and abort processes with hung threads
(TPM_DEBUG_ABORT_TIMEOUT). Long running threads waiting for input
should call tpm_wait_work() to avoid trigger a process abort.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
| |
Older versions of openssl don't seem to like certificates signed with
ecdsa_with_SHA3-256.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
|
|
| |
Adds functions needed for authentication using X509 certificates,
implemented using OpenSSL.
Refactors some library internals, and adds some unit tests for them.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
|
|
|
|
| |
If & where the systemd service files are installed should be
controllable by the user, while keeping a good default*. This commit
adds the flexibility to choose, while keeping the options clear.
*A good default is already provided before this commit and remains the
same.
Signed-off-by: Thijs Paelman <thijs@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
|
|
|
| |
When the variable SYSTEMD_UNITDIR is specified (for example on the
command line), always install the systemd service file to this
directory, even if systemd is not found by pkg-config.
This also will help building with Guix without patching CMakeLists.txt.
Signed-off-by: Thijs Paelman <thijs@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
| |
Fixed the directories having an additional /usr after the prefix.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
|
| |
Since protobuf-c version 1.5.1 using protoc-c is deprecated:
https://github.com/protobuf-c/protobuf-c/pull/758
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
| |
On OS X, user-installed libraries will have their headers in
/usr/local/include/ which is not automatically included.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
| |
On OS X user-installed libraries (e.g. libtomlc99 used for the
configfile) will have their headers in /usr/local/include/.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
| |
The library was still trying to link against OpenSSL on OS X after it
was uninstalled.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
| |
AF_INET is defined in <sys/socket.h> on FreeBSD.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
| |
There was a logline printing tv_sec as a ssize_t instead of a
long, causing compilation on raspbian 32-bit to fail.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
|
| |
Macro was missing for CLOCK_REALTIME_COARSE, which does not exist on
OS X, and the preprocessor macro for the ipcpd-eth had an #endif
misplaced which was exposing an otherwise unknown label.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
| |
The state was set to IRMD_NULL, so the IRMd was still waiting for a
signal to actually shut down. It should be set to IRMD_SHUTDOWN.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Removing the testdriver source by the driver name doesn't work anymore
in CMake 3.29 because of the following (breaking) change:
Changed in version 3.29: The test driver source is listed by absolute
path in the build tree. Previously it was listed only as <driverName>.
https://cmake.org/cmake/help/latest/command/create_test_sourcelist.html
When using CMake 3.29 or above, Ouroboros will use the list POP_FRONT
function (introduced in CMake 3.15) to get rid of it.
https://cmake.org/cmake/help/latest/command/list.html#pop-front
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
| |
The argc counter was improperly reset before building the arguments
list.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The Common Application Connection Establishment Protocol (CACEP) is a
RINA construct associated with the Common Distributed Application
Protocol (CDAP). We dropped CDAP as O7s sees connection establishment
as common to all applications (though it can be a nop). The wiki
already refers to this as (O7s) Connection Establishment Protocol
(CEP).
The connection manager will now timeout waiting for CEP messages to
avoid hanging forever, configurable at build time via
CONNMGR_RCV_TIMEOUT.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
| |
The object must be refreshed from the list to see if it wasn't yet
destroyed if the wait times out.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
| |
The get_ipcpd_by_dst function was returning the hash length of the
last IPCP in the list instead of the length of the actual hash.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
|
|
|
| |
If the mainloop is cancelled during a write, the response buffer
leaks.
The IRMd now warns about failed writes only when the error is not
EPIPE, as EPIPE is expected to happen with timed out requests.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The wait_flow_req_arr and wait_flow_resp functions are there to ensure
the responses of the IRMd to flow allocation requests arrive in the
correct order. These functions use a mutex: alloc_lock.
After these functions return, the IPCP will switch to it's own
(usually read-write) lock. In the local IPCP, this leaves room for a
race where the state of the flow is accessed in alloc_resp before it
is updated in wait_flow_req_arr. This race is only visible in the
local IPCP, as the other IPCP have to send information between these
calls, but it is theoretically possible when using any IPCP for local
IPC. In the ipcpd-local, it happens ~0.01% to ~0.03% of flow allocations.
This mitigates the problem in the ipcpd-local by adding a 1ms wait to
the flow allocation if this race is detected.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
| |
The timeout was set to a value calculated as abstime for a cond_wait
instead of a timeout, causing flows to linger in the IPCP.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
|
|
|
| |
Destroying a process will now always be done with reg_destroy_proc,
regardless of whether it was an IPCP or spawned. This makes it easier
to keep the registry consistent and avoid races.
Also improves some logs and updates some default settings.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
|
| |
This adds a build option IPCP_DEBUG_LOCAL that will use the pid as the
DT name (address) in the unicast IPCP, which is handy for integration
testing and debugging.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
| |
The connection element was not free'd on shutdown during a connect
operation.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
|
| |
If a flow allocation times out just before the response, there is a
short window where the response will still find the flow, but in
DEALLOCATED state.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
This revises the application flow allocator to use the flow_info
struct/message between the components. Revises the messaging to move
the use protocol buffers to its own source (serdes-irm).
Adds a timeout to the IRMd flow allocator to make sure flow
allocations don't hang forever (this was previously taken care of by
the sanitize thread).
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
|
|
|
| |
The configuration parameter was not correctly passed, which caused the
build system to build with debug symbols (configuration does not match
"Release" and not set CONFIG_OUROBOROS_DEBUG, causing the compiler to
include the debugging code. Fixed appveyor command. The build will now
fail on an unknown CMAKE_BUILD_TYPE.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
|
| |
I assumed that all hashes were stored in Big Endian, but apparently
the CRC32 was still in machine endianness when calculated by the
rhash implementation. Fixed and updated hash tests a bit.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
| |
The macro checks tests for exit code 134 (interrupted by SIGABRT) but
not for exit code 6.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
|
| |
The reg_list_ipcps function left *ipcps uninitialized when there were
no IPCPs in the system. This caused a free to SEGV in the IRMd when
trying to allocate a flow.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
| |
Some files had a newline at the end, others didn't. Now they all do.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
This is a full revision of the IRMd internal implementation.
The registry is now a proper subcomponent managing its own internal
lock (a single mutex). Some tests are added for the registry and its
data structures. Some macros for tests are added in <ouroboros/test.h>.
Flow allocation is now more symmetric between the client side (alloc)
and server size (accept). Each will create a flow in pending state
(ALLOC_PENDING/ACCEPT_PENDING) that is potentially fulfilled by an
IPCP using respond_alloc and respond_accept primitives. Deallocation
is split in flow_dealloc (application side) and ipcp_flow_dealloc
(IPCP side) to get the flow in DEALLOC_PENDING and DEALLOCATED state.
Cleanup of failed flow allocation is now properly handled instead of
relying on the sanitizer thread. The new sanitizer only needs to
monitor crashed processes.
On shutdown, the IRMd will now detect hanging processes and SIGKILL
them and clean up their fuse mountpoints if needed.
A lot of other things have been cleaned up and shuffled around a bit.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
| |
The bootstrap function was not returning the correct hash. Bug
introduced in 99545fa2.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
| |
On fail_dup, the last element (0) was not free'd.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
|
| |
Fixes warnings in CMake 3.28 and newer related to CMP0153.
https://cmake.org/cmake/help/latest/policy/CMP0153.html
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
| |
Some definitions/enums were different between the library and IRMd
(flow_state, ipcp_state). This moves them to common ground.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
| |
The list_del operation now lets nxt and prv point to the element itself, so
that list_is_empty(e) is true after list_del(e).
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|
|
|
|
|
|
|
|
|
|
| |
When flow_alloc failed, it was releasing the flow_id, but the flow was
needs to be cleaned up by the sanitizer. Bug introduced by ongoing
refactor of the flow allocator, which - when done - will properly
clean up the flow after a failure and not depend on the sanitizer.
Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
|