diff options
| author | Dimitri Staessens <dimitri@ouroboros.rocks> | 2026-06-30 18:56:00 +0200 |
|---|---|---|
| committer | Dimitri Staessens <dimitri@ouroboros.rocks> | 2026-06-30 19:05:31 +0200 |
| commit | a6c2bf9cbfcd3e13569d3a7d915c27d42fb7bbec (patch) | |
| tree | fe14d51fd2c9b0b1b34420c5bf12683030e2fa38 | |
| parent | 5f2bc27eacafb2b714317dd9cc0f286c774bdb64 (diff) | |
| download | pyouroboros-a6c2bf9cbfcd3e13569d3a7d915c27d42fb7bbec.tar.gz pyouroboros-a6c2bf9cbfcd3e13569d3a7d915c27d42fb7bbec.zip | |
ouroboros: Use QoSService in QoSSpec
Match the use of service instead of the in_order field in Ouroboros.
| -rw-r--r-- | ouroboros/_qosspec.py | 70 | ||||
| -rw-r--r-- | ouroboros/_timespec.py | 64 | ||||
| -rw-r--r-- | ouroboros/qos.py | 106 |
3 files changed, 211 insertions, 29 deletions
diff --git a/ouroboros/_qosspec.py b/ouroboros/_qosspec.py new file mode 100644 index 0000000..6031c42 --- /dev/null +++ b/ouroboros/_qosspec.py @@ -0,0 +1,70 @@ +# +# Ouroboros - Copyright (C) 2016 - 2026 +# +# Internal helpers for translating QoSSpec <-> CFFI qosspec_t. +# +# Dimitri Staessens <dimitri@ouroboros.rocks> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public License +# version 2.1 as published by the Free Software Foundation. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., http://www.fsf.org/about/contact/. +# + +"""Shared QoSSpec <-> qosspec_t conversion helpers. + +dev.py and irm.py each link against their own CFFI extension module +(``_ouroboros_dev_cffi`` and ``_ouroboros_irm_cffi``), so they each +have their own ``ffi`` instance and their own ``qosspec_t`` type. +CFFI types are not interchangeable across modules, so these helpers +take the caller's ``ffi`` instance as a parameter. +""" + +from __future__ import annotations + +from typing import Optional + +from ouroboros.qos import QoSSpec + + +def qos_to_qosspec(ffi, qos: Optional[QoSSpec]): + """Convert a :class:`QoSSpec` to a freshly-allocated ``qosspec_t *``. + + Returns ``ffi.NULL`` when *qos* is ``None``. + """ + if qos is None: + return ffi.NULL + return ffi.new("qosspec_t *", + [qos.service, + qos.delay, + qos.bandwidth, + qos.availability, + qos.loss, + qos.ber, + qos.max_gap, + qos.timeout]) + + +def qosspec_to_qos(ffi, _qos) -> Optional[QoSSpec]: + """Convert a ``qosspec_t *`` back to a :class:`QoSSpec`. + + Returns ``None`` when *_qos* is ``ffi.NULL``. + """ + if _qos == ffi.NULL: + return None + return QoSSpec(service=_qos.service, + delay=_qos.delay, + bandwidth=_qos.bandwidth, + availability=_qos.availability, + loss=_qos.loss, + ber=_qos.ber, + max_gap=_qos.max_gap, + timeout=_qos.timeout) diff --git a/ouroboros/_timespec.py b/ouroboros/_timespec.py new file mode 100644 index 0000000..2fcf666 --- /dev/null +++ b/ouroboros/_timespec.py @@ -0,0 +1,64 @@ +# +# Ouroboros - Copyright (C) 2016 - 2026 +# +# Internal helpers for translating float seconds <-> struct timespec. +# +# Dimitri Staessens <dimitri@ouroboros.rocks> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public License +# version 2.1 as published by the Free Software Foundation. +# +# This library 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., http://www.fsf.org/about/contact/. +# + +"""Shared float-seconds <-> ``struct timespec *`` conversions. + +The dev and event modules both deal in seconds-as-float timeouts on +the Python side and pass ``struct timespec *`` to the C library. +The helpers take the caller's ``ffi`` instance as a parameter so the +returned CData is bound to the right CFFI module. +""" + +from __future__ import annotations + +from math import modf +from typing import Optional + +BILLION = 1000 * 1000 * 1000 + + +def fl_to_timespec(ffi, timeo: Optional[float]): + """Convert *timeo* (seconds, or None) to a ``struct timespec *``. + + *None* yields ``ffi.NULL`` (block forever). A non-positive *timeo* + yields a zeroed timespec (async / poll). + """ + if timeo is None: + return ffi.NULL + if timeo <= 0: + return ffi.new("struct timespec *", [0, 0]) + frac, whole = modf(timeo) + _timeo = ffi.new("struct timespec *") + _timeo.tv_sec = int(whole) + _timeo.tv_nsec = int(frac * BILLION) + return _timeo + + +def timespec_to_fl(ffi, _timeo) -> Optional[float]: + """Convert a ``struct timespec *`` to seconds-as-float. + + ``ffi.NULL`` yields *None* (block forever). + """ + if _timeo == ffi.NULL: + return None + if _timeo.tv_sec <= 0 and _timeo.tv_nsec == 0: + return 0.0 + return _timeo.tv_sec + _timeo.tv_nsec / BILLION diff --git a/ouroboros/qos.py b/ouroboros/qos.py index bb4ecc4..f1b6d11 100644 --- a/ouroboros/qos.py +++ b/ouroboros/qos.py @@ -19,40 +19,88 @@ # Foundation, Inc., http://www.fsf.org/about/contact/. # -# Some constants -MILLION = 1000 * 1000 -BILLION = 1000 * 1000 * 1000 +"""QoS specification for Ouroboros flows.""" + +from __future__ import annotations + +from dataclasses import dataclass +from enum import IntEnum DEFAULT_PEER_TIMEOUT = 120000 UINT32_MAX = 0xFFFFFFFF +UINT64_MAX = 0xFFFFFFFFFFFFFFFF + + +class QoSService(IntEnum): + """Mirrors ``enum qos_service`` in ``ouroboros/qos.h``.""" + RAW = 0 # No FRCT; best-effort raw messages + MESSAGE = 1 # FRCT, reliable ordered messages + STREAM = 2 # FRCT, reliable ordered byte stream +@dataclass(frozen=True) class QoSSpec: + """QoS specification for a flow. + + Frozen so the module-level ``QOS_*`` predefined cubes are safe to + share. Construct a new spec with ``dataclasses.replace(qos, ...)`` + or ``QoSSpec(...)`` to override fields. + + :ivar service: :class:`QoSService` (gates FRCT when > 0). + :ivar delay: Maximum one-way delay in ms. + :ivar bandwidth: Required bandwidth in bits/s. + :ivar availability: Class of 9s (number of nines of uptime). + :ivar loss: Maximum packet loss (per million). + :ivar ber: Maximum bit error rate (errors per billion). + :ivar max_gap: Maximum interruption in ms. + :ivar timeout: Peer timeout in ms (default 120000 ms). """ - delay: In ms, default UINT32_MAX - bandwidth: In bits / s, default 0 - availability: Class of 9s, default 0 - loss: Packet loss, default 1 - ber: Bit error rate, errors per billion bits, default 1 - in_order: In-order delivery, enables FRCT, default 0 - max_gap: Maximum interruption in ms, default UINT32_MAX - timeout: Peer timeout (ms), default 120000 (2 minutes) - """ + service: QoSService = QoSService.RAW + delay: int = UINT32_MAX + bandwidth: int = 0 + availability: int = 0 + loss: int = 1 + ber: int = 1 + max_gap: int = UINT32_MAX + timeout: int = DEFAULT_PEER_TIMEOUT + + +# Predefined QoS specs, mirroring the static const qosspec_t values in +# ouroboros/qos.h. "_safe" sets ber=0 (integrity check); "rt" trades +# reliability for latency. + +QOS_RAW = QoSSpec( + service=QoSService.RAW, + delay=UINT32_MAX, bandwidth=0, availability=0, + loss=1, ber=1, + max_gap=UINT32_MAX, timeout=0) + +QOS_RAW_SAFE = QoSSpec( + service=QoSService.RAW, + delay=UINT32_MAX, bandwidth=0, availability=0, + loss=1, ber=0, + max_gap=UINT32_MAX, timeout=0) + +QOS_RT = QoSSpec( + service=QoSService.MESSAGE, + delay=100, bandwidth=UINT64_MAX, availability=3, + loss=1, ber=1, + max_gap=100, timeout=DEFAULT_PEER_TIMEOUT) + +QOS_RT_SAFE = QoSSpec( + service=QoSService.MESSAGE, + delay=100, bandwidth=UINT64_MAX, availability=3, + loss=1, ber=0, + max_gap=100, timeout=DEFAULT_PEER_TIMEOUT) + +QOS_MSG = QoSSpec( + service=QoSService.MESSAGE, + delay=1000, bandwidth=0, availability=0, + loss=0, ber=0, + max_gap=2000, timeout=DEFAULT_PEER_TIMEOUT) - def __init__(self, - delay: int = UINT32_MAX, - bandwidth: int = 0, - availability: int = 0, - loss: int = 1, - ber: int = 1, - in_order: int = 0, - max_gap: int = UINT32_MAX, - timeout: int = DEFAULT_PEER_TIMEOUT): - self.delay = delay - self.bandwidth = bandwidth - self.availability = availability - self.loss = loss - self.ber = ber - self.in_order = in_order - self.max_gap = max_gap - self.timeout = timeout +QOS_STREAM = QoSSpec( + service=QoSService.STREAM, + delay=1000, bandwidth=0, availability=0, + loss=0, ber=0, + max_gap=2000, timeout=DEFAULT_PEER_TIMEOUT) |
