aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/tests/test_sctp_stats.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-node/tests/test_sctp_stats.py')
-rw-r--r--packages/meshbay-node/tests/test_sctp_stats.py74
1 files changed, 74 insertions, 0 deletions
diff --git a/packages/meshbay-node/tests/test_sctp_stats.py b/packages/meshbay-node/tests/test_sctp_stats.py
new file mode 100644
index 0000000..bf8970d
--- /dev/null
+++ b/packages/meshbay-node/tests/test_sctp_stats.py
@@ -0,0 +1,74 @@
+"""Losses and T3 expirations are counted per association (see sctp_stats.py).
+
+Replayed against aiortc's own RTCSctpTransport: five chunks sent and the first
+one lost, so three acknowledgements in a row report it missing and the sender
+enters fast recovery, once. Then T3 expires, once.
+"""
+
+import asyncio
+
+from aiortc.rtcsctptransport import RTCSctpTransport, SackChunk
+from meshbay_node.transport.webrtc import sctp_stats
+
+
+class _Dtls:
+ state = "connected"
+
+ def _register_data_receiver(self, *a):
+ pass
+
+
+class _Pc:
+ def __init__(self, sctp):
+ self.sctp = sctp
+
+
+def _sack(cumulative_tsn: int, gaps: list) -> SackChunk:
+ sack = SackChunk()
+ sack.cumulative_tsn = cumulative_tsn % 2**32
+ sack.gaps = gaps
+ sack.duplicates = []
+ sack.advertised_rwnd = 1 << 20
+ return sack
+
+
+async def _replay() -> RTCSctpTransport:
+ s = RTCSctpTransport(_Dtls())
+ s._association_state = s.State.ESTABLISHED
+
+ async def sent(*_a, **_k):
+ pass
+
+ s._send_chunk = sent
+ for name in ("_t3_start", "_t3_restart", "_t3_cancel"):
+ setattr(s, name, lambda: None)
+ s._cwnd = 100_000
+
+ for _ in range(5):
+ await s._send(1, 51, b"x" * 1000)
+ first = s._sent_queue[0].tsn
+ for last_seen in (2, 3, 4):
+ await s._receive_sack_chunk(_sack(first - 1, [(2, last_seen + 1)]))
+ # Still recovering from the same loss: one episode, not a second.
+ await s._receive_sack_chunk(_sack(first - 1, [(2, 5)]))
+ s._t3_expired()
+ return s
+
+
+def test_a_loss_and_a_t3_expiry_are_each_counted_once():
+ s = asyncio.run(_replay())
+ assert s._mb_losses == 1
+ assert s._mb_t3 == 1
+ assert sctp_stats.describe(_Pc(s)).endswith("loss=1 t3=1")
+
+
+def test_an_association_that_lost_nothing_says_so():
+ async def fresh() -> RTCSctpTransport:
+ return RTCSctpTransport(_Dtls())
+
+ s = asyncio.run(fresh())
+ assert sctp_stats.describe(_Pc(s)).endswith("rtt=? loss=0 t3=0")
+
+
+def test_a_session_without_an_association_adds_nothing():
+ assert sctp_stats.describe(None) == ""