reticulum-js
    Preparing search index...

    Class Link

    An ephemeral encrypted channel between two destinations.

    A Link is established through a LINKREQUEST/LRPROOF handshake which derives shared session keys; once ACTIVE, application packets are Token-encrypted over the link. Provides inbound sequencing, keepalive/watchdog handling, link identification, and resource advertisement transport.

    Construct via Link.initiate or Link.accept; do not call the constructor directly.

    Hierarchy

    • EventTarget
      • Link
    Index
    • Low-level constructor. Prefer the Link.initiate / Link.accept factories.

      Parameters

      • opts: {
            destination: Destination;
            ephemeralEd25519Priv?: CryptoKey;
            ephemeralX25519Priv: CryptoKey;
            ephemeralX25519Pub?: Uint8Array<ArrayBufferLike>;
            initiator: boolean;
            linkId: Uint8Array<ArrayBufferLike>;
            mode?: number;
            mtu?: number;
            peerEd25519Pub?: Uint8Array<ArrayBufferLike>;
            peerX25519Pub?: Uint8Array<ArrayBufferLike>;
            transport: TransportCore;
        }
        • destination: Destination
        • OptionalephemeralEd25519Priv?: CryptoKey

          Initiator's fresh ephemeral Ed25519 private key (link-proof signing).

        • ephemeralX25519Priv: CryptoKey

          This side's ephemeral X25519 private key.

        • OptionalephemeralX25519Pub?: Uint8Array<ArrayBufferLike>

          This side's ephemeral X25519 public key (raw 32 bytes).

        • initiator: boolean
        • linkId: Uint8Array<ArrayBufferLike>
        • Optionalmode?: number
        • Optionalmtu?: number
        • OptionalpeerEd25519Pub?: Uint8Array<ArrayBufferLike>

          Peer's ephemeral Ed25519 public key (raw 32 bytes). Responder-only: the initiator's link-proof signing pub, captured from the LINKREQUEST body so the responder can verify initiator-signed link proofs (§6.5).

        • OptionalpeerX25519Pub?: Uint8Array<ArrayBufferLike>

          Peer's ephemeral X25519 public key (raw 32 bytes).

        • transport: TransportCore

      Returns Link

    _establishmentTimer: Timeout | null = null
    _peerEd25519Key: CryptoKey | null

    Cached verify-only CryptoKey imported from peerEd25519Pub (responder).

    _pendingLinkProofs: Map<any, any>

    Outbound CTX_NONE DATA packet hashes (hex → bytes) awaiting a link PROOF (§6.5).

    _rxQueue: Promise<void> = ...
    _status: number
    _watchdogTimer: Timeout | null = null
    attachedInterface: Interface | null

    The interface this link's traffic flows over — the interface the LINKREQUEST arrived on (responder) or the LRPROOF arrived on (initiator). Mirrors the Python reference's Link.attached_interface.

    The transport's TransportCore.sendPacket routes LINK-destination packets (LRPROOF, link DATA, keepalives, teardown) over this interface when no routing-table entry exists for the link id — the link's return path is its arrival interface for the link's lifetime, independent of which interface happens to be the node's default.

    bz2: Bzip2 | undefined = undefined

    Injected bz2 module (PROTOCOL-SPEC.md §10.2 step 2). The library never imports a compression dependency; the application assigns this if it wants Resource compression. When unset, compressed advertisements cannot be decompressed locally and sender-side compression is skipped.

    derivedKey: Uint8Array<ArrayBufferLike> | null = null
    destination: Destination
    ephemeralEd25519Priv: CryptoKey | null
    ephemeralX25519Priv: CryptoKey
    ephemeralX25519Pub: Uint8Array<ArrayBufferLike> | undefined
    expectedHops: number | null

    Hop count the link was initiated expecting (Link.expected_hops). Set from hopsTo(destination) on the initiator and from the LINKREQUEST hop count on the responder. null until then. The terminus rebalance corrects both this and the path table if the real hop count differs.

    failedSplitResources: Set<string> = ...

    Original hashes of split resources that failed mid-transfer; late segments for these are rejected instead of restarting an assembler.

    incomingResources: Map<string, Resource> = ...

    Incoming Resources (this side is the receiver) keyed by hex(resource.hash). Populated from RESOURCE_ADV; parts are routed by map_hash matching.

    initiator: boolean
    keepaliveInterval: number = Link.KEEPALIVE_MAX_SECS
    lastInboundTime: number
    lastKeepaliveTime: number = 0

    Wall-clock ms of the last keepalive sent on this link. Purely an egress-cadence guard: unlike Link#lastInboundTime it never counts toward liveness — a keepalive we sent proves nothing about the peer still being there. Gating pings on it also prevents a ping storm while a pong is in flight or lost.

    lastResourceWindow: number | null

    The request window the most recently concluded incoming transfer on this link had adapted to (Python Link.last_resource_window). New incoming transfers seed their window from it, so repeated transfers over one link — repeated peer syncs, repeated client fetches — resume at the earned throughput instead of re-learning it from 4 parts per round-trip every time.

    linkId: Uint8Array<ArrayBufferLike>
    maxResourceSize: number | undefined = undefined

    Cap on advertised Resource size accepted inbound (§10.4 bomb defense). Applications may lower this; null keeps the Resource default.

    mode: number = Link.MODE_AES256_CBC
    mtu: number = Link.DEFAULT_MTU
    outgoingResources: Map<string, Resource> = ...

    Outgoing Resources (this side is the sender) keyed by hex(resource.hash) — PROTOCOL-SPEC.md §10. Driven by Resource.advertise and fulfilled by inbound RESOURCE_REQ / RESOURCE_PRF / RESOURCE_RCL.

    peerEd25519Pub: Uint8Array<ArrayBufferLike> | null
    peerX25519Pub: Uint8Array<ArrayBufferLike> | null
    pendingRequests: Map<
        string,
        {
            handedOut: boolean;
            onMetadata: ((metadata: any) => void)
            | undefined;
            onProgress: ((info: any) => void) | undefined;
            reject: Function;
            resolve: Function;
            settledQuietly: boolean;
            settledSignal: Promise<undefined>;
            settleError: Error | null;
            signalSettled: ((value: undefined) => void) | undefined;
            timer: Timeout;
        },
    > = ...

    Initiator-side pending REQUESTs keyed by hex(request_id) (PROTOCOL-SPEC.md §11.5). Each entry resolves/rejects its returned Promise when the matching RESPONSE arrives or the timeout fires.

    handedOut tracks whether request() has returned the response promise to a caller yet. Between registering the entry and returning the promise, request() is still awaiting the transmit — during that window nobody holds the promise, so settling it with a rejection would surface as an unhandled rejection and crash the process. Unheld entries are settled quietly instead (see _failPendingRequest), and request() rethrows the recorded error once the transmit settles.

    pendingResources: Map<any, any> = ...

    Pending outgoing resource payloads keyed by hex hash (RESOURCE_ADV/REQ).

    rebalanced: number | null

    Epoch ms of the last path-rebalance, or null if none (Link.rebalanced).

    remoteIdentity: Identity | undefined
    requestTimeMs: number = 0

    Wall-clock time (ms) at which the LINKREQUEST was sent (initiator) or received (responder). Used to measure RTT.

    rtt: number = 0
    splitResources: Map<string, SplitResourceAssembler> = ...

    Split-resource assemblers (§10.3) keyed by hex(originalHash) — the first segment's hash that ties all segments of one logical transfer together.

    staleTime: number = ...
    teardownReason: number = 0
    token: Token | null = null
    transport: TransportCore
    ALLOW_LINK_PATH_REBALANCE: boolean = true

    Whether the link terminus corrects its path-table hop estimate when a link handshake's packets traverse a different hop count than expected (RNS.Transport.ALLOW_LINK_PATH_REBALANCE). Default on, matching upstream.

    DEFAULT_MTU: number = 500

    Default Reticulum MTU when MTU discovery is disabled or unavailable.

    EC_PUBLIC_KEY_SIZE: number = 64

    Combined size of the two initiator ephemeral public keys (X25519 + Ed25519).

    KEEPALIVE_MAX_RTT_SECS: number = 1.75
    KEEPALIVE_MAX_SECS: number = 360
    KEEPALIVE_MIN_SECS: number = 5
    MODE_AES256_CBC: number = 0x01

    Default link mode (the only enabled mode in upstream RNS).

    RESPONSE_GRACE_FACTOR: number = 1.125

    Fixed multiplier on the response grace term (PROTOCOL-SPEC.md §11.5).

    RESPONSE_MAX_GRACE_TIME_SECS: number = 4.0

    Response-side grace term for the default REQUEST timeout (PROTOCOL-SPEC.md §11.5). Mirrors RNS.Resource.RESPONSE_MAX_GRACE_TIME — the same caveat applies.

    SIGNALLING_SIZE: number = 3

    Size of the optional MTU/mode signalling trailer on LINKREQUEST/LRPROOF.

    STALE_FACTOR: number = 2
    TRAFFIC_TIMEOUT_FACTOR: number = 6

    Multiplier on measured RTT used when computing the default REQUEST response timeout (PROTOCOL-SPEC.md §11.5). Mirrors RNS.Link.TRAFFIC_TIMEOUT_FACTOR — verify against upstream if precise timeout parity matters.

    • get mdu(): number

      Maximum plaintext bytes that fit in a single link DATA packet after Token encryption and the HEADER_1 framing are applied (PROTOCOL-SPEC.md §11.1, §5.2).

      The on-wire form of a link DATA packet is:

      flags(1) hops(1) dest_hash(16) context(1)   iv(16) aes_ct hmac(32)
      \---------------------- 19 ----------------/ \------ 48 ------/

      PKCS#7 padding always adds 1–16 bytes (a full block when the plaintext is itself a block multiple), so the largest plaintext P whose ciphertext fits the remaining budget is the largest block-multiple ciphertext ≤ (mtu − 67) minus one byte. At the default mtu = 500 this yields the spec-pinned MDU = 431 (wire packet 499 B, verified against RNS).

      Returns number

    • Internal

      Removes an incoming Resource registration.

      Parameters

      • hash: Uint8Array<ArrayBufferLike>

      Returns void

    • Internal

      Removes an outgoing Resource registration (e.g. a split resource advancing to its next segment, keyed by the previous segment's hash).

      Parameters

      • hash: Uint8Array<ArrayBufferLike>

      Returns void

    • The addEventListener() method of the EventTarget interface sets up a function that will be called whenever the specified event is delivered to the target.

      MDN Reference

      Parameters

      • type: string
      • callback: EventListenerOrEventListenerObject | null
      • Optionaloptions: boolean | AddEventListenerOptions

      Returns void

    • The dispatchEvent() method of the EventTarget sends an Event to the object, (synchronously) invoking the affected event listeners in the appropriate order. The normal event processing rules (including the capturing and optional bubbling phase) also apply to events dispatched manually with dispatchEvent().

      MDN Reference

      Parameters

      • event: Event

      Returns boolean

    • The reliable typed-message Channel for this link (the Python reference's Link carries one lazily). Lazily created on first access (or on the first inbound CHANNEL packet). Returns the same instance for the lifetime of the link.

      Register message classes with channel.registerMessageType(...) and receive with channel.addMessageHandler(cb); send with channel.send(message).

      Returns Channel

    • Initiator: prove which long-term identity owns this link to the responder.

      Must be called AFTER the link is ACTIVE and BEFORE sending any application DATA. This is load-bearing for Python-LXMF interop: the Python LXMRouter does not install its link-data listener until it has processed LINKIDENTIFY, so a DATA/RESOURCE sent before LINKIDENTIFY is silently dropped on the Python side. Wire body (link-encrypted):

      public_key(64) || signature(64)

      where signature = identity.sign(link_id || public_key), matching the Python reference.

      Parameters

      • identity: Identity

        The initiator's long-term identity.

      Returns Promise<void>

    • Queues and processes an inbound packet addressed to this link.

      Parameters

      • packet: Packet
      • OptionalreceivingInterface: Interface | null = null

        Interface the packet arrived on. On the initiator side the LRPROOF's arrival interface becomes Link.attachedInterface (the reference sets attached_interface at proof validation), pinning the return path for the rest of the link's lifetime.

      Returns Promise<void>

    • The removeEventListener() method of the EventTarget interface removes an event listener previously registered with EventTarget.addEventListener() from the target. The event listener to be removed is identified using a combination of the event type, the event listener function itself, and various optional options that may affect the matching process; see Matching event listeners for removal.

      MDN Reference

      Parameters

      • type: string
      • callback: EventListenerOrEventListenerObject | null
      • Optionaloptions: boolean | EventListenerOptions

      Returns void

    • Initiator: send a REQUEST over the link and await the RESPONSE (PROTOCOL-SPEC.md §11.1, §11.5).

      Packs the msgpack envelope [timestamp, path_hash, data] (single pack — data is encoded directly, NOT pre-msgpacked), dispatches by size, and returns a Promise that resolves with the response value when the server's matching RESPONSE arrives.

      For a single-packet REQUEST the request_id the server echoes back is SHA-256(packet.get_hashable_part())[:16] — the truncated hash of the encrypted wire packet, computed identically on both sides. It is NOT random and NOT a hash of the plaintext envelope.

      Parameters

      • path: string

        Opaque path token (e.g. "/page/index.mu").

      • Optionaldata: any = null

        Application value for envelope element [2] (null for plain GETs, an object for NomadNet form posts, an array for LXMF /get rounds, a Uint8Array for opaque blobs …). Passed to msgpack directly — do NOT pre-pack it.

      • Optionaloptions: {
            onMetadata?: (metadata: any) => void;
            onProgress?: (
                info: {
                    direction: "request" | "response";
                    loaded: number;
                    segmentIndex: number;
                    segmentTotal: number;
                    total: number;
                },
            ) => void;
            timeout?: number;
        } = {}
        • OptionalonMetadata?: (metadata: any) => void

          Called with the decoded response metadata when the responder answers with a metadata-carrying Resource (§10.4 x flag). Invoked before the returned Promise resolves, so callers observe it as soon as the response is available.

        • OptionalonProgress?: (
              info: {
                  direction: "request" | "response";
                  loaded: number;
                  segmentIndex: number;
                  segmentTotal: number;
                  total: number;
              },
          ) => void

          Transfer progress for Resource-backed request/response bodies

          • direction tells whether the bytes flow away from (request upload) or towards (response download) the caller. loaded / total are approximate logical byte counts for the current segment; segmentIndex / segmentTotal let callers aggregate split transfers.
        • Optionaltimeout?: number

          Response timeout in ms (defaults to rtt * TRAFFIC_TIMEOUT_FACTOR + RESPONSE_MAX_GRACE_TIME_SECS * 1.125).

      Returns Promise<any>

      The decoded RESPONSE value.

    • Cleanly tears down the link by sending a LINKCLOSE whose encrypted body is the link_id, then transitioning to CLOSED locally.

      Returns Promise<void>

    • Resolves once the Link reaches ACTIVE status (immediately if it already is). Callers that obtain a Link reference before the handshake finishes — e.g. right after Destination.createLink() resolves — should await this before sending application data, since the session token is only derived once the handshake completes.

      Mirrors the gating the LXMF router applies in its outbound processing before sending DIRECT messages.

      Parameters

      • OptionaltimeoutMs: number

        How long to wait for the handshake. Defaults to the bitrate-adaptive establishment timeout (import("./transport.js").TransportCore.establishmentTimeout); falls back to 15 s when no transport/destination is available.

      Returns Promise<Link>

    • Responder side: accept an incoming LINKREQUEST and complete the handshake up to LRPROOF.

      Derives the link_id, extracts the initiator's ephemeral keys, derives the session keys, builds and sends the LRPROOF signed with the destination's long-term identity key, registers the link with the transport, and transitions to HANDSHAKE. The link becomes ACTIVE once the initiator's LRRTT arrives.

      Parameters

      • destination: Destination

        IN destination whose identity is this node's.

      • transport: TransportCore
      • requestPacket: Packet

        The incoming LINKREQUEST (with raw populated).

      • OptionalreceivingInterface: Interface | null = null

        The interface the LINKREQUEST arrived on. Captured as the link's return path (Link.attachedInterface), so responder→initiator traffic flows back over it regardless of the node's default interface. Matches the reference's link.attached_interface = packet.receiving_interface.

      Returns Promise<Link>

    • Initiator side: establish a new link to destination.

      Generates fresh ephemeral X25519 + Ed25519 keypairs, builds and sends the LINKREQUEST (dest_type=SINGLE, addressed to the responder's destination hash), derives the link_id from the serialized packet, registers the link with the transport, and transitions to HANDSHAKE. The link becomes ACTIVE once the responder's LRPROOF is validated.

      Parameters

      • destination: Destination

        OUT destination whose identity is the responder's.

      • transport: TransportCore

      Returns Promise<Link>

    • Packs the 3-byte MTU/mode signalling trailer.

      Parameters

      • mtu: number
      • mode: number

      Returns Uint8Array<ArrayBufferLike>