# How Croc Multiplexes Multiple Transfers Over a Single Connection

> Learn how Croc multiplexes multiple file transfers over one encrypted TCP connection using message identifiers. Discover efficient concurrent data sharing.

- Repository: [Zack/croc](https://github.com/schollz/croc)
- Tags: internals
- Published: 2026-07-26

---

**Croc establishes one encrypted TCP connection using a PAKE handshake, then interleaves chunks from different files using unique message identifiers, allowing concurrent transfers without opening additional sockets.**

Croc is an open-source command-line file transfer tool that streams multiple files simultaneously over a single encrypted session. Unlike traditional transfer methods that open new TCP connections per file, croc implements message-level multiplexing to push chunks from many files through one persistent socket, maximizing bandwidth utilization while maintaining end-to-end encryption.

## Session Establishment with PAKE

Before any data flows, croc creates a secure channel between sender and receiver. The two endpoints perform a **Password-Authenticated Key Exchange (PAKE)** handshake implemented in `croc.Pake`. This exchange yields a shared secret key that is stored in the `Client` struct as `Key []byte` in [`src/croc/croc.go`](https://github.com/schollz/croc/blob/main/src/croc/croc.go) (lines 15–19). 

All subsequent messages traveling over the single `net.Conn` are encrypted and decrypted using this shared key, ensuring that the multiplexed stream remains private even when interleaving data from multiple files.

## Message Framing and Transfer IDs

Multiplexing relies on a strict message protocol defined in [`src/message/message.go`](https://github.com/schollz/croc/blob/main/src/message/message.go). Every payload moving across the connection is wrapped in a `Message` structure that contains metadata allowing the receiver to demultiplex the stream:

```go
type Message struct {
    Kind string      // "chunk", "fileinfo", "close", etc.
    ID   string      // unique identifier for the specific file
    Data []byte      // actual bytes or JSON metadata
}

```

The **transfer ID** field is critical: it identifies which file a given chunk belongs to. Because each message carries its own ID, the receiver can process chunks from file A, then file B, then file A again, writing each to the correct destination without confusion.

## Chunk-Level Multiplexing Over a Single Channel

Once the session is established, croc breaks each file into fixed-size blocks (chunks). Instead of creating separate network threads or sockets per file, the sender pushes all chunks onto a **single `comm.Comm` channel** (`c.conn []*comm.Comm`).

As implemented in [`src/comm/comm.go`](https://github.com/schollz/croc/blob/main/src/comm/comm.go), the `Send` method encrypts each `Message` with the shared secret key and writes it to the underlying TCP connection:

```go
func (c *Comm) Send(msg Message) error {
    // Encrypt with the shared secret key (c.key)
    enc, err := encrypt(msg, c.key)
    if err != nil {
        return err
    }
    // Write to the single underlying net.Conn
    _, err = c.conn.Write(enc)
    return err
}

```

The sender iterates over the file list and calls `Send` for each chunk immediately, without waiting for previous files to complete. The receiver reads from the same stream, inspects the `Kind` and `ID` fields, and writes the decrypted bytes to the appropriate target file. This interleaving allows many files to progress concurrently, fully utilizing the bandwidth of one TCP stream.

## Controlling Multiplexing Behavior

### Disabling Multiplexing

Multiplexing is enabled by default, but croc provides an escape hatch for debugging or compatibility with restricted endpoints. The `--no-multi` flag (parsed in [`src/cli/cli.go`](https://github.com/schollz/croc/blob/main/src/cli/cli.go)) sets the `NoMultiplexing` boolean in `Options`. When active, the transfer loop in [`src/croc/croc.go`](https://github.com/schollz/croc/blob/main/src/croc/croc.go) (referenced at lines 1140, 1438, and 1775) emits a `"no multiplexing"` debug message and falls back to a sequential, one-file-at-a-time mode.

```bash

# Default: multiplexed transfer

croc send fileA.txt fileB.pdf

# Disable multiplexing for sequential transfer

croc send --no-multi fileA.txt fileB.pdf

```

### Flow Control and Rate Limiting

To prevent the single connection from overwhelming the receiver, croc employs a **per-second rate limiter** (`rate.Limiter`) that throttles the sending of chunks. This guarantees steady throughput while keeping the pipe full, ensuring that multiplexing does not degrade performance on slower networks.

## Key Implementation Files

The multiplexing logic spans several core components:

- **[`src/croc/croc.go`](https://github.com/schollz/croc/blob/main/src/croc/croc.go)**: Contains the `Client` struct, session setup, and the main transfer loop that orchestrates multiplexing.
- **[`src/message/message.go`](https://github.com/schollz/croc/blob/main/src/message/message.go)**: Defines the `Message` type with `Kind`, `ID`, and payload fields—the building blocks of multiplexed streams.
- **[`src/comm/comm.go`](https://github.com/schollz/croc/blob/main/src/comm/comm.go)**: Low-level wrapper around the TCP connection providing `Send` and `Receive` methods that handle encryption and the single-socket I/O.
- **[`src/cli/cli.go`](https://github.com/schollz/croc/blob/main/src/cli/cli.go)**: Parses the `--no-multi` flag and propagates the `NoMultiplexing` option to the client.
- **[`src/utils/utils.go`](https://github.com/schollz/croc/blob/main/src/utils/utils.go)**: Provides helper functions for chunking files and calculating byte ranges used during multiplexed transfers.

## Summary

- Croc creates **one encrypted TCP session** using PAKE and reuses that single `net.Conn` for all files.
- The `Message` struct in [`src/message/message.go`](https://github.com/schollz/croc/blob/main/src/message/message.go) wraps every payload with a unique **transfer ID**, enabling the receiver to demultiplex interleaved chunks.
- All file chunks flow through a **single `comm.Comm` channel**, allowing parallel progress across many files without socket overhead.
- The `--no-multi` flag forces sequential transfers for debugging or compatibility purposes.
- A **rate limiter** prevents the multiplexed stream from saturating the receiver’s buffer.

## Frequently Asked Questions

### Does croc open a new connection for each file?

No. Croc opens exactly **one TCP connection** per transfer session. All files are broken into chunks and streamed over this single encrypted socket using the `Message` protocol with unique transfer IDs to keep data separated.

### What encryption protects the multiplexed stream?

Croc uses **PAKE (Password-Authenticated Key Exchange)** to negotiate a shared secret, then encrypts every `Message` using that key with AES-256-GCM via the methods in [`src/comm/comm.go`](https://github.com/schollz/croc/blob/main/src/comm/comm.go). This ensures that even though chunks from different files are interleaved on the wire, they remain unreadable to anyone without the session key.

### Can I disable multiplexing if transfers fail?

Yes. Use the **`--no-multi`** command-line flag when sending. This forces croc to transfer files sequentially (one at a time) rather than interleaving chunks, which can resolve issues with restrictive firewalls or broken middleboxes that cannot handle out-of-order application data.

### How does croc prevent chunks from different files getting mixed up?

Each chunk is wrapped in a `Message` struct that carries a unique **string ID** identifying its parent file. The receiver maintains a mapping of these IDs to open file handles. When a chunk arrives, croc inspects the `ID` field and writes the payload to the corresponding destination, ensuring that data never crosses between files even when chunks arrive out of order.