RapiDrop uses a lightweight, binary framing protocol over direct TCP socket connections (default port 58240).
Every network frame begins with a fixed 28-byte header encoded in Big-Endian (Network Byte Order):
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Payload Length (4B) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Packet Type (2B) | Protocol Version (2B) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Timestamp (8B, UInt64) +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Initialization Vector (12B) +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Ciphertext Payload (Variable) |
| ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Authentication Tag (16B) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Field | Offset | Length | Type | Description |
|---|---|---|---|---|
| Payload Length | 0..3 |
4 Bytes |
UInt32 (BE) |
Length of ciphertext payload + 16-byte auth tag ( |
| Packet Type | 4..5 |
2 Bytes |
UInt16 (BE) |
Opcode identifier (see Packet Type table below). |
| Protocol Version | 6..7 |
2 Bytes |
UInt16 (BE) |
Canonical protocol version: 0x0001. |
| Timestamp | 8..15 |
8 Bytes |
UInt64 (BE) |
Unix timestamp in milliseconds when frame was sent. |
| Nonce (IV) | 16..27 |
12 Bytes | Raw Bytes | Cryptographically random initialization vector per frame. |
| Ciphertext | 28..N-16 |
Variable | Encrypted Data | AES-256-GCM encrypted payload. |
| Auth Tag | N-15..N |
16 Bytes | Raw Bytes | 128-bit Galois Message Authentication Code. |
| Opcode | Hex | Name | Payload Content | Purpose |
|---|---|---|---|---|
1 |
0x0001 |
PING |
Empty | Keepalive heartbeat probe |
2 |
0x0002 |
PONG |
Empty | Keepalive heartbeat response |
3 |
0x0003 |
PAIR_REQUEST |
JSON Envelope | Initial pairing request probe |
4 |
0x0004 |
PAIR_CONFIRM |
JSON Envelope | Bidirectional handshake confirmation |
5 |
0x0005 |
DEVICE_INFO |
JSON (Encrypted) | Hardware model, OS name, and device name exchange |
6 |
0x0006 |
PAIR_INVITE |
JSON Envelope | Interactive AirDrop-style invitation with ephemeral key |
7 |
0x0007 |
PAIR_FAIL |
JSON Envelope | Pairing decline or user cancellation signal |
8 |
0x0008 |
PAIR_ACCEPT |
JSON Envelope | Pairing acceptance with ephemeral public key |
16 |
0x0010 |
CLIP_TEXT |
UTF-8 Text (Encrypted) | Synchronized plain text clipboard item |
17 |
0x0011 |
CLIP_URL |
UTF-8 URL (Encrypted) | Synchronized web link clipboard item |
18 |
0x0012 |
CLIP_IMAGE |
2B Name Len + Name + Image | Synchronized screenshot/image (PNG/JPEG) |
19 |
0x0013 |
CLIP_FILE |
2B Name Len + Name + Data | Small inline binary file item |
20 |
0x0014 |
FILE_START |
JSON (Encrypted) | Initiates chunked streaming file transfer |
21 |
0x0015 |
FILE_CHUNK |
28B Header + 1 MB Data | Discrete chunk slice of active streaming transfer |
22 |
0x0016 |
FILE_END |
JSON (Encrypted) | Signals streaming completion with SHA-256 hash |
23 |
0x0017 |
FILE_CANCEL |
JSON (Encrypted) | Aborts streaming transfer and triggers cleanup |
32 |
0x0020 |
CONFIG_SYNC |
JSON (Encrypted) | Synchronizes direct mode / privacy preferences |
255 |
0x00FF |
DISCONNECT |
UTF-8 String (Encrypted) | Unpair / graceful session termination |
Device A (Initiator) Device B (Receiver)
│ │
Generate Ephemeral (a, A, N_A) Generate Ephemeral (b, B, N_B)
│ │
│─── PAIR_INVITE (A, N_A, NameA) ──────►│
│ Display Invite Dialog
│ User Taps "Accept"
│◄── PAIR_ACCEPT (B, N_B, NameB) ───────│
│ │
Compute S = X25519(a, B) Compute S = X25519(b, A)
Extract & Expand Handshake Keys Extract & Expand Handshake Keys
│ │
│─── DEVICE_INFO (Encrypted) ──────────►│
│◄── DEVICE_INFO (Encrypted) ───────────│
│ │
[ Trusted Session Established ] [ Trusted Session Established ]
Files are streamed in discrete 1 MB (1,048,576 bytes) slices:
FILE_START(0x0014):{ "transferId": "550e8400-e29b-41d4-a716-446655440000", "fileIndex": 0, "totalFiles": 1, "fileName": "archive.zip", "relativePath": "archive.zip", "fileSize": 104857600, "totalBytes": 104857600 }FILE_CHUNK(0x0015) (28-byte Header + Chunk Payload):0..15: 16-byte Transfer UUID16..19: 4-bytefileIndex(UInt32BE)20..27: 8-bytechunkIndex(UInt64BE)28..N: Chunk data bytes (up to 1 MB)
FILE_END(0x0016):{ "transferId": "550e8400-e29b-41d4-a716-446655440000", "fileIndex": 0, "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "status": "OK" }FILE_CANCEL(0x0017):{ "transferId": "550e8400-e29b-41d4-a716-446655440000", "reason": "user_cancelled" }
All protocol timeouts, window limits, and constants are single-sourced on WireFrame:
| Constant | Value | Description |
|---|---|---|
DEFAULT_PORT |
58240 |
Default listening TCP port for server sockets |
DEFAULT_CLIENT_PORT |
58241 |
Default listening TCP port for reverse client sockets |
SERVICE_TYPE |
_clipsync._tcp |
Primary mDNS service advertisement type |
CLIENT_SERVICE_TYPE |
_clipsync-cli._tcp |
Secondary mDNS service advertisement type |
MAX_PAYLOAD_SIZE |
104,857,600 (100 MB) |
Strict upper bound on single non-streamed frames |
STREAMING_CHUNK_SIZE |
1,048,576 (1 MB) |
Fixed slice size for FILE_CHUNK transmissions |
CHUNK_HEADER_SIZE |
28 Bytes |
Binary header prefixing each raw chunk payload |
MAX_IN_FLIGHT_CHUNKS |
4 |
Maximum unacknowledged chunks allowed in pipelined flow control |
INACTIVITY_TIMEOUT_MS |
16,000 ms |
Connection drop threshold if no frame or heartbeat arrives |
WATCHDOG_INTERVAL_MS |
5,000 ms |
Interval for connection health and socket watchdog checks |
HEARTBEAT_INTERVAL_MS |
4,000 ms |
Periodic PING frame transmission interval |
PAIRING_INVITE_TIMEOUT_MS |
30,000 ms |
Timeout before unaccepted pairing invites expire |
To prevent captured handshake packets from being replayed on local subnets:
- Handshake frames (
PAIR_INVITEandPAIR_ACCEPT) validate the 8-byte Unix timestamp in the binary header. - Frames with
|now - frame.timestamp| > 60,000ms (60 seconds) are dropped immediately and the socket connection is aborted.