PeerIdentityRegistry

class PeerIdentityRegistry<T : Any>(selfId: PeerId)

The one place a fabric decides whether a peer-supplied identity may join its roster, and who is allowed to take it away again.

A fabric learns a remote peer's PeerId from bytes the remote itself supplied — a display name, a handshake payload, a native callback's string. Those bytes are not trustworthy and not necessarily well-formed, and every fabric that decided for itself what to do about that decided slightly differently. This type is that decision, once: bind answers "may this identity join, and who holds it", unbind answers "may this device take it away", and peers is the roster that follows from the two.

Membership is keyed by the underlying device identity T rather than by the bare PeerId, which is what makes the answers possible:

The incidents this closes

Historically each fabric kept peers in a bare Set<PeerId> fed from those bytes:

  • #1494 / the #1466 class — two devices colliding on one id collapsed to a single set entry, and a disconnect for either removed that entry, evicting BOTH. MCSessionLink was the first path fixed.

  • #1466 self-dial — a peer handed its own advertisement registers itself as a remote, and the eventual drop of that self-link evicts the peer from its own roster.

  • #1821 — the same shape, still open in two sibling paths, plus the blank-id variant: an unaddressable PeerId("") that a set-equality teardown test (remaining == setOf(selfId)) can never clear, so a seam holding one stays Woven after its last real peer has gone.

What it is and is not

This is defence-in-depth against a malformed or hostile announcement, not a substitute for a fabric minting distinct identities in the first place (a per-device nonce, freshPeerId). Its guarantee is that the failure mode is "a peer is refused and logged", never "the wrong peer is evicted" or "the seam never tears".

It also cannot un-merge what the layer beneath it already merged. A fabric whose transport hands up only the id string — with no device handle to key by — must pass the id as its own T, and BindResult.COLLISION is then structurally unreachable for it: two devices really have become one identity before this type ever sees them. Such a caller still gets the blank/self refusals and the identity-scoped unbind; closing the collision needs a change one layer down.

Threading

Callers fire from framework queues with no cross-peer serialization guarantee (an MCSession delegate, a JNA callback thread), so bound is guarded by an explicit reentrantLock — matching the Quilter/SeamRoom exemplars. Correctness is a local property of this type, never an assumption about caller threading. There are no suspend calls, so the whole body of each operation runs under the lock.

Constructors

Link copied to clipboard
constructor(selfId: PeerId)

Types

Link copied to clipboard

Properties

Link copied to clipboard

Snapshot of the ids currently held by a live device. Never contains selfId.

Functions

Link copied to clipboard

Binds id to token, unless the id is blank, is this peer's own selfId, or is already held by a different device. The incumbent always wins a COLLISION — the id is never reassigned out from under a live peer.

Link copied to clipboard
fun clear()

Drops every binding, so peers is empty until something binds again.

Link copied to clipboard
fun holderOf(id: PeerId): T?

The device currently holding id, or null if nothing holds it.

Link copied to clipboard
fun idHeldBy(token: T): PeerId?

The id token currently holds, or null if it holds none.

Link copied to clipboard
fun unbind(id: PeerId, token: T): Boolean

Removes id only if token is the device currently holding it. Returns true when a binding was actually removed. A drop from a device that does not hold id — a collision-refused newcomer, a stale callback after clear, a peer that never bound at all — is a no-op, so the incumbent survives.