aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/tests/test_sctp_stats.py
blob: bf8970dc85bb5194de8bb49b0fa7b4d551531cc6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
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) == ""