Message Protocol Definitions in `message.go`: How Croc Structures Peer-to-Peer Communication

The src/message/message.go file defines Croc's core messaging protocol through JSON-serializable structs, string-based message type constants, and utility functions that handle optional compression and encryption.

Croc is a secure file transfer tool that coordinates complex handshakes between sender and receiver over arbitrary network transports. The message protocol definitions in message.go establish the fundamental data structures and serialization logic that power every PAKE exchange, metadata transfer, and termination signal. This file implements a lightweight, extensible protocol that can be secured with Croc's built-in compression and encryption layers.

Message Types and Structure

Protocol Message Types

The protocol defines a set of string-based constants that enumerate every possible message purpose. According to the Croc source code, these types span from TypePAKE (for the PAKE handshake) to TypeFileInfo (for file metadata), covering critical actions such as IP discovery, recipient readiness signals, and connection termination. You can find these definitions between lines 15-24 in src/message/message.go.

Each constant represents a distinct phase in the file transfer lifecycle, allowing peers to route messages to the appropriate handlers without ambiguity.

The Message Struct

The Message struct serves as the universal container for all protocol communication. As implemented in lines 27-33 of src/message/message.go, it contains five fields:

  • t – The message Type (using the constants discussed above)
  • m – A text string payload for human-readable messages or metadata
  • b – An optional binary blob for file data or cryptographic material
  • b2 – A secondary optional binary field for additional data
  • n – An integer field useful for sequence numbers or chunk indices

This structure is designed to be marshaled to and from JSON, making it transport-agnostic while remaining compact enough for efficient network transmission.

Serialization and Transport Utilities

String Representation

For debugging and logging purposes, the Message struct implements the fmt.Stringer interface. The String() method (lines 35-38) JSON-marshals the entire struct, providing a human-readable representation that excludes sensitive binary data while preserving the type and text fields.

Sending Messages

The Send function (lines 40-48) handles the complete transmission pipeline. It accepts three parameters: a comm.Comm channel (defined in src/comm/comm.go), an optional encryption key, and the Message itself. When invoked, the function encodes the message and writes it to the provided communication channel, returning any transmission errors encountered.

Encoding Pipeline

Before messages hit the wire, the Encode function (lines 50-64) processes them through a three-stage pipeline:

  1. JSON Serialization – Converts the Message struct to bytes
  2. Compression – Applies compression using utilities from src/compress/compress.go
  3. Encryption – Optionally encrypts the compressed bytes using the provided key via src/crypt/crypt.go

The function returns the final byte slice ready for socket transmission, with internal logging that distinguishes between encrypted and plain-text operations.

Decoding Incoming Payloads

The Decode function (lines 66-84) reverses the encoding process. It accepts an optional encryption key and a byte slice payload, then executes:

  1. Decryption – Decrypts the payload if a key is supplied
  2. Decompression – Restores the original JSON bytes
  3. Unmarshaling – Parses the JSON into a Message struct

This function includes logging to track successful reads and returns detailed errors if any stage of the pipeline fails.

Practical Usage Examples

Creating and sending a simple "recipient ready" message requires instantiating the struct and invoking Send:

msg := message.Message{
    Type:    message.TypeRecipientReady,
    Message: "ready",
}
err := message.Send(commChannel, nil, msg) // no encryption key
if err != nil {
    log.Fatalf("send failed: %v", err)
}

For scenarios requiring manual transport or storage, use Encode with encryption:

key := []byte("my-secret-key")
msg := message.Message{
    Type:  message.TypeFileInfo,
    Bytes: []byte("filename.txt"),
    Num:   12345,
}
payload, err := message.Encode(key, msg)
if err != nil {
    log.Fatalf("encode error: %v", err)
}
// `payload` can now be written to a socket or stored.

Decoding a received payload on the receiving end uses the same key:

received, err := message.Decode(key, payload)
if err != nil {
    log.Fatalf("decode error: %v", err)
}
fmt.Printf("Got %s message: %+v\n", received.Type, received)

Summary

  • src/message/message.go defines the core protocol using a Message struct with fields for type (t), text (m), binary data (b, b2), and integers (n).
  • Message types are string constants (e.g., TypePAKE, TypeFileInfo) that enumerate all possible protocol actions from handshake to file transfer.
  • The Encode function serializes messages to JSON, compresses them, and optionally encrypts them using src/crypt/crypt.go.
  • The Decode function reverses this process, handling decryption, decompression, and JSON unmarshaling.
  • The Send function abstracts transport concerns by accepting a comm.Comm channel from src/comm/comm.go, enabling the protocol to work over any network transport.

Frequently Asked Questions

What fields does the Message struct contain?

The Message struct contains five fields: t (the message Type), m (a text string), b and b2 (optional binary blobs), and n (an integer). These fields accommodate various protocol needs from simple text signals to file chunks and cryptographic nonces.

How does Croc secure message transmission?

Croc secures messages through the Encode and Decode functions, which optionally encrypt payloads using AES encryption from src/crypt/crypt.go after compressing them. The encryption is only applied when a key is provided, allowing the same code path for both secure and plaintext modes.

What is the difference between Encode and Send?

Encode prepares a message for transmission by converting it to bytes (JSON, compression, and optional encryption) but does not transmit it. Send is a higher-level function that calls Encode internally and then writes the result to a comm.Comm transport channel, handling the full transmission lifecycle.

Where are the message types defined?

Message types are defined as string constants in the src/message/message.go file, specifically between lines 15-24. These include TypePAKE for handshake initialization, TypeFileInfo for metadata exchange, and types for IP discovery and connection termination.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →