CultLib/packages/cultnet-py/tests/test_cultnet.py
Meta Vens 3b83cb6c08 Merge origin/main into hands/variants-c2a-main (main-into-c2a)
Sixteen conflicted files resolved by the spec's rule: main owns payload
ownership, bounds and the Clock seam; C2a owns persistence semantics and exact
binding. MintStoredAt is internal static (previous, now); the cache and the
store pass their cache's Clock. Package versions are main's. The cultmesh-ts
payload-parse acceptance and its test are deleted under retire-schema-alias;
peer snapshot reads take the schema's own id, else an id the definition lists
as compatible. The held firstRecordAtKey fallback is removed: main's handles
take definitions only, so nothing could reach it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 04:06:41 +02:00

2751 lines
126 KiB
Python

from __future__ import annotations
import json
import socket
import threading
import time
import unittest
import hashlib
import hmac
import io
from dataclasses import dataclass, replace
from datetime import UTC, datetime
from pathlib import Path
from typing import Callable
from uuid import uuid4
from cultcache_py import CultCache, define_database_entry_type
from cultnet_py.benchmark import run_benchmark
from cultnet_py.compare_csharp import (
DEFAULT_PARITY_THRESHOLD,
DEFAULT_SAMPLE_COUNT,
_compare_common_metrics,
_median_result,
_parity_status,
)
from cultnet_py.interop_peer import (
append_shard_log_put,
build_state,
handle_server_message,
raw_snapshot_response,
)
from cultnet_py import (
compute_simulation_claim_hash,
create_rudp_schema_transport,
rudp_client_bind_host,
CultNetDatabaseChange,
CultNetRawClient,
CultNetRawDocumentRecord,
CultNetRawSnapshotResponse,
CultNetSchemaCatalog,
CultNetSchemaDescriptor,
CultNetShardCatalog,
CultNetShardDescriptor,
CultNetShardLogEntry,
CultNetShardLogResponse,
CultNetPeerError,
CultNetClientSecurityOptions,
CultNetSecret,
CultNetServerSecurityOptions,
CultNetSimulationConsensusOptions,
CultNetSimulationConsensusCandidate,
CultNetSimulationObservation,
CultNetRudpPacket,
CultNetRudpPacketType,
CultNetRudpSendOptions,
CultNetRudpSession,
CultNetRudpSessionOptions,
CultNetRudpSocketMode,
CultNetRudpReconnectLoop,
CultNetRudpSocketTransportConnection,
CultNetRudpSocketTransportOptions,
TcpFramedTransportConnection,
CultNetWitnessArtifactBundle,
apply_raw_document_record,
apply_raw_snapshot,
apply_shard_log_response,
create_rudp_transport_profile,
create_tcp_framed_transport_profile,
create_reconnect_policy,
CultNetReconnectController,
database_subscribe,
database_unsubscribe,
decode_frame,
decode_rudp_packet,
decode_witness_artifact_bundle_payload,
document_delete,
document_put_raw,
encode_frame,
encode_rudp_packet,
encode_witness_artifact_bundle_payload,
hello,
login,
login_success,
parse_message,
register,
schema_catalog_request,
schema_document_map,
shard_catalog_request,
shard_log_request,
simulation_observation,
snapshot_request,
verify_session,
wire_message_schema_catalog,
wire_message_schema_descriptors,
witness_artifact_bundle,
compute_reconnect_delay_ms,
)
def acknowledges(packet, sequence: int) -> bool:
"""Whether the packet's ack field and mask cover the sequence."""
if packet.ack == sequence:
return True
return (
packet.ack > sequence
and packet.ack - sequence <= 32
and (packet.ack_mask & (1 << (packet.ack - sequence - 1))) != 0
)
def bind_udp_socket() -> socket.socket:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(("127.0.0.1", 0))
sock.settimeout(0.02)
return sock
def pump_rudp_handshake(
client: CultNetRudpSocketTransportConnection,
server: CultNetRudpSocketTransportConnection,
) -> None:
for _ in range(20):
server.receive_once()
client.receive_once()
server.receive_once()
if client.connected and server.connected:
return
time.sleep(0.005)
raise AssertionError("RUDP socket handshake did not complete")
def receive_rudp_frame(transport: CultNetRudpSocketTransportConnection):
for _ in range(20):
frame = transport.receive_once()
if frame is not None:
return frame
time.sleep(0.005)
raise AssertionError("RUDP socket frame was not delivered")
@dataclass
class Item:
name: str
category: str
value: int
class CultNetTests(unittest.TestCase):
def test_benchmark_reports_core_hot_path_metrics(self) -> None:
result = run_benchmark(8)
metric_names = {metric["name"] for metric in result["metrics"]}
self.assertEqual(result["records"], 8)
self.assertEqual(result["wireMessageCount"], 8)
self.assertEqual(metric_names, {
"database_entry_encode",
"database_entry_decode",
"cultnet_frame_parse",
"raw_snapshot_apply",
"cache_upsert",
"cache_get",
})
self.assertTrue(all(metric["opsPerSecond"] > 0 for metric in result["metrics"]))
def test_compare_csharp_median_result_summarizes_samples(self) -> None:
self.assertEqual(DEFAULT_SAMPLE_COUNT, 3)
self.assertEqual(DEFAULT_PARITY_THRESHOLD, 0.90)
samples = [
{
"runtime": "python",
"records": 3,
"metrics": [
{"name": "cache_get", "operations": 3, "elapsedMs": 30.0, "opsPerSecond": 100.0},
{"name": "cache_upsert", "operations": 3, "elapsedMs": 60.0, "opsPerSecond": 50.0},
],
},
{
"runtime": "python",
"records": 3,
"metrics": [
{"name": "cache_get", "operations": 3, "elapsedMs": 10.0, "opsPerSecond": 300.0},
{"name": "cache_upsert", "operations": 3, "elapsedMs": 20.0, "opsPerSecond": 150.0},
],
},
{
"runtime": "python",
"records": 3,
"metrics": [
{"name": "cache_get", "operations": 3, "elapsedMs": 20.0, "opsPerSecond": 200.0},
{"name": "cache_upsert", "operations": 3, "elapsedMs": 40.0, "opsPerSecond": 75.0},
],
},
]
summarized = _median_result(samples)
self.assertEqual(summarized["sampleCount"], 3)
self.assertEqual(summarized["metrics"][0]["name"], "cache_get")
self.assertEqual(summarized["metrics"][0]["elapsedMs"], 20.0)
self.assertEqual(summarized["metrics"][0]["opsPerSecond"], 200.0)
self.assertEqual(summarized["metrics"][1]["opsPerSecond"], 75.0)
def test_compare_csharp_marks_threshold_status_per_metric(self) -> None:
python_result = {
"metrics": [
{"name": "cache_get", "operations": 3, "elapsedMs": 2.0, "opsPerSecond": 50.0},
{"name": "cache_upsert", "operations": 3, "elapsedMs": 1.0, "opsPerSecond": 95.0},
],
}
csharp_result = {
"metrics": [
{"name": "cache_get", "operations": 3, "elapsedMs": 1.0, "opsPerSecond": 100.0},
{"name": "cache_upsert", "operations": 3, "elapsedMs": 1.0, "opsPerSecond": 100.0},
],
}
comparison = _compare_common_metrics(python_result, csharp_result, parity_threshold=0.90)
by_name = {item["name"]: item for item in comparison}
self.assertFalse(by_name["cache_get"]["meetsParityThreshold"])
self.assertTrue(by_name["cache_upsert"]["meetsParityThreshold"])
self.assertEqual(_parity_status(comparison, "ok"), "below-threshold")
self.assertEqual(_parity_status([by_name["cache_upsert"]], "ok"), "meets-threshold")
self.assertEqual(_parity_status(comparison, "failed"), "unknown")
def test_compare_csharp_marks_meets_threshold_when_all_common_metrics_pass(self) -> None:
comparison = _compare_common_metrics(
{
"metrics": [
{"name": "cache_get", "operations": 3, "elapsedMs": 1.0, "opsPerSecond": 95.0},
{"name": "cache_upsert", "operations": 3, "elapsedMs": 1.0, "opsPerSecond": 120.0},
],
},
{
"metrics": [
{"name": "cache_get", "operations": 3, "elapsedMs": 1.0, "opsPerSecond": 100.0},
{"name": "cache_upsert", "operations": 3, "elapsedMs": 1.0, "opsPerSecond": 100.0},
],
},
parity_threshold=0.90,
)
self.assertEqual(_parity_status(comparison, "ok"), "meets-threshold")
def test_cultnet_security_options_match_cultlib_defaults_and_environment_rules(self) -> None:
client = CultNetClientSecurityOptions.development()
server = CultNetServerSecurityOptions.development()
self.assertEqual(client.connection_key, "gamecult-dev-connection-key")
self.assertEqual(server.connection_key, "gamecult-dev-connection-key")
self.assertTrue(server.is_development)
self.assertEqual(server.to_client_options().connection_key, client.connection_key)
self.assertEqual(client.encryption_key(), hashlib.sha256(b"gamecult-dev-connection-key").digest())
self.assertEqual(server.session_signing_key(), hashlib.sha256(b"gamecult-dev-session-signing-secret").digest())
self.assertEqual(
CultNetServerSecurityOptions.from_environment({}, allow_development_defaults=True),
server,
)
with self.assertRaisesRegex(ValueError, "not configured"):
CultNetServerSecurityOptions.from_environment({})
with self.assertRaisesRegex(ValueError, "GAMECULT_SESSION_SIGNING_SECRET"):
CultNetServerSecurityOptions.from_environment({"GAMECULT_CONNECTION_KEY": "key"})
configured = CultNetServerSecurityOptions.from_environment({
"GAMECULT_CONNECTION_KEY": "prod-key",
"GAMECULT_SESSION_SIGNING_SECRET": "prod-secret",
})
self.assertEqual(configured.connection_key, "prod-key")
self.assertFalse(configured.is_development)
def test_cultnet_secret_helpers_validate_csharp_versioned_session_tokens(self) -> None:
security = CultNetServerSecurityOptions("connection-key", "session-secret")
user_id = uuid4()
expires = datetime(2035, 1, 2, 3, 4, 5, tzinfo=UTC)
token = CultNetSecret.create_session_token(user_id, expires, security, session_version=7)
payload_b64, signature_b64 = token.split(".")
self.assertEqual(
CultNetSecret.from_base64url(payload_b64).decode("utf-8"),
f"{user_id.hex}|{int(expires.timestamp())}|7",
)
self.assertEqual(
CultNetSecret.from_base64url(signature_b64),
hmac.new(
hashlib.sha256(b"session-secret").digest(),
CultNetSecret.from_base64url(payload_b64),
hashlib.sha256,
).digest(),
)
validated = CultNetSecret.try_validate_session_token(
token,
security,
now_utc=datetime(2035, 1, 2, 3, 4, 4, tzinfo=UTC),
)
self.assertIsNotNone(validated)
assert validated is not None
self.assertEqual(validated.user_id, user_id)
self.assertEqual(validated.expires_at_utc, expires)
self.assertEqual(validated.session_version, 7)
tampered_payload = CultNetSecret.to_base64url(CultNetSecret.from_base64url(payload_b64) + b"tamper")
tampered = f"{tampered_payload}.{signature_b64}"
self.assertIsNone(CultNetSecret.try_validate_session_token(tampered, security))
self.assertIsNone(CultNetSecret.try_validate_session_token(token, security, now_utc=expires))
def test_cultnet_secret_helpers_validate_legacy_two_field_session_tokens(self) -> None:
security = CultNetServerSecurityOptions.development()
user_id = uuid4()
expires_at_seconds = int(datetime(2036, 1, 1, tzinfo=UTC).timestamp())
payload = f"{user_id.hex}|{expires_at_seconds}".encode("utf-8")
signature = hmac.new(security.session_signing_key(), payload, hashlib.sha256).digest()
token = f"{CultNetSecret.to_base64url(payload)}.{CultNetSecret.to_base64url(signature)}"
validated = CultNetSecret.try_validate_session_token(
token,
security,
now_utc=datetime(2035, 12, 31, tzinfo=UTC),
)
self.assertIsNotNone(validated)
assert validated is not None
self.assertEqual(validated.user_id, user_id)
self.assertEqual(validated.session_version, 0)
def test_cultnet_secret_encryption_helpers_are_optional_without_crypto_dependency(self) -> None:
nonce = b"123456789012"
security = CultNetServerSecurityOptions.development()
try:
encrypted = CultNetSecret.encrypt_string("hello", nonce, security.to_client_options())
except ImportError as exc:
self.assertIn("cryptography", str(exc))
return
self.assertEqual(CultNetSecret.decrypt_string(encrypted, nonce, security), "hello")
def test_cultnet_schema_message_frame_round_trip(self) -> None:
message = hello(
runtime_id="python-test",
supported_schema_versions=["cultnet.hello.v0"],
transport_profiles=[
{
"schemaVersion": "cultnet.transport_profile.v0",
"runtimeId": "python-test",
"transports": [
{
"transportId": "test",
"protocol": "tcp_framed",
"channels": [{"channelId": "schema", "delivery": "reliable", "ordering": "ordered"}],
}
],
}
],
)
parsed = parse_message(decode_frame(encode_frame(message.to_bytes())))
self.assertEqual(parsed.schema_version, "cultnet.hello.v0")
self.assertEqual(parsed.body["runtimeId"], "python-test")
self.assertEqual(parsed.body["transportProfiles"][0]["transports"][0]["protocol"], "tcp_framed")
def test_cultnet_tcp_framed_transport_carries_schema_payloads_with_stats(self) -> None:
payload = b"cultnet-payload"
output = io.BytesIO()
sender = TcpFramedTransportConnection(
output,
profile=create_tcp_framed_transport_profile("sender", transport_id="test-tcp"),
)
sender.send("schema", payload)
self.assertEqual(sender.stats.frames_sent, 1)
self.assertEqual(sender.stats.bytes_sent, len(payload) + 4)
with self.assertRaisesRegex(ValueError, "only supports the schema channel"):
sender.send("unreliable", b"")
receiver = TcpFramedTransportConnection(
io.BytesIO(output.getvalue()),
profile=create_tcp_framed_transport_profile("receiver", transport_id="test-tcp"),
)
frame = receiver.receive()
self.assertEqual(frame.channel_id, "schema")
self.assertEqual(frame.payload, payload)
self.assertEqual(receiver.stats.frames_received, 1)
self.assertEqual(receiver.stats.bytes_received, len(payload) + 4)
self.assertEqual(receiver.profile["transports"][0]["protocol"], "tcp_framed")
def test_cultnet_raw_client_uses_schema_transport_factory(self) -> None:
import msgpack # type: ignore
sent_messages: list[dict[str, object]] = []
test_case = self
class FakeSchemaTransport:
profile = create_tcp_framed_transport_profile("fake-client")
def __enter__(self) -> "FakeSchemaTransport":
return self
def __exit__(self, exc_type: object, exc: object, traceback: object) -> None:
self.close()
def send(self, channel_id: str, payload: bytes) -> None:
test_case.assertEqual(channel_id, "schema")
sent_messages.append(msgpack.unpackb(payload, raw=False))
def receive(self) -> object:
response = {
"schemaVersion": "cultnet.schema_catalog_response.v0",
"messageId": "transport-factory",
"schemas": [],
}
return type("Frame", (), {"channel_id": "schema", "payload": msgpack.packb(response, use_bin_type=True)})()
def close(self) -> None:
pass
client = CultNetRawClient(
"unused.example.test",
1,
create_transport=FakeSchemaTransport,
)
response = client.fetch_schema_catalog(message_id="transport-factory")
self.assertEqual(response["schemaVersion"], "cultnet.schema_catalog_response.v0")
self.assertEqual(sent_messages[0]["schemaVersion"], "cultnet.schema_catalog_request.v0")
self.assertEqual(sent_messages[0]["messageId"], "transport-factory")
def test_cultnet_rudp_packet_codec_uses_deterministic_reliable_ordered_fixture(self) -> None:
encoded = encode_rudp_packet(
CultNetRudpPacket(
packet_type=CultNetRudpPacketType.DATA,
connection_id=0x01020304,
sequence=0x0000002A,
ack=0x00000029,
ack_mask=0x80000001,
channel_id="schema",
reliable=True,
ordered=True,
fragment_id=7,
fragment_index=1,
fragment_count=3,
payload=b"hello",
)
)
self.assertEqual(
encoded.hex(),
"434e523000030b2a010203040000002a0000002980000001000700010003000000050600736368656d6168656c6c6f",
)
decoded = decode_rudp_packet(encoded)
self.assertEqual(decoded.packet_type, CultNetRudpPacketType.DATA)
self.assertEqual(decoded.connection_id, 0x01020304)
self.assertEqual(decoded.sequence, 0x0000002A)
self.assertEqual(decoded.ack, 0x00000029)
self.assertEqual(decoded.ack_mask, 0x80000001)
self.assertEqual(decoded.channel_id, "schema")
self.assertTrue(decoded.reliable)
self.assertTrue(decoded.ordered)
self.assertFalse(decoded.sequenced)
self.assertEqual(decoded.fragment_id, 7)
self.assertEqual(decoded.fragment_index, 1)
self.assertEqual(decoded.fragment_count, 3)
self.assertEqual(decoded.payload, b"hello")
def test_cultnet_rudp_transport_profile_advertises_state_and_realtime_channels(self) -> None:
profile = create_rudp_transport_profile(
"python-rudp",
transport_id="public-rudp",
host="127.0.0.1",
port=7777,
max_payload_bytes=1200,
max_fragment_bytes=1000,
)
self.assertEqual(profile["transports"][0]["protocol"], "rudp")
self.assertEqual(profile["transports"][0]["reconnectPolicy"]["schemaVersion"], "cultnet.reconnect_policy.v0")
self.assertEqual(profile["transports"][0]["reconnectPolicy"]["baseDelayMs"], 1_000)
self.assertEqual(
[
(channel["channelId"], channel["delivery"], channel["ordering"])
for channel in profile["transports"][0]["channels"]
],
[
("schema", "reliable", "ordered"),
("latest", "unreliable", "sequenced"),
("realtime", "unreliable", "unordered"),
],
)
def test_cultnet_reconnect_policy_exposes_portable_delay_contract(self) -> None:
policy = create_reconnect_policy(policy_id="rudp-default", max_attempts=8)
self.assertEqual(policy.schema_version, "cultnet.reconnect_policy.v0")
self.assertEqual(policy.policy_id, "rudp-default")
self.assertEqual(policy.max_attempts, 8)
self.assertEqual(policy.to_wire()["policyId"], "rudp-default")
self.assertEqual(policy.to_wire()["maxAttempts"], 8)
self.assertEqual(compute_reconnect_delay_ms(policy, 1), 1_000)
self.assertEqual(compute_reconnect_delay_ms(policy, 3, 17), 4_017)
self.assertEqual(compute_reconnect_delay_ms(policy, 9, 999), 30_250)
self.assertEqual(compute_reconnect_delay_ms(policy, 0, -5), 1_000)
def test_cultnet_reconnect_controller_schedules_attempts_and_reset(self) -> None:
controller = CultNetReconnectController(create_reconnect_policy(max_attempts=2))
first = controller.record_failure(10_000)
self.assertEqual(first.attempt, 1)
self.assertTrue(first.should_retry)
self.assertEqual(first.delay_ms, 1_000)
self.assertEqual(first.next_attempt_at_ms, 11_000)
self.assertFalse(first.exhausted)
self.assertFalse(controller.can_attempt(10_999))
self.assertTrue(controller.can_attempt(11_000))
second = controller.record_failure(11_000, 17)
self.assertEqual(second.attempt, 2)
self.assertEqual(second.delay_ms, 2_017)
self.assertEqual(second.next_attempt_at_ms, 13_017)
self.assertTrue(second.should_retry)
exhausted = controller.record_failure(13_017)
self.assertEqual(exhausted.attempt, 2)
self.assertFalse(exhausted.should_retry)
self.assertEqual(exhausted.delay_ms, 0)
self.assertIsNone(exhausted.next_attempt_at_ms)
self.assertTrue(exhausted.exhausted)
self.assertFalse(controller.can_attempt(99_000))
controller.reset()
self.assertEqual(controller.attempt, 0)
self.assertIsNone(controller.next_attempt_at_ms)
self.assertFalse(controller.exhausted)
self.assertTrue(controller.can_attempt(99_000))
def test_cultnet_rudp_reconnect_loop_consumes_shared_controller(self) -> None:
class FakeRudpReconnectTransport:
def __init__(self) -> None:
self.connect_calls = 0
self.close_calls = 0
self.last_payload: bytes | None = None
def connect(self, payload: bytes = b"") -> None:
self.connect_calls += 1
self.last_payload = bytes(payload)
def close(self) -> None:
self.close_calls += 1
now_ms = 10_000
captured_callback: list[Callable[[], None]] = []
transports: list[FakeRudpReconnectTransport] = []
def create_transport() -> FakeRudpReconnectTransport:
transport = FakeRudpReconnectTransport()
transports.append(transport)
return transport
def scheduler(delay_ms: int, callback: Callable[[], None]) -> Callable[[], None]:
self.assertEqual(delay_ms, 1_017)
captured_callback[:] = [callback]
def cancel() -> None:
captured_callback.clear()
return cancel
loop = CultNetRudpReconnectLoop(
create_transport,
reconnect_policy=create_reconnect_policy(max_attempts=2),
connect_payload=b"join",
now_ms=lambda: now_ms,
jitter_ms=lambda: 17,
scheduler=scheduler,
)
first = loop.start()
self.assertEqual(first.connect_calls, 1)
self.assertEqual(first.last_payload, b"join")
self.assertIs(loop.transport, first)
decision = loop.handle_closed()
self.assertIsNotNone(decision)
self.assertEqual(decision.attempt, 1)
self.assertTrue(decision.should_retry)
self.assertEqual(decision.delay_ms, 1_017)
self.assertEqual(decision.next_attempt_at_ms, 11_017)
self.assertEqual(loop.reconnect_controller.attempt, 1)
self.assertEqual(loop.reconnect_controller.next_attempt_at_ms, 11_017)
self.assertEqual(len(captured_callback), 1)
now_ms = 11_017
captured_callback[0]()
self.assertEqual(len(transports), 2)
self.assertEqual(transports[1].connect_calls, 1)
self.assertIs(loop.transport, transports[1])
loop.mark_connected()
self.assertEqual(loop.reconnect_controller.attempt, 0)
loop.stop()
self.assertEqual(transports[1].close_calls, 1)
self.assertIsNone(loop.transport)
def test_cultnet_rudp_session_handshake_acks_reliable_connect_and_accept_packets(self) -> None:
client = CultNetRudpSession(
CultNetRudpSessionOptions(connection_id=0x0A0B0C0D, initial_sequence=1, resend_delay_ms=50)
)
server = CultNetRudpSession(
CultNetRudpSessionOptions(connection_id=0x0A0B0C0D, initial_sequence=100, resend_delay_ms=50)
)
connect = client.create_connect(0, b"join")
self.assertEqual(connect.packet_type, CultNetRudpPacketType.CONNECT)
self.assertEqual(connect.sequence, 1)
self.assertEqual(client.pending_reliable_sequences, (1,))
accept = server.accept_connect(connect, 10, b"ok")
self.assertEqual(accept.packet_type, CultNetRudpPacketType.ACCEPT)
self.assertEqual(accept.ack, 1)
self.assertTrue(server.connected)
self.assertEqual(server.pending_reliable_sequences, (100,))
client.receive(accept, 20)
self.assertTrue(client.connected)
self.assertEqual(client.pending_reliable_sequences, ())
ack = client.create_ack()
self.assertEqual(ack.ack, 100)
server.receive(ack, 30)
self.assertEqual(server.pending_reliable_sequences, ())
def test_cultnet_rudp_session_computes_ack_masks_and_clears_pending_reliable_packets(self) -> None:
sender = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=7, initial_sequence=10, resend_delay_ms=100))
receiver = CultNetRudpSession(
CultNetRudpSessionOptions(connection_id=7, initial_sequence=200, resend_delay_ms=100)
)
sender.receive(receiver.accept_connect(sender.create_connect(0), 0), 0)
first = sender.send(
"schema",
b"first",
CultNetRudpSendOptions(reliable=True, ordered=True, now_ms=0),
)
second = sender.send(
"schema",
b"second",
CultNetRudpSendOptions(reliable=True, ordered=True, now_ms=0),
)
third = sender.send(
"schema",
b"third",
CultNetRudpSendOptions(reliable=True, ordered=True, now_ms=0),
)
self.assertEqual(sender.pending_reliable_sequences, (11, 12, 13))
receiver.receive(first)
receiver.receive(third)
ack_with_gap = receiver.create_ack()
self.assertEqual(ack_with_gap.ack, 13)
self.assertEqual(ack_with_gap.ack_mask, 0b110)
sender.receive(ack_with_gap)
self.assertEqual(sender.pending_reliable_sequences, (12,))
receiver.receive(second)
full_ack = receiver.create_ack()
self.assertEqual(full_ack.ack, 13)
self.assertEqual(full_ack.ack_mask, 0b111)
sender.receive(full_ack)
self.assertEqual(sender.pending_reliable_sequences, ())
def test_cultnet_rudp_session_schedules_reliable_resends_until_acked(self) -> None:
session = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=99, initial_sequence=1, resend_delay_ms=100))
session.receive(
CultNetRudpSession(CultNetRudpSessionOptions(connection_id=99, initial_sequence=900)).accept_connect(session.create_connect(0), 0),
0,
)
sent = session.send(
"schema",
b"payload",
CultNetRudpSendOptions(reliable=True, ordered=True, now_ms=10),
)
self.assertEqual(session.due_resends(90), ())
self.assertEqual(tuple(packet.sequence for packet in session.due_resends(110)), (sent.sequence,))
self.assertEqual(session.due_resends(150), ())
session.receive(
CultNetRudpPacket(CultNetRudpPacketType.ACK, 99, 51, sent.sequence, 0, "control")
)
self.assertEqual(session.due_resends(250), ())
def test_cultnet_rudp_session_pings_and_detects_receive_timeout(self) -> None:
client = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=101, initial_sequence=1))
server = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=101, initial_sequence=100))
connect = client.create_connect(0, b"join")
accept = server.accept_connect(connect, 10)
client.receive(accept, 20)
ping = client.create_ping(b"pulse")
ping_result = server.receive(ping, 30)
self.assertIsNotNone(ping_result.reply)
self.assertEqual(ping_result.reply.packet_type, CultNetRudpPacketType.PONG)
self.assertEqual(ping_result.reply.payload, b"pulse")
pong_result = client.receive(ping_result.reply, 40)
self.assertTrue(pong_result.pong)
self.assertEqual(pong_result.pong_payload, b"pulse")
self.assertFalse(client.check_timeout(90, 50))
self.assertTrue(client.check_timeout(91, 50))
self.assertFalse(client.connected)
def test_a_repeated_connect_acknowledges_what_it_carries(self) -> None:
client = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=103, initial_sequence=1))
server = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=103, initial_sequence=100))
connect = client.create_connect(0, b"join")
accept = server.accept_connect(connect, 0)
reply = server.accept_connect(replace(connect, ack=accept.sequence), 1)
self.assertEqual(reply.packet_type, CultNetRudpPacketType.ACK)
self.assertEqual(server.outstanding_reliable_packet_count, 0)
def test_cultnet_rudp_session_bounds_pending_reliable_packets_before_enqueue(self) -> None:
session = CultNetRudpSession(
CultNetRudpSessionOptions(connection_id=102, initial_sequence=1, max_pending_reliable_packets=2)
)
session.receive(
CultNetRudpSession(CultNetRudpSessionOptions(connection_id=102, initial_sequence=900)).accept_connect(session.create_connect(0), 0),
0,
)
session.send("schema", b"first", CultNetRudpSendOptions(reliable=True, ordered=True))
session.send("schema", b"second", CultNetRudpSendOptions(reliable=True, ordered=True))
with self.assertRaisesRegex(ValueError, "reliable send queue is full"):
session.send("schema", b"third", CultNetRudpSendOptions(reliable=True, ordered=True))
self.assertEqual(session.pending_reliable_sequences, (2, 3))
fragmented = CultNetRudpSession(
CultNetRudpSessionOptions(connection_id=103, initial_sequence=1, max_pending_reliable_packets=3)
)
fragmented.receive(
CultNetRudpSession(CultNetRudpSessionOptions(connection_id=103, initial_sequence=900)).accept_connect(fragmented.create_connect(0), 0),
0,
)
with self.assertRaisesRegex(ValueError, "reliable send queue is full"):
fragmented.send_many(
"schema",
b"fragment-me",
CultNetRudpSendOptions(reliable=True, ordered=True),
max_fragment_bytes=3,
)
self.assertEqual(fragmented.pending_reliable_sequences, ())
def test_cultnet_rudp_session_suppresses_duplicates_and_delivers_reliable_ordered_payloads(self) -> None:
sender = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=123, initial_sequence=1))
receiver = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=123, initial_sequence=100))
sender.receive(receiver.accept_connect(sender.create_connect(0), 0))
first = sender.send("schema", b"first", CultNetRudpSendOptions(reliable=True, ordered=True))
second = sender.send("schema", b"second", CultNetRudpSendOptions(reliable=True, ordered=True))
third = sender.send("schema", b"third", CultNetRudpSendOptions(reliable=True, ordered=True))
self.assertEqual([frame.payload.decode("utf-8") for frame in receiver.receive(first).delivered], ["first"])
self.assertEqual(receiver.receive(third).delivered, ())
self.assertEqual(receiver.receive(first).delivered, ())
self.assertEqual(
[frame.payload.decode("utf-8") for frame in receiver.receive(second).delivered],
["second", "third"],
)
def test_cultnet_rudp_lossy_packets_cannot_create_reliable_ordered_gaps(self) -> None:
sender = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=198, initial_sequence=1))
receiver = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=198, initial_sequence=100))
sender.receive(receiver.accept_connect(sender.create_connect(0), 0), 0)
realtime = sender.send("realtime", b"discarded realtime", CultNetRudpSendOptions())
latest = sender.send("latest", b"discarded latest state", CultNetRudpSendOptions(sequenced=True))
schema = sender.send("schema", b"committed response", CultNetRudpSendOptions(reliable=True, ordered=True))
self.assertEqual(realtime.sequence, 0)
self.assertEqual(latest.sequence, 1)
self.assertEqual(
[frame.payload.decode("utf-8") for frame in receiver.receive(schema).delivered],
["committed response"],
)
def test_cultnet_rudp_unreliable_sequenced_delivery_is_scoped_to_its_channel(self) -> None:
sender = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=197, initial_sequence=50))
receiver = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=197, initial_sequence=100))
sender.receive(receiver.accept_connect(sender.create_connect(0), 0), 0)
older = sender.send("latest", b"older", CultNetRudpSendOptions(sequenced=True))
newer = sender.send("latest", b"newer", CultNetRudpSendOptions(sequenced=True))
self.assertEqual(older.sequence, 1)
self.assertEqual(newer.sequence, 2)
self.assertEqual(
[frame.payload.decode("utf-8") for frame in receiver.receive(newer).delivered],
["newer"],
)
self.assertEqual(receiver.receive(older).delivered, ())
def test_cultnet_rudp_rejects_unreliable_ordered_delivery(self) -> None:
session = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=196, initial_sequence=1))
session.receive(
CultNetRudpSession(CultNetRudpSessionOptions(connection_id=196, initial_sequence=900)).accept_connect(session.create_connect(0), 0),
0,
)
with self.assertRaises(ValueError) as raised:
session.send(
"schema",
b"cannot order what will not retransmit",
CultNetRudpSendOptions(ordered=True),
)
self.assertIn("ordered delivery requires reliability", str(raised.exception))
def test_cultnet_rudp_session_skips_control_packets_while_ordering_schema_payloads(self) -> None:
sender = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=124, initial_sequence=1))
receiver = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=124, initial_sequence=100))
sender.receive(receiver.accept_connect(sender.create_connect(0), 0), 0)
first = sender.send("schema", b"first", CultNetRudpSendOptions(reliable=True, ordered=True))
control = sender.create_ack()
second = sender.send("schema", b"second", CultNetRudpSendOptions(reliable=True, ordered=True))
self.assertEqual([frame.payload.decode("utf-8") for frame in receiver.receive(first).delivered], ["first"])
self.assertEqual(receiver.receive(control).delivered, ())
self.assertEqual([frame.payload.decode("utf-8") for frame in receiver.receive(second).delivered], ["second"])
def test_cultnet_rudp_session_fragments_and_reassembles_reliable_ordered_payloads(self) -> None:
sender = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=456, initial_sequence=1))
receiver = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=456, initial_sequence=100))
sender.receive(receiver.accept_connect(sender.create_connect(0), 0), 0)
packets = sender.send_many(
"schema",
b"fragment-me-please",
CultNetRudpSendOptions(reliable=True, ordered=True, now_ms=10),
max_fragment_bytes=5,
)
self.assertEqual(len(packets), 4)
self.assertEqual(tuple(packet.fragment_count for packet in packets), (4, 4, 4, 4))
self.assertEqual(tuple(packet.fragment_index for packet in packets), (0, 1, 2, 3))
self.assertTrue(all(packet.fragment_id == packets[0].fragment_id for packet in packets))
self.assertEqual(receiver.receive(packets[0]).delivered, ())
self.assertEqual(receiver.receive(packets[1]).delivered, ())
self.assertEqual(receiver.receive(packets[2]).delivered, ())
delivered = receiver.receive(packets[3]).delivered
self.assertEqual(len(delivered), 1)
self.assertEqual(delivered[0].payload, b"fragment-me-please")
self.assertEqual(delivered[0].sequence, packets[0].sequence)
def test_cultnet_rudp_session_advances_large_fragment_sets_through_bounded_window(self) -> None:
connection_id = 457
sender = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=connection_id, initial_sequence=1))
receiver = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=connection_id, initial_sequence=100))
sender.receive(receiver.accept_connect(sender.create_connect(0), 0), 0)
fragment_count = CultNetRudpSession.RELIABLE_SEND_WINDOW_PACKETS + 17
payload = bytes(index % 251 for index in range(fragment_count * 8))
wire = list(sender.send_many(
"schema",
payload,
CultNetRudpSendOptions(reliable=True, ordered=True, now_ms=1),
max_fragment_bytes=8,
))
self.assertEqual(len(wire), CultNetRudpSession.RELIABLE_SEND_WINDOW_PACKETS)
oldest_sequence = wire[0].sequence
self.assertEqual(len(sender.pending_reliable_sequences), len(wire))
self.assertEqual(sender.queued_reliable_packet_count, 17)
delivered = []
while wire:
admitted = len(wire)
for _ in range(admitted):
packet = wire.pop(0)
delivered.extend(receiver.receive(packet, 2).delivered)
acknowledged = sender.receive(receiver.create_ack(), 3)
wire.extend(acknowledged.ready_to_send)
self.assertEqual(sender.outstanding_reliable_packet_count, 0)
self.assertEqual(len(delivered), 1)
self.assertEqual(delivered[0].payload, payload)
old_ack = receiver.create_ack_for_received(oldest_sequence)
self.assertEqual(old_ack.ack, oldest_sequence)
self.assertEqual(old_ack.ack_mask, 0)
def test_cultnet_rudp_only_a_datagram_that_can_never_be_sent_is_a_permanent_failure(self) -> None:
import errno
from cultnet_py import is_permanent_send_error
for code in (errno.EMSGSIZE, errno.EINVAL, errno.EAFNOSUPPORT):
self.assertTrue(is_permanent_send_error(OSError(code, "permanent")), errno.errorcode[code])
for code in (
errno.EPERM,
errno.EINTR,
errno.EAGAIN,
errno.EACCES,
errno.ENETDOWN,
errno.ENETUNREACH,
errno.EADDRNOTAVAIL,
errno.ENOBUFS,
errno.ECONNREFUSED,
errno.EHOSTUNREACH,
):
self.assertFalse(is_permanent_send_error(OSError(code, "transient")), errno.errorcode[code])
self.assertFalse(is_permanent_send_error(OSError("no errno")))
def test_cultnet_rudp_an_unsendable_goodbye_names_the_failure_by_a_fixed_code(self) -> None:
import errno
from cultnet_py import send_error_code
for code, name in ((errno.EMSGSIZE, "EMSGSIZE"), (errno.EINVAL, "EINVAL"), (errno.EAFNOSUPPORT, "EAFNOSUPPORT")):
self.assertEqual(send_error_code(OSError(code, "CANARY-7f3a")), name)
self.assertEqual(send_error_code(OSError(errno.ECONNREFUSED, "CANARY-7f3a")), "UNKNOWN")
session = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=1))
goodbye = session.end_unsendable_session(OSError(errno.EMSGSIZE, "Message too long CANARY-7f3a"))
self.assertEqual(bytes(goodbye.payload), b"packet could not be sent: EMSGSIZE")
# The sender's flow window: a reliable packet goes on the wire only while its sequence is at most 1,023 above
# the lowest unacked one and the payload above that sequence stays within 4 MiB.
FLOW_CONNECTION_ID = 0x464C4F57
MIB = 1024 * 1024
def _flow_session(self, initial_sequence: int) -> CultNetRudpSession:
return CultNetRudpSession(
CultNetRudpSessionOptions(connection_id=self.FLOW_CONNECTION_ID, initial_sequence=initial_sequence)
)
def _connected_flow_pair(
self, sender_initial_sequence: int, receiver_initial_sequence: int
) -> tuple[CultNetRudpSession, CultNetRudpSession]:
"""A sender and a receiver that have shaken hands: the receiver's watermark is seeded by the sender's
Connect, and the sender's Connect is acknowledged, so nothing is pending on the sender."""
sender = self._flow_session(sender_initial_sequence)
receiver = self._flow_session(receiver_initial_sequence)
sender.receive(receiver.accept_connect(sender.create_connect(0), 0), 0)
return sender, receiver
def _connected_flow_session(self, initial_sequence: int) -> CultNetRudpSession:
return self._connected_flow_pair(initial_sequence, 900)[0]
@staticmethod
def _flow_send(session: CultNetRudpSession, payload_bytes: int) -> tuple[CultNetRudpPacket, ...]:
return session.send_many("state", bytes(payload_bytes), CultNetRudpSendOptions(reliable=True))
def _flow_ack(self, sequence: int) -> CultNetRudpPacket:
return CultNetRudpPacket(CultNetRudpPacketType.ACK, self.FLOW_CONNECTION_ID, 0, sequence, 0, "control")
def _lose_g_and_fill_the_span(self, session: CultNetRudpSession) -> CultNetRudpPacket:
g = self._flow_send(session, 1)[0]
for offset in range(1, 1024):
self.assertEqual([p.sequence for p in self._flow_send(session, 1)], [g.sequence + offset])
session.receive(self._flow_ack(g.sequence + offset), 1)
return g
def test_cultnet_rudp_a_lost_packet_holds_the_sender_1023_sequences_ahead_and_is_still_delivered(self) -> None:
sender, receiver = self._connected_flow_pair(1, 100)
g = self._flow_send(sender, 1)[0]
admitted_after_g = 0
for _ in range(4200):
for packet in self._flow_send(sender, 1):
admitted_after_g += 1
receiver.receive(packet, 1)
sender.receive(receiver.create_ack(), 1)
self.assertEqual(admitted_after_g, 1023)
self.assertEqual(sender.queued_reliable_packet_count, 4200 - 1023)
retransmit = next(p for p in sender.due_resends(1000) if p.sequence == g.sequence)
delivered = receiver.receive(retransmit, 1000).delivered
self.assertEqual([frame.sequence for frame in delivered], [g.sequence])
promoted = sender.receive(receiver.create_ack_for_received(g.sequence), 1001).ready_to_send
self.assertEqual(promoted[0].sequence, g.sequence + 1024)
def test_cultnet_rudp_a_direct_send_past_the_span_is_refused_without_consuming_a_sequence(self) -> None:
sender = self._connected_flow_session(1)
g = self._lose_g_and_fill_the_span(sender)
with self.assertRaises(ValueError):
sender.send("state", bytes(1), CultNetRudpSendOptions(reliable=True))
self.assertEqual(sender.queued_reliable_packet_count, 0)
sender.receive(self._flow_ack(g.sequence), 2)
following = sender.send("state", bytes(1), CultNetRudpSendOptions(reliable=True))
self.assertEqual(following.sequence, g.sequence + 1024)
def test_cultnet_rudp_payload_above_the_lowest_unacked_sequence_is_bounded_by_4_mib(self) -> None:
sender = self._connected_flow_session(1)
g = self._flow_send(sender, self.MIB)[0]
# The lowest unacked packet's own bytes do not count: four more MiB fit above it.
for _ in range(4):
self.assertEqual(len(self._flow_send(sender, self.MIB)), 1)
self.assertEqual(len(self._flow_send(sender, self.MIB)), 0)
self.assertEqual(len(self._flow_send(sender, self.MIB)), 0)
self.assertEqual(sender.queued_reliable_packet_count, 2)
# Acking g moves the floor up one packet: exactly one MiB more fits.
promoted = sender.receive(self._flow_ack(g.sequence), 1).ready_to_send
self.assertEqual([p.sequence for p in promoted], [g.sequence + 5])
self.assertEqual(sender.queued_reliable_packet_count, 1)
def test_cultnet_rudp_a_packet_larger_than_the_window_goes_out_alone_and_waits_behind_anything_unacked(self) -> None:
self.assertEqual(len(self._flow_send(self._connected_flow_session(1), 5 * self.MIB)), 1)
sender = self._connected_flow_session(1)
g = self._flow_send(sender, 1)[0]
self.assertEqual(len(self._flow_send(sender, 5 * self.MIB)), 0)
promoted = sender.receive(self._flow_ack(g.sequence), 1).ready_to_send
self.assertEqual([p.sequence for p in promoted], [g.sequence + 1])
def test_cultnet_rudp_a_small_packet_does_not_overtake_a_queued_large_one(self) -> None:
sender = self._connected_flow_session(1)
g = self._flow_send(sender, 1)[0]
self.assertEqual(len(self._flow_send(sender, 3 * self.MIB)), 1)
self.assertEqual(len(self._flow_send(sender, 2 * self.MIB)), 0)
# A direct send refuses instead of queueing behind it, and takes no sequence.
with self.assertRaises(ValueError):
sender.send("state", bytes(1), CultNetRudpSendOptions(reliable=True))
self.assertEqual(sender.queued_reliable_packet_count, 1)
# One byte would fit above g, but the 2 MiB packet is ahead of it.
self.assertEqual(len(self._flow_send(sender, 1)), 0)
promoted = sender.receive(self._flow_ack(g.sequence), 1).ready_to_send
self.assertEqual([p.sequence for p in promoted], [g.sequence + 2, g.sequence + 3])
def test_cultnet_rudp_acknowledged_bytes_above_a_lost_packet_still_count_against_4_mib(self) -> None:
sender = self._connected_flow_session(1)
g = self._flow_send(sender, 1)[0]
# g is lost. Each 1 MiB frame above it is delivered and acknowledged, and the receiver still holds it behind
# the gap, so it keeps counting.
for offset in range(1, 5):
self.assertEqual([p.sequence for p in self._flow_send(sender, self.MIB)], [g.sequence + offset])
sender.receive(self._flow_ack(g.sequence + offset), 1)
self.assertEqual(len(self._flow_send(sender, self.MIB)), 0)
self.assertEqual(sender.queued_reliable_packet_count, 1)
with self.assertRaises(ValueError):
sender.send("state", bytes(self.MIB), CultNetRudpSendOptions(reliable=True))
# g arrives: nothing is held behind a gap any more, and the queue drains.
promoted = sender.receive(self._flow_ack(g.sequence), 2).ready_to_send
self.assertEqual([p.sequence for p in promoted], [g.sequence + 5])
# The bytes above the old gap no longer count: another MiB fits.
self.assertEqual([p.sequence for p in self._flow_send(sender, self.MIB)], [g.sequence + 6])
def test_cultnet_rudp_acknowledged_bytes_count_against_the_lowest_unacked_sequence_as_it_advances(self) -> None:
sender = self._connected_flow_session(1)
g = self._flow_send(sender, 1)[0]
h = self._flow_send(sender, 1)[0]
for _ in range(3):
self._flow_send(sender, self.MIB)
# g and the 3 MiB above h are acknowledged; h is lost.
for offset in (2, 3, 4, 0):
sender.receive(self._flow_ack(g.sequence + offset), 1)
self.assertEqual([p.sequence for p in self._flow_send(sender, self.MIB)], [g.sequence + 5])
# 4 MiB sit above h, acknowledged or not: one more byte does not fit.
self.assertEqual(len(self._flow_send(sender, 1)), 0)
# h arrives, the floor moves to g + 5, and the byte fits.
promoted = sender.receive(self._flow_ack(h.sequence), 2).ready_to_send
self.assertEqual([p.sequence for p in promoted], [g.sequence + 6])
def test_cultnet_rudp_a_new_generation_on_the_server_does_not_inherit_acknowledged_bytes(self) -> None:
# A server that accepts a fresh Connect may send before its Accept is acknowledged, so no acknowledgement of
# the new generation has moved the floor yet. Bytes the ended generation had acknowledged above its own lost
# packet belong to no receiver's hold any more and must not count.
server = self._flow_session(500)
accept = server.accept_connect(self._flow_session(1).create_connect(0), 0)
server.receive(self._flow_ack(accept.sequence), 0)
g = self._flow_send(server, 1)[0]
for offset in range(1, 5):
self.assertEqual([p.sequence for p in self._flow_send(server, self.MIB)], [g.sequence + offset])
server.receive(self._flow_ack(g.sequence + offset), 1)
self.assertEqual(len(self._flow_send(server, self.MIB)), 0)
next_accept = server.accept_connect(self._flow_session(200_000).create_connect(2), 2)
self.assertEqual(next_accept.packet_type, CultNetRudpPacketType.ACCEPT)
# The Accept is unacknowledged and the only thing pending: 1 MiB fits above it.
sent = server.send("state", bytes(self.MIB), CultNetRudpSendOptions(reliable=True))
self.assertEqual(sent.sequence, next_accept.sequence + 1)
def test_cultnet_rudp_socket_transport_handshakes_and_carries_reliable_ordered_schema_frames(self) -> None:
server_socket = bind_udp_socket()
client_socket = bind_udp_socket()
connection_id = 0x10203040
server = CultNetRudpSocketTransportConnection(
CultNetRudpSocketTransportOptions(
runtime_id="python-rudp-server",
socket=server_socket,
mode=CultNetRudpSocketMode.SERVER,
connection_id=connection_id,
initial_sequence=100,
resend_delay_ms=25,
)
)
client = CultNetRudpSocketTransportConnection(
CultNetRudpSocketTransportOptions(
runtime_id="python-rudp-client",
socket=client_socket,
mode=CultNetRudpSocketMode.CLIENT,
remote_addr=server_socket.getsockname(),
connection_id=connection_id,
initial_sequence=1,
resend_delay_ms=25,
)
)
try:
client.connect(b"join")
pump_rudp_handshake(client, server)
self.assertTrue(client.connected)
self.assertTrue(server.connected)
client.send("schema", b"client-state")
server_frame = receive_rudp_frame(server)
self.assertEqual(server_frame.channel_id, "schema")
self.assertEqual(server_frame.payload, b"client-state")
server.send("schema", b"server-state")
client_frame = receive_rudp_frame(client)
self.assertEqual(client_frame.channel_id, "schema")
self.assertEqual(client_frame.payload, b"server-state")
self.assertEqual(server.profile["transports"][0]["protocol"], "rudp")
self.assertEqual(client.stats.frames_sent, 1)
self.assertEqual(server.stats.frames_received, 1)
finally:
client.close()
server.close()
def test_cultnet_rudp_socket_transport_drops_strays_and_keeps_serving_its_peer(self) -> None:
server_socket = bind_udp_socket()
client_socket = bind_udp_socket()
stray_socket = bind_udp_socket()
connection_id = 0x10203050
server = CultNetRudpSocketTransportConnection(
CultNetRudpSocketTransportOptions(
runtime_id="python-rudp-server",
socket=server_socket,
mode=CultNetRudpSocketMode.SERVER,
connection_id=connection_id,
)
)
client = CultNetRudpSocketTransportConnection(
CultNetRudpSocketTransportOptions(
runtime_id="python-rudp-client",
socket=client_socket,
mode=CultNetRudpSocketMode.CLIENT,
remote_addr=server_socket.getsockname(),
connection_id=connection_id,
)
)
foreign = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=0x0BADF10E))
strays = [
b"not a rudp packet",
b"",
encode_rudp_packet(foreign.create_connect(0, b"foreign")),
]
try:
# A stray that arrives before the peer must not become the peer.
for stray in strays:
stray_socket.sendto(stray, server_socket.getsockname())
for _ in strays:
self.assertIsNone(server.receive_once())
self.assertEqual(server.stats.packets_dropped, len(strays))
client.connect(b"join")
pump_rudp_handshake(client, server)
# A moved flow answers from the peer's own address with someone else's session.
for stray in strays:
server_socket.sendto(stray, client_socket.getsockname())
client_socket.sendto(stray, server_socket.getsockname())
for _ in strays:
self.assertIsNone(client.receive_once())
self.assertIsNone(server.receive_once())
self.assertEqual(client.stats.packets_dropped, len(strays))
self.assertEqual(server.stats.packets_dropped, 2 * len(strays))
client.send("schema", b"still here")
self.assertEqual(receive_rudp_frame(server).payload, b"still here")
server.send("schema", b"server still here")
self.assertEqual(receive_rudp_frame(client).payload, b"server still here")
finally:
stray_socket.close()
client.close()
server.close()
def test_cultnet_rudp_server_mode_is_claimed_only_by_a_connect(self) -> None:
server_socket = bind_udp_socket()
orphan_socket = bind_udp_socket()
client_socket = bind_udp_socket()
connection_id = 0x10203052
server = CultNetRudpSocketTransportConnection(
CultNetRudpSocketTransportOptions(
runtime_id="python-rudp-server",
socket=server_socket,
mode=CultNetRudpSocketMode.SERVER,
connection_id=connection_id,
)
)
client = CultNetRudpSocketTransportConnection(
CultNetRudpSocketTransportOptions(
runtime_id="python-rudp-client",
socket=client_socket,
mode=CultNetRudpSocketMode.CLIENT,
remote_addr=server_socket.getsockname(),
connection_id=connection_id,
)
)
orphan = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=connection_id))
try:
# A right-id packet that is not a Connect must claim nothing.
orphan_socket.sendto(encode_rudp_packet(orphan.create_ack()), server_socket.getsockname())
self.assertIsNone(server.receive_once())
self.assertEqual(server.stats.packets_dropped, 1)
self.assertIsNone(server.remote_addr)
client.connect(b"join")
pump_rudp_handshake(client, server)
self.assertTrue(client.connected)
self.assertEqual(server.remote_addr, client_socket.getsockname())
finally:
orphan_socket.close()
client.close()
server.close()
def test_cultnet_rudp_socket_transport_ends_the_session_that_refuses_a_reliable_frame(self) -> None:
server_socket = bind_udp_socket()
peer_socket = bind_udp_socket()
connection_id = 0x10203053
server = CultNetRudpSocketTransportConnection(
CultNetRudpSocketTransportOptions(
runtime_id="python-rudp-server",
socket=server_socket,
mode=CultNetRudpSocketMode.SERVER,
connection_id=connection_id,
)
)
peer = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=connection_id))
def to_server(packet) -> None:
peer_socket.sendto(encode_rudp_packet(packet), server_socket.getsockname())
def drain_peer() -> list:
packets = []
while True:
try:
wire, _ = peer_socket.recvfrom(65535)
except TimeoutError:
return packets
packets.append(decode_rudp_packet(wire))
try:
to_server(peer.create_connect(0, b"join"))
server.receive_once()
accept = [p for p in drain_peer() if p.packet_type == CultNetRudpPacketType.ACCEPT][0]
peer.receive(accept, 0)
(frame,) = peer.send_many("schema", b"poison", CultNetRudpSendOptions(reliable=True, ordered=True))
poison = replace(frame, fragment_count=2, fragment_id=0)
to_server(poison)
self.assertIsNone(server.receive_once())
self.assertEqual(server.stats.packets_dropped, 1)
self.assertFalse(server.connected)
self.assertEqual(server.disconnect_reason, b"session refused a packet")
# The peer is told, and the goodbye does not acknowledge the refused frame.
(goodbye,) = [p for p in drain_peer() if p.packet_type == CultNetRudpPacketType.DISCONNECT]
self.assertTrue(peer.receive(goodbye, 1).disconnected)
self.assertFalse(acknowledges(goodbye, poison.sequence), "the goodbye acknowledged the refused frame")
# A retransmit is dropped, not acknowledged.
to_server(poison)
self.assertIsNone(server.receive_once())
self.assertEqual(server.stats.packets_dropped, 2)
self.assertEqual(drain_peer(), [])
# A Connect claims the endpoint again.
again = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=connection_id))
to_server(again.create_connect(0, b"again"))
server.receive_once()
self.assertEqual(
[p.packet_type for p in drain_peer()], [CultNetRudpPacketType.ACCEPT]
)
finally:
peer_socket.close()
server.close()
def _server_mode_pair(self, connection_id: int):
server_socket = bind_udp_socket()
peer_socket = bind_udp_socket()
server = CultNetRudpSocketTransportConnection(
CultNetRudpSocketTransportOptions(
runtime_id="python-rudp-server",
socket=server_socket,
mode=CultNetRudpSocketMode.SERVER,
connection_id=connection_id,
)
)
peer = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=connection_id))
def to_server(packet) -> None:
peer_socket.sendto(encode_rudp_packet(packet), server_socket.getsockname())
def drain_peer() -> list:
packets = []
while True:
try:
wire, _ = peer_socket.recvfrom(65535)
except TimeoutError:
return packets
packets.append(decode_rudp_packet(wire))
return server, server_socket, peer_socket, peer, to_server, drain_peer
def test_cultnet_rudp_flush_fails_for_a_write_the_ended_session_forgot(self) -> None:
server, _, peer_socket, peer, to_server, drain_peer = self._server_mode_pair(0x10203054)
try:
to_server(peer.create_connect(0, b"join"))
server.receive_once()
peer.receive([p for p in drain_peer() if p.packet_type == CultNetRudpPacketType.ACCEPT][0], 0)
server.send("schema", b"never acknowledged")
(frame,) = peer.send_many("schema", b"poison", CultNetRudpSendOptions(reliable=True, ordered=True))
to_server(replace(frame, fragment_count=2, fragment_id=0))
self.assertIsNone(server.receive_once())
with self.assertRaises(ConnectionError):
server.flush_reliable(0.2)
finally:
peer_socket.close()
server.close()
def test_cultnet_rudp_server_mode_owes_one_reliable_accept_however_many_connects_repeat(self) -> None:
server, _, peer_socket, peer, to_server, drain_peer = self._server_mode_pair(0x10203055)
try:
connect = peer.create_connect(0, b"join")
for _ in range(20):
to_server(connect)
server.receive_once()
accepts = {p.sequence for p in drain_peer() if p.packet_type == CultNetRudpPacketType.ACCEPT}
self.assertEqual(len(accepts), 1)
self.assertLessEqual(server.outstanding_reliable_packet_count, 1)
finally:
peer_socket.close()
server.close()
def test_cultnet_rudp_server_mode_admits_a_restarted_client_and_keeps_its_session_on_a_retransmit(self) -> None:
server, _, peer_socket, _, to_server, drain_peer = self._server_mode_pair(0x10203058)
connection_id = 0x10203058
reliable = CultNetRudpSendOptions(reliable=True, ordered=True)
try:
first = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=connection_id, initial_sequence=50_000))
connect = first.create_connect(0, b"same")
to_server(connect)
server.receive_once()
first.receive([p for p in drain_peer() if p.packet_type == CultNetRudpPacketType.ACCEPT][0], 0)
sent = [first.send("schema", f"c{index}".encode(), reliable) for index in range(3)]
delivered = []
for packet in sent:
to_server(packet)
delivered.append(server.receive_once().payload)
self.assertEqual(delivered, [b"c0", b"c1", b"c2"])
# A retransmitted Connect is a repeat: what was delivered stays delivered once.
to_server(connect)
server.receive_once()
to_server(sent[0])
self.assertIsNone(server.receive_once(), "the retransmitted Connect reset the session")
# The same client restarted on the same address, connection id and payload.
drain_peer()
second = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=connection_id, initial_sequence=7))
to_server(second.create_connect(0, b"same"))
server.receive_once()
second.receive([p for p in drain_peer() if p.packet_type == CultNetRudpPacketType.ACCEPT][0], 0)
to_server(second.send("schema", b"after restart", reliable))
self.assertEqual(server.receive_once().payload, b"after restart")
finally:
peer_socket.close()
server.close()
def test_cultnet_rudp_server_mode_admits_a_pinned_client_that_restarts_on_a_new_port(self) -> None:
server, server_socket, peer_socket, _, to_server, drain_peer = self._server_mode_pair(0x1020305B)
connection_id = 0x1020305B
reliable = CultNetRudpSendOptions(reliable=True, ordered=True)
other_socket = bind_udp_socket()
try:
first = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=connection_id, initial_sequence=1))
to_server(first.create_connect(0, b"join"))
server.receive_once()
first.receive([p for p in drain_peer() if p.packet_type == CultNetRudpPacketType.ACCEPT][0], 0)
to_server(first.send("schema", b"hello", reliable))
self.assertEqual(server.receive_once().payload, b"hello")
# The process restarted on a new port with the same pinned sequence.
second = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=connection_id, initial_sequence=1))
other_socket.sendto(encode_rudp_packet(second.create_connect(0, b"join")), server_socket.getsockname())
server.receive_once()
wire, _ = other_socket.recvfrom(65535)
second.receive(decode_rudp_packet(wire), 0)
self.assertTrue(second.connected, "the restarted client on a new port was not admitted")
other_socket.sendto(encode_rudp_packet(second.send("schema", b"after", reliable)), server_socket.getsockname())
self.assertEqual(server.receive_once().payload, b"after")
finally:
other_socket.close()
peer_socket.close()
server.close()
def test_cultnet_rudp_server_mode_admits_a_pinned_client_that_restarts_on_the_same_address(self) -> None:
server, _, peer_socket, _, to_server, drain_peer = self._server_mode_pair(0x1020305C)
connection_id = 0x1020305C
try:
first = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=connection_id, initial_sequence=1))
to_server(first.create_connect(0, b"join"))
server.receive_once()
accept = [p for p in drain_peer() if p.packet_type == CultNetRudpPacketType.ACCEPT][0]
first.receive(accept, 0)
to_server(first.create_ack_for_received(accept.sequence))
server.receive_once()
drain_peer()
# The process restarted on the same address with the same pinned sequence.
restarted = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=connection_id, initial_sequence=1))
to_server(restarted.create_connect(0, b"join"))
server.receive_once()
for packet in drain_peer():
restarted.receive(packet, 1)
self.assertFalse(restarted.connected)
to_server(restarted.due_resends(3_000)[0])
server.receive_once()
for packet in drain_peer():
restarted.receive(packet, 3_001)
self.assertTrue(restarted.connected, "the restarted pinned client was never admitted")
finally:
peer_socket.close()
server.close()
def test_cultnet_rudp_server_mode_admits_a_connect_from_a_new_endpoint(self) -> None:
server, server_socket, peer_socket, peer, to_server, drain_peer = self._server_mode_pair(0x10203059)
other_socket = bind_udp_socket()
try:
to_server(peer.create_connect(0, b"a"))
server.receive_once()
drain_peer()
other = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=0x10203059, initial_sequence=9))
other_socket.sendto(encode_rudp_packet(other.create_connect(0, b"b")), server_socket.getsockname())
server.receive_once()
wire, _ = other_socket.recvfrom(65535)
other.receive(decode_rudp_packet(wire), 0)
self.assertTrue(other.connected, "the new endpoint was not admitted")
other_socket.sendto(
encode_rudp_packet(other.send("schema", b"from b", CultNetRudpSendOptions(reliable=True, ordered=True))),
server_socket.getsockname(),
)
self.assertEqual(server.receive_once().payload, b"from b")
finally:
other_socket.close()
peer_socket.close()
server.close()
def test_cultnet_rudp_flush_after_a_timeout_names_the_timeout(self) -> None:
server, _, peer_socket, peer, to_server, drain_peer = self._server_mode_pair(0x1020305A)
try:
to_server(peer.create_connect(0, b"join"))
server.receive_once()
server.send("schema", b"unanswered")
self.assertTrue(server.check_timeout(0, now_ms=10**14))
with self.assertRaisesRegex(ConnectionError, "session timed out"):
server.flush_reliable(0.1)
finally:
peer_socket.close()
server.close()
def test_cultnet_rudp_session_rejects_limits_that_cannot_admit_a_connect(self) -> None:
with self.assertRaises(ValueError):
CultNetRudpSession(CultNetRudpSessionOptions(connection_id=1, initial_sequence=0xFFFFFFFF))
with self.assertRaises(ValueError):
CultNetRudpSession(CultNetRudpSessionOptions(connection_id=1, max_pending_reliable_packets=0))
CultNetRudpSession(
CultNetRudpSessionOptions(connection_id=1, initial_sequence=0xFFFFFFFE, max_pending_reliable_packets=1)
)
def test_cultnet_rudp_socket_transport_carries_fragmented_reliable_ordered_schema_frames(self) -> None:
server_socket = bind_udp_socket()
client_socket = bind_udp_socket()
connection_id = 0x10203041
server = CultNetRudpSocketTransportConnection(
CultNetRudpSocketTransportOptions(
runtime_id="python-rudp-fragment-server",
socket=server_socket,
mode=CultNetRudpSocketMode.SERVER,
connection_id=connection_id,
initial_sequence=100,
resend_delay_ms=25,
max_fragment_bytes=8,
)
)
client = CultNetRudpSocketTransportConnection(
CultNetRudpSocketTransportOptions(
runtime_id="python-rudp-fragment-client",
socket=client_socket,
mode=CultNetRudpSocketMode.CLIENT,
remote_addr=server_socket.getsockname(),
connection_id=connection_id,
initial_sequence=1,
resend_delay_ms=25,
max_fragment_bytes=8,
)
)
try:
payload = b"this-schema-frame-is-larger-than-one-rudp-fragment"
client.connect(b"join")
pump_rudp_handshake(client, server)
client.send("schema", payload)
server_frame = receive_rudp_frame(server)
self.assertEqual(server_frame.channel_id, "schema")
self.assertEqual(server_frame.payload, payload)
self.assertEqual(client.stats.frames_sent, 1)
self.assertEqual(server.stats.frames_received, 1)
finally:
client.close()
server.close()
def test_cultnet_database_subscription_helpers_match_schema_v0_shape(self) -> None:
subscribe = database_subscribe(
message_id="sub-message",
subscription_id="sub-1",
schema_ids=["schema-a"],
record_keys=["record-a"],
include_snapshot=False,
).to_wire()
self.assertEqual(subscribe["schemaVersion"], "cultnet.database_subscribe.v0")
self.assertEqual(subscribe["messageId"], "sub-message")
self.assertEqual(subscribe["subscriptionId"], "sub-1")
self.assertEqual(subscribe["schemaIds"], ["schema-a"])
self.assertEqual(subscribe["recordKeys"], ["record-a"])
self.assertFalse(subscribe["includeSnapshot"])
unsubscribe = database_unsubscribe(message_id="unsub-message", subscription_id="sub-1").to_wire()
self.assertEqual(unsubscribe["schemaVersion"], "cultnet.database_unsubscribe.v0")
self.assertEqual(unsubscribe["messageId"], "unsub-message")
self.assertEqual(unsubscribe["subscriptionId"], "sub-1")
def test_cultnet_database_change_parses_put_and_delete_shapes(self) -> None:
put_change = CultNetDatabaseChange.from_wire({
"schemaVersion": "cultnet.database_change_raw.v0",
"messageId": "change-put",
"subscriptionId": "sub-1",
"changeKind": "added",
"document": {
"schemaId": "schema-note",
"recordKey": "note:1",
"payload": b"payload",
},
})
self.assertEqual(put_change.schema_id, "schema-note")
self.assertEqual(put_change.record_key, "note:1")
self.assertIsInstance(put_change.raw_document, CultNetRawDocumentRecord)
self.assertEqual(put_change.raw_document.payload, b"payload")
self.assertEqual(put_change.to_wire()["document"]["recordKey"], "note:1")
delete_change = CultNetDatabaseChange.from_wire({
"schemaVersion": "cultnet.database_change_raw.v0",
"messageId": "change-delete",
"subscriptionId": "sub-1",
"changeKind": "removed",
"schemaId": "schema-note",
"recordKey": "note:1",
})
self.assertIsNone(delete_change.document)
self.assertIsNone(delete_change.raw_document)
self.assertEqual(delete_change.schema_id, "schema-note")
self.assertEqual(delete_change.to_wire()["recordKey"], "note:1")
def test_cultnet_raw_put_helper_carries_message_id(self) -> None:
put = document_put_raw(
message_id="put-1",
key="record-a",
schema_id="schema-a",
stored_at="2026-06-13T00:00:00Z",
payload=b"payload",
source_runtime_id="python-test",
shard_id="interop",
shard_epoch=1,
).to_wire()
self.assertEqual(put["schemaVersion"], "cultnet.document_put_raw.v0")
self.assertEqual(put["messageId"], "put-1")
self.assertEqual(put["document"]["recordKey"], "record-a")
self.assertEqual(put["document"]["sourceRuntimeId"], "python-test")
self.assertEqual(put["shardId"], "interop")
self.assertEqual(put["shardEpoch"], 1)
def test_cultnet_catalog_and_snapshot_helpers_accept_filters(self) -> None:
catalog = schema_catalog_request(
message_id="catalog-1",
include_schema_json=True,
schema_ids=["schema-a"],
kinds=["wire_message"],
).to_wire()
self.assertEqual(catalog["schemaVersion"], "cultnet.schema_catalog_request.v0")
self.assertEqual(catalog["messageId"], "catalog-1")
self.assertTrue(catalog["includeSchemaJson"])
self.assertEqual(catalog["schemaIds"], ["schema-a"])
self.assertEqual(catalog["kinds"], ["wire_message"])
snapshot = snapshot_request(
message_id="snapshot-1",
schema_ids=["schema-a"],
record_keys=["record-a"],
shard_id="interop",
shard_epoch=1,
).to_wire()
self.assertEqual(snapshot["schemaVersion"], "cultnet.snapshot_request.v0")
self.assertEqual(snapshot["messageId"], "snapshot-1")
self.assertEqual(snapshot["schemaIds"], ["schema-a"])
self.assertEqual(snapshot["recordKeys"], ["record-a"])
self.assertEqual(snapshot["shardId"], "interop")
self.assertEqual(snapshot["shardEpoch"], 1)
def test_cultnet_raw_snapshot_response_filters_and_roundtrips_records(self) -> None:
response = CultNetRawSnapshotResponse.from_wire({
"schemaVersion": "cultnet.snapshot_response_raw.v0",
"messageId": "snapshot-1",
"shardId": "notes",
"shardEpoch": 3,
"shardLogSequence": 8,
"documents": [
CultNetRawDocumentRecord(
schema_id="schema-note",
record_key="note:1",
stored_at="2026-06-14T00:00:00Z",
payload=b"note",
source_runtime_id="python-test",
tags=("snapshot",),
).to_wire(),
{
"schemaId": "schema-fact",
"recordKey": "fact:1",
"payloadEncoding": "messagepack",
"payload": b"fact",
},
],
})
self.assertEqual(response.shard_id, "notes")
self.assertEqual(response.shard_epoch, 3)
self.assertEqual(response.shard_log_sequence, 8)
self.assertEqual(response.documents[0].source_runtime_id, "python-test")
self.assertEqual(response.filter(schema_ids=["schema-note"])[0].record_key, "note:1")
self.assertEqual(response.filter(record_keys=["fact:1"])[0].schema_id, "schema-fact")
self.assertEqual(response.to_wire()["documents"][0]["tags"], ["snapshot"])
def test_cultnet_raw_client_fetches_schema_snapshot_and_shard_reads(self) -> None:
import msgpack # type: ignore
from cultnet_py import read_frame, write_frame
received_versions: list[str] = []
ready = threading.Event()
server_error: list[BaseException] = []
port_holder: list[int] = []
def serve_requests() -> None:
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(("127.0.0.1", 0))
port_holder.append(server.getsockname()[1])
server.listen(4)
ready.set()
for _ in range(4):
connection, _ = server.accept()
with connection:
stream = connection.makefile("rwb")
request = msgpack.unpackb(read_frame(stream), raw=False)
received_versions.append(request["schemaVersion"])
if request["schemaVersion"] == "cultnet.schema_catalog_request.v0":
self.assertEqual(request["kinds"], ["wire_message"])
response = {"schemaVersion": "cultnet.schema_catalog_response.v0", "messageId": request["messageId"], "schemas": []}
elif request["schemaVersion"] == "cultnet.snapshot_request.v0":
self.assertEqual(request["shardId"], "interop")
response = {"schemaVersion": "cultnet.snapshot_response_raw.v0", "messageId": request["messageId"], "documents": []}
elif request["schemaVersion"] == "cultnet.shard_catalog_request.v0":
response = {
"schemaVersion": "cultnet.shard_catalog_response.v0",
"messageId": request["messageId"],
"shards": [{
"shardId": "interop",
"ownerRuntimeId": "socket-test",
"epoch": 1,
"schemaIds": request["schemaIds"],
"keyPrefix": "record-",
"primaryEndpoints": ["cultnet://127.0.0.1:1"],
}],
}
elif request["schemaVersion"] == "cultnet.shard_log_request.v0":
self.assertEqual(request["afterSequence"], 7)
response = {
"schemaVersion": "cultnet.shard_log_response.v0",
"messageId": request["messageId"],
"shardId": request["shardId"],
"shardEpoch": request["shardEpoch"],
"entries": [],
"resyncRequired": False,
}
else:
raise AssertionError(f"unexpected request {request['schemaVersion']}")
write_frame(stream, msgpack.packb(response, use_bin_type=True))
stream.flush()
except BaseException as error:
server_error.append(error)
ready.set()
thread = threading.Thread(target=serve_requests, daemon=True)
thread.start()
self.assertTrue(ready.wait(2.0))
self.assertFalse(server_error)
client = CultNetRawClient("127.0.0.1", port_holder[0], timeout_seconds=2.0)
self.assertEqual(client.fetch_schema_catalog(kinds=["wire_message"])["schemaVersion"], "cultnet.schema_catalog_response.v0")
typed_snapshot = client.fetch_snapshot_response(shard_id="interop", shard_epoch=1)
self.assertEqual(typed_snapshot.shard_id, None)
self.assertEqual(typed_snapshot.documents, ())
shard_catalog = CultNetShardCatalog()
synced_shards = client.sync_shard_catalog(shard_catalog, schema_ids=["schema-a"])
self.assertEqual(synced_shards[0].shard_id, "interop")
self.assertEqual(shard_catalog.get("interop"), synced_shards[0])
shard_log = client.fetch_shard_log_response(shard_id="interop", shard_epoch=1, after_sequence=7)
self.assertEqual(shard_log.shard_id, "interop")
self.assertFalse(shard_log.resync_required)
self.assertEqual(shard_log.last_sequence, 0)
thread.join(2.0)
self.assertFalse(server_error)
self.assertEqual(received_versions, [
"cultnet.schema_catalog_request.v0",
"cultnet.snapshot_request.v0",
"cultnet.shard_catalog_request.v0",
"cultnet.shard_log_request.v0",
])
def test_cultnet_raw_client_raises_typed_peer_error_response(self) -> None:
import msgpack # type: ignore
from cultnet_py import read_frame, write_frame
ready = threading.Event()
port_holder: list[int] = []
def serve_error() -> None:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(("127.0.0.1", 0))
port_holder.append(server.getsockname()[1])
server.listen(1)
ready.set()
connection, _ = server.accept()
with connection:
stream = connection.makefile("rwb")
read_frame(stream)
write_frame(stream, msgpack.packb({
"schemaVersion": "cultnet.error.v0",
"messageId": "error-1",
"error": "Snapshot document limit exceeded",
}, use_bin_type=True))
stream.flush()
thread = threading.Thread(target=serve_error, daemon=True)
thread.start()
self.assertTrue(ready.wait(2.0))
client = CultNetRawClient("127.0.0.1", port_holder[0], timeout_seconds=2.0)
with self.assertRaisesRegex(CultNetPeerError, "Snapshot document limit exceeded") as raised:
client.fetch_snapshot()
self.assertEqual(raised.exception.peer_error, "Snapshot document limit exceeded")
self.assertEqual(raised.exception.response["schemaVersion"], "cultnet.error.v0")
thread.join(2.0)
def test_cultnet_database_subscription_reads_snapshot_and_change(self) -> None:
import msgpack # type: ignore
from cultnet_py import read_frame, write_frame
document = define_database_entry_type(
"sub.item",
[
("name", 0),
("category", 1),
("value", 2, 0),
],
cls=Item,
)
schema_id = document.catalog_entry().schema_id
ready = threading.Event()
server_error: list[BaseException] = []
port_holder: list[int] = []
received_versions: list[str] = []
def serve_subscription() -> None:
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(("127.0.0.1", 0))
port_holder.append(server.getsockname()[1])
server.listen(1)
ready.set()
connection, _ = server.accept()
with connection:
stream = connection.makefile("rwb")
subscribe = msgpack.unpackb(read_frame(stream), raw=False)
received_versions.append(subscribe["schemaVersion"])
self.assertEqual(subscribe["subscriptionId"], "sub-1")
snapshot = {
"schemaVersion": "cultnet.snapshot_response_raw.v0",
"messageId": subscribe["messageId"],
"documents": [],
}
write_frame(stream, msgpack.packb(snapshot, use_bin_type=True))
stream.flush()
put = msgpack.unpackb(read_frame(stream), raw=False)
received_versions.append(put["schemaVersion"])
change = {
"schemaVersion": "cultnet.database_change_raw.v0",
"messageId": "change-1",
"subscriptionId": "sub-1",
"changeKind": "added",
"document": put["document"],
}
write_frame(stream, msgpack.packb(change, use_bin_type=True))
stream.flush()
unsubscribe = msgpack.unpackb(read_frame(stream), raw=False)
received_versions.append(unsubscribe["schemaVersion"])
except BaseException as error:
server_error.append(error)
ready.set()
thread = threading.Thread(target=serve_subscription, daemon=True)
thread.start()
self.assertTrue(ready.wait(2.0))
self.assertFalse(server_error)
client = CultNetRawClient("127.0.0.1", port_holder[0], timeout_seconds=2.0)
put = document_put_raw(
message_id="put-sub",
key="item:sub",
schema_id=schema_id,
stored_at="2026-06-14T00:00:00Z",
payload=document.encode_payload(Item("orb", "gear", 8)),
)
with client.subscribe_database(subscription_id="sub-1", schema_ids=[schema_id]) as subscription:
snapshot = subscription.read_next_snapshot_response()
subscription.send(put)
change = subscription.read_next_change()
thread.join(2.0)
self.assertFalse(server_error)
self.assertIsInstance(snapshot, CultNetRawSnapshotResponse)
self.assertEqual(snapshot.message_id, "cultnet-python-subscribe")
self.assertEqual(snapshot.to_wire()["schemaVersion"], "cultnet.snapshot_response_raw.v0")
self.assertEqual(change.change_kind, "added")
self.assertEqual(change.record_key, "item:sub")
self.assertIsInstance(change.raw_document, CultNetRawDocumentRecord)
self.assertEqual(change.raw_document.record_key, "item:sub")
self.assertEqual(received_versions, [
"cultnet.database_subscribe.v0",
"cultnet.document_put_raw.v0",
"cultnet.database_unsubscribe.v0",
])
def test_cultnet_database_subscription_raises_typed_peer_error(self) -> None:
import msgpack # type: ignore
from cultnet_py import read_frame, write_frame
ready = threading.Event()
port_holder: list[int] = []
def serve_subscription_error() -> None:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(("127.0.0.1", 0))
port_holder.append(server.getsockname()[1])
server.listen(1)
ready.set()
connection, _ = server.accept()
with connection:
stream = connection.makefile("rwb")
read_frame(stream)
write_frame(stream, msgpack.packb({
"schemaVersion": "cultnet.error.v0",
"messageId": "subscription-error",
"error": "subscription rejected",
}, use_bin_type=True))
stream.flush()
thread = threading.Thread(target=serve_subscription_error, daemon=True)
thread.start()
self.assertTrue(ready.wait(2.0))
client = CultNetRawClient("127.0.0.1", port_holder[0], timeout_seconds=2.0)
with client.subscribe_database(subscription_id="sub-error") as subscription:
with self.assertRaisesRegex(CultNetPeerError, "subscription rejected") as raised:
subscription.read_next()
self.assertEqual(raised.exception.response["messageId"], "subscription-error")
thread.join(2.0)
def test_cultnet_replication_helpers_apply_raw_snapshot_and_shard_log(self) -> None:
document = define_database_entry_type(
"replica.item",
[
("name", 0),
("category", 1),
("value", 2, 0),
],
cls=Item,
)
cache = CultCache()
cache.register_document_type(document)
schema_id = document.catalog_entry().schema_id
snapshot = {
"schemaVersion": "cultnet.snapshot_response_raw.v0",
"messageId": "snapshot-1",
"documents": [
{
"schemaId": schema_id,
"recordKey": "item:1",
"storedAt": "2026-06-13T00:00:00Z",
"payloadEncoding": "messagepack",
"payload": document.encode_payload(Item("sword", "gear", 3)),
}
],
}
applied_snapshot = apply_raw_snapshot(cache, [document], snapshot)
self.assertEqual(applied_snapshot[0].record_key, "item:1")
self.assertEqual(cache.get_required(document, "item:1").value, 3)
typed_snapshot = CultNetRawSnapshotResponse.from_wire(snapshot)
direct_applied = apply_raw_document_record(cache, schema_document_map([document]), typed_snapshot.documents[0])
self.assertEqual(direct_applied.schema_id, schema_id)
self.assertEqual(direct_applied.record_key, "item:1")
typed_snapshot_applied = apply_raw_snapshot(cache, [document], typed_snapshot)
self.assertEqual(typed_snapshot_applied[0].record_key, "item:1")
shard_log = {
"schemaVersion": "cultnet.shard_log_response.v0",
"messageId": "log-1",
"shardId": "interop",
"shardEpoch": 1,
"resyncRequired": False,
"entries": [
{
"sequence": 0,
"changeKind": "updated",
"put": {
"schemaVersion": "cultnet.document_put_raw.v0",
"messageId": "put-unknown",
"document": {
"schemaId": "replica.unknown.v1",
"recordKey": "unknown:1",
"storedAt": "2026-06-13T00:00:01Z",
"payloadEncoding": "messagepack",
"payload": document.encode_payload(Item("ignored", "unknown", 0)),
},
"shardId": "interop",
"shardEpoch": 1,
},
},
{
"sequence": 1,
"changeKind": "updated",
"put": {
"schemaVersion": "cultnet.document_put_raw.v0",
"messageId": "put-1",
"document": {
"schemaId": schema_id,
"recordKey": "item:1",
"storedAt": "2026-06-13T00:00:01Z",
"payloadEncoding": "messagepack",
"payload": document.encode_payload(Item("sword", "gear", 12)),
},
"shardId": "interop",
"shardEpoch": 1,
},
},
{
"sequence": 2,
"changeKind": "removed",
"delete": {
"schemaVersion": "cultnet.document_delete.v0",
"messageId": "delete-1",
"schemaId": schema_id,
"recordKey": "item:1",
"shardId": "interop",
"shardEpoch": 1,
},
},
],
}
applied_log = apply_shard_log_response(cache, [document], shard_log)
self.assertEqual([change.change_kind for change in applied_log], ["updated", "removed"])
self.assertIsNone(cache.get(document, "item:1"))
cache.put(document, "item:1", Item("sword", "gear", 3))
typed_shard_log = CultNetShardLogResponse.from_wire({
**shard_log,
"entries": shard_log["entries"][1:],
})
typed_applied_log = apply_shard_log_response(cache, [document], typed_shard_log)
self.assertEqual([change.change_kind for change in typed_applied_log], ["updated", "removed"])
self.assertIsNone(cache.get(document, "item:1"))
def test_cultnet_replication_helpers_recover_schema_stamped_raw_records(self) -> None:
document = define_database_entry_type(
"replica.runtime-policy",
[
("schema_version", 0),
("name", 1),
("value", 2),
],
schema_id="replica.runtime-policy.current",
schema_name="replica.runtime_policy",
schema_version="replica.runtime_policy.v1",
)
cache = CultCache()
cache.register_document_type(document)
stale_schema_id = "sha256:stale-replica-runtime-policy"
payload = document.encode_payload({
"schema_version": "replica.runtime_policy.v1",
"name": "policy",
"value": "still synced",
})
record = {
"schemaId": stale_schema_id,
"recordKey": "policy:1",
"storedAt": "2026-06-25T00:00:00Z",
"payloadEncoding": "messagepack",
"payload": payload,
}
snapshot = {
"schemaVersion": "cultnet.snapshot_response_raw.v0",
"messageId": "snapshot-stale",
"documents": [record],
}
applied_snapshot = apply_raw_snapshot(cache, [document], snapshot)
local_schema_id = document.catalog_entry().schema_id
self.assertEqual(applied_snapshot[0].schema_id, local_schema_id)
self.assertEqual(cache.get_required(document, "policy:1")["value"], "still synced")
self.assertEqual(cache.get_required_envelope(document, "policy:1").schema_id, local_schema_id)
direct_applied = apply_raw_document_record(cache, schema_document_map([document]), record)
self.assertEqual(direct_applied.schema_id, local_schema_id)
self.assertEqual(cache.get_required_envelope(document, "policy:1").schema_id, local_schema_id)
shard_log = {
"schemaVersion": "cultnet.shard_log_response.v0",
"messageId": "log-stale",
"shardId": "interop",
"shardEpoch": 1,
"resyncRequired": False,
"entries": [
{
"sequence": 0,
"changeKind": "updated",
"put": {
"schemaVersion": "cultnet.document_put_raw.v0",
"messageId": "put-stale",
"document": {
**record,
"payload": document.encode_payload({
"schema_version": "replica.runtime_policy.v1",
"name": "policy",
"value": "updated through log",
}),
},
"shardId": "interop",
"shardEpoch": 1,
},
}
],
}
applied_log = apply_shard_log_response(cache, [document], shard_log)
self.assertEqual(applied_log[0].schema_id, local_schema_id)
self.assertEqual(cache.get_required(document, "policy:1")["value"], "updated through log")
self.assertEqual(cache.get_required_envelope(document, "policy:1").schema_id, local_schema_id)
delete_log = {
"schemaVersion": "cultnet.shard_log_response.v0",
"messageId": "log-delete-alias",
"shardId": "interop",
"shardEpoch": 1,
"resyncRequired": False,
"entries": [
{
"sequence": 1,
"changeKind": "removed",
"delete": {
"schemaVersion": "cultnet.document_delete.v0",
"messageId": "delete-version-alias",
"schemaId": "replica.runtime_policy.v1",
"recordKey": "policy:1",
"shardId": "interop",
"shardEpoch": 1,
},
}
],
}
applied_delete = apply_shard_log_response(cache, [document], delete_log)
self.assertEqual(applied_delete[0].schema_id, local_schema_id)
self.assertEqual(applied_delete[0].change_kind, "removed")
self.assertIsNone(cache.get(document, "policy:1"))
# A compatible schema id applies through the real replication function, the record is stamped with the registered id, and
# the store it leaves reopens and takes the next write.
def test_cultnet_raw_replication_under_a_compatible_schema_id_leaves_a_store_that_reopens(self) -> None:
import tempfile
from pathlib import Path
from cultcache_py import SingleFileMessagePackBackingStore
document = define_database_entry_type(
"replica.compatible-policy",
[("schema_version", 0), ("name", 1)],
schema_id="replica.compatible-policy.current",
schema_name="replica.compatible_policy",
schema_version="replica.compatible_policy.v1",
compatible_schema_ids=["replica.compatible-policy.current", "replica.compatible-policy.older"],
)
value = {"schema_version": "replica.compatible_policy.v1", "name": "synced"}
record = {
"schemaId": "replica.compatible-policy.older",
"recordKey": "policy:1",
"storedAt": "2026-06-25T00:00:00Z",
"payloadEncoding": "messagepack",
"payload": document.encode_payload(value),
}
with tempfile.TemporaryDirectory() as tmp:
path = Path(tmp) / "store.msgpack"
def open_cache() -> CultCache:
cache = CultCache.builder().register_document_type(document).add_generic_store(
SingleFileMessagePackBackingStore(path)
).build()
cache.pull_all_backing_stores()
return cache
cache = open_cache()
applied = apply_raw_document_record(cache, schema_document_map([document]), record)
self.assertEqual(applied.schema_id, document.catalog_entry().schema_id)
self.assertEqual(
cache.get_required_envelope(document, "policy:1").schema_id, document.catalog_entry().schema_id
)
reopened = open_cache()
self.assertEqual(reopened.get_required(document, "policy:1")["name"], "synced")
reopened.put(document, "policy:2", {**value, "name": "next"})
self.assertEqual(open_cache().get_required(document, "policy:2")["name"], "next")
def test_cultnet_document_delete_helper_matches_schema_v0_shape(self) -> None:
message = document_delete(
message_id="delete-1",
schema_id="schema-a",
record_key="record-a",
shard_id="interop",
shard_epoch=1,
).to_wire()
self.assertEqual(message["schemaVersion"], "cultnet.document_delete.v0")
self.assertEqual(message["messageId"], "delete-1")
self.assertEqual(message["schemaId"], "schema-a")
self.assertEqual(message["recordKey"], "record-a")
self.assertEqual(message["shardId"], "interop")
self.assertEqual(message["shardEpoch"], 1)
def test_cultnet_auth_session_helpers_match_schema_v0_shape(self) -> None:
self.assertEqual(
login(nonce="nonce", auth="encrypted-auth", password="encrypted-password").to_wire(),
{
"schemaVersion": "cultnet.login.v0",
"nonce": "nonce",
"auth": "encrypted-auth",
"password": "encrypted-password",
},
)
self.assertEqual(
register(
nonce="nonce",
email="encrypted-email",
password="encrypted-password",
name="encrypted-name",
).to_wire(),
{
"schemaVersion": "cultnet.register.v0",
"nonce": "nonce",
"email": "encrypted-email",
"password": "encrypted-password",
"name": "encrypted-name",
},
)
self.assertEqual(
verify_session(nonce="nonce", session="encrypted-session").to_wire(),
{
"schemaVersion": "cultnet.verify.v0",
"nonce": "nonce",
"session": "encrypted-session",
},
)
self.assertEqual(
login_success(nonce="nonce", session="encrypted-session").to_wire(),
{
"schemaVersion": "cultnet.login_success.v0",
"nonce": "nonce",
"session": "encrypted-session",
},
)
def test_cultnet_wire_catalog_describes_python_handled_messages(self) -> None:
descriptors = wire_message_schema_descriptors(include_schema_json=True)
by_version = {descriptor["schemaVersion"]: descriptor for descriptor in descriptors}
for schema_version in [
"cultnet.error.v0",
"cultnet.login.v0",
"cultnet.register.v0",
"cultnet.verify.v0",
"cultnet.login_success.v0",
"cultnet.transport_profile.v0",
"cultnet.document_delete.v0",
"cultnet.database_change_raw.v0",
"cultnet.shard_log_response.v0",
"cultnet.simulation_consensus_candidate.v0",
"cultmesh.peer_exchange_response.v0",
]:
self.assertIn(schema_version, by_version)
expected_kind = "shared_contract" if schema_version == "cultnet.transport_profile.v0" else "wire_message"
self.assertEqual(by_version[schema_version]["kind"], expected_kind)
self.assertIn("cultnet.schema.v0", by_version[schema_version]["wireContracts"])
self.assertIn(schema_version, by_version[schema_version]["schemaJson"])
self.assertEqual(len(by_version[schema_version]["contentHash"]), 64)
delete_schema = json.loads(by_version["cultnet.document_delete.v0"]["schemaJson"])
self.assertEqual(
delete_schema["required"],
["schemaVersion", "messageId", "schemaId", "recordKey"],
)
self.assertEqual(delete_schema["properties"]["recordKey"]["type"], "string")
error_schema = json.loads(by_version["cultnet.error.v0"]["schemaJson"])
self.assertEqual(error_schema["required"], ["schemaVersion", "error"])
self.assertEqual(error_schema["properties"]["error"]["type"], "string")
self.assertIn("details", error_schema["properties"])
login_schema = json.loads(by_version["cultnet.login.v0"]["schemaJson"])
self.assertEqual(login_schema["required"], ["schemaVersion", "nonce", "auth", "password"])
self.assertEqual(login_schema["properties"]["auth"]["type"], "string")
register_schema = json.loads(by_version["cultnet.register.v0"]["schemaJson"])
self.assertEqual(register_schema["required"], ["schemaVersion", "nonce", "email", "password", "name"])
self.assertEqual(register_schema["properties"]["email"]["type"], "string")
verify_schema = json.loads(by_version["cultnet.verify.v0"]["schemaJson"])
self.assertEqual(verify_schema["required"], ["schemaVersion", "nonce", "session"])
self.assertEqual(verify_schema["properties"]["session"]["type"], "string")
success_schema = json.loads(by_version["cultnet.login_success.v0"]["schemaJson"])
self.assertEqual(success_schema["required"], ["schemaVersion", "nonce", "session"])
transport_profile_schema = json.loads(by_version["cultnet.transport_profile.v0"]["schemaJson"])
self.assertEqual(transport_profile_schema["required"], ["schemaVersion", "runtimeId", "transports"])
transport = transport_profile_schema["properties"]["transports"]["items"]
self.assertEqual(transport["properties"]["protocol"]["enum"], ["tcp_framed", "litenetlib", "websocket", "rudp"])
self.assertEqual(
transport["properties"]["reconnectPolicy"]["properties"]["schemaVersion"]["const"],
"cultnet.reconnect_policy.v0",
)
channel = transport["properties"]["channels"]["items"]
self.assertEqual(channel["properties"]["ordering"]["enum"], ["ordered", "unordered", "sequenced"])
change_schema = json.loads(by_version["cultnet.database_change_raw.v0"]["schemaJson"])
self.assertEqual(change_schema["properties"]["changeKind"]["enum"], ["added", "updated", "removed"])
self.assertIn("document", change_schema["properties"])
self.assertIn("schemaId", change_schema["properties"])
self.assertIn("recordKey", change_schema["properties"])
log_schema = json.loads(by_version["cultnet.shard_log_response.v0"]["schemaJson"])
self.assertIn("entries", log_schema["required"])
self.assertIn("resyncRequired", log_schema["required"])
self.assertIn("compactedThrough", log_schema["properties"])
hello_schema = json.loads(by_version["cultnet.hello.v0"]["schemaJson"])
mutation_contract = hello_schema["properties"]["supportedMutationContracts"]["items"]
self.assertEqual(mutation_contract["type"], "object")
self.assertIn("operations", mutation_contract["properties"])
self.assertEqual(
hello_schema["properties"]["transportProfiles"]["items"]["$ref"],
"https://github.com/GameCult/CultLib/contracts/cultnet/cultnet.transport-profile.schema.json",
)
consensus_schema = json.loads(by_version["cultnet.simulation_consensus_candidate.v0"]["schemaJson"])
for required_field in ["witnessCount", "supportWeight", "totalWeight", "hasQuorum", "confidence"]:
self.assertIn(required_field, consensus_schema["required"])
def test_cultnet_schema_catalog_applies_filters_and_responses(self) -> None:
catalog = wire_message_schema_catalog(include_schema_json=True)
descriptor = catalog.get("https://github.com/GameCult/CultLib/contracts/cultnet/cultnet.document-put-raw.schema.json")
self.assertIsNotNone(descriptor)
assert descriptor is not None
self.assertEqual(descriptor.kind, "wire_message")
self.assertEqual(descriptor.schema_version, "cultnet.document_put_raw.v0")
self.assertIsNotNone(descriptor.schema_json)
filtered = catalog.list(kinds=["wire_message"], schema_ids=[descriptor.schema_id])
self.assertEqual(filtered, [descriptor])
response = catalog.create_response(
message_id="catalog-response",
include_schema_json=False,
schema_ids=[descriptor.schema_id],
kinds=["wire_message"],
)
self.assertEqual(response["schemaVersion"], "cultnet.schema_catalog_response.v0")
self.assertNotIn("schemaJson", response["schemas"][0])
remote = CultNetSchemaCatalog()
applied = remote.apply_response({
"schemaVersion": "cultnet.schema_catalog_response.v0",
"messageId": "remote",
"schemas": [
CultNetSchemaDescriptor(
schema_id="schema:custom",
kind="shared_contract",
schema_version="custom.v0",
wire_contracts=("cultnet.schema.v0",),
content_hash="hash",
schema_json="{}",
).to_wire()
],
})
self.assertEqual(applied[0].schema_id, "schema:custom")
self.assertEqual(remote.get("schema:custom"), applied[0])
def test_cultnet_shard_helpers_match_schema_v0_shape(self) -> None:
catalog = shard_catalog_request(
message_id="catalog-1",
schema_ids=["schema-a"],
record_keys=["record-a"],
).to_wire()
self.assertEqual(catalog["schemaVersion"], "cultnet.shard_catalog_request.v0")
self.assertEqual(catalog["messageId"], "catalog-1")
self.assertEqual(catalog["schemaIds"], ["schema-a"])
self.assertEqual(catalog["recordKeys"], ["record-a"])
log = shard_log_request(
message_id="log-1",
shard_id="interop",
shard_epoch=7,
after_sequence=3,
limit=2,
).to_wire()
self.assertEqual(log["schemaVersion"], "cultnet.shard_log_request.v0")
self.assertEqual(log["messageId"], "log-1")
self.assertEqual(log["shardId"], "interop")
self.assertEqual(log["shardEpoch"], 7)
self.assertEqual(log["afterSequence"], 3)
self.assertEqual(log["limit"], 2)
def test_cultnet_shard_catalog_applies_filters_and_responses(self) -> None:
catalog = CultNetShardCatalog()
primary = catalog.upsert(CultNetShardDescriptor(
shard_id="notes",
owner_runtime_id="python-runtime",
epoch=2,
is_primary=True,
schema_ids=("schema-note.v1",),
key_prefix="note:",
primary_endpoints=("cultnet://127.0.0.1:3075",),
read_replica_endpoints=("cultnet://127.0.0.1:3075",),
region="local",
))
catalog.upsert({
"shardId": "facts",
"ownerRuntimeId": "python-runtime",
"epoch": 1,
"schemaIds": ["schema-fact"],
"keyPrefix": "fact:",
"primaryEndpoints": ["cultnet://127.0.0.1:3076"],
})
self.assertTrue(primary.serves(schema_id="schema-note.v1", record_key="note:1"))
self.assertTrue(primary.serves(schema_id="schema-note", record_key="note:1"))
self.assertFalse(primary.serves(schema_id="schema-note", record_key="fact:1"))
self.assertEqual(catalog.list(schema_ids=["schema-note.v1"]), [primary])
self.assertEqual(catalog.list(schema_ids=["schema-note"]), [primary])
self.assertEqual(catalog.list(record_keys=["note:1"]), [primary])
response = catalog.create_response(
message_id="shards",
schema_ids=["schema-note"],
record_keys=["note:1"],
)
self.assertEqual(response["schemaVersion"], "cultnet.shard_catalog_response.v0")
self.assertEqual(response["shards"][0]["shardId"], "notes")
self.assertEqual(response["shards"][0]["ownerRuntimeId"], "python-runtime")
remote = CultNetShardCatalog()
applied = remote.apply_response(response)
self.assertEqual(applied[0].shard_id, "notes")
self.assertEqual(remote.get("notes"), applied[0])
def test_cultnet_shard_log_response_tracks_cursor_and_resync_state(self) -> None:
response = CultNetShardLogResponse.from_wire({
"schemaVersion": "cultnet.shard_log_response.v0",
"messageId": "log",
"shardId": "notes",
"shardEpoch": 2,
"entries": [
CultNetShardLogEntry(
sequence=2,
change_kind="updated",
put={"schemaVersion": "cultnet.document_put_raw.v0", "messageId": "put", "document": {"schemaId": "schema-note", "recordKey": "note:1", "payload": b"p"}},
committed_at="2026-06-14T00:00:00Z",
).to_wire(),
{"sequence": 3, "changeKind": "removed", "delete": {"schemaId": "schema-note", "recordKey": "note:1"}},
],
"resyncRequired": False,
})
self.assertEqual(response.last_sequence, 3)
self.assertFalse(response.resync_required)
self.assertEqual(response.entries[0].put["document"]["recordKey"], "note:1")
self.assertIsInstance(response.entries[0].raw_document, CultNetRawDocumentRecord)
self.assertEqual(response.entries[0].raw_document.record_key, "note:1")
self.assertEqual(response.entries[1].delete_schema_id, "schema-note")
self.assertEqual(response.entries[1].delete_record_key, "note:1")
self.assertEqual(response.to_wire()["entries"][1]["delete"]["recordKey"], "note:1")
self.assertIs(response.require_usable(), response)
resync = CultNetShardLogResponse.from_wire({
"schemaVersion": "cultnet.shard_log_response.v0",
"messageId": "log-resync",
"shardId": "notes",
"shardEpoch": 2,
"entries": [],
"resyncRequired": True,
"reason": "compacted",
"compactedThrough": 12,
})
self.assertEqual(resync.last_sequence, 12)
with self.assertRaisesRegex(ValueError, "compacted"):
resync.require_usable()
def test_cultnet_simulation_observation_helper_matches_schema_v0_shape(self) -> None:
claim_hash = compute_simulation_claim_hash("frame:42", "subject:player-1", "hit")
self.assertEqual(len(claim_hash), 64)
message = simulation_observation(
message_id="obs-1",
witness_runtime_id="python-test",
shard_id="interop",
shard_epoch=1,
frame=42,
subject_id="player-1",
claim_kind="hit",
claim_hash=claim_hash,
claim_summary="player-1 hit target-a",
observed_at="2026-06-13T00:00:02Z",
).to_wire()
self.assertEqual(message["schemaVersion"], "cultnet.simulation_observation.v0")
self.assertEqual(message["messageId"], "obs-1")
self.assertEqual(message["observation"]["witnessRuntimeId"], "python-test")
self.assertEqual(message["observation"]["claimHash"], claim_hash)
self.assertEqual(message["observation"]["weight"], 1.0)
observation = CultNetSimulationObservation.from_wire(message)
self.assertEqual(observation.witness_runtime_id, "python-test")
self.assertEqual(observation.claim_summary, "player-1 hit target-a")
self.assertEqual(observation.to_message_wire(message_id="obs-2")["observation"], message["observation"])
def test_cultnet_witness_artifact_bundle_uses_csharp_slot_order(self) -> None:
import msgpack # type: ignore
bundle = witness_artifact_bundle(
bundle_id="bundle-1",
witness_kind="interop-proof",
captured_at="2026-06-13T00:00:03Z",
subject={"documentType": "cultnet.interop-note", "subjectId": "note:python"},
contracts=[{"id": "c0e1a2b3c4d5", "role": "payload", "schemaId": "schema-a"}],
artifacts=[{"id": "a0e1a2b3c4d5", "role": "log", "uri": "cultcache://bundle-1/log", "mediaType": "text/plain"}],
timing_witnesses=[{"id": "e0e1a2b3c4d5", "stage": "roundtrip", "startedAt": "2026-06-13T00:00:03Z", "completedAt": "2026-06-13T00:00:04Z", "latencyMs": 1.0}],
provenance={"pipelineId": "interop", "runId": "run-1", "runtimeId": "python-test"},
)
payload = encode_witness_artifact_bundle_payload(bundle)
decoded = decode_witness_artifact_bundle_payload(payload)
typed = CultNetWitnessArtifactBundle.from_wire(bundle)
typed_payload = typed.to_payload()
typed_decoded = CultNetWitnessArtifactBundle.from_payload(typed_payload)
slots = msgpack.unpackb(payload, raw=False)
self.assertEqual(payload, typed_payload)
self.assertEqual(slots[0], "bundle-1")
self.assertEqual(slots[1], "interop-proof")
self.assertEqual(slots[2], "2026-06-13T00:00:03Z")
self.assertEqual(slots[3]["subjectId"], "note:python")
self.assertEqual(slots[4][0]["schemaId"], "schema-a")
self.assertEqual([slots[4][0]["id"], slots[5][0]["id"], slots[6][0]["id"]], ["c0e1a2b3c4d5", "a0e1a2b3c4d5", "e0e1a2b3c4d5"])
self.assertEqual(typed_decoded.contracts[0]["id"], "c0e1a2b3c4d5")
self.assertEqual(slots[5][0]["uri"], "cultcache://bundle-1/log")
self.assertEqual(slots[6][0]["latencyMs"], 1.0)
self.assertEqual(slots[7]["runtimeId"], "python-test")
self.assertEqual(decoded["bundleId"], "bundle-1")
self.assertEqual(decoded["witnessKind"], "interop-proof")
self.assertEqual(decoded["subject"]["subjectId"], "note:python")
self.assertEqual(decoded["contracts"][0]["schemaId"], "schema-a")
self.assertEqual(decoded["artifacts"][0]["mediaType"], "text/plain")
self.assertEqual(decoded["provenance"]["runtimeId"], "python-test")
self.assertEqual(typed.bundle_id, "bundle-1")
self.assertEqual(typed_decoded.to_wire(), bundle)
def test_python_interop_peer_filters_shard_snapshot_by_logged_membership(self) -> None:
runtime_id = f"python-interop-test-{uuid4().hex}"
state = build_state(
runtime_id=runtime_id,
runtime_kind="python",
display_name="Python Interop Test",
agent_id="python-interop-test-agent",
schema_path=str(Path(__file__).resolve().parents[3] / "contracts" / "cultnet" / "interop" / "cultnet.interop-note.schema.json"),
)
binding = state.bindings["note"]
logged_value = {
"schemaVersion": "cultnet.interop_note.v0",
"documentId": "note:logged",
"authorRuntimeId": runtime_id,
"title": "Logged",
"body": "This record belongs to the shard log.",
"tags": ["interop", "logged"],
}
unlogged_value = {
**logged_value,
"documentId": "note:unlogged",
"title": "Unlogged",
"body": "This record is in cache but not in the shard log.",
}
state.cache.put(binding.document, "note:logged", logged_value)
state.cache.put(binding.document, "note:unlogged", unlogged_value)
logged_record = {
"schemaId": state.note_schema_id,
"recordKey": "note:logged",
"storedAt": "2026-06-14T00:00:00Z",
"payloadEncoding": "messagepack",
"payload": binding.document.encode_payload(logged_value),
}
append_shard_log_put(
state,
{"messageId": "logged-put"},
logged_record,
)
response = raw_snapshot_response(
state,
{
"schemaVersion": "cultnet.snapshot_request.v0",
"messageId": "interop-shard-snapshot",
"schemaIds": [state.note_schema_id],
"shardId": state.shard_id,
},
)
self.assertEqual(response["shardId"], state.shard_id)
self.assertEqual(response["shardLogSequence"], 1)
self.assertEqual([record["recordKey"] for record in response["documents"]], ["note:logged"])
def test_python_interop_peer_refuses_v1_selection_messages(self) -> None:
# R-2, owed by Cut 1 (docs/cultnet-selection-cut.md, section 9): a v0-only peer refuses any
# non-v0 message loudly - cultnet.error.v0 { code: "unsupported_schema_version" } - rather than
# silently answering with an empty list. handle_server_message never touches `state` before
# this refusal fires, so no PeerState fixture is needed here. R-L: the original check matched
# only ".v1" exactly, so v2+ silently fell through to an empty list instead of being refused -
# this covers v2 and v10 (double-digit) too, not only the exact v1 spelling.
for schema_version in (
"cultnet.snapshot_request.v1",
"cultnet.database_subscribe.v1",
"cultnet.snapshot_request.v2",
"cultnet.database_subscribe.v2",
"cultnet.snapshot_request.v10",
):
with self.subTest(schema_version=schema_version):
responses = handle_server_message(
None, # type: ignore[arg-type]
{"schemaVersion": schema_version, "messageId": "v1-refused"},
{},
)
self.assertEqual(len(responses), 1)
self.assertEqual(responses[0]["schemaVersion"], "cultnet.error.v0")
self.assertEqual(responses[0]["code"], "unsupported_schema_version")
self.assertEqual(responses[0]["messageId"], "v1-refused")
def test_cultnet_simulation_consensus_dedupes_witnesses_and_requires_quorum(self) -> None:
from cultnet_py import CultNetSimulationConsensus
consensus = CultNetSimulationConsensus(
CultNetSimulationConsensusOptions(minimum_witnesses=2, quorum_ratio=1.0)
)
claim_hash = compute_simulation_claim_hash("hit", "alice", "bob", "frame:100")
observations = [
{
"witnessRuntimeId": "watcher-1",
"shardId": "arena",
"shardEpoch": 4,
"frame": 100,
"subjectId": "bob",
"claimKind": "hit",
"claimHash": claim_hash,
"claimSummary": "alice shot bob first",
"weight": 1.0,
},
{
"witnessRuntimeId": "watcher-1",
"shardId": "arena",
"shardEpoch": 4,
"frame": 100,
"subjectId": "bob",
"claimKind": "hit",
"claimHash": "stale-duplicate",
"weight": 0.5,
},
{
"witnessRuntimeId": "watcher-2",
"shardId": "arena",
"shardEpoch": 4,
"frame": 100,
"subjectId": "bob",
"claimKind": "hit",
"claimHash": claim_hash,
"weight": 1.0,
},
]
candidates = consensus.build_candidates(observations)
typed_candidates = consensus.build_candidate_objects([
CultNetSimulationObservation.from_wire(observation)
for observation in observations
])
self.assertEqual(len(candidates), 1)
self.assertEqual(candidates[0]["claimHash"], claim_hash)
self.assertEqual(candidates[0]["witnessCount"], 2)
self.assertEqual(candidates[0]["supportWeight"], 2.0)
self.assertTrue(candidates[0]["hasQuorum"])
self.assertEqual(len(typed_candidates), 1)
self.assertIsInstance(typed_candidates[0], CultNetSimulationConsensusCandidate)
self.assertEqual(typed_candidates[0].claim_hash, claim_hash)
self.assertEqual(typed_candidates[0].to_wire(), candidates[0])
self.assertEqual(CultNetSimulationConsensusCandidate.from_wire(candidates[0]), typed_candidates[0])
def _non_loopback_ipv4_address() -> str | None:
probe = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
probe.connect(("192.0.2.1", 9))
address = probe.getsockname()[0]
except OSError:
return None
finally:
probe.close()
return None if address.startswith("127.") or address == "0.0.0.0" else address
class CultNetRudpClientBindTests(unittest.TestCase):
def test_an_rudp_client_binds_loopback_only_for_a_loopback_endpoint(self) -> None:
self.assertEqual(rudp_client_bind_host("10.77.0.1"), "0.0.0.0")
self.assertEqual(rudp_client_bind_host("2001:db8::1"), "::")
self.assertEqual(rudp_client_bind_host("[2001:db8::1]"), "::")
self.assertEqual(rudp_client_bind_host("127.0.0.1"), "127.0.0.1")
self.assertEqual(rudp_client_bind_host("127.0.0.2"), "127.0.0.1")
self.assertEqual(rudp_client_bind_host("::1"), "::1")
self.assertEqual(rudp_client_bind_host("[::1]"), "::1")
# A socket bound to loopback cannot send off the host on Windows. On Linux
# it reaches a local non-loopback address, but from 127.0.0.1, so the
# source address shows which one the client bound.
def test_a_schema_transport_for_a_non_loopback_endpoint_does_not_send_from_loopback(self) -> None:
address = _non_loopback_ipv4_address()
if address is None:
self.skipTest("no non-loopback IPv4 address on this host")
listener = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
listener.bind(("0.0.0.0", 0))
listener.settimeout(2.0)
try:
with self.assertRaises(TimeoutError):
create_rudp_schema_transport(
host=address,
port=listener.getsockname()[1],
connection_id=0x43554C54,
timeout_seconds=0.2,
)
_, source = listener.recvfrom(65535)
self.assertEqual(source[0], address)
finally:
listener.close()
# The endpoint alone would bind 127.0.0.1; the explicit host must win, and the
# source address shows which one was bound. It stays on loopback, because a
# loopback-bound socket cannot send off the host on Windows.
def test_a_schema_transport_binds_an_explicit_bind_host(self) -> None:
probe = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
probe.bind(("127.0.0.2", 0))
except OSError:
self.skipTest("127.0.0.2 is not a local address on this host")
finally:
probe.close()
listener = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
listener.bind(("127.0.0.1", 0))
listener.settimeout(2.0)
try:
with self.assertRaises(TimeoutError):
create_rudp_schema_transport(
host="127.0.0.1",
port=listener.getsockname()[1],
connection_id=0x43554C54,
timeout_seconds=0.2,
bind_host="127.0.0.2",
)
_, source = listener.recvfrom(65535)
self.assertEqual(source[0], "127.0.0.2")
finally:
listener.close()
class CultNetRudpFragmentBoundTests(unittest.TestCase):
def _pair(self) -> tuple[CultNetRudpSession, CultNetRudpSession]:
sender = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=311, initial_sequence=1))
receiver = CultNetRudpSession(CultNetRudpSessionOptions(connection_id=311, initial_sequence=100))
sender.receive(receiver.accept_connect(sender.create_connect(0), 0), 0)
return sender, receiver
def test_stranded_fragment_sets_are_bounded_and_evicted_oldest_first(self) -> None:
sender, receiver = self._pair()
receiver._max_pending_fragment_sets = 4
# Eight payloads each lose their last fragment; the map must not grow past four.
stranded = []
for fill in range(1, 9):
packets = sender.send_many("media", bytes([fill]) * 2500, max_fragment_bytes=1000)
self.assertEqual(len(packets), 3)
for packet in packets[:2]:
self.assertEqual(receiver.receive(packet).delivered, ())
stranded.append(packets[2])
self.assertEqual(len(receiver._fragment_buffers), 4)
self.assertEqual(receiver.fragment_sets_evicted, 4)
# A complete payload still lands.
delivered = []
for packet in sender.send_many("media", bytes([9]) * 2500, max_fragment_bytes=1000):
delivered.extend(receiver.receive(packet).delivered)
self.assertEqual([frame.payload for frame in delivered], [bytes([9]) * 2500])
self.assertEqual(receiver.fragment_sets_evicted, 5)
# Oldest-first: set 8 still pends and completes late; set 1 was evicted.
self.assertEqual(receiver.receive(stranded[7]).delivered[0].payload, bytes([8]) * 2500)
self.assertEqual(receiver.receive(stranded[0]).delivered, ())
if __name__ == "__main__":
unittest.main()