mirror of
https://github.com/pyMC-dev/pyMC_Repeater.git
synced 2026-07-27 12:03:08 +02:00
95555e0c12
Local identities occupy two routing/persistence namespaces: companions (companion_bridges[hash] plus the companion_* tables keyed by the hash byte) and server-side identities (the repeater and every room server, which share the login/text/protocol helper handlers[hash] slot and the room_* tables). A one-byte prefix collision is only unrepresentable when both identities share a namespace; a companion and a server-side identity live in physically separate stores, and the packet router (_consume_via_local_candidates) already offers a colliding packet to both and lets HMAC pick the owner. Key IdentityManager state by (hash_byte, namespace) instead of the bare hash so the guard rejects only same-namespace collisions. This keeps blocking the pairs that actually break -- companion<->companion (bridge overwrite plus companion_prefs PRIMARY KEY corruption) and server<->server, i.e. repeater<->room-server and room-server<->room-server (handlers[hash] overwrite plus room_* corruption) -- while allowing a companion to share a prefix with the repeater or a room server, which was previously rejected despite being only a cosmetic label clash.
184 lines
7.2 KiB
Python
184 lines
7.2 KiB
Python
import pytest
|
|
|
|
from repeater.identity_manager import IdentityConfigurationError, IdentityManager, IdentitySpec
|
|
|
|
|
|
class _FakeIdentity:
|
|
def __init__(self, pubkey: bytes, addr: bytes = b"\xaa\xbb"):
|
|
self._pubkey = pubkey
|
|
self._addr = addr
|
|
|
|
def get_public_key(self):
|
|
return self._pubkey
|
|
|
|
def get_address_bytes(self):
|
|
return self._addr
|
|
|
|
|
|
def test_identity_manager_register_lookup_and_collision_paths():
|
|
mgr = IdentityManager(config={})
|
|
id_a = _FakeIdentity(bytes([0x11]) + b"A" * 31, addr=b"\x01\x02")
|
|
id_b_collision = _FakeIdentity(bytes([0x11]) + b"B" * 31, addr=b"\x03\x04")
|
|
|
|
assert mgr.register_identity("alpha", id_a, {"k": 1}, "repeater") is True
|
|
assert mgr.has_identity(0x11) is True
|
|
assert mgr.get_identity_by_hash(0x11)[0] is id_a
|
|
assert mgr.get_identity_by_name("alpha")[0] is id_a
|
|
|
|
assert mgr.register_identity("beta", id_b_collision, {"k": 2}, "room_server") is False
|
|
|
|
|
|
def test_identity_manager_rejects_duplicate_names_without_mutating_state():
|
|
mgr = IdentityManager(config={})
|
|
id_a = _FakeIdentity(bytes([0x11]) + b"A" * 31)
|
|
id_b = _FakeIdentity(bytes([0x22]) + b"B" * 31)
|
|
|
|
assert mgr.register_identity("alpha", id_a, {}, "repeater") is True
|
|
assert "already registered" in mgr.registration_error("alpha", id_b, "companion")
|
|
assert mgr.validate_identity("alpha", id_b, "companion") is False
|
|
assert mgr.get_identity_by_hash(0x22) is None
|
|
|
|
|
|
def test_identity_manager_list_and_type_filtering():
|
|
mgr = IdentityManager(config={})
|
|
id_a = _FakeIdentity(bytes([0x22]) + b"A" * 31)
|
|
id_b = _FakeIdentity(bytes([0x33]) + b"B" * 31)
|
|
|
|
mgr.register_identity("rep-main", id_a, {"x": 1}, "repeater")
|
|
mgr.register_identity("room-a", id_b, {"y": 2}, "room_server")
|
|
|
|
listed = mgr.list_identities()
|
|
assert len(listed) == 2
|
|
assert any(item["hash"] == "0x22" and item["name"] == "repeater:rep-main" for item in listed)
|
|
assert any(item["hash"] == "0x33" and item["type"] == "room_server" for item in listed)
|
|
|
|
assert mgr.has_identity_type("repeater") is True
|
|
assert mgr.has_identity_type("room_server") is True
|
|
assert mgr.has_identity_type("unknown") is False
|
|
|
|
by_type = mgr.get_identities_by_type("room_server")
|
|
assert len(by_type) == 1
|
|
assert by_type[0][0] == "room-a"
|
|
|
|
|
|
def test_identity_manager_list_handles_none_identity_fields():
|
|
mgr = IdentityManager(config={})
|
|
mgr.identities[(0x44, "server")] = (None, {}, "repeater")
|
|
mgr.registered_hashes[(0x44, "server")] = "repeater:ghost"
|
|
|
|
listed = mgr.list_identities()
|
|
assert listed[0]["address"] == "N/A"
|
|
assert listed[0]["public_key"] is None
|
|
|
|
|
|
def test_validate_specs_rejects_intra_batch_same_namespace_hash_collision():
|
|
"""Two server-side identities (repeater + room server) share the login/
|
|
text helper handlers[hash] slot, so a prefix collision is unrepresentable."""
|
|
mgr = IdentityManager(config={})
|
|
id_a = _FakeIdentity(bytes([0x11]) + b"A" * 31)
|
|
id_b = _FakeIdentity(bytes([0x11]) + b"B" * 31)
|
|
|
|
with pytest.raises(IdentityConfigurationError, match="one-byte public-key prefixes"):
|
|
mgr.validate_specs(
|
|
[
|
|
IdentitySpec("alpha", id_a, {}, "repeater"),
|
|
IdentitySpec("beta", id_b, {}, "room_server"),
|
|
]
|
|
)
|
|
|
|
|
|
def test_validate_specs_rejects_two_companions_same_prefix():
|
|
"""Two companions share companion_bridges[hash] and the companion_* DB
|
|
keying, so their prefix must be unique."""
|
|
mgr = IdentityManager(config={})
|
|
id_a = _FakeIdentity(bytes([0x11]) + b"A" * 31)
|
|
id_b = _FakeIdentity(bytes([0x11]) + b"B" * 31)
|
|
|
|
with pytest.raises(IdentityConfigurationError, match="one-byte public-key prefixes"):
|
|
mgr.validate_specs(
|
|
[
|
|
IdentitySpec("alpha", id_a, {}, "companion"),
|
|
IdentitySpec("beta", id_b, {}, "companion"),
|
|
]
|
|
)
|
|
|
|
|
|
def test_validate_specs_allows_companion_sharing_prefix_with_server_identity():
|
|
"""A companion and a server-side identity live in separate runtime stores
|
|
and DB tables (the router disambiguates by HMAC), so they may share a
|
|
one-byte prefix. Verified for both the repeater and a room server."""
|
|
mgr = IdentityManager(config={})
|
|
repeater = _FakeIdentity(bytes([0x11]) + b"R" * 31)
|
|
room = _FakeIdentity(bytes([0x22]) + b"S" * 31)
|
|
comp_vs_repeater = _FakeIdentity(bytes([0x11]) + b"C" * 31)
|
|
comp_vs_room = _FakeIdentity(bytes([0x22]) + b"D" * 31)
|
|
|
|
mgr.validate_specs(
|
|
[
|
|
IdentitySpec("rep", repeater, {}, "repeater"),
|
|
IdentitySpec("room", room, {}, "room_server"),
|
|
IdentitySpec("comp-a", comp_vs_repeater, {}, "companion"),
|
|
IdentitySpec("comp-b", comp_vs_room, {}, "companion"),
|
|
]
|
|
)
|
|
|
|
|
|
def test_register_companion_sharing_repeater_prefix_keeps_both():
|
|
"""Cross-namespace registration keeps both entries addressable."""
|
|
mgr = IdentityManager(config={})
|
|
repeater = _FakeIdentity(bytes([0x11]) + b"R" * 31)
|
|
companion = _FakeIdentity(bytes([0x11]) + b"C" * 31)
|
|
|
|
assert mgr.register_identity("rep", repeater, {}, "repeater") is True
|
|
assert mgr.register_identity("comp", companion, {}, "companion") is True
|
|
|
|
assert mgr.get_identity_by_hash(0x11, "server")[0] is repeater
|
|
assert mgr.get_identity_by_hash(0x11, "companion")[0] is companion
|
|
assert mgr.has_identity(0x11, "server") is True
|
|
assert mgr.has_identity(0x11, "companion") is True
|
|
assert len(mgr.list_identities()) == 2
|
|
|
|
|
|
def test_validate_specs_rejects_intra_batch_duplicate_name():
|
|
mgr = IdentityManager(config={})
|
|
id_a = _FakeIdentity(bytes([0x11]) + b"A" * 31)
|
|
id_b = _FakeIdentity(bytes([0x22]) + b"B" * 31)
|
|
|
|
with pytest.raises(IdentityConfigurationError, match="repeater:alpha"):
|
|
mgr.validate_specs(
|
|
[
|
|
IdentitySpec("alpha", id_a, {}, "repeater"),
|
|
IdentitySpec("alpha", id_b, {}, "companion"),
|
|
]
|
|
)
|
|
|
|
|
|
def test_validate_specs_rejects_registered_collisions_without_mutation():
|
|
mgr = IdentityManager(config={})
|
|
id_a = _FakeIdentity(bytes([0x11]) + b"A" * 31)
|
|
id_hash_collision = _FakeIdentity(bytes([0x11]) + b"B" * 31)
|
|
id_name_collision = _FakeIdentity(bytes([0x22]) + b"C" * 31)
|
|
|
|
assert mgr.register_identity("alpha", id_a, {}, "repeater") is True
|
|
|
|
# A room server (server namespace) collides with the registered repeater.
|
|
with pytest.raises(IdentityConfigurationError, match="conflicts"):
|
|
mgr.validate_specs([IdentitySpec("beta", id_hash_collision, {}, "room_server")])
|
|
with pytest.raises(IdentityConfigurationError, match="already registered"):
|
|
mgr.validate_specs([IdentitySpec("alpha", id_name_collision, {}, "companion")])
|
|
|
|
# Validation never registers anything.
|
|
assert mgr.get_identity_by_hash(0x22) is None
|
|
assert mgr.get_identity_by_name("beta") is None
|
|
|
|
|
|
def test_validate_specs_accepts_distinct_batch():
|
|
mgr = IdentityManager(config={})
|
|
mgr.validate_specs(
|
|
[
|
|
IdentitySpec("alpha", _FakeIdentity(bytes([0x11]) + b"A" * 31), {}, "repeater"),
|
|
IdentitySpec("beta", _FakeIdentity(bytes([0x22]) + b"B" * 31), {}, "room_server"),
|
|
IdentitySpec("gamma", _FakeIdentity(bytes([0x33]) + b"C" * 31), {}, "companion"),
|
|
]
|
|
)
|