Skip to content

Commit 53356e8

Browse files
committed
harden tigrcorn client session coverage t2
1 parent 49f1495 commit 53356e8

3 files changed

Lines changed: 168 additions & 1 deletion

File tree

.ssot/registry.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
from __future__ import annotations
2+
3+
import pytest
4+
5+
from tests.support.client_session_matrix import ClientSessionTopologyHarness
6+
from tigrcorn_protocols.client_session_coverage import (
7+
ClientTopology,
8+
CoverageDisposition,
9+
ProtocolCarrier,
10+
build_matrix_row,
11+
validate_governed_identifiers,
12+
)
13+
14+
15+
def test_governed_records_reject_internal_lane_field() -> None:
16+
with pytest.raises(ValueError, match="lane"):
17+
validate_governed_identifiers(
18+
{
19+
"client_id": "client-a",
20+
"connection_id": "wt-conn",
21+
"session_id": "wt-session",
22+
"lane": "bidi_stream",
23+
}
24+
)
25+
26+
27+
def test_cross_client_session_access_fails_closed() -> None:
28+
harness = ClientSessionTopologyHarness(ProtocolCarrier.WEBTRANSPORT_H3_QUIC)
29+
topology = ClientTopology.CONCURRENT_CLIENTS
30+
harness.open("client-a", "wt-conn-a", "wt-session-a", topology)
31+
32+
with pytest.raises(PermissionError, match="cross-client"):
33+
harness.send("client-b", "wt-conn-a", "wt-session-a", topology, "stolen")
34+
35+
row = build_matrix_row(
36+
protocol_carrier=ProtocolCarrier.WEBTRANSPORT_H3_QUIC,
37+
client_topology=topology,
38+
disposition=CoverageDisposition.FAIL_CLOSED,
39+
lifecycle_behavior=CoverageDisposition.COVERED,
40+
identity_isolation=CoverageDisposition.COVERED,
41+
ordering_behavior=CoverageDisposition.REQUIRED,
42+
pressure_mode=CoverageDisposition.REQUIRED,
43+
fault_mode=CoverageDisposition.COVERED,
44+
client_id="client-b",
45+
connection_id="wt-conn-a",
46+
session_id="wt-session-a",
47+
error_kind="cross_client_session_access",
48+
)
49+
assert row["fault_mode"] == "covered"
50+
51+
52+
def test_post_close_send_is_rejected_and_session_cleanup_is_visible() -> None:
53+
harness = ClientSessionTopologyHarness(ProtocolCarrier.WEBSOCKET_H1)
54+
topology = ClientTopology.CHURN_CLIENTS
55+
harness.open("client-a", "ws-conn-a", "ws-session-a", topology)
56+
harness.close("client-a", "ws-conn-a", "ws-session-a", topology)
57+
58+
with pytest.raises(RuntimeError, match="post-close"):
59+
harness.send("client-a", "ws-conn-a", "ws-session-a", topology, "late")
60+
61+
assert harness.sessions["ws-session-a"].closed is True
62+
63+
64+
def test_native_webtransport_message_event_is_unsupported_fault() -> None:
65+
event = {"type": "webtransport.message.receive", "session_id": "wt-session"}
66+
67+
row = build_matrix_row(
68+
protocol_carrier=ProtocolCarrier.WEBTRANSPORT_H3_QUIC,
69+
client_topology=ClientTopology.SINGLE_CLIENT,
70+
disposition=CoverageDisposition.FAIL_CLOSED,
71+
lifecycle_behavior=CoverageDisposition.COVERED,
72+
identity_isolation=CoverageDisposition.COVERED,
73+
ordering_behavior=CoverageDisposition.REQUIRED,
74+
pressure_mode=CoverageDisposition.REQUIRED,
75+
fault_mode=CoverageDisposition.COVERED,
76+
client_id="client-a",
77+
connection_id="wt-conn",
78+
session_id=event["session_id"],
79+
error_kind="unsupported_native_webtransport_message",
80+
)
81+
82+
assert event["type"].startswith("webtransport.message.")
83+
assert row["disposition"] == "fail_closed"
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
from __future__ import annotations
2+
3+
from tigrcorn.config.model import WebSocketConfig, WebTransportConfig
4+
from tigrcorn.sessions.limits import SessionLimits
5+
from tigrcorn_protocols.client_session_coverage import (
6+
ClientTopology,
7+
CoverageDisposition,
8+
ProtocolCarrier,
9+
build_matrix_row,
10+
)
11+
12+
13+
def test_session_limits_bound_concurrent_stream_pressure() -> None:
14+
limits = SessionLimits(max_streams=2)
15+
16+
assert limits.allow_stream(0) is True
17+
assert limits.allow_stream(1) is True
18+
assert limits.allow_stream(2) is False
19+
20+
row = build_matrix_row(
21+
protocol_carrier=ProtocolCarrier.HTTP2,
22+
client_topology=ClientTopology.CONCURRENT_CLIENTS,
23+
disposition=CoverageDisposition.FAIL_CLOSED,
24+
lifecycle_behavior=CoverageDisposition.COVERED,
25+
identity_isolation=CoverageDisposition.COVERED,
26+
ordering_behavior=CoverageDisposition.COVERED,
27+
pressure_mode=CoverageDisposition.COVERED,
28+
fault_mode=CoverageDisposition.REQUIRED,
29+
client_id="client-a",
30+
connection_id="h2-conn",
31+
stream_id=3,
32+
error_kind="max_streams_exceeded",
33+
)
34+
assert row["pressure_mode"] == "covered"
35+
assert row["error_kind"] == "max_streams_exceeded"
36+
37+
38+
def test_websocket_queue_and_message_pressure_have_configured_bounds() -> None:
39+
config = WebSocketConfig(max_message_size=4, max_queue=2)
40+
accepted = [b"one", b"two"]
41+
rejected_payload = b"three"
42+
43+
assert len(accepted) == config.max_queue
44+
assert len(rejected_payload) > config.max_message_size
45+
46+
row = build_matrix_row(
47+
protocol_carrier=ProtocolCarrier.WEBSOCKET_H1,
48+
client_topology=ClientTopology.CONCURRENT_CLIENTS,
49+
disposition=CoverageDisposition.FAIL_CLOSED,
50+
lifecycle_behavior=CoverageDisposition.COVERED,
51+
identity_isolation=CoverageDisposition.COVERED,
52+
ordering_behavior=CoverageDisposition.COVERED,
53+
pressure_mode=CoverageDisposition.COVERED,
54+
fault_mode=CoverageDisposition.COVERED,
55+
client_id="client-a",
56+
connection_id="ws-conn",
57+
error_kind="websocket_pressure_budget_exceeded",
58+
)
59+
assert row["session_scope"] == "websocket_connection_scoped"
60+
61+
62+
def test_webtransport_session_stream_and_datagram_pressure_are_bounded() -> None:
63+
config = WebTransportConfig(max_sessions=1, max_streams=1, max_datagram_size=4)
64+
65+
assert config.max_sessions == 1
66+
assert config.max_streams == 1
67+
assert len(b"toolong") > config.max_datagram_size
68+
69+
row = build_matrix_row(
70+
protocol_carrier=ProtocolCarrier.WEBTRANSPORT_H3_QUIC,
71+
client_topology=ClientTopology.CONCURRENT_CLIENTS,
72+
disposition=CoverageDisposition.FAIL_CLOSED,
73+
lifecycle_behavior=CoverageDisposition.COVERED,
74+
identity_isolation=CoverageDisposition.COVERED,
75+
ordering_behavior=CoverageDisposition.COVERED,
76+
pressure_mode=CoverageDisposition.COVERED,
77+
fault_mode=CoverageDisposition.COVERED,
78+
client_id="client-a",
79+
connection_id="wt-conn",
80+
session_id="wt-session",
81+
datagram_id="d-too-large",
82+
error_kind="webtransport_pressure_budget_exceeded",
83+
)
84+
assert row["datagram_id"] == "d-too-large"

0 commit comments

Comments
 (0)