aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDimitri Staessens <dimitri@ouroboros.rocks>2026-06-30 18:56:00 +0200
committerDimitri Staessens <dimitri@ouroboros.rocks>2026-06-30 19:05:31 +0200
commita6c2bf9cbfcd3e13569d3a7d915c27d42fb7bbec (patch)
treefe14d51fd2c9b0b1b34420c5bf12683030e2fa38
parent5f2bc27eacafb2b714317dd9cc0f286c774bdb64 (diff)
downloadpyouroboros-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.py70
-rw-r--r--ouroboros/_timespec.py64
-rw-r--r--ouroboros/qos.py106
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)