libp2p-hs-0.1.0.0: Haskell implementation of the libp2p networking stack
Safe HaskellNone
LanguageGHC2021

LibP2P.Protocol.Identify

Description

Identify protocol implementation (specs/identify).

Protocol ID: ipfsid/1.0.0

After a connection is established, both sides exchange IdentifyInfo messages to learn about each other's capabilities, listen addresses, and agent version. Like all libp2p protobuf streams, the message is varint-length-delimited on the wire: uvarint(len) ++ protobuf. This matches the delimited reader/writer used by go-libp2p (pbio), rust-libp2p, and js-libp2p.

Also implements Identify Push (ipfsidpush1.0.0) for proactive updates when local state changes.

Synopsis

Protocol IDs

identifyProtocolId :: ProtocolId Source #

Identify protocol ID.

identifyPushProtocolId :: ProtocolId Source #

Identify Push protocol ID.

Protocol logic

handleIdentify :: Switch -> Connection -> StreamIO -> IO () Source #

Handle an inbound Identify request (responder side).

Sends our local IdentifyInfo as a varint-length-prefixed protobuf, then closes the stream (per specs/identify: respond and close). The connection provides the remote address used for observedAddr.

requestIdentify :: Connection -> IO (Either String IdentifyInfo) Source #

Request Identify from a remote peer (initiator side).

Opens a new stream, negotiates ipfsid/1.0.0, then reads one varint-length-prefixed protobuf message. The publicKey field is validated against the connection's authenticated peer id (see validatePublicKey).

Identify is a one-shot exchange, so the stream is closed on every exit path — success, negotiation refusal, decode failure, timeout and exception alike. A caller that runs this per connection would otherwise leak a half-open stream per connection. The whole exchange is bounded by identifyTimeoutMicros: a peer that negotiates and then never answers must not pin a stream and a thread forever.

handleIdentifyPush :: Switch -> Connection -> StreamIO -> IO () Source #

Handle an inbound Identify Push (responder side).

Reads the pushed varint-length-prefixed IdentifyInfo from the remote peer. The length prefix is the message boundary — identify push has no stream-close boundary to fall back on. The local stream side is still closed on every exit path so a one-shot push cannot leak a half-open Yamux stream (go-libp2p's handleIdentifyResponse does the same with defer s.Close()).

The pushed info is merged into the existing peer entry via mergeIdentify: pushes may be partial updates, so fields absent from the message must not erase what we already know.

pushIdentify :: Switch -> IO () Source #

Push our current IdentifyInfo to every connected peer (sender side of ipfsidpush1.0.0).

Per specs/identify: open a stream to each remote peer, negotiate the push protocol id, send one Identify message and close the stream. Call this whenever local state advertised via identify changes (listen addresses, registered protocols). Failures on individual peers (e.g. push protocol not supported) are ignored.

mergeIdentify :: IdentifyInfo -> IdentifyInfo -> IdentifyInfo Source #

Merge a received (possibly partial) Identify update into the previously known info for a peer.

Per specs/identify: "missing fields should be ignored, as peers may choose to send partial updates containing only the fields whose values have changed." Optional fields keep the known value when the update omits them; repeated fields (protobuf cannot distinguish absent from empty) keep the known list when the update's is empty and are replaced wholesale otherwise, matching go-libp2p.

Identify on connect

identifyPeer :: Switch -> Connection -> IO (Either String ()) Source #

Run Identify against a freshly established connection and record the result in the peer store (specs/identify).

This is what makes a peer's advertised addresses and protocols known to us: without it swPeerStore only ever fills from an inbound push, so nothing is known about a peer we dialled or accepted. go-libp2p drives its IDService from the swarm's Connected notification for the same reason.

Failure is returned rather than thrown: the connection stays usable, and no peer store entry is created. There is no retry — a peer that does not answer Identify now will be picked up by a later push, if it sends one.

identifyTimeoutMicros :: Int Source #

Timeout for one Identify exchange: 5 seconds.

Matches go-libp2p's identify.DefaultTimeout, which it applies to all id interactions in both directions.

Building local info

buildLocalIdentify :: Switch -> Maybe Connection -> IO IdentifyInfo Source #

Build our local IdentifyInfo from Switch state, including a signed peer record (RFC 0003) over our listen addresses, sealed with the identity key.

Registration

registerIdentifyHandlers :: Switch -> IO () Source #

Register Identify protocol handlers on the Switch.

Registers: ipfsid/1.0.0 — respond to Identify requests ipfsidpush1.0.0 — handle Identify Push from remote

Wire framing

encodeFramedIdentify :: IdentifyInfo -> ByteString Source #

Encode an IdentifyInfo with its uvarint length prefix, as written on the wire: uvarint(len) ++ protobuf.

readFramedIdentify :: StreamIO -> Int -> IO (Either String IdentifyInfo) Source #

Read one varint-length-prefixed Identify message from a stream.

Reads the uvarint length prefix, then exactly that many payload bytes, and decodes the protobuf. Rejects messages larger than maxSize before reading the payload.