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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
|
"""
Joining a group must fail, or succeed — never wait forever.
Reported: a first attempt to connect hung with nothing on screen, and the same
account connected a few minutes later. That is the shape of a network wait with
no deadline, not of a refusal, and there were two of them.
**ICE gathering.** Signaling here is non-trickle — the offer carries its
candidates, so it is not sent until gathering says it is done. A STUN server
that is slow, filtered, or resolved through a DNS that is not answering means
`icegatheringstatechange` never reaches `complete`, and `connect()` never
returns. Same shape as the `fullscreen` denial: a promise that never settles
produces no error to find.
**The hub call in the desktop client.** Node's `fetch` has no default timeout,
so a host that accepts a connection and then says nothing holds the request for
as long as the OS allows. `hub:probe` had a deadline; `hub:fetch`, which carries
signaling, did not.
"""
import re
from pathlib import Path
import pytest
from spa_source import transport_source
STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static"
TRANSPORT = STATIC / "transport.js"
MAIN = (Path(__file__).resolve().parents[2] / "meshbay-client" / "src" / "main.js")
pytestmark = pytest.mark.skipif(not TRANSPORT.exists(),
reason="SPA sources unavailable")
def _gathering_block() -> str:
"""The wait on ICE gathering, and only it."""
source = transport_source()
start = source.index("iceGatheringState")
return source[max(0, start - 900):start + 700]
def test_ice_gathering_has_a_deadline():
block = _gathering_block()
assert "setTimeout" in block, (
"the wait on ICE gathering can never end, and connect() with it")
assert "ICE_GATHER_TIMEOUT_MS" in block
def test_the_deadline_is_long_enough_for_a_stun_round_trip():
"""Cutting gathering off too early drops the reflexive candidate and breaks
every connection that is not on the same network."""
source = transport_source()
match = re.search(r"const ICE_GATHER_TIMEOUT_MS = (\d+);", source)
assert match, "the constant is gone or was renamed"
assert 2000 <= int(match.group(1)) <= 10000
def test_a_timed_out_gathering_still_sends_the_offer():
"""Host candidates are already gathered, which is enough on a LAN. Giving
up instead would turn a slow STUN server into a refusal to connect."""
source = transport_source()
block = _gathering_block()
# The deadline resolves the promise; it does not reject it.
assert "reject" not in block.split("setTimeout", 1)[1][:300]
# And the offer is still posted afterwards.
assert "webrtc/offer" in source[source.index("iceGatheringState"):]
@pytest.mark.skipif(not MAIN.exists(), reason="the desktop client is not present")
def test_every_hub_call_from_the_client_has_a_deadline():
source = MAIN.read_text(encoding="utf-8")
block = source.split("ipcMain.handle('hub:fetch'", 1)[1].split("ipcMain.handle", 1)[0]
assert "AbortSignal.timeout" in block, (
"a hub that accepts the connection and says nothing holds this for "
"as long as the OS allows")
@pytest.mark.skipif(not MAIN.exists(), reason="the desktop client is not present")
def test_the_deadline_outlasts_the_hubs_own_longest_call():
"""Signaling waits fifteen seconds for a node to answer an offer. A client
deadline under that would abort calls that were about to succeed."""
source = MAIN.read_text(encoding="utf-8")
match = re.search(r"const HUB_FETCH_TIMEOUT_MS = (\d+);", source)
assert match, "the constant is gone or was renamed"
assert int(match.group(1)) > 15000
@pytest.mark.skipif(not MAIN.exists(), reason="the desktop client is not present")
def test_a_timeout_says_so_rather_than_saying_fetch_failed():
source = MAIN.read_text(encoding="utf-8")
block = source.split("ipcMain.handle('hub:fetch'", 1)[1].split("ipcMain.handle", 1)[0]
assert "TimeoutError" in block and "did not answer" in block
|