Message Format
All messages follow this structure:\n):
Message Types
handshake
Sent when establishing a connection to exchange peer identity and file metadata. Direction: Bidirectional (both peers send on connection) Structure:Message type identifier:
"handshake"Unique peer identifier (16 hexadecimal characters)
Name of the file being shared
Total file size in bytes
SHA-1 hash of the file content (40 hex characters)
Size of each piece in bytes (typically 65536)
TCP port this peer is listening on
- Initiating an outgoing connection (immediately after connection)
- Responding to an incoming connection (after receiving peer’s handshake)
- Seeders include all file metadata
- Leechers may have
nullvalues for file metadata until received from seeder - File hash is used to verify peers are sharing the same file
- Connection is terminated if file hashes don’t match
bitfield
Sent after handshake to announce which pieces this peer possesses. Direction: Sent by peer with pieces → peer without pieces Structure:Message type identifier:
"bitfield"Array of piece indices (numbers) that this peer has
- After successful handshake exchange (if peer has any pieces)
- Only sent if
havePieces.size > 0
- Receiver updates peer’s
availablePiecesset - Triggers
_scheduleRequests()to begin requesting needed pieces - Seeders send a complete bitfield with all piece indices
request
Requests a specific piece from a peer. Direction: Leecher → Seeder (or peer with the piece) Structure:Message type identifier:
"request"Zero-based index of the requested piece
- Peer needs a piece that another peer has
- Peer is not busy with another request
- Piece is not already pending
- Sender marks piece as pending
- Sender marks peer as busy
- Receiver reads piece from file and responds with
piecemessage
piece
Delivers piece data in response to a request. Direction: Seeder → Leecher (or peer with piece → requesting peer) Structure:Message type identifier:
"piece"Zero-based index of the piece
Base64-encoded piece data
- Peer receives a
requestmessage for a piece it has
- Receiver decodes base64 data to Buffer
- Receiver writes piece to file using
fileManager.writePiece() - Receiver updates tracking: adds to
havePieces, removes frommissingPiecesandpendingPieces - Receiver broadcasts
havemessage to other peers - Receiver marks sender peer as not busy
have
Announces that this peer has acquired a new piece. Direction: Broadcast to all connected peers Structure:Message type identifier:
"have"Zero-based index of the newly acquired piece
- After successfully writing a received piece to disk
- Broadcast to all connected peers (except the sender)
- Receiver adds piece index to peer’s
availablePiecesset - Receiver may request the piece if needed
- Enables efficient distribution in swarms (leechers can share with each other)
peers
Shares information about other known peers for peer discovery. Direction: Typically server → client (peer exchange) Structure:Message type identifier:
"peers"Array of peer information objects
Unique identifier of the peer
IP address or hostname of the peer
TCP port the peer is listening on
- After accepting an incoming connection (sharing other known peers)
- When a new peer joins (announcing the new peer to others)
- Receiver adds new peers to
knownPeersmap - Receiver initiates connections based on ID comparison rule
- Prevents duplicate connections (peer with higher ID initiates)
Message Parsing
Messages are buffered and parsed line-by-line:Message Flow Example
Scenario: Leecher connects to seeder- Leecher → Seeder:
handshake(with null file metadata) - Seeder → Leecher:
handshake(with complete file metadata) - Seeder → Leecher:
bitfield(all pieces) - Seeder → Leecher:
peers(list of other peers) - Leecher → Seeder:
request(piece 0) - Seeder → Leecher:
piece(piece 0 data) - Leecher → All peers:
have(piece 0) - Leecher → Seeder:
request(piece 1) - … continues until all pieces downloaded
Protocol Design Notes
- Newline delimiters allow for simple buffering and parsing
- JSON format is human-readable and easy to debug
- Base64 encoding for binary piece data ensures JSON compatibility
- Stateless messages - each message is self-contained
- Asynchronous - peers can send/receive messages independently